diff --git a/LibCmo/CK2/CKBitmapData.cpp b/LibCmo/CK2/CKBitmapData.cpp index 9c9f42f..fb6a1d3 100644 --- a/LibCmo/CK2/CKBitmapData.cpp +++ b/LibCmo/CK2/CKBitmapData.cpp @@ -10,14 +10,117 @@ namespace LibCmo::CK2 { #pragma region Assist RW Functions bool CKBitmapData::ReadSpecificFormatBitmap(CKStateChunk* chk, VxMath::VxImageDescEx* slot) { - return false; + // read transparent prop + CKDWORD transprop; + chk->ReadStruct(transprop); + + // get ext and guid to find correct guid + CKCHAR filerawext[4]; + CKGUID fileguid; + chk->ReadNoSizeBuffer(CKSizeof(filerawext), filerawext); + chk->ReadStruct(fileguid); + CKFileExtension fileext(filerawext); + auto reader = DataHandlers::CKBitmapHandler::GetBitmapHandlerWrapper(fileext, fileguid); + if (reader == nullptr) { + return false; + } + + // read image size + CKDWORD imgbytesize; + chk->ReadStruct(imgbytesize); + if (imgbytesize != 0) { + // get image data ptr + const void* imgdata = nullptr; + if (!chk->ReadDryBuffer(&imgdata, imgbytesize)) { + return false; + } + + // parse image + VxMath::VxImageDescEx cache; + if (!reader->ReadMemory(imgdata, imgbytesize, &cache)) { + return false; + } + + // post proc image (copy to slot) + VxMath::VxDoBlit(&cache, slot); + + // proc image alpha + if (transprop == 2) { + CKDWORD alphacount; + chk->ReadStruct(alphacount); + if (alphacount == 1) { + CKDWORD globalalpha; + chk->ReadStruct(globalalpha); + VxMath::VxDoAlphaBlit(slot, static_cast(globalalpha)); + } else { + CKStateChunk::TBuffer alphabuf; + CKDWORD buflen; + chk->ReadBufferWrapper(&alphabuf, &buflen); + VxMath::VxDoAlphaBlit(slot, reinterpret_cast(alphabuf.get())); + } + } + + } + + return true; } bool CKBitmapData::ReadRawBitmap(CKStateChunk* chk, VxMath::VxImageDescEx* slot) { - return false; + CKDWORD bytePerPixel, width, height, redMask, greenMask, blueMask, alphaMask; + chk->ReadStruct(bytePerPixel); // not used + if (bytePerPixel == 0) return false; + + chk->ReadStruct(width); + chk->ReadStruct(height); + chk->ReadStruct(alphaMask); + chk->ReadStruct(redMask); + chk->ReadStruct(greenMask); + chk->ReadStruct(blueMask); + + // read RGBA buffer + CKStateChunk::TBuffer redBuffer, greenBuffer, blueBuffer, alphaBuffer; + CKDWORD bufsize; + CKDWORD bufopt; + chk->ReadStruct(bufopt); + bufopt &= 0xFu; + if (bufopt != 0) { + // MARK: not supported CCompressionTools::jpegDecode() + // There are some shitty jpeg decode function. + // I do not want to touch them because all of my work do not related to them + // so return false simply + return false; + } else { + chk->ReadBufferWrapper(&redBuffer, &bufsize); + chk->ReadBufferWrapper(&greenBuffer, &bufsize); + chk->ReadBufferWrapper(&blueBuffer, &bufsize); + } + chk->ReadBufferWrapper(&alphaBuffer, &bufsize); + + // write into file + if (redBuffer != nullptr && greenBuffer != nullptr && blueBuffer != nullptr) { + // create image + slot->CreateImage(width, height); + // get essential data + CKDWORD pixelcount = slot->GetPixelCount(); + CKBYTE* dst = slot->GetMutableImage(), + * redSrc = reinterpret_cast(redBuffer.get()), + * greenSrc = reinterpret_cast(greenBuffer.get()), + * blueSrc = reinterpret_cast(blueBuffer.get()), + * alphaSrc = reinterpret_cast(alphaBuffer.get()); + for (CKDWORD p = 0; p < pixelcount; ++p) { + // if no alpha data, set to 0xFF + *(dst++) = (alphaBuffer != nullptr ? (*(alphaSrc++)) : 0xFFu); + *(dst++) = *(redSrc++); + *(dst++) = *(greenSrc++); + *(dst++) = *(blueSrc++); + } + } + + return true; } bool CKBitmapData::ReadOldRawBitmap(CKStateChunk* chk, VxMath::VxImageDescEx* slot) { + // MARK: not supported because all of my work do not involve this function. return false; } @@ -37,6 +140,9 @@ namespace LibCmo::CK2 { XContainer::XBitArray notReadSlot; // check 3 types enbedded image + // MARK: i think there is a potential vulnerable issue. + // if a slot failed, all following slot will read data from a wrong position. + // thus the program will crash or allocated massive garbage data. if (chunk->SeekIdentifierDword(identifiers.m_SpecificFormat)) { // specific format CKDWORD slotcount, width, height, bpp; @@ -48,6 +154,10 @@ namespace LibCmo::CK2 { SetSlotCount(slotcount); notReadSlot.resize(slotcount, false); + // the height and width is written outside of specific format + // so we create image first for it. + // and let reader to read data. + // and free image if is is failed. if (width > 0 && height > 0) { for (CKDWORD i = 0; i < slotcount; ++i) { CreateImage(width, height, i); @@ -67,11 +177,17 @@ namespace LibCmo::CK2 { SetSlotCount(slotcount); notReadSlot.resize(slotcount, false); + // the height and width is read by raw data function self. + // so we pass a cache variable to reader and do some modification + // if it is success. for (CKDWORD i = 0; i < slotcount; ++i) { - if (ReadRawBitmap(chunk, GetImageDesc(i))) { + VxMath::VxImageDescEx rawcache; + if (ReadRawBitmap(chunk, &rawcache)) { notReadSlot[i] = true; - } else { - ReleaseImage(i); + + // do upside down blit + CreateImage(rawcache.GetWidth(), rawcache.GetHeight(), i); + VxMath::VxDoBlitUpsideDown(&rawcache, GetImageDesc(i)); } } @@ -83,6 +199,7 @@ namespace LibCmo::CK2 { SetSlotCount(slotcount); notReadSlot.resize(slotcount, false); + // MARK: a rough implement because we do not support this identifier for (CKDWORD i = 0; i < slotcount; ++i) { if (ReadOldRawBitmap(chunk, GetImageDesc(i))) { notReadSlot[i] = true; @@ -105,17 +222,23 @@ namespace LibCmo::CK2 { for (CKDWORD i = 0; i < slotcount; ++i) { std::string filename; chunk->ReadString(filename); - if (filename.empty()) return; - - // set - SetSlotFileName(i, filename.c_str()); - - // if not loaded, and suc in resolve file path. - // load it + if (filename.empty()) continue; + bool isNotLoaded = i >= notReadSlot.size() || notReadSlot[i]; - if (isNotLoaded && m_Context->GetPathManager()->ResolveFileName(filename)) { - LoadImage(filename.c_str(), i); + if (isNotLoaded) { + // if this image is not loaded. + // try resolve its file name and load it. + // and set resolved filename for it. + if (m_Context->GetPathManager()->ResolveFileName(filename)) { + if (LoadImage(filename.c_str(), i)) { + SetSlotFileName(i, filename.c_str()); + } + } + } else { + // otherwise, set filename simply + SetSlotFileName(i, filename.c_str()); } + } } @@ -124,6 +247,7 @@ namespace LibCmo::CK2 { // MARK: movie is not implemented here. } + return true; } bool CKBitmapData::DumpToChunk(CKStateChunk* chunk, CKFileVisitor* file, const CKBitmapDataWriteIdentifiers& identifiers) { @@ -176,29 +300,45 @@ namespace LibCmo::CK2 { // get extension of file. then get corresponding reader std::string ext(filename); m_Context->GetPathManager()->GetExtension(ext); - std::unique_ptr reader( - DataHandlers::CKBitmapHandler::GetBitmapHandler(CKFileExtension(ext.c_str()), CKGUID()) - ); + auto reader = DataHandlers::CKBitmapHandler::GetBitmapHandlerWrapper(CKFileExtension(ext.c_str()), CKGUID()); if (reader == nullptr) return false; - // get desc - VxMath::VxImageDescEx* desc = GetImageDesc(slot); - if (desc == nullptr) return false; - - // read data - if (!reader->ReadFile(filename, desc)) { + // get desc and read data + if (!reader->ReadFile(filename, GetImageDesc(slot))) { return false; } - // free reader - reader.reset(); return true; } - bool CKBitmapData::SaveImage(CKSTRING filename, CKDWORD slot) { + bool CKBitmapData::SaveImage(CKSTRING filename, CKDWORD slot, bool isForceThisFmt) { if (filename == nullptr) return false; if (slot >= m_Slots.size()) return false; + // prepare save format + CKBitmapProperties savefmt; + if (isForceThisFmt) { + savefmt = this->m_SaveProperties; + } else { + std::string ext(filename); + m_Context->GetPathManager()->GetExtension(ext); + if (ext.empty()) { + // fallback to this fmt + savefmt = this->m_SaveProperties; + } else { + savefmt.m_Ext.SetExt(ext.c_str()); + } + } + + // get reader by format + auto reader = DataHandlers::CKBitmapHandler::GetBitmapHandlerWrapper(savefmt.m_Ext, savefmt.m_ReaderGuid); + if (reader == nullptr) return false; + + // save file + if (!reader->SaveFile(filename, GetImageDesc(slot), savefmt)) { + return false; + } + return true; } @@ -240,7 +380,11 @@ namespace LibCmo::CK2 { } void CKBitmapData::SetTransparent(bool Transparency) { - EnumsHelper::Add(m_BitmapFlags, CK_BITMAPDATA_FLAGS::CKBITMAPDATA_TRANSPARENT); + if (Transparency) { + EnumsHelper::Add(m_BitmapFlags, CK_BITMAPDATA_FLAGS::CKBITMAPDATA_TRANSPARENT); + } else { + EnumsHelper::Rm(m_BitmapFlags, CK_BITMAPDATA_FLAGS::CKBITMAPDATA_TRANSPARENT); + } } bool CKBitmapData::IsTransparent() { diff --git a/LibCmo/CK2/CKBitmapData.hpp b/LibCmo/CK2/CKBitmapData.hpp index 16135b5..1d2c461 100644 --- a/LibCmo/CK2/CKBitmapData.hpp +++ b/LibCmo/CK2/CKBitmapData.hpp @@ -54,7 +54,7 @@ namespace LibCmo::CK2 { void CreateImage(CKDWORD Width, CKDWORD Height, CKDWORD Slot); bool LoadImage(CKSTRING filename, CKDWORD slot); - bool SaveImage(CKSTRING filename, CKDWORD slot); + bool SaveImage(CKSTRING filename, CKDWORD slot, bool isForceThisFmt = false); VxMath::VxImageDescEx* GetImageDesc(CKDWORD slot); void ReleaseImage(CKDWORD slot); diff --git a/LibCmo/CK2/CKStateChunk.cpp b/LibCmo/CK2/CKStateChunk.cpp index 5b4a65a..958107c 100644 --- a/LibCmo/CK2/CKStateChunk.cpp +++ b/LibCmo/CK2/CKStateChunk.cpp @@ -161,11 +161,6 @@ namespace LibCmo::CK2 { this->m_DataVersion = version; } - void CKStateChunk::DeleteBuffer(const void* buf) { - if (buf == nullptr) return; - delete[] reinterpret_cast(buf); - } - bool CKStateChunk::Skip(CKDWORD DwordCount) { bool result; switch (this->m_Parser.m_Status) { @@ -190,6 +185,17 @@ namespace LibCmo::CK2 { + void* CKStateChunk::GetCurrentPointer() { + switch (this->m_Parser.m_Status) { + case CKStateChunkStatus::READ: + case CKStateChunkStatus::WRITE: + return this->m_pData + this->m_Parser.m_CurrentPos; + case CKStateChunkStatus::IDLE: + default: + return nullptr; + } + } + CKDWORD CKStateChunk::GetCeilDwordSize(size_t char_size) { return static_cast((char_size + 3) >> 2); } @@ -560,11 +566,14 @@ namespace LibCmo::CK2 { bool CKStateChunk::ReadByteData(void* data_ptr, CKDWORD size_in_byte) { if (this->m_Parser.m_Status != CKStateChunkStatus::READ) return false; - if (data_ptr == nullptr) return false; CKDWORD size_in_dword = this->GetCeilDwordSize(size_in_byte); if (this->EnsureReadSpace(size_in_dword)) { - std::memcpy(data_ptr, this->m_pData + this->m_Parser.m_CurrentPos, size_in_byte); + // only copy when data_ptr is not nullptr + // do dry run if there are no dest to copy for. + if (data_ptr != nullptr) { + std::memcpy(data_ptr, this->m_pData + this->m_Parser.m_CurrentPos, size_in_byte); + } this->m_Parser.m_CurrentPos += size_in_dword; return true; } else { @@ -760,6 +769,12 @@ namespace LibCmo::CK2 { } *len_in_byte = bufByteSize; + // special treat for zero length buffer + if (bufByteSize == 0) { + *buf = nullptr; + return true; + } + // create buffer *buf = new char[bufByteSize]; @@ -774,6 +789,38 @@ namespace LibCmo::CK2 { return true; } + bool CKStateChunk::ReadBufferWrapper(TBuffer* uptr, CKDWORD* len_in_byte) { + if (uptr == nullptr || len_in_byte == nullptr) return false; + + void* bufcache = nullptr; + bool ret = ReadBuffer(&bufcache, len_in_byte); + uptr->reset(bufcache); + + return ret; + } + + bool CKStateChunk::ReadDryBuffer(const void** buf, CKDWORD ordered_size) { + if (buf == nullptr) return false; + + // backup current pos + *buf = GetCurrentPointer(); + if (!this->ReadByteData(nullptr, ordered_size)) { + *buf = nullptr; + return false; + } + return true; + } + + void CKStateChunk::BufferDeleter::operator()(void* buf) { + if (buf == nullptr) return; + delete[] reinterpret_cast(buf); + } + + void CKStateChunk::DeleteBuffer(const void* buf) { + if (buf == nullptr) return; + delete[] reinterpret_cast(buf); + } + /* ========== Sequence Functions ==========*/ bool CKStateChunk::ReadObjectIDSequence(XContainer::XArray* ls) { @@ -957,5 +1004,4 @@ namespace LibCmo::CK2 { #pragma endregion - } diff --git a/LibCmo/CK2/CKStateChunk.hpp b/LibCmo/CK2/CKStateChunk.hpp index b258b45..247e1e7 100644 --- a/LibCmo/CK2/CKStateChunk.hpp +++ b/LibCmo/CK2/CKStateChunk.hpp @@ -1,6 +1,8 @@ #pragma once #include "../VTAll.hpp" +#include +#include namespace LibCmo::CK2 { @@ -80,15 +82,11 @@ namespace LibCmo::CK2 { CKDWORD GetDataSize(void); CK_STATECHUNK_DATAVERSION GetDataVersion(); void SetDataVersion(CK_STATECHUNK_DATAVERSION version); - /** - * @brief Free the buffer allocated by CKStateChunk reading functions. - * @param buf The buffer need to be free. - */ - void DeleteBuffer(const void* buf); bool Skip(CKDWORD DwordCount); private: CKDWORD GetCeilDwordSize(size_t char_size); + void* GetCurrentPointer(); bool ResizeBuffer(CKDWORD new_dwsize); bool EnsureWriteSpace(CKDWORD dwsize); bool EnsureReadSpace(CKDWORD dword_required); @@ -200,6 +198,19 @@ namespace LibCmo::CK2 { ReadAndFillBuffer_LEndian16(void*) Read Byte based size. -> ReadBuffer */ + /** + * @brief The deleter for std::unique_ptr of CKStateChunk created buffer. + */ + struct BufferDeleter { + BufferDeleter() = default; + BufferDeleter(const BufferDeleter&) noexcept {} + void operator()(void* buf); + }; + /** + * @brief The type of CKStateChunk auto free buffer. + */ + using TBuffer = std::unique_ptr; + /// /// Read a buffer with unknow size (order user specific it). /// ReadAndFillBuffer(int, void*), ReadAndFillBuffer_LEndian(int, void*), ReadAndFillBuffer_LEndian16(int, void*) are redirected to this. @@ -219,6 +230,29 @@ namespace LibCmo::CK2 { /// a pointer to the variable receiving the length of gotten buffer. /// bool ReadBuffer(void** buf, CKDWORD* len_in_byte); + /** + * @brief A auto free wrapper for ReadBuffer + * @param uptr The pointer to unique_ptr receiving data. + * @param len_in_byte The size of gotten buffer. + * @return + */ + bool ReadBufferWrapper(TBuffer* uptr, CKDWORD* len_in_byte); + /** + * @brief Perform a dry buffer reading. + * This function will only make sure there is enough space for your reading. + * And return the start memory address to you. + * And will not create any extra memory like ReadBuffer. + * @param buf[out] a pointer to the pointer receiving data start address. + * @param ordered_sizepin] your expected length of this buffer. + * @return + */ + bool ReadDryBuffer(const void** buf, CKDWORD ordered_size); + + /** + * @brief Free the buffer allocated by CKStateChunk reading functions. + * @param buf The buffer need to be free. + */ + void DeleteBuffer(const void* buf); /* ========== Sequence Functions ==========*/ diff --git a/LibCmo/CK2/DataHandlers/CKBitmapHandler.cpp b/LibCmo/CK2/DataHandlers/CKBitmapHandler.cpp index 819097d..5b3766c 100644 --- a/LibCmo/CK2/DataHandlers/CKBitmapHandler.cpp +++ b/LibCmo/CK2/DataHandlers/CKBitmapHandler.cpp @@ -129,6 +129,7 @@ namespace LibCmo::CK2::DataHandlers { using SaveOperation = std::function; static bool StbSaveFile(CKSTRING u8filename, const VxMath::VxImageDescEx* write_image, SaveOperation oper) { if (u8filename == nullptr || write_image == nullptr) return false; + if (!write_image->IsValid()) return false; FILE* fs = EncodingHelper::U8FOpen(u8filename, "wb"); if (fs == nullptr) return false; @@ -153,6 +154,7 @@ namespace LibCmo::CK2::DataHandlers { } static CKDWORD StbSaveMemory(void* memory, const VxMath::VxImageDescEx* write_image, SaveOperation oper) { if (write_image == nullptr) return 0; + if (!write_image->IsValid()) return 0; // allocate buffer and convert data from ARGB to RGBA CKBYTE* data = new CKBYTE[write_image->GetImageSize()]; @@ -279,6 +281,14 @@ namespace LibCmo::CK2::DataHandlers { return nullptr; } + std::unique_ptr CKBitmapHandler::GetBitmapHandlerWrapper(const CKFileExtension& ext, const CKGUID& guid) { + return std::unique_ptr>(GetBitmapHandler(ext, guid)); + } + + void CKBitmapHandlerDeleter::operator()(CKBitmapHandler* handler) { + CKBitmapHandler::ReleaseBitmapHandler(handler); + } + void CKBitmapHandler::ReleaseBitmapHandler(CKBitmapHandler* handler) { if (handler != nullptr) delete handler; } diff --git a/LibCmo/CK2/DataHandlers/CKBitmapHandler.hpp b/LibCmo/CK2/DataHandlers/CKBitmapHandler.hpp index f70e321..bdeed1f 100644 --- a/LibCmo/CK2/DataHandlers/CKBitmapHandler.hpp +++ b/LibCmo/CK2/DataHandlers/CKBitmapHandler.hpp @@ -1,9 +1,22 @@ #pragma once #include "../../VTAll.hpp" +#include +#include namespace LibCmo::CK2::DataHandlers { + class CKBitmapHandler; + /** + * @brief An assist class which can applied to std::unique_ptr as a custom deleter + * to make sure the CKBitmapHandler* can be free correctly. + */ + struct CKBitmapHandlerDeleter { + CKBitmapHandlerDeleter() = default; + CKBitmapHandlerDeleter(const CKBitmapHandlerDeleter&) noexcept {} + void operator()(CKBitmapHandler* handler); + }; + /** * The interface about processing bitmap data between raw data and specific data. * This interface will be capable to converting specific bitmap data into raw ARGB8888 raw data, @@ -25,6 +38,10 @@ namespace LibCmo::CK2::DataHandlers { * @return The pointer to CKBitmapHandler. nullptr if fail to find. */ static CKBitmapHandler* GetBitmapHandler(const CKFileExtension& ext, const CKGUID& guid); + /** + * @brief A auto free wrapper for GetBitmapHandler + */ + static std::unique_ptr GetBitmapHandlerWrapper(const CKFileExtension& ext, const CKGUID& guid); /** * @brief General CKBitmapHandler disposer * @param handler[in] The handler need to be free. @@ -73,18 +90,6 @@ namespace LibCmo::CK2::DataHandlers { }; - /** - * @brief An assist class which can applied to std::unique_ptr as a custom deleter - * to make sure the CKBitmapHandler* can be free correctly. - */ - struct CKBitmapHandlerDeleter { - CKBitmapHandlerDeleter() = default; - CKBitmapHandlerDeleter(const CKBitmapHandlerDeleter&) noexcept {} - void operator()(CKBitmapHandler* handler) { - CKBitmapHandler::ReleaseBitmapHandler(handler); - } - }; - class CKBitmapBMPHandler : public CKBitmapHandler { public: CKBitmapBMPHandler();