From d3b0e92517db9447aef600f4ed2debcf4ae2b389 Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Wed, 8 Mar 2023 15:45:06 +0800 Subject: [PATCH] almost finish CKStateChunk read function --- Documents/Recorder.txt | 2 +- LibCmo/CKDefines.hpp | 6 + LibCmo/CKFile.cpp | 7 +- LibCmo/CKFile.hpp | 2 +- LibCmo/CKFileReader.cpp | 13 +- LibCmo/CKObjects.hpp | 3 +- LibCmo/CKStateChunk.cpp | 271 ++++++++++++++++++++++++++++++++++++++-- LibCmo/CKStateChunk.hpp | 6 +- 8 files changed, 290 insertions(+), 20 deletions(-) diff --git a/Documents/Recorder.txt b/Documents/Recorder.txt index a663d93..e67b8d1 100644 --- a/Documents/Recorder.txt +++ b/Documents/Recorder.txt @@ -288,7 +288,7 @@ struct CKStateChunk { CKFile* m_BindFile; DWORD m_unknow; }; -struct CKStateChunkRemap { // 0x40 count 16 +struct ChunkIteratorData { // 0x40 count 16 DWORD m_ChunkVersion; DWORD* m_pData; DWORD m_DataDwordSize; diff --git a/LibCmo/CKDefines.hpp b/LibCmo/CKDefines.hpp index 0c7af81..b301a6e 100644 --- a/LibCmo/CKDefines.hpp +++ b/LibCmo/CKDefines.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace LibCmo { namespace CK2 { @@ -30,6 +31,11 @@ namespace LibCmo { using XObjectArray = std::vector; //using CKObjectArray = std::vector; + // some print macro define +#define PRICKdword PRIu32 +#define PRICKword PRIu16 +#define PRICKbyte PRIu8 +#define PRICKint PRIi32 // forward decl for some CKObjects namespace CKObjectImplements { diff --git a/LibCmo/CKFile.cpp b/LibCmo/CKFile.cpp index f868d57..ef36785 100644 --- a/LibCmo/CKFile.cpp +++ b/LibCmo/CKFile.cpp @@ -63,12 +63,12 @@ namespace LibCmo::CK2 { #pragma region CKFileObject CKFileObject::CKFileObject() : - ObjectId(0u), ObjectCid(CK_CLASSID::CKCID_OBJECT), Name(), + ObjectId(0u), CreatedObject(0u), ObjectCid(CK_CLASSID::CKCID_OBJECT), Name(), ObjPtr(nullptr), Data(nullptr), FileIndex(0u) { } CKFileObject::CKFileObject(const CKFileObject& rhs) : - ObjectId(rhs.ObjectId), ObjectCid(rhs.ObjectCid), Name(rhs.Name), + ObjectId(rhs.ObjectId), CreatedObject(rhs.CreatedObject), ObjectCid(rhs.ObjectCid), Name(rhs.Name), ObjPtr(rhs.ObjPtr), Data(rhs.Data), FileIndex(rhs.FileIndex) { // CKObject is managed by CKMinContext, so we just copy its pointer. @@ -81,6 +81,7 @@ namespace LibCmo::CK2 { CKFileObject& CKFileObject::operator=(const CKFileObject& rhs) { this->ObjectId = rhs.ObjectId; + this->CreatedObject = rhs.CreatedObject; this->ObjectCid = rhs.ObjectCid; this->Name = rhs.Name; this->FileIndex = rhs.FileIndex; @@ -173,7 +174,7 @@ namespace LibCmo::CK2 { #pragma region CKFile Misc CKFile::CKFile(CKMinContext* ctx) : - m_FileName(), m_MinCtx(ctx) { + m_MinCtx(ctx) { ; } diff --git a/LibCmo/CKFile.hpp b/LibCmo/CKFile.hpp index 3382fc0..953c30b 100644 --- a/LibCmo/CKFile.hpp +++ b/LibCmo/CKFile.hpp @@ -89,6 +89,7 @@ namespace LibCmo::CK2 { ~CKFileObject(); CK_ID ObjectId; // ID of the object being load/saved (as it will be/was saved in the file) + CK_ID CreatedObject; // ID of the object being created CK_CLASSID ObjectCid; // Class Identifier of the object CKObjectImplements::CKObject* ObjPtr; // A pointer to the object itself (as CreatedObject when loading) std::string Name; // Name of the Object @@ -186,7 +187,6 @@ namespace LibCmo::CK2 { // writer function and varibales // shared function and variables - std::string m_FileName; CKMinContext* m_MinCtx; }; diff --git a/LibCmo/CKFileReader.cpp b/LibCmo/CKFileReader.cpp index fc12847..fb02847 100644 --- a/LibCmo/CKFileReader.cpp +++ b/LibCmo/CKFileReader.cpp @@ -27,7 +27,7 @@ namespace LibCmo::CK2 { return CKERROR::CKERR_OUTOFMEMORY; } if (!mappedFile->IsValid()) { - this->m_MinCtx->Printf("Fail to create Memory File for \"%s\".", this->m_FileName.c_str()); + this->m_MinCtx->Printf("Fail to create Memory File for \"%s\".", u8_filename); return CKERROR::CKERR_INVALIDFILE; } @@ -270,7 +270,7 @@ namespace LibCmo::CK2 { } // read statechunk - mgr.Data = new(std::nothrow) CKStateChunk(); + mgr.Data = new(std::nothrow) CKStateChunk(doc, this->m_MinCtx); if (mgr.Data != nullptr) { stateChkParseSuccess = mgr.Data->ConvertFromBuffer(parser->GetPtr()); if (!stateChkParseSuccess) { @@ -298,7 +298,7 @@ namespace LibCmo::CK2 { } // read state chunk - obj.Data = new(std::nothrow) CKStateChunk(); + obj.Data = new(std::nothrow) CKStateChunk(doc, this->m_MinCtx); if (obj.Data != nullptr) { stateChkParseSuccess = obj.Data->ConvertFromBuffer(parser->GetPtr()); if (!stateChkParseSuccess) { @@ -370,7 +370,13 @@ namespace LibCmo::CK2 { // todo: resolve references if (obj.Data == nullptr) continue; + // create object and assign created obj ckid obj.ObjPtr = m_MinCtx->CreateCKObject(obj.ObjectId, obj.ObjectCid, obj.Name.c_str()); + if (obj.ObjPtr == nullptr) { + obj.CreatedObject = 0u; + } else { + obj.CreatedObject = obj.ObjPtr->m_ID; + } } // ========== CKStateChunk remap ========== @@ -397,6 +403,7 @@ namespace LibCmo::CK2 { // if failed, delete it m_MinCtx->DestroyCKObject(obj.ObjectId); obj.ObjPtr = nullptr; + obj.CreatedObject = 0u; } } diff --git a/LibCmo/CKObjects.hpp b/LibCmo/CKObjects.hpp index 7984b76..f0b6281 100644 --- a/LibCmo/CKObjects.hpp +++ b/LibCmo/CKObjects.hpp @@ -6,13 +6,12 @@ namespace LibCmo::CK2::CKObjectImplements { class CKObject { - protected: + public: CK_ID m_ID; std::string m_Name; CK_OBJECT_FLAGS m_ObjectFlags; CKMinContext* m_Context; - public: CKObject(CKMinContext* ctx, CK_ID ckid, CKSTRING name); CKObject(const CKObject&) = delete; CKObject& operator=(const CKObject&) = delete; diff --git a/LibCmo/CKStateChunk.cpp b/LibCmo/CKStateChunk.cpp index cf118b7..cc86abb 100644 --- a/LibCmo/CKStateChunk.cpp +++ b/LibCmo/CKStateChunk.cpp @@ -1,15 +1,27 @@ #include "VTUtils.hpp" #include "CKStateChunk.hpp" +#include "CKMinContext.hpp" +#include "CKFile.hpp" namespace LibCmo::CK2 { #pragma region Ctor Dtor - CKStateChunk::CKStateChunk() : + //CKStateChunk::CKStateChunk() : + // m_ClassId(CK_CLASSID::CKCID_OBJECT), m_DataDwSize(0u), m_pData(nullptr), + // m_DataVersion(CK_STATECHUNK_DATAVERSION::CHUNKDATA_CURRENTVERSION), m_ChunkVersion(CK_STATECHUNK_CHUNKVERSION::CHUNK_VERSION4), + // m_Parser{ CKStateChunkStatus::IDLE, 0u, 0u, 0u }, + // m_ObjectList(), m_ChunkList(), m_ManagerList(), + // m_BindDoc(nullptr), m_BindContext(nullptr) + //{ + // ; + //} + CKStateChunk::CKStateChunk(CKFileDocument* doc, CKMinContext* ctx) : m_ClassId(CK_CLASSID::CKCID_OBJECT), m_DataDwSize(0u), m_pData(nullptr), m_DataVersion(CK_STATECHUNK_DATAVERSION::CHUNKDATA_CURRENTVERSION), m_ChunkVersion(CK_STATECHUNK_CHUNKVERSION::CHUNK_VERSION4), m_Parser{ CKStateChunkStatus::IDLE, 0u, 0u, 0u }, - m_ObjectList(), m_ChunkList(), m_ManagerList() + m_ObjectList(), m_ChunkList(), m_ManagerList(), + m_BindDoc(doc), m_BindContext(ctx) { ; } @@ -17,7 +29,8 @@ namespace LibCmo::CK2 { m_ClassId(rhs.m_ClassId), m_DataVersion(rhs.m_DataVersion), m_ChunkVersion(rhs.m_ChunkVersion), m_Parser(rhs.m_Parser), m_ObjectList(rhs.m_ObjectList), m_ManagerList(rhs.m_ManagerList), m_ChunkList(rhs.m_ChunkList), - m_pData(nullptr), m_DataDwSize(rhs.m_DataDwSize) { + m_pData(nullptr), m_DataDwSize(rhs.m_DataDwSize), + m_BindDoc(rhs.m_BindDoc), m_BindContext(rhs.m_BindContext) { // copy buffer if (rhs.m_pData != nullptr) { this->m_pData = new(std::nothrow) CKDWORD[rhs.m_DataDwSize]; @@ -39,6 +52,9 @@ namespace LibCmo::CK2 { this->m_ManagerList = rhs.m_ManagerList; this->m_ChunkList = rhs.m_ChunkList; + this->m_BindDoc = rhs.m_BindDoc; + this->m_BindContext = rhs.m_BindContext; + // copy buffer if (rhs.m_pData != nullptr) { this->m_pData = new(std::nothrow) CKDWORD[rhs.m_DataDwSize]; @@ -408,7 +424,11 @@ namespace LibCmo::CK2 { if (this->EnsureReadSpace(size_in_dword)) { std::memcpy(data_ptr, this->m_pData + this->m_Parser.m_CurrentPos, size_in_byte); return true; - } else return false; + } else { + // failed, report to context + m_BindContext->Printf("CKStateChunk read length error at %" PRICKdword ".", this->m_Parser.m_CurrentPos); + return false; + } } bool CKStateChunk::ReadString(std::string* strl) { @@ -433,14 +453,147 @@ namespace LibCmo::CK2 { /* ========== Complex Data Read Functions ==========*/ bool CKStateChunk::ReadObjectID(CK_ID* id) { + if (id == nullptr) return false; + + // get basic value + CKINT gotten_id = 0; + if (!this->ReadStruct(gotten_id)) return false; + + // different strategy according to chunk ver + if (this->m_ChunkVersion >= CK_STATECHUNK_CHUNKVERSION::CHUNK_VERSION1) { + // new file + + // if no doc associated, return directly + if (this->m_BindDoc == nullptr) { + *id = static_cast(gotten_id); + return true; + } + // if it is positive, return corresponding value + if (gotten_id >= 0) { + *id = this->m_BindDoc->m_FileObjects[gotten_id].CreatedObject; + return true; + } + + } else { + // old file + // i don't know why I need skip 2 DWORD + // just copy IDA code. + + if (gotten_id) { + this->Skip(2); + return this->ReadStruct(id); + } + } + + // all failed + *id = 0u; return false; } bool CKStateChunk::ReadManagerInt(CKGUID* guid, CKINT* intval) { - return false; + if (guid == nullptr || intval == nullptr) return false; + + // read guid first + if (!this->ReadStruct(guid)) return false; + // then read int value + if (!this->ReadStruct(intval)) return false; + + return true; } CKStateChunk* CKStateChunk::ReadSubChunk(void) { + CKStateChunk* subchunk = nullptr; + + // get size and do a enough space check + CKDWORD subChunkSize; + if (!this->ReadStruct(subChunkSize)) goto subchunk_defer; + if (!this->EnsureReadSpace(subChunkSize)) goto subchunk_defer; + + // create statechunk + subchunk = new(std::nothrow) CKStateChunk(this->m_BindDoc, this->m_BindContext); + if (subchunk == nullptr) goto subchunk_defer; + + // start read data + // read class id + if (!this->ReadStruct(subchunk->m_ClassId)) goto subchunk_defer; + + // different read strategy by chunk version + if (this->m_ChunkVersion >= CK_STATECHUNK_CHUNKVERSION::CHUNK_VERSION1) { + // new file + + // read combined version + CKDWORD versionInfo; + if (!this->ReadStruct(versionInfo)) goto subchunk_defer; + subchunk->m_DataVersion = static_cast(versionInfo & 0xffff); + subchunk->m_ChunkVersion = static_cast((versionInfo >> 16) & 0xffff); + + // read data size and create it + if (!this->ReadStruct(subchunk->m_DataDwSize)) goto subchunk_defer; + subchunk->m_pData = new(std::nothrow) CKDWORD[subchunk->m_DataDwSize]; + if (subchunk->m_pData == nullptr) goto subchunk_defer; + + // has bind file? + CKDWORD hasBindFile; + if (!this->ReadStruct(hasBindFile)) goto subchunk_defer; + if (hasBindFile == 1) subchunk->m_BindDoc = nullptr; + + // 3 list size + // manager only existed when ver > 4 + CKDWORD lssize; + if (!this->ReadStruct(lssize)) goto subchunk_defer; + subchunk->m_ObjectList.resize(lssize); + if (!this->ReadStruct(lssize)) goto subchunk_defer; + subchunk->m_ChunkList.resize(lssize); + if (this->m_ChunkVersion > CK_STATECHUNK_CHUNKVERSION::CHUNK_VERSION1) { + if (!this->ReadStruct(lssize)) goto subchunk_defer; + subchunk->m_ManagerList.resize(lssize); + } + + // core data + if (subchunk->m_DataDwSize != 0) { + if (!this->ReadByteData(subchunk->m_pData, subchunk->m_DataDwSize * sizeof(CKDWORD))) goto subchunk_defer; + } + + // 3 list data + if (!subchunk->m_ObjectList.empty()) { + if (!this->ReadByteData( + subchunk->m_ObjectList.data(), + subchunk->m_ObjectList.size() * sizeof(CKDWORD) + )) goto subchunk_defer; + } + if (!subchunk->m_ChunkList.empty()) { + if (!this->ReadByteData( + subchunk->m_ChunkList.data(), + subchunk->m_ChunkList.size() * sizeof(CKDWORD) + )) goto subchunk_defer; + } + if (!subchunk->m_ManagerList.empty()) { + if (!this->ReadByteData( + subchunk->m_ManagerList.data(), + subchunk->m_ManagerList.size() * sizeof(CKDWORD) + )) goto subchunk_defer; + } + + } else { + // old file + + // read data size and create it + if (!this->ReadStruct(subchunk->m_DataDwSize)) goto subchunk_defer; + subchunk->m_pData = new(std::nothrow) CKDWORD[subchunk->m_DataDwSize]; + if (subchunk->m_pData == nullptr) goto subchunk_defer; + + // skip one? + // I don't know why + this->Skip(1u); + + // read core buf + if (!this->ReadByteData(subchunk->m_pData, subchunk->m_DataDwSize * sizeof(CKDWORD))) goto subchunk_defer; + + } + + return subchunk; + subchunk_defer: + if (subchunk != nullptr) delete subchunk; return nullptr; } @@ -484,19 +637,119 @@ namespace LibCmo::CK2 { /* ========== Sequence Functions ==========*/ bool CKStateChunk::ReadObjectIDSequence(std::vector* ls) { - return false; + if (ls == nullptr) return false; + ls->clear(); + + // read count + CKDWORD count; + if (!this->ReadStruct(count)) return false; + + // resize list and read it + ls->resize(count); + for (size_t i = 0; i < count; ++i) { + if (!this->ReadObjectID(ls->data() + i)) { + ls->clear(); + return false; + } + } + + return true; } bool CKStateChunk::ReadManagerIntSequence(CKGUID* guid, std::vector* ls) { - return false; + if (guid == nullptr || ls == nullptr) return false; + + // read count + CKDWORD count; + if (!this->ReadStruct(count)) return false; + + // read guid + if (!this->ReadStruct(guid)) return false; + + // resize list and read it + ls->resize(count); + for (size_t i = 0; i < count; ++i) { + if (!this->ReadStruct(ls->data() + i)) { + ls->clear(); + return false; + } + } + + return true; } bool CKStateChunk::ReadSubChunkSequence(std::vector* ls) { - return false; + if (ls == nullptr) return false; + + // clear first + for (auto& item : *ls) { + if (item != nullptr) + delete (item); + } + ls->clear(); + + // read count + CKDWORD count; + if (!this->ReadStruct(count)) return false; + + // resize list and read it + ls->resize(count, nullptr); + for (size_t i = 0; i < count; ++i) { + (*ls)[i] = this->ReadSubChunk(); + if ((*ls)[i] == nullptr) { + // fail. remove all created statechunk and clear it + for (auto& item : *ls) { + if (item != nullptr) + delete (item); + } + ls->clear(); + // return + return false; + } + } + + return true; } bool CKStateChunk::ReadObjectArray(std::vector* ls) { - return false; + if (ls == nullptr) return false; + ls->clear(); + + // read count + CKDWORD count; + if (!this->ReadStruct(count)) return false; + if (!count) return true; // 0 size array + + // old file size correction + if (this->m_ChunkVersion < CK_STATECHUNK_CHUNKVERSION::CHUNK_VERSION1) { + // skip 4. but I don't know why!!! + this->Skip(4); + if (!this->ReadStruct(count)) return false; + } + + // resize list and read + ls->resize(count); + for (auto& id : *ls) { + // read ID first + CKINT cache; + if (!this->ReadStruct(cache)) { + ls->clear(); + return false; + } + + // remap id + if (this->m_BindDoc != nullptr) { + if (cache < 0) { + id = 0u; + } else { + id = this->m_BindDoc->m_FileObjects[cache].CreatedObject; + } + } else { + id = static_cast(cache); + } + } + + return true; } #pragma endregion diff --git a/LibCmo/CKStateChunk.hpp b/LibCmo/CKStateChunk.hpp index a416ecb..1cacfa4 100644 --- a/LibCmo/CKStateChunk.hpp +++ b/LibCmo/CKStateChunk.hpp @@ -9,7 +9,8 @@ namespace LibCmo::CK2 { class CKStateChunk { public: - CKStateChunk(); + //CKStateChunk(); + CKStateChunk(CKFileDocument* doc, CKMinContext* ctx); CKStateChunk(const CKStateChunk&); CKStateChunk& operator=(const CKStateChunk&); ~CKStateChunk(); @@ -39,6 +40,9 @@ namespace LibCmo::CK2 { std::vector m_ChunkList; std::vector m_ManagerList; + CKFileDocument* m_BindDoc; + CKMinContext* m_BindContext; + #pragma region Buffer Related public: