diff --git a/LibCmo/CK2/CKFile.hpp b/LibCmo/CK2/CKFile.hpp index 4e35d33..27b15e5 100644 --- a/LibCmo/CK2/CKFile.hpp +++ b/LibCmo/CK2/CKFile.hpp @@ -173,6 +173,7 @@ namespace LibCmo::CK2 { CKFileVisitor& operator=(CKFileVisitor&&); const CKFileObject* GetFileObjectByIndex(size_t index); + CKDWORD GetIndexByObjectID(CK_ID objid); protected: bool m_IsReader; CKFileReader* m_Reader; @@ -226,7 +227,7 @@ namespace LibCmo::CK2 { // ========== Saving Preparing ========== bool AddSavedObject(ObjImpls::CKObject* obj, CKDWORD flags = CK_STATESAVE_ALL); - bool AddSavedObjects(CKObjectArray* objarray, CKDWORD flags = CK_STATESAVE_ALL); + bool AddSavedObjects(const XContainer::XObjectPointerArray& objarray, CKDWORD flags = CK_STATESAVE_ALL); bool AddSavedFile(CKSTRING u8FileName); // ========== Saving ========== @@ -248,6 +249,8 @@ namespace LibCmo::CK2 { XContainer::XArray m_ManagersData; /**< Manager Data loaded */ XContainer::XArray m_PluginsDep; /**< Plugins dependencies for this file */ XContainer::XArray m_IncludedFiles; /**< List of files that should be inserted in the CMO file. */ + XContainer::XHashTable m_ObjectsHashTable; /**< A Object ID to save index hash table. */ + XContainer::XBitArray m_AlreadySavedMask; /**< Field recording saved object id. If this object is saved, set m_AlreadySavedMask[id] to true. Also used to check whether object already is in save list. */ CKFileInfo m_FileInfo; /**< Headers summary */ CKERROR PrepareFile(CKSTRING filename); diff --git a/LibCmo/CK2/CKFileOthers.cpp b/LibCmo/CK2/CKFileOthers.cpp index 8d2019b..29ebce8 100644 --- a/LibCmo/CK2/CKFileOthers.cpp +++ b/LibCmo/CK2/CKFileOthers.cpp @@ -234,17 +234,47 @@ namespace LibCmo::CK2 { bool CKFileWriter::AddSavedObject(ObjImpls::CKObject* obj, CKDWORD flags) { if (m_Done || m_IsCopyFromReader) return false; - return false; + if (obj == nullptr) return false; + + // check whether is saved. + CK_ID objid = obj->GetID(); + if (XContainer::NSXBitArray::IsSet(m_AlreadySavedMask, static_cast(objid))) return false; + + // ok, insert this value + m_ObjectsHashTable.try_emplace(objid, static_cast(m_FileObjects.size())); + + XContainer::NSXBitArray::Set(m_AlreadySavedMask, static_cast(objid)); + + CKFileObject fobj; + fobj.ObjectId = objid; + fobj.ObjPtr = obj; + fobj.ObjectCid = obj->GetClassID(); + fobj.SaveFlags = flags; + XContainer::NSXString::FromCKSTRING(fobj.Name, obj->GetName()); + m_FileObjects.emplace_back(std::move(fobj)); + + return true; } - bool CKFileWriter::AddSavedObjects(CKObjectArray* objarray, CKDWORD flags) { + bool CKFileWriter::AddSavedObjects(const XContainer::XObjectPointerArray& objarray, CKDWORD flags) { if (m_Done || m_IsCopyFromReader) return false; - return false; + + bool ret = true; + for (auto obj : objarray) { + if (!AddSavedObject(obj, flags)) { + ret = false; + } + } + + return ret; } bool CKFileWriter::AddSavedFile(CKSTRING u8FileName) { if (m_Done || m_IsCopyFromReader) return false; - return false; + if (u8FileName == nullptr) return false; + + m_IncludedFiles.emplace_back(u8FileName); + return true; } #pragma endregion @@ -294,6 +324,16 @@ namespace LibCmo::CK2 { } } + CKDWORD CKFileVisitor::GetIndexByObjectID(CK_ID objid) { + // see CKFile::SaveFindObjectIndex in IDA + CKDWORD idx = -1; + if (m_IsReader) return idx; + + auto finder = m_Writer->m_ObjectsHashTable.find(objid); + if (finder == m_Writer->m_ObjectsHashTable.end()) return idx; + return finder->second; + } + #pragma endregion } diff --git a/LibCmo/CK2/CKStateChunk.hpp b/LibCmo/CK2/CKStateChunk.hpp index 6cb1064..5dc4fd6 100644 --- a/LibCmo/CK2/CKStateChunk.hpp +++ b/LibCmo/CK2/CKStateChunk.hpp @@ -6,6 +6,17 @@ namespace LibCmo::CK2 { + /** + * @remark + * + We make sure m_BindContext and m_BindFile always are not nullptr. So some code of BindFile check and write different data struct has been removed. + * + Calling StartRead multiple times is illegal. + * - The solution is that use StartRead and StopRead to warp the real CKStateChunk consumer. And just calling read functions in real consumer directly. + * - See CKFileReader for more infomation. + * + Same as StartRead, calling StartWrite multiple times also is illegal. We also remove CKStateChunk merge function, AddChunkAndDelete and AddChunk. + * - The solution is same as StartRead solution. Use StartWrite and StopWrite warp the real CKStateChunk writer. Call write function in consumer directly. + * - Every inherited CKObject::Save must call SetClassId at the end of function if they have data to write. + * - See CKFileWrite for more infomation. + */ class CKStateChunk { public: CKStateChunk(CKFileVisitor* visitor, CKContext* ctx); @@ -50,7 +61,7 @@ namespace LibCmo::CK2 { CKStateChunk* m_Host; CKDWORD m_ConsumedSize; }; - + class LockedWriteBufferDeleter { public: LockedWriteBufferDeleter() : m_Host(nullptr), m_ConsumedSize(0) {} @@ -64,11 +75,11 @@ namespace LibCmo::CK2 { CKStateChunk* m_Host; CKDWORD m_ConsumedSize; }; - + class BufferDeleter { public: BufferDeleter() : m_Host(nullptr), m_BufSize(0) {} - BufferDeleter(CKStateChunk* host, CKDWORD bufsize) : + BufferDeleter(CKStateChunk* host, CKDWORD bufsize) : m_Host(host), m_BufSize(bufsize) {} LIBCMO_DEFAULT_COPY_MOVE(BufferDeleter); @@ -137,7 +148,7 @@ namespace LibCmo::CK2 { */ const ProfileStateChunk_t GetStateChunkProfile(); /** - * @brief Get all indentifier infos of this CKStateChunk, + * @brief Get all indentifier infos of this CKStateChunk, * including identifier self, data area size and address. * @return A arrary, each item describe a single identifier's info. * @remark The detail of implement can be seen in SeekIdentifierAndReturnSize() @@ -184,12 +195,12 @@ namespace LibCmo::CK2 { private: /** * @brief Convert byte based size to DWORD based size. - * + * * Becase CKStateChunk use DWORD based buffer, so all data should be aligned to DWORD boundary. * This function can convert byte based size to DWORD based size while keeping its size aligned with DWORD boundary. * For example, caller want to allocate 3 bytes for data storing, this function will first align it to DWORD boundary, 4 bytes. * Then convert it in DWORD size, 1 DWORD. - * + * * @param char_size[in] The size in byte unit. * @return The size in DWORD unit. */ @@ -197,13 +208,13 @@ namespace LibCmo::CK2 { bool ResizeBuffer(CKDWORD new_dwsize); /** * @brief Check whether there are enough buffer to read. - * - * This function will check whether current CKStateChunk is in read mode and + * + * This function will check whether current CKStateChunk is in read mode and * whether data area is enough to write. * However, it is different with EnsureReadSpace. If no space to write, this function will - * try calling ResizeBuffer to get a enough buffer. Only when resize failed, + * try calling ResizeBuffer to get a enough buffer. Only when resize failed, * this function will return false. - * + * * @param dwsize[in] Required buffer size in DWORD unit. * @return True if have enough space to write. * @see EnsureReadSpace @@ -211,10 +222,10 @@ namespace LibCmo::CK2 { bool EnsureWriteSpace(CKDWORD dwsize); /** * @brief Check whether there are enough buffer to read. - * - * This function will check whether current CKStateChunk is in read mode and + * + * This function will check whether current CKStateChunk is in read mode and * whether data area is enough to read. - * + * * @param dword_required[in] Required buffer size in DWORD unit. * @return True if have enough space to read. * @see EnsureWriteSpace @@ -231,6 +242,7 @@ namespace LibCmo::CK2 { /* ========== Identifier Functions ==========*/ + public: bool SeekIdentifierDword(CKDWORD identifier); bool SeekIdentifierDwordAndReturnSize(CKDWORD identifier, CKDWORD* out_size); template @@ -281,7 +293,7 @@ namespace LibCmo::CK2 { * @remark Here is a example. * ``` * auto buf = chunk->LockReadBufferWrapper(1919810); - * if (buf) { + * if (buf) { * stuff(buf.get()); // do some operation... * buf.get_deleter().SetConsumedSize(114514); // i only consume these bytes. * buf.reset(); // immediately free it. @@ -290,7 +302,7 @@ namespace LibCmo::CK2 { * @see LockReadBuffer, UnLockReadBuffer, LockedReadBuffer_t */ LockedReadBuffer_t LockReadBufferWrapper(CKDWORD size_in_byte); - + /* ========== Basic Data Read Functions ==========*/ private: @@ -379,10 +391,10 @@ namespace LibCmo::CK2 { /** * @brief Read buffer and copy it. - * + * * The copied buffer and the size of buffer will be returned to caller. * Caller should free the buffer by calling CKStateChunk::DeleteBuffer(void*). - * + * * @param ppData[out] The pointer to pointer holding the new copied data. * @param size_in_byte[out] Set to the size of buffer when success. * @return True if success. @@ -407,7 +419,7 @@ namespace LibCmo::CK2 { * @remark Here is a exmaple about how to use this function * ``` * Buffer_t buf = chunk->ReadBufferWrapper(114); - * if (buf) { + * if (buf) { * stuff(buf.get(), buf.get_deleter().GetBufferSize()); // do some operation... * buf.reset(); // immediately free it. * } @@ -417,9 +429,9 @@ namespace LibCmo::CK2 { /** * @brief Read buffer and fill user struct. - * + * * The size of buffer will be read from CKStateChunk internally and return to caller. - * + * * @param pData[out] The pointer holding the data. * @return True if success. * @remark Following original Virtools functions can use this function to implement: @@ -430,9 +442,9 @@ namespace LibCmo::CK2 { bool ReadAndFillBuffer(void* pData); /** * @brief Read buffer and fill user struct. - * + * * The size of buffer is provided by user. - * + * * @param pData[out] The pointer holding the data. * @param size_in_byte[in] The size of data which you want to read in byte unit * @return True if success. @@ -502,61 +514,122 @@ namespace LibCmo::CK2 { return ReadXObjectPointerArray(&ls); } - //int ReadInt(); - //int StartReadSequence(); - //CK_ID ReadObjectID(); - //CKStateChunk* ReadSubChunk(); - //int StartManagerReadSequence(CKGUID* guid); - //CKGUID ReadGuid(); - //void ReadAndFillBuffer_LEndian(void* buffer); - //void ReadAndFillBuffer_LEndian16(void* buffer); - //float ReadFloat(); - //CKWORD ReadWord(); - //CKDWORD ReadDword(); - //CKDWORD ReadDwordAsWords(); - //void ReadVector(VxMath::VxVector* v); - //void ReadMatrix(VxMath::VxMatrix& mat); - //CKObjectImplements::CKObject* ReadObject(CKMinContext*); - //void ReadAndFillBuffer(void* buffer); - //CKBYTE* ReadRawBitmap(VxMath::VxImageDescEx& desc); - //XObjectArray ReadXObjectArray(void); - #pragma endregion #pragma region Write Function public: void StartWrite(); - //void WriteIdentifier(CKDWORD id); - //void AddChunkAndDelete(CKStateChunk*); - //void StartObjectIDSequence(int count); - //void WriteObjectSequence(CKObjectImplements::CKObject* obj); - //void WriteInt(int data); - //void WriteFloat(float data); - //void WriteDword(CKDWORD data); - //void WriteDwordAsWords(CKDWORD data); - //void WriteVector(const VxMath::VxVector* v); - //void WriteMatrix(const VxMath::VxMatrix& mat); - //void WriteObject(CKObjectImplements::CKObject* obj); - //void WriteBuffer_LEndian(int size, void* buf); - //void WriteBuffer_LEndian16(int size, void* buf); - //void WriteBufferNoSize_LEndian(int size, void* buf); - ///*void UpdateDataSize();*/ - //void* LockWriteBuffer(int DwordCount); - /* - * Old Name: CloseChunk(); + * Actually this function mix various functions, including CloseChunk(), UpdateSize() and etc. */ void StopWrite(void); - bool LockWriteBuffer(const void** ppData, CKDWORD size_in_byte); + + /* ========== Identifier Functions ==========*/ + + public: + bool WriteIdentifierDword(CKDWORD identifier); + template + inline bool WriteIdentifier(TEnum enum_v) { + return WriteIdentifierDword(static_cast(enum_v)); + } + + /* ========== Write Buffer Controller ==========*/ + + public: + bool LockWriteBuffer(void** ppData, CKDWORD size_in_byte); bool UnLockWriteBuffer(CKDWORD size_in_byte); LockedWriteBuffer_t LockWriteBufferWrapper(CKDWORD size_in_byte); + /* ========== Basic Data Write Functions ==========*/ + + private: + bool WriteByteData(const void* data_ptr, CKDWORD size_in_byte); + + public: + template + bool WriteStruct(const T* data) { + return WriteByteData(data, CKSizeof(T)); + } + template + inline bool WriteStruct(const T& data) { + return WriteByteData(&data, CKSizeof(T)); + } + + bool WriteString(const XContainer::XString* strl); + inline bool WriteString(const XContainer::XString& strl) { + return WriteString(&strl); + } + + + /* ========== Complex Data Read Functions ==========*/ + + public: + bool WriteObjectID(const CK_ID* id); + bool WriteObjectPointer(ObjImpls::CKObject* obj); + inline bool WriteObjectID(const CK_ID& id) { + return WriteObjectID(&id); + } + + bool WriteManagerInt(const CKGUID* guid, CKINT intval); + inline bool WriteManagerInt(const CKGUID& guid, CKINT intval) { + return WriteManagerInt(&guid, intval); + } + + // Sub Chunk not support now. + // Too complex and I even don't use it in my code. + //CKStateChunk* ReadSubChunk(); + + /* ========== Buffer Functions ==========*/ + + /* + Buffer related function implements: + + WriteBuffer(int, void*) Write buffer with size. -> WriteBuffer(const void*, CKDWORD) + WriteBufferNoSize(int, void*) Write buffer without size. -> WriteBufferNoSize(const void*, CKDWORD) + WriteBuffer_LEndian(int, void*) Write buffer with size. -> WriteBuffer(const void*, CKDWORD) + WriteBuffer_LEndian16(int, void*) Write buffer with size. -> WriteBuffer(const void*, CKDWORD) + WriteBufferNoSize_LEndian(int, void*) Write buffer without size. -> WriteBufferNoSize(const void*, CKDWORD) + WriteBufferNoSize_LEndian16(int, void*) Write buffer without size. -> WriteBufferNoSize(const void*, CKDWORD) + */ + + bool WriteBuffer(const void* buf, CKDWORD size_in_byte); + bool WriteBufferNoSize(const void* buf, CKDWORD size_in_byte); + + /* ========== Sequence Functions ==========*/ + + public: + bool WriteObjectIDSequence(const XContainer::XObjectArray* ls); + inline bool ReadObjectIDSequence(const XContainer::XObjectArray& ls) { + return WriteObjectIDSequence(&ls); + } + + bool WriteManagerIntSequence(const CKGUID* guid, const XContainer::XArray* ls); + inline bool WriteManagerIntSequence(const CKGUID& guid, const XContainer::XArray& ls) { + return WriteManagerIntSequence(&guid, &ls); + } + + // Sub chunk is not available now + // Because my code never use it and it is too complex. + //bool ReadSubChunkSequence(XContainer::XArray* ls); + //inline bool ReadSubChunkSequence(XContainer::XArray& ls) { + // return ReadSubChunkSequence(&ls); + //} + + bool WriteXObjectArray(const XContainer::XObjectArray* ls); + inline bool WriteXObjectArray(const XContainer::XObjectArray& ls) { + return WriteXObjectArray(&ls); + } + + bool WriteXObjectPointerArray(const XContainer::XObjectPointerArray* ls); + inline bool WriteXObjectPointerArray(const XContainer::XObjectPointerArray& ls) { + return WriteXObjectPointerArray(&ls); + } + + #pragma endregion - - }; } diff --git a/LibCmo/CK2/CKStateChunkReader.cpp b/LibCmo/CK2/CKStateChunkReader.cpp index 78ca5bb..8171516 100644 --- a/LibCmo/CK2/CKStateChunkReader.cpp +++ b/LibCmo/CK2/CKStateChunkReader.cpp @@ -209,9 +209,9 @@ namespace LibCmo::CK2 { 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; + CKDWORD subDwordChunkSize; + if (!this->ReadStruct(subDwordChunkSize)) goto subchunk_defer; + if (!this->EnsureReadSpace(subDwordChunkSize)) goto subchunk_defer; // create statechunk subchunk = new CKStateChunk(this->m_BindFile, this->m_BindContext); @@ -502,6 +502,7 @@ namespace LibCmo::CK2 { bool CKStateChunk::ReadXObjectPointerArray(XContainer::XObjectPointerArray* ls) { if (ls == nullptr) return false; + ls->clear(); // very very similar to ReadXObjectArray // we execute it first. diff --git a/LibCmo/CK2/CKStateChunkWriter.cpp b/LibCmo/CK2/CKStateChunkWriter.cpp index 143462e..af0b307 100644 --- a/LibCmo/CK2/CKStateChunkWriter.cpp +++ b/LibCmo/CK2/CKStateChunkWriter.cpp @@ -1,6 +1,7 @@ #include "CKStateChunk.hpp" #include "CKFile.hpp" #include "CKContext.hpp" +#include "ObjImpls/CKObject.hpp" namespace LibCmo::CK2 { @@ -46,16 +47,179 @@ namespace LibCmo::CK2 { this->m_Parser.m_Status = CKStateChunkStatus::IDLE; } - bool CKStateChunk::LockWriteBuffer(const void** ppData, CKDWORD size_in_byte) { - return false; + /* ========== Identifier Functions ==========*/ + + bool CKStateChunk::WriteIdentifierDword(CKDWORD identifier) { + // check self status + if (this->m_Parser.m_Status != CKStateChunkStatus::WRITE) return false; + // make sure there are 2 DWORD space for writing identifier header + if (!EnsureWriteSpace(2)) return false; + + // update the last identifier header to fill its length indicator + if (m_Parser.m_PrevIdentifierPos < m_Parser.m_CurrentPos) { + m_pData[m_Parser.m_PrevIdentifierPos + 1] = m_Parser.m_CurrentPos; + } + + // set prev ident to this new created ident + m_Parser.m_PrevIdentifierPos = m_Parser.m_CurrentPos; + // write identifier and set default next ident data + m_pData[m_Parser.m_CurrentPos++] = identifier; + m_pData[m_Parser.m_CurrentPos++] = 0; + return true; + } + + /* ========== Write Buffer Controller ==========*/ + + bool CKStateChunk::LockWriteBuffer(void** ppData, CKDWORD size_in_byte) { + // same as LockReadBuffer with slight difference. + if (this->m_Parser.m_Status != CKStateChunkStatus::WRITE) return false; + if (*ppData == nullptr) return false; + *ppData = nullptr; + + CKDWORD size_in_dword = this->GetCeilDwordSize(size_in_byte); + if (this->EnsureWriteSpace(size_in_dword)) { + *ppData = this->m_pData + this->m_Parser.m_CurrentPos; + return true; + } else { + m_BindContext->OutputToConsoleEx("CKStateChunk::LockWriteBuffer at buffer pos %" PRIuCKDWORD ".", this->m_Parser.m_CurrentPos); + return false; + } } bool CKStateChunk::UnLockWriteBuffer(CKDWORD size_in_byte) { - return false; + // same as UnLockReadBuffer with slight difference. + if (this->m_Parser.m_Status != CKStateChunkStatus::WRITE) return false; + + CKDWORD size_in_dword = this->GetCeilDwordSize(size_in_byte); + if (this->EnsureWriteSpace(size_in_dword)) { + this->m_Parser.m_CurrentPos += size_in_dword; + return true; + } else { + m_BindContext->OutputToConsoleEx("CKStateChunk::UnLockWriteBuffer at buffer pos %" PRIuCKDWORD ".", this->m_Parser.m_CurrentPos); + return false; + } } CKStateChunk::LockedWriteBuffer_t CKStateChunk::LockWriteBufferWrapper(CKDWORD size_in_byte) { - return LockedWriteBuffer_t(); + // same as LockReadBufferWrapper with slight difference. + void* pData; + bool ret = LockWriteBuffer(&pData, size_in_byte); + if (ret) { + return LockedWriteBuffer_t(pData, LockedWriteBufferDeleter(this, size_in_byte)); + } else { + return LockedWriteBuffer_t(); + } + } + + /* ========== Basic Data Write Functions ==========*/ + + bool CKStateChunk::WriteByteData(const void* data_ptr, CKDWORD size_in_byte) { + // same as ReadByteData with slight difference. + if (data_ptr == nullptr) return false; + + void* pData; + bool ret = LockWriteBuffer(&pData, size_in_byte); + if (ret) { + std::memcpy(pData, data_ptr, size_in_byte); + UnLockWriteBuffer(size_in_byte); + return true; + } else { + return false; + } + } + + bool CKStateChunk::WriteString(const XContainer::XString* strl) { + if (strl == nullptr) return; + + // convert encoding + XContainer::XString cache; + m_BindContext->GetNativeString(*strl, cache); + + // get size + CKDWORD strByteSize = static_cast(cache.size()); + if (!this->WriteStruct(strByteSize)) { + return false; + } + + // write data + if (!this->WriteByteData(cache.c_str(), strByteSize)) { + return false; + } + + return true; + } + + bool CKStateChunk::WriteObjectID(const CK_ID* id) { + // MARK: if BindFile is not nullptr, no need to push this obj into obj list according to IDA code. + // but we assume BindFile always it not nullptr, so I remove that pushing code. + return this->WriteStruct(m_BindFile->GetIndexByObjectID(*id)); + } + + bool CKStateChunk::WriteObjectPointer(ObjImpls::CKObject* obj) { + CK_ID objid; + if (obj != nullptr) { + objid = obj->GetID(); + } + + return WriteObjectID(objid); + } + + bool CKStateChunk::WriteManagerInt(const CKGUID* guid, CKINT intval) { + // push into manager list + m_ManagerList.emplace_back(m_Parser.m_CurrentPos); + // write data + if (!this->WriteStruct(guid)) return false; + if (!this->WriteStruct(intval)) return false; + return true; + } + + /* ========== Buffer Functions ==========*/ + + bool CKStateChunk::WriteBuffer(const void* buf, CKDWORD size_in_byte) { + if (buf != nullptr) { + // write size + if (!this->WriteStruct(size_in_byte)) return false; + // write data + auto locker = LockWriteBufferWrapper(size_in_byte); + if (locker == nullptr) return false; + std::memcpy(locker.get(), buf, size_in_byte); + locker.reset(); + } else { + // write blank data + if (!this->WriteStruct(0)) return false; + } + + return true; + } + + bool CKStateChunk::WriteBufferNoSize(const void* buf, CKDWORD size_in_byte) { + if (buf != nullptr) { + // write data + auto locker = LockWriteBufferWrapper(size_in_byte); + if (locker == nullptr) return false; + std::memcpy(locker.get(), buf, size_in_byte); + locker.reset(); + } + // if nosize buffer is nullptr, nothing need to write. + return true; + } + + /* ========== Sequence Functions ==========*/ + + bool CKStateChunk::WriteObjectIDSequence(const XContainer::XObjectArray* ls) { + return false; + } + + bool CKStateChunk::WriteManagerIntSequence(const CKGUID* guid, const XContainer::XArray* ls) { + return false; + } + + bool CKStateChunk::WriteXObjectArray(const XContainer::XObjectArray* ls) { + return false; + } + + bool CKStateChunk::WriteXObjectPointerArray(const XContainer::XObjectPointerArray* ls) { + return false; } }