You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3369 lines
95 KiB
3369 lines
95 KiB
//========= 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( §ionsInfo, 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( §ionheaderversion, sizeof( sectionheaderversion ), pFile ); |
|
|
|
if ( sectionheaderversion != SECTION_VERSION_NUMBER ) |
|
{ |
|
g_pSaveRestoreFileSystem->Close( pFile ); |
|
return; |
|
} |
|
g_pSaveRestoreFileSystem->Read( §ions, 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( §ions, 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( §ionheaderversion, sizeof( sectionheaderversion ) ); |
|
buffer.Put( (baseclientsections_t * )§ions, 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; |
|
int 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) |
|
nFieldSize = *(short *)pData; |
|
pData += sizeof(short); |
|
pFieldName = pTokenList[ *(short *)pData ]; |
|
|
|
if ( !pFieldName || Q_stricmp( pFieldName, "GameHeader" ) ) |
|
{ |
|
delete[] pSaveData; |
|
delete[] pTokenList; |
|
return 0; |
|
}; |
|
|
|
// int (fieldcount) |
|
pData += sizeof(short); |
|
nNumberOfFields = *(int*)pData; |
|
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 |
|
|
|
nFieldSize = *(short *)pData; |
|
pData += sizeof(short); |
|
|
|
pFieldName = pTokenList[ *(short *)pData ]; |
|
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( §ionsInfo, 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( ¤tLevelData, 0, sizeof(CSaveRestoreData) ); |
|
g_ServerGlobalVariables.pSaveData = ¤tLevelData; |
|
// 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( ¤tLevelData, 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\nCan'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; |
|
} |
|
|
|
|