summaryrefslogtreecommitdiff
path: root/engine/saverestore_filesystem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/saverestore_filesystem.cpp')
-rw-r--r--engine/saverestore_filesystem.cpp1478
1 files changed, 1478 insertions, 0 deletions
diff --git a/engine/saverestore_filesystem.cpp b/engine/saverestore_filesystem.cpp
new file mode 100644
index 0000000..3aef07a
--- /dev/null
+++ b/engine/saverestore_filesystem.cpp
@@ -0,0 +1,1478 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Filesystem abstraction for CSaveRestore - allows for storing temp save files
+// either in memory or on disk.
+//
+//===========================================================================//
+
+#ifdef _WIN32
+#include "winerror.h"
+#endif
+#include "filesystem_engine.h"
+#include "saverestore_filesystem.h"
+#include "host_saverestore.h"
+#include "host.h"
+#include "sys.h"
+#include "tier1/utlbuffer.h"
+#include "tier1/lzss.h"
+#include "tier1/convar.h"
+#include "ixboxsystem.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+extern ConVar save_spew;
+extern IXboxSystem *g_pXboxSystem;
+
+#define SaveMsg if ( !save_spew.GetBool() ) ; else Msg
+
+void SaveInMemoryCallback( IConVar *var, const char *pOldString, float flOldValue );
+
+#ifdef _X360
+ConVar save_in_memory( "save_in_memory", "1", 1, "Set to 1 to save to memory instead of disk (Xbox 360)", SaveInMemoryCallback );
+#else
+ConVar save_in_memory( "save_in_memory", "0", 0, "Set to 1 to save to memory instead of disk (Xbox 360)", SaveInMemoryCallback );
+#endif // _X360
+
+#define INVALID_INDEX (GetDirectory().InvalidIndex())
+enum { READ_ONLY, WRITE_ONLY };
+
+static float g_fPrevSaveInMemoryValue;
+
+static bool SaveFileLessFunc( const CUtlSymbol &lhs, const CUtlSymbol &rhs )
+{
+ return lhs < rhs;
+}
+
+//----------------------------------------------------------------------------
+// Simulates the save directory in RAM.
+//----------------------------------------------------------------------------
+class CSaveDirectory
+{
+public:
+ CSaveDirectory()
+ {
+ m_Files.SetLessFunc( SaveFileLessFunc );
+ file_t dummy;
+ dummy.name = m_SymbolTable.AddString( "dummy" );
+ m_Files.Insert( dummy.name, dummy );
+ }
+
+ ~CSaveDirectory()
+ {
+ int i = m_Files.FirstInorder();
+ while ( m_Files.IsValidIndex( i ) )
+ {
+ int idx = i;
+ i = m_Files.NextInorder( i );
+
+ delete m_Files[idx].pBuffer;
+ delete m_Files[idx].pCompressedBuffer;
+ m_Files.RemoveAt( idx );
+ }
+ }
+
+ struct file_t
+ {
+ file_t()
+ {
+ pBuffer = NULL;
+ pCompressedBuffer = NULL;
+ nSize = 0;
+ nCompressedSize = NULL;
+ }
+
+
+ int eType;
+ CUtlSymbol name;
+ unsigned int nSize;
+ unsigned int nCompressedSize;
+ CUtlBuffer *pBuffer;
+ CUtlBuffer *pCompressedBuffer;
+ };
+
+ CUtlSymbolTable m_SymbolTable;
+ CUtlMap<CUtlSymbol, file_t> m_Files;
+};
+
+typedef CSaveDirectory::file_t SaveFile_t;
+
+//----------------------------------------------------------------------------
+// CSaveRestoreFileSystem: Manipulates files in the CSaveDirectory
+//----------------------------------------------------------------------------
+class CSaveRestoreFileSystem : public ISaveRestoreFileSystem
+{
+public:
+ CSaveRestoreFileSystem()
+ {
+ m_pSaveDirectory = new CSaveDirectory();
+ m_iContainerOpens = 0;
+ }
+
+ ~CSaveRestoreFileSystem()
+ {
+ delete m_pSaveDirectory;
+ }
+
+ bool FileExists( const char *pFileName, const char *pPathID = NULL );
+ void RenameFile( char const *pOldPath, char const *pNewPath, const char *pathID = NULL );
+ void RemoveFile( char const* pRelativePath, const char *pathID = NULL );
+
+ FileHandle_t Open( const char *pFileName, const char *pOptions, const char *pathID = NULL );
+ void Close( FileHandle_t );
+ int Read( void *pOutput, int size, FileHandle_t file );
+ int Write( void const* pInput, int size, FileHandle_t file );
+ void Seek( FileHandle_t file, int pos, FileSystemSeek_t method );
+ unsigned int Tell( FileHandle_t file );
+ unsigned int Size( FileHandle_t file );
+ unsigned int Size( const char *pFileName, const char *pPathID = NULL );
+
+ void AsyncFinishAllWrites( void );
+ void AsyncRelease( FSAsyncControl_t hControl );
+ FSAsyncStatus_t AsyncWrite( const char *pFileName, const void *pSrc, int nSrcBytes, bool bFreeMemory, bool bAppend, FSAsyncControl_t *pControl = NULL );
+ FSAsyncStatus_t AsyncFinish( FSAsyncControl_t hControl, bool wait = false );
+ FSAsyncStatus_t AsyncAppend( const char *pFileName, const void *pSrc, int nSrcBytes, bool bFreeMemory, FSAsyncControl_t *pControl = NULL );
+ FSAsyncStatus_t AsyncAppendFile( const char *pDestFileName, const char *pSrcFileName, FSAsyncControl_t *pControl = NULL );
+
+ void DirectoryCopy( const char *pPath, const char *pDestFileName, bool bIsXSave );
+ void DirectorCopyToMemory( const char *pPath, const char *pDestFileName );
+ bool DirectoryExtract( FileHandle_t pFile, int fileCount, bool bIsXSave );
+ int DirectoryCount( const char *pPath );
+ void DirectoryClear( const char *pPath, bool bIsXSave );
+
+ void WriteSaveDirectoryToDisk( void );
+ void LoadSaveDirectoryFromDisk( const char *pPath );
+ void DumpSaveDirectory( void );
+
+ void Compress( SaveFile_t *pFile );
+ void Uncompress( SaveFile_t *pFile );
+
+ void AuditFiles( void );
+ bool LoadFileFromDisk( const char *pFilename );
+
+private:
+ CSaveDirectory *m_pSaveDirectory;
+ CUtlMap<CUtlSymbol, SaveFile_t> &GetDirectory( void ) { return m_pSaveDirectory->m_Files; }
+ SaveFile_t &GetFile( const int idx ) { return m_pSaveDirectory->m_Files[idx]; }
+ SaveFile_t &GetFile( const FileHandle_t hFile ) { return GetFile( (unsigned int)hFile ); }
+
+ FileHandle_t GetFileHandle( const char *pFileName );
+ int GetFileIndex( const char *pFileName );
+
+ bool HandleIsValid( FileHandle_t hFile );
+
+ unsigned int CompressedSize( const char *pFileName );
+
+ CUtlSymbol AddString( const char *str )
+ {
+ char szString[ MAX_PATH ];
+ Q_strncpy( szString, str, sizeof( szString ) );
+ return m_pSaveDirectory->m_SymbolTable.AddString( Q_strlower( szString ) );
+ }
+ const char *GetString( CUtlSymbol &id ) { return m_pSaveDirectory->m_SymbolTable.String( id ); }
+
+
+ int m_iContainerOpens;
+};
+
+//#define TEST_LZSS_WINDOW_SIZES
+
+//----------------------------------------------------------------------------
+// Compress the file data
+//----------------------------------------------------------------------------
+void CSaveRestoreFileSystem::Compress( SaveFile_t *pFile )
+{
+ pFile->pCompressedBuffer->Purge();
+
+#ifdef TEST_LZSS_WINDOW_SIZES
+ // Compress the data here
+ CLZSS compressor_test;
+ CLZSS newcompressor_test( 2048 );
+ pFile->nCompressedSize = 0;
+ float start = Plat_FloatTime();
+ for(int i=0;i<10;i++)
+ {
+ uint32 sz;
+ unsigned char *pCompressedBuffer = compressor_test.Compress(
+ (unsigned char *) pFile->pBuffer->Base(), pFile->nSize, &sz );
+ delete[] pCompressedBuffer;
+ }
+ Warning(" old compressor_test %f", Plat_FloatTime() - start );
+ start = Plat_FloatTime();
+ for(int i=0;i<10;i++)
+ {
+ uint32 sz;
+ unsigned char *pCompressedBuffer = newcompressor_test.Compress(
+ (unsigned char *) pFile->pBuffer->Base(), pFile->nSize, &sz );
+ delete[] pCompressedBuffer;
+ }
+ Warning(" new compressor_test %f", Plat_FloatTime() - start );
+ if ( 1)
+ {
+ uint32 sz;
+ uint32 sz1;
+ unsigned char *pNewCompressedBuffer = newcompressor_test.Compress(
+ (unsigned char *) pFile->pBuffer->Base(), pFile->nSize, &sz );
+ unsigned char *pOldCompressedBuffer = compressor_test.Compress(
+ (unsigned char *) pFile->pBuffer->Base(), pFile->nSize, &sz1 );
+ if ( ! pNewCompressedBuffer )
+ Warning("new no comp");
+ if ( ! pOldCompressedBuffer )
+ Warning("old no comp");
+ if ( pNewCompressedBuffer && pOldCompressedBuffer )
+ {
+ if ( sz != sz1 )
+ Warning(" new size = %d old = %d", sz, sz1 );
+ if ( memcmp( pNewCompressedBuffer, pOldCompressedBuffer, sz ) )
+ Warning("data mismatch");
+ }
+ delete[] pOldCompressedBuffer;
+ delete[] pNewCompressedBuffer;
+ }
+#endif
+
+ CLZSS compressor( 2048 );
+
+ unsigned char *pCompressedBuffer = compressor.Compress( (unsigned char *) pFile->pBuffer->Base(), pFile->nSize, &pFile->nCompressedSize );
+ if ( pCompressedBuffer == NULL )
+ {
+ // Just copy the buffer uncompressed
+ pFile->pCompressedBuffer->Put( pFile->pBuffer->Base(), pFile->nSize );
+ pFile->nCompressedSize = pFile->nSize;
+ }
+ else
+ {
+ // Take the compressed buffer as our own
+ pFile->pCompressedBuffer->AssumeMemory( pCompressedBuffer, pFile->nCompressedSize, pFile->nCompressedSize ); // ?
+ }
+ // end compression
+
+ pFile->pCompressedBuffer->SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
+ pFile->pCompressedBuffer->SeekPut( CUtlBuffer::SEEK_HEAD, pFile->nCompressedSize );
+
+ // Don't want the uncompressed memory hanging around
+ pFile->pBuffer->Purge();
+
+ unsigned int srcBytes = pFile->nSize;
+
+ pFile->nSize = 0;
+
+ unsigned int destBytes = pFile->nCompressedSize;
+
+ float percent = 0.f;
+ if ( srcBytes )
+ percent = 100.0f * (1.0f - (float)destBytes/(float)srcBytes);
+
+ SaveMsg( "SIM: SaveDir: (%s) Compressed %d bytes to %d bytes. (%.0f%%)\n", GetString( pFile->name ), srcBytes, destBytes, percent );
+}
+
+//----------------------------------------------------------------------------
+// Uncompress the file data
+//----------------------------------------------------------------------------
+void CSaveRestoreFileSystem::Uncompress( SaveFile_t *pFile )
+{
+ pFile->pBuffer->Purge();
+
+ // Uncompress the data here
+ CLZSS compressor;
+ unsigned int nUncompressedSize = compressor.GetActualSize( (unsigned char *) pFile->pCompressedBuffer->Base() );
+ if ( nUncompressedSize != 0 )
+ {
+ unsigned char *pUncompressBuffer = (unsigned char *) malloc( nUncompressedSize );
+ nUncompressedSize = compressor.Uncompress( (unsigned char *) pFile->pCompressedBuffer->Base(), pUncompressBuffer );
+ pFile->pBuffer->AssumeMemory( pUncompressBuffer, nUncompressedSize, nUncompressedSize ); // ?
+ }
+ else
+ {
+ // Put it directly into our target
+ pFile->pBuffer->Put( (unsigned char *) pFile->pCompressedBuffer->Base(), pFile->nCompressedSize );
+ }
+ // end decompression
+
+ pFile->nSize = pFile->pBuffer->TellMaxPut();
+ pFile->pBuffer->SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
+ pFile->pBuffer->SeekPut( CUtlBuffer::SEEK_HEAD, pFile->nSize );
+
+ unsigned int srcBytes = pFile->nCompressedSize;
+ unsigned int destBytes = pFile->nSize;
+
+ SaveMsg( "SIM: SaveDir: (%s) Uncompressed %d bytes to %d bytes.\n", GetString( pFile->name ), srcBytes, destBytes );
+}
+
+//----------------------------------------------------------------------------
+// Access the save files
+//----------------------------------------------------------------------------
+int CSaveRestoreFileSystem::GetFileIndex( const char *filename )
+{
+ CUtlSymbol id = AddString( Q_UnqualifiedFileName( filename ) );
+ return GetDirectory().Find( id );
+}
+
+FileHandle_t CSaveRestoreFileSystem::GetFileHandle( const char *filename )
+{
+ int idx = GetFileIndex( filename );
+ if ( idx == INVALID_INDEX )
+ {
+ idx = 0;
+ }
+ return (void*)idx;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns whether the named memory block exists
+//-----------------------------------------------------------------------------
+bool CSaveRestoreFileSystem::FileExists( const char *pFileName, const char *pPathID )
+{
+ return ( GetFileHandle( pFileName ) != NULL );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Validates a file handle
+//-----------------------------------------------------------------------------
+bool CSaveRestoreFileSystem::HandleIsValid( FileHandle_t hFile )
+{
+ return hFile && GetDirectory().IsValidIndex( (unsigned int)hFile );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Renames a block of memory
+//-----------------------------------------------------------------------------
+void CSaveRestoreFileSystem::RenameFile( char const *pOldPath, char const *pNewPath, const char *pathID )
+{
+ int idx = GetFileIndex( pOldPath );
+ if ( idx != INVALID_INDEX )
+ {
+ CUtlSymbol newID = AddString( Q_UnqualifiedFileName( pNewPath ) );
+ GetFile( idx ).name = newID;
+ GetDirectory().Reinsert( newID, idx );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Removes a memory block from CSaveDirectory and frees it.
+//-----------------------------------------------------------------------------
+void CSaveRestoreFileSystem::RemoveFile( char const* pRelativePath, const char *pathID )
+{
+ int idx = GetFileIndex( pRelativePath );
+ if ( idx != INVALID_INDEX )
+ {
+ delete GetFile( idx ).pBuffer;
+ delete GetFile( idx ).pCompressedBuffer;
+ GetDirectory().RemoveAt( idx );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Access an existing memory block if it exists, else allocate one.
+//-----------------------------------------------------------------------------
+FileHandle_t CSaveRestoreFileSystem::Open( const char *pFullName, const char *pOptions, const char *pathID )
+{
+ SaveFile_t *pFile = NULL;
+ CUtlSymbol id = AddString( Q_UnqualifiedFileName( pFullName ) );
+ int idx = GetDirectory().Find( id );
+ if ( idx == INVALID_INDEX )
+ {
+ // Don't create a read-only file
+ if ( Q_stricmp( pOptions, "rb" ) )
+ {
+ // Create new file
+ SaveFile_t newFile;
+ newFile.name = id;
+ newFile.pBuffer = new CUtlBuffer();
+ newFile.pCompressedBuffer = new CUtlBuffer();
+ idx = GetDirectory().Insert( id, newFile );
+ }
+ else
+ {
+ return (void*)0;
+ }
+ }
+
+ pFile = &GetFile( idx );
+
+ if ( !Q_stricmp( pOptions, "rb" ) )
+ {
+ Uncompress( pFile );
+ pFile->eType = READ_ONLY;
+ }
+ else if ( !Q_stricmp( pOptions, "wb" ) )
+ {
+ pFile->pBuffer->Clear();
+ pFile->eType = WRITE_ONLY;
+ }
+ else if ( !Q_stricmp( pOptions, "a" ) )
+ {
+ Uncompress( pFile );
+ pFile->eType = WRITE_ONLY;
+ }
+ else if ( !Q_stricmp( pOptions, "ab+" ) )
+ {
+ Uncompress( pFile );
+ pFile->eType = WRITE_ONLY;
+ pFile->pBuffer->SeekPut( CUtlBuffer::SEEK_TAIL, 0 );
+ }
+ else
+ {
+ Assert( 0 );
+ Warning( "CSaveRestoreFileSystem: Attempted to open %s with unsupported option %s\n", pFullName, pOptions );
+ return (void*)0;
+ }
+
+ return (void*)idx;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: No need to close files in memory. Could perform post processing here.
+//-----------------------------------------------------------------------------
+void CSaveRestoreFileSystem::Close( FileHandle_t hFile )
+{
+ // Compress the file
+ if ( HandleIsValid( hFile ) )
+ {
+ SaveFile_t &file = GetFile( hFile );
+
+ // Files opened for read don't need to be recompressed
+ if ( file.eType == READ_ONLY )
+ {
+ SaveMsg("SIM: Closed file: %s\n", GetString( file.name ) );
+ file.pBuffer->Purge();
+ file.nSize = 0;
+ }
+ else
+ {
+ Compress( &file );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reads data from memory.
+//-----------------------------------------------------------------------------
+int CSaveRestoreFileSystem::Read( void *pOutput, int size, FileHandle_t hFile )
+{
+ int readSize = 0;
+ if ( HandleIsValid( hFile ) )
+ {
+ SaveFile_t &file = GetFile( hFile );
+
+ if( file.eType == READ_ONLY )
+ {
+ readSize = file.pBuffer->GetUpTo( pOutput, size );
+ }
+ else
+ {
+ Warning( "Read: Attempted to read from a write-only file" );
+ readSize = 0;
+ Assert( 0 );
+ }
+ }
+ return readSize;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Writes data to memory.
+//-----------------------------------------------------------------------------
+int CSaveRestoreFileSystem::Write( void const* pInput, int size, FileHandle_t hFile )
+{
+ int writeSize = 0;
+ if ( HandleIsValid( hFile ) )
+ {
+ SaveFile_t &file = GetFile( hFile );
+
+ if( file.eType == WRITE_ONLY )
+ {
+ file.pBuffer->Put( pInput, size );
+ file.nSize = file.pBuffer->TellMaxPut();
+ writeSize = size;
+ }
+ else
+ {
+ Warning( "Write: Attempted to write to a read-only file" );
+ writeSize = 0;
+ Assert( 0 );
+ }
+ }
+ return writeSize;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Seek in memory. Seeks the UtlBuffer put or get pos depending
+// on whether the file was opened for read or write.
+//-----------------------------------------------------------------------------
+void CSaveRestoreFileSystem::Seek( FileHandle_t hFile, int pos, FileSystemSeek_t method )
+{
+ if ( HandleIsValid( hFile ) )
+ {
+ SaveFile_t &file = GetFile( hFile );
+ if ( file.eType == READ_ONLY )
+ {
+ file.pBuffer->SeekGet( (CUtlBuffer::SeekType_t)method, pos );
+ }
+ else if ( file.eType == WRITE_ONLY )
+ {
+ file.pBuffer->SeekPut( (CUtlBuffer::SeekType_t)method, pos );
+ }
+ else
+ {
+ Assert( 0 );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return position in memory. Returns UtlBuffer put or get pos depending
+// on whether the file was opened for read or write.
+//-----------------------------------------------------------------------------
+unsigned int CSaveRestoreFileSystem::Tell( FileHandle_t hFile )
+{
+ unsigned int pos = 0;
+ if ( HandleIsValid( hFile ) )
+ {
+ SaveFile_t &file = GetFile( hFile );
+ if ( file.eType == READ_ONLY )
+ {
+ pos = file.pBuffer->TellGet();
+ }
+ else if ( file.eType == WRITE_ONLY )
+ {
+ pos = file.pBuffer->TellPut();
+ }
+ else
+ {
+ Assert( 0 );
+ }
+ }
+ return pos;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return uncompressed memory size
+//-----------------------------------------------------------------------------
+unsigned int CSaveRestoreFileSystem::Size( FileHandle_t hFile )
+{
+ if ( HandleIsValid( hFile ) )
+ {
+ return GetFile( hFile ).nSize;
+ }
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return uncompressed size of data in memory
+//-----------------------------------------------------------------------------
+unsigned int CSaveRestoreFileSystem::Size( const char *pFileName, const char *pPathID )
+{
+ return Size( GetFileHandle( pFileName ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Return compressed size of data in memory
+//-----------------------------------------------------------------------------
+unsigned int CSaveRestoreFileSystem::CompressedSize( const char *pFileName )
+{
+ FileHandle_t hFile = GetFileHandle( pFileName );
+ if ( hFile )
+ {
+ return GetFile( hFile ).nCompressedSize;
+ }
+ return 0;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Writes data to memory. Function is NOT async.
+//-----------------------------------------------------------------------------
+FSAsyncStatus_t CSaveRestoreFileSystem::AsyncWrite( const char *pFileName, const void *pSrc, int nSrcBytes, bool bFreeMemory, bool bAppend, FSAsyncControl_t *pControl )
+{
+ FSAsyncStatus_t retval = FSASYNC_ERR_FAILURE;
+
+ FileHandle_t hFile = Open( pFileName, "wb" );
+ if ( hFile )
+ {
+ SaveFile_t &file = GetFile( (unsigned int)hFile );
+
+ if( file.eType == WRITE_ONLY )
+ {
+ file.pBuffer->Put( pSrc, nSrcBytes );
+ file.nSize = file.pBuffer->TellMaxPut();
+ Compress( &file );
+ retval = FSASYNC_OK;
+ }
+ else
+ {
+ Warning( "AsyncWrite: Attempted to write to a read-only file" );
+ Assert( 0 );
+ }
+ }
+
+ if ( bFreeMemory )
+ free( const_cast< void * >( pSrc ) );
+
+ return retval;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Appends data to a memory block. Function is NOT async.
+//-----------------------------------------------------------------------------
+FSAsyncStatus_t CSaveRestoreFileSystem::AsyncAppend(const char *pFileName, const void *pSrc, int nSrcBytes, bool bFreeMemory, FSAsyncControl_t *pControl )
+{
+ FSAsyncStatus_t retval = FSASYNC_ERR_FAILURE;
+
+ FileHandle_t hFile = Open( pFileName, "a" );
+ if ( hFile )
+ {
+ SaveFile_t &file = GetFile( hFile );
+ if( file.eType == WRITE_ONLY )
+ {
+ file.pBuffer->Put( pSrc, nSrcBytes );
+ file.nSize = file.pBuffer->TellMaxPut();
+ Compress( &file );
+ retval = FSASYNC_OK;
+ }
+ else
+ {
+ Warning( "AsyncAppend: Attempted to write to a read-only file" );
+ Assert( 0 );
+ }
+ }
+
+ if ( bFreeMemory )
+ free( const_cast< void * >( pSrc ) );
+
+ return retval;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Appends a memory block to another memory block. Function is NOT async.
+//-----------------------------------------------------------------------------
+FSAsyncStatus_t CSaveRestoreFileSystem::AsyncAppendFile(const char *pDestFileName, const char *pSrcFileName, FSAsyncControl_t *pControl )
+{
+ FileHandle_t hFile = Open( pSrcFileName, "rb" );
+ if ( hFile )
+ {
+ SaveFile_t &file = GetFile( hFile );
+ return AsyncAppend( pDestFileName, file.pBuffer->Base(), file.nSize, false );
+ }
+ return FSASYNC_ERR_FILEOPEN;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: All operations in memory are synchronous
+//-----------------------------------------------------------------------------
+FSAsyncStatus_t CSaveRestoreFileSystem::AsyncFinish( FSAsyncControl_t hControl, bool wait )
+{
+ // do nothing
+ return FSASYNC_OK;
+}
+void CSaveRestoreFileSystem::AsyncRelease( FSAsyncControl_t hControl )
+{
+ // do nothing
+}
+void CSaveRestoreFileSystem::AsyncFinishAllWrites( void )
+{
+ // Do nothing
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Package up all intermediate files to a save game as per usual, but keep them in memory instead of commiting them to disk
+//-----------------------------------------------------------------------------
+void CSaveRestoreFileSystem::DirectorCopyToMemory( const char *pPath, const char *pDestFileName )
+{
+ // Write the save file
+ FileHandle_t hSaveFile = Open( pDestFileName, "ab+", pPath );
+ if ( !hSaveFile )
+ return;
+
+ SaveFile_t &saveFile = GetFile( hSaveFile );
+
+ // At this point, we're going to be sneaky and spoof the uncompressed buffer back into the compressed one
+ // We need to do this because the file goes out to disk as a mixture of an uncompressed header and tags, and compressed
+ // intermediate files, so this emulates that in memory
+ saveFile.pCompressedBuffer->Purge();
+ saveFile.nCompressedSize = 0;
+ saveFile.pCompressedBuffer->Put( saveFile.pBuffer->Base(), saveFile.nSize );
+
+ unsigned int nNumFilesPacked = 0;
+ for ( int i = GetDirectory().FirstInorder(); GetDirectory().IsValidIndex( i ); i = GetDirectory().NextInorder( i ) )
+ {
+ SaveFile_t &file = GetFile( i );
+ const char *pName = GetString( file.name );
+ char szFileName[MAX_PATH];
+ if ( Q_stristr( pName, ".hl" ) )
+ {
+ int fileSize = CompressedSize( pName );
+ if ( fileSize )
+ {
+ Assert( Q_strlen( pName ) <= MAX_PATH );
+
+ memset( szFileName, 0, sizeof( szFileName ) );
+ Q_strncpy( szFileName, pName, sizeof( szFileName ) );
+ saveFile.pCompressedBuffer->Put( szFileName, sizeof( szFileName ) );
+ saveFile.pCompressedBuffer->Put( &fileSize, sizeof(fileSize) );
+ saveFile.pCompressedBuffer->Put( file.pCompressedBuffer->Base(), file.nCompressedSize );
+
+ SaveMsg("SIM: Packed: %s [Size: %.02f KB]\n", GetString( file.name ), (float)file.nCompressedSize / 1024.0f );
+ nNumFilesPacked++;
+ }
+ }
+ }
+
+ // Set the final, complete size of the file
+ saveFile.nCompressedSize = saveFile.pCompressedBuffer->TellMaxPut();
+ SaveMsg("SIM: (%s) Total Files Packed: %d [Size: %.02f KB]\n", GetString( saveFile.name ), nNumFilesPacked, (float) saveFile.nCompressedSize / 1024.0f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Copies the compressed contents of the CSaveDirectory into the save file on disk.
+// Note: This expects standard saverestore behavior, and does NOT
+// currently use pPath to filter the filename search.
+//-----------------------------------------------------------------------------
+void CSaveRestoreFileSystem::DirectoryCopy( const char *pPath, const char *pDestFileName, bool bIsXSave )
+{
+ if ( !Q_stristr( pPath, "*.hl?" ) )
+ {
+ // Function depends on known behavior
+ Assert( 0 );
+ return;
+ }
+
+ // If we don't have a valid storage device, save this to memory instead
+ if ( saverestore->StorageDeviceValid() == false )
+ {
+ DirectorCopyToMemory( pPath, pDestFileName );
+ return;
+ }
+
+ // Write the save file
+ FileHandle_t hSaveFile = Open( pDestFileName, "rb", pPath );
+ if ( !hSaveFile )
+ return;
+
+ SaveFile_t &saveFile = GetFile( hSaveFile );
+
+ // Find out how large this is going to be
+ unsigned int nWriteSize = saveFile.nSize;
+ for ( int i = GetDirectory().FirstInorder(); GetDirectory().IsValidIndex( i ); i = GetDirectory().NextInorder( i ) )
+ {
+ SaveFile_t &file = GetFile( i );
+ const char *pName = GetString( file.name );
+ if ( Q_stristr( pName, ".hl" ) )
+ {
+ // Account for the lump header size
+ nWriteSize += MAX_PATH + sizeof(int) + file.nCompressedSize;
+ }
+ }
+
+ // Fail to write
+#if defined( _X360 )
+ if ( nWriteSize > XBX_SAVEGAME_BYTES )
+ {
+ // FIXME: This error is now lost in the ether!
+ return;
+ }
+#endif
+
+ g_pFileSystem->AsyncWriteFile( pDestFileName, saveFile.pBuffer, saveFile.nSize, true, false );
+
+ // AsyncWriteFile() takes control of the utlbuffer, so don't let RemoveFile() delete it.
+ saveFile.pBuffer = NULL;
+ RemoveFile( pDestFileName );
+
+ // write the list of files to the save file
+ for ( int i = GetDirectory().FirstInorder(); GetDirectory().IsValidIndex( i ); i = GetDirectory().NextInorder( i ) )
+ {
+ SaveFile_t &file = GetFile( i );
+ const char *pName = GetString( file.name );
+ if ( Q_stristr( pName, ".hl" ) )
+ {
+ int fileSize = CompressedSize( pName );
+ if ( fileSize )
+ {
+ Assert( Q_strlen( pName ) <= MAX_PATH );
+
+ g_pFileSystem->AsyncAppend( pDestFileName, memcpy( new char[MAX_PATH], pName, MAX_PATH), MAX_PATH, true );
+ g_pFileSystem->AsyncAppend( pDestFileName, new int(fileSize), sizeof(int), true );
+
+ // This behaves like AsyncAppendFile (due to 5th param) but gets src file from memory instead of off disk.
+ g_pFileSystem->AsyncWriteFile( pDestFileName, file.pCompressedBuffer, file.nCompressedSize, false, true );
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Extracts all the files contained within pFile.
+// Does not Uncompress the extracted data.
+//-----------------------------------------------------------------------------
+bool CSaveRestoreFileSystem::DirectoryExtract( FileHandle_t pFile, int fileCount, bool bIsXSave )
+{
+ int fileSize;
+ FileHandle_t pCopy;
+ char fileName[ MAX_PATH ];
+
+ // Read compressed files from disk into the virtual directory
+ for ( int i = 0; i < fileCount; i++ )
+ {
+ Read( fileName, MAX_PATH, pFile );
+ Read( &fileSize, sizeof(int), pFile );
+ if ( !fileSize )
+ return false;
+
+ pCopy = Open( fileName, "wb" );
+ if ( !pCopy )
+ return false;
+
+ SaveFile_t &destFile = GetFile( pCopy );
+ destFile.pCompressedBuffer->EnsureCapacity( fileSize );
+
+ // Must read in the correct amount of data
+ if ( Read( destFile.pCompressedBuffer->PeekPut(), fileSize, pFile ) != fileSize )
+ return false;
+
+ destFile.pCompressedBuffer->SeekPut( CUtlBuffer::SEEK_HEAD, fileSize );
+ destFile.nCompressedSize = fileSize;
+
+ SaveMsg("SIM: Extracted: %s [Size: %d KB]\n", GetString( destFile.name ), destFile.nCompressedSize / 1024 );
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pFilename -
+//-----------------------------------------------------------------------------
+bool CSaveRestoreFileSystem::LoadFileFromDisk( const char *pFilename )
+{
+ // Open a new file for writing in memory
+ FileHandle_t hMemoryFile = Open( pFilename, "wb" );
+ if ( !hMemoryFile )
+ return false;
+
+ // Open the file off the disk
+ FileHandle_t hDiskFile = g_pFileSystem->OpenEx( pFilename, "rb", ( IsX360() ) ? FSOPEN_NEVERINPACK : 0 );
+ if ( !hDiskFile )
+ return false;
+
+ // Read it in off of the disk to memory
+ SaveFile_t &memoryFile = GetFile( hMemoryFile );
+ if ( g_pFileSystem->ReadToBuffer( hDiskFile, *memoryFile.pCompressedBuffer ) == false )
+ return false;
+
+ // Hold the compressed size
+ memoryFile.nCompressedSize = memoryFile.pCompressedBuffer->TellMaxPut();
+
+ // Close the disk file
+ g_pFileSystem->Close( hDiskFile );
+
+ SaveMsg("SIM: Loaded %s into memory\n", pFilename );
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the number of save files in the specified filter
+// Note: This expects standard saverestore behavior, and does NOT
+// currently use pPath to filter file search.
+//-----------------------------------------------------------------------------
+int CSaveRestoreFileSystem::DirectoryCount( const char *pPath )
+{
+ if ( !Q_stristr( pPath, "*.hl?" ) )
+ {
+ // Function depends on known behavior
+ Assert( 0 );
+ return 0;
+ }
+
+ int count = 0;
+ for ( int i = GetDirectory().FirstInorder(); GetDirectory().IsValidIndex( i ); i = GetDirectory().NextInorder( i ) )
+ {
+ SaveFile_t &file = GetFile( i );
+ if ( Q_stristr( GetString( file.name ), ".hl" ) )
+ {
+ ++count;
+ }
+ }
+ return count;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Clears the save directory of temporary save files (*.hl)
+// Note: This expects standard saverestore behavior, and does NOT
+// currently use pPath to filter file search.
+//-----------------------------------------------------------------------------
+void CSaveRestoreFileSystem::DirectoryClear( const char *pPath, bool bIsXSave )
+{
+ if ( !Q_stristr( pPath, "*.hl?" ) )
+ {
+ // Function depends on known behavior
+ Assert( 0 );
+ return;
+ }
+
+ int i = GetDirectory().FirstInorder();
+ while ( GetDirectory().IsValidIndex( i ) )
+ {
+ SaveFile_t &file = GetFile( i );
+ i = GetDirectory().NextInorder( i );
+ if ( Q_stristr( GetString( file.name ), ".hl" ) )
+ {
+ SaveMsg("SIM: Cleared: %s\n", GetString( file.name ) );
+
+ // Delete the temporary save file
+ RemoveFile( GetString( file.name ) );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Transfer all save files from memory to disk.
+//-----------------------------------------------------------------------------
+void CSaveRestoreFileSystem::WriteSaveDirectoryToDisk( void )
+{
+ char szPath[ MAX_PATH ];
+
+ int i = GetDirectory().FirstInorder();
+ while ( GetDirectory().IsValidIndex( i ) )
+ {
+ SaveFile_t &file = GetFile( i );
+ i = GetDirectory().NextInorder( i );
+ const char *pName = GetString( file.name );
+ if ( Q_stristr( pName, ".hl" ) )
+ {
+ // Write the temporary save file to disk
+ Open( pName, "rb" );
+ Q_snprintf( szPath, sizeof( szPath ), "%s%s", saverestore->GetSaveDir(), pName );
+ g_pFileSystem->WriteFile( szPath, "GAME", *file.pBuffer );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Transfer all save files from disk to memory.
+//-----------------------------------------------------------------------------
+void CSaveRestoreFileSystem::LoadSaveDirectoryFromDisk( const char *pPath )
+{
+ char const *findfn;
+ char szPath[ MAX_PATH ];
+
+ findfn = Sys_FindFirstEx( pPath, "DEFAULT_WRITE_PATH", NULL, 0 );
+
+ while ( findfn != NULL )
+ {
+ Q_snprintf( szPath, sizeof( szPath ), "%s%s", saverestore->GetSaveDir(), findfn );
+
+ // Add the temporary save file
+ FileHandle_t hFile = Open( findfn, "wb" );
+ if ( hFile )
+ {
+ SaveFile_t &file = GetFile( hFile );
+ g_pFileSystem->ReadFile( szPath, "GAME", *file.pBuffer );
+ file.nSize = file.pBuffer->TellMaxPut();
+ Close( hFile );
+ }
+
+ // Any more save files
+ findfn = Sys_FindNext( NULL, 0 );
+ }
+ Sys_FindClose();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CSaveRestoreFileSystem::AuditFiles( void )
+{
+ unsigned int nTotalFiles = 0;
+ unsigned int nTotalCompressed = 0;
+ unsigned int nTotalUncompressed = 0;
+
+ int i = GetDirectory().FirstInorder();
+ while ( GetDirectory().IsValidIndex( i ) )
+ {
+ SaveFile_t &file = GetFile( i );
+ i = GetDirectory().NextInorder( i );
+
+ nTotalFiles++;
+ nTotalCompressed += file.nCompressedSize;
+ nTotalUncompressed += file.nSize;
+ Msg("SIM: File: %s [c: %.02f KB / u: %.02f KB]\n", GetString( file.name ), (float)file.nCompressedSize/1024.0f, (float)file.nSize/1024.0f );
+ }
+
+ Msg("SIM: ------------------------------------------------------------");
+ Msg("SIM: Total files: %d [c: %.02f KB / c: %.02f KB] : Total Size: %.02f KB\n", nTotalFiles, (float)nTotalCompressed/1024.0f, (float)nTotalUncompressed/1024.0f, (float)(nTotalCompressed+nTotalUncompressed)/1024.0f );
+}
+
+CON_COMMAND( audit_save_in_memory, "Audit the memory usage and files in the save-to-memory system" )
+{
+ if ( !IsX360() )
+ return;
+
+ g_pSaveRestoreFileSystem->AuditFiles();
+}
+
+CON_COMMAND( dump_x360_saves, "Dump X360 save games to disk" )
+{
+ if ( !IsX360() )
+ {
+ Warning("dump_x360 only available on X360 platform!\n");
+ return;
+ }
+
+ if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED )
+ {
+ Warning( "No storage device attached!\n" );
+ return;
+ }
+
+ char szInName[MAX_PATH]; // Read path from the container
+ char szOutName[MAX_PATH]; // Output path to the disk
+ char szFileNameBase[MAX_PATH]; // Name of the file minus directories or extensions
+ FileFindHandle_t findHandle;
+
+ char szSearchPath[MAX_PATH];
+ Q_snprintf( szSearchPath, sizeof( szSearchPath ), "%s:\\*.*", GetCurrentMod() );
+
+ const char *pFileName = g_pFileSystem->FindFirst( szSearchPath, &findHandle );
+ while (pFileName)
+ {
+ // Create the proper read path
+ Q_snprintf( szInName, sizeof( szInName ), "%s:\\%s", GetCurrentMod(), pFileName );
+ // Read the file and blat it out
+ CUtlBuffer buf( 0, 0, 0 );
+ if ( g_pFileSystem->ReadFile( szInName, NULL, buf ) )
+ {
+ // Strip us down to just our filename
+ Q_FileBase( pFileName, szFileNameBase, sizeof ( szFileNameBase ) );
+ Q_snprintf( szOutName, sizeof( szOutName ), "save/%s.sav", szFileNameBase );
+ g_pFileSystem->WriteFile( szOutName, NULL, buf );
+
+ Msg("Copied file: %s to %s\n", szInName, szOutName );
+ }
+
+ // Clean up
+ buf.Clear();
+
+ // Any more save files
+ pFileName = g_pFileSystem->FindNext( findHandle );
+ }
+
+ g_pFileSystem->FindClose( findHandle );
+}
+
+CON_COMMAND( dump_x360_cfg, "Dump X360 config files to disk" )
+{
+ if ( !IsX360() )
+ {
+ Warning("dump_x360 only available on X360 platform!\n");
+ return;
+ }
+
+ if ( XBX_GetStorageDeviceId() == XBX_INVALID_STORAGE_ID || XBX_GetStorageDeviceId() == XBX_STORAGE_DECLINED )
+ {
+ Warning( "No storage device attached!\n" );
+ return;
+ }
+
+ char szInName[MAX_PATH]; // Read path from the container
+ char szOutName[MAX_PATH]; // Output path to the disk
+ char szFileNameBase[MAX_PATH]; // Name of the file minus directories or extensions
+ FileFindHandle_t findHandle;
+
+ char szSearchPath[MAX_PATH];
+ Q_snprintf( szSearchPath, sizeof( szSearchPath ), "cfg:\\*.*" );
+
+ const char *pFileName = g_pFileSystem->FindFirst( szSearchPath, &findHandle );
+ while (pFileName)
+ {
+ // Create the proper read path
+ Q_snprintf( szInName, sizeof( szInName ), "cfg:\\%s", pFileName );
+ // Read the file and blat it out
+ CUtlBuffer buf( 0, 0, 0 );
+ if ( g_pFileSystem->ReadFile( szInName, NULL, buf ) )
+ {
+ // Strip us down to just our filename
+ Q_FileBase( pFileName, szFileNameBase, sizeof ( szFileNameBase ) );
+ Q_snprintf( szOutName, sizeof( szOutName ), "%s.cfg", szFileNameBase );
+ g_pFileSystem->WriteFile( szOutName, NULL, buf );
+
+ Msg("Copied file: %s to %s\n", szInName, szOutName );
+ }
+
+ // Clean up
+ buf.Clear();
+
+ // Any more save files
+ pFileName = g_pFileSystem->FindNext( findHandle );
+ }
+
+ g_pFileSystem->FindClose( findHandle );
+}
+
+#define FILECOPYBUFSIZE (1024 * 1024)
+//-----------------------------------------------------------------------------
+// Purpose: Copy one file to another file
+//-----------------------------------------------------------------------------
+static bool FileCopy( FileHandle_t pOutput, FileHandle_t pInput, int fileSize )
+{
+ // allocate a reasonably large file copy buffer, since otherwise write performance under steam suffers
+ char *buf = (char *)malloc(FILECOPYBUFSIZE);
+ int size;
+ int readSize;
+ bool success = true;
+
+ while ( fileSize > 0 )
+ {
+ if ( fileSize > FILECOPYBUFSIZE )
+ size = FILECOPYBUFSIZE;
+ else
+ size = fileSize;
+ if ( ( readSize = g_pSaveRestoreFileSystem->Read( buf, size, pInput ) ) < size )
+ {
+ Warning( "Unexpected end of file expanding save game\n" );
+ fileSize = 0;
+ success = false;
+ break;
+ }
+ g_pSaveRestoreFileSystem->Write( buf, readSize, pOutput );
+
+ fileSize -= size;
+ }
+
+ free(buf);
+ return success;
+}
+
+struct filelistelem_t
+{
+ char szFileName[MAX_PATH];
+};
+
+//-----------------------------------------------------------------------------
+// Purpose: Implementation to execute traditional save to disk behavior
+//-----------------------------------------------------------------------------
+class CSaveRestoreFileSystemPassthrough : public ISaveRestoreFileSystem
+{
+public:
+ CSaveRestoreFileSystemPassthrough() : m_iContainerOpens( 0 ) {}
+
+ bool FileExists( const char *pFileName, const char *pPathID )
+ {
+ return g_pFileSystem->FileExists( pFileName, pPathID );
+ }
+
+ void RemoveFile( char const* pRelativePath, const char *pathID )
+ {
+ g_pFileSystem->RemoveFile( pRelativePath, pathID );
+ }
+
+ void RenameFile( char const *pOldPath, char const *pNewPath, const char *pathID )
+ {
+ g_pFileSystem->RenameFile( pOldPath, pNewPath, pathID );
+ }
+
+ void AsyncFinishAllWrites( void )
+ {
+ g_pFileSystem->AsyncFinishAllWrites();
+ }
+
+ FileHandle_t Open( const char *pFullName, const char *pOptions, const char *pathID )
+ {
+ return g_pFileSystem->OpenEx( pFullName, pOptions, FSOPEN_NEVERINPACK, pathID );
+ }
+
+ void Close( FileHandle_t hSaveFile )
+ {
+ g_pFileSystem->Close( hSaveFile );
+ }
+
+ int Read( void *pOutput, int size, FileHandle_t hFile )
+ {
+ return g_pFileSystem->Read( pOutput, size, hFile );
+ }
+
+ int Write( void const* pInput, int size, FileHandle_t hFile )
+ {
+ return g_pFileSystem->Write( pInput, size, hFile );
+ }
+
+ FSAsyncStatus_t AsyncWrite( const char *pFileName, const void *pSrc, int nSrcBytes, bool bFreeMemory, bool bAppend, FSAsyncControl_t *pControl )
+ {
+ SaveMsg( "AsyncWrite (%s/%d)...\n", pFileName, nSrcBytes );
+ return g_pFileSystem->AsyncWrite( pFileName, pSrc, nSrcBytes, bFreeMemory, bAppend, pControl );
+ }
+
+ void Seek( FileHandle_t hFile, int pos, FileSystemSeek_t method )
+ {
+ g_pFileSystem->Seek( hFile, pos, method );
+ }
+
+ unsigned int Tell( FileHandle_t hFile )
+ {
+ return g_pFileSystem->Tell( hFile );
+ }
+
+ unsigned int Size( FileHandle_t hFile )
+ {
+ return g_pFileSystem->Size( hFile );
+ }
+
+ unsigned int Size( const char *pFileName, const char *pPathID )
+ {
+ return g_pFileSystem->Size( pFileName, pPathID );
+ }
+
+ FSAsyncStatus_t AsyncFinish( FSAsyncControl_t hControl, bool wait )
+ {
+ return g_pFileSystem->AsyncFinish( hControl, wait );
+ }
+
+ void AsyncRelease( FSAsyncControl_t hControl )
+ {
+ g_pFileSystem->AsyncRelease( hControl );
+ }
+
+ FSAsyncStatus_t AsyncAppend(const char *pFileName, const void *pSrc, int nSrcBytes, bool bFreeMemory, FSAsyncControl_t *pControl )
+ {
+ return g_pFileSystem->AsyncAppend( pFileName, pSrc, nSrcBytes, bFreeMemory, pControl );
+ }
+
+ FSAsyncStatus_t AsyncAppendFile(const char *pDestFileName, const char *pSrcFileName, FSAsyncControl_t *pControl )
+ {
+ return g_pFileSystem->AsyncAppendFile( pDestFileName, pSrcFileName, pControl );
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose: Copies the contents of the save directory into a single file
+ //-----------------------------------------------------------------------------
+ void DirectoryCopy( const char *pPath, const char *pDestFileName, bool bIsXSave )
+ {
+ SaveMsg( "DirectoryCopy....\n");
+
+ CUtlVector<filelistelem_t> list;
+
+ // force the writes to finish before trying to get the size/existence of a file
+ // @TODO: don't need this if retain sizes for files written earlier in process
+ SaveMsg( "DirectoryCopy: AsyncFinishAllWrites\n");
+ g_pFileSystem->AsyncFinishAllWrites();
+
+ // build the directory list
+ char basefindfn[ MAX_PATH ];
+ const char *findfn = Sys_FindFirstEx(pPath, "DEFAULT_WRITE_PATH", basefindfn, sizeof( basefindfn ) );
+ while ( findfn )
+ {
+ int index = list.AddToTail();
+ memset( list[index].szFileName, 0, sizeof(list[index].szFileName) );
+ Q_strncpy( list[index].szFileName, findfn, sizeof(list[index].szFileName) );
+
+ findfn = Sys_FindNext( basefindfn, sizeof( basefindfn ) );
+ }
+ Sys_FindClose();
+
+ // write the list of files to the save file
+ char szName[MAX_PATH];
+ for ( int i = 0; i < list.Count(); i++ )
+ {
+ if ( !bIsXSave )
+ {
+ Q_snprintf( szName, sizeof( szName ), "%s%s", saverestore->GetSaveDir(), list[i].szFileName );
+ }
+ else
+ {
+ Q_snprintf( szName, sizeof( szName ), "%s:\\%s", GetCurrentMod(), list[i].szFileName );
+ }
+
+ Q_FixSlashes( szName );
+
+ int fileSize = g_pFileSystem->Size( szName );
+ if ( fileSize )
+ {
+ Assert( sizeof(list[i].szFileName) == MAX_PATH );
+
+ SaveMsg( "DirectoryCopy: AsyncAppend %s, %s\n", szName, pDestFileName );
+ g_pFileSystem->AsyncAppend( pDestFileName, memcpy( new char[MAX_PATH], list[i].szFileName, MAX_PATH), MAX_PATH, true ); // Filename can only be as long as a map name + extension
+ g_pFileSystem->AsyncAppend( pDestFileName, new int(fileSize), sizeof(int), true );
+ g_pFileSystem->AsyncAppendFile( pDestFileName, szName );
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose: Extracts all the files contained within pFile
+ //-----------------------------------------------------------------------------
+ bool DirectoryExtract( FileHandle_t pFile, int fileCount, bool bIsXSave )
+ {
+ int fileSize;
+ FileHandle_t pCopy;
+ char szName[ MAX_PATH ], fileName[ MAX_PATH ];
+ bool success = true;
+
+ for ( int i = 0; i < fileCount && success; i++ )
+ {
+ // Filename can only be as long as a map name + extension
+ if ( g_pSaveRestoreFileSystem->Read( fileName, MAX_PATH, pFile ) != MAX_PATH )
+ return false;
+
+ if ( g_pSaveRestoreFileSystem->Read( &fileSize, sizeof(int), pFile ) != sizeof(int) )
+ return false;
+
+ if ( !fileSize )
+ return false;
+
+ if ( !bIsXSave )
+ {
+ Q_snprintf( szName, sizeof( szName ), "%s%s", saverestore->GetSaveDir(), fileName );
+ }
+ else
+ {
+ Q_snprintf( szName, sizeof( szName ), "%s:\\%s", GetCurrentMod(), fileName );
+ }
+
+ Q_FixSlashes( szName );
+ pCopy = g_pSaveRestoreFileSystem->Open( szName, "wb", "MOD" );
+ if ( !pCopy )
+ return false;
+ success = FileCopy( pCopy, pFile, fileSize );
+ g_pSaveRestoreFileSystem->Close( pCopy );
+ }
+
+ return success;
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose: returns the number of files in the specified filter
+ //-----------------------------------------------------------------------------
+ int DirectoryCount( const char *pPath )
+ {
+ int count = 0;
+ const char *findfn = Sys_FindFirstEx( pPath, "DEFAULT_WRITE_PATH", NULL, 0 );
+
+ while ( findfn != NULL )
+ {
+ count++;
+ findfn = Sys_FindNext(NULL, 0 );
+ }
+ Sys_FindClose();
+
+ return count;
+ }
+
+ //-----------------------------------------------------------------------------
+ // Purpose: Clears the save directory of all temporary files (*.hl)
+ //-----------------------------------------------------------------------------
+ void DirectoryClear( const char *pPath, bool bIsXSave )
+ {
+ char const *findfn;
+ char szPath[ MAX_PATH ];
+
+ findfn = Sys_FindFirstEx( pPath, "DEFAULT_WRITE_PATH", NULL, 0 );
+ while ( findfn != NULL )
+ {
+ if ( !bIsXSave )
+ {
+ Q_snprintf( szPath, sizeof( szPath ), "%s%s", saverestore->GetSaveDir(), findfn );
+ }
+ else
+ {
+ Q_snprintf( szPath, sizeof( szPath ), "%s:\\%s", GetCurrentMod(), findfn );
+ }
+
+ // Delete the temporary save file
+ g_pFileSystem->RemoveFile( szPath, "MOD" );
+
+ // Any more save files
+ findfn = Sys_FindNext( NULL, 0 );
+ }
+ Sys_FindClose();
+ }
+
+ void AuditFiles( void )
+ {
+ Msg("Not using save-in-memory path!\n" );
+ }
+
+ bool LoadFileFromDisk( const char *pFilename )
+ {
+ Msg("Not using save-in-memory path!\n" );
+ return true;
+ }
+
+private:
+ int m_iContainerOpens;
+};
+
+static CSaveRestoreFileSystem s_SaveRestoreFileSystem;
+static CSaveRestoreFileSystemPassthrough s_SaveRestoreFileSystemPassthrough;
+
+#ifdef _X360
+ISaveRestoreFileSystem *g_pSaveRestoreFileSystem = &s_SaveRestoreFileSystem;
+#else
+ISaveRestoreFileSystem *g_pSaveRestoreFileSystem = &s_SaveRestoreFileSystemPassthrough;
+#endif // _X360
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when switching between saving in memory and saving to disk.
+//-----------------------------------------------------------------------------
+void SaveInMemoryCallback( IConVar *pConVar, const char *pOldString, float flOldValue )
+{
+ if ( !IsX360() )
+ {
+ Warning( "save_in_memory is compatible with only the Xbox 360!\n" );
+ return;
+ }
+
+ ConVarRef var( pConVar );
+ if ( var.GetFloat() == flOldValue )
+ return;
+
+ // *.hl? files are transferred between disk and memory when this cvar changes
+ char szPath[ MAX_PATH ];
+ Q_snprintf( szPath, sizeof( szPath ), "%s%s", saverestore->GetSaveDir(), "*.hl?" );
+ if ( var.GetBool() )
+ {
+ g_pSaveRestoreFileSystem = &s_SaveRestoreFileSystem;
+
+ // Clear memory and load
+ s_SaveRestoreFileSystem.DirectoryClear( "*.hl?", IsX360() );
+ s_SaveRestoreFileSystem.LoadSaveDirectoryFromDisk( szPath );
+
+ // Clear disk
+ s_SaveRestoreFileSystemPassthrough.DirectoryClear( szPath, IsX360() );
+ }
+ else
+ {
+ g_pSaveRestoreFileSystem = &s_SaveRestoreFileSystemPassthrough;
+
+ // Clear disk and write
+ s_SaveRestoreFileSystemPassthrough.DirectoryClear( szPath, IsX360() );
+ s_SaveRestoreFileSystem.WriteSaveDirectoryToDisk();
+
+ // Clear memory
+ s_SaveRestoreFileSystem.DirectoryClear( "*.hl?", IsX360() );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Dump a list of the save directory contents (in memory) to the console
+//-----------------------------------------------------------------------------
+void CSaveRestoreFileSystem::DumpSaveDirectory( void )
+{
+ unsigned int totalCompressedSize = 0;
+ unsigned int totalUncompressedSize = 0;
+ for ( int i = GetDirectory().FirstInorder(); GetDirectory().IsValidIndex( i ); i = GetDirectory().NextInorder( i ) )
+ {
+ SaveFile_t &file = GetFile( i );
+ Msg( "File %d: %s Size:%d\n", i, GetString( file.name ), file.nCompressedSize );
+ totalUncompressedSize += file.nSize;
+ totalCompressedSize += file.nCompressedSize;
+ }
+ float percent = 0.f;
+ if ( totalUncompressedSize )
+ percent = 100.f - (totalCompressedSize / totalUncompressedSize * 100.f);
+ Msg( "Total Size: %.2f Mb (%d bytes)\n", totalCompressedSize / (1024.f*1024.f), totalCompressedSize );
+ Msg( "Compression: %.2f Mb to %.2f Mb (%.0f%%)\n", totalUncompressedSize/(1024.f*1024.f), totalCompressedSize/(1024.f*1024.f), percent );
+}
+
+CON_COMMAND( dumpsavedir, "List the contents of the save directory in memory" )
+{
+ s_SaveRestoreFileSystem.DumpSaveDirectory();
+}
+