2026-03-01 12:16:08 +08:00
# pragma once
using namespace std ;
# include "System.h"
// The first 4 bytes is the location of the header (the header itself is at the end of the file)
// Then 4 bytes for the size of the header
// Then 2 bytes for the version number at which this save was first generated
// Then 2 bytes for the version number that the save should now be at
// ( the rest of the header is actually a footer )
# define SAVE_FILE_HEADER_SIZE 12
enum ESaveVersions
{
// Pre-release version
SAVE_FILE_VERSION_PRE_LAUNCH = 1 ,
// This is the version at which we launched the Xbox360 version
SAVE_FILE_VERSION_LAUNCH = 2 ,
// This is the version at which we had made changes that broke older saves
SAVE_FILE_VERSION_POST_LAUNCH = 3 ,
// This is the version at which we introduced the End, and any saves older than this will have their End data deleted
SAVE_FILE_VERSION_NEW_END = 4 ,
// This is the version at which we change the stronghold generation, and any saves older than this should should the original version
SAVE_FILE_VERSION_MOVED_STRONGHOLD = 5 ,
// This is the version at which we changed the playeruid format for PS3
SAVE_FILE_VERSION_CHANGE_MAP_DATA_MAPPING_SIZE = 6 ,
// This is the version at which we changed the playeruid format for Xbox One
SAVE_FILE_VERSION_DURANGO_CHANGE_MAP_DATA_MAPPING_SIZE = 7 ,
// This is the version at which we changed the chunk format to directly save the compressed storage formats
SAVE_FILE_VERSION_COMPRESSED_CHUNK_STORAGE ,
SAVE_FILE_VERSION_NEXT ,
} ;
// This is the version at which we changed the playeruid format for Xbox One
# define SAVE_FILE_VERSION_DURANGO_CHANGE_MAP_DATA_MAPPING_SIZE 7
enum ESavePlatform
{
SAVE_FILE_PLATFORM_NONE = MAKE_FOURCC ( ' N ' , ' O ' , ' N ' , ' E ' ) ,
SAVE_FILE_PLATFORM_X360 = MAKE_FOURCC ( ' X ' , ' 3 ' , ' 6 ' , ' 0 ' ) ,
SAVE_FILE_PLATFORM_XBONE = MAKE_FOURCC ( ' X ' , ' B ' , ' 1 ' , ' _ ' ) ,
SAVE_FILE_PLATFORM_PS3 = MAKE_FOURCC ( ' P ' , ' S ' , ' 3 ' , ' _ ' ) ,
SAVE_FILE_PLATFORM_PS4 = MAKE_FOURCC ( ' P ' , ' S ' , ' 4 ' , ' _ ' ) ,
SAVE_FILE_PLATFORM_PSVITA = MAKE_FOURCC ( ' P ' , ' S ' , ' V ' , ' _ ' ) ,
SAVE_FILE_PLATFORM_WIN64 = MAKE_FOURCC ( ' W ' , ' I ' , ' N ' , ' _ ' ) ,
# if defined _XBOX
SAVE_FILE_PLATFORM_LOCAL = SAVE_FILE_PLATFORM_X360
# elif defined _DURANGO
SAVE_FILE_PLATFORM_LOCAL = SAVE_FILE_PLATFORM_XBONE
# elif defined __PS3__
SAVE_FILE_PLATFORM_LOCAL = SAVE_FILE_PLATFORM_PS3
# elif defined __ORBIS__
SAVE_FILE_PLATFORM_LOCAL = SAVE_FILE_PLATFORM_PS4
# elif defined __PSVITA__
SAVE_FILE_PLATFORM_LOCAL = SAVE_FILE_PLATFORM_PSVITA
# elif defined _WINDOWS64
SAVE_FILE_PLATFORM_LOCAL = SAVE_FILE_PLATFORM_WIN64
# endif
} ;
# define SAVE_FILE_VERSION_NUMBER (SAVE_FILE_VERSION_NEXT - 1)
struct FileEntrySaveDataV1
{
public :
wchar_t filename [ 64 ] ; // 64 * 2B
unsigned int length ; // In bytes // 4B
// This is only valid once the save file has been written/loaded at least once
unsigned int startOffset ; // 4B
} ;
// It's important that we keep the order and size of the data here to smooth updating
// 4J Stu - As of writing the tutorial level uses a V1 save file
struct FileEntrySaveDataV2
{
public :
wchar_t filename [ 64 ] ; // 64 * 2B
unsigned int length ; // In bytes // 4B
union
{
// This is only valid once the save file has been written/loaded at least once
unsigned int startOffset ; // 4B
// For region files stored via ConsolveSaveFileSplit, these aren't stored within the normal save file, identified by not having a name (filename[0] is 0).
// Note: These won't be read or written as part of a file header, and should only exist wrapped up in a FileEntry class
unsigned int regionIndex ; // 4B
} ;
2026-03-02 17:39:35 +07:00
__int64 lastModifiedTime ; // 8B
2026-03-01 12:16:08 +08:00
} ;
typedef FileEntrySaveDataV2 FileEntrySaveData ;
class FileEntry
{
public :
FileEntrySaveData data ;
unsigned int currentFilePointer ;
FileEntry ( ) { ZeroMemory ( & data , sizeof ( FileEntrySaveData ) ) ; }
FileEntry ( wchar_t name [ 64 ] , unsigned int length , unsigned int startOffset )
{
data . length = length ;
data . startOffset = startOffset ;
memset ( & data . filename , 0 , sizeof ( wchar_t ) * 64 ) ;
memcpy ( & data . filename , name , sizeof ( wchar_t ) * 64 ) ;
data . lastModifiedTime = 0 ;
currentFilePointer = data . startOffset ;
}
2026-03-02 17:39:35 +07:00
unsigned int getFileSize ( ) { return data . length ; }
2026-03-01 12:16:08 +08:00
bool isRegionFile ( ) { return data . filename [ 0 ] = = 0 ; } // When using ConsoleSaveFileSplit only
unsigned int getRegionFileIndex ( ) { return data . regionIndex ; } // When using ConsoleSaveFileSplit only
void updateLastModifiedTime ( ) { data . lastModifiedTime = System : : currentRealTimeMillis ( ) ; }
/*
Comparison function object that returns true if the first argument goes before the second argument in the specific strict weak ordering it defines , and false otherwise .
Used in a call to std : : sort in DirectoryLevelStorage . cpp
*/
static bool newestFirst ( FileEntry * a , FileEntry * b ) { return a - > data . lastModifiedTime > b - > data . lastModifiedTime ; }
} ;
// A class the represents the header of the save file
class FileHeader
{
friend class ConsoleSaveFileOriginal ;
friend class ConsoleSaveFileSplit ;
private :
vector < FileEntry * > fileTable ;
ESavePlatform m_savePlatform ;
ByteOrder m_saveEndian ;
# if defined(__PS3__) || defined(_XBOX)
static const ByteOrder m_localEndian = BIGENDIAN ;
# else
static const ByteOrder m_localEndian = LITTLEENDIAN ;
# endif
short m_saveVersion ;
short m_originalSaveVersion ;
public :
FileEntry * lastFile ;
public :
FileHeader ( ) ;
~ FileHeader ( ) ;
protected :
FileEntry * AddFile ( const wstring & name , unsigned int length = 0 ) ;
void RemoveFile ( FileEntry * ) ;
void WriteHeader ( LPVOID saveMem ) ;
void ReadHeader ( LPVOID saveMem , ESavePlatform plat = SAVE_FILE_PLATFORM_LOCAL ) ;
unsigned int GetStartOfNextData ( ) ;
unsigned int GetFileSize ( ) ;
void AdjustStartOffsets ( FileEntry * file , DWORD nNumberOfBytesToWrite , bool subtract = false ) ;
bool fileExists ( const wstring & name ) ;
vector < FileEntry * > * getFilesWithPrefix ( const wstring & prefix ) ;
vector < FileEntry * > * getValidPlayerDatFiles ( ) ;
# if defined(__PS3__) || defined(__ORBIS__) || defined(__PSVITA__)
wstring getPlayerDataFilenameForLoad ( const PlayerUID & pUID ) ;
wstring getPlayerDataFilenameForSave ( const PlayerUID & pUID ) ;
vector < FileEntry * > * getDatFilesWithOnlineID ( const PlayerUID & pUID ) ;
vector < FileEntry * > * getDatFilesWithMacAndUserID ( const PlayerUID & pUID ) ;
vector < FileEntry * > * getDatFilesWithPrimaryUser ( ) ;
# endif
2026-03-02 17:39:35 +07:00
2026-03-01 12:16:08 +08:00
void setSaveVersion ( int version ) { m_saveVersion = version ; }
int getSaveVersion ( ) { return m_saveVersion ; }
void setOriginalSaveVersion ( int version ) { m_originalSaveVersion = version ; }
int getOriginalSaveVersion ( ) { return m_originalSaveVersion ; }
ESavePlatform getSavePlatform ( ) { return m_savePlatform ; }
void setPlatform ( ESavePlatform plat ) { m_savePlatform = plat ; }
bool isSaveEndianDifferent ( ) { return m_saveEndian ! = m_localEndian ; }
void setLocalPlatform ( ) { m_savePlatform = SAVE_FILE_PLATFORM_LOCAL ; m_saveEndian = m_localEndian ; }
ByteOrder getSaveEndian ( ) { return m_saveEndian ; }
static ByteOrder getLocalEndian ( ) { return m_localEndian ; }
void setEndian ( ByteOrder endian ) { m_saveEndian = endian ; }
static ByteOrder getEndian ( ESavePlatform plat ) ;
bool isLocalEndianDifferent ( ESavePlatform plat ) { return m_localEndian ! = getEndian ( plat ) ; }
2026-03-02 17:39:35 +07:00
2026-03-01 12:16:08 +08:00
} ;