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.

589 lines
23 KiB

  1. //===== Copyright � 1996-2011, Valve Corporation, All rights reserved. ======//
  2. #include "ps3_saveutil_v2.h"
  3. #include "memdbgon.h"
  4. #include <vjobs/jobparams_shared.h>
  5. #include <vjobs/root.h>
  6. #include <ps3/vjobutils.h>
  7. ConVar ps3_saveutil2_compress( "ps3_saveutil2_compress", "1", FCVAR_DEVELOPMENTONLY );
  8. class CCellFsFileDescriptorAutoClose
  9. {
  10. public:
  11. CCellFsFileDescriptorAutoClose( int fd = -1 ) : m_fd( fd ), m_pJobDeflate( NULL ) {}
  12. ~CCellFsFileDescriptorAutoClose() { Close(); EndDeflate(); }
  13. void Close()
  14. {
  15. if ( m_fd != -1 )
  16. {
  17. cellFsClose( m_fd ); m_fd = -1;
  18. }
  19. }
  20. int m_fd;
  21. job_zlibdeflate::JobDescriptor_t * m_pJobDeflate;
  22. void BeginDeflate()
  23. {
  24. m_pJobDeflate = NewJob128( *g_saveUtilVjobInstance.m_pRoot->m_pJobZlibDeflate );
  25. m_pJobDeflate->header.sizeScratch = ( 32 * 1024 ) / 16 ;
  26. job_zlibdeflate::GetJobParams( m_pJobDeflate )->m_nStatus = 2; // status: the job isn't queued yet, so it's considered "done"
  27. }
  28. void EndDeflate()
  29. {
  30. if( m_pJobDeflate )
  31. {
  32. while( !job_zlibdeflate::GetJobParams( m_pJobDeflate )->IsDone() )
  33. {
  34. ThreadSleep( 1 );
  35. }
  36. DeleteJob( m_pJobDeflate );
  37. m_pJobDeflate = NULL;
  38. }
  39. }
  40. };
  41. class CSaveUtilV2Job_Save : public ISaveUtilV2Job
  42. {
  43. public: // Job entry point
  44. virtual JobStatus_t DoExecute();
  45. public: // Data passed from main thread
  46. char m_chComment[VALVE_CONTAINER_STRLEN];
  47. char m_chFile[VALVE_CONTAINER_FPARTS][VALVE_CONTAINER_STRLEN];
  48. int m_numAutoSavesCount;
  49. int m_numCloudFiles;
  50. protected: // Buffer used for file data
  51. CSaveUtilV2ContainerTOC m_newTOC;
  52. CUtlBuffer m_bufFiles;
  53. CSaveUtilV2ContainerTOC::TocStorageReserved_t *m_pNewTOCEntry;
  54. struct OverwriteRequest_t
  55. {
  56. char m_chOverwriteContainerFile[VALVE_CONTAINER_8_3_LEN];
  57. };
  58. CUtlVector< OverwriteRequest_t > m_arrOverwriteRequests;
  59. OverwriteRequest_t m_owrDelete;
  60. CCellFsFileDescriptorAutoClose m_fd[VALVE_CONTAINER_FPARTS];
  61. int OpenAndStatFiles();
  62. int LoadAndCompressFileData();
  63. void PrepareToc();
  64. protected: // Stat callback
  65. virtual void DoDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS );
  66. protected: // Write the file and update TOC
  67. void DoDataFile_DeleteOldFile( SONY_SAVEUTIL_FILE_PARAMS );
  68. void DoDataFile_WriteFiles( SONY_SAVEUTIL_FILE_PARAMS );
  69. void DoDataFile_UpdateTOC( SONY_SAVEUTIL_FILE_PARAMS );
  70. };
  71. //////////////////////////////////////////////////////////////////////////
  72. void SaveUtilV2_Write( CPS3SaveRestoreAsyncStatus *pAsync, const char *pSourcepath, const char *pScreenshotPath, const char *pComment )
  73. {
  74. if ( !SaveUtilV2_CanStartJob() )
  75. return;
  76. // Start the job
  77. CSaveUtilV2Job_Save *pJob = new CSaveUtilV2Job_Save;
  78. V_strncpy( pJob->m_chComment, pComment ? pComment : "", sizeof( pJob->m_chComment ) );
  79. V_strncpy( pJob->m_chFile[0], pSourcepath ? pSourcepath : "", sizeof( pJob->m_chFile[0] ) );
  80. V_strncpy( pJob->m_chFile[1], pScreenshotPath ? pScreenshotPath : "", sizeof( pJob->m_chFile[1] ) );
  81. pJob->m_numAutoSavesCount = 0;
  82. pJob->m_numCloudFiles = 0;
  83. SaveUtilV2_EnqueueJob( pAsync, pJob );
  84. }
  85. void SaveUtilV2_WriteAutosave( CPS3SaveRestoreAsyncStatus *pAsync,
  86. const char *pSourcepath, // eg "/dev_hdd1/tempsave/autosave.ps3.sav"
  87. const char *pComment, // the comment field for the new autosave.
  88. const unsigned int nMaxNumAutosaves )
  89. {
  90. if ( !SaveUtilV2_CanStartJob() )
  91. return;
  92. // Start the job
  93. CSaveUtilV2Job_Save *pJob = new CSaveUtilV2Job_Save;
  94. V_strncpy( pJob->m_chComment, pComment ? pComment : "", sizeof( pJob->m_chComment ) );
  95. V_strncpy( pJob->m_chFile[0], pSourcepath ? pSourcepath : "", sizeof( pJob->m_chFile[0] ) );
  96. V_strncpy( pJob->m_chFile[1], "", sizeof( pJob->m_chFile[1] ) );
  97. pJob->m_numAutoSavesCount = nMaxNumAutosaves;
  98. pJob->m_numCloudFiles = 0;
  99. SaveUtilV2_EnqueueJob( pAsync, pJob );
  100. }
  101. void SaveUtilV2_WriteCloudFile( CPS3SaveRestoreAsyncStatus *pAsync,
  102. const char *pSourcepath, // eg "/dev_hdd1/tempsave/autosave.ps3.sav"
  103. const unsigned int nMaxNumCloudFiles )
  104. {
  105. if ( !SaveUtilV2_CanStartJob() )
  106. return;
  107. // Start the job
  108. CSaveUtilV2Job_Save *pJob = new CSaveUtilV2Job_Save;
  109. V_strncpy( pJob->m_chComment, "", sizeof( pJob->m_chComment ) );
  110. V_strncpy( pJob->m_chFile[0], pSourcepath ? pSourcepath : "", sizeof( pJob->m_chFile[0] ) );
  111. V_strncpy( pJob->m_chFile[1], "", sizeof( pJob->m_chFile[1] ) );
  112. pJob->m_numAutoSavesCount = 0;
  113. pJob->m_numCloudFiles = nMaxNumCloudFiles;
  114. SaveUtilV2_EnqueueJob( pAsync, pJob );
  115. }
  116. //////////////////////////////////////////////////////////////////////////
  117. JobStatus_t CSaveUtilV2Job_Save::DoExecute()
  118. {
  119. float flTimeStamp = Plat_FloatTime();
  120. Msg( "CSaveUtilV2Job_Save @%.3f\n", flTimeStamp );
  121. // Prepare new TOC and attempt to determine if the operation is write new or overwrite
  122. PrepareToc();
  123. // Fill out the rest of the TOC
  124. if ( OpenAndStatFiles() < 0 )
  125. {
  126. for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart )
  127. m_fd[iPart].Close();
  128. return SaveUtilV2_JobDone( CELL_SAVEDATA_ERROR_FAILURE );
  129. }
  130. // Allocate our buffer
  131. int nCapacityRequired = 0;
  132. for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart )
  133. nCapacityRequired += m_pNewTOCEntry->m_entry.m_numBytesFile[iPart];
  134. m_bufFiles.EnsureCapacity( CSaveUtilV2ContainerTOC::kStorageCapacity +
  135. ( ( ps3_saveutil2_compress.GetBool() && ( m_numCloudFiles <= 0 ) ) ? 2 : 1 ) * nCapacityRequired );
  136. if ( ps3_saveutil2_compress.GetBool() && ( m_numCloudFiles <= 0 ) )
  137. {
  138. uint8 *pBaseRawData = ( ( uint8 * ) m_bufFiles.Base() ) + CSaveUtilV2ContainerTOC::kStorageCapacity;
  139. uint8 *pBaseOutData = pBaseRawData + nCapacityRequired;
  140. for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart )
  141. {
  142. if( m_fd[iPart].m_fd == -1 )
  143. continue;
  144. m_fd[iPart].BeginDeflate();
  145. uint64_t numBytesActuallyRead = 0, nUncompressedSize = m_pNewTOCEntry->m_entry.m_numBytesFile[iPart];
  146. // Read the file at the end of the buffer
  147. int ret = cellFsRead( m_fd[iPart].m_fd, pBaseRawData, nUncompressedSize, &numBytesActuallyRead );
  148. m_fd[iPart].Close();
  149. if ( ret < 0 || numBytesActuallyRead != nUncompressedSize )
  150. return SaveUtilV2_JobDone( CELL_SAVEDATA_ERROR_FAILURE );
  151. // Compress the file
  152. job_zlibdeflate::JobDescriptor_t * pJobDeflate = m_fd[iPart].m_pJobDeflate;
  153. job_zlibdeflate::JobParams_t * pJobParams = job_zlibdeflate::GetJobParams( pJobDeflate );
  154. pJobParams->m_eaInputUncompressedData = pBaseRawData;
  155. pJobParams->m_eaOutputCompressedData = pBaseOutData;
  156. pJobParams->m_nMaxCompressedOutputSize = nUncompressedSize;
  157. pJobParams->m_nUncompressedSize = nUncompressedSize;
  158. pJobParams->m_nStatus = 0; // get ready to push the job
  159. int nError = g_saveUtilVjobInstance.m_pRoot->m_queuePortSound.pushJob( &pJobDeflate->header, sizeof( *pJobDeflate ), 0, CELL_SPURS_JOBQUEUE_FLAG_SYNC_JOB );
  160. if( nError != CELL_OK )
  161. {
  162. pJobParams->m_nStatus = 3; // done
  163. pJobParams->m_nError = 1; // error
  164. Warning( "Cannot push zlib job, ERROR 0x%X\n", nError );
  165. }
  166. pBaseRawData += nUncompressedSize;
  167. pBaseOutData += nUncompressedSize;
  168. }
  169. Assert( pBaseOutData <= ((uint8*)m_bufFiles.Base())+m_bufFiles.Size() );
  170. }
  171. // Call saveutil
  172. int retv = cellSaveDataAutoSave2(
  173. CELL_SAVEDATA_VERSION_CURRENT,
  174. g_pszSaveUtilContainerName,
  175. // autosaves report PS3 system dialog-errors
  176. (m_numAutoSavesCount||g_pSaveUtilAsyncStatus->m_bUseSystemDialogs) ? CELL_SAVEDATA_ERRDIALOG_ALWAYS : CELL_SAVEDATA_ERRDIALOG_NONE,
  177. &m_SaveDirInfo,
  178. csDataStatCallback,
  179. csDataFileCallback,
  180. SYS_MEMORY_CONTAINER_ID_INVALID,
  181. this );
  182. for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart )
  183. m_fd[iPart].EndDeflate();
  184. float flEndTimeStamp = Plat_FloatTime();
  185. Msg( "CSaveUtilV2Job_Save: cellSaveDataAutoSave2 returned %x @%.3f ( total time = %.3f sec )\n", retv, flEndTimeStamp, flEndTimeStamp - flTimeStamp );
  186. // Close the file handles before returning so that we didn't hold the files locked
  187. // in case main thread resumes after job is done and tries to re-use the files
  188. for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart )
  189. m_fd[iPart].Close();
  190. ++ g_SaveUtilV2TOCVersion;
  191. return SaveUtilV2_JobDone( retv );
  192. }
  193. void CSaveUtilV2Job_Save::DoDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS )
  194. {
  195. Msg( "CSaveUtilV2Job_Save::DoDataStatCallback @%.3f\n", Plat_FloatTime() );
  196. // TODO: investigate how to move delete after the write and maintain
  197. // consistent transactional state of TOC
  198. SetDataFileCallback( &CSaveUtilV2Job_Save::DoDataFile_DeleteOldFile );
  199. cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;
  200. }
  201. void CSaveUtilV2Job_Save::DoDataFile_DeleteOldFile( SONY_SAVEUTIL_FILE_PARAMS )
  202. {
  203. if ( !m_arrOverwriteRequests.Count() )
  204. {
  205. DoDataFile_WriteFiles( SONY_SAVEUTIL_PARAMS );
  206. return;
  207. }
  208. m_owrDelete = m_arrOverwriteRequests[m_arrOverwriteRequests.Count() - 1];
  209. m_arrOverwriteRequests.SetCountNonDestructively( m_arrOverwriteRequests.Count() - 1 );
  210. Msg( "CSaveUtilV2Job_Save::DoDataFile_DeleteOldFile @%.3f\n", Plat_FloatTime() );
  211. // Perform the delete
  212. set->fileOperation = CELL_SAVEDATA_FILEOP_DELETE;
  213. set->fileBuf = NULL;
  214. set->fileSize = set->fileBufSize = 0;
  215. set->fileName = m_owrDelete.m_chOverwriteContainerFile;
  216. set->fileOffset = 0;
  217. set->fileType = CELL_SAVEDATA_FILETYPE_SECUREFILE;
  218. memcpy( set->secureFileId, g_pszSaveUtilSecureFileId, CELL_SAVEDATA_SECUREFILEID_SIZE );
  219. set->reserved = NULL;
  220. SetDataFileCallback( &CSaveUtilV2Job_Save::DoDataFile_DeleteOldFile );
  221. cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;
  222. Msg( "CSaveUtilV2Job_Save::DoDataFile_DeleteOldFile will delete '%s'...\n", set->fileName );
  223. }
  224. void CSaveUtilV2Job_Save::DoDataFile_WriteFiles( SONY_SAVEUTIL_FILE_PARAMS )
  225. {
  226. Msg( "CSaveUtilV2Job_Save::DoDataFile_WriteFiles @%.3f\n", Plat_FloatTime() );
  227. // Obtain the files data required to be written
  228. if ( LoadAndCompressFileData() < 0 )
  229. {
  230. Msg( "ERROR: CSaveUtilV2Job_Save::DoDataFile_WriteFiles failed to load file!\n" );
  231. g_pSaveUtilAsyncStatus->m_nSonyRetValue = CELL_SAVEDATA_ERROR_FAILURE;
  232. cbResult->result = CELL_SAVEDATA_CBRESULT_ERR_FAILURE;
  233. return;
  234. }
  235. // Perform the write
  236. set->fileOperation = CELL_SAVEDATA_FILEOP_WRITE;
  237. set->fileBuf = m_bufFiles.Base();
  238. set->fileSize = set->fileBufSize = m_bufFiles.TellPut();
  239. set->fileName = m_pNewTOCEntry->m_entry.m_chContainerName;
  240. set->fileOffset = 0;
  241. set->fileType = CELL_SAVEDATA_FILETYPE_SECUREFILE;
  242. memcpy( set->secureFileId, g_pszSaveUtilSecureFileId, CELL_SAVEDATA_SECUREFILEID_SIZE );
  243. set->reserved = NULL;
  244. // update our TOC after the write succeeds
  245. SetDataFileCallback( &CSaveUtilV2Job_Save::DoDataFile_UpdateTOC );
  246. cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;
  247. Msg( "CSaveUtilV2Job_Save::DoDataFile_WriteFiles will write %d bytes...\n", set->fileSize );
  248. }
  249. void CSaveUtilV2Job_Save::DoDataFile_UpdateTOC( SONY_SAVEUTIL_FILE_PARAMS )
  250. {
  251. Msg( "CSaveUtilV2Job_Save::DoDataFile_UpdateTOC @%.3f\n", Plat_FloatTime() );
  252. // Update TOC since we successfully wrote the file
  253. {
  254. AUTO_LOCK( g_SaveUtilV2TOC.m_mtx );
  255. // Memory has been pre-reserved for the larger number of entries
  256. m_newTOC.CopyInto( &g_SaveUtilV2TOC );
  257. }
  258. cbResult->result = CELL_SAVEDATA_CBRESULT_OK_LAST;
  259. }
  260. int CSaveUtilV2Job_Save::OpenAndStatFiles()
  261. {
  262. float flTimeStamp = Plat_FloatTime();
  263. if ( m_numCloudFiles > 0 )
  264. {
  265. // Cloud save
  266. int ret = cellFsOpen( m_chFile[0], CELL_FS_O_RDONLY, &m_fd[0].m_fd, NULL, 0 );
  267. if ( ret < 0 )
  268. return ret;
  269. CellFsStat cfs;
  270. ret = cellFsFstat( m_fd[0].m_fd, &cfs );
  271. if ( ret < 0 )
  272. return ret;
  273. if ( cfs.st_size <= sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t ) )
  274. return -1;
  275. m_pNewTOCEntry->m_entry.m_numBytesFile[0] = cfs.st_size;
  276. }
  277. else
  278. {
  279. // Non-cloud save
  280. Q_strncpy( m_pNewTOCEntry->m_entry.m_chComment, m_chComment, sizeof( m_pNewTOCEntry->m_entry.m_chComment ) );
  281. m_pNewTOCEntry->m_entry.m_timeModification = time( NULL );
  282. for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart )
  283. {
  284. if ( !m_chFile[iPart][0] )
  285. continue;
  286. Q_strncpy( m_pNewTOCEntry->m_entry.m_chFile[iPart], V_GetFileName( m_chFile[iPart] ), VALVE_CONTAINER_FILENAME_LEN );
  287. int ret = cellFsOpen( m_chFile[iPart], CELL_FS_O_RDONLY, &m_fd[iPart].m_fd, NULL, 0 );
  288. if ( ret < 0 )
  289. return ret;
  290. CellFsStat cfs;
  291. ret = cellFsFstat( m_fd[iPart].m_fd, &cfs );
  292. if ( ret < 0 )
  293. return ret;
  294. m_pNewTOCEntry->m_entry.m_numBytesFile[iPart] = cfs.st_size;
  295. }
  296. }
  297. float flEndTimeStamp = Plat_FloatTime();
  298. Msg( "CSaveUtilV2Job_Save::OpenAndStatFiles took %.3f sec [ %u + %u bytes ]\n",
  299. flEndTimeStamp - flTimeStamp, m_pNewTOCEntry->m_entry.m_numBytesFile[0], m_pNewTOCEntry->m_entry.m_numBytesFile[1] );
  300. return 0;
  301. }
  302. int CSaveUtilV2Job_Save::LoadAndCompressFileData()
  303. {
  304. // Leave room for TOC
  305. m_bufFiles.SeekPut( CUtlBuffer::SEEK_HEAD, CSaveUtilV2ContainerTOC::kStorageCapacity );
  306. float flTimeStamp = Plat_FloatTime();
  307. Assert( m_bufFiles.TellPut() == CSaveUtilV2ContainerTOC::kStorageCapacity ) ;
  308. uint8 *pBaseData = ( ( uint8 * ) m_bufFiles.Base() ) + CSaveUtilV2ContainerTOC::kStorageCapacity;
  309. if ( m_numCloudFiles <= 0 )
  310. {
  311. for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart )
  312. {
  313. if ( ps3_saveutil2_compress.GetBool() )
  314. {
  315. job_zlibdeflate::JobDescriptor_t * pJobDeflate = m_fd[iPart].m_pJobDeflate;
  316. if( !pJobDeflate )
  317. continue;
  318. job_zlibdeflate::JobParams_t * pJobParams = job_zlibdeflate::GetJobParams( pJobDeflate );
  319. uint nStallCount = 0;
  320. while( !pJobParams->IsDone() )
  321. {
  322. ThreadSleep( 1 );
  323. ++nStallCount;
  324. }
  325. uint numCompressedBytes = pJobParams->m_nCompressedSizeOut & 0x7FFFFFFF;
  326. Msg( "job_zlibDeflate stalled ~%u ms : %u -> %u KiB\n", nStallCount, pJobParams->m_nUncompressedSize / 1024, ( numCompressedBytes & 0x7FFFFFFF ) / 1024 );
  327. if ( pJobParams->m_nError == 0 && ( pJobParams->m_nCompressedSizeOut & 0x80000000 ) && numCompressedBytes < m_pNewTOCEntry->m_entry.m_numBytesFile[iPart] ) // we actually have deflated data
  328. {
  329. // file compressed successfully
  330. // remove MSB
  331. m_pNewTOCEntry->m_entry.m_numBytesDecompressedFile[iPart] = m_pNewTOCEntry->m_entry.m_numBytesFile[iPart];
  332. m_pNewTOCEntry->m_entry.m_numBytesFile[iPart] = numCompressedBytes;
  333. V_memcpy( pBaseData, pJobParams->m_eaOutputCompressedData, numCompressedBytes );
  334. Msg( "CSaveUtilV2Job_Save::LoadAndCompressFileData compresses '%s' %d/%d bytes\n", m_pNewTOCEntry->m_entry.m_chFile[iPart], m_pNewTOCEntry->m_entry.m_numBytesFile[iPart], m_pNewTOCEntry->m_entry.m_numBytesDecompressedFile[iPart] );
  335. }
  336. else
  337. {
  338. // there was an error during compression; use uncompressed data
  339. m_pNewTOCEntry->m_entry.m_numBytesDecompressedFile[iPart] = 0;
  340. if( pBaseData != pJobParams->m_eaInputUncompressedData )
  341. {
  342. V_memmove( pBaseData, pJobParams->m_eaInputUncompressedData, m_pNewTOCEntry->m_entry.m_numBytesFile[iPart] );
  343. }
  344. }
  345. pBaseData += m_pNewTOCEntry->m_entry.m_numBytesFile[iPart];
  346. }
  347. else
  348. {
  349. if ( m_fd[iPart].m_fd == -1 )
  350. continue;
  351. uint64 numBytesActuallyRead;
  352. int ret = cellFsRead( m_fd[iPart].m_fd, ( ( uint8 * )m_bufFiles.Base() ) + m_bufFiles.TellPut(), m_bufFiles.Size() - m_bufFiles.TellPut(), &numBytesActuallyRead );
  353. m_fd[iPart].Close();
  354. if ( ret < 0 )
  355. return ret;
  356. if ( numBytesActuallyRead != m_pNewTOCEntry->m_entry.m_numBytesFile[iPart] )
  357. return -1;
  358. }
  359. m_bufFiles.SeekPut( CUtlBuffer::SEEK_HEAD, m_bufFiles.TellPut() + m_pNewTOCEntry->m_entry.m_numBytesFile[iPart] );
  360. }
  361. }
  362. else
  363. {
  364. if ( m_fd[0].m_fd == -1 )
  365. return -1;
  366. uint64 numBytesActuallyRead;
  367. m_bufFiles.SeekPut( CUtlBuffer::SEEK_HEAD, CSaveUtilV2ContainerTOC::kStorageCapacity - sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t ) );
  368. char unsigned *pbIncomingFileBase = ( ( char unsigned * ) m_bufFiles.Base() ) + m_bufFiles.TellPut();
  369. int ret = cellFsRead( m_fd[0].m_fd, pbIncomingFileBase, m_bufFiles.Size() - m_bufFiles.TellPut(), &numBytesActuallyRead );
  370. m_fd[0].Close();
  371. if ( ret < 0 )
  372. return ret;
  373. if ( numBytesActuallyRead != m_pNewTOCEntry->m_entry.m_numBytesFile[0] )
  374. return -1;
  375. m_bufFiles.SeekPut( CUtlBuffer::SEEK_HEAD, m_bufFiles.TellPut() + m_pNewTOCEntry->m_entry.m_numBytesFile[0] );
  376. //
  377. // Signature
  378. //
  379. // Version of our save header
  380. CSaveUtilV2ContainerTOC::TocEntry_t *pSignature = (CSaveUtilV2ContainerTOC::TocEntry_t *) pbIncomingFileBase;
  381. if ( pSignature->m_chContainerName[0] != 'S' ||
  382. pSignature->m_chContainerName[1] != 'A' ||
  383. pSignature->m_chContainerName[2] != 'V' ||
  384. pSignature->m_chContainerName[3] != '1' )
  385. {
  386. Msg( "ERROR: CSaveUtilV2Job_Save : header mismatch, expecting SAV1\n" );
  387. return -2; // header mismatch
  388. }
  389. // Fetch the current hash
  390. uint32 uiFileHashCurrent = 0, uiHash = 0;
  391. V_memcpy( &uiFileHashCurrent, ( (char*) pSignature ) + 8 + sizeof( g_uiSteamCloudCryptoKey ) - sizeof( uiHash ), sizeof( uiHash ) );
  392. // Temporarily put our cryptokey in place of hash
  393. V_memcpy( ( (char*) pSignature ) + 8, &g_uiSteamCloudCryptoKey, sizeof( g_uiSteamCloudCryptoKey ) );
  394. uiHash = SaveUtilV2_ComputeBufferHash( pSignature, m_pNewTOCEntry->m_entry.m_numBytesFile[0] );
  395. if ( uiHash != uiFileHashCurrent )
  396. {
  397. Msg( "ERROR: CSaveUtilV2Job_Save : signature hash mismatch\n" );
  398. return -3;
  399. }
  400. // We only need to preserve container name
  401. char chContainerName[sizeof( m_pNewTOCEntry->m_entry.m_chContainerName )];
  402. V_memcpy( chContainerName, m_pNewTOCEntry->m_entry.m_chContainerName, sizeof( m_pNewTOCEntry->m_entry.m_chContainerName ) );
  403. V_memcpy( m_pNewTOCEntry, pbIncomingFileBase, sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t ) );
  404. V_memcpy( m_pNewTOCEntry->m_entry.m_chContainerName, chContainerName, sizeof( m_pNewTOCEntry->m_entry.m_chContainerName ) );
  405. V_memset( m_pNewTOCEntry->m_entry.m_chFile[0], 0, sizeof( m_pNewTOCEntry->m_entry.m_chFile[0] ) );
  406. V_snprintf( m_pNewTOCEntry->m_entry.m_chFile[0], sizeof( m_pNewTOCEntry->m_entry.m_chFile[0] ), "cloudsave%016llx.ps3.sav", m_pNewTOCEntry->m_entry.m_timeModification );
  407. if ( *m_pNewTOCEntry->m_entry.m_chFile[1] )
  408. V_snprintf( m_pNewTOCEntry->m_entry.m_chFile[1], sizeof( m_pNewTOCEntry->m_entry.m_chFile[1] ), "cloudsave%016llx.ps3.tga", m_pNewTOCEntry->m_entry.m_timeModification );
  409. }
  410. float flEndTimeStamp = Plat_FloatTime();
  411. Msg( "CSaveUtilV2Job_Save::LoadFileToBuffer finished loading%s @%.3f (%.3f sec)\n", ps3_saveutil2_compress.GetBool() ? " and compressing" : "",
  412. flEndTimeStamp, flEndTimeStamp - flTimeStamp );
  413. // New TOC is fully ready, serialize it for writing
  414. m_newTOC.SerializeIntoTocBuffer( m_bufFiles.Base() );
  415. return 0;
  416. }
  417. static int TocAutosavesSortFunc( CSaveUtilV2ContainerTOC::TocStorageReserved_t * const *a, CSaveUtilV2ContainerTOC::TocStorageReserved_t * const *b )
  418. {
  419. if ( (*a)->m_entry.m_timeModification != (*b)->m_entry.m_timeModification )
  420. return ( (*a)->m_entry.m_timeModification < (*b)->m_entry.m_timeModification ) ? -1 : 1;
  421. else
  422. return V_stricmp( (*a)->m_entry.m_chContainerName, (*b)->m_entry.m_chContainerName );
  423. }
  424. void CSaveUtilV2Job_Save::PrepareToc()
  425. {
  426. {
  427. AUTO_LOCK( g_SaveUtilV2TOC.m_mtx );
  428. m_newTOC.m_arrEntries.EnsureCapacity( g_SaveUtilV2TOC.m_arrEntries.Count() + 1 );
  429. g_SaveUtilV2TOC.CopyInto( &m_newTOC );
  430. // Assuming all goes well, make room in the global TOC (need to do this now because we MUST NOT allocate inside the callback!):
  431. g_SaveUtilV2TOC.m_arrEntries.EnsureCapacity( g_SaveUtilV2TOC.m_arrEntries.Count() + 1 );
  432. }
  433. // PrepareToc is running before callback so can use memory allocations
  434. int k = m_newTOC.FindByEmbeddedFileName( V_GetFileName( m_chFile[0] ), NULL );
  435. if ( ( k < 0 ) || ( k >= m_newTOC.m_arrEntries.Count() ) || ( m_numCloudFiles > 0 ) )
  436. {
  437. // AUTOSAVE NOTE: Even if this is an autosave, if there's no other autosave.sav file
  438. // in the container then we can freely write the the new file, since the total number
  439. // of autosaves will be below the threshold
  440. // CLOUD NOTE: All cloud file writes are controlled by cloud sync manager
  441. // when cloud sync manager requests a write it will always be a new file
  442. m_pNewTOCEntry = &m_newTOC.m_arrEntries[ m_newTOC.m_arrEntries.AddToTail() ];
  443. }
  444. else if ( m_numAutoSavesCount <= 0 )
  445. {
  446. // It's a regular save, not autosave, overwrite the file inside container
  447. m_pNewTOCEntry = &m_newTOC.m_arrEntries[ k ];
  448. OverwriteRequest_t owr;
  449. V_strncpy( owr.m_chOverwriteContainerFile, m_pNewTOCEntry->m_entry.m_chContainerName, sizeof( owr.m_chOverwriteContainerFile ) );
  450. m_arrOverwriteRequests.AddToTail( owr );
  451. Msg( "CSaveUtilV2Job_Save will overwrite existing file '%s'\n", m_pNewTOCEntry->m_entry.m_chContainerName );
  452. }
  453. else // AUTOSAVE CASE
  454. {
  455. // It's an autosave and should overwrite the oldest autosave in this case
  456. // other autosaves must be aged and renamed
  457. char const *szSaveFileStringSearch = "autosave";
  458. int numPreserve = MAX( m_numAutoSavesCount, m_numCloudFiles );
  459. CUtlVector< CSaveUtilV2ContainerTOC::TocStorageReserved_t * > arrAutoSaves;
  460. arrAutoSaves.EnsureCapacity( numPreserve + 3 );
  461. for ( int ii = 0; ii < m_newTOC.m_arrEntries.Count(); ++ ii )
  462. {
  463. if ( V_stristr( m_newTOC.m_arrEntries[ii].m_entry.m_chFile[0], szSaveFileStringSearch ) )
  464. arrAutoSaves.AddToTail( &m_newTOC.m_arrEntries[ii] );
  465. }
  466. arrAutoSaves.Sort( TocAutosavesSortFunc );
  467. // Now we have a sorted list of autosaves, first element is oldest, last element is newest
  468. // the list is guaranteed non-empty, otherwise we would be creating new file altogether
  469. // Walk the list backwards and rename the autosaves appropriately
  470. int nAutosaveNameIndex = 1;
  471. for ( int ii = arrAutoSaves.Count(); ii-- > 0; ++ nAutosaveNameIndex )
  472. {
  473. V_snprintf( arrAutoSaves[ii]->m_entry.m_chFile[0], sizeof( arrAutoSaves[ii]->m_entry.m_chFile[0] ),
  474. "%s%02d.ps3.sav", szSaveFileStringSearch, nAutosaveNameIndex );
  475. if ( *arrAutoSaves[ii]->m_entry.m_chFile[1] )
  476. {
  477. V_snprintf( arrAutoSaves[ii]->m_entry.m_chFile[1], sizeof( arrAutoSaves[ii]->m_entry.m_chFile[1] ),
  478. "%s%02d.ps3.tga", szSaveFileStringSearch, nAutosaveNameIndex );
  479. }
  480. }
  481. // Now if the list of autosaves hasn't yet reached the max number, then just create a new file now
  482. if ( arrAutoSaves.Count() <= numPreserve )
  483. {
  484. // Generate filename to make inside container
  485. m_pNewTOCEntry = &m_newTOC.m_arrEntries[ m_newTOC.m_arrEntries.AddToTail() ];
  486. Msg( "CSaveUtilV2Job_Save will create new %s, %d existing %ss renamed\n",
  487. szSaveFileStringSearch, arrAutoSaves.Count(), szSaveFileStringSearch );
  488. }
  489. else
  490. {
  491. // Overwrite the oldest autosave (the TOC entry is updated, and the old container file will be deleted)
  492. m_pNewTOCEntry = arrAutoSaves[0];
  493. OverwriteRequest_t owr;
  494. V_strncpy( owr.m_chOverwriteContainerFile, m_pNewTOCEntry->m_entry.m_chContainerName, sizeof( owr.m_chOverwriteContainerFile ) );
  495. m_arrOverwriteRequests.AddToTail( owr );
  496. Msg( "CSaveUtilV2Job_Save will overwrite oldest %s '%s', %d other existing %ss preserved and renamed\n",
  497. szSaveFileStringSearch, m_pNewTOCEntry->m_entry.m_chContainerName, arrAutoSaves.Count() - 1, szSaveFileStringSearch );
  498. }
  499. }
  500. // Prepare the new/updated TOC entry
  501. Q_memset( m_pNewTOCEntry, 0, sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t ) );
  502. int idxContainerIndex = ++ m_newTOC.m_idxNewSaveName;
  503. V_snprintf( m_pNewTOCEntry->m_entry.m_chContainerName, sizeof( m_pNewTOCEntry->m_entry.m_chContainerName ), "%08X.SAV", idxContainerIndex );
  504. Msg( "CSaveUtilV2Job_Save will create new file '%s'\n", m_pNewTOCEntry->m_entry.m_chContainerName );
  505. }