From d381369ed6c4cefb2aa531b6841ca2f27ca7a26b Mon Sep 17 00:00:00 2001 From: yyc12345 Date: Mon, 18 Sep 2023 23:11:33 +0800 Subject: [PATCH] now can read CKMesh without memory issue --- LibCmo/CK2/CKBitmapData.cpp | 5 +- LibCmo/CK2/CKGlobals.cpp | 2 + LibCmo/CK2/CKStateChunk.hpp | 118 +++++++++++++++++------------- LibCmo/CK2/CKStateChunkReader.cpp | 92 +++++++++++++++-------- LibCmo/CK2/ObjImpls/CKMesh.cpp | 17 ++++- LibCmo/VxMath/VxMath.cpp | 2 +- LibCmo/VxMath/VxMath.hpp | 2 +- 7 files changed, 149 insertions(+), 89 deletions(-) diff --git a/LibCmo/CK2/CKBitmapData.cpp b/LibCmo/CK2/CKBitmapData.cpp index 02e37e8..0b14107 100644 --- a/LibCmo/CK2/CKBitmapData.cpp +++ b/LibCmo/CK2/CKBitmapData.cpp @@ -17,7 +17,7 @@ namespace LibCmo::CK2 { // get ext and guid to find correct guid CKCHAR filerawext[4]; CKGUID fileguid; - chk->ReadNoSizeBuffer(CKSizeof(filerawext), filerawext); + chk->ReadAndFillBuffer(filerawext, CKSizeof(filerawext)); chk->ReadStruct(fileguid); CKFileExtension fileext(filerawext); auto reader = DataHandlers::CKBitmapHandler::GetBitmapHandlerWrapper(fileext, fileguid); @@ -56,7 +56,7 @@ namespace LibCmo::CK2 { VxMath::VxDoAlphaBlit(slot, static_cast(globalalpha)); } else { auto alphabuf = chk->ReadBufferWrapper(); - VxMath::VxDoAlphaBlit(slot, reinterpret_cast(alphabuf.get())); + VxMath::VxDoAlphaBlit(slot, reinterpret_cast(alphabuf.get())); } } @@ -79,7 +79,6 @@ namespace LibCmo::CK2 { // read RGBA buffer CKStateChunk::Buffer_t redBuffer, greenBuffer, blueBuffer, alphaBuffer; - CKDWORD bufsize; CKDWORD bufopt; chk->ReadStruct(bufopt); bufopt &= 0xFu; diff --git a/LibCmo/CK2/CKGlobals.cpp b/LibCmo/CK2/CKGlobals.cpp index f374428..91787ec 100644 --- a/LibCmo/CK2/CKGlobals.cpp +++ b/LibCmo/CK2/CKGlobals.cpp @@ -19,6 +19,7 @@ #include "ObjImpls/CK3dObject.hpp" #include "ObjImpls/CKTexture.hpp" #include "ObjImpls/CKMaterial.hpp" +#include "ObjImpls/CKMesh.hpp" namespace LibCmo::CK2 { @@ -414,6 +415,7 @@ CKClassRegister(cid, parentCid, \ EasyClassReg(ObjImpls::CK3dObject, CK_CLASSID::CKCID_3DOBJECT, CK_CLASSID::CKCID_3DENTITY, "3D Object"); EasyClassReg(ObjImpls::CKTexture, CK_CLASSID::CKCID_TEXTURE, CK_CLASSID::CKCID_BEOBJECT, "Texture"); EasyClassReg(ObjImpls::CKMaterial, CK_CLASSID::CKCID_MATERIAL, CK_CLASSID::CKCID_BEOBJECT, "Material"); + EasyClassReg(ObjImpls::CKMesh, CK_CLASSID::CKCID_MESH, CK_CLASSID::CKCID_BEOBJECT, "Mesh"); #undef EasyClassReg #undef EasyClassRegWithNotify diff --git a/LibCmo/CK2/CKStateChunk.hpp b/LibCmo/CK2/CKStateChunk.hpp index 7481a8d..0cb6736 100644 --- a/LibCmo/CK2/CKStateChunk.hpp +++ b/LibCmo/CK2/CKStateChunk.hpp @@ -65,7 +65,7 @@ namespace LibCmo::CK2 { CKStateChunk* m_Host; CKDWORD m_ConsumedSize; }; - + class BufferDeleter { public: BufferDeleter() : m_Host(nullptr), m_BufSize(0) {} @@ -333,73 +333,89 @@ namespace LibCmo::CK2 { /* Buffer related function implements: - ReadBuffer(void**) Read Byte based size. -> ReadBuffer - ReadAndFillBuffer(int, void*) User give Byte based size. -> ReadNoSizeBuffer - ReadAndFillBuffer(void*) Read Byte based size. -> ReadBuffer - ReadAndFillBuffer_LEndian(int, void*) User give Byte based size. -> ReadNoSizeBuffer - ReadAndFillBuffer_LEndian(void*) Read Byte based size. -> ReadBuffer - ReadAndFillBuffer_LEndian16(int, void*) User give Byte based size. -> ReadNoSizeBuffer - ReadAndFillBuffer_LEndian16(void*) Read Byte based size. -> ReadBuffer + ReadBuffer(void**) Read Byte based size. -> ReadAndCopyBuffer(void**, CKDWORD*) + ReadAndFillBuffer(int, void*) User give Byte based size. -> ReadBuffer(const void**, CKDWORD) + ReadAndFillBuffer(void*) Read Byte based size. -> ReadBuffer(const void**, CKDWORD*) + ReadAndFillBuffer_LEndian(int, void*) User give Byte based size. -> ReadBuffer(const void**, CKDWORD) + ReadAndFillBuffer_LEndian(void*) Read Byte based size. -> ReadBuffer(const void**, CKDWORD*) + ReadAndFillBuffer_LEndian16(int, void*) User give Byte based size. -> ReadBuffer(const void**, CKDWORD) + ReadAndFillBuffer_LEndian16(void*) Read Byte based size. -> ReadBuffer(const void**, CKDWORD*) */ /** - * @brief Read a buffer with unknow size (order user specific it). - * @param size_in_byte[in] The size of buffer. - * @param allocatedBuf[out] Buffer for filling. - * @return true if success. - * @remark - * + Following original Virtools functions can use this function to implement: - * - ReadAndFillBuffer(int, void*) - * - ReadAndFillBuffer_LEndian(int, void*) - * - ReadAndFillBuffer_LEndian16(int, void*) - */ - bool ReadNoSizeBuffer(CKDWORD size_in_byte, void* allocatedBuf); - /** - * @brief Read a buffer with knowen size stored in chunk. - * @param buf[out] a pointer to the pointer receiving data start address. - * @param len_in_byte[out] a pointer to the variable receiving the length of gotten buffer. - * @return true if success. + * @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. * @remark * + Following original Virtools functions can use this function to implement: * - ReadBuffer(void**) - * - ReadAndFillBuffer(void*) - * - ReadAndFillBuffer_LEndian(void*) - * - ReadAndFillBuffer_LEndian16(void*) - * + The created buffer should be freed by DeleteBuffer(). */ - bool ReadBuffer(void** buf, CKDWORD* len_in_byte); - + bool ReadBuffer(void** ppData, CKDWORD* size_in_byte); /** * @brief Free the buffer allocated by CKStateChunk reading functions. * @param buf[in] The buffer need to be free. * @see ReadBuffer */ void DeleteBuffer(const void* buf); - /** - * @brief A RAII wrapper for ReadBuffer and DeleteBuffer - * @param uptr The pointer to unique_ptr receiving data. - * @param len_in_byte The size of gotten buffer. - * @return A buffer with RAII feature (more like std::unique_ptr). - * @remark - * + The return value is more like std::unique_ptr but it have more features. - * + If Buffer_t::GetPtr return nullptr, it mean this function is failed. - * + Use Buffer_t::GetSize to get the size of buffer. - * + You can use Buffer_t::Reset to force free the return value. - * @example - * ``` - * Buffer_t buf = ReadBufferWrapper(1919810); - * if (buf.GetPtr() == nullptr) { - * // failed - * } else { - * stuff(buf); // do some operation... - * buf.SetSize(114514); // i only consume these bytes. - * buf.Reset(); // immediately free it. - * } - * ``` + * @brief A wrapper for ReadAndCopyBuffer(void**, CKDWORD*) + * @return */ Buffer_t ReadBufferWrapper(); + /** + * @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: + * - ReadAndFillBuffer(void*) + * - ReadAndFillBuffer_LEndian(void*) + * - ReadAndFillBuffer_LEndian16(void*) + */ + 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. + * @remark + * + Following original Virtools functions can use this function to implement: + * - ReadAndFillBuffer(int, void*) + * - ReadAndFillBuffer_LEndian(int, void*) + * - ReadAndFillBuffer_LEndian16(int, void*) + */ + bool ReadAndFillBuffer(void* pData, CKDWORD size_in_byte); + + ///** + // * @brief A RAII wrapper for ReadBuffer and DeleteBuffer + // * @param uptr The pointer to unique_ptr receiving data. + // * @param len_in_byte The size of gotten buffer. + // * @return A buffer with RAII feature (more like std::unique_ptr). + // * @remark + // * + The return value is more like std::unique_ptr but it have more features. + // * + If Buffer_t::GetPtr return nullptr, it mean this function is failed. + // * + Use Buffer_t::GetSize to get the size of buffer. + // * + You can use Buffer_t::Reset to force free the return value. + // * @example + // * ``` + // * Buffer_t buf = ReadBufferWrapper(1919810); + // * if (buf.GetPtr() == nullptr) { + // * // failed + // * } else { + // * stuff(buf); // do some operation... + // * buf.SetSize(114514); // i only consume these bytes. + // * buf.Reset(); // immediately free it. + // * } + // * ``` + //*/ + //Buffer_t ReadBufferWrapper(); + /* ========== Sequence Functions ==========*/ /// diff --git a/LibCmo/CK2/CKStateChunkReader.cpp b/LibCmo/CK2/CKStateChunkReader.cpp index 012e54d..0f0762c 100644 --- a/LibCmo/CK2/CKStateChunkReader.cpp +++ b/LibCmo/CK2/CKStateChunkReader.cpp @@ -289,57 +289,87 @@ namespace LibCmo::CK2 { /* ========== Buffer Functions ==========*/ - bool CKStateChunk::ReadNoSizeBuffer(CKDWORD size_in_byte, void* allocatedBuf) { - if (allocatedBuf == nullptr) return false; - return this->ReadByteData(allocatedBuf, size_in_byte); - } + bool CKStateChunk::ReadBuffer(void** ppData, CKDWORD* size_in_byte) { + if (ppData == nullptr || size_in_byte == nullptr) return false; + *ppData = nullptr; + *size_in_byte = 0; - bool CKStateChunk::ReadBuffer(void** buf, CKDWORD* len_in_byte) { - if (buf == nullptr || len_in_byte == nullptr) return false; - - // get buffer size. - CKDWORD bufByteSize = 0u; - if (!this->ReadStruct(bufByteSize)) { - *buf = nullptr; - *len_in_byte = 0; + // read size first + if (!this->ReadStruct(size_in_byte)) { return false; } - *len_in_byte = bufByteSize; - // special treat for zero length buffer - if (bufByteSize == 0) { - *buf = nullptr; + // if it is empty buffer, create a fake buffer + // and simply return it. + if (*size_in_byte == 0) { + *ppData = new CKBYTE[1]; return true; } - // create buffer - *buf = new CKBYTE[bufByteSize]; - - // read data - if (!this->ReadByteData(*buf, bufByteSize)) { - this->DeleteBuffer(*buf); - *buf = nullptr; - *len_in_byte = 0; + // use LockReadBuffer to get pointer + auto locker = LockReadBufferWrapper(*size_in_byte); + if (locker == nullptr) { + *size_in_byte = 0; return false; } + // copy data and unlock buffer + *ppData = new CKBYTE[*size_in_byte]; + std::memcpy(*ppData, locker.get(), *size_in_byte); + locker.reset(); return true; } - + void CKStateChunk::DeleteBuffer(const void* buf) { if (buf == nullptr) return; delete[] reinterpret_cast(buf); } CKStateChunk::Buffer_t CKStateChunk::ReadBufferWrapper() { - void* bufcache = nullptr; - CKDWORD len_in_byte; - bool ret = ReadBuffer(&bufcache, &len_in_byte); - if (ret) { - return Buffer_t(bufcache, BufferDeleter(this, len_in_byte)); - } else { + void* cache; + CKDWORD size; + if (!ReadBuffer(&cache, &size)) { return Buffer_t(); } + return Buffer_t(cache, BufferDeleter(this, size)); + } + + bool CKStateChunk::ReadAndFillBuffer(void* pData) { + if (pData == nullptr) return false; + + // get buffer size. + CKDWORD size_in_byte; + if (!this->ReadStruct(size_in_byte)) { + return false; + } + + // use LockReadBuffer to get pointer + // this step can process zero length buffer + // so we do not treat it specially + auto locker = LockReadBufferWrapper(size_in_byte); + if (locker == nullptr) { + return false; + } + + // then copy the data + std::memcpy(pData, locker.get(), size_in_byte); + locker.reset(); + return true; + } + + bool CKStateChunk::ReadAndFillBuffer(void* pData, CKDWORD size_in_byte) { + if (pData == nullptr) return false; + + // directly use LockReadBuffer to get pointer + auto locker = LockReadBufferWrapper(size_in_byte); + if (locker == nullptr) { + return false; + } + + // then copy the data + std::memcpy(pData, locker.get(), size_in_byte); + locker.reset(); + return true; } /* ========== Sequence Functions ==========*/ diff --git a/LibCmo/CK2/ObjImpls/CKMesh.cpp b/LibCmo/CK2/ObjImpls/CKMesh.cpp index 1972432..231f8db 100644 --- a/LibCmo/CK2/ObjImpls/CKMesh.cpp +++ b/LibCmo/CK2/ObjImpls/CKMesh.cpp @@ -79,14 +79,16 @@ namespace LibCmo::CK2::ObjImpls { // read and set vertex count CKDWORD vertexCount; chunk->ReadStruct(vertexCount); + SetVertexCount(vertexCount); if (vertexCount != 0) { // read save flags chunk->ReadStruct(saveflags); - // read size in dword + // read size in dword (including it self) CKDWORD sizeInDword; chunk->ReadStruct(sizeInDword); + --sizeInDword; // remove self. // lock read buffer auto buf = chunk->LockReadBufferWrapper(sizeInDword * CKSizeof(CKDWORD)); @@ -211,7 +213,14 @@ namespace LibCmo::CK2::ObjImpls { chunk->ReadStruct(lineCount); SetLineCount(lineCount); - chunk->ReadNoSizeBuffer(lineCount * 2 ) + chunk->ReadAndFillBuffer(m_LineIndices.data(), CKSizeof(CKWORD) * lineCount * 2); + } + + // build normals + if (EnumsHelper::Has(saveflags, VertexSaveFlags::NoNormal)) { + BuildNormals(); + } else { + BuildFaceNormals(); } return true; @@ -227,6 +236,10 @@ namespace LibCmo::CK2::ObjImpls { SetLineCount(0); } + void CKMesh::BuildNormals() {} + + void CKMesh::BuildFaceNormals() {} + #pragma region Vertex Section CKDWORD CKMesh::GetVertexCount() { diff --git a/LibCmo/VxMath/VxMath.cpp b/LibCmo/VxMath/VxMath.cpp index 07e99aa..d208765 100644 --- a/LibCmo/VxMath/VxMath.cpp +++ b/LibCmo/VxMath/VxMath.cpp @@ -109,7 +109,7 @@ namespace LibCmo::VxMath { } } - void VxDoAlphaBlit(VxImageDescEx* dst_desc, CKBYTE* AlphaValues) { + void VxDoAlphaBlit(VxImageDescEx* dst_desc, const CKBYTE* AlphaValues) { if (dst_desc == nullptr) return; CKDWORD* pixels = dst_desc->GetMutablePixels(); diff --git a/LibCmo/VxMath/VxMath.hpp b/LibCmo/VxMath/VxMath.hpp index 52dc918..5eeb2e5 100644 --- a/LibCmo/VxMath/VxMath.hpp +++ b/LibCmo/VxMath/VxMath.hpp @@ -81,7 +81,7 @@ namespace LibCmo::VxMath { * @param AlphaValues[in] A BYTE array containing the alpha values for each pixel. This array should be allocated to Width*Height bytes. * @remark If the destination image does not have alpha information the function returns immediatly. */ - void VxDoAlphaBlit(VxImageDescEx* dst_desc, CKBYTE* AlphaValues); + void VxDoAlphaBlit(VxImageDescEx* dst_desc, const CKBYTE* AlphaValues); }