//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Save game read and write. Any *.hl? files may be stored in memory, so use
// g_pSaveRestoreFileSystem when accessing them. The .sav file is always stored
// on disk, so use g_pFileSystem when accessing it.
//
// $Workfile: $
// $Date: $
// $NoKeywords: $
//=============================================================================//
// Save / Restore System
# include <ctype.h>
# ifdef _WIN32
# include "winerror.h"
# endif
# include "client.h"
# include "server.h"
# include "vengineserver_impl.h"
# include "host_cmd.h"
# include "sys.h"
# include "cdll_int.h"
# include "tmessage.h"
# include "screen.h"
# include "decal.h"
# include "zone.h"
# include "sv_main.h"
# include "host.h"
# include "r_local.h"
# include "filesystem.h"
# include "filesystem_engine.h"
# include "host_state.h"
# include "datamap.h"
# include "string_t.h"
# include "PlayerState.h"
# include "saverestoretypes.h"
# include "demo.h"
# include "icliententity.h"
# include "r_efx.h"
# include "icliententitylist.h"
# include "cdll_int.h"
# include "utldict.h"
# include "decal_private.h"
# include "engine/IEngineTrace.h"
# include "enginetrace.h"
# include "baseautocompletefilelist.h"
# include "sound.h"
# include "vgui_baseui_interface.h"
# include "gl_matsysiface.h"
# include "cl_main.h"
# include "pr_edict.h"
# include "tier0/vprof.h"
# include <vgui/ILocalize.h>
# include "vgui_controls/Controls.h"
# include "tier0/icommandline.h"
# include "testscriptmgr.h"
# include "vengineserver_impl.h"
# include "saverestore_filesystem.h"
# include "tier1/callqueue.h"
# include "vstdlib/jobthread.h"
# include "enginebugreporter.h"
# include "tier1/memstack.h"
# include "vstdlib/jobthread.h"
# if !defined( _X360 )
# include "xbox/xboxstubs.h"
# else
# include "xbox/xbox_launch.h"
# endif
# include "ixboxsystem.h"
extern IXboxSystem * g_pXboxSystem ;
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
extern IBaseClientDLL * g_ClientDLL ;
extern ConVar deathmatch ;
extern ConVar skill ;
extern ConVar save_in_memory ;
extern CGlobalVars g_ServerGlobalVariables ;
extern CNetworkStringTableContainer * networkStringTableContainerServer ;
// Keep the last 1 autosave / quick saves
ConVar save_history_count ( " save_history_count " , " 1 " , 0 , " Keep this many old copies in history of autosaves and quicksaves. " ) ;
ConVar sv_autosave ( " sv_autosave " , " 1 " , 0 , " Set to 1 to autosave game on level transition. Does not affect autosave triggers. " ) ;
ConVar save_async ( " save_async " , " 1 " ) ;
ConVar save_disable ( " save_disable " , " 0 " ) ;
ConVar save_noxsave ( " save_noxsave " , " 0 " ) ;
ConVar save_screenshot ( " save_screenshot " , " 1 " , 0 , " 0 = none, 1 = non-autosave, 2 = always " ) ;
ConVar save_spew ( " save_spew " , " 0 " ) ;
# define SaveMsg if ( !save_spew.GetBool() ) ; else Msg
// HACK HACK: Some hacking to keep the .sav file backward compatible on the client!!!
# define SECTION_MAGIC_NUMBER 0x54541234
# define SECTION_VERSION_NUMBER 2
CCallQueue g_AsyncSaveCallQueue ;
static bool g_ConsoleInput = false ;
static char g_szMapLoadOverride [ 32 ] ;
# define MOD_DIR ( IsX360() ? "DEFAULT_WRITE_PATH" : "MOD" )
//-----------------------------------------------------------------------------
IThreadPool * g_pSaveThread ;
static bool g_bSaveInProgress = false ;
//-----------------------------------------------------------------------------
static bool HaveExactMap ( const char * pszMapName )
{
char szCanonName [ 64 ] = { 0 } ;
V_strncpy ( szCanonName , pszMapName , sizeof ( szCanonName ) ) ;
IVEngineServer : : eFindMapResult eResult = g_pVEngineServer - > FindMap ( szCanonName , sizeof ( szCanonName ) ) ;
switch ( eResult )
{
case IVEngineServer : : eFindMap_Found :
return true ;
case IVEngineServer : : eFindMap_NonCanonical :
case IVEngineServer : : eFindMap_NotFound :
case IVEngineServer : : eFindMap_FuzzyMatch :
case IVEngineServer : : eFindMap_PossiblyAvailable :
return false ;
}
AssertMsg ( false , " Unhandled engine->FindMap return value \n " ) ;
return false ;
}
void FinishAsyncSave ( )
{
LOCAL_THREAD_LOCK ( ) ;
SaveMsg ( " FinishAsyncSave() (%d/%d) \n " , ThreadInMainThread ( ) , ThreadGetCurrentId ( ) ) ;
if ( g_AsyncSaveCallQueue . Count ( ) )
{
g_AsyncSaveCallQueue . CallQueued ( ) ;
g_pFileSystem - > AsyncFinishAllWrites ( ) ;
}
g_bSaveInProgress = false ;
}
void DispatchAsyncSave ( )
{
Assert ( ! g_bSaveInProgress ) ;
g_bSaveInProgress = true ;
if ( save_async . GetBool ( ) )
{
g_pSaveThread - > QueueCall ( & FinishAsyncSave ) ;
}
else
{
FinishAsyncSave ( ) ;
}
}
//-----------------------------------------------------------------------------
inline void GetServerSaveCommentEx ( char * comment , int maxlength , float flMinutes , float flSeconds )
{
if ( g_iServerGameDLLVersion > = 5 )
{
serverGameDLL - > GetSaveComment ( comment , maxlength , flMinutes , flSeconds ) ;
}
else
{
Assert ( 0 ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Alloc/free memory for save games
// Input : num -
// size -
//-----------------------------------------------------------------------------
class CSaveMemory : public CMemoryStack
{
public :
CSaveMemory ( )
{
MEM_ALLOC_CREDIT ( ) ;
Init ( 32 * 1024 * 1024 , 64 , 2 * 1024 * 1024 + 192 * 1024 ) ;
}
int m_nSaveAllocs ;
} ;
CSaveMemory & GetSaveMemory ( )
{
static CSaveMemory g_SaveMemory ;
return g_SaveMemory ;
}
void * SaveAllocMemory ( size_t num , size_t size , bool bClear )
{
MEM_ALLOC_CREDIT ( ) ;
+ + GetSaveMemory ( ) . m_nSaveAllocs ;
size_t nBytes = num * size ;
return GetSaveMemory ( ) . Alloc ( nBytes , bClear ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSaveMem -
//-----------------------------------------------------------------------------
void SaveFreeMemory ( void * pSaveMem )
{
- - GetSaveMemory ( ) . m_nSaveAllocs ;
if ( ! GetSaveMemory ( ) . m_nSaveAllocs )
{
GetSaveMemory ( ) . FreeAll ( false ) ;
}
}
//-----------------------------------------------------------------------------
// Reset save memory stack, as some failed save/load paths will leak
//-----------------------------------------------------------------------------
void SaveResetMemory ( )
{
GetSaveMemory ( ) . m_nSaveAllocs = 0 ;
GetSaveMemory ( ) . FreeAll ( false ) ;
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
struct GAME_HEADER
{
DECLARE_SIMPLE_DATADESC ( ) ;
char mapName [ 32 ] ;
char comment [ 80 ] ;
int mapCount ; // the number of map state files in the save file. This is usually number of maps * 3 (.hl1, .hl2, .hl3 files)
char originMapName [ 32 ] ;
char landmark [ 256 ] ;
} ;
struct SAVE_HEADER
{
DECLARE_SIMPLE_DATADESC ( ) ;
int saveId ;
int version ;
int skillLevel ;
int connectionCount ;
int lightStyleCount ;
int mapVersion ;
float time__USE_VCR_MODE ; // This is renamed to include the __USE_VCR_MODE prefix due to a #define on win32 from the VCR mode changes
// The actual save games have the string "time__USE_VCR_MODE" in them
char mapName [ 32 ] ;
char skyName [ 32 ] ;
} ;
struct SAVELIGHTSTYLE
{
DECLARE_SIMPLE_DATADESC ( ) ;
int index ;
char style [ 64 ] ;
} ;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CSaveRestore : public ISaveRestore
{
public :
CSaveRestore ( )
{
m_bClearSaveDir = false ;
m_szSaveGameScreenshotFile [ 0 ] = 0 ;
SetMostRecentElapsedMinutes ( 0 ) ;
SetMostRecentElapsedSeconds ( 0 ) ;
m_szMostRecentSaveLoadGame [ 0 ] = 0 ;
m_szSaveGameName [ 0 ] = 0 ;
m_bIsXSave = IsX360 ( ) ;
}
void Init ( void ) ;
void Shutdown ( void ) ;
void OnFrameRendered ( ) ;
virtual bool SaveFileExists ( const char * pName ) ;
bool LoadGame ( const char * pName ) ;
char * GetSaveDir ( void ) ;
void ClearSaveDir ( void ) ;
void DoClearSaveDir ( bool bIsXSave ) ;
void RequestClearSaveDir ( void ) ;
int LoadGameState ( char const * level , bool createPlayers ) ;
void LoadAdjacentEnts ( const char * pOldLevel , const char * pLandmarkName ) ;
const char * FindRecentSave ( char * pNameBuf , int nameBufLen ) ;
void ForgetRecentSave ( void ) ;
int SaveGameSlot ( const char * pSaveName , const char * pSaveComment , bool onlyThisLevel , bool bSetMostRecent , const char * pszDestMap = NULL , const char * pszLandmark = NULL ) ;
bool SaveGameState ( bool bTransition , CSaveRestoreData * * = NULL , bool bOpenContainer = true , bool bIsAutosaveOrDangerous = false ) ;
void RestoreClientState ( char const * fileName , bool adjacent ) ;
void RestoreAdjacenClientState ( char const * map ) ;
int IsValidSave ( void ) ;
void Finish ( CSaveRestoreData * save ) ;
void ClearRestoredIndexTranslationTables ( ) ;
void OnFinishedClientRestore ( ) ;
void AutoSaveDangerousIsSafe ( ) ;
virtual void UpdateSaveGameScreenshots ( ) ;
virtual char const * GetMostRecentlyLoadedFileName ( ) ;
virtual char const * GetSaveFileName ( ) ;
virtual void SetIsXSave ( bool bIsXSave ) { m_bIsXSave = bIsXSave ; }
virtual bool IsXSave ( ) { return ( m_bIsXSave & & ! save_noxsave . GetBool ( ) ) ; }
virtual void FinishAsyncSave ( ) { : : FinishAsyncSave ( ) ; }
void AddDeferredCommand ( char const * pchCommand ) ;
virtual bool StorageDeviceValid ( void ) ;
virtual bool IsSaveInProgress ( ) ;
private :
bool SaveClientState ( const char * name ) ;
void EntityPatchWrite ( CSaveRestoreData * pSaveData , const char * level , bool bAsync = false ) ;
void EntityPatchRead ( CSaveRestoreData * pSaveData , const char * level ) ;
void DirectoryCount ( const char * pPath , int * pResult ) ;
void DirectoryCopy ( const char * pPath , const char * pDestFileName , bool bIsXSave ) ;
bool DirectoryExtract ( FileHandle_t pFile , int mapCount ) ;
void DirectoryClear ( const char * pPath ) ;
void AgeSaveList ( const char * pName , int count , bool bIsXSave ) ;
void AgeSaveFile ( const char * pName , const char * ext , int count , bool bIsXSave ) ;
int SaveReadHeader ( FileHandle_t pFile , GAME_HEADER * pHeader , int readGlobalState , bool * pbOldSave ) ;
CSaveRestoreData * LoadSaveData ( const char * level ) ;
void ParseSaveTables ( CSaveRestoreData * pSaveData , SAVE_HEADER * pHeader , int updateGlobals ) ;
int FileSize ( FileHandle_t pFile ) ;
bool CalcSaveGameName ( const char * pName , char * output , int outputStringLength ) ;
CSaveRestoreData * SaveGameStateInit ( void ) ;
void SaveGameStateGlobals ( CSaveRestoreData * pSaveData ) ;
int SaveReadNameAndComment ( FileHandle_t f , OUT_Z_CAP ( nameSize ) char * name , int nameSize , OUT_Z_CAP ( commentSize ) char * comment , int commentSize ) OVERRIDE ;
void BuildRestoredIndexTranslationTable ( char const * mapname , CSaveRestoreData * pSaveData , bool verbose ) ;
char const * GetSaveGameMapName ( char const * level ) ;
void SetMostRecentSaveGame ( const char * pSaveName ) ;
int GetMostRecentElapsedMinutes ( void ) ;
int GetMostRecentElapsedSeconds ( void ) ;
int GetMostRecentElapsedTimeSet ( void ) ;
void SetMostRecentElapsedMinutes ( const int min ) ;
void SetMostRecentElapsedSeconds ( const int sec ) ;
struct SaveRestoreTranslate
{
string_t classname ;
int savedindex ;
int restoredindex ;
} ;
struct RestoreLookupTable
{
RestoreLookupTable ( ) :
m_vecLandMarkOffset ( 0 , 0 , 0 )
{
}
void Clear ( )
{
lookup . RemoveAll ( ) ;
m_vecLandMarkOffset . Init ( ) ;
}
RestoreLookupTable ( const RestoreLookupTable & src )
{
int c = src . lookup . Count ( ) ;
for ( int i = 0 ; i < c ; i + + )
{
lookup . AddToTail ( src . lookup [ i ] ) ;
}
m_vecLandMarkOffset = src . m_vecLandMarkOffset ;
}
RestoreLookupTable & operator = ( const RestoreLookupTable & src )
{
if ( this = = & src )
return * this ;
int c = src . lookup . Count ( ) ;
for ( int i = 0 ; i < c ; i + + )
{
lookup . AddToTail ( src . lookup [ i ] ) ;
}
m_vecLandMarkOffset = src . m_vecLandMarkOffset ;
return * this ;
}
CUtlVector < SaveRestoreTranslate > lookup ;
Vector m_vecLandMarkOffset ;
} ;
RestoreLookupTable * FindOrAddRestoreLookupTable ( char const * mapname ) ;
int LookupRestoreSpotSaveIndex ( RestoreLookupTable * table , int save ) ;
void ReapplyDecal ( bool adjacent , RestoreLookupTable * table , decallist_t * entry ) ;
CUtlDict < RestoreLookupTable , int > m_RestoreLookup ;
bool m_bClearSaveDir ;
char m_szSaveGameScreenshotFile [ MAX_OSPATH ] ;
float m_flClientSaveRestoreTime ;
char m_szMostRecentSaveLoadGame [ MAX_OSPATH ] ;
char m_szSaveGameName [ MAX_OSPATH ] ;
int m_MostRecentElapsedMinutes ;
int m_MostRecentElapsedSeconds ;
int m_MostRecentElapsedTimeSet ;
bool m_bWaitingForSafeDangerousSave ;
bool m_bIsXSave ;
int m_nDeferredCommandFrames ;
CUtlVector < CUtlSymbol > m_sDeferredCommands ;
} ;
CSaveRestore g_SaveRestore ;
ISaveRestore * saverestore = ( ISaveRestore * ) & g_SaveRestore ;
BEGIN_SIMPLE_DATADESC ( GAME_HEADER )
DEFINE_FIELD ( mapCount , FIELD_INTEGER ) ,
DEFINE_ARRAY ( mapName , FIELD_CHARACTER , 32 ) ,
DEFINE_ARRAY ( comment , FIELD_CHARACTER , 80 ) ,
DEFINE_ARRAY ( originMapName , FIELD_CHARACTER , 32 ) ,
DEFINE_ARRAY ( landmark , FIELD_CHARACTER , 256 ) ,
END_DATADESC ( )
// The proper way to extend the file format (add a new data chunk) is to add a field here, and use it to determine
// whether your new data chunk is in the file or not. If the file was not saved with your new field, the chunk
// won't be there either.
// Structure members can be added/deleted without any problems, new structures must be reflected in an existing struct
// and not read unless actually in the file. New structure members will be zeroed out when reading 'old' files.
BEGIN_SIMPLE_DATADESC ( SAVE_HEADER )
// DEFINE_FIELD( saveId, FIELD_INTEGER ),
// DEFINE_FIELD( version, FIELD_INTEGER ),
DEFINE_FIELD ( skillLevel , FIELD_INTEGER ) ,
DEFINE_FIELD ( connectionCount , FIELD_INTEGER ) ,
DEFINE_FIELD ( lightStyleCount , FIELD_INTEGER ) ,
DEFINE_FIELD ( mapVersion , FIELD_INTEGER ) ,
DEFINE_FIELD ( time__USE_VCR_MODE , FIELD_TIME ) ,
DEFINE_ARRAY ( mapName , FIELD_CHARACTER , 32 ) ,
DEFINE_ARRAY ( skyName , FIELD_CHARACTER , 32 ) ,
END_DATADESC ( )
BEGIN_SIMPLE_DATADESC ( levellist_t )
DEFINE_ARRAY ( mapName , FIELD_CHARACTER , 32 ) ,
DEFINE_ARRAY ( landmarkName , FIELD_CHARACTER , 32 ) ,
DEFINE_FIELD ( pentLandmark , FIELD_EDICT ) ,
DEFINE_FIELD ( vecLandmarkOrigin , FIELD_VECTOR ) ,
END_DATADESC ( )
BEGIN_SIMPLE_DATADESC ( SAVELIGHTSTYLE )
DEFINE_FIELD ( index , FIELD_INTEGER ) ,
DEFINE_ARRAY ( style , FIELD_CHARACTER , 64 ) ,
END_DATADESC ( )
//-----------------------------------------------------------------------------
// Purpose:
// Output : char const
//-----------------------------------------------------------------------------
char const * CSaveRestore : : GetSaveGameMapName ( char const * level )
{
Assert ( level ) ;
static char mapname [ 256 ] ;
Q_FileBase ( level , mapname , sizeof ( mapname ) ) ;
return mapname ;
}
//-----------------------------------------------------------------------------
// Purpose: returns the most recent save
//-----------------------------------------------------------------------------
const char * CSaveRestore : : FindRecentSave ( char * pNameBuf , int nameBufLen )
{
Q_strncpy ( pNameBuf , m_szMostRecentSaveLoadGame , nameBufLen ) ;
if ( ! m_szMostRecentSaveLoadGame [ 0 ] )
return NULL ;
return pNameBuf ;
}
//-----------------------------------------------------------------------------
// Purpose: Forgets the most recent save game
// this is so the current level will just restart if the player dies
//-----------------------------------------------------------------------------
void CSaveRestore : : ForgetRecentSave ( )
{
m_szMostRecentSaveLoadGame [ 0 ] = 0 ;
}
//-----------------------------------------------------------------------------
// Purpose: Returns the save game directory for the current player profile
//-----------------------------------------------------------------------------
char * CSaveRestore : : GetSaveDir ( void )
{
static char szDirectory [ MAX_OSPATH ] ;
Q_memset ( szDirectory , 0 , MAX_OSPATH ) ;
Q_strncpy ( szDirectory , " save/ " , sizeof ( szDirectory ) ) ;
return szDirectory ;
}
//-----------------------------------------------------------------------------
// Purpose: keeps the last few save files of the specified file around, renamed
//-----------------------------------------------------------------------------
void CSaveRestore : : AgeSaveList ( const char * pName , int count , bool bIsXSave )
{
// age all the previous save files (including screenshots)
while ( count > 0 )
{
AgeSaveFile ( pName , IsX360 ( ) ? " 360.sav " : " sav " , count , bIsXSave ) ;
if ( ! IsX360 ( ) )
{
AgeSaveFile ( pName , " tga " , count , bIsXSave ) ;
}
count - - ;
}
}
//-----------------------------------------------------------------------------
// Purpose: ages a single sav file
//-----------------------------------------------------------------------------
void CSaveRestore : : AgeSaveFile ( const char * pName , const char * ext , int count , bool bIsXSave )
{
char newName [ MAX_OSPATH ] , oldName [ MAX_OSPATH ] ;
if ( ! IsXSave ( ) )
{
if ( count = = 1 )
{
Q_snprintf ( oldName , sizeof ( oldName ) , " //%s/%s%s.%s " , MOD_DIR , GetSaveDir ( ) , pName , ext ) ; // quick.sav. DON'T FixSlashes on this, it needs to be //MOD
}
else
{
Q_snprintf ( oldName , sizeof ( oldName ) , " //%s/%s%s%02d.%s " , MOD_DIR , GetSaveDir ( ) , pName , count - 1 , ext ) ; // quick04.sav, etc. DON'T FixSlashes on this, it needs to be //MOD
}
Q_snprintf ( newName , sizeof ( newName ) , " //%s/%s%s%02d.%s " , MOD_DIR , GetSaveDir ( ) , pName , count , ext ) ; // DON'T FixSlashes on this, it needs to be //MOD
}
else
{
if ( count = = 1 )
{
Q_snprintf ( oldName , sizeof ( oldName ) , " %s: \\ %s.%s " , GetCurrentMod ( ) , pName , ext ) ;
}
else
{
Q_snprintf ( oldName , sizeof ( oldName ) , " %s: \\ %s%02d.%s " , GetCurrentMod ( ) , pName , count - 1 , ext ) ;
}
Q_snprintf ( newName , sizeof ( newName ) , " %s: \\ %s%02d.%s " , GetCurrentMod ( ) , pName , count , ext ) ;
}
// Scroll the name list down (rename quick04.sav to quick05.sav)
if ( g_pFileSystem - > FileExists ( oldName ) )
{
if ( count = = save_history_count . GetInt ( ) )
{
// there could be an old version, remove it
if ( g_pFileSystem - > FileExists ( newName ) )
{
g_pFileSystem - > RemoveFile ( newName ) ;
}
}
g_pFileSystem - > RenameFile ( oldName , newName ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CSaveRestore : : IsValidSave ( void )
{
if ( cmd_source ! = src_command )
return 0 ;
// Don't parse autosave/transition save/restores during playback!
if ( demoplayer - > IsPlayingBack ( ) )
{
return 0 ;
}
if ( ! sv . IsActive ( ) )
{
ConMsg ( " Not playing a local game. \n " ) ;
return 0 ;
}
if ( ! cl . IsActive ( ) )
{
ConMsg ( " Can't save if not active. \n " ) ;
return 0 ;
}
if ( sv . IsMultiplayer ( ) )
{
ConMsg ( " Can't save multiplayer games. \n " ) ;
return 0 ;
}
if ( sv . GetClientCount ( ) > 0 & & sv . GetClient ( 0 ) - > IsActive ( ) )
{
Assert ( serverGameClients ) ;
CGameClient * pGameClient = sv . Client ( 0 ) ;
CPlayerState * pl = serverGameClients - > GetPlayerState ( pGameClient - > edict ) ;
if ( ! pl )
{
ConMsg ( " Can't savegame without a player! \n " ) ;
return 0 ;
}
// we can't save if we're dead... unless we're reporting a bug.
if ( pl - > deadflag ! = false & & ! bugreporter - > IsVisible ( ) )
{
ConMsg ( " Can't savegame with a dead player \n " ) ;
return 0 ;
}
}
// Passed all checks, it's ok to save
return 1 ;
}
static ConVar save_asyncdelay ( " save_asyncdelay " , " 0 " , 0 , " For testing, adds this many milliseconds of delay to the save operation. " ) ;
//-----------------------------------------------------------------------------
// Purpose: save a game with the given name/comment
// note: Added S_ExtraUpdate calls to fix audio pops in autosaves
//-----------------------------------------------------------------------------
int CSaveRestore : : SaveGameSlot ( const char * pSaveName , const char * pSaveComment , bool onlyThisLevel , bool bSetMostRecent , const char * pszDestMap , const char * pszLandmark )
{
if ( save_disable . GetBool ( ) )
{
return 0 ;
}
if ( save_asyncdelay . GetInt ( ) > 0 )
{
Sys_Sleep ( clamp ( save_asyncdelay . GetInt ( ) , 0 , 3000 ) ) ;
}
SaveMsg ( " Start save... (%d/%d) \n " , ThreadInMainThread ( ) , ThreadGetCurrentId ( ) ) ;
VPROF_BUDGET ( " SaveGameSlot " , " Save " ) ;
char hlPath [ 256 ] , name [ 256 ] , * pTokenData ;
int tag , i , tokenSize ;
CSaveRestoreData * pSaveData ;
GAME_HEADER gameHeader ;
# if defined( _MEMTEST )
Cbuf_AddText ( " mem_dump \n " ) ;
# endif
g_pSaveRestoreFileSystem - > AsyncFinishAllWrites ( ) ;
S_ExtraUpdate ( ) ;
FinishAsyncSave ( ) ;
SaveResetMemory ( ) ;
S_ExtraUpdate ( ) ;
g_AsyncSaveCallQueue . DisableQueue ( ! save_async . GetBool ( ) ) ;
// Figure out the name for this save game
CalcSaveGameName ( pSaveName , name , sizeof ( name ) ) ;
ConDMsg ( " Saving game to %s... \n " , name ) ;
Q_strncpy ( m_szSaveGameName , name , sizeof ( m_szSaveGameName ) ) ;
if ( m_bClearSaveDir )
{
m_bClearSaveDir = false ;
g_AsyncSaveCallQueue . QueueCall ( this , & CSaveRestore : : DoClearSaveDir , IsXSave ( ) ) ;
}
if ( ! IsXSave ( ) )
{
if ( onlyThisLevel )
{
Q_snprintf ( hlPath , sizeof ( hlPath ) , " %s%s*.HL? " , GetSaveDir ( ) , sv . GetMapName ( ) ) ;
}
else
{
Q_snprintf ( hlPath , sizeof ( hlPath ) , " %s*.HL? " , GetSaveDir ( ) ) ;
}
}
else
{
if ( onlyThisLevel )
{
Q_snprintf ( hlPath , sizeof ( hlPath ) , " %s: \\ %s*.HL? " , GetCurrentMod ( ) , sv . GetMapName ( ) ) ;
}
else
{
Q_snprintf ( hlPath , sizeof ( hlPath ) , " %s: \\ *.HL? " , GetCurrentMod ( ) ) ;
}
}
// Output to disk
bool bClearFile = true ;
bool bIsQuick = ( stricmp ( pSaveName , " quick " ) = = 0 ) ;
bool bIsAutosave = ( ! bIsQuick & & stricmp ( pSaveName , " autosave " ) = = 0 ) ;
bool bIsAutosaveDangerous = ( ! bIsAutosave & & stricmp ( pSaveName , " autosavedangerous " ) = = 0 ) ;
if ( bIsQuick | | bIsAutosave | | bIsAutosaveDangerous )
{
bClearFile = false ;
SaveMsg ( " Queue AgeSaveList \n " ) ;
if ( StorageDeviceValid ( ) )
{
g_AsyncSaveCallQueue . QueueCall ( this , & CSaveRestore : : AgeSaveList , CUtlEnvelope < const char * > ( pSaveName ) , save_history_count . GetInt ( ) , IsXSave ( ) ) ;
}
}
S_ExtraUpdate ( ) ;
if ( ! SaveGameState ( ( pszDestMap ! = NULL ) , NULL , false , ( bIsAutosave | | bIsAutosaveDangerous ) ) )
{
m_szSaveGameName [ 0 ] = 0 ;
return 0 ;
}
S_ExtraUpdate ( ) ;
//---------------------------------
pSaveData = serverGameDLL - > SaveInit ( 0 ) ;
if ( ! pSaveData )
{
m_szSaveGameName [ 0 ] = 0 ;
return 0 ;
}
Q_FixSlashes ( hlPath ) ;
Q_strncpy ( gameHeader . comment , pSaveComment , sizeof ( gameHeader . comment ) ) ;
if ( pszDestMap & & pszLandmark & & * pszDestMap & & * pszLandmark )
{
Q_strncpy ( gameHeader . mapName , pszDestMap , sizeof ( gameHeader . mapName ) ) ;
Q_strncpy ( gameHeader . originMapName , sv . GetMapName ( ) , sizeof ( gameHeader . originMapName ) ) ;
Q_strncpy ( gameHeader . landmark , pszLandmark , sizeof ( gameHeader . landmark ) ) ;
}
else
{
Q_strncpy ( gameHeader . mapName , sv . GetMapName ( ) , sizeof ( gameHeader . mapName ) ) ;
gameHeader . originMapName [ 0 ] = 0 ;
gameHeader . landmark [ 0 ] = 0 ;
}
gameHeader . mapCount = 0 ; // No longer used. The map packer will place the map count at the head of the compound files (toml 7/18/2007)
serverGameDLL - > SaveWriteFields ( pSaveData , " GameHeader " , & gameHeader , NULL , GAME_HEADER : : m_DataMap . dataDesc , GAME_HEADER : : m_DataMap . dataNumFields ) ;
serverGameDLL - > SaveGlobalState ( pSaveData ) ;
// Write entity string token table
pTokenData = pSaveData - > AccessCurPos ( ) ;
for ( i = 0 ; i < pSaveData - > SizeSymbolTable ( ) ; i + + )
{
const char * pszToken = ( pSaveData - > StringFromSymbol ( i ) ) ? pSaveData - > StringFromSymbol ( i ) : " " ;
if ( ! pSaveData - > Write ( pszToken , strlen ( pszToken ) + 1 ) )
{
ConMsg ( " Token Table Save/Restore overflow! " ) ;
break ;
}
}
tokenSize = pSaveData - > AccessCurPos ( ) - pTokenData ;
pSaveData - > Rewind ( tokenSize ) ;
// open the file to validate it exists, and to clear it
if ( bClearFile & & ! IsX360 ( ) )
{
FileHandle_t pSaveFile = g_pSaveRestoreFileSystem - > Open ( name , " wb " ) ;
if ( ! pSaveFile & & g_pFileSystem - > FileExists ( name , " GAME " ) )
{
Msg ( " Save failed: invalid file name '%s' \n " , pSaveName ) ;
m_szSaveGameName [ 0 ] = 0 ;
return 0 ;
}
if ( pSaveFile )
g_pSaveRestoreFileSystem - > Close ( pSaveFile ) ;
S_ExtraUpdate ( ) ;
}
// If this isn't a dangerous auto save use it next
if ( bSetMostRecent )
{
SetMostRecentSaveGame ( pSaveName ) ;
}
m_bWaitingForSafeDangerousSave = bIsAutosaveDangerous ;
int iHeaderBufferSize = 64 + tokenSize + pSaveData - > GetCurPos ( ) ;
void * pMem = malloc ( iHeaderBufferSize ) ;
CUtlBuffer saveHeader ( pMem , iHeaderBufferSize ) ;
// Write the header -- THIS SHOULD NEVER CHANGE STRUCTURE, USE SAVE_HEADER FOR NEW HEADER INFORMATION
// THIS IS ONLY HERE TO IDENTIFY THE FILE AND GET IT'S SIZE.
tag = MAKEID ( ' J ' , ' S ' , ' A ' , ' V ' ) ;
saveHeader . Put ( & tag , sizeof ( int ) ) ;
tag = SAVEGAME_VERSION ;
saveHeader . Put ( & tag , sizeof ( int ) ) ;
tag = pSaveData - > GetCurPos ( ) ;
saveHeader . Put ( & tag , sizeof ( int ) ) ; // Does not include token table
// Write out the tokens first so we can load them before we load the entities
tag = pSaveData - > SizeSymbolTable ( ) ;
saveHeader . Put ( & tag , sizeof ( int ) ) ;
saveHeader . Put ( & tokenSize , sizeof ( int ) ) ;
saveHeader . Put ( pTokenData , tokenSize ) ;
saveHeader . Put ( pSaveData - > GetBuffer ( ) , pSaveData - > GetCurPos ( ) ) ;
// Create the save game container before the directory copy
g_AsyncSaveCallQueue . QueueCall ( g_pSaveRestoreFileSystem , & ISaveRestoreFileSystem : : AsyncWrite , CUtlEnvelope < const char * > ( name ) , saveHeader . Base ( ) , saveHeader . TellPut ( ) , true , false , ( FSAsyncControl_t * ) NULL ) ;
g_AsyncSaveCallQueue . QueueCall ( this , & CSaveRestore : : DirectoryCopy , CUtlEnvelope < const char * > ( hlPath ) , CUtlEnvelope < const char * > ( name ) , m_bIsXSave ) ;
// Finish all writes and close the save game container
// @TODO: this async finish all writes has to go away, very expensive and will make game hitchy. switch to a wait on the last async op
g_AsyncSaveCallQueue . QueueCall ( g_pFileSystem , & IFileSystem : : AsyncFinishAllWrites ) ;
if ( IsXSave ( ) & & StorageDeviceValid ( ) )
{
// Finish all pending I/O to the storage devices
g_AsyncSaveCallQueue . QueueCall ( g_pXboxSystem , & IXboxSystem : : FinishContainerWrites ) ;
}
S_ExtraUpdate ( ) ;
Finish ( pSaveData ) ;
S_ExtraUpdate ( ) ;
// queue up to save a matching screenshot
if ( ! IsX360 ( ) & & save_screenshot . GetBool ( ) ) // X360TBD: Faster savegame screenshots
{
if ( ! ( bIsAutosave | | bIsAutosaveDangerous ) | | save_screenshot . GetInt ( ) = = 2 )
{
Q_snprintf ( m_szSaveGameScreenshotFile , sizeof ( m_szSaveGameScreenshotFile ) , " %s%s%s.tga " , GetSaveDir ( ) , pSaveName , GetPlatformExt ( ) ) ;
}
}
S_ExtraUpdate ( ) ;
DispatchAsyncSave ( ) ;
m_szSaveGameName [ 0 ] = 0 ;
return 1 ;
}
//-----------------------------------------------------------------------------
// Purpose: Saves a screenshot for save game if necessary
//-----------------------------------------------------------------------------
void CSaveRestore : : UpdateSaveGameScreenshots ( )
{
if ( IsPC ( ) & & g_LostVideoMemory )
return ;
# ifndef SWDS
if ( m_szSaveGameScreenshotFile [ 0 ] )
{
host_framecount + + ;
g_ClientGlobalVariables . framecount = host_framecount ;
g_ClientDLL - > WriteSaveGameScreenshot ( m_szSaveGameScreenshotFile ) ;
m_szSaveGameScreenshotFile [ 0 ] = 0 ;
}
# endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CSaveRestore : : SaveReadHeader ( FileHandle_t pFile , GAME_HEADER * pHeader , int readGlobalState , bool * pbOldSave )
{
int i , tag , size , tokenCount , tokenSize ;
char * pszTokenList ;
CSaveRestoreData * pSaveData = NULL ;
if ( g_pSaveRestoreFileSystem - > Read ( & tag , sizeof ( int ) , pFile ) ! = sizeof ( int ) )
return 0 ;
if ( tag ! = MAKEID ( ' J ' , ' S ' , ' A ' , ' V ' ) )
{
Warning ( " Can't load saved game, incorrect FILEID \n " ) ;
return 0 ;
}
if ( g_pSaveRestoreFileSystem - > Read ( & tag , sizeof ( int ) , pFile ) ! = sizeof ( int ) )
return 0 ;
if ( tag ! = SAVEGAME_VERSION ) // Enforce version for now
{
Warning ( " Can't load saved game, incorrect version (got %i expecting %i) \n " , tag , SAVEGAME_VERSION ) ;
return 0 ;
}
if ( g_pSaveRestoreFileSystem - > Read ( & size , sizeof ( int ) , pFile ) ! = sizeof ( int ) )
return 0 ;
if ( g_pSaveRestoreFileSystem - > Read ( & tokenCount , sizeof ( int ) , pFile ) ! = sizeof ( int ) )
return 0 ;
if ( g_pSaveRestoreFileSystem - > Read ( & tokenSize , sizeof ( int ) , pFile ) ! = sizeof ( int ) )
return 0 ;
// At this point we must clean this data up if we fail!
void * pSaveMemory = SaveAllocMemory ( sizeof ( CSaveRestoreData ) + tokenSize + size , sizeof ( char ) ) ;
if ( ! pSaveMemory )
{
return 0 ;
}
pSaveData = MakeSaveRestoreData ( pSaveMemory ) ;
pSaveData - > levelInfo . connectionCount = 0 ;
pszTokenList = ( char * ) ( pSaveData + 1 ) ;
if ( tokenSize > 0 )
{
if ( g_pSaveRestoreFileSystem - > Read ( pszTokenList , tokenSize , pFile ) ! = tokenSize )
{
Finish ( pSaveData ) ;
return 0 ;
}
pSaveMemory = SaveAllocMemory ( tokenCount , sizeof ( char * ) , true ) ;
if ( ! pSaveMemory )
{
Finish ( pSaveData ) ;
return 0 ;
}
pSaveData - > InitSymbolTable ( ( char * * ) pSaveMemory , tokenCount ) ;
// Make sure the token strings pointed to by the pToken hashtable.
for ( i = 0 ; i < tokenCount ; i + + )
{
if ( * pszTokenList )
{
Verify ( pSaveData - > DefineSymbol ( pszTokenList , i ) ) ;
}
while ( * pszTokenList + + ) ; // Find next token (after next null)
}
}
else
{
pSaveData - > InitSymbolTable ( NULL , 0 ) ;
}
pSaveData - > levelInfo . fUseLandmark = false ;
pSaveData - > levelInfo . time = 0 ;
// pszTokenList now points after token data
pSaveData - > Init ( pszTokenList , size ) ;
if ( g_pSaveRestoreFileSystem - > Read ( pSaveData - > GetBuffer ( ) , size , pFile ) ! = size )
{
Finish ( pSaveData ) ;
return 0 ;
}
serverGameDLL - > SaveReadFields ( pSaveData , " GameHeader " , pHeader , NULL , GAME_HEADER : : m_DataMap . dataDesc , GAME_HEADER : : m_DataMap . dataNumFields ) ;
if ( g_szMapLoadOverride [ 0 ] )
{
V_strncpy ( pHeader - > mapName , g_szMapLoadOverride , sizeof ( pHeader - > mapName ) ) ;
g_szMapLoadOverride [ 0 ] = 0 ;
}
if ( pHeader - > mapCount ! = 0 & & pbOldSave )
* pbOldSave = true ;
if ( readGlobalState & & pHeader - > mapCount = = 0 ) // Alfred: Only load save games from the OB era engine where mapCount is forced to zero
{
serverGameDLL - > RestoreGlobalState ( pSaveData ) ;
}
Finish ( pSaveData ) ;
if ( pHeader - > mapCount = = 0 )
{
if ( g_pSaveRestoreFileSystem - > Read ( & pHeader - > mapCount , sizeof ( pHeader - > mapCount ) , pFile ) ! = sizeof ( pHeader - > mapCount ) )
return 0 ;
}
return 1 ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pName -
// *output -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CSaveRestore : : CalcSaveGameName ( const char * pName , char * output , int outputStringLength )
{
if ( ! pName | | ! pName [ 0 ] )
return false ;
if ( IsXSave ( ) )
{
Q_snprintf ( output , outputStringLength , " %s:/%s " , GetCurrentMod ( ) , pName ) ;
}
else
{
Q_snprintf ( output , outputStringLength , " %s%s " , GetSaveDir ( ) , pName ) ;
}
Q_DefaultExtension ( output , IsX360 ( ) ? " .360.sav " : " .sav " , outputStringLength ) ;
Q_FixSlashes ( output ) ;
return true ;
}
//-----------------------------------------------------------------------------
// Does this save file exist?
//-----------------------------------------------------------------------------
bool CSaveRestore : : SaveFileExists ( const char * pName )
{
FinishAsyncSave ( ) ;
char name [ 256 ] ;
if ( ! CalcSaveGameName ( pName , name , sizeof ( name ) ) )
return false ;
bool bExists = false ;
if ( IsXSave ( ) )
{
if ( StorageDeviceValid ( ) )
{
bExists = g_pFileSystem - > FileExists ( name ) ;
}
else
{
bExists = g_pSaveRestoreFileSystem - > FileExists ( name ) ;
}
}
else
{
bExists = g_pFileSystem - > FileExists ( name ) ;
}
return bExists ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pName -
// Output : int
//-----------------------------------------------------------------------------
bool CL_HL2Demo_MapCheck ( const char * name ) ; // in host_cmd.cpp
bool CL_PortalDemo_MapCheck ( const char * name ) ; // in host_cmd.cpp
bool CSaveRestore : : LoadGame ( const char * pName )
{
FileHandle_t pFile ;
GAME_HEADER gameHeader ;
char name [ MAX_PATH ] ;
bool validload = false ;
FinishAsyncSave ( ) ;
SaveResetMemory ( ) ;
if ( ! CalcSaveGameName ( pName , name , sizeof ( name ) ) )
{
DevWarning ( " Loaded bad game %s \n " , pName ) ;
Assert ( 0 ) ;
return false ;
}
// store off the most recent save
SetMostRecentSaveGame ( pName ) ;
ConMsg ( " Loading game from %s... \n " , name ) ;
m_bClearSaveDir = false ;
DoClearSaveDir ( IsXSave ( ) ) ;
bool bLoadedToMemory = false ;
if ( IsX360 ( ) )
{
bool bValidStorageDevice = StorageDeviceValid ( ) ;
if ( bValidStorageDevice )
{
// Load the file into memory, whole hog
bLoadedToMemory = g_pSaveRestoreFileSystem - > LoadFileFromDisk ( name ) ;
if ( bLoadedToMemory = = false )
return false ;
}
}
int iElapsedMinutes = 0 ;
int iElapsedSeconds = 0 ;
bool bOldSave = false ;
pFile = g_pSaveRestoreFileSystem - > Open ( name , " rb " , MOD_DIR ) ;
if ( pFile )
{
char szDummyName [ MAX_PATH ] ;
char szComment [ MAX_PATH ] ;
char szElapsedTime [ MAX_PATH ] ;
if ( SaveReadNameAndComment ( pFile , szDummyName , sizeof ( szDummyName ) , szComment , sizeof ( szComment ) ) )
{
// Elapsed time is the last 6 characters in comment. (mmm:ss)
int i ;
i = strlen ( szComment ) ;
Q_strncpy ( szElapsedTime , " ?? " , sizeof ( szElapsedTime ) ) ;
if ( i > = 6 )
{
Q_strncpy ( szElapsedTime , ( char * ) & szComment [ i - 6 ] , 7 ) ;
szElapsedTime [ 6 ] = ' \0 ' ;
// parse out
iElapsedMinutes = atoi ( szElapsedTime ) ;
iElapsedSeconds = atoi ( szElapsedTime + 4 ) ;
}
}
else
{
g_pSaveRestoreFileSystem - > Close ( pFile ) ;
if ( bLoadedToMemory )
{
g_pSaveRestoreFileSystem - > RemoveFile ( name ) ;
}
return NULL ;
}
// Reset the file pointer to the start of the file
g_pSaveRestoreFileSystem - > Seek ( pFile , 0 , FILESYSTEM_SEEK_HEAD ) ;
if ( SaveReadHeader ( pFile , & gameHeader , 1 , & bOldSave ) )
{
validload = DirectoryExtract ( pFile , gameHeader . mapCount ) ;
}
if ( ! HaveExactMap ( gameHeader . mapName ) )
{
Msg ( " Map '%s' missing or invalid \n " , gameHeader . mapName ) ;
validload = false ;
}
g_pSaveRestoreFileSystem - > Close ( pFile ) ;
if ( bLoadedToMemory )
{
g_pSaveRestoreFileSystem - > RemoveFile ( name ) ;
}
}
else
{
ConMsg ( " File not found or failed to open. \n " ) ;
return false ;
}
if ( ! validload )
{
Msg ( " Save file %s is not valid \n " , name ) ;
return false ;
}
// stop demo loop in case this fails
cl . demonum = - 1 ;
deathmatch . SetValue ( 0 ) ;
coop . SetValue ( 0 ) ;
if ( ! CL_HL2Demo_MapCheck ( gameHeader . mapName ) )
{
Warning ( " Save file %s is not valid \n " , name ) ;
return false ;
}
if ( ! CL_PortalDemo_MapCheck ( gameHeader . mapName ) )
{
Warning ( " Save file %s is not valid \n " , name ) ;
return false ;
}
bool bIsTransitionSave = ( gameHeader . originMapName [ 0 ] ! = 0 ) ;
bool retval = Host_NewGame ( gameHeader . mapName , true , false , ( bIsTransitionSave ) ? gameHeader . originMapName : NULL , ( bIsTransitionSave ) ? gameHeader . landmark : NULL , bOldSave ) ;
SetMostRecentElapsedMinutes ( iElapsedMinutes ) ;
SetMostRecentElapsedSeconds ( iElapsedSeconds ) ;
return retval ;
}
//-----------------------------------------------------------------------------
// Purpose: Remebers the most recent save game
//-----------------------------------------------------------------------------
void CSaveRestore : : SetMostRecentSaveGame ( const char * pSaveName )
{
// Only remember xsaves in the x360 case
if ( IsX360 ( ) & & IsXSave ( ) = = false )
return ;
if ( pSaveName )
{
Q_strncpy ( m_szMostRecentSaveLoadGame , pSaveName , sizeof ( m_szMostRecentSaveLoadGame ) ) ;
}
else
{
m_szMostRecentSaveLoadGame [ 0 ] = 0 ;
}
if ( ! m_szMostRecentSaveLoadGame [ 0 ] )
{
DevWarning ( " Cleared most recent save! \n " ) ;
Assert ( 0 ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Returns the last recored elapsed minutes
//-----------------------------------------------------------------------------
int CSaveRestore : : GetMostRecentElapsedMinutes ( void )
{
return m_MostRecentElapsedMinutes ;
}
//-----------------------------------------------------------------------------
// Purpose: Returns the last recored elapsed seconds
//-----------------------------------------------------------------------------
int CSaveRestore : : GetMostRecentElapsedSeconds ( void )
{
return m_MostRecentElapsedSeconds ;
}
int CSaveRestore : : GetMostRecentElapsedTimeSet ( void )
{
return m_MostRecentElapsedTimeSet ;
}
//-----------------------------------------------------------------------------
// Purpose: Sets the last recored elapsed minutes
//-----------------------------------------------------------------------------
void CSaveRestore : : SetMostRecentElapsedMinutes ( const int min )
{
m_MostRecentElapsedMinutes = min ;
m_MostRecentElapsedTimeSet = g_ServerGlobalVariables . curtime ;
}
//-----------------------------------------------------------------------------
// Purpose: Sets the last recored elapsed seconds
//-----------------------------------------------------------------------------
void CSaveRestore : : SetMostRecentElapsedSeconds ( const int sec )
{
m_MostRecentElapsedSeconds = sec ;
m_MostRecentElapsedTimeSet = g_ServerGlobalVariables . curtime ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : CSaveRestoreData
//-----------------------------------------------------------------------------
struct SaveFileHeaderTag_t
{
int id ;
int version ;
bool operator = = ( const SaveFileHeaderTag_t & rhs ) const { return ( memcmp ( this , & rhs , sizeof ( SaveFileHeaderTag_t ) ) = = 0 ) ; }
bool operator ! = ( const SaveFileHeaderTag_t & rhs ) const { return ( memcmp ( this , & rhs , sizeof ( SaveFileHeaderTag_t ) ) ! = 0 ) ; }
} ;
# define MAKEID(d,c,b,a) ( ((int)(a) << 24) | ((int)(b) << 16) | ((int)(c) << 8) | ((int)(d)) )
const struct SaveFileHeaderTag_t CURRENT_SAVEFILE_HEADER_TAG = { MAKEID ( ' V ' , ' A ' , ' L ' , ' V ' ) , SAVEGAME_VERSION } ;
struct SaveFileSectionsInfo_t
{
int nBytesSymbols ;
int nSymbols ;
int nBytesDataHeaders ;
int nBytesData ;
int SumBytes ( ) const
{
return ( nBytesSymbols + nBytesDataHeaders + nBytesData ) ;
}
} ;
struct SaveFileSections_t
{
char * pSymbols ;
char * pDataHeaders ;
char * pData ;
} ;
void CSaveRestore : : SaveGameStateGlobals ( CSaveRestoreData * pSaveData )
{
SAVE_HEADER header ;
INetworkStringTable * table = sv . GetLightStyleTable ( ) ;
Assert ( table ) ;
// Write global data
header . version = build_number ( ) ;
header . skillLevel = skill . GetInt ( ) ; // This is created from an int even though it's a float
header . connectionCount = pSaveData - > levelInfo . connectionCount ;
header . time__USE_VCR_MODE = sv . GetTime ( ) ;
ConVarRef skyname ( " sv_skyname " ) ;
if ( skyname . IsValid ( ) )
{
Q_strncpy ( header . skyName , skyname . GetString ( ) , sizeof ( header . skyName ) ) ;
}
else
{
Q_strncpy ( header . skyName , " unknown " , sizeof ( header . skyName ) ) ;
}
Q_strncpy ( header . mapName , sv . GetMapName ( ) , sizeof ( header . mapName ) ) ;
header . lightStyleCount = 0 ;
header . mapVersion = g_ServerGlobalVariables . mapversion ;
int i ;
for ( i = 0 ; i < MAX_LIGHTSTYLES ; i + + )
{
const char * ligthStyle = ( const char * ) table - > GetStringUserData ( i , NULL ) ;
if ( ligthStyle & & ligthStyle [ 0 ] )
header . lightStyleCount + + ;
}
pSaveData - > levelInfo . time = 0 ; // prohibits rebase of header.time (why not just save time as a field_float and ditch this hack?)
serverGameDLL - > SaveWriteFields ( pSaveData , " Save Header " , & header , NULL , SAVE_HEADER : : m_DataMap . dataDesc , SAVE_HEADER : : m_DataMap . dataNumFields ) ;
pSaveData - > levelInfo . time = header . time__USE_VCR_MODE ;
// Write adjacency list
for ( i = 0 ; i < pSaveData - > levelInfo . connectionCount ; i + + )
serverGameDLL - > SaveWriteFields ( pSaveData , " ADJACENCY " , pSaveData - > levelInfo . levelList + i , NULL , levellist_t : : m_DataMap . dataDesc , levellist_t : : m_DataMap . dataNumFields ) ;
// Write the lightstyles
SAVELIGHTSTYLE light ;
for ( i = 0 ; i < MAX_LIGHTSTYLES ; i + + )
{
const char * ligthStyle = ( const char * ) table - > GetStringUserData ( i , NULL ) ;
if ( ligthStyle & & ligthStyle [ 0 ] )
{
light . index = i ;
Q_strncpy ( light . style , ligthStyle , sizeof ( light . style ) ) ;
serverGameDLL - > SaveWriteFields ( pSaveData , " LIGHTSTYLE " , & light , NULL , SAVELIGHTSTYLE : : m_DataMap . dataDesc , SAVELIGHTSTYLE : : m_DataMap . dataNumFields ) ;
}
}
}
CSaveRestoreData * CSaveRestore : : SaveGameStateInit ( void )
{
CSaveRestoreData * pSaveData = serverGameDLL - > SaveInit ( 0 ) ;
return pSaveData ;
}
bool CSaveRestore : : SaveGameState ( bool bTransition , CSaveRestoreData * * ppReturnSaveData , bool bOpenContainer , bool bIsAutosaveOrDangerous )
{
MDLCACHE_COARSE_LOCK_ ( g_pMDLCache ) ;
SaveMsg ( " SaveGameState... \n " ) ;
int i ;
SaveFileSectionsInfo_t sectionsInfo ;
SaveFileSections_t sections ;
if ( ppReturnSaveData )
{
* ppReturnSaveData = NULL ;
}
if ( bTransition )
{
if ( m_bClearSaveDir )
{
m_bClearSaveDir = false ;
DoClearSaveDir ( IsXSave ( ) ) ;
}
}
S_ExtraUpdate ( ) ;
CSaveRestoreData * pSaveData = SaveGameStateInit ( ) ;
if ( ! pSaveData )
{
return false ;
}
pSaveData - > bAsync = bIsAutosaveOrDangerous ;
//---------------------------------
// Save the data
sections . pData = pSaveData - > AccessCurPos ( ) ;
//---------------------------------
// Pre-save
serverGameDLL - > PreSave ( pSaveData ) ;
// Build the adjacent map list (after entity table build by game in presave)
if ( bTransition )
{
serverGameDLL - > BuildAdjacentMapList ( ) ;
}
else
{
pSaveData - > levelInfo . connectionCount = 0 ;
}
S_ExtraUpdate ( ) ;
//---------------------------------
SaveGameStateGlobals ( pSaveData ) ;
S_ExtraUpdate ( ) ;
serverGameDLL - > Save ( pSaveData ) ;
S_ExtraUpdate ( ) ;
sectionsInfo . nBytesData = pSaveData - > AccessCurPos ( ) - sections . pData ;
//---------------------------------
// Save necessary tables/dictionaries/directories
sections . pDataHeaders = pSaveData - > AccessCurPos ( ) ;
serverGameDLL - > WriteSaveHeaders ( pSaveData ) ;
sectionsInfo . nBytesDataHeaders = pSaveData - > AccessCurPos ( ) - sections . pDataHeaders ;
//---------------------------------
// Write the save file symbol table
sections . pSymbols = pSaveData - > AccessCurPos ( ) ;
for ( i = 0 ; i < pSaveData - > SizeSymbolTable ( ) ; i + + )
{
const char * pszToken = ( pSaveData - > StringFromSymbol ( i ) ) ? pSaveData - > StringFromSymbol ( i ) : " " ;
if ( ! pSaveData - > Write ( pszToken , strlen ( pszToken ) + 1 ) )
{
break ;
}
}
sectionsInfo . nBytesSymbols = pSaveData - > AccessCurPos ( ) - sections . pSymbols ;
sectionsInfo . nSymbols = pSaveData - > SizeSymbolTable ( ) ;
//---------------------------------
// Output to disk
char name [ 256 ] ;
int nBytesStateFile = sizeof ( CURRENT_SAVEFILE_HEADER_TAG ) +
sizeof ( sectionsInfo ) +
sectionsInfo . nBytesSymbols +
sectionsInfo . nBytesDataHeaders +
sectionsInfo . nBytesData ;
void * pBuffer = new byte [ nBytesStateFile ] ;
CUtlBuffer buffer ( pBuffer , nBytesStateFile ) ;
// Write the header -- THIS SHOULD NEVER CHANGE STRUCTURE, USE SAVE_HEADER FOR NEW HEADER INFORMATION
// THIS IS ONLY HERE TO IDENTIFY THE FILE AND GET IT'S SIZE.
buffer . Put ( & CURRENT_SAVEFILE_HEADER_TAG , sizeof ( CURRENT_SAVEFILE_HEADER_TAG ) ) ;
// Write out the tokens and table FIRST so they are loaded in the right order, then write out the rest of the data in the file.
buffer . Put ( & sectionsInfo , sizeof ( sectionsInfo ) ) ;
buffer . Put ( sections . pSymbols , sectionsInfo . nBytesSymbols ) ;
buffer . Put ( sections . pDataHeaders , sectionsInfo . nBytesDataHeaders ) ;
buffer . Put ( sections . pData , sectionsInfo . nBytesData ) ;
if ( ! IsXSave ( ) )
{
Q_snprintf ( name , 256 , " //%s/%s%s.HL1 " , MOD_DIR , GetSaveDir ( ) , GetSaveGameMapName ( sv . GetMapName ( ) ) ) ; // DON'T FixSlashes on this, it needs to be //MOD
SaveMsg ( " Queue COM_CreatePath \n " ) ;
g_AsyncSaveCallQueue . QueueCall ( & COM_CreatePath , CUtlEnvelope < const char * > ( name ) ) ;
}
else
{
Q_snprintf ( name , 256 , " %s:/%s.HL1 " , GetCurrentMod ( ) , GetSaveGameMapName ( sv . GetMapName ( ) ) ) ; // DON'T FixSlashes on this, it needs to be //MOD
}
S_ExtraUpdate ( ) ;
SaveMsg ( " Queue AsyncWrite (%s) \n " , name ) ;
g_AsyncSaveCallQueue . QueueCall ( g_pSaveRestoreFileSystem , & ISaveRestoreFileSystem : : AsyncWrite , CUtlEnvelope < const char * > ( name ) , pBuffer , nBytesStateFile , true , false , ( FSAsyncControl_t * ) NULL ) ;
pBuffer = NULL ;
//---------------------------------
EntityPatchWrite ( pSaveData , GetSaveGameMapName ( sv . GetMapName ( ) ) , true ) ;
if ( ! ppReturnSaveData )
{
Finish ( pSaveData ) ;
}
else
{
* ppReturnSaveData = pSaveData ;
}
if ( ! IsXSave ( ) )
{
Q_snprintf ( name , sizeof ( name ) , " //%s/%s%s.HL2 " , MOD_DIR , GetSaveDir ( ) , GetSaveGameMapName ( sv . GetMapName ( ) ) ) ; // DON'T FixSlashes on this, it needs to be //MOD
}
else
{
Q_snprintf ( name , sizeof ( name ) , " %s:/%s.HL2 " , GetCurrentMod ( ) , GetSaveGameMapName ( sv . GetMapName ( ) ) ) ; // DON'T FixSlashes on this, it needs to be //MOD
}
// Let the client see the server entity to id lookup tables, etc.
S_ExtraUpdate ( ) ;
bool bSuccess = SaveClientState ( name ) ;
S_ExtraUpdate ( ) ;
//---------------------------------
if ( bTransition )
{
FinishAsyncSave ( ) ;
}
S_ExtraUpdate ( ) ;
return bSuccess ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *save -
//-----------------------------------------------------------------------------
void CSaveRestore : : Finish ( CSaveRestoreData * save )
{
char * * pTokens = save - > DetachSymbolTable ( ) ;
if ( pTokens )
SaveFreeMemory ( pTokens ) ;
entitytable_t * pEntityTable = save - > DetachEntityTable ( ) ;
if ( pEntityTable )
SaveFreeMemory ( pEntityTable ) ;
save - > PurgeEntityHash ( ) ;
SaveFreeMemory ( save ) ;
g_ServerGlobalVariables . pSaveData = NULL ;
}
BEGIN_SIMPLE_DATADESC ( musicsave_t )
DEFINE_ARRAY ( songname , FIELD_CHARACTER , 128 ) ,
DEFINE_FIELD ( sampleposition , FIELD_INTEGER ) ,
DEFINE_FIELD ( master_volume , FIELD_SHORT ) ,
END_DATADESC ( )
BEGIN_SIMPLE_DATADESC ( decallist_t )
DEFINE_FIELD ( position , FIELD_POSITION_VECTOR ) ,
DEFINE_ARRAY ( name , FIELD_CHARACTER , 128 ) ,
DEFINE_FIELD ( entityIndex , FIELD_SHORT ) ,
// DEFINE_FIELD( depth, FIELD_CHARACTER ),
DEFINE_FIELD ( flags , FIELD_CHARACTER ) ,
DEFINE_FIELD ( impactPlaneNormal , FIELD_VECTOR ) ,
END_DATADESC ( )
struct baseclientsectionsold_t
{
int entitysize ;
int headersize ;
int decalsize ;
int symbolsize ;
int decalcount ;
int symbolcount ;
int SumBytes ( )
{
return entitysize + headersize + decalsize + symbolsize ;
}
} ;
struct clientsectionsold_t : public baseclientsectionsold_t
{
char * symboldata ;
char * entitydata ;
char * headerdata ;
char * decaldata ;
} ;
// FIXME: Remove the above and replace with this once we update the save format!!
struct baseclientsections_t
{
int entitysize ;
int headersize ;
int decalsize ;
int musicsize ;
int symbolsize ;
int decalcount ;
int musiccount ;
int symbolcount ;
int SumBytes ( )
{
return entitysize + headersize + decalsize + symbolsize + musicsize ;
}
} ;
struct clientsections_t : public baseclientsections_t
{
char * symboldata ;
char * entitydata ;
char * headerdata ;
char * decaldata ;
char * musicdata ;
} ;
int CSaveRestore : : LookupRestoreSpotSaveIndex ( RestoreLookupTable * table , int save )
{
int c = table - > lookup . Count ( ) ;
for ( int i = 0 ; i < c ; i + + )
{
SaveRestoreTranslate * slot = & table - > lookup [ i ] ;
if ( slot - > savedindex = = save )
return slot - > restoredindex ;
}
return - 1 ;
}
void CSaveRestore : : ReapplyDecal ( bool adjacent , RestoreLookupTable * table , decallist_t * entry )
{
int flags = entry - > flags ;
if ( adjacent )
{
flags | = FDECAL_DONTSAVE ;
}
// unlock sting tables to allow changes, helps to find unwanted changes (bebug build only)
bool oldlock = networkStringTableContainerServer - > Lock ( false ) ;
if ( adjacent )
{
// These entities might not exist over transitions, so we'll use the saved plane and do a traceline instead
Vector testspot = entry - > position ;
VectorMA ( testspot , 5.0f , entry - > impactPlaneNormal , testspot ) ;
Vector testend = entry - > position ;
VectorMA ( testend , - 5.0f , entry - > impactPlaneNormal , testend ) ;
CTraceFilterHitAll traceFilter ;
trace_t tr ;
Ray_t ray ;
ray . Init ( testspot , testend ) ;
g_pEngineTraceServer - > TraceRay ( ray , MASK_OPAQUE , & traceFilter , & tr ) ;
if ( tr . fraction ! = 1.0f & & ! tr . allsolid )
{
// Check impact plane normal
float dot = entry - > impactPlaneNormal . Dot ( tr . plane . normal ) ;
if ( dot > = 0.99 )
{
// Hack, have to use server traceline stuff to get at an actuall index here
edict_t * hit = tr . GetEdict ( ) ;
if ( hit ! = NULL )
{
// Looks like a good match for original splat plane, reapply the decal
int entityToHit = NUM_FOR_EDICT ( hit ) ;
if ( entityToHit > = 0 )
{
IClientEntity * clientEntity = entitylist - > GetClientEntity ( entityToHit ) ;
if ( ! clientEntity )
return ;
bool found = false ;
int decalIndex = Draw_DecalIndexFromName ( entry - > name , & found ) ;
if ( ! found )
{
// This is a serious HACK because we're grabbing the index that the server hasn't networked down to us and forcing
// the decal name directly. However, the message should eventually arrive and set the decal back to the same
// name on the server's index...we can live with that I suppose.
decalIndex = sv . PrecacheDecal ( entry - > name , RES_FATALIFMISSING ) ;
Draw_DecalSetName ( decalIndex , entry - > name ) ;
}
g_pEfx - > DecalShoot (
decalIndex ,
entityToHit ,
clientEntity - > GetModel ( ) ,
clientEntity - > GetAbsOrigin ( ) ,
clientEntity - > GetAbsAngles ( ) ,
entry - > position , 0 , flags ) ;
}
}
}
}
}
else
{
int entityToHit = entry - > entityIndex ! = 0 ? LookupRestoreSpotSaveIndex ( table , entry - > entityIndex ) : entry - > entityIndex ;
if ( entityToHit > = 0 )
{
// NOTE: I re-initialized the origin and angles as the decals pos/angle are saved in local space (ie. relative to
// the entity they are attached to.
Vector vecOrigin ( 0.0f , 0.0f , 0.0f ) ;
QAngle vecAngle ( 0.0f , 0.0f , 0.0f ) ;
const model_t * pModel = NULL ;
IClientEntity * clientEntity = entitylist - > GetClientEntity ( entityToHit ) ;
if ( clientEntity )
{
pModel = clientEntity - > GetModel ( ) ;
}
else
{
// This breaks client/server. However, non-world entities are not in your PVS potentially.
edict_t * pEdict = EDICT_NUM ( entityToHit ) ;
if ( pEdict )
{
IServerEntity * pServerEntity = pEdict - > GetIServerEntity ( ) ;
if ( pServerEntity )
{
pModel = sv . GetModel ( pServerEntity - > GetModelIndex ( ) ) ;
}
}
}
if ( pModel )
{
bool found = false ;
int decalIndex = Draw_DecalIndexFromName ( entry - > name , & found ) ;
if ( ! found )
{
// This is a serious HACK because we're grabbing the index that the server hasn't networked down to us and forcing
// the decal name directly. However, the message should eventually arrive and set the decal back to the same
// name on the server's index...we can live with that I suppose.
decalIndex = sv . PrecacheDecal ( entry - > name , RES_FATALIFMISSING ) ;
Draw_DecalSetName ( decalIndex , entry - > name ) ;
}
g_pEfx - > DecalShoot ( decalIndex , entityToHit , pModel , vecOrigin , vecAngle , entry - > position , 0 , flags ) ;
}
}
}
// unlock sting tables to allow changes, helps to find unwanted changes (bebug build only)
networkStringTableContainerServer - > Lock ( oldlock ) ;
}
void CSaveRestore : : RestoreClientState ( char const * fileName , bool adjacent )
{
FileHandle_t pFile ;
pFile = g_pSaveRestoreFileSystem - > Open ( fileName , " rb " ) ;
if ( ! pFile )
{
DevMsg ( " Failed to open client state file %s \n " , fileName ) ;
return ;
}
SaveFileHeaderTag_t tag ;
g_pSaveRestoreFileSystem - > Read ( & tag , sizeof ( tag ) , pFile ) ;
if ( tag ! = CURRENT_SAVEFILE_HEADER_TAG )
{
g_pSaveRestoreFileSystem - > Close ( pFile ) ;
return ;
}
// Check for magic number
int savePos = g_pSaveRestoreFileSystem - > Tell ( pFile ) ;
int sectionheaderversion = 1 ;
int magicnumber = 0 ;
baseclientsections_t sections ;
g_pSaveRestoreFileSystem - > Read ( & magicnumber , sizeof ( magicnumber ) , pFile ) ;
if ( magicnumber = = SECTION_MAGIC_NUMBER )
{
g_pSaveRestoreFileSystem - > Read ( & sectionheaderversion , sizeof ( sectionheaderversion ) , pFile ) ;
if ( sectionheaderversion ! = SECTION_VERSION_NUMBER )
{
g_pSaveRestoreFileSystem - > Close ( pFile ) ;
return ;
}
g_pSaveRestoreFileSystem - > Read ( & sections , sizeof ( baseclientsections_t ) , pFile ) ;
}
else
{
// Rewind
g_pSaveRestoreFileSystem - > Seek ( pFile , savePos , FILESYSTEM_SEEK_HEAD ) ;
baseclientsectionsold_t oldsections ;
g_pSaveRestoreFileSystem - > Read ( & oldsections , sizeof ( baseclientsectionsold_t ) , pFile ) ;
Q_memset ( & sections , 0 , sizeof ( sections ) ) ;
sections . entitysize = oldsections . entitysize ;
sections . headersize = oldsections . headersize ;
sections . decalsize = oldsections . decalsize ;
sections . symbolsize = oldsections . symbolsize ;
sections . decalcount = oldsections . decalcount ;
sections . symbolcount = oldsections . symbolcount ;
}
void * pSaveMemory = SaveAllocMemory ( sizeof ( CSaveRestoreData ) + sections . SumBytes ( ) , sizeof ( char ) ) ;
if ( ! pSaveMemory )
{
return ;
}
CSaveRestoreData * pSaveData = MakeSaveRestoreData ( pSaveMemory ) ;
// Needed?
Q_strncpy ( pSaveData - > levelInfo . szCurrentMapName , fileName , sizeof ( pSaveData - > levelInfo . szCurrentMapName ) ) ;
g_pSaveRestoreFileSystem - > Read ( ( char * ) ( pSaveData + 1 ) , sections . SumBytes ( ) , pFile ) ;
g_pSaveRestoreFileSystem - > Close ( pFile ) ;
char * pszTokenList = ( char * ) ( pSaveData + 1 ) ;
if ( sections . symbolsize > 0 )
{
pSaveMemory = SaveAllocMemory ( sections . symbolcount , sizeof ( char * ) , true ) ;
if ( ! pSaveMemory )
{
SaveFreeMemory ( pSaveData ) ;
return ;
}
pSaveData - > InitSymbolTable ( ( char * * ) pSaveMemory , sections . symbolcount ) ;
// Make sure the token strings pointed to by the pToken hashtable.
for ( int i = 0 ; i < sections . symbolcount ; i + + )
{
if ( * pszTokenList )
{
Verify ( pSaveData - > DefineSymbol ( pszTokenList , i ) ) ;
}
while ( * pszTokenList + + ) ; // Find next token (after next null)
}
}
else
{
pSaveData - > InitSymbolTable ( NULL , 0 ) ;
}
Assert ( pszTokenList - ( char * ) ( pSaveData + 1 ) = = sections . symbolsize ) ;
//---------------------------------
// Set up the restore basis
int size = sections . SumBytes ( ) - sections . symbolsize ;
pSaveData - > Init ( ( char * ) ( pszTokenList ) , size ) ; // The point pszTokenList was incremented to the end of the tokens
g_ClientDLL - > ReadRestoreHeaders ( pSaveData ) ;
pSaveData - > Rebase ( ) ;
//HACKHACK
pSaveData - > levelInfo . time = m_flClientSaveRestoreTime ;
char name [ 256 ] ;
Q_FileBase ( fileName , name , sizeof ( name ) ) ;
Q_strlower ( name ) ;
RestoreLookupTable * table = FindOrAddRestoreLookupTable ( name ) ;
pSaveData - > levelInfo . fUseLandmark = adjacent ;
if ( adjacent )
{
pSaveData - > levelInfo . vecLandmarkOffset = table - > m_vecLandMarkOffset ;
}
bool bFixTable = false ;
// Fixup restore indices based on what server re-created for us
int c = pSaveData - > NumEntities ( ) ;
for ( int i = 0 ; i < c ; i + + )
{
entitytable_t * entry = pSaveData - > GetEntityInfo ( i ) ;
entry - > restoreentityindex = LookupRestoreSpotSaveIndex ( table , entry - > saveentityindex ) ;
//Adrian: This means we are a client entity with no index to restore and we need our model precached.
if ( entry - > restoreentityindex = = - 1 & & entry - > classname ! = NULL_STRING & & entry - > modelname ! = NULL_STRING )
{
sv . PrecacheModel ( STRING ( entry - > modelname ) , RES_FATALIFMISSING | RES_PRELOAD ) ;
bFixTable = true ;
}
}
//Adrian: Fix up model string tables to make sure they match on both sides.
if ( bFixTable = = true )
{
int iCount = cl . m_pModelPrecacheTable - > GetNumStrings ( ) ;
while ( iCount < sv . GetModelPrecacheTable ( ) - > GetNumStrings ( ) )
{
string_t szString = MAKE_STRING ( sv . GetModelPrecacheTable ( ) - > GetString ( iCount ) ) ;
cl . m_pModelPrecacheTable - > AddString ( true , STRING ( szString ) ) ;
iCount + + ;
}
}
g_ClientDLL - > Restore ( pSaveData , false ) ;
if ( r_decals . GetInt ( ) )
{
for ( int i = 0 ; i < sections . decalcount ; i + + )
{
decallist_t entry ;
g_ClientDLL - > SaveReadFields ( pSaveData , " DECALLIST " , & entry , NULL , decallist_t : : m_DataMap . dataDesc , decallist_t : : m_DataMap . dataNumFields ) ;
ReapplyDecal ( adjacent , table , & entry ) ;
}
}
for ( int i = 0 ; i < sections . musiccount ; i + + )
{
musicsave_t song ;
g_ClientDLL - > SaveReadFields ( pSaveData , " MUSICLIST " , & song , NULL , musicsave_t : : m_DataMap . dataDesc , musicsave_t : : m_DataMap . dataNumFields ) ;
// Tell sound system to restart the music
S_RestartSong ( & song ) ;
}
Finish ( pSaveData ) ;
}
void CSaveRestore : : RestoreAdjacenClientState ( char const * map )
{
char name [ 256 ] ;
if ( ! IsXSave ( ) )
{
Q_snprintf ( name , sizeof ( name ) , " //%s/%s%s.HL2 " , MOD_DIR , GetSaveDir ( ) , GetSaveGameMapName ( map ) ) ; // DON'T FixSlashes on this, it needs to be //MOD
}
else
{
Q_snprintf ( name , sizeof ( name ) , " %s:/%s.HL2 " , GetCurrentMod ( ) , GetSaveGameMapName ( map ) ) ; // DON'T FixSlashes on this, it needs to be //MOD
}
COM_CreatePath ( name ) ;
RestoreClientState ( name , true ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *name -
//-----------------------------------------------------------------------------
bool CSaveRestore : : SaveClientState ( const char * name )
{
# ifndef SWDS
decallist_t * decalList ;
int i ;
clientsections_t sections ;
CSaveRestoreData * pSaveData = g_ClientDLL - > SaveInit ( 0 ) ;
if ( ! pSaveData )
{
return false ;
}
sections . entitydata = pSaveData - > AccessCurPos ( ) ;
// Now write out the client .dll entities to the save file, too
g_ClientDLL - > PreSave ( pSaveData ) ;
g_ClientDLL - > Save ( pSaveData ) ;
sections . entitysize = pSaveData - > AccessCurPos ( ) - sections . entitydata ;
sections . headerdata = pSaveData - > AccessCurPos ( ) ;
g_ClientDLL - > WriteSaveHeaders ( pSaveData ) ;
sections . headersize = pSaveData - > AccessCurPos ( ) - sections . headerdata ;
sections . decaldata = pSaveData - > AccessCurPos ( ) ;
decalList = ( decallist_t * ) malloc ( sizeof ( decallist_t ) * Draw_DecalMax ( ) ) ;
sections . decalcount = DecalListCreate ( decalList ) ;
for ( i = 0 ; i < sections . decalcount ; i + + )
{
decallist_t * entry = & decalList [ i ] ;
g_ClientDLL - > SaveWriteFields ( pSaveData , " DECALLIST " , entry , NULL , decallist_t : : m_DataMap . dataDesc , decallist_t : : m_DataMap . dataNumFields ) ;
}
sections . decalsize = pSaveData - > AccessCurPos ( ) - sections . decaldata ;
sections . musicdata = pSaveData - > AccessCurPos ( ) ;
CUtlVector < musicsave_t > music ;
// Ask sound system for current music tracks
S_GetCurrentlyPlayingMusic ( music ) ;
sections . musiccount = music . Count ( ) ;
for ( i = 0 ; i < sections . musiccount ; + + i )
{
musicsave_t * song = & music [ i ] ;
g_ClientDLL - > SaveWriteFields ( pSaveData , " MUSICLIST " , song , NULL , musicsave_t : : m_DataMap . dataDesc , musicsave_t : : m_DataMap . dataNumFields ) ;
}
sections . musicsize = pSaveData - > AccessCurPos ( ) - sections . musicdata ;
// Write string token table
sections . symboldata = pSaveData - > AccessCurPos ( ) ;
for ( i = 0 ; i < pSaveData - > SizeSymbolTable ( ) ; i + + )
{
const char * pszToken = ( pSaveData - > StringFromSymbol ( i ) ) ? pSaveData - > StringFromSymbol ( i ) : " " ;
if ( ! pSaveData - > Write ( pszToken , strlen ( pszToken ) + 1 ) )
{
ConMsg ( " Token Table Save/Restore overflow! " ) ;
break ;
}
}
sections . symbolcount = pSaveData - > SizeSymbolTable ( ) ;
sections . symbolsize = pSaveData - > AccessCurPos ( ) - sections . symboldata ;
int magicnumber = SECTION_MAGIC_NUMBER ;
int sectionheaderversion = SECTION_VERSION_NUMBER ;
unsigned nBytes = sizeof ( CURRENT_SAVEFILE_HEADER_TAG ) +
sizeof ( magicnumber ) +
sizeof ( sectionheaderversion ) +
sizeof ( baseclientsections_t ) +
sections . symbolsize +
sections . headersize +
sections . entitysize +
sections . decalsize +
sections . musicsize ;
void * pBuffer = new byte [ nBytes ] ;
CUtlBuffer buffer ( pBuffer , nBytes ) ;
buffer . Put ( & CURRENT_SAVEFILE_HEADER_TAG , sizeof ( CURRENT_SAVEFILE_HEADER_TAG ) ) ;
buffer . Put ( & magicnumber , sizeof ( magicnumber ) ) ;
buffer . Put ( & sectionheaderversion , sizeof ( sectionheaderversion ) ) ;
buffer . Put ( ( baseclientsections_t * ) & sections , sizeof ( baseclientsections_t ) ) ;
buffer . Put ( sections . symboldata , sections . symbolsize ) ;
buffer . Put ( sections . headerdata , sections . headersize ) ;
buffer . Put ( sections . entitydata , sections . entitysize ) ;
buffer . Put ( sections . decaldata , sections . decalsize ) ;
buffer . Put ( sections . musicdata , sections . musicsize ) ;
SaveMsg ( " Queue AsyncWrite (%s) \n " , name ) ;
g_AsyncSaveCallQueue . QueueCall ( g_pSaveRestoreFileSystem , & ISaveRestoreFileSystem : : AsyncWrite , CUtlEnvelope < const char * > ( name ) , pBuffer , nBytes , true , false , ( FSAsyncControl_t * ) NULL ) ;
Finish ( pSaveData ) ;
free ( decalList ) ;
return true ;
# endif
}
//-----------------------------------------------------------------------------
// Purpose: Parses and confirms save information. Pulled from PC UI
//-----------------------------------------------------------------------------
int CSaveRestore : : SaveReadNameAndComment ( FileHandle_t f , OUT_Z_CAP ( nameSize ) char * name , int nameSize , OUT_Z_CAP ( commentSize ) char * comment , int commentSize )
{
int i , tag , size , tokenSize , tokenCount ;
char * pSaveData = NULL ;
char * pFieldName = NULL ;
char * * pTokenList = NULL ;
name [ 0 ] = ' \0 ' ;
comment [ 0 ] = ' \0 ' ;
// Make sure we can at least read in the first five fields
unsigned int tagsize = sizeof ( int ) * 5 ;
if ( g_pSaveRestoreFileSystem - > Size ( f ) < tagsize )
return 0 ;
int nRead = g_pSaveRestoreFileSystem - > Read ( & tag , sizeof ( int ) , f ) ;
if ( ( nRead ! = sizeof ( int ) ) | | tag ! = MAKEID ( ' J ' , ' S ' , ' A ' , ' V ' ) )
return 0 ;
if ( g_pSaveRestoreFileSystem - > Read ( & tag , sizeof ( int ) , f ) ! = sizeof ( int ) )
return 0 ;
if ( g_pSaveRestoreFileSystem - > Read ( & size , sizeof ( int ) , f ) ! = sizeof ( int ) )
return 0 ;
if ( g_pSaveRestoreFileSystem - > Read ( & tokenCount , sizeof ( int ) , f ) ! = sizeof ( int ) ) // These two ints are the token list
return 0 ;
if ( g_pSaveRestoreFileSystem - > Read ( & tokenSize , sizeof ( int ) , f ) ! = sizeof ( int ) )
return 0 ;
size + = tokenSize ;
// Sanity Check.
if ( tokenCount < 0 | | tokenCount > 1024 * 1024 * 32 )
{
return 0 ;
}
if ( tokenSize < 0 | | tokenSize > 1024 * 1024 * 10 )
{
return 0 ;
}
pSaveData = ( char * ) new char [ size ] ;
if ( g_pSaveRestoreFileSystem - > Read ( pSaveData , size , f ) ! = size )
{
delete [ ] pSaveData ;
return 0 ;
}
int nNumberOfFields ;
char * pData ;
short nFieldSize ;
pData = pSaveData ;
// Allocate a table for the strings, and parse the table
if ( tokenSize > 0 )
{
pTokenList = new char * [ tokenCount ] ;
// Make sure the token strings pointed to by the pToken hashtable.
for ( i = 0 ; i < tokenCount ; i + + )
{
pTokenList [ i ] = * pData ? pData : NULL ; // Point to each string in the pToken table
while ( * pData + + ) ; // Find next token (after next null)
}
}
else
pTokenList = NULL ;
// short, short (size, index of field name)
Q_memcpy ( & nFieldSize , pData , sizeof ( short ) ) ;
pData + = sizeof ( short ) ;
short index = 0 ;
Q_memcpy ( & index , pData , sizeof ( short ) ) ;
pFieldName = pTokenList [ index ] ;
if ( ! pFieldName | | Q_stricmp ( pFieldName , " GameHeader " ) )
{
delete [ ] pSaveData ;
delete [ ] pTokenList ;
return 0 ;
} ;
// int (fieldcount)
pData + = sizeof ( short ) ;
Q_memcpy ( & nNumberOfFields , pData , sizeof ( int ) ) ;
pData + = nFieldSize ;
// Each field is a short (size), short (index of name), binary string of "size" bytes (data)
for ( i = 0 ; i < nNumberOfFields ; + + i )
{
// Data order is:
// Size
// szName
// Actual Data
Q_memcpy ( & nFieldSize , pData , sizeof ( short ) ) ;
pData + = sizeof ( short ) ;
Q_memcpy ( & index , pData , sizeof ( short ) ) ;
pFieldName = pTokenList [ index ] ;
pData + = sizeof ( short ) ;
if ( ! Q_stricmp ( pFieldName , " comment " ) )
{
int copySize = MAX ( commentSize , nFieldSize ) ;
Q_strncpy ( comment , pData , copySize ) ;
}
else if ( ! Q_stricmp ( pFieldName , " mapName " ) )
{
int copySize = MAX ( commentSize , nFieldSize ) ;
Q_strncpy ( name , pData , copySize ) ;
} ;
// Move to Start of next field.
pData + = nFieldSize ;
}
// Delete the string table we allocated
delete [ ] pTokenList ;
delete [ ] pSaveData ;
if ( strlen ( name ) > 0 & & strlen ( comment ) > 0 )
return 1 ;
return 0 ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *level -
// Output : CSaveRestoreData
//-----------------------------------------------------------------------------
CSaveRestoreData * CSaveRestore : : LoadSaveData ( const char * level )
{
char name [ MAX_OSPATH ] ;
FileHandle_t pFile ;
if ( ! IsXSave ( ) )
{
Q_snprintf ( name , sizeof ( name ) , " //%s/%s%s.HL1 " , MOD_DIR , GetSaveDir ( ) , level ) ; // DON'T FixSlashes on this, it needs to be //MOD
}
else
{
Q_snprintf ( name , sizeof ( name ) , " %s:/%s.HL1 " , GetCurrentMod ( ) , level ) ; // DON'T FixSlashes on this, it needs to be //MOD
}
ConMsg ( " Loading game from %s... \n " , name ) ;
pFile = g_pSaveRestoreFileSystem - > Open ( name , " rb " ) ;
if ( ! pFile )
{
ConMsg ( " ERROR: couldn't open. \n " ) ;
return NULL ;
}
//---------------------------------
// Read the header
SaveFileHeaderTag_t tag ;
if ( g_pSaveRestoreFileSystem - > Read ( & tag , sizeof ( tag ) , pFile ) ! = sizeof ( tag ) )
return NULL ;
// Is this a valid save?
if ( tag ! = CURRENT_SAVEFILE_HEADER_TAG )
return NULL ;
//---------------------------------
// Read the sections info and the data
//
SaveFileSectionsInfo_t sectionsInfo ;
if ( g_pSaveRestoreFileSystem - > Read ( & sectionsInfo , sizeof ( sectionsInfo ) , pFile ) ! = sizeof ( sectionsInfo ) )
return NULL ;
void * pSaveMemory = SaveAllocMemory ( sizeof ( CSaveRestoreData ) + sectionsInfo . SumBytes ( ) , sizeof ( char ) ) ;
if ( ! pSaveMemory )
{
return 0 ;
}
CSaveRestoreData * pSaveData = MakeSaveRestoreData ( pSaveMemory ) ;
Q_strncpy ( pSaveData - > levelInfo . szCurrentMapName , level , sizeof ( pSaveData - > levelInfo . szCurrentMapName ) ) ;
if ( g_pSaveRestoreFileSystem - > Read ( ( char * ) ( pSaveData + 1 ) , sectionsInfo . SumBytes ( ) , pFile ) ! = sectionsInfo . SumBytes ( ) )
{
// Free the memory and give up
Finish ( pSaveData ) ;
return NULL ;
}
g_pSaveRestoreFileSystem - > Close ( pFile ) ;
//---------------------------------
// Parse the symbol table
char * pszTokenList = ( char * ) ( pSaveData + 1 ) ; // Skip past the CSaveRestoreData structure
if ( sectionsInfo . nBytesSymbols > 0 )
{
pSaveMemory = SaveAllocMemory ( sectionsInfo . nSymbols , sizeof ( char * ) , true ) ;
if ( ! pSaveMemory )
{
SaveFreeMemory ( pSaveData ) ;
return 0 ;
}
pSaveData - > InitSymbolTable ( ( char * * ) pSaveMemory , sectionsInfo . nSymbols ) ;
// Make sure the token strings pointed to by the pToken hashtable.
for ( int i = 0 ; i < sectionsInfo . nSymbols ; i + + )
{
if ( * pszTokenList )
{
Verify ( pSaveData - > DefineSymbol ( pszTokenList , i ) ) ;
}
while ( * pszTokenList + + ) ; // Find next token (after next null)
}
}
else
{
pSaveData - > InitSymbolTable ( NULL , 0 ) ;
}
Assert ( pszTokenList - ( char * ) ( pSaveData + 1 ) = = sectionsInfo . nBytesSymbols ) ;
//---------------------------------
// Set up the restore basis
int size = sectionsInfo . SumBytes ( ) - sectionsInfo . nBytesSymbols ;
pSaveData - > levelInfo . connectionCount = 0 ;
pSaveData - > Init ( ( char * ) ( pszTokenList ) , size ) ; // The point pszTokenList was incremented to the end of the tokens
pSaveData - > levelInfo . fUseLandmark = true ;
pSaveData - > levelInfo . time = 0 ;
VectorCopy ( vec3_origin , pSaveData - > levelInfo . vecLandmarkOffset ) ;
g_ServerGlobalVariables . pSaveData = ( CSaveRestoreData * ) pSaveData ;
return pSaveData ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSaveData -
// *pHeader -
// updateGlobals -
//-----------------------------------------------------------------------------
void CSaveRestore : : ParseSaveTables ( CSaveRestoreData * pSaveData , SAVE_HEADER * pHeader , int updateGlobals )
{
int i ;
SAVELIGHTSTYLE light ;
INetworkStringTable * table = sv . GetLightStyleTable ( ) ;
// Re-base the savedata since we re-ordered the entity/table / restore fields
pSaveData - > Rebase ( ) ;
// Process SAVE_HEADER
serverGameDLL - > SaveReadFields ( pSaveData , " Save Header " , pHeader , NULL , SAVE_HEADER : : m_DataMap . dataDesc , SAVE_HEADER : : m_DataMap . dataNumFields ) ;
// header.version = ENGINE_VERSION;
pSaveData - > levelInfo . mapVersion = pHeader - > mapVersion ;
pSaveData - > levelInfo . connectionCount = pHeader - > connectionCount ;
pSaveData - > levelInfo . time = pHeader - > time__USE_VCR_MODE ;
pSaveData - > levelInfo . fUseLandmark = true ;
VectorCopy ( vec3_origin , pSaveData - > levelInfo . vecLandmarkOffset ) ;
// Read adjacency list
for ( i = 0 ; i < pSaveData - > levelInfo . connectionCount ; i + + )
serverGameDLL - > SaveReadFields ( pSaveData , " ADJACENCY " , pSaveData - > levelInfo . levelList + i , NULL , levellist_t : : m_DataMap . dataDesc , levellist_t : : m_DataMap . dataNumFields ) ;
if ( updateGlobals )
{
for ( i = 0 ; i < MAX_LIGHTSTYLES ; i + + )
table - > SetStringUserData ( i , 1 , " " ) ;
}
for ( i = 0 ; i < pHeader - > lightStyleCount ; i + + )
{
serverGameDLL - > SaveReadFields ( pSaveData , " LIGHTSTYLE " , & light , NULL , SAVELIGHTSTYLE : : m_DataMap . dataDesc , SAVELIGHTSTYLE : : m_DataMap . dataNumFields ) ;
if ( updateGlobals )
{
table - > SetStringUserData ( light . index , Q_strlen ( light . style ) + 1 , light . style ) ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Write out the list of entities that are no longer in the save file for this level
// (they've been moved to another level)
// Input : *pSaveData -
// *level -
//-----------------------------------------------------------------------------
void CSaveRestore : : EntityPatchWrite ( CSaveRestoreData * pSaveData , const char * level , bool bAsync )
{
char name [ MAX_OSPATH ] ;
int i , size ;
if ( ! IsXSave ( ) )
{
Q_snprintf ( name , sizeof ( name ) , " //%s/%s%s.HL3 " , MOD_DIR , GetSaveDir ( ) , level ) ; // DON'T FixSlashes on this, it needs to be //MOD
}
else
{
Q_snprintf ( name , sizeof ( name ) , " %s:/%s.HL3 " , GetCurrentMod ( ) , level ) ; // DON'T FixSlashes on this, it needs to be //MOD
}
size = 0 ;
for ( i = 0 ; i < pSaveData - > NumEntities ( ) ; i + + )
{
if ( pSaveData - > GetEntityInfo ( i ) - > flags & FENTTABLE_REMOVED )
size + + ;
}
int nBytesEntityPatch = sizeof ( int ) + size * sizeof ( int ) ;
void * pBuffer = new byte [ nBytesEntityPatch ] ;
CUtlBuffer buffer ( pBuffer , nBytesEntityPatch ) ;
// Patch count
buffer . Put ( & size , sizeof ( int ) ) ;
for ( i = 0 ; i < pSaveData - > NumEntities ( ) ; i + + )
{
if ( pSaveData - > GetEntityInfo ( i ) - > flags & FENTTABLE_REMOVED )
buffer . Put ( & i , sizeof ( int ) ) ;
}
if ( ! bAsync )
{
g_pSaveRestoreFileSystem - > AsyncWrite ( name , pBuffer , nBytesEntityPatch , true , false ) ;
g_pSaveRestoreFileSystem - > AsyncFinishAllWrites ( ) ;
}
else
{
SaveMsg ( " Queue AsyncWrite (%s) \n " , name ) ;
g_AsyncSaveCallQueue . QueueCall ( g_pSaveRestoreFileSystem , & ISaveRestoreFileSystem : : AsyncWrite , CUtlEnvelope < const char * > ( name ) , pBuffer , nBytesEntityPatch , true , false , ( FSAsyncControl_t * ) NULL ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Read the list of entities that are no longer in the save file for this level (they've been moved to another level)
// and correct the table
// Input : *pSaveData -
// *level -
//-----------------------------------------------------------------------------
void CSaveRestore : : EntityPatchRead ( CSaveRestoreData * pSaveData , const char * level )
{
char name [ MAX_OSPATH ] ;
FileHandle_t pFile ;
int i , size , entityId ;
if ( ! IsXSave ( ) )
{
Q_snprintf ( name , sizeof ( name ) , " //%s/%s%s.HL3 " , MOD_DIR , GetSaveDir ( ) , GetSaveGameMapName ( level ) ) ; // DON'T FixSlashes on this, it needs to be //MOD
}
else
{
Q_snprintf ( name , sizeof ( name ) , " %s:/%s.HL3 " , GetCurrentMod ( ) , GetSaveGameMapName ( level ) ) ; // DON'T FixSlashes on this, it needs to be //MOD
}
pFile = g_pSaveRestoreFileSystem - > Open ( name , " rb " ) ;
if ( pFile )
{
// Patch count
g_pSaveRestoreFileSystem - > Read ( & size , sizeof ( int ) , pFile ) ;
for ( i = 0 ; i < size ; i + + )
{
g_pSaveRestoreFileSystem - > Read ( & entityId , sizeof ( int ) , pFile ) ;
pSaveData - > GetEntityInfo ( entityId ) - > flags = FENTTABLE_REMOVED ;
}
g_pSaveRestoreFileSystem - > Close ( pFile ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *level -
// createPlayers -
// Output : int
//-----------------------------------------------------------------------------
int CSaveRestore : : LoadGameState ( char const * level , bool createPlayers )
{
VPROF ( " CSaveRestore::LoadGameState " ) ;
SAVE_HEADER header ;
CSaveRestoreData * pSaveData ;
pSaveData = LoadSaveData ( GetSaveGameMapName ( level ) ) ;
if ( ! pSaveData ) // Couldn't load the file
return 0 ;
serverGameDLL - > ReadRestoreHeaders ( pSaveData ) ;
ParseSaveTables ( pSaveData , & header , 1 ) ;
EntityPatchRead ( pSaveData , level ) ;
if ( ! IsX360 ( ) )
{
skill . SetValue ( header . skillLevel ) ;
}
Q_strncpy ( sv . m_szMapname , header . mapName , sizeof ( sv . m_szMapname ) ) ;
ConVarRef skyname ( " sv_skyname " ) ;
if ( skyname . IsValid ( ) )
{
skyname . SetValue ( header . skyName ) ;
}
// Create entity list
serverGameDLL - > Restore ( pSaveData , createPlayers ) ;
BuildRestoredIndexTranslationTable ( level , pSaveData , false ) ;
m_flClientSaveRestoreTime = pSaveData - > levelInfo . time ;
Finish ( pSaveData ) ;
sv . m_nTickCount = ( int ) ( header . time__USE_VCR_MODE / host_state . interval_per_tick ) ;
// SUCCESS!
return 1 ;
}
CSaveRestore : : RestoreLookupTable * CSaveRestore : : FindOrAddRestoreLookupTable ( char const * mapname )
{
int idx = m_RestoreLookup . Find ( mapname ) ;
if ( idx = = m_RestoreLookup . InvalidIndex ( ) )
{
idx = m_RestoreLookup . Insert ( mapname ) ;
}
return & m_RestoreLookup [ idx ] ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSaveData -
// Output : int
//-----------------------------------------------------------------------------
void CSaveRestore : : BuildRestoredIndexTranslationTable ( char const * mapname , CSaveRestoreData * pSaveData , bool verbose )
{
char name [ 256 ] ;
Q_FileBase ( mapname , name , sizeof ( name ) ) ;
Q_strlower ( name ) ;
// Build Translation Lookup
RestoreLookupTable * table = FindOrAddRestoreLookupTable ( name ) ;
table - > Clear ( ) ;
int c = pSaveData - > NumEntities ( ) ;
for ( int i = 0 ; i < c ; i + + )
{
entitytable_t * entry = pSaveData - > GetEntityInfo ( i ) ;
SaveRestoreTranslate slot ;
slot . classname = entry - > classname ;
slot . savedindex = entry - > saveentityindex ;
slot . restoredindex = entry - > restoreentityindex ;
table - > lookup . AddToTail ( slot ) ;
}
table - > m_vecLandMarkOffset = pSaveData - > levelInfo . vecLandmarkOffset ;
}
void CSaveRestore : : ClearRestoredIndexTranslationTables ( )
{
m_RestoreLookup . RemoveAll ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Find all occurances of the map in the adjacency table
// Input : *pSaveData -
// *pMapName -
// index -
// Output : int
//-----------------------------------------------------------------------------
int EntryInTable ( CSaveRestoreData * pSaveData , const char * pMapName , int index )
{
int i ;
index + + ;
for ( i = index ; i < pSaveData - > levelInfo . connectionCount ; i + + )
{
if ( ! stricmp ( pSaveData - > levelInfo . levelList [ i ] . mapName , pMapName ) )
return i ;
}
return - 1 ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pSaveData -
// output -
// *pLandmarkName -
//-----------------------------------------------------------------------------
void LandmarkOrigin ( CSaveRestoreData * pSaveData , Vector & output , const char * pLandmarkName )
{
int i ;
for ( i = 0 ; i < pSaveData - > levelInfo . connectionCount ; i + + )
{
if ( ! stricmp ( pSaveData - > levelInfo . levelList [ i ] . landmarkName , pLandmarkName ) )
{
VectorCopy ( pSaveData - > levelInfo . levelList [ i ] . vecLandmarkOrigin , output ) ;
return ;
}
}
VectorCopy ( vec3_origin , output ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pOldLevel -
// *pLandmarkName -
//-----------------------------------------------------------------------------
void CSaveRestore : : LoadAdjacentEnts ( const char * pOldLevel , const char * pLandmarkName )
{
FinishAsyncSave ( ) ;
CSaveRestoreData currentLevelData , * pSaveData ;
int i , test , flags , index , movedCount = 0 ;
SAVE_HEADER header ;
Vector landmarkOrigin ;
memset ( & currentLevelData , 0 , sizeof ( CSaveRestoreData ) ) ;
g_ServerGlobalVariables . pSaveData = & currentLevelData ;
// Build the adjacent map list
serverGameDLL - > BuildAdjacentMapList ( ) ;
bool foundprevious = false ;
for ( i = 0 ; i < currentLevelData . levelInfo . connectionCount ; i + + )
{
// make sure the previous level is in the connection list so we can
// bring over the player.
if ( ! strcmpi ( currentLevelData . levelInfo . levelList [ i ] . mapName , pOldLevel ) )
{
foundprevious = true ;
}
for ( test = 0 ; test < i ; test + + )
{
// Only do maps once
if ( ! stricmp ( currentLevelData . levelInfo . levelList [ i ] . mapName , currentLevelData . levelInfo . levelList [ test ] . mapName ) )
break ;
}
// Map was already in the list
if ( test < i )
continue ;
// ConMsg("Merging entities from %s ( at %s )\n", currentLevelData.levelInfo.levelList[i].mapName, currentLevelData.levelInfo.levelList[i].landmarkName );
pSaveData = LoadSaveData ( GetSaveGameMapName ( currentLevelData . levelInfo . levelList [ i ] . mapName ) ) ;
if ( pSaveData )
{
serverGameDLL - > ReadRestoreHeaders ( pSaveData ) ;
ParseSaveTables ( pSaveData , & header , 0 ) ;
EntityPatchRead ( pSaveData , currentLevelData . levelInfo . levelList [ i ] . mapName ) ;
pSaveData - > levelInfo . time = sv . GetTime ( ) ; // - header.time;
pSaveData - > levelInfo . fUseLandmark = true ;
flags = 0 ;
LandmarkOrigin ( & currentLevelData , landmarkOrigin , pLandmarkName ) ;
LandmarkOrigin ( pSaveData , pSaveData - > levelInfo . vecLandmarkOffset , pLandmarkName ) ;
VectorSubtract ( landmarkOrigin , pSaveData - > levelInfo . vecLandmarkOffset , pSaveData - > levelInfo . vecLandmarkOffset ) ;
if ( ! stricmp ( currentLevelData . levelInfo . levelList [ i ] . mapName , pOldLevel ) )
flags | = FENTTABLE_PLAYER ;
index = - 1 ;
while ( 1 )
{
index = EntryInTable ( pSaveData , sv . GetMapName ( ) , index ) ;
if ( index < 0 )
break ;
flags | = 1 < < index ;
}
if ( flags )
movedCount = serverGameDLL - > CreateEntityTransitionList ( pSaveData , flags ) ;
// If ents were moved, rewrite entity table to save file
if ( movedCount )
EntityPatchWrite ( pSaveData , GetSaveGameMapName ( currentLevelData . levelInfo . levelList [ i ] . mapName ) ) ;
BuildRestoredIndexTranslationTable ( currentLevelData . levelInfo . levelList [ i ] . mapName , pSaveData , true ) ;
Finish ( pSaveData ) ;
}
}
g_ServerGlobalVariables . pSaveData = NULL ;
if ( ! foundprevious )
{
// Host_Error( "Level transition ERROR\nCan't find connection to %s from %s\n", pOldLevel, sv.GetMapName() );
Warning ( " Level transition ERROR \n Can't find connection to %s from %s \n " , pOldLevel , sv . GetMapName ( ) ) ;
Cbuf_AddText ( " disconnect \n " ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pFile -
// Output : int
//-----------------------------------------------------------------------------
int CSaveRestore : : FileSize ( FileHandle_t pFile )
{
if ( ! pFile )
return 0 ;
return g_pSaveRestoreFileSystem - > Size ( pFile ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Copies the contents of the save directory into a single file
//-----------------------------------------------------------------------------
void CSaveRestore : : DirectoryCopy ( const char * pPath , const char * pDestFileName , bool bIsXSave )
{
SaveMsg ( " Directory copy (%s) \n " , pPath ) ;
g_pSaveRestoreFileSystem - > AsyncFinishAllWrites ( ) ;
int nMaps = g_pSaveRestoreFileSystem - > DirectoryCount ( pPath ) ;
FileHandle_t hFile = g_pSaveRestoreFileSystem - > Open ( pDestFileName , " ab+ " ) ;
if ( hFile )
{
g_pSaveRestoreFileSystem - > Write ( & nMaps , sizeof ( nMaps ) , hFile ) ;
g_pSaveRestoreFileSystem - > Close ( hFile ) ;
g_pSaveRestoreFileSystem - > DirectoryCopy ( pPath , pDestFileName , bIsXSave ) ;
}
else
{
Warning ( " Invalid save, failed to open file \n " ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Extracts all the files contained within pFile
//-----------------------------------------------------------------------------
bool CSaveRestore : : DirectoryExtract ( FileHandle_t pFile , int fileCount )
{
return g_pSaveRestoreFileSystem - > DirectoryExtract ( pFile , fileCount , IsXSave ( ) ) ;
}
//-----------------------------------------------------------------------------
// Purpose: returns the number of save files in the specified filter
//-----------------------------------------------------------------------------
void CSaveRestore : : DirectoryCount ( const char * pPath , int * pResult )
{
LOCAL_THREAD_LOCK ( ) ;
if ( * pResult = = - 1 )
* pResult = g_pSaveRestoreFileSystem - > DirectoryCount ( pPath ) ;
// else already set by worker thread
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pPath -
//-----------------------------------------------------------------------------
void CSaveRestore : : DirectoryClear ( const char * pPath )
{
g_pSaveRestoreFileSystem - > DirectoryClear ( pPath , IsXSave ( ) ) ;
}
//-----------------------------------------------------------------------------
// Purpose: deletes all the partial save files from the save game directory
//-----------------------------------------------------------------------------
void CSaveRestore : : ClearSaveDir ( void )
{
m_bClearSaveDir = true ;
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CSaveRestore : : DoClearSaveDir ( bool bIsXSave )
{
// before we clear the save dir, we need to make sure that
// any async-written save games have finished writing,
// since we still may need these temp files to write the save game
char szName [ MAX_OSPATH ] ;
if ( ! bIsXSave )
{
Q_snprintf ( szName , sizeof ( szName ) , " %s " , GetSaveDir ( ) ) ;
Q_FixSlashes ( szName ) ;
// Create save directory if it doesn't exist
Sys_mkdir ( szName ) ;
}
else
{
Q_snprintf ( szName , sizeof ( szName ) , " %s: \\ " , GetCurrentMod ( ) ) ;
}
Q_strncat ( szName , " *.HL? " , sizeof ( szName ) , COPY_ALL_CHARACTERS ) ;
DirectoryClear ( szName ) ;
}
void CSaveRestore : : RequestClearSaveDir ( void )
{
m_bClearSaveDir = true ;
}
void CSaveRestore : : OnFinishedClientRestore ( )
{
g_ClientDLL - > DispatchOnRestore ( ) ;
ClearRestoredIndexTranslationTables ( ) ;
if ( m_bClearSaveDir )
{
m_bClearSaveDir = false ;
FinishAsyncSave ( ) ;
DoClearSaveDir ( IsXSave ( ) ) ;
}
}
void CSaveRestore : : AutoSaveDangerousIsSafe ( )
{
if ( save_async . GetBool ( ) & & ThreadInMainThread ( ) & & g_pSaveThread )
{
g_pSaveThread - > QueueCall ( this , & CSaveRestore : : FinishAsyncSave ) ;
g_pSaveThread - > QueueCall ( this , & CSaveRestore : : AutoSaveDangerousIsSafe ) ;
return ;
}
if ( ! m_bWaitingForSafeDangerousSave )
return ;
m_bWaitingForSafeDangerousSave = false ;
ConDMsg ( " Committing autosavedangerous... \n " ) ;
char szOldName [ MAX_PATH ] ;
char szNewName [ MAX_PATH ] ;
// Back up the old autosaves
if ( StorageDeviceValid ( ) )
{
AgeSaveList ( " autosave " , save_history_count . GetInt ( ) , IsXSave ( ) ) ;
}
// Rename the screenshot
if ( ! IsX360 ( ) )
{
Q_snprintf ( szOldName , sizeof ( szOldName ) , " //%s/%sautosavedangerous%s.tga " , MOD_DIR , GetSaveDir ( ) , GetPlatformExt ( ) ) ;
Q_snprintf ( szNewName , sizeof ( szNewName ) , " //%s/%sautosave%s.tga " , MOD_DIR , GetSaveDir ( ) , GetPlatformExt ( ) ) ;
// there could be an old version, remove it
if ( g_pFileSystem - > FileExists ( szNewName ) )
{
g_pFileSystem - > RemoveFile ( szNewName ) ;
}
if ( g_pFileSystem - > FileExists ( szOldName ) )
{
if ( ! g_pFileSystem - > RenameFile ( szOldName , szNewName ) )
{
SetMostRecentSaveGame ( " autosavedangerous " ) ;
return ;
}
}
}
// Rename the dangerous auto save as a normal auto save
if ( ! IsXSave ( ) )
{
Q_snprintf ( szOldName , sizeof ( szOldName ) , " //%s/%sautosavedangerous%s.sav " , MOD_DIR , GetSaveDir ( ) , GetPlatformExt ( ) ) ;
Q_snprintf ( szNewName , sizeof ( szNewName ) , " //%s/%sautosave%s.sav " , MOD_DIR , GetSaveDir ( ) , GetPlatformExt ( ) ) ;
}
else
{
Q_snprintf ( szOldName , sizeof ( szOldName ) , " %s: \\ autosavedangerous%s.sav " , GetCurrentMod ( ) , GetPlatformExt ( ) ) ;
Q_snprintf ( szNewName , sizeof ( szNewName ) , " %s: \\ autosave%s.sav " , GetCurrentMod ( ) , GetPlatformExt ( ) ) ;
}
// there could be an old version, remove it
if ( g_pFileSystem - > FileExists ( szNewName ) )
{
g_pFileSystem - > RemoveFile ( szNewName ) ;
}
if ( ! g_pFileSystem - > RenameFile ( szOldName , szNewName ) )
{
SetMostRecentSaveGame ( " autosavedangerous " ) ;
return ;
}
// Use this as the most recent now that it's safe
SetMostRecentSaveGame ( " autosave " ) ;
// Finish off all writes
if ( IsXSave ( ) )
{
g_pXboxSystem - > FinishContainerWrites ( ) ;
}
}
static void SaveGame ( const CCommand & args )
{
bool bFinishAsync = false ;
bool bSetMostRecent = true ;
bool bRenameMap = false ;
if ( args . ArgC ( ) > 2 )
{
for ( int i = 2 ; i < args . ArgC ( ) ; i + + )
{
if ( ! Q_stricmp ( args [ i ] , " wait " ) )
{
bFinishAsync = true ;
}
else if ( ! Q_stricmp ( args [ i ] , " notmostrecent " ) )
{
bSetMostRecent = false ;
}
else if ( ! Q_stricmp ( args [ i ] , " copymap " ) )
{
bRenameMap = true ;
}
}
}
char szMapName [ MAX_PATH ] ;
if ( bRenameMap )
{
// HACK: The bug is going to make a copy of this map, so replace the global state to
// fool the system
Q_strncpy ( szMapName , sv . m_szMapname , sizeof ( szMapName ) ) ;
Q_strncpy ( sv . m_szMapname , args [ 1 ] , sizeof ( sv . m_szMapname ) ) ;
}
int iAdditionalSeconds = g_ServerGlobalVariables . curtime - saverestore - > GetMostRecentElapsedTimeSet ( ) ;
int iAdditionalMinutes = iAdditionalSeconds / 60 ;
iAdditionalSeconds - = iAdditionalMinutes * 60 ;
char comment [ 80 ] ;
GetServerSaveCommentEx (
comment ,
sizeof ( comment ) ,
saverestore - > GetMostRecentElapsedMinutes ( ) + iAdditionalMinutes ,
saverestore - > GetMostRecentElapsedSeconds ( ) + iAdditionalSeconds ) ;
saverestore - > SaveGameSlot ( args [ 1 ] , comment , false , bSetMostRecent ) ;
if ( bFinishAsync )
{
FinishAsyncSave ( ) ;
}
if ( bRenameMap )
{
// HACK: Put the original name back
Q_strncpy ( sv . m_szMapname , szMapName , sizeof ( sv . m_szMapname ) ) ;
}
# if !defined (SWDS)
CL_HudMessage ( IsX360 ( ) ? " GAMESAVED_360 " : " GAMESAVED " ) ;
# endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : void Host_Savegame_f
//-----------------------------------------------------------------------------
CON_COMMAND_F ( save , " Saves current game. " , FCVAR_DONTRECORD )
{
// Can we save at this point?
if ( ! saverestore - > IsValidSave ( ) )
return ;
if ( args . ArgC ( ) < 2 )
{
ConDMsg ( " save <savename> [wait]: save a game \n " ) ;
return ;
}
if ( strstr ( args [ 1 ] , " .. " ) )
{
ConDMsg ( " Relative pathnames are not allowed. \n " ) ;
return ;
}
if ( strstr ( sv . m_szMapname , " background " ) )
{
ConDMsg ( " \" background \" is a reserved map name and cannot be saved or loaded. \n " ) ;
return ;
}
g_SaveRestore . SetIsXSave ( false ) ;
SaveGame ( args ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : void Host_Savegame_f
//-----------------------------------------------------------------------------
CON_COMMAND_F ( xsave , " Saves current game to a 360 storage device. " , FCVAR_DONTRECORD )
{
// Can we save at this point?
if ( ! saverestore - > IsValidSave ( ) )
return ;
if ( args . ArgC ( ) < 2 )
{
ConDMsg ( " save <savename> [wait]: save a game \n " ) ;
return ;
}
if ( strstr ( args [ 1 ] , " .. " ) )
{
ConDMsg ( " Relative pathnames are not allowed. \n " ) ;
return ;
}
if ( strstr ( sv . m_szMapname , " background " ) )
{
ConDMsg ( " \" background \" is a reserved map name and cannot be saved or loaded. \n " ) ;
return ;
}
g_SaveRestore . SetIsXSave ( IsX360 ( ) ) ;
SaveGame ( args ) ;
}
//-----------------------------------------------------------------------------
// Purpose: saves the game, but only includes the state for the current level
// useful for bug reporting.
// Output :
//-----------------------------------------------------------------------------
CON_COMMAND_F ( minisave , " Saves game (for current level only!) " , FCVAR_DONTRECORD )
{
// Can we save at this point?
if ( ! saverestore - > IsValidSave ( ) )
return ;
if ( args . ArgC ( ) ! = 2 | | strstr ( args [ 1 ] , " .. " ) )
return ;
int iAdditionalSeconds = g_ServerGlobalVariables . curtime - saverestore - > GetMostRecentElapsedTimeSet ( ) ;
int iAdditionalMinutes = iAdditionalSeconds / 60 ;
iAdditionalSeconds - = iAdditionalMinutes * 60 ;
char comment [ 80 ] ;
GetServerSaveCommentEx (
comment ,
sizeof ( comment ) ,
saverestore - > GetMostRecentElapsedMinutes ( ) + iAdditionalMinutes ,
saverestore - > GetMostRecentElapsedSeconds ( ) + iAdditionalSeconds ) ;
saverestore - > SaveGameSlot ( args [ 1 ] , comment , true , true ) ;
}
static void AutoSave_Silent ( bool bDangerous )
{
// Can we save at this point?
if ( ! saverestore - > IsValidSave ( ) )
return ;
int iAdditionalSeconds = g_ServerGlobalVariables . curtime - saverestore - > GetMostRecentElapsedTimeSet ( ) ;
int iAdditionalMinutes = iAdditionalSeconds / 60 ;
iAdditionalSeconds - = iAdditionalMinutes * 60 ;
char comment [ 80 ] ;
GetServerSaveCommentEx (
comment ,
sizeof ( comment ) ,
saverestore - > GetMostRecentElapsedMinutes ( ) + iAdditionalMinutes ,
saverestore - > GetMostRecentElapsedSeconds ( ) + iAdditionalSeconds ) ;
g_SaveRestore . SetIsXSave ( IsX360 ( ) ) ;
if ( ! bDangerous )
{
saverestore - > SaveGameSlot ( " autosave " , comment , false , true ) ;
}
else
{
saverestore - > SaveGameSlot ( " autosavedangerous " , comment , false , false ) ;
}
}
static ConVar save_console ( " save_console " , " 0 " , 0 , " Autosave on the PC behaves like it does on the consoles. " ) ;
static ConVar save_huddelayframes ( " save_huddelayframes " , " 1 " , 0 , " Number of frames to defer for drawing the Saving message. " ) ;
CON_COMMAND ( _autosave , " Autosave " )
{
AutoSave_Silent ( false ) ;
bool bConsole = save_console . GetBool ( ) ;
# if defined ( _X360 )
bConsole = true ;
# endif
if ( bConsole )
{
# if !defined (SWDS)
CL_HudMessage ( IsX360 ( ) ? " GAMESAVED_360 " : " GAMESAVED " ) ;
# endif
}
}
CON_COMMAND ( _autosavedangerous , " AutoSaveDangerous " )
{
// Don't even bother if we've got an invalid save
if ( saverestore - > StorageDeviceValid ( ) = = false )
return ;
AutoSave_Silent ( true ) ;
bool bConsole = save_console . GetBool ( ) ;
# if defined ( _X360 )
bConsole = true ;
# endif
if ( bConsole )
{
# if !defined (SWDS)
CL_HudMessage ( IsX360 ( ) ? " GAMESAVED_360 " : " GAMESAVED " ) ;
# endif
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : void Host_AutoSave_f
//-----------------------------------------------------------------------------
CON_COMMAND ( autosave , " Autosave " )
{
// Can we save at this point?
if ( ! saverestore - > IsValidSave ( ) | | ! sv_autosave . GetBool ( ) )
return ;
bool bConsole = save_console . GetBool ( ) ;
char const * pchSaving = IsX360 ( ) ? " GAMESAVING_360 " : " GAMESAVING " ;
# if defined ( _X360 )
bConsole = true ;
# endif
if ( bConsole )
{
# if !defined (SWDS)
CL_HudMessage ( pchSaving ) ;
# endif
g_SaveRestore . AddDeferredCommand ( " _autosave " ) ;
}
else
{
AutoSave_Silent ( false ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : void Host_AutoSaveDangerous_f
//-----------------------------------------------------------------------------
CON_COMMAND ( autosavedangerous , " AutoSaveDangerous " )
{
// Can we save at this point?
if ( ! saverestore - > IsValidSave ( ) | | ! sv_autosave . GetBool ( ) )
return ;
// Don't even bother if we've got an invalid save
if ( saverestore - > StorageDeviceValid ( ) = = false )
return ;
//Don't print out "SAVED" unless we're running on an Xbox (in which case it prints "CHECKPOINT").
bool bConsole = save_console . GetBool ( ) ;
char const * pchSaving = IsX360 ( ) ? " GAMESAVING_360 " : " GAMESAVING " ;
# if defined ( _X360 )
bConsole = true ;
# endif
if ( bConsole )
{
# if !defined (SWDS)
CL_HudMessage ( pchSaving ) ;
# endif
g_SaveRestore . AddDeferredCommand ( " _autosavedangerous " ) ;
}
else
{
AutoSave_Silent ( true ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : void Host_AutoSaveSafe_f
//-----------------------------------------------------------------------------
CON_COMMAND ( autosavedangerousissafe , " " )
{
saverestore - > AutoSaveDangerousIsSafe ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Load a save game in response to a console command (load or xload)
//-----------------------------------------------------------------------------
static void LoadSaveGame ( const char * savename )
{
// Make sure the freaking save file exists....
if ( ! saverestore - > SaveFileExists ( savename ) )
{
Warning ( " Can't load '%s', file missing! \n " , savename ) ;
return ;
}
GetTestScriptMgr ( ) - > SetWaitCheckPoint ( " load_game " ) ;
// if we're not currently in a game, show progress
if ( ! sv . IsActive ( ) | | sv . IsLevelMainMenuBackground ( ) )
{
EngineVGui ( ) - > EnabledProgressBarForNextLoad ( ) ;
}
// Put up loading plaque
SCR_BeginLoadingPlaque ( ) ;
Host_Disconnect ( false ) ; // stop old game
HostState_LoadGame ( savename , false ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : void Host_Loadgame_f
//-----------------------------------------------------------------------------
void Host_Loadgame_f ( const CCommand & args )
{
if ( cmd_source ! = src_command )
return ;
if ( sv . IsMultiplayer ( ) )
{
ConMsg ( " Can't load in multiplayer games. \n " ) ;
return ;
}
if ( args . ArgC ( ) < 2 )
{
ConMsg ( " load <savename> : load a game \n " ) ;
return ;
}
g_szMapLoadOverride [ 0 ] = 0 ;
if ( args . ArgC ( ) > 2 )
{
V_strncpy ( g_szMapLoadOverride , args [ 2 ] , sizeof ( g_szMapLoadOverride ) ) ;
}
g_SaveRestore . SetIsXSave ( false ) ;
LoadSaveGame ( args [ 1 ] ) ;
}
// Always loads saves from DEFAULT_WRITE_PATH, regardless of platform
CON_COMMAND_AUTOCOMPLETEFILE ( load , Host_Loadgame_f , " Load a saved game. " , " save " , sav ) ;
// Loads saves from the 360 storage device
CON_COMMAND ( xload , " Load a saved game from a 360 storage device. " )
{
if ( sv . IsMultiplayer ( ) )
{
ConMsg ( " Can't load in multiplayer games. \n " ) ;
return ;
}
if ( args . ArgC ( ) ! = 2 )
{
ConMsg ( " xload <savename> \n " ) ;
return ;
}
g_SaveRestore . SetIsXSave ( IsX360 ( ) ) ;
LoadSaveGame ( args [ 1 ] ) ;
}
CON_COMMAND ( save_finish_async , " " )
{
FinishAsyncSave ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CSaveRestore : : Init ( void )
{
int minplayers = 1 ;
// serverGameClients should have been initialized by the CModAppSystemGroup Create method (so it's before the Host_Init stuff which calls this)
Assert ( serverGameClients ) ;
if ( serverGameClients )
{
int dummy = 1 ;
int dummy2 = 1 ;
serverGameClients - > GetPlayerLimits ( minplayers , dummy , dummy2 ) ;
}
if ( ! serverGameClients | |
( minplayers = = 1 ) )
{
GetSaveMemory ( ) ;
Assert ( ! g_pSaveThread ) ;
ThreadPoolStartParams_t threadPoolStartParams ;
threadPoolStartParams . nThreads = 1 ;
if ( ! IsX360 ( ) )
{
threadPoolStartParams . fDistribute = TRS_FALSE ;
}
else
{
threadPoolStartParams . iAffinityTable [ 0 ] = XBOX_PROCESSOR_1 ;
threadPoolStartParams . bUseAffinityTable = true ;
}
g_pSaveThread = CreateThreadPool ( ) ;
g_pSaveThread - > Start ( threadPoolStartParams , " SaveJob " ) ;
}
m_nDeferredCommandFrames = 0 ;
m_szSaveGameScreenshotFile [ 0 ] = 0 ;
if ( ! IsX360 ( ) & & ! CommandLine ( ) - > FindParm ( " -noclearsave " ) )
{
ClearSaveDir ( ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CSaveRestore : : Shutdown ( void )
{
FinishAsyncSave ( ) ;
if ( g_pSaveThread )
{
g_pSaveThread - > Stop ( ) ;
g_pSaveThread - > Release ( ) ;
g_pSaveThread = NULL ;
}
m_szSaveGameScreenshotFile [ 0 ] = 0 ;
}
char const * CSaveRestore : : GetMostRecentlyLoadedFileName ( )
{
return m_szMostRecentSaveLoadGame ;
}
char const * CSaveRestore : : GetSaveFileName ( )
{
return m_szSaveGameName ;
}
void CSaveRestore : : AddDeferredCommand ( char const * pchCommand )
{
m_nDeferredCommandFrames = clamp ( save_huddelayframes . GetInt ( ) , 0 , 10 ) ;
CUtlSymbol sym ;
sym = pchCommand ;
m_sDeferredCommands . AddToTail ( sym ) ;
}
void CSaveRestore : : OnFrameRendered ( )
{
if ( m_nDeferredCommandFrames > 0 )
{
- - m_nDeferredCommandFrames ;
if ( m_nDeferredCommandFrames = = 0 )
{
// Dispatch deferred command
for ( int i = 0 ; i < m_sDeferredCommands . Count ( ) ; + + i )
{
Cbuf_AddText ( m_sDeferredCommands [ i ] . String ( ) ) ;
}
m_sDeferredCommands . Purge ( ) ;
}
}
}
bool CSaveRestore : : StorageDeviceValid ( void )
{
// PC is always valid
if ( ! IsX360 ( ) )
return true ;
// Non-XSaves are always valid
if ( ! IsXSave ( ) )
return true ;
# ifdef _X360
// Otherwise, we must have a real storage device
int nStorageDeviceID = XBX_GetStorageDeviceId ( ) ;
return ( nStorageDeviceID ! = XBX_INVALID_STORAGE_ID & & nStorageDeviceID ! = XBX_STORAGE_DECLINED ) ;
# endif
return true ;
}
bool CSaveRestore : : IsSaveInProgress ( )
{
return g_bSaveInProgress ;
}