2023-02-25 17:39:39 +08:00
# include "CKFile.hpp"
2023-08-28 17:04:28 +08:00
# include "CKContext.hpp"
# include "CKStateChunk.hpp"
# include "ObjImpls/CKObject.hpp"
# include "MgrImpls/CKBaseManager.hpp"
2023-09-06 10:42:23 +08:00
# include "MgrImpls/CKPathManager.hpp"
2023-08-28 17:04:28 +08:00
# include "../VxMath/VxMemoryMappedFile.hpp"
# include <memory>
2023-02-25 17:39:39 +08:00
2023-08-25 17:35:45 +08:00
namespace LibCmo : : CK2 {
2023-02-25 17:39:39 +08:00
2023-08-25 21:57:22 +08:00
CKERROR CKFileWriter : : Save ( CKSTRING u8_filename ) {
2023-08-28 17:04:28 +08:00
// check document status
2023-09-26 12:07:13 +08:00
if ( this - > m_Done ) return CKERROR : : CKERR_CANCELLED ;
2024-08-23 17:38:45 +08:00
// check CKContext encoding sequence
if ( ! this - > m_Ctx - > IsValidEncoding ( ) ) return CKERROR : : CKERR_CANCELLED ;
2023-08-28 17:04:28 +08:00
2023-08-28 21:21:40 +08:00
// encoding conv helper
2024-08-23 11:28:49 +08:00
std : : string name_conv ;
2023-08-28 21:21:40 +08:00
2023-08-28 17:04:28 +08:00
// try detect filename legality
CKERROR err = PrepareFile ( u8_filename ) ;
if ( err ! = CKERROR : : CKERR_OK ) return err ;
2023-08-28 21:21:40 +08:00
2023-08-28 17:04:28 +08:00
// ========== Prepare Stage ==========
// todo: add TOBEDELETED flag for all Referenced objects's m_ObjectFlags
// MARK: ignore the notification to all CKBehavior based objects.
// ========== StateChunk convertion ==========
2023-08-28 21:21:40 +08:00
2023-08-28 17:04:28 +08:00
// 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
2023-09-30 14:24:37 +08:00
// if object adding is disabled, skip. because in that mode, no need to query manager data.
if ( ! m_DisableAddingObject ) {
2023-08-28 17:04:28 +08:00
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 ) ;
}
2023-09-30 14:24:37 +08:00
// if object adding is disabled, skip plugin dep. same reason with manager iteration.
if ( ! m_DisableAddingObject ) {
2023-08-28 17:04:28 +08:00
// 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 ) {
2023-08-28 22:20:46 +08:00
// += 4DWORD(ObjId, ObjCid, FileIndex, NameLen)
2023-08-29 14:00:34 +08:00
sumHdrObjSize + = 4 * CKSizeof ( CKDWORD ) ;
2023-09-16 18:31:25 +08:00
if ( XContainer : : NSXString : : ToCKSTRING ( obj . Name ) ! = nullptr ) {
2023-08-28 22:20:46 +08:00
// += Name size
2024-08-27 11:25:53 +08:00
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. " ) ;
2023-08-29 14:00:34 +08:00
sumHdrObjSize + = static_cast < CKDWORD > ( name_conv . size ( ) ) ;
2023-08-28 22:20:46 +08:00
}
2023-08-28 17:04:28 +08:00
if ( obj . Data = = nullptr ) {
obj . PackSize = 0 ;
} else {
obj . PackSize = obj . Data - > ConvertToBuffer ( nullptr ) ;
}
// += chunk size + chunk
2023-08-29 14:00:34 +08:00
sumDataObjSize + = obj . PackSize + CKSizeof ( CKDWORD ) ;
2023-08-28 17:04:28 +08:00
}
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
2023-08-29 14:00:34 +08:00
sumDataMgrSize + = chunksize + 3 * CKSizeof ( CKDWORD ) ;
2023-08-28 17:04:28 +08:00
}
// += Plugin Dep list size
2023-08-29 14:00:34 +08:00
CKDWORD sumHdrPlgSize = CKSizeof ( CKDWORD ) ;
2023-08-28 17:04:28 +08:00
for ( auto & plg : m_PluginsDep ) {
// += GUID list + (dep category + GUID list size)
2023-08-29 14:00:34 +08:00
sumHdrPlgSize + =
CKSizeof ( CKGUID ) * static_cast < CKDWORD > ( plg . m_Guids . size ( ) )
+ 2 * CKSizeof ( CKDWORD ) ;
2023-08-28 17:04:28 +08:00
}
2023-09-17 10:38:46 +08:00
CKDWORD sumHdrIncludedFiles = CKSizeof ( CKINT ) + CKSizeof ( CKDWORD ) ;
2023-08-28 17:04:28 +08:00
// 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
2023-08-29 14:00:34 +08:00
m_FileObjects [ 0 ] . FileIndex = sumHdrSize + sumDataMgrSize + CKSizeof ( CKRawFileInfo ) ;
2023-08-28 17:04:28 +08:00
// calc the remains
for ( size_t i = 1 ; i < m_FileObjects . size ( ) ; + + i ) {
// prev obj PackSize + prev obj FileIndex + prev obj chunk size
2023-08-29 14:00:34 +08:00
m_FileObjects [ i ] . FileIndex = m_FileObjects [ i - 1 ] . FileIndex + m_FileObjects [ i - 1 ] . PackSize + CKSizeof ( CKDWORD ) ;
2023-08-28 17:04:28 +08:00
}
}
// ========== Construct File Header ==========
2023-08-28 21:21:40 +08:00
CK_FILE_WRITEMODE fileWriteMode = m_Ctx - > GetFileWriteMode ( ) ;
2023-08-28 17:04:28 +08:00
CKRawFileInfo rawHeader ;
std : : memcpy ( & rawHeader . NeMo , CKNEMOFI , sizeof ( CKRawFileInfo : : NeMo ) ) ;
rawHeader . Crc = 0 ;
rawHeader . Zero = 0 ;
rawHeader . CKVersion = CKVERSION ;
rawHeader . FileVersion = 8 ;
2023-08-28 21:21:40 +08:00
rawHeader . FileWriteMode = static_cast < CKDWORD > ( fileWriteMode ) ;
2023-08-28 17:04:28 +08:00
rawHeader . ObjectCount = static_cast < CKDWORD > ( m_FileObjects . size ( ) ) ;
rawHeader . ManagerCount = static_cast < CKDWORD > ( m_ManagersData . size ( ) ) ;
rawHeader . Hdr1UnPackSize = sumHdrSize ;
rawHeader . DataUnPackSize = sumDataSize ;
rawHeader . Hdr1PackSize = sumHdrSize ;
rawHeader . DataPackSize = sumDataSize ;
rawHeader . ProductVersion = DEVVERSION ;
rawHeader . ProductBuild = DEVBUILD ;
2023-09-30 14:24:37 +08:00
rawHeader . MaxIDSaved = static_cast < CKDWORD > ( m_SaveIDMax ) ;
2023-08-28 17:04:28 +08:00
// crc will filled later
2023-08-28 21:21:40 +08:00
// ========== Write header ==========
2023-08-28 17:04:28 +08:00
// create a buffer
std : : unique_ptr < CKBufferParser > hdrparser ( new CKBufferParser ( sumHdrSize ) ) ;
// write obj
for ( auto & obj : m_FileObjects ) {
// todo: remove TOBEDELETED for referenced objects' m_ObjectFlags
2023-09-17 10:38:46 +08:00
hdrparser - > Write ( & obj . ObjectId ) ;
hdrparser - > Write ( & obj . ObjectCid ) ;
hdrparser - > Write ( & obj . FileIndex ) ;
2023-08-28 17:04:28 +08:00
2023-09-16 18:31:25 +08:00
if ( XContainer : : NSXString : : ToCKSTRING ( obj . Name ) ! = nullptr ) {
2023-11-30 22:48:40 +08:00
// if have name, write it
2024-08-27 11:25:53 +08:00
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. " ) ;
2023-08-28 17:04:28 +08:00
CKDWORD namelen = static_cast < CKDWORD > ( name_conv . size ( ) ) ;
2023-09-17 10:38:46 +08:00
hdrparser - > Write ( & namelen ) ;
2023-08-28 17:04:28 +08:00
hdrparser - > Write ( name_conv . data ( ) , namelen ) ;
2023-11-30 22:48:40 +08:00
} else {
// otherwise, write 0 to indicate no name
CKDWORD namelen = 0 ;
hdrparser - > Write ( & namelen ) ;
2023-08-28 17:04:28 +08:00
}
}
// write plugin dep
{
CKDWORD depsize = static_cast < CKDWORD > ( m_PluginsDep . size ( ) ) ;
2023-09-17 10:38:46 +08:00
hdrparser - > Write ( & depsize ) ;
2023-08-28 17:04:28 +08:00
for ( auto & dep : m_PluginsDep ) {
hdrparser - > Write ( & dep . m_PluginCategory , sizeof ( CK_PLUGIN_TYPE ) ) ;
CKDWORD guidsize = static_cast < CKDWORD > ( dep . m_Guids . size ( ) ) ;
2023-09-17 10:38:46 +08:00
hdrparser - > Write ( & guidsize ) ;
2023-08-28 17:04:28 +08:00
hdrparser - > Write ( dep . m_Guids . data ( ) , sizeof ( CKGUID ) * guidsize ) ;
}
}
2023-08-28 21:21:40 +08:00
// write included file
{
2023-08-29 14:00:34 +08:00
CKDWORD cache = CKSizeof ( CKDWORD ) ;
2023-09-17 10:38:46 +08:00
hdrparser - > Write ( & cache ) ;
2023-08-28 21:21:40 +08:00
cache = static_cast < CKDWORD > ( m_IncludedFiles . size ( ) ) ;
2023-09-17 10:38:46 +08:00
hdrparser - > Write ( & cache ) ;
2023-08-28 21:21:40 +08:00
}
// 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 ;
2023-08-30 10:03:02 +08:00
void * comp_buffer = CKPackData ( hdrparser - > GetBase ( ) , hdrparser - > GetSize ( ) , comp_buf_size , m_Ctx - > GetCompressionLevel ( ) ) ;
2023-08-28 21:21:40 +08:00
if ( comp_buffer ! = nullptr ) {
hdrparser = std : : unique_ptr < CKBufferParser > ( 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 < CKBufferParser > datparser ( new CKBufferParser ( sumDataSize ) ) ;
// write manager
for ( auto & mgr : m_ManagersData ) {
2023-09-17 10:38:46 +08:00
datparser - > Write ( & mgr . Manager ) ;
2023-08-28 21:21:40 +08:00
CKDWORD writtenSize = 0 ;
if ( mgr . Data ! = nullptr ) {
2023-09-17 10:38:46 +08:00
writtenSize = mgr . Data - > ConvertToBuffer ( datparser - > GetMutablePtr ( CKSizeof ( CKDWORD ) ) ) ;
2023-08-28 21:21:40 +08:00
delete mgr . Data ;
mgr . Data = nullptr ;
}
2023-09-17 10:38:46 +08:00
datparser - > Write ( & writtenSize ) ;
2023-08-28 21:21:40 +08:00
datparser - > MoveCursor ( writtenSize ) ;
}
// write object
for ( auto & obj : m_FileObjects ) {
2023-09-17 10:38:46 +08:00
datparser - > Write ( & obj . PackSize ) ;
2023-08-28 21:21:40 +08:00
if ( obj . Data ! = nullptr ) {
obj . Data - > ConvertToBuffer ( datparser - > GetMutablePtr ( ) ) ;
delete obj . Data ;
obj . Data = nullptr ;
}
datparser - > MoveCursor ( obj . PackSize ) ;
}
2023-08-29 14:00:34 +08:00
2023-08-28 21:21:40 +08:00
// 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 ;
2023-08-30 10:03:02 +08:00
void * comp_buffer = CKPackData ( datparser - > GetBase ( ) , datparser - > GetSize ( ) , comp_buf_size , m_Ctx - > GetCompressionLevel ( ) ) ;
2023-08-28 21:21:40 +08:00
if ( comp_buffer ! = nullptr ) {
datparser = std : : unique_ptr < CKBufferParser > ( new CKBufferParser ( comp_buffer , comp_buf_size , true ) ) ;
rawHeader . DataPackSize = comp_buf_size ;
} else {
// fallback
rawHeader . DataPackSize = rawHeader . DataUnPackSize ;
}
}
// ========== Construct File Info ==========
// compute crc
2023-08-29 14:00:34 +08:00
CKDWORD computedcrc = CKComputeDataCRC ( & rawHeader , CKSizeof ( CKRawFileInfo ) , 0u ) ;
2023-08-28 21:21:40 +08:00
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 < CK_FILE_WRITEMODE > ( 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 ;
2023-09-30 14:24:37 +08:00
this - > m_FileInfo . MaxIDSaved = static_cast < CK_ID > ( rawHeader . MaxIDSaved ) ;
2023-08-28 21:21:40 +08:00
// fill file size and crc
2023-08-29 14:00:34 +08:00
this - > m_FileInfo . FileSize = CKSizeof ( CKRawFileInfo ) + rawHeader . DataPackSize + rawHeader . Hdr1PackSize ;
2023-08-28 21:21:40 +08:00
this - > m_FileInfo . Crc = computedcrc ;
rawHeader . Crc = computedcrc ;
// ========== Open File & Write Essential Data ==========
// open file and test
2024-08-23 11:28:49 +08:00
FILE * fs = YYCC : : IOHelper : : UTF8FOpen ( u8_filename , u8 " wb " ) ;
2023-08-28 21:21:40 +08:00
if ( fs = = nullptr ) return CKERROR : : CKERR_CANTWRITETOFILE ;
// write small header + header + data
std : : fwrite ( & rawHeader , sizeof ( CKRawFileInfo ) , 1 , fs ) ;
2023-09-17 10:38:46 +08:00
std : : fwrite ( hdrparser - > GetBase ( ) , sizeof ( CKBYTE ) , hdrparser - > GetSize ( ) , fs ) ;
std : : fwrite ( datparser - > GetBase ( ) , sizeof ( CKBYTE ) , datparser - > GetSize ( ) , fs ) ;
2023-08-28 21:21:40 +08:00
// free buffer
hdrparser . reset ( ) ;
datparser . reset ( ) ;
// ========== Included Files ==========
2023-09-30 14:24:37 +08:00
for ( const auto & fentry : m_IncludedFiles ) {
// get file name from full path
XContainer : : XString filename ( fentry ) ;
m_Ctx - > GetPathManager ( ) - > GetFileName ( filename ) ;
2023-08-28 21:21:40 +08:00
// write filename
2024-08-27 11:25:53 +08:00
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. " ) ;
2023-08-28 21:21:40 +08:00
CKDWORD filenamelen = static_cast < CKDWORD > ( name_conv . size ( ) ) ;
2023-08-29 14:00:34 +08:00
std : : fwrite ( & filenamelen , sizeof ( CKDWORD ) , 1 , fs ) ;
2023-09-17 10:38:46 +08:00
std : : fwrite ( name_conv . data ( ) , sizeof ( CKBYTE ) , filenamelen , fs ) ;
2023-08-29 14:00:34 +08:00
2023-08-28 21:21:40 +08:00
// try mapping file.
2023-09-30 14:24:37 +08:00
std : : unique_ptr < VxMath : : VxMemoryMappedFile > mappedFile ( new VxMath : : VxMemoryMappedFile ( fentry . c_str ( ) ) ) ;
2023-08-28 21:21:40 +08:00
if ( mappedFile - > IsValid ( ) ) {
2023-08-30 10:03:02 +08:00
// write file length
CKDWORD filebodylen = mappedFile - > GetFileSize ( ) ;
std : : fwrite ( & filebodylen , sizeof ( CKDWORD ) , 1 , fs ) ;
// write file body
2023-09-17 10:38:46 +08:00
std : : fwrite ( mappedFile - > GetBase ( ) , sizeof ( CKBYTE ) , filebodylen , fs ) ;
2023-08-30 10:03:02 +08:00
} else {
// write zero file length
CKDWORD filebodylen = 0 ;
std : : fwrite ( & filebodylen , sizeof ( CKDWORD ) , 1 , fs ) ;
// report error
2024-08-23 11:28:49 +08:00
m_Ctx - > OutputToConsoleEx ( u8 " Fail to open temp file: % " PRI_CKSTRING , fentry . c_str ( ) ) ;
2023-08-28 21:21:40 +08:00
}
// release mapped file
mappedFile . reset ( ) ;
}
// close file
2023-08-29 14:00:34 +08:00
std : : fclose ( fs ) ;
2023-08-28 21:21:40 +08:00
2024-08-23 17:38:45 +08:00
// set done flag and return
this - > m_Done = true ;
2023-08-28 21:21:40 +08:00
return CKERROR : : CKERR_OK ;
2023-08-28 17:04:28 +08:00
}
CKERROR CKFileWriter : : PrepareFile ( CKSTRING filename ) {
// check nullptr
if ( filename = = nullptr ) return CKERROR : : CKERR_INVALIDFILE ;
2023-08-29 14:00:34 +08:00
2023-08-28 17:04:28 +08:00
// try open file to check whether we can write it.
CKERROR err ;
2024-08-23 11:28:49 +08:00
FILE * tryfile = YYCC : : IOHelper : : UTF8FOpen ( filename , u8 " ab " ) ;
2023-08-28 17:04:28 +08:00
if ( tryfile = = nullptr ) {
err = CKERROR : : CKERR_CANTWRITETOFILE ;
} else {
err = CKERROR : : CKERR_OK ;
std : : fclose ( tryfile ) ;
}
return err ;
2023-08-25 21:57:22 +08:00
}
2023-02-25 17:39:39 +08:00
}