libcmo21/LibCmo/CK2/CKFileReader.cpp

393 lines
13 KiB
C++
Raw Normal View History

2023-02-25 17:39:39 +08:00
#include "CKFile.hpp"
2023-02-26 21:48:03 +08:00
#include "CKStateChunk.hpp"
#include "ObjImpls/CKObject.hpp"
2023-09-06 10:42:23 +08:00
#include "MgrImpls/CKPathManager.hpp"
2023-08-25 17:35:45 +08:00
#include "../VxMath/VxMemoryMappedFile.hpp"
#include "CKContext.hpp"
2023-02-25 17:39:39 +08:00
#include <memory>
2023-02-26 21:48:03 +08:00
namespace LibCmo::CK2 {
2023-02-25 17:39:39 +08:00
/*
* NOTE:
* We onlt support read Virtools file with FileVersion >= 7.
* The file with FileVersion < 7 is older than NeMo 1.0 (CK 1.1).
* No need to support them.
*/
2023-08-25 21:57:22 +08:00
CKERROR CKFileReader::ShallowLoad(CKSTRING u8_filename) {
// check document status
2023-08-28 17:04:28 +08:00
if (this->m_Done) CKERROR::CKERR_CANCELLED;
2023-02-25 22:58:28 +08:00
// check file and open memory
if (u8_filename == nullptr) return CKERROR::CKERR_INVALIDPARAMETER;
2023-08-25 21:57:22 +08:00
std::unique_ptr<VxMath::VxMemoryMappedFile> mappedFile(new VxMath::VxMemoryMappedFile(u8_filename));
2023-02-25 22:58:28 +08:00
if (!mappedFile->IsValid()) {
2023-08-25 21:57:22 +08:00
this->m_Ctx->OutputToConsoleEx("Fail to create Memory File for \"%s\".", u8_filename);
2023-02-25 17:39:39 +08:00
return CKERROR::CKERR_INVALIDFILE;
}
// create buffer and start loading
2023-08-25 21:57:22 +08:00
std::unique_ptr<CKBufferParser> parser(new CKBufferParser(mappedFile->GetBase(), mappedFile->GetFileSize(), false));
CKERROR err = this->ReadFileHeader(parser.get());
2023-02-25 17:39:39 +08:00
if (err != CKERROR::CKERR_OK) return err;
2023-08-25 21:57:22 +08:00
err = this->ReadFileData(parser.get());
2023-02-25 17:39:39 +08:00
if (err != CKERROR::CKERR_OK) return err;
2023-02-25 22:58:28 +08:00
// other data will be free automatically
2023-02-25 17:39:39 +08:00
return CKERROR::CKERR_OK;
}
2023-08-25 21:57:22 +08:00
CKERROR CKFileReader::ReadFileHeader(CKBufferParser* ParserPtr) {
std::unique_ptr<CKBufferParser> parser(new CKBufferParser(ParserPtr->GetBase(), ParserPtr->GetSize(), false));
2023-02-25 22:58:28 +08:00
parser->SetCursor(ParserPtr->GetCursor());
2023-08-25 21:57:22 +08:00
std::string name_conv, name_dest;
2023-02-25 17:39:39 +08:00
// ========== read header ==========
// check header size
if (parser->GetSize() < CKSizeof(CKRawFileInfo)) return CKERROR::CKERR_INVALIDFILE;
2023-08-28 17:04:28 +08:00
if (std::memcmp(parser->GetPtr(), CKNEMOFI, sizeof(CKRawFileInfo::NeMo))) return CKERROR::CKERR_INVALIDFILE;
2023-02-25 17:39:39 +08:00
// read header
CKRawFileInfo rawHeader;
parser->Read(&rawHeader, sizeof(CKRawFileInfo));
// ========== header checker ==========
// check zero flag?
if (rawHeader.Zero) return CKERROR::CKERR_INVALIDFILE;
// check file version
if (rawHeader.FileVersion > 9 || rawHeader.FileVersion < 7) return CKERROR::CKERR_OBSOLETEVIRTOOLS;
// force reset too big product ver?
if (rawHeader.ProductVersion >= 12u) {
rawHeader.ProductVersion = 0u;
2023-02-28 14:04:38 +08:00
rawHeader.ProductBuild = 0x01010000u;
2023-02-25 17:39:39 +08:00
}
// ========== assign value ==========
2023-08-25 21:57:22 +08:00
this->m_FileInfo.ProductVersion = rawHeader.ProductVersion;
this->m_FileInfo.ProductBuild = rawHeader.ProductBuild;
this->m_FileInfo.FileWriteMode = static_cast<CK_FILE_WRITEMODE>(rawHeader.FileWriteMode);
this->m_FileInfo.CKVersion = rawHeader.CKVersion;
this->m_FileInfo.FileVersion = rawHeader.FileVersion;
2023-08-30 10:03:02 +08:00
this->m_FileInfo.FileSize = parser->GetSize();
2023-08-25 21:57:22 +08:00
this->m_FileInfo.ManagerCount = rawHeader.ManagerCount;
this->m_FileInfo.ObjectCount = rawHeader.ObjectCount;
this->m_FileInfo.MaxIDSaved = rawHeader.MaxIDSaved;
this->m_FileInfo.Hdr1PackSize = rawHeader.FileVersion >= 8 ? rawHeader.Hdr1PackSize : 0u;
this->m_FileInfo.Hdr1UnPackSize = rawHeader.FileVersion >= 8 ? rawHeader.Hdr1UnPackSize : 0u;
this->m_FileInfo.DataPackSize = rawHeader.DataPackSize;
this->m_FileInfo.DataUnPackSize = rawHeader.DataUnPackSize;
this->m_FileInfo.Crc = rawHeader.Crc;
2023-02-25 17:39:39 +08:00
// ========== crc and body unpacker ==========
2023-08-25 21:57:22 +08:00
if (this->m_FileInfo.FileVersion >= 8) {
2023-02-25 17:39:39 +08:00
// crc checker for file ver >= 8
// reset crc field of header
rawHeader.Crc = 0u;
// compute crc
CKDWORD gotten_crc = CKComputeDataCRC(&rawHeader, CKSizeof(CKRawFileInfo), 0u);
2023-02-25 17:39:39 +08:00
parser->SetCursor(sizeof(CKRawFileInfo));
2023-08-25 21:57:22 +08:00
gotten_crc = CKComputeDataCRC(parser->GetPtr(), this->m_FileInfo.Hdr1PackSize, gotten_crc);
parser->MoveCursor(this->m_FileInfo.Hdr1PackSize);
gotten_crc = CKComputeDataCRC(parser->GetPtr(), this->m_FileInfo.DataPackSize, gotten_crc);
2023-02-25 17:39:39 +08:00
2023-08-25 21:57:22 +08:00
if (gotten_crc != this->m_FileInfo.Crc) {
this->m_Ctx->OutputToConsole("Virtools file CRC error.");
2023-02-25 22:58:28 +08:00
return CKERROR::CKERR_FILECRCERROR;
}
2023-02-25 17:39:39 +08:00
// reset cursor
parser->SetCursor(sizeof(CKRawFileInfo));
// compare size to decide wheher use compress feature
2023-08-28 21:21:40 +08:00
if (this->m_FileInfo.Hdr1PackSize != this->m_FileInfo.Hdr1UnPackSize) {
void* decomp_buffer = CKUnPackData(this->m_FileInfo.Hdr1UnPackSize, parser->GetPtr(), this->m_FileInfo.Hdr1PackSize);
if (decomp_buffer != nullptr) {
parser = std::unique_ptr<CKBufferParser>(new CKBufferParser(decomp_buffer, this->m_FileInfo.Hdr1UnPackSize, true));
}
2023-02-25 17:39:39 +08:00
}
}
// ========== object list read ==========
// file ver >= 7 have this features
2023-02-25 22:58:28 +08:00
{
2023-02-25 17:39:39 +08:00
// apply max id saved
2023-08-25 21:57:22 +08:00
this->m_SaveIDMax = this->m_FileInfo.MaxIDSaved;
2023-02-25 17:39:39 +08:00
// resize
2023-08-25 21:57:22 +08:00
this->m_FileObjects.resize(this->m_FileInfo.ObjectCount);
2023-02-25 17:39:39 +08:00
// read data
2023-08-25 21:57:22 +08:00
for (auto& fileobj : this->m_FileObjects) {
2023-02-25 17:39:39 +08:00
// read basic fields
parser->Read(&(fileobj.ObjectId), sizeof(CK_ID));
parser->Read(&(fileobj.ObjectCid), sizeof(CK_CLASSID));
parser->Read(&(fileobj.FileIndex), sizeof(CKDWORD));
CKDWORD namelen;
parser->Read(&namelen, sizeof(CKDWORD));
if (namelen != 0) {
2023-02-25 22:58:28 +08:00
name_conv.resize(namelen);
parser->Read(name_conv.data(), namelen);
2023-08-25 21:57:22 +08:00
m_Ctx->GetUtf8String(name_conv, name_dest);
fileobj.Name = name_dest;
2023-02-25 17:39:39 +08:00
}
}
}
// ========== dep list read ==========
// file ver >= 8 have this feature
2023-08-25 21:57:22 +08:00
if (this->m_FileInfo.FileVersion >= 8) {
2023-02-25 17:39:39 +08:00
// get size and resize
CKDWORD depSize;
parser->Read(&depSize, sizeof(CKDWORD));
2023-08-25 21:57:22 +08:00
this->m_PluginsDep.resize(depSize);
2023-02-25 17:39:39 +08:00
CKDWORD guid_size;
2023-08-25 21:57:22 +08:00
for (auto& dep : this->m_PluginsDep) {
2023-02-25 17:39:39 +08:00
// read category
parser->Read(&(dep.m_PluginCategory), sizeof(CK_PLUGIN_TYPE));
// get size and resize
parser->Read(&guid_size, sizeof(CKDWORD));
dep.m_Guids.resize(guid_size);
// read data
if (guid_size != 0) {
parser->Read(dep.m_Guids.data(), sizeof(CKGUID) * guid_size);
}
}
}
// ========== included file list read ==========
// file ver >= 8 have this feature
2023-08-25 21:57:22 +08:00
if (this->m_FileInfo.FileVersion >= 8) {
2023-02-25 17:39:39 +08:00
// MARK: i don't knwo what is this!
int32_t hasIncludedFile;
parser->Read(&hasIncludedFile, sizeof(int32_t));
if (hasIncludedFile > 0) {
// read included file size and resize
CKDWORD includedFileCount;
parser->Read(&includedFileCount, sizeof(CKDWORD));
2023-08-25 21:57:22 +08:00
this->m_IncludedFiles.resize(includedFileCount);
2023-02-25 17:39:39 +08:00
hasIncludedFile -= static_cast<int32_t>(sizeof(CKDWORD));
2023-02-25 17:39:39 +08:00
}
2023-02-25 22:58:28 +08:00
// MARK: backward pos
// backward with 0?
parser->MoveCursor(hasIncludedFile);
}
// ========== sync main parser ==========
2023-08-25 21:57:22 +08:00
if (this->m_FileInfo.FileVersion >= 8) {
2023-02-25 22:58:28 +08:00
// file ver >= 8, use header offset
// because it have compress feature
2023-08-25 21:57:22 +08:00
ParserPtr->SetCursor(this->m_FileInfo.Hdr1PackSize + sizeof(CKRawFileInfo));
2023-02-25 22:58:28 +08:00
} else {
// otherwise, sync with current parser.
ParserPtr->SetCursor(parser->GetCursor());
2023-02-25 17:39:39 +08:00
}
return CKERROR::CKERR_OK;
}
2023-08-25 21:57:22 +08:00
CKERROR CKFileReader::ReadFileData(CKBufferParser* ParserPtr) {
std::unique_ptr<CKBufferParser> parser(new CKBufferParser(ParserPtr->GetBase(), ParserPtr->GetSize(), false));
2023-02-25 22:58:28 +08:00
parser->SetCursor(ParserPtr->GetCursor());
std::string name_conv;
// ========== compress feature process ==========
2023-08-25 21:57:22 +08:00
if (EnumsHelper::Has(this->m_FileInfo.FileWriteMode, CK_FILE_WRITEMODE::CKFILE_CHUNKCOMPRESSED_OLD) ||
EnumsHelper::Has(this->m_FileInfo.FileWriteMode, CK_FILE_WRITEMODE::CKFILE_WHOLECOMPRESSED)) {
2023-02-25 22:58:28 +08:00
2023-08-25 21:57:22 +08:00
void* decomp_buffer = CKUnPackData(this->m_FileInfo.DataUnPackSize, parser->GetPtr(), this->m_FileInfo.DataPackSize);
2023-02-25 22:58:28 +08:00
if (decomp_buffer != nullptr) {
2023-08-25 21:57:22 +08:00
parser = std::unique_ptr<CKBufferParser>(new CKBufferParser(decomp_buffer, this->m_FileInfo.DataUnPackSize, true));
2023-02-25 22:58:28 +08:00
}
}
// ========== old file crc and obj list read ==========
// only file ver < 8 run this
2023-08-25 21:57:22 +08:00
if (this->m_FileInfo.FileVersion < 8) {
2023-02-25 22:58:28 +08:00
// check crc
CKDWORD gotten_crc = CKComputeDataCRC(
parser->GetPtr(),
parser->GetSize() - parser->GetCursor(),
0u
);
2023-08-25 21:57:22 +08:00
if (gotten_crc != this->m_FileInfo.Crc) {
this->m_Ctx->OutputToConsole("Virtools file CRC error.");
2023-02-25 22:58:28 +08:00
return CKERROR::CKERR_FILECRCERROR;
}
// MARK: why read again? especially for file ver == 7.
// get save id max
2023-08-25 21:57:22 +08:00
parser->Read(&this->m_SaveIDMax, sizeof(int32_t));
2023-02-25 22:58:28 +08:00
// get object count and resize
2023-08-25 21:57:22 +08:00
parser->Read(&this->m_FileInfo.ObjectCount, sizeof(CKDWORD));
if (this->m_FileObjects.empty()) {
this->m_FileObjects.resize(this->m_FileInfo.ObjectCount);
2023-02-25 22:58:28 +08:00
}
}
// ========== manager read ==========
// only file ver >= 6 have this
2023-08-25 21:57:22 +08:00
if (this->m_FileInfo.ManagerCount != 0) {
this->m_ManagersData.resize(this->m_FileInfo.ManagerCount);
2023-02-25 22:58:28 +08:00
CKDWORD stateChunkLen = 0u;
bool stateChkParseSuccess = false;
2023-08-25 21:57:22 +08:00
for (auto& mgr : this->m_ManagersData) {
2023-02-25 22:58:28 +08:00
// read guid
parser->Read(&(mgr.Manager), sizeof(CKGUID));
// read statechunk len
parser->Read(&stateChunkLen, sizeof(CKDWORD));
// check len
if (stateChunkLen == 0) {
mgr.Data = nullptr;
continue;
}
// read statechunk
2023-08-25 21:57:22 +08:00
mgr.Data = new CKStateChunk(&this->m_Visitor, this->m_Ctx);
stateChkParseSuccess = mgr.Data->ConvertFromBuffer(parser->GetPtr());
if (!stateChkParseSuccess) {
delete mgr.Data;
mgr.Data = nullptr;
2023-02-25 22:58:28 +08:00
}
parser->MoveCursor(stateChunkLen);
}
}
// ========== object read ==========
// only works file version >= 4. < 4 section has been removed.
2023-08-25 21:57:22 +08:00
if (this->m_FileInfo.ObjectCount != 0) {
2023-02-25 22:58:28 +08:00
// new file reader section
bool stateChkParseSuccess = false;
2023-08-25 21:57:22 +08:00
for (auto& obj : this->m_FileObjects) {
2023-02-25 22:58:28 +08:00
// get statechunk len
2023-08-28 17:04:28 +08:00
parser->Read(&obj.PackSize, sizeof(CKDWORD));
2023-02-25 22:58:28 +08:00
// check state chunk len
2023-08-28 17:04:28 +08:00
if (obj.PackSize == 0) {
2023-02-25 22:58:28 +08:00
obj.Data = nullptr;
continue;
}
// read state chunk
2023-08-25 21:57:22 +08:00
obj.Data = new CKStateChunk(&this->m_Visitor, this->m_Ctx);
stateChkParseSuccess = obj.Data->ConvertFromBuffer(parser->GetPtr());
if (!stateChkParseSuccess) {
delete obj.Data;
obj.Data = nullptr;
2023-02-25 22:58:28 +08:00
}
2023-08-28 17:04:28 +08:00
parser->MoveCursor(obj.PackSize);
2023-02-25 22:58:28 +08:00
}
}
// ========== included file get ==========
// before reading, we need switch back to original parser.
// and skip data chunk size
parser = std::unique_ptr<CKBufferParser>(new CKBufferParser(ParserPtr->GetBase(), ParserPtr->GetSize(), false));
2023-08-30 10:03:02 +08:00
parser->SetCursor(ParserPtr->GetCursor());
2023-08-25 21:57:22 +08:00
parser->MoveCursor(this->m_FileInfo.DataPackSize);
2023-02-25 22:58:28 +08:00
// then we can read it.
2023-08-25 21:57:22 +08:00
if (this->m_IncludedFiles.size() != 0) {
for (auto& file : this->m_IncludedFiles) {
2023-02-25 22:58:28 +08:00
// get file name length and resize it
CKDWORD filenamelen = 0u;
parser->Read(&filenamelen, sizeof(CKDWORD));
name_conv.resize(filenamelen);
// read filename
if (filenamelen != 0) {
parser->Read(name_conv.data(), filenamelen);
2023-08-25 21:57:22 +08:00
m_Ctx->GetUtf8String(name_conv, file);
2023-02-25 22:58:28 +08:00
}
// read file body length
CKDWORD filebodylen = 0u;
parser->Read(&filebodylen, sizeof(CKDWORD));
// read file body
2023-09-06 10:42:23 +08:00
std::string tempfilename = m_Ctx->GetPathManager()->GetTempFilePath(file.c_str());
2023-08-30 10:03:02 +08:00
FILE* fp = EncodingHelper::U8FOpen(tempfilename.c_str(), "wb");
2023-02-25 22:58:28 +08:00
if (fp != nullptr) {
std::fwrite(parser->GetPtr(), sizeof(char), filebodylen, fp);
std::fclose(fp);
2023-08-30 10:03:02 +08:00
} else {
m_Ctx->OutputToConsoleEx("Fail to open temp file: %s", tempfilename.c_str());
2023-02-25 22:58:28 +08:00
}
// move to next
parser->MoveCursor(filebodylen);
}
}
return CKERROR::CKERR_OK;
2023-02-25 17:39:39 +08:00
}
2023-08-25 21:57:22 +08:00
CKERROR CKFileReader::DeepLoad(CKSTRING u8_filename) {
2023-08-28 17:04:28 +08:00
// check document status
if (this->m_Done) CKERROR::CKERR_CANCELLED;
2023-02-28 11:35:54 +08:00
// ========== prepare work ==========
2023-03-03 11:06:26 +08:00
CKERROR err = CKERROR::CKERR_OK;
2023-02-28 11:35:54 +08:00
// get shallow document first
2023-08-25 21:57:22 +08:00
err = this->ShallowLoad(u8_filename);
2023-02-28 11:35:54 +08:00
if (err != CKERROR::CKERR_OK) return err;
// ========== create object first ==========
2023-08-25 21:57:22 +08:00
for (auto& obj : this->m_FileObjects) {
2023-02-28 11:35:54 +08:00
// todo: skip CK_LEVEL
// todo: resolve references
if (obj.Data == nullptr) continue;
// create object and assign created obj ckid
2023-09-04 22:58:53 +08:00
obj.ObjPtr = m_Ctx->CreateObject(obj.ObjectCid, obj.Name.toCKSTRING());
if (obj.ObjPtr == nullptr) {
2023-08-25 21:57:22 +08:00
obj.CreatedObjectId = 0u;
} else {
2023-08-25 21:57:22 +08:00
obj.CreatedObjectId = obj.ObjPtr->GetID();
}
2023-02-28 11:35:54 +08:00
}
// ========== CKStateChunk remap ==========
// todo: remap
// todo: CK_LEVEL special proc
// ========== consume Managers ==========
// todo...
// ========== analyze objects CKStateChunk ==========
2023-08-25 21:57:22 +08:00
for (auto& obj : this->m_FileObjects) {
2023-03-03 11:06:26 +08:00
if (obj.Data == nullptr || obj.ObjPtr == nullptr) continue;
2023-02-28 11:35:54 +08:00
// todo: special treat for CK_LEVEL
// try parsing data
2023-02-28 14:04:38 +08:00
obj.Data->StartRead();
2023-08-25 21:57:22 +08:00
bool success = obj.ObjPtr->Load(obj.Data, &this->m_Visitor);
2023-02-28 14:04:38 +08:00
obj.Data->StopRead();
2023-03-05 22:31:11 +08:00
if (success) {
2023-03-03 11:06:26 +08:00
// if success, clear CKStateChunk*
delete obj.Data;
obj.Data = nullptr;
2023-03-05 22:31:11 +08:00
} else {
// if failed, delete it
2023-09-01 13:27:46 +08:00
m_Ctx->DestroyObject(obj.ObjectId);
2023-03-05 22:31:11 +08:00
obj.ObjPtr = nullptr;
2023-08-25 21:57:22 +08:00
obj.CreatedObjectId = 0u;
2023-02-28 11:35:54 +08:00
}
}
// ========== finalize work ==========
2023-02-26 13:57:32 +08:00
return CKERROR::CKERR_OK;
}
2023-02-25 17:39:39 +08:00
}