Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1153 lines
37 KiB

  1. //===== Copyright � 1996-2006, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose: Filesystem abstraction for CSaveRestore - allows for storing temp save files
  4. // either in memory or on disk.
  5. //
  6. //===========================================================================//
  7. #ifdef _WIN32
  8. #include "winerror.h"
  9. #endif
  10. #include "filesystem_engine.h"
  11. #include "saverestore_filesystem.h"
  12. #include "host_saverestore.h"
  13. #include "host.h"
  14. #include "sys.h"
  15. #include "tier1/utlbuffer.h"
  16. #include "tier1/lzss.h"
  17. #include "tier1/convar.h"
  18. #include "ixboxsystem.h"
  19. // memdbgon must be the last include file in a .cpp file!!!
  20. #include "tier0/memdbgon.h"
  21. extern IXboxSystem *g_pXboxSystem;
  22. #define MOD_DIR "DEFAULT_WRITE_PATH"
  23. void SaveInMemoryCallback( IConVar *var, const char *pOldString, float flOldValue );
  24. ConVar save_in_memory( "save_in_memory", IsX360() ? "1" : "0", 0, "Set to 1 to save to memory instead of disk (Xbox 360)", SaveInMemoryCallback );
  25. #define INVALID_INDEX (GetDirectory().InvalidIndex())
  26. enum { READ_ONLY, WRITE_ONLY };
  27. static float g_fPrevSaveInMemoryValue;
  28. static bool SaveFileLessFunc( const CUtlSymbol &lhs, const CUtlSymbol &rhs )
  29. {
  30. return lhs < rhs;
  31. }
  32. //----------------------------------------------------------------------------
  33. // Simulates the save directory in RAM.
  34. //----------------------------------------------------------------------------
  35. class CSaveDirectory
  36. {
  37. public:
  38. CSaveDirectory()
  39. {
  40. m_Files.SetLessFunc( SaveFileLessFunc );
  41. file_t dummy;
  42. dummy.name = m_SymbolTable.AddString( "dummy" );
  43. m_Files.Insert( dummy.name, dummy );
  44. }
  45. ~CSaveDirectory()
  46. {
  47. int i = m_Files.FirstInorder();
  48. while ( m_Files.IsValidIndex( i ) )
  49. {
  50. int idx = i;
  51. i = m_Files.NextInorder( i );
  52. delete m_Files[idx].pBuffer;
  53. delete m_Files[idx].pCompressedBuffer;
  54. m_Files.RemoveAt( idx );
  55. }
  56. }
  57. struct file_t
  58. {
  59. file_t()
  60. {
  61. pBuffer = NULL;
  62. pCompressedBuffer = NULL;
  63. nSize = 0;
  64. nCompressedSize = NULL;
  65. }
  66. int eType;
  67. CUtlSymbol name;
  68. unsigned int nSize;
  69. unsigned int nCompressedSize;
  70. CUtlBuffer *pBuffer;
  71. CUtlBuffer *pCompressedBuffer;
  72. };
  73. CUtlSymbolTable m_SymbolTable;
  74. CUtlMap<CUtlSymbol, file_t> m_Files;
  75. };
  76. typedef CSaveDirectory::file_t SaveFile_t;
  77. //----------------------------------------------------------------------------
  78. // CSaveRestoreFileSystem: Manipulates files in the CSaveDirectory
  79. //----------------------------------------------------------------------------
  80. class CSaveRestoreFileSystem : public ISaveRestoreFileSystem
  81. {
  82. public:
  83. CSaveRestoreFileSystem()
  84. {
  85. m_pSaveDirectory = new CSaveDirectory();
  86. m_iContainerOpens = 0;
  87. }
  88. ~CSaveRestoreFileSystem()
  89. {
  90. delete m_pSaveDirectory;
  91. }
  92. bool FileExists( const char *pFileName, const char *pPathID = NULL );
  93. void RenameFile( char const *pOldPath, char const *pNewPath, const char *pathID = NULL );
  94. void RemoveFile( char const* pRelativePath, const char *pathID = NULL );
  95. FileHandle_t Open( const char *pFileName, const char *pOptions, const char *pathID = NULL );
  96. void Close( FileHandle_t );
  97. int Read( void *pOutput, int size, FileHandle_t file );
  98. int Write( void const* pInput, int size, FileHandle_t file );
  99. void Seek( FileHandle_t file, int pos, FileSystemSeek_t method );
  100. unsigned int Tell( FileHandle_t file );
  101. unsigned int Size( FileHandle_t file );
  102. unsigned int Size( const char *pFileName, const char *pPathID = NULL );
  103. void AsyncFinishAllWrites( void );
  104. void AsyncRelease( FSAsyncControl_t hControl );
  105. FSAsyncStatus_t AsyncWrite( const char *pFileName, const void *pSrc, int nSrcBytes, bool bFreeMemory, bool bAppend, FSAsyncControl_t *pControl = NULL );
  106. FSAsyncStatus_t AsyncFinish( FSAsyncControl_t hControl, bool wait = false );
  107. FSAsyncStatus_t AsyncAppend( const char *pFileName, const void *pSrc, int nSrcBytes, bool bFreeMemory, FSAsyncControl_t *pControl = NULL );
  108. FSAsyncStatus_t AsyncAppendFile( const char *pDestFileName, const char *pSrcFileName, FSAsyncControl_t *pControl = NULL );
  109. void DirectoryCopy( const char *pPath, const char *pDestFileName, bool bIsXSave );
  110. void DirectorCopyToMemory( const char *pPath, const char *pDestFileName );
  111. bool DirectoryExtract( FileHandle_t pFile, int fileCount, bool bIsXSave );
  112. int DirectoryCount( const char *pPath );
  113. void DirectoryClear( const char *pPath, bool bIsXSave );
  114. void WriteSaveDirectoryToDisk( void );
  115. void LoadSaveDirectoryFromDisk( const char *pPath );
  116. void DumpSaveDirectory( void );
  117. void Compress( SaveFile_t *pFile );
  118. void Uncompress( SaveFile_t *pFile );
  119. void AuditFiles( void );
  120. bool LoadFileFromDisk( const char *pFilename );
  121. private:
  122. CSaveDirectory *m_pSaveDirectory;
  123. CUtlMap<CUtlSymbol, SaveFile_t> &GetDirectory( void ) { return m_pSaveDirectory->m_Files; }
  124. SaveFile_t &GetFile( const int idx ) { return m_pSaveDirectory->m_Files[idx]; }
  125. SaveFile_t &GetFile( const FileHandle_t hFile ) { return GetFile( size_cast< unsigned int >( (uintp) hFile ) ); }
  126. FileHandle_t GetFileHandle( const char *pFileName );
  127. int GetFileIndex( const char *pFileName );
  128. bool HandleIsValid( FileHandle_t hFile );
  129. unsigned int CompressedSize( const char *pFileName );
  130. CUtlSymbol AddString( const char *str )
  131. {
  132. char szString[ MAX_PATH ];
  133. Q_strncpy( szString, str, sizeof( szString ) );
  134. return m_pSaveDirectory->m_SymbolTable.AddString( Q_strlower( szString ) );
  135. }
  136. const char *GetString( CUtlSymbol &id ) { return m_pSaveDirectory->m_SymbolTable.String( id ); }
  137. int m_iContainerOpens;
  138. };
  139. //#define TEST_LZSS_WINDOW_SIZES
  140. //----------------------------------------------------------------------------
  141. // Compress the file data
  142. //----------------------------------------------------------------------------
  143. void CSaveRestoreFileSystem::Compress( SaveFile_t *pFile )
  144. {
  145. pFile->pCompressedBuffer->Purge();
  146. #ifdef TEST_LZSS_WINDOW_SIZES
  147. // Compress the data here
  148. CLZSS compressor_test;
  149. CLZSS newcompressor_test( 2048 );
  150. pFile->nCompressedSize = 0;
  151. float start = Plat_FloatTime();
  152. for(int i=0;i<10;i++)
  153. {
  154. uint32 sz;
  155. unsigned char *pCompressedBuffer = compressor_test.Compress(
  156. (unsigned char *) pFile->pBuffer->Base(), pFile->nSize, &sz );
  157. delete[] pCompressedBuffer;
  158. }
  159. Warning(" old compressor_test %f", Plat_FloatTime() - start );
  160. start = Plat_FloatTime();
  161. for(int i=0;i<10;i++)
  162. {
  163. uint32 sz;
  164. unsigned char *pCompressedBuffer = newcompressor_test.Compress(
  165. (unsigned char *) pFile->pBuffer->Base(), pFile->nSize, &sz );
  166. delete[] pCompressedBuffer;
  167. }
  168. Warning(" new compressor_test %f", Plat_FloatTime() - start );
  169. if ( 1)
  170. {
  171. uint32 sz;
  172. uint32 sz1;
  173. unsigned char *pNewCompressedBuffer = newcompressor_test.Compress(
  174. (unsigned char *) pFile->pBuffer->Base(), pFile->nSize, &sz );
  175. unsigned char *pOldCompressedBuffer = compressor_test.Compress(
  176. (unsigned char *) pFile->pBuffer->Base(), pFile->nSize, &sz1 );
  177. if ( ! pNewCompressedBuffer )
  178. Warning("new no comp");
  179. if ( ! pOldCompressedBuffer )
  180. Warning("old no comp");
  181. if ( pNewCompressedBuffer && pOldCompressedBuffer )
  182. {
  183. if ( sz != sz1 )
  184. Warning(" new size = %d old = %d", sz, sz1 );
  185. if ( memcmp( pNewCompressedBuffer, pOldCompressedBuffer, sz ) )
  186. Warning("data mismatch");
  187. }
  188. delete[] pOldCompressedBuffer;
  189. delete[] pNewCompressedBuffer;
  190. }
  191. #endif
  192. CLZSS compressor( 2048 );
  193. unsigned char *pCompressedBuffer = compressor.Compress( (unsigned char *) pFile->pBuffer->Base(), pFile->nSize, &pFile->nCompressedSize );
  194. if ( pCompressedBuffer == NULL )
  195. {
  196. // Just copy the buffer uncompressed
  197. pFile->pCompressedBuffer->Put( pFile->pBuffer->Base(), pFile->nSize );
  198. pFile->nCompressedSize = pFile->nSize;
  199. }
  200. else
  201. {
  202. // Take the compressed buffer as our own
  203. pFile->pCompressedBuffer->AssumeMemory( pCompressedBuffer, pFile->nCompressedSize, pFile->nCompressedSize ); // ?
  204. }
  205. // end compression
  206. pFile->pCompressedBuffer->SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  207. pFile->pCompressedBuffer->SeekPut( CUtlBuffer::SEEK_HEAD, pFile->nCompressedSize );
  208. // Don't want the uncompressed memory hanging around
  209. pFile->pBuffer->Purge();
  210. unsigned int srcBytes = pFile->nSize;
  211. pFile->nSize = 0;
  212. unsigned int destBytes = pFile->nCompressedSize;
  213. float percent = 0.f;
  214. if ( srcBytes )
  215. percent = 100.0f * (1.0f - (float)destBytes/(float)srcBytes);
  216. SaveMsg( "SIM: SaveDir: (%s) Compressed %d bytes to %d bytes. (%.0f%%)\n", GetString( pFile->name ), srcBytes, destBytes, percent );
  217. }
  218. //----------------------------------------------------------------------------
  219. // Uncompress the file data
  220. //----------------------------------------------------------------------------
  221. void CSaveRestoreFileSystem::Uncompress( SaveFile_t *pFile )
  222. {
  223. pFile->pBuffer->Purge();
  224. // Uncompress the data here
  225. CLZSS compressor;
  226. unsigned int nUncompressedSize = compressor.GetActualSize( (unsigned char *) pFile->pCompressedBuffer->Base() );
  227. if ( nUncompressedSize != 0 )
  228. {
  229. unsigned char *pUncompressBuffer = (unsigned char *) malloc( nUncompressedSize );
  230. nUncompressedSize = compressor.Uncompress( (unsigned char *) pFile->pCompressedBuffer->Base(), pUncompressBuffer );
  231. pFile->pBuffer->AssumeMemory( pUncompressBuffer, nUncompressedSize, nUncompressedSize ); // ?
  232. }
  233. else
  234. {
  235. // Put it directly into our target
  236. pFile->pBuffer->Put( (unsigned char *) pFile->pCompressedBuffer->Base(), pFile->nCompressedSize );
  237. }
  238. // end decompression
  239. pFile->nSize = pFile->pBuffer->TellMaxPut();
  240. pFile->pBuffer->SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  241. pFile->pBuffer->SeekPut( CUtlBuffer::SEEK_HEAD, pFile->nSize );
  242. unsigned int srcBytes = pFile->nCompressedSize;
  243. unsigned int destBytes = pFile->nSize;
  244. SaveMsg( "SIM: SaveDir: (%s) Uncompressed %d bytes to %d bytes.\n", GetString( pFile->name ), srcBytes, destBytes );
  245. }
  246. //----------------------------------------------------------------------------
  247. // Access the save files
  248. //----------------------------------------------------------------------------
  249. int CSaveRestoreFileSystem::GetFileIndex( const char *filename )
  250. {
  251. CUtlSymbol id = AddString( Q_UnqualifiedFileName( filename ) );
  252. return GetDirectory().Find( id );
  253. }
  254. FileHandle_t CSaveRestoreFileSystem::GetFileHandle( const char *filename )
  255. {
  256. int idx = GetFileIndex( filename );
  257. if ( idx == INVALID_INDEX )
  258. {
  259. idx = 0;
  260. }
  261. return (void*)(intp)idx;
  262. }
  263. //-----------------------------------------------------------------------------
  264. // Purpose: Returns whether the named memory block exists
  265. //-----------------------------------------------------------------------------
  266. bool CSaveRestoreFileSystem::FileExists( const char *pFileName, const char *pPathID )
  267. {
  268. return ( GetFileHandle( pFileName ) != NULL );
  269. }
  270. //-----------------------------------------------------------------------------
  271. // Purpose: Validates a file handle
  272. //-----------------------------------------------------------------------------
  273. bool CSaveRestoreFileSystem::HandleIsValid( FileHandle_t hFile )
  274. {
  275. return hFile && GetDirectory().IsValidIndex( size_cast< unsigned int >( (uintp) hFile ) );
  276. }
  277. //-----------------------------------------------------------------------------
  278. // Purpose: Renames a block of memory
  279. //-----------------------------------------------------------------------------
  280. void CSaveRestoreFileSystem::RenameFile( char const *pOldPath, char const *pNewPath, const char *pathID )
  281. {
  282. int idx = GetFileIndex( pOldPath );
  283. if ( idx != INVALID_INDEX )
  284. {
  285. CUtlSymbol newID = AddString( Q_UnqualifiedFileName( pNewPath ) );
  286. GetFile( idx ).name = newID;
  287. GetDirectory().Reinsert( newID, idx );
  288. }
  289. }
  290. //-----------------------------------------------------------------------------
  291. // Purpose: Removes a memory block from CSaveDirectory and frees it.
  292. //-----------------------------------------------------------------------------
  293. void CSaveRestoreFileSystem::RemoveFile( char const* pRelativePath, const char *pathID )
  294. {
  295. int idx = GetFileIndex( pRelativePath );
  296. if ( idx != INVALID_INDEX )
  297. {
  298. delete GetFile( idx ).pBuffer;
  299. delete GetFile( idx ).pCompressedBuffer;
  300. GetDirectory().RemoveAt( idx );
  301. }
  302. }
  303. //-----------------------------------------------------------------------------
  304. // Purpose: Access an existing memory block if it exists, else allocate one.
  305. //-----------------------------------------------------------------------------
  306. FileHandle_t CSaveRestoreFileSystem::Open( const char *pFullName, const char *pOptions, const char *pathID )
  307. {
  308. SaveFile_t *pFile = NULL;
  309. CUtlSymbol id = AddString( Q_UnqualifiedFileName( pFullName ) );
  310. int idx = GetDirectory().Find( id );
  311. if ( idx == INVALID_INDEX )
  312. {
  313. // Don't create a read-only file
  314. if ( Q_stricmp( pOptions, "rb" ) )
  315. {
  316. // Create new file
  317. SaveFile_t newFile;
  318. newFile.name = id;
  319. newFile.pBuffer = new CUtlBuffer();
  320. newFile.pCompressedBuffer = new CUtlBuffer();
  321. idx = GetDirectory().Insert( id, newFile );
  322. }
  323. else
  324. {
  325. return (void*)0;
  326. }
  327. }
  328. pFile = &GetFile( idx );
  329. if ( !Q_stricmp( pOptions, "rb" ) )
  330. {
  331. Uncompress( pFile );
  332. pFile->eType = READ_ONLY;
  333. }
  334. else if ( !Q_stricmp( pOptions, "wb" ) )
  335. {
  336. pFile->pBuffer->Clear();
  337. pFile->eType = WRITE_ONLY;
  338. }
  339. else if ( !Q_stricmp( pOptions, "a" ) )
  340. {
  341. Uncompress( pFile );
  342. pFile->eType = WRITE_ONLY;
  343. }
  344. else if ( !Q_stricmp( pOptions, "ab+" ) )
  345. {
  346. Uncompress( pFile );
  347. pFile->eType = WRITE_ONLY;
  348. pFile->pBuffer->SeekPut( CUtlBuffer::SEEK_TAIL, 0 );
  349. }
  350. else
  351. {
  352. Assert( 0 );
  353. Warning( "CSaveRestoreFileSystem: Attempted to open %s with unsupported option %s\n", pFullName, pOptions );
  354. return (void*)0;
  355. }
  356. return (void*)(intp)idx;
  357. }
  358. //-----------------------------------------------------------------------------
  359. // Purpose: No need to close files in memory. Could perform post processing here.
  360. //-----------------------------------------------------------------------------
  361. void CSaveRestoreFileSystem::Close( FileHandle_t hFile )
  362. {
  363. // Compress the file
  364. if ( HandleIsValid( hFile ) )
  365. {
  366. SaveFile_t &file = GetFile( hFile );
  367. // Files opened for read don't need to be recompressed
  368. if ( file.eType == READ_ONLY )
  369. {
  370. SaveMsg("SIM: Closed file: %s\n", GetString( file.name ) );
  371. file.pBuffer->Purge();
  372. file.nSize = 0;
  373. }
  374. else
  375. {
  376. Compress( &file );
  377. }
  378. }
  379. }
  380. //-----------------------------------------------------------------------------
  381. // Purpose: Reads data from memory.
  382. //-----------------------------------------------------------------------------
  383. int CSaveRestoreFileSystem::Read( void *pOutput, int size, FileHandle_t hFile )
  384. {
  385. int readSize = 0;
  386. if ( HandleIsValid( hFile ) )
  387. {
  388. SaveFile_t &file = GetFile( hFile );
  389. if( file.eType == READ_ONLY )
  390. {
  391. readSize = file.pBuffer->GetUpTo( pOutput, size );
  392. }
  393. else
  394. {
  395. Warning( "Read: Attempted to read from a write-only file" );
  396. readSize = 0;
  397. Assert( 0 );
  398. }
  399. }
  400. return readSize;
  401. }
  402. //-----------------------------------------------------------------------------
  403. // Purpose: Writes data to memory.
  404. //-----------------------------------------------------------------------------
  405. int CSaveRestoreFileSystem::Write( void const* pInput, int size, FileHandle_t hFile )
  406. {
  407. int writeSize = 0;
  408. if ( HandleIsValid( hFile ) )
  409. {
  410. SaveFile_t &file = GetFile( hFile );
  411. if( file.eType == WRITE_ONLY )
  412. {
  413. file.pBuffer->Put( pInput, size );
  414. file.nSize = file.pBuffer->TellMaxPut();
  415. writeSize = size;
  416. }
  417. else
  418. {
  419. Warning( "Write: Attempted to write to a read-only file" );
  420. writeSize = 0;
  421. Assert( 0 );
  422. }
  423. }
  424. return writeSize;
  425. }
  426. //-----------------------------------------------------------------------------
  427. // Purpose: Seek in memory. Seeks the UtlBuffer put or get pos depending
  428. // on whether the file was opened for read or write.
  429. //-----------------------------------------------------------------------------
  430. void CSaveRestoreFileSystem::Seek( FileHandle_t hFile, int pos, FileSystemSeek_t method )
  431. {
  432. if ( HandleIsValid( hFile ) )
  433. {
  434. SaveFile_t &file = GetFile( hFile );
  435. if ( file.eType == READ_ONLY )
  436. {
  437. file.pBuffer->SeekGet( (CUtlBuffer::SeekType_t)method, pos );
  438. }
  439. else if ( file.eType == WRITE_ONLY )
  440. {
  441. file.pBuffer->SeekPut( (CUtlBuffer::SeekType_t)method, pos );
  442. }
  443. else
  444. {
  445. Assert( 0 );
  446. }
  447. }
  448. }
  449. //-----------------------------------------------------------------------------
  450. // Purpose: Return position in memory. Returns UtlBuffer put or get pos depending
  451. // on whether the file was opened for read or write.
  452. //-----------------------------------------------------------------------------
  453. unsigned int CSaveRestoreFileSystem::Tell( FileHandle_t hFile )
  454. {
  455. unsigned int pos = 0;
  456. if ( HandleIsValid( hFile ) )
  457. {
  458. SaveFile_t &file = GetFile( hFile );
  459. if ( file.eType == READ_ONLY )
  460. {
  461. pos = file.pBuffer->TellGet();
  462. }
  463. else if ( file.eType == WRITE_ONLY )
  464. {
  465. pos = file.pBuffer->TellPut();
  466. }
  467. else
  468. {
  469. Assert( 0 );
  470. }
  471. }
  472. return pos;
  473. }
  474. //-----------------------------------------------------------------------------
  475. // Purpose: Return uncompressed memory size
  476. //-----------------------------------------------------------------------------
  477. unsigned int CSaveRestoreFileSystem::Size( FileHandle_t hFile )
  478. {
  479. if ( HandleIsValid( hFile ) )
  480. {
  481. return GetFile( hFile ).nSize;
  482. }
  483. return 0;
  484. }
  485. //-----------------------------------------------------------------------------
  486. // Purpose: Return uncompressed size of data in memory
  487. //-----------------------------------------------------------------------------
  488. unsigned int CSaveRestoreFileSystem::Size( const char *pFileName, const char *pPathID )
  489. {
  490. return Size( GetFileHandle( pFileName ) );
  491. }
  492. //-----------------------------------------------------------------------------
  493. // Purpose: Return compressed size of data in memory
  494. //-----------------------------------------------------------------------------
  495. unsigned int CSaveRestoreFileSystem::CompressedSize( const char *pFileName )
  496. {
  497. FileHandle_t hFile = GetFileHandle( pFileName );
  498. if ( hFile )
  499. {
  500. return GetFile( hFile ).nCompressedSize;
  501. }
  502. return 0;
  503. }
  504. //-----------------------------------------------------------------------------
  505. // Purpose: Writes data to memory. Function is NOT async.
  506. //-----------------------------------------------------------------------------
  507. FSAsyncStatus_t CSaveRestoreFileSystem::AsyncWrite( const char *pFileName, const void *pSrc, int nSrcBytes, bool bFreeMemory, bool bAppend, FSAsyncControl_t *pControl )
  508. {
  509. FSAsyncStatus_t retval = FSASYNC_ERR_FAILURE;
  510. FileHandle_t hFile = Open( pFileName, "wb" );
  511. if ( hFile )
  512. {
  513. SaveFile_t &file = GetFile( size_cast<unsigned int>( (uintp)hFile ) );
  514. if( file.eType == WRITE_ONLY )
  515. {
  516. file.pBuffer->Put( pSrc, nSrcBytes );
  517. file.nSize = file.pBuffer->TellMaxPut();
  518. Compress( &file );
  519. retval = FSASYNC_OK;
  520. }
  521. else
  522. {
  523. Warning( "AsyncWrite: Attempted to write to a read-only file" );
  524. Assert( 0 );
  525. }
  526. }
  527. if ( bFreeMemory )
  528. free( const_cast< void * >( pSrc ) );
  529. return retval;
  530. }
  531. //-----------------------------------------------------------------------------
  532. // Purpose: Appends data to a memory block. Function is NOT async.
  533. //-----------------------------------------------------------------------------
  534. FSAsyncStatus_t CSaveRestoreFileSystem::AsyncAppend(const char *pFileName, const void *pSrc, int nSrcBytes, bool bFreeMemory, FSAsyncControl_t *pControl )
  535. {
  536. FSAsyncStatus_t retval = FSASYNC_ERR_FAILURE;
  537. FileHandle_t hFile = Open( pFileName, "a" );
  538. if ( hFile )
  539. {
  540. SaveFile_t &file = GetFile( hFile );
  541. if( file.eType == WRITE_ONLY )
  542. {
  543. file.pBuffer->Put( pSrc, nSrcBytes );
  544. file.nSize = file.pBuffer->TellMaxPut();
  545. Compress( &file );
  546. retval = FSASYNC_OK;
  547. }
  548. else
  549. {
  550. Warning( "AsyncAppend: Attempted to write to a read-only file" );
  551. Assert( 0 );
  552. }
  553. }
  554. if ( bFreeMemory )
  555. free( const_cast< void * >( pSrc ) );
  556. return retval;
  557. }
  558. //-----------------------------------------------------------------------------
  559. // Purpose: Appends a memory block to another memory block. Function is NOT async.
  560. //-----------------------------------------------------------------------------
  561. FSAsyncStatus_t CSaveRestoreFileSystem::AsyncAppendFile(const char *pDestFileName, const char *pSrcFileName, FSAsyncControl_t *pControl )
  562. {
  563. FileHandle_t hFile = Open( pSrcFileName, "rb" );
  564. if ( hFile )
  565. {
  566. SaveFile_t &file = GetFile( hFile );
  567. return AsyncAppend( pDestFileName, file.pBuffer->Base(), file.nSize, false );
  568. }
  569. return FSASYNC_ERR_FILEOPEN;
  570. }
  571. //-----------------------------------------------------------------------------
  572. // Purpose: All operations in memory are synchronous
  573. //-----------------------------------------------------------------------------
  574. FSAsyncStatus_t CSaveRestoreFileSystem::AsyncFinish( FSAsyncControl_t hControl, bool wait )
  575. {
  576. // do nothing
  577. return FSASYNC_OK;
  578. }
  579. void CSaveRestoreFileSystem::AsyncRelease( FSAsyncControl_t hControl )
  580. {
  581. // do nothing
  582. }
  583. void CSaveRestoreFileSystem::AsyncFinishAllWrites( void )
  584. {
  585. // Do nothing
  586. }
  587. //-----------------------------------------------------------------------------
  588. // Purpose: Package up all intermediate files to a save game as per usual, but keep them in memory instead of commiting them to disk
  589. //-----------------------------------------------------------------------------
  590. void CSaveRestoreFileSystem::DirectorCopyToMemory( const char *pPath, const char *pDestFileName )
  591. {
  592. // Write the save file
  593. FileHandle_t hSaveFile = Open( pDestFileName, "ab+", pPath );
  594. if ( !hSaveFile )
  595. return;
  596. SaveFile_t &saveFile = GetFile( hSaveFile );
  597. // At this point, we're going to be sneaky and spoof the uncompressed buffer back into the compressed one
  598. // We need to do this because the file goes out to disk as a mixture of an uncompressed header and tags, and compressed
  599. // intermediate files, so this emulates that in memory
  600. saveFile.pCompressedBuffer->Purge();
  601. saveFile.nCompressedSize = 0;
  602. saveFile.pCompressedBuffer->Put( saveFile.pBuffer->Base(), saveFile.nSize );
  603. unsigned int nNumFilesPacked = 0;
  604. for ( int i = GetDirectory().FirstInorder(); GetDirectory().IsValidIndex( i ); i = GetDirectory().NextInorder( i ) )
  605. {
  606. SaveFile_t &file = GetFile( i );
  607. const char *pName = GetString( file.name );
  608. char szFileName[MAX_PATH];
  609. if ( Q_stristr( pName, ".hl" ) )
  610. {
  611. int fileSize = CompressedSize( pName );
  612. if ( fileSize )
  613. {
  614. Assert( Q_strlen( pName ) <= MAX_PATH );
  615. memset( szFileName, 0, sizeof( szFileName ) );
  616. Q_strncpy( szFileName, pName, sizeof( szFileName ) );
  617. saveFile.pCompressedBuffer->Put( szFileName, sizeof( szFileName ) );
  618. saveFile.pCompressedBuffer->Put( &fileSize, sizeof(fileSize) );
  619. saveFile.pCompressedBuffer->Put( file.pCompressedBuffer->Base(), file.nCompressedSize );
  620. SaveMsg("SIM: Packed: %s [Size: %.02f KB]\n", GetString( file.name ), (float)file.nCompressedSize / 1024.0f );
  621. nNumFilesPacked++;
  622. }
  623. }
  624. }
  625. // Set the final, complete size of the file
  626. saveFile.nCompressedSize = saveFile.pCompressedBuffer->TellMaxPut();
  627. SaveMsg("SIM: (%s) Total Files Packed: %d [Size: %.02f KB]\n", GetString( saveFile.name ), nNumFilesPacked, (float) saveFile.nCompressedSize / 1024.0f );
  628. }
  629. //-----------------------------------------------------------------------------
  630. // Purpose: Copies the compressed contents of the CSaveDirectory into the save file on disk.
  631. // Note: This expects standard saverestore behavior, and does NOT
  632. // currently use pPath to filter the filename search.
  633. //-----------------------------------------------------------------------------
  634. void CSaveRestoreFileSystem::DirectoryCopy( const char *pPath, const char *pDestFileName, bool bIsXSave )
  635. {
  636. if ( !Q_stristr( pPath, "*.hl?" ) )
  637. {
  638. // Function depends on known behavior
  639. Assert( 0 );
  640. return;
  641. }
  642. // If we don't have a valid storage device, save this to memory instead
  643. if ( saverestore->StorageDeviceValid() == false )
  644. {
  645. DirectorCopyToMemory( pPath, pDestFileName );
  646. return;
  647. }
  648. // Write the save file
  649. FileHandle_t hSaveFile = Open( pDestFileName, "rb", pPath );
  650. if ( !hSaveFile )
  651. return;
  652. SaveFile_t &saveFile = GetFile( hSaveFile );
  653. // Find out how large this is going to be
  654. unsigned int nWriteSize = saveFile.nSize;
  655. for ( int i = GetDirectory().FirstInorder(); GetDirectory().IsValidIndex( i ); i = GetDirectory().NextInorder( i ) )
  656. {
  657. SaveFile_t &file = GetFile( i );
  658. const char *pName = GetString( file.name );
  659. if ( Q_stristr( pName, ".hl" ) )
  660. {
  661. // Account for the lump header size
  662. nWriteSize += MAX_PATH + sizeof(int) + file.nCompressedSize;
  663. }
  664. }
  665. // Fail to write
  666. #if defined( _X360 )
  667. if ( nWriteSize > XBX_SAVEGAME_BYTES )
  668. {
  669. // FIXME: This error is now lost in the ether!
  670. return;
  671. }
  672. #endif
  673. g_pFileSystem->AsyncWriteFile( pDestFileName, saveFile.pBuffer, saveFile.nSize, true, false );
  674. // AsyncWriteFile() takes control of the utlbuffer, so don't let RemoveFile() delete it.
  675. saveFile.pBuffer = NULL;
  676. RemoveFile( pDestFileName );
  677. // write the list of files to the save file
  678. for ( int i = GetDirectory().FirstInorder(); GetDirectory().IsValidIndex( i ); i = GetDirectory().NextInorder( i ) )
  679. {
  680. SaveFile_t &file = GetFile( i );
  681. const char *pName = GetString( file.name );
  682. if ( Q_stristr( pName, ".hl" ) )
  683. {
  684. int fileSize = CompressedSize( pName );
  685. if ( fileSize )
  686. {
  687. Assert( Q_strlen( pName ) <= MAX_PATH );
  688. g_pFileSystem->AsyncAppend( pDestFileName, memcpy( new char[MAX_PATH], pName, MAX_PATH), MAX_PATH, true );
  689. g_pFileSystem->AsyncAppend( pDestFileName, new int(fileSize), sizeof(int), true );
  690. // This behaves like AsyncAppendFile (due to 5th param) but gets src file from memory instead of off disk.
  691. g_pFileSystem->AsyncWriteFile( pDestFileName, file.pCompressedBuffer, file.nCompressedSize, false, true );
  692. }
  693. }
  694. }
  695. }
  696. //-----------------------------------------------------------------------------
  697. // Purpose: Extracts all the files contained within pFile.
  698. // Does not Uncompress the extracted data.
  699. //-----------------------------------------------------------------------------
  700. bool CSaveRestoreFileSystem::DirectoryExtract( FileHandle_t pFile, int fileCount, bool bIsXSave )
  701. {
  702. int fileSize;
  703. FileHandle_t pCopy;
  704. char fileName[ MAX_PATH ];
  705. // Read compressed files from disk into the virtual directory
  706. for ( int i = 0; i < fileCount; i++ )
  707. {
  708. Read( fileName, MAX_PATH, pFile );
  709. Read( &fileSize, sizeof(int), pFile );
  710. if ( !fileSize )
  711. return false;
  712. pCopy = Open( fileName, "wb" );
  713. if ( !pCopy )
  714. return false;
  715. SaveFile_t &destFile = GetFile( pCopy );
  716. destFile.pCompressedBuffer->EnsureCapacity( fileSize );
  717. // Must read in the correct amount of data
  718. if ( Read( destFile.pCompressedBuffer->PeekPut(), fileSize, pFile ) != fileSize )
  719. return false;
  720. destFile.pCompressedBuffer->SeekPut( CUtlBuffer::SEEK_HEAD, fileSize );
  721. destFile.nCompressedSize = fileSize;
  722. SaveMsg("SIM: Extracted: %s [Size: %d KB]\n", GetString( destFile.name ), destFile.nCompressedSize / 1024 );
  723. }
  724. return true;
  725. }
  726. //-----------------------------------------------------------------------------
  727. // Purpose:
  728. // Input : *pFilename -
  729. //-----------------------------------------------------------------------------
  730. bool CSaveRestoreFileSystem::LoadFileFromDisk( const char *pFilename )
  731. {
  732. // Open a new file for writing in memory
  733. FileHandle_t hMemoryFile = Open( pFilename, "wb" );
  734. if ( !hMemoryFile )
  735. return false;
  736. // Open the file off the disk
  737. FileHandle_t hDiskFile = g_pFileSystem->OpenEx( pFilename, "rb", ( IsX360() ) ? FSOPEN_NEVERINPACK : 0 );
  738. if ( !hDiskFile )
  739. return false;
  740. // Read it in off of the disk to memory
  741. SaveFile_t &memoryFile = GetFile( hMemoryFile );
  742. if ( g_pFileSystem->ReadToBuffer( hDiskFile, *memoryFile.pCompressedBuffer ) == false )
  743. return false;
  744. // Hold the compressed size
  745. memoryFile.nCompressedSize = memoryFile.pCompressedBuffer->TellMaxPut();
  746. // Close the disk file
  747. g_pFileSystem->Close( hDiskFile );
  748. SaveMsg("SIM: Loaded %s into memory\n", pFilename );
  749. return true;
  750. }
  751. //-----------------------------------------------------------------------------
  752. // Purpose: Returns the number of save files in the specified filter
  753. // Note: This expects standard saverestore behavior, and does NOT
  754. // currently use pPath to filter file search.
  755. //-----------------------------------------------------------------------------
  756. int CSaveRestoreFileSystem::DirectoryCount( const char *pPath )
  757. {
  758. if ( !Q_stristr( pPath, "*.hl?" ) )
  759. {
  760. // Function depends on known behavior
  761. Assert( 0 );
  762. return 0;
  763. }
  764. int count = 0;
  765. for ( int i = GetDirectory().FirstInorder(); GetDirectory().IsValidIndex( i ); i = GetDirectory().NextInorder( i ) )
  766. {
  767. SaveFile_t &file = GetFile( i );
  768. if ( Q_stristr( GetString( file.name ), ".hl" ) )
  769. {
  770. ++count;
  771. }
  772. }
  773. return count;
  774. }
  775. //-----------------------------------------------------------------------------
  776. // Purpose: Clears the save directory of temporary save files (*.hl)
  777. // Note: This expects standard saverestore behavior, and does NOT
  778. // currently use pPath to filter file search.
  779. //-----------------------------------------------------------------------------
  780. void CSaveRestoreFileSystem::DirectoryClear( const char *pPath, bool bIsXSave )
  781. {
  782. if ( !Q_stristr( pPath, "*.hl?" ) )
  783. {
  784. // Function depends on known behavior
  785. Assert( 0 );
  786. return;
  787. }
  788. int i = GetDirectory().FirstInorder();
  789. while ( GetDirectory().IsValidIndex( i ) )
  790. {
  791. SaveFile_t &file = GetFile( i );
  792. i = GetDirectory().NextInorder( i );
  793. if ( Q_stristr( GetString( file.name ), ".hl" ) )
  794. {
  795. SaveMsg("SIM: Cleared: %s\n", GetString( file.name ) );
  796. // Delete the temporary save file
  797. RemoveFile( GetString( file.name ) );
  798. }
  799. }
  800. }
  801. //-----------------------------------------------------------------------------
  802. // Purpose: Transfer all save files from memory to disk.
  803. //-----------------------------------------------------------------------------
  804. void CSaveRestoreFileSystem::WriteSaveDirectoryToDisk( void )
  805. {
  806. char szPath[ MAX_PATH ];
  807. int i = GetDirectory().FirstInorder();
  808. while ( GetDirectory().IsValidIndex( i ) )
  809. {
  810. SaveFile_t &file = GetFile( i );
  811. i = GetDirectory().NextInorder( i );
  812. const char *pName = GetString( file.name );
  813. if ( Q_stristr( pName, ".hl" ) )
  814. {
  815. // Write the temporary save file to disk
  816. Open( pName, "rb" );
  817. Q_snprintf( szPath, sizeof( szPath ), "%s%s", saverestore->GetSaveDir(), pName );
  818. g_pFileSystem->WriteFile( szPath, MOD_DIR, *file.pBuffer );
  819. }
  820. }
  821. }
  822. //-----------------------------------------------------------------------------
  823. // Purpose: Transfer all save files from disk to memory.
  824. //-----------------------------------------------------------------------------
  825. void CSaveRestoreFileSystem::LoadSaveDirectoryFromDisk( const char *pPath )
  826. {
  827. char const *findfn;
  828. char szPath[ MAX_PATH ];
  829. findfn = Sys_FindFirstEx( pPath, MOD_DIR, NULL, 0 );
  830. while ( findfn != NULL )
  831. {
  832. Q_snprintf( szPath, sizeof( szPath ), "%s%s", saverestore->GetSaveDir(), findfn );
  833. // Add the temporary save file
  834. FileHandle_t hFile = Open( findfn, "wb" );
  835. if ( hFile )
  836. {
  837. SaveFile_t &file = GetFile( hFile );
  838. g_pFileSystem->ReadFile( szPath, MOD_DIR, *file.pBuffer );
  839. file.nSize = file.pBuffer->TellMaxPut();
  840. Close( hFile );
  841. }
  842. // Any more save files
  843. findfn = Sys_FindNext( NULL, 0 );
  844. }
  845. Sys_FindClose();
  846. }
  847. //-----------------------------------------------------------------------------
  848. // Purpose:
  849. //-----------------------------------------------------------------------------
  850. void CSaveRestoreFileSystem::AuditFiles( void )
  851. {
  852. unsigned int nTotalFiles = 0;
  853. unsigned int nTotalCompressed = 0;
  854. unsigned int nTotalUncompressed = 0;
  855. int i = GetDirectory().FirstInorder();
  856. while ( GetDirectory().IsValidIndex( i ) )
  857. {
  858. SaveFile_t &file = GetFile( i );
  859. i = GetDirectory().NextInorder( i );
  860. nTotalFiles++;
  861. nTotalCompressed += file.nCompressedSize;
  862. nTotalUncompressed += file.nSize;
  863. Msg("SIM: File: %s [c: %.02f KB / u: %.02f KB]\n", GetString( file.name ), (float)file.nCompressedSize/1024.0f, (float)file.nSize/1024.0f );
  864. }
  865. Msg("SIM: ------------------------------------------------------------");
  866. 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 );
  867. }
  868. CON_COMMAND( audit_save_in_memory, "Audit the memory usage and files in the save-to-memory system" )
  869. {
  870. if ( !IsX360() )
  871. return;
  872. g_pSaveRestoreFileSystem->AuditFiles();
  873. }
  874. #ifdef _X360
  875. CON_COMMAND( dump_x360_data, "Dump X360 save games to disk" )
  876. {
  877. int iController = args.FindArgInt( "-c", XBX_GetPrimaryUserId() );
  878. char const *szDataType = args.FindArg( "-t" );
  879. if ( !szDataType )
  880. szDataType = XBX_USER_SETTINGS_CONTAINER_DRIVE;
  881. if ( XBX_GetUserIsGuest( iController ) )
  882. return;
  883. DWORD nStorageDevice = XBX_GetStorageDeviceId( iController );
  884. if ( !XBX_DescribeStorageDevice( nStorageDevice ) )
  885. {
  886. Warning( "No storage device for controller %d!\n", iController );
  887. return;
  888. }
  889. char szInName[MAX_PATH]; // Read path from the container
  890. char szOutName[MAX_PATH]; // Output path to the disk
  891. char szFileNameBase[MAX_PATH]; // Name of the file minus directories or extensions
  892. FileFindHandle_t findHandle;
  893. char szSearchPath[MAX_PATH];
  894. XBX_MakeStorageContainerRoot( iController, szDataType, szSearchPath, sizeof( szSearchPath ) );
  895. int nLen = strlen( szSearchPath );
  896. Q_snprintf( szSearchPath + nLen, sizeof( szSearchPath ) - nLen, ":\\*.*" );
  897. const char *pFileName = g_pFileSystem->FindFirst( szSearchPath, &findHandle );
  898. while (pFileName)
  899. {
  900. // Create the proper read path
  901. XBX_MakeStorageContainerRoot( iController, szDataType, szInName, sizeof( szInName ) );
  902. int nLen = strlen( szInName );
  903. Q_snprintf( szInName + nLen, sizeof( szInName ) - nLen, ":\\%s", pFileName );
  904. // Read the file and blat it out
  905. CUtlBuffer buf( 0, 0, 0 );
  906. if ( g_pFileSystem->ReadFile( szInName, NULL, buf ) )
  907. {
  908. // Strip us down to just our filename
  909. Q_FileBase( pFileName, szFileNameBase, sizeof ( szFileNameBase ) );
  910. Q_snprintf( szOutName, sizeof( szOutName ), "%s%d/%s", szDataType, iController, szFileNameBase );
  911. g_pFileSystem->WriteFile( szOutName, NULL, buf );
  912. Msg("Copied file: %s to %s\n", szInName, szOutName );
  913. }
  914. // Clean up
  915. buf.Clear();
  916. // Any more save files
  917. pFileName = g_pFileSystem->FindNext( findHandle );
  918. }
  919. g_pFileSystem->FindClose( findHandle );
  920. }
  921. #endif
  922. #include "saverestore_filesystem_passthrough.h"
  923. static CSaveRestoreFileSystem s_SaveRestoreFileSystem;
  924. static CSaveRestoreFileSystemPassthrough s_SaveRestoreFileSystemPassthrough;
  925. #ifdef _X360
  926. ISaveRestoreFileSystem *g_pSaveRestoreFileSystem = &s_SaveRestoreFileSystem;
  927. #else
  928. ISaveRestoreFileSystem *g_pSaveRestoreFileSystem = &s_SaveRestoreFileSystemPassthrough;
  929. #endif // _X360
  930. //-----------------------------------------------------------------------------
  931. // Purpose: Called when switching between saving in memory and saving to disk.
  932. //-----------------------------------------------------------------------------
  933. void SaveInMemoryCallback( IConVar *pConVar, const char *pOldString, float flOldValue )
  934. {
  935. if ( !IsX360() )
  936. {
  937. Warning( "save_in_memory is compatible with only the Xbox 360!\n" );
  938. return;
  939. }
  940. #ifdef _X360
  941. ConVarRef var( pConVar );
  942. if ( var.GetFloat() == flOldValue )
  943. return;
  944. // *.hl? files are transferred between disk and memory when this cvar changes
  945. char szPath[ MAX_PATH ];
  946. Q_snprintf( szPath, sizeof( szPath ), "%s%s", saverestore->GetSaveDir(), "*.hl?" );
  947. if ( var.GetBool() )
  948. {
  949. g_pSaveRestoreFileSystem = &s_SaveRestoreFileSystem;
  950. // Clear memory and load
  951. s_SaveRestoreFileSystem.DirectoryClear( "*.hl?", IsX360() );
  952. s_SaveRestoreFileSystem.LoadSaveDirectoryFromDisk( szPath );
  953. // Clear disk
  954. s_SaveRestoreFileSystemPassthrough.DirectoryClear( szPath, IsX360() );
  955. }
  956. else
  957. {
  958. g_pSaveRestoreFileSystem = &s_SaveRestoreFileSystemPassthrough;
  959. // Clear disk and write
  960. s_SaveRestoreFileSystemPassthrough.DirectoryClear( szPath, IsX360() );
  961. s_SaveRestoreFileSystem.WriteSaveDirectoryToDisk();
  962. // Clear memory
  963. s_SaveRestoreFileSystem.DirectoryClear( "*.hl?", IsX360() );
  964. }
  965. #endif
  966. }
  967. //-----------------------------------------------------------------------------
  968. // Purpose: Dump a list of the save directory contents (in memory) to the console
  969. //-----------------------------------------------------------------------------
  970. void CSaveRestoreFileSystem::DumpSaveDirectory( void )
  971. {
  972. unsigned int totalCompressedSize = 0;
  973. unsigned int totalUncompressedSize = 0;
  974. for ( int i = GetDirectory().FirstInorder(); GetDirectory().IsValidIndex( i ); i = GetDirectory().NextInorder( i ) )
  975. {
  976. SaveFile_t &file = GetFile( i );
  977. Msg( "File %d: %s Size:%d\n", i, GetString( file.name ), file.nCompressedSize );
  978. totalUncompressedSize += file.nSize;
  979. totalCompressedSize += file.nCompressedSize;
  980. }
  981. float percent = 0.f;
  982. if ( totalUncompressedSize )
  983. percent = 100.f - (totalCompressedSize / totalUncompressedSize * 100.f);
  984. Msg( "Total Size: %.2f Mb (%d bytes)\n", totalCompressedSize / (1024.f*1024.f), totalCompressedSize );
  985. Msg( "Compression: %.2f Mb to %.2f Mb (%.0f%%)\n", totalUncompressedSize/(1024.f*1024.f), totalCompressedSize/(1024.f*1024.f), percent );
  986. }
  987. #ifdef _X360
  988. CON_COMMAND( dumpsavedir, "List the contents of the save directory in memory" )
  989. {
  990. s_SaveRestoreFileSystem.DumpSaveDirectory();
  991. }
  992. #endif