#include "CKFile.hpp" #include "CKContext.hpp" #include "CKStateChunk.hpp" #include "ObjImpls/CKObject.hpp" #include "MgrImpls/CKBaseManager.hpp" #include "MgrImpls/CKPathManager.hpp" #include "../VxMath/VxMemoryMappedFile.hpp" #include namespace LibCmo::CK2 { CKERROR CKFileWriter::Save(CKSTRING u8_filename) { // check document status if (this->m_Done) return CKERROR::CKERR_CANCELLED; // check CKContext encoding sequence if (!this->m_Ctx->IsValidEncoding()) return CKERROR::CKERR_CANCELLED; // encoding conv helper std::string name_conv; // try detect filename legality CKERROR err = PrepareFile(u8_filename); if (err != CKERROR::CKERR_OK) return err; // ========== Prepare Stage ========== // todo: add TOBEDELETED flag for all Referenced objects's m_ObjectFlags // MARK: ignore the notification to all CKBehavior based objects. // ========== StateChunk convertion ========== // iterate all objects and transform it into CKStateChunk // MARK: Drop the support of collecting the sum of InterfaceChunk's size. // because it is useless. for (auto& obj : m_FileObjects) { // if there is a chunk, skip if (obj.Data != nullptr) continue; obj.Data = new CKStateChunk(&m_Visitor, m_Ctx); obj.Data->StartWrite(); bool suc = obj.ObjPtr->Save(obj.Data, &m_Visitor, obj.SaveFlags); obj.Data->StopWrite(); if (!suc) { // fail to parse delete obj.Data; obj.Data = nullptr; } } // iterate manager // if object adding is disabled, skip. because in that mode, no need to query manager data. if (!m_DisableAddingObject) { CKINT mgrcount = m_Ctx->GetManagerCount(); CKINT availablemgr = 0; // place manager // if no data, skip it m_ManagersData.resize(mgrcount); for (CKINT i = 0; i < mgrcount; ++i) { MgrImpls::CKBaseManager* mgr = m_Ctx->GetManager(i); m_ManagersData[availablemgr].Manager = mgr->GetGuid(); m_ManagersData[availablemgr].Data = new CKStateChunk(&m_Visitor, m_Ctx); m_ManagersData[availablemgr].Data->StartWrite(); bool suc = mgr->SaveData(m_ManagersData[availablemgr].Data, &m_Visitor); m_ManagersData[availablemgr].Data->StopWrite(); if (!suc) { delete m_ManagersData[availablemgr].Data; m_ManagersData[availablemgr].Data = nullptr; } else { ++availablemgr; } } // resize to the new size which erase all skipped manager m_ManagersData.resize(availablemgr); } // if object adding is disabled, skip plugin dep. same reason with manager iteration. if (!m_DisableAddingObject) { // todo: finish plugin dep filling } // ========== Size Calc ========== // iterate 3 list to get each parts' size CKDWORD sumDataObjSize = 0, sumHdrObjSize = 0; for (auto& obj : m_FileObjects) { // += 4DWORD(ObjId, ObjCid, FileIndex, NameLen) sumHdrObjSize += 4 * CKSizeof(CKDWORD); if (XContainer::NSXString::ToCKSTRING(obj.Name) != nullptr) { // += Name size if (!m_Ctx->GetOrdinaryString(obj.Name, name_conv)) m_Ctx->OutputToConsole(u8"Fail to get ordinary string for CKObject name when computing the size of saved file. It may cause application crash or saved file has blank object name."); sumHdrObjSize += static_cast(name_conv.size()); } if (obj.Data == nullptr) { obj.PackSize = 0; } else { obj.PackSize = obj.Data->ConvertToBuffer(nullptr); } // += chunk size + chunk sumDataObjSize += obj.PackSize + CKSizeof(CKDWORD); } CKDWORD sumDataMgrSize = 0; for (auto& mgr : m_ManagersData) { CKDWORD chunksize; if (mgr.Data == nullptr) { chunksize = 0; } else { chunksize = mgr.Data->ConvertToBuffer(nullptr); } // += GUID(2 DWORD) + chunk size + chunk sumDataMgrSize += chunksize + 3 * CKSizeof(CKDWORD); } // += Plugin Dep list size CKDWORD sumHdrPlgSize = CKSizeof(CKDWORD); for (auto& plg : m_PluginsDep) { // += GUID list + (dep category + GUID list size) sumHdrPlgSize += CKSizeof(CKGUID) * static_cast(plg.m_Guids.size()) + 2 * CKSizeof(CKDWORD); } CKDWORD sumHdrIncludedFiles = CKSizeof(CKINT) + CKSizeof(CKDWORD); // calc the whole size CKDWORD sumHdrSize = sumHdrObjSize + sumHdrPlgSize + sumHdrIncludedFiles; CKDWORD sumDataSize = sumDataObjSize + sumDataMgrSize; // compute file index for all object if (!m_FileObjects.empty()) { // set base for first one m_FileObjects[0].FileIndex = sumHdrSize + sumDataMgrSize + CKSizeof(CKRawFileInfo); // calc the remains for (size_t i = 1; i < m_FileObjects.size(); ++i) { // prev obj PackSize + prev obj FileIndex + prev obj chunk size m_FileObjects[i].FileIndex = m_FileObjects[i - 1].FileIndex + m_FileObjects[i - 1].PackSize + CKSizeof(CKDWORD); } } // ========== Construct File Header ========== CK_FILE_WRITEMODE fileWriteMode = m_Ctx->GetFileWriteMode(); CKRawFileInfo rawHeader; std::memcpy(&rawHeader.NeMo, CKNEMOFI, sizeof(CKRawFileInfo::NeMo)); rawHeader.Crc = 0; rawHeader.Zero = 0; rawHeader.CKVersion = CKVERSION; rawHeader.FileVersion = 8; rawHeader.FileWriteMode = static_cast(fileWriteMode); rawHeader.ObjectCount = static_cast(m_FileObjects.size()); rawHeader.ManagerCount = static_cast(m_ManagersData.size()); rawHeader.Hdr1UnPackSize = sumHdrSize; rawHeader.DataUnPackSize = sumDataSize; rawHeader.Hdr1PackSize = sumHdrSize; rawHeader.DataPackSize = sumDataSize; rawHeader.ProductVersion = DEVVERSION; rawHeader.ProductBuild = DEVBUILD; rawHeader.MaxIDSaved = static_cast(m_SaveIDMax); // crc will filled later // ========== Write header ========== // create a buffer std::unique_ptr hdrparser(new CKBufferParser(sumHdrSize)); // write obj for (auto& obj : m_FileObjects) { // todo: remove TOBEDELETED for referenced objects' m_ObjectFlags hdrparser->Write(&obj.ObjectId); hdrparser->Write(&obj.ObjectCid); hdrparser->Write(&obj.FileIndex); if (XContainer::NSXString::ToCKSTRING(obj.Name) != nullptr) { // if have name, write it if (!m_Ctx->GetOrdinaryString(obj.Name, name_conv)) m_Ctx->OutputToConsole(u8"Fail to get ordinary string for CKObject name when saving file. Some objects may be saved with blank name."); CKDWORD namelen = static_cast(name_conv.size()); hdrparser->Write(&namelen); hdrparser->Write(name_conv.data(), namelen); } else { // otherwise, write 0 to indicate no name CKDWORD namelen = 0; hdrparser->Write(&namelen); } } // write plugin dep { CKDWORD depsize = static_cast(m_PluginsDep.size()); hdrparser->Write(&depsize); for (auto& dep : m_PluginsDep) { hdrparser->Write(&dep.m_PluginCategory, sizeof(CK_PLUGIN_TYPE)); CKDWORD guidsize = static_cast(dep.m_Guids.size()); hdrparser->Write(&guidsize); hdrparser->Write(dep.m_Guids.data(), sizeof(CKGUID) * guidsize); } } // write included file { CKDWORD cache = CKSizeof(CKDWORD); hdrparser->Write(&cache); cache = static_cast(m_IncludedFiles.size()); hdrparser->Write(&cache); } // compress header if needed if (EnumsHelper::Has(fileWriteMode, CK_FILE_WRITEMODE::CKFILE_CHUNKCOMPRESSED_OLD) || EnumsHelper::Has(fileWriteMode, CK_FILE_WRITEMODE::CKFILE_WHOLECOMPRESSED)) { CKDWORD comp_buf_size = 0; void* comp_buffer = CKPackData(hdrparser->GetBase(), hdrparser->GetSize(), comp_buf_size, m_Ctx->GetCompressionLevel()); if (comp_buffer != nullptr) { hdrparser = std::unique_ptr(new CKBufferParser(comp_buffer, comp_buf_size, true)); rawHeader.Hdr1PackSize = comp_buf_size; } else { // fallback rawHeader.Hdr1PackSize = rawHeader.Hdr1UnPackSize; } } // ========== Write data ========== // create a buffer std::unique_ptr datparser(new CKBufferParser(sumDataSize)); // write manager for (auto& mgr : m_ManagersData) { datparser->Write(&mgr.Manager); CKDWORD writtenSize = 0; if (mgr.Data != nullptr) { writtenSize = mgr.Data->ConvertToBuffer(datparser->GetMutablePtr(CKSizeof(CKDWORD))); delete mgr.Data; mgr.Data = nullptr; } datparser->Write(&writtenSize); datparser->MoveCursor(writtenSize); } // write object for (auto& obj : m_FileObjects) { datparser->Write(&obj.PackSize); if (obj.Data != nullptr) { obj.Data->ConvertToBuffer(datparser->GetMutablePtr()); delete obj.Data; obj.Data = nullptr; } datparser->MoveCursor(obj.PackSize); } // compress header if needed if (EnumsHelper::Has(fileWriteMode, CK_FILE_WRITEMODE::CKFILE_CHUNKCOMPRESSED_OLD) || EnumsHelper::Has(fileWriteMode, CK_FILE_WRITEMODE::CKFILE_WHOLECOMPRESSED)) { CKDWORD comp_buf_size = 0; void* comp_buffer = CKPackData(datparser->GetBase(), datparser->GetSize(), comp_buf_size, m_Ctx->GetCompressionLevel()); if (comp_buffer != nullptr) { datparser = std::unique_ptr(new CKBufferParser(comp_buffer, comp_buf_size, true)); rawHeader.DataPackSize = comp_buf_size; } else { // fallback rawHeader.DataPackSize = rawHeader.DataUnPackSize; } } // ========== Construct File Info ========== // compute crc CKDWORD computedcrc = CKComputeDataCRC(&rawHeader, CKSizeof(CKRawFileInfo), 0u); computedcrc = CKComputeDataCRC(hdrparser->GetBase(), hdrparser->GetSize(), computedcrc); computedcrc = CKComputeDataCRC(datparser->GetBase(), datparser->GetSize(), computedcrc); // copy to file info this->m_FileInfo.ProductVersion = rawHeader.ProductVersion; this->m_FileInfo.ProductBuild = rawHeader.ProductBuild; this->m_FileInfo.FileWriteMode = static_cast(rawHeader.FileWriteMode); this->m_FileInfo.CKVersion = rawHeader.CKVersion; this->m_FileInfo.FileVersion = rawHeader.FileVersion; this->m_FileInfo.Hdr1PackSize = rawHeader.Hdr1PackSize; this->m_FileInfo.Hdr1UnPackSize = rawHeader.Hdr1UnPackSize; this->m_FileInfo.DataPackSize = rawHeader.DataPackSize; this->m_FileInfo.DataUnPackSize = rawHeader.DataUnPackSize; this->m_FileInfo.ManagerCount = rawHeader.ManagerCount; this->m_FileInfo.ObjectCount = rawHeader.ObjectCount; this->m_FileInfo.MaxIDSaved = static_cast(rawHeader.MaxIDSaved); // fill file size and crc this->m_FileInfo.FileSize = CKSizeof(CKRawFileInfo) + rawHeader.DataPackSize + rawHeader.Hdr1PackSize; this->m_FileInfo.Crc = computedcrc; rawHeader.Crc = computedcrc; // ========== Open File & Write Essential Data ========== // open file and test FILE* fs = YYCC::IOHelper::UTF8FOpen(u8_filename, u8"wb"); if (fs == nullptr) return CKERROR::CKERR_CANTWRITETOFILE; // write small header + header + data std::fwrite(&rawHeader, sizeof(CKRawFileInfo), 1, fs); std::fwrite(hdrparser->GetBase(), sizeof(CKBYTE), hdrparser->GetSize(), fs); std::fwrite(datparser->GetBase(), sizeof(CKBYTE), datparser->GetSize(), fs); // free buffer hdrparser.reset(); datparser.reset(); // ========== Included Files ========== for (const auto& fentry : m_IncludedFiles) { // get file name from full path XContainer::XString filename(fentry); m_Ctx->GetPathManager()->GetFileName(filename); // write filename if (!m_Ctx->GetOrdinaryString(filename, name_conv)) m_Ctx->OutputToConsole(u8"Fail to get ordinary string for included file when saving file. Some included files may not be saved correctly."); CKDWORD filenamelen = static_cast(name_conv.size()); std::fwrite(&filenamelen, sizeof(CKDWORD), 1, fs); std::fwrite(name_conv.data(), sizeof(CKBYTE), filenamelen, fs); // try mapping file. std::unique_ptr mappedFile(new VxMath::VxMemoryMappedFile(fentry.c_str())); if (mappedFile->IsValid()) { // write file length CKDWORD filebodylen = mappedFile->GetFileSize(); std::fwrite(&filebodylen, sizeof(CKDWORD), 1, fs); // write file body std::fwrite(mappedFile->GetBase(), sizeof(CKBYTE), filebodylen, fs); } else { // write zero file length CKDWORD filebodylen = 0; std::fwrite(&filebodylen, sizeof(CKDWORD), 1, fs); // report error m_Ctx->OutputToConsoleEx(u8"Fail to open temp file: %" PRI_CKSTRING, fentry.c_str()); } // release mapped file mappedFile.reset(); } // close file std::fclose(fs); // set done flag and return this->m_Done = true; return CKERROR::CKERR_OK; } CKERROR CKFileWriter::PrepareFile(CKSTRING filename) { // check nullptr if (filename == nullptr) return CKERROR::CKERR_INVALIDFILE; // try open file to check whether we can write it. CKERROR err; FILE* tryfile = YYCC::IOHelper::UTF8FOpen(filename, u8"ab"); if (tryfile == nullptr) { err = CKERROR::CKERR_CANTWRITETOFILE; } else { err = CKERROR::CKERR_OK; std::fclose(tryfile); } return err; } }