//========= 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( (uintp)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 )
{
	intp idx = GetFileIndex( filename );
	if ( idx == INVALID_INDEX )
	{
		idx = 0;
	}
	return (FileHandle_t)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( (uintp)hFile );
}

//-----------------------------------------------------------------------------
// Purpose: Renames a block of memory
//-----------------------------------------------------------------------------
void CSaveRestoreFileSystem::RenameFile( char const *pOldPath, char const *pNewPath, const char *pathID )
{
	intp 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 ) );
	intp 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( (uintp)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, memcpy( new char[sizeof(int)], &fileSize, sizeof(int)), 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();
}