|
|
//===== Copyright � 1996-2011, Valve Corporation, All rights reserved. ======//
#include "ps3_saveutil_v2.h"
#include "memdbgon.h"
#include <vjobs/jobparams_shared.h>
#include <vjobs/root.h>
#include <ps3/vjobutils.h>
ConVar ps3_saveutil2_compress( "ps3_saveutil2_compress", "1", FCVAR_DEVELOPMENTONLY );
class CCellFsFileDescriptorAutoClose { public: CCellFsFileDescriptorAutoClose( int fd = -1 ) : m_fd( fd ), m_pJobDeflate( NULL ) {} ~CCellFsFileDescriptorAutoClose() { Close(); EndDeflate(); } void Close() { if ( m_fd != -1 ) { cellFsClose( m_fd ); m_fd = -1; } } int m_fd; job_zlibdeflate::JobDescriptor_t * m_pJobDeflate;
void BeginDeflate() { m_pJobDeflate = NewJob128( *g_saveUtilVjobInstance.m_pRoot->m_pJobZlibDeflate ); m_pJobDeflate->header.sizeScratch = ( 32 * 1024 ) / 16 ; job_zlibdeflate::GetJobParams( m_pJobDeflate )->m_nStatus = 2; // status: the job isn't queued yet, so it's considered "done"
}
void EndDeflate() { if( m_pJobDeflate ) { while( !job_zlibdeflate::GetJobParams( m_pJobDeflate )->IsDone() ) { ThreadSleep( 1 ); } DeleteJob( m_pJobDeflate ); m_pJobDeflate = NULL; } } };
class CSaveUtilV2Job_Save : public ISaveUtilV2Job { public: // Job entry point
virtual JobStatus_t DoExecute();
public: // Data passed from main thread
char m_chComment[VALVE_CONTAINER_STRLEN]; char m_chFile[VALVE_CONTAINER_FPARTS][VALVE_CONTAINER_STRLEN]; int m_numAutoSavesCount; int m_numCloudFiles;
protected: // Buffer used for file data
CSaveUtilV2ContainerTOC m_newTOC; CUtlBuffer m_bufFiles;
CSaveUtilV2ContainerTOC::TocStorageReserved_t *m_pNewTOCEntry; struct OverwriteRequest_t { char m_chOverwriteContainerFile[VALVE_CONTAINER_8_3_LEN]; }; CUtlVector< OverwriteRequest_t > m_arrOverwriteRequests; OverwriteRequest_t m_owrDelete;
CCellFsFileDescriptorAutoClose m_fd[VALVE_CONTAINER_FPARTS]; int OpenAndStatFiles(); int LoadAndCompressFileData(); void PrepareToc();
protected: // Stat callback
virtual void DoDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS );
protected: // Write the file and update TOC
void DoDataFile_DeleteOldFile( SONY_SAVEUTIL_FILE_PARAMS ); void DoDataFile_WriteFiles( SONY_SAVEUTIL_FILE_PARAMS ); void DoDataFile_UpdateTOC( SONY_SAVEUTIL_FILE_PARAMS ); };
//////////////////////////////////////////////////////////////////////////
void SaveUtilV2_Write( CPS3SaveRestoreAsyncStatus *pAsync, const char *pSourcepath, const char *pScreenshotPath, const char *pComment ) { if ( !SaveUtilV2_CanStartJob() ) return;
// Start the job
CSaveUtilV2Job_Save *pJob = new CSaveUtilV2Job_Save; V_strncpy( pJob->m_chComment, pComment ? pComment : "", sizeof( pJob->m_chComment ) ); V_strncpy( pJob->m_chFile[0], pSourcepath ? pSourcepath : "", sizeof( pJob->m_chFile[0] ) ); V_strncpy( pJob->m_chFile[1], pScreenshotPath ? pScreenshotPath : "", sizeof( pJob->m_chFile[1] ) ); pJob->m_numAutoSavesCount = 0; pJob->m_numCloudFiles = 0;
SaveUtilV2_EnqueueJob( pAsync, pJob ); }
void SaveUtilV2_WriteAutosave( CPS3SaveRestoreAsyncStatus *pAsync, const char *pSourcepath, // eg "/dev_hdd1/tempsave/autosave.ps3.sav"
const char *pComment, // the comment field for the new autosave.
const unsigned int nMaxNumAutosaves ) { if ( !SaveUtilV2_CanStartJob() ) return;
// Start the job
CSaveUtilV2Job_Save *pJob = new CSaveUtilV2Job_Save; V_strncpy( pJob->m_chComment, pComment ? pComment : "", sizeof( pJob->m_chComment ) ); V_strncpy( pJob->m_chFile[0], pSourcepath ? pSourcepath : "", sizeof( pJob->m_chFile[0] ) ); V_strncpy( pJob->m_chFile[1], "", sizeof( pJob->m_chFile[1] ) ); pJob->m_numAutoSavesCount = nMaxNumAutosaves; pJob->m_numCloudFiles = 0;
SaveUtilV2_EnqueueJob( pAsync, pJob ); }
void SaveUtilV2_WriteCloudFile( CPS3SaveRestoreAsyncStatus *pAsync, const char *pSourcepath, // eg "/dev_hdd1/tempsave/autosave.ps3.sav"
const unsigned int nMaxNumCloudFiles ) { if ( !SaveUtilV2_CanStartJob() ) return;
// Start the job
CSaveUtilV2Job_Save *pJob = new CSaveUtilV2Job_Save; V_strncpy( pJob->m_chComment, "", sizeof( pJob->m_chComment ) ); V_strncpy( pJob->m_chFile[0], pSourcepath ? pSourcepath : "", sizeof( pJob->m_chFile[0] ) ); V_strncpy( pJob->m_chFile[1], "", sizeof( pJob->m_chFile[1] ) ); pJob->m_numAutoSavesCount = 0; pJob->m_numCloudFiles = nMaxNumCloudFiles;
SaveUtilV2_EnqueueJob( pAsync, pJob ); }
//////////////////////////////////////////////////////////////////////////
JobStatus_t CSaveUtilV2Job_Save::DoExecute() { float flTimeStamp = Plat_FloatTime(); Msg( "CSaveUtilV2Job_Save @%.3f\n", flTimeStamp );
// Prepare new TOC and attempt to determine if the operation is write new or overwrite
PrepareToc();
// Fill out the rest of the TOC
if ( OpenAndStatFiles() < 0 ) { for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart ) m_fd[iPart].Close(); return SaveUtilV2_JobDone( CELL_SAVEDATA_ERROR_FAILURE ); }
// Allocate our buffer
int nCapacityRequired = 0; for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart ) nCapacityRequired += m_pNewTOCEntry->m_entry.m_numBytesFile[iPart]; m_bufFiles.EnsureCapacity( CSaveUtilV2ContainerTOC::kStorageCapacity + ( ( ps3_saveutil2_compress.GetBool() && ( m_numCloudFiles <= 0 ) ) ? 2 : 1 ) * nCapacityRequired );
if ( ps3_saveutil2_compress.GetBool() && ( m_numCloudFiles <= 0 ) ) { uint8 *pBaseRawData = ( ( uint8 * ) m_bufFiles.Base() ) + CSaveUtilV2ContainerTOC::kStorageCapacity; uint8 *pBaseOutData = pBaseRawData + nCapacityRequired; for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart ) { if( m_fd[iPart].m_fd == -1 ) continue; m_fd[iPart].BeginDeflate(); uint64_t numBytesActuallyRead = 0, nUncompressedSize = m_pNewTOCEntry->m_entry.m_numBytesFile[iPart]; // Read the file at the end of the buffer
int ret = cellFsRead( m_fd[iPart].m_fd, pBaseRawData, nUncompressedSize, &numBytesActuallyRead ); m_fd[iPart].Close(); if ( ret < 0 || numBytesActuallyRead != nUncompressedSize ) return SaveUtilV2_JobDone( CELL_SAVEDATA_ERROR_FAILURE );
// Compress the file
job_zlibdeflate::JobDescriptor_t * pJobDeflate = m_fd[iPart].m_pJobDeflate; job_zlibdeflate::JobParams_t * pJobParams = job_zlibdeflate::GetJobParams( pJobDeflate ); pJobParams->m_eaInputUncompressedData = pBaseRawData; pJobParams->m_eaOutputCompressedData = pBaseOutData; pJobParams->m_nMaxCompressedOutputSize = nUncompressedSize; pJobParams->m_nUncompressedSize = nUncompressedSize;
pJobParams->m_nStatus = 0; // get ready to push the job
int nError = g_saveUtilVjobInstance.m_pRoot->m_queuePortSound.pushJob( &pJobDeflate->header, sizeof( *pJobDeflate ), 0, CELL_SPURS_JOBQUEUE_FLAG_SYNC_JOB ); if( nError != CELL_OK ) { pJobParams->m_nStatus = 3; // done
pJobParams->m_nError = 1; // error
Warning( "Cannot push zlib job, ERROR 0x%X\n", nError ); }
pBaseRawData += nUncompressedSize; pBaseOutData += nUncompressedSize; } Assert( pBaseOutData <= ((uint8*)m_bufFiles.Base())+m_bufFiles.Size() ); }
// Call saveutil
int retv = cellSaveDataAutoSave2( CELL_SAVEDATA_VERSION_CURRENT, g_pszSaveUtilContainerName, // autosaves report PS3 system dialog-errors
(m_numAutoSavesCount||g_pSaveUtilAsyncStatus->m_bUseSystemDialogs) ? CELL_SAVEDATA_ERRDIALOG_ALWAYS : CELL_SAVEDATA_ERRDIALOG_NONE, &m_SaveDirInfo, csDataStatCallback, csDataFileCallback, SYS_MEMORY_CONTAINER_ID_INVALID, this );
for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart ) m_fd[iPart].EndDeflate();
float flEndTimeStamp = Plat_FloatTime(); Msg( "CSaveUtilV2Job_Save: cellSaveDataAutoSave2 returned %x @%.3f ( total time = %.3f sec )\n", retv, flEndTimeStamp, flEndTimeStamp - flTimeStamp );
// Close the file handles before returning so that we didn't hold the files locked
// in case main thread resumes after job is done and tries to re-use the files
for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart ) m_fd[iPart].Close(); ++ g_SaveUtilV2TOCVersion; return SaveUtilV2_JobDone( retv ); }
void CSaveUtilV2Job_Save::DoDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS ) { Msg( "CSaveUtilV2Job_Save::DoDataStatCallback @%.3f\n", Plat_FloatTime() );
// TODO: investigate how to move delete after the write and maintain
// consistent transactional state of TOC
SetDataFileCallback( &CSaveUtilV2Job_Save::DoDataFile_DeleteOldFile ); cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT; }
void CSaveUtilV2Job_Save::DoDataFile_DeleteOldFile( SONY_SAVEUTIL_FILE_PARAMS ) { if ( !m_arrOverwriteRequests.Count() ) { DoDataFile_WriteFiles( SONY_SAVEUTIL_PARAMS ); return; } m_owrDelete = m_arrOverwriteRequests[m_arrOverwriteRequests.Count() - 1]; m_arrOverwriteRequests.SetCountNonDestructively( m_arrOverwriteRequests.Count() - 1 );
Msg( "CSaveUtilV2Job_Save::DoDataFile_DeleteOldFile @%.3f\n", Plat_FloatTime() );
// Perform the delete
set->fileOperation = CELL_SAVEDATA_FILEOP_DELETE; set->fileBuf = NULL; set->fileSize = set->fileBufSize = 0; set->fileName = m_owrDelete.m_chOverwriteContainerFile; set->fileOffset = 0; set->fileType = CELL_SAVEDATA_FILETYPE_SECUREFILE; memcpy( set->secureFileId, g_pszSaveUtilSecureFileId, CELL_SAVEDATA_SECUREFILEID_SIZE ); set->reserved = NULL;
SetDataFileCallback( &CSaveUtilV2Job_Save::DoDataFile_DeleteOldFile ); cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;
Msg( "CSaveUtilV2Job_Save::DoDataFile_DeleteOldFile will delete '%s'...\n", set->fileName ); }
void CSaveUtilV2Job_Save::DoDataFile_WriteFiles( SONY_SAVEUTIL_FILE_PARAMS ) { Msg( "CSaveUtilV2Job_Save::DoDataFile_WriteFiles @%.3f\n", Plat_FloatTime() );
// Obtain the files data required to be written
if ( LoadAndCompressFileData() < 0 ) { Msg( "ERROR: CSaveUtilV2Job_Save::DoDataFile_WriteFiles failed to load file!\n" ); g_pSaveUtilAsyncStatus->m_nSonyRetValue = CELL_SAVEDATA_ERROR_FAILURE; cbResult->result = CELL_SAVEDATA_CBRESULT_ERR_FAILURE; return; }
// Perform the write
set->fileOperation = CELL_SAVEDATA_FILEOP_WRITE; set->fileBuf = m_bufFiles.Base(); set->fileSize = set->fileBufSize = m_bufFiles.TellPut(); set->fileName = m_pNewTOCEntry->m_entry.m_chContainerName; set->fileOffset = 0; set->fileType = CELL_SAVEDATA_FILETYPE_SECUREFILE; memcpy( set->secureFileId, g_pszSaveUtilSecureFileId, CELL_SAVEDATA_SECUREFILEID_SIZE ); set->reserved = NULL;
// update our TOC after the write succeeds
SetDataFileCallback( &CSaveUtilV2Job_Save::DoDataFile_UpdateTOC ); cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;
Msg( "CSaveUtilV2Job_Save::DoDataFile_WriteFiles will write %d bytes...\n", set->fileSize ); }
void CSaveUtilV2Job_Save::DoDataFile_UpdateTOC( SONY_SAVEUTIL_FILE_PARAMS ) { Msg( "CSaveUtilV2Job_Save::DoDataFile_UpdateTOC @%.3f\n", Plat_FloatTime() );
// Update TOC since we successfully wrote the file
{ AUTO_LOCK( g_SaveUtilV2TOC.m_mtx ); // Memory has been pre-reserved for the larger number of entries
m_newTOC.CopyInto( &g_SaveUtilV2TOC ); }
cbResult->result = CELL_SAVEDATA_CBRESULT_OK_LAST; }
int CSaveUtilV2Job_Save::OpenAndStatFiles() { float flTimeStamp = Plat_FloatTime();
if ( m_numCloudFiles > 0 ) { // Cloud save
int ret = cellFsOpen( m_chFile[0], CELL_FS_O_RDONLY, &m_fd[0].m_fd, NULL, 0 ); if ( ret < 0 ) return ret; CellFsStat cfs; ret = cellFsFstat( m_fd[0].m_fd, &cfs ); if ( ret < 0 ) return ret; if ( cfs.st_size <= sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t ) ) return -1; m_pNewTOCEntry->m_entry.m_numBytesFile[0] = cfs.st_size; } else { // Non-cloud save
Q_strncpy( m_pNewTOCEntry->m_entry.m_chComment, m_chComment, sizeof( m_pNewTOCEntry->m_entry.m_chComment ) ); m_pNewTOCEntry->m_entry.m_timeModification = time( NULL ); for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart ) { if ( !m_chFile[iPart][0] ) continue;
Q_strncpy( m_pNewTOCEntry->m_entry.m_chFile[iPart], V_GetFileName( m_chFile[iPart] ), VALVE_CONTAINER_FILENAME_LEN ); int ret = cellFsOpen( m_chFile[iPart], CELL_FS_O_RDONLY, &m_fd[iPart].m_fd, NULL, 0 ); if ( ret < 0 ) return ret; CellFsStat cfs; ret = cellFsFstat( m_fd[iPart].m_fd, &cfs ); if ( ret < 0 ) return ret; m_pNewTOCEntry->m_entry.m_numBytesFile[iPart] = cfs.st_size; } } float flEndTimeStamp = Plat_FloatTime(); Msg( "CSaveUtilV2Job_Save::OpenAndStatFiles took %.3f sec [ %u + %u bytes ]\n", flEndTimeStamp - flTimeStamp, m_pNewTOCEntry->m_entry.m_numBytesFile[0], m_pNewTOCEntry->m_entry.m_numBytesFile[1] ); return 0; }
int CSaveUtilV2Job_Save::LoadAndCompressFileData() { // Leave room for TOC
m_bufFiles.SeekPut( CUtlBuffer::SEEK_HEAD, CSaveUtilV2ContainerTOC::kStorageCapacity );
float flTimeStamp = Plat_FloatTime(); Assert( m_bufFiles.TellPut() == CSaveUtilV2ContainerTOC::kStorageCapacity ) ; uint8 *pBaseData = ( ( uint8 * ) m_bufFiles.Base() ) + CSaveUtilV2ContainerTOC::kStorageCapacity;
if ( m_numCloudFiles <= 0 ) { for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart ) { if ( ps3_saveutil2_compress.GetBool() ) { job_zlibdeflate::JobDescriptor_t * pJobDeflate = m_fd[iPart].m_pJobDeflate; if( !pJobDeflate ) continue;
job_zlibdeflate::JobParams_t * pJobParams = job_zlibdeflate::GetJobParams( pJobDeflate ); uint nStallCount = 0; while( !pJobParams->IsDone() ) { ThreadSleep( 1 ); ++nStallCount; } uint numCompressedBytes = pJobParams->m_nCompressedSizeOut & 0x7FFFFFFF; Msg( "job_zlibDeflate stalled ~%u ms : %u -> %u KiB\n", nStallCount, pJobParams->m_nUncompressedSize / 1024, ( numCompressedBytes & 0x7FFFFFFF ) / 1024 );
if ( pJobParams->m_nError == 0 && ( pJobParams->m_nCompressedSizeOut & 0x80000000 ) && numCompressedBytes < m_pNewTOCEntry->m_entry.m_numBytesFile[iPart] ) // we actually have deflated data
{ // file compressed successfully
// remove MSB
m_pNewTOCEntry->m_entry.m_numBytesDecompressedFile[iPart] = m_pNewTOCEntry->m_entry.m_numBytesFile[iPart]; m_pNewTOCEntry->m_entry.m_numBytesFile[iPart] = numCompressedBytes; V_memcpy( pBaseData, pJobParams->m_eaOutputCompressedData, numCompressedBytes ); 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] ); } else { // there was an error during compression; use uncompressed data
m_pNewTOCEntry->m_entry.m_numBytesDecompressedFile[iPart] = 0; if( pBaseData != pJobParams->m_eaInputUncompressedData ) { V_memmove( pBaseData, pJobParams->m_eaInputUncompressedData, m_pNewTOCEntry->m_entry.m_numBytesFile[iPart] ); } } pBaseData += m_pNewTOCEntry->m_entry.m_numBytesFile[iPart]; } else { if ( m_fd[iPart].m_fd == -1 ) continue;
uint64 numBytesActuallyRead; int ret = cellFsRead( m_fd[iPart].m_fd, ( ( uint8 * )m_bufFiles.Base() ) + m_bufFiles.TellPut(), m_bufFiles.Size() - m_bufFiles.TellPut(), &numBytesActuallyRead ); m_fd[iPart].Close(); if ( ret < 0 ) return ret;
if ( numBytesActuallyRead != m_pNewTOCEntry->m_entry.m_numBytesFile[iPart] ) return -1;
} m_bufFiles.SeekPut( CUtlBuffer::SEEK_HEAD, m_bufFiles.TellPut() + m_pNewTOCEntry->m_entry.m_numBytesFile[iPart] ); } } else { if ( m_fd[0].m_fd == -1 ) return -1;
uint64 numBytesActuallyRead; m_bufFiles.SeekPut( CUtlBuffer::SEEK_HEAD, CSaveUtilV2ContainerTOC::kStorageCapacity - sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t ) ); char unsigned *pbIncomingFileBase = ( ( char unsigned * ) m_bufFiles.Base() ) + m_bufFiles.TellPut(); int ret = cellFsRead( m_fd[0].m_fd, pbIncomingFileBase, m_bufFiles.Size() - m_bufFiles.TellPut(), &numBytesActuallyRead ); m_fd[0].Close(); if ( ret < 0 ) return ret;
if ( numBytesActuallyRead != m_pNewTOCEntry->m_entry.m_numBytesFile[0] ) return -1;
m_bufFiles.SeekPut( CUtlBuffer::SEEK_HEAD, m_bufFiles.TellPut() + m_pNewTOCEntry->m_entry.m_numBytesFile[0] );
//
// Signature
//
// Version of our save header
CSaveUtilV2ContainerTOC::TocEntry_t *pSignature = (CSaveUtilV2ContainerTOC::TocEntry_t *) pbIncomingFileBase; if ( pSignature->m_chContainerName[0] != 'S' || pSignature->m_chContainerName[1] != 'A' || pSignature->m_chContainerName[2] != 'V' || pSignature->m_chContainerName[3] != '1' ) { Msg( "ERROR: CSaveUtilV2Job_Save : header mismatch, expecting SAV1\n" ); return -2; // header mismatch
}
// Fetch the current hash
uint32 uiFileHashCurrent = 0, uiHash = 0; V_memcpy( &uiFileHashCurrent, ( (char*) pSignature ) + 8 + sizeof( g_uiSteamCloudCryptoKey ) - sizeof( uiHash ), sizeof( uiHash ) );
// Temporarily put our cryptokey in place of hash
V_memcpy( ( (char*) pSignature ) + 8, &g_uiSteamCloudCryptoKey, sizeof( g_uiSteamCloudCryptoKey ) ); uiHash = SaveUtilV2_ComputeBufferHash( pSignature, m_pNewTOCEntry->m_entry.m_numBytesFile[0] ); if ( uiHash != uiFileHashCurrent ) { Msg( "ERROR: CSaveUtilV2Job_Save : signature hash mismatch\n" ); return -3; }
// We only need to preserve container name
char chContainerName[sizeof( m_pNewTOCEntry->m_entry.m_chContainerName )]; V_memcpy( chContainerName, m_pNewTOCEntry->m_entry.m_chContainerName, sizeof( m_pNewTOCEntry->m_entry.m_chContainerName ) ); V_memcpy( m_pNewTOCEntry, pbIncomingFileBase, sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t ) ); V_memcpy( m_pNewTOCEntry->m_entry.m_chContainerName, chContainerName, sizeof( m_pNewTOCEntry->m_entry.m_chContainerName ) ); V_memset( m_pNewTOCEntry->m_entry.m_chFile[0], 0, sizeof( m_pNewTOCEntry->m_entry.m_chFile[0] ) ); 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 ); if ( *m_pNewTOCEntry->m_entry.m_chFile[1] ) 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 ); }
float flEndTimeStamp = Plat_FloatTime(); Msg( "CSaveUtilV2Job_Save::LoadFileToBuffer finished loading%s @%.3f (%.3f sec)\n", ps3_saveutil2_compress.GetBool() ? " and compressing" : "", flEndTimeStamp, flEndTimeStamp - flTimeStamp );
// New TOC is fully ready, serialize it for writing
m_newTOC.SerializeIntoTocBuffer( m_bufFiles.Base() ); return 0; }
static int TocAutosavesSortFunc( CSaveUtilV2ContainerTOC::TocStorageReserved_t * const *a, CSaveUtilV2ContainerTOC::TocStorageReserved_t * const *b ) { if ( (*a)->m_entry.m_timeModification != (*b)->m_entry.m_timeModification ) return ( (*a)->m_entry.m_timeModification < (*b)->m_entry.m_timeModification ) ? -1 : 1; else return V_stricmp( (*a)->m_entry.m_chContainerName, (*b)->m_entry.m_chContainerName ); }
void CSaveUtilV2Job_Save::PrepareToc() { { AUTO_LOCK( g_SaveUtilV2TOC.m_mtx ); m_newTOC.m_arrEntries.EnsureCapacity( g_SaveUtilV2TOC.m_arrEntries.Count() + 1 ); g_SaveUtilV2TOC.CopyInto( &m_newTOC );
// Assuming all goes well, make room in the global TOC (need to do this now because we MUST NOT allocate inside the callback!):
g_SaveUtilV2TOC.m_arrEntries.EnsureCapacity( g_SaveUtilV2TOC.m_arrEntries.Count() + 1 ); }
// PrepareToc is running before callback so can use memory allocations
int k = m_newTOC.FindByEmbeddedFileName( V_GetFileName( m_chFile[0] ), NULL ); if ( ( k < 0 ) || ( k >= m_newTOC.m_arrEntries.Count() ) || ( m_numCloudFiles > 0 ) ) { // AUTOSAVE NOTE: Even if this is an autosave, if there's no other autosave.sav file
// in the container then we can freely write the the new file, since the total number
// of autosaves will be below the threshold
// CLOUD NOTE: All cloud file writes are controlled by cloud sync manager
// when cloud sync manager requests a write it will always be a new file
m_pNewTOCEntry = &m_newTOC.m_arrEntries[ m_newTOC.m_arrEntries.AddToTail() ]; } else if ( m_numAutoSavesCount <= 0 ) { // It's a regular save, not autosave, overwrite the file inside container
m_pNewTOCEntry = &m_newTOC.m_arrEntries[ k ]; OverwriteRequest_t owr; V_strncpy( owr.m_chOverwriteContainerFile, m_pNewTOCEntry->m_entry.m_chContainerName, sizeof( owr.m_chOverwriteContainerFile ) ); m_arrOverwriteRequests.AddToTail( owr ); Msg( "CSaveUtilV2Job_Save will overwrite existing file '%s'\n", m_pNewTOCEntry->m_entry.m_chContainerName ); } else // AUTOSAVE CASE
{ // It's an autosave and should overwrite the oldest autosave in this case
// other autosaves must be aged and renamed
char const *szSaveFileStringSearch = "autosave"; int numPreserve = MAX( m_numAutoSavesCount, m_numCloudFiles ); CUtlVector< CSaveUtilV2ContainerTOC::TocStorageReserved_t * > arrAutoSaves; arrAutoSaves.EnsureCapacity( numPreserve + 3 ); for ( int ii = 0; ii < m_newTOC.m_arrEntries.Count(); ++ ii ) { if ( V_stristr( m_newTOC.m_arrEntries[ii].m_entry.m_chFile[0], szSaveFileStringSearch ) ) arrAutoSaves.AddToTail( &m_newTOC.m_arrEntries[ii] ); } arrAutoSaves.Sort( TocAutosavesSortFunc );
// Now we have a sorted list of autosaves, first element is oldest, last element is newest
// the list is guaranteed non-empty, otherwise we would be creating new file altogether
// Walk the list backwards and rename the autosaves appropriately
int nAutosaveNameIndex = 1; for ( int ii = arrAutoSaves.Count(); ii-- > 0; ++ nAutosaveNameIndex ) { V_snprintf( arrAutoSaves[ii]->m_entry.m_chFile[0], sizeof( arrAutoSaves[ii]->m_entry.m_chFile[0] ), "%s%02d.ps3.sav", szSaveFileStringSearch, nAutosaveNameIndex ); if ( *arrAutoSaves[ii]->m_entry.m_chFile[1] ) { V_snprintf( arrAutoSaves[ii]->m_entry.m_chFile[1], sizeof( arrAutoSaves[ii]->m_entry.m_chFile[1] ), "%s%02d.ps3.tga", szSaveFileStringSearch, nAutosaveNameIndex ); } }
// Now if the list of autosaves hasn't yet reached the max number, then just create a new file now
if ( arrAutoSaves.Count() <= numPreserve ) { // Generate filename to make inside container
m_pNewTOCEntry = &m_newTOC.m_arrEntries[ m_newTOC.m_arrEntries.AddToTail() ]; Msg( "CSaveUtilV2Job_Save will create new %s, %d existing %ss renamed\n", szSaveFileStringSearch, arrAutoSaves.Count(), szSaveFileStringSearch ); } else { // Overwrite the oldest autosave (the TOC entry is updated, and the old container file will be deleted)
m_pNewTOCEntry = arrAutoSaves[0]; OverwriteRequest_t owr; V_strncpy( owr.m_chOverwriteContainerFile, m_pNewTOCEntry->m_entry.m_chContainerName, sizeof( owr.m_chOverwriteContainerFile ) ); m_arrOverwriteRequests.AddToTail( owr ); Msg( "CSaveUtilV2Job_Save will overwrite oldest %s '%s', %d other existing %ss preserved and renamed\n", szSaveFileStringSearch, m_pNewTOCEntry->m_entry.m_chContainerName, arrAutoSaves.Count() - 1, szSaveFileStringSearch ); } }
// Prepare the new/updated TOC entry
Q_memset( m_pNewTOCEntry, 0, sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t ) ); int idxContainerIndex = ++ m_newTOC.m_idxNewSaveName; V_snprintf( m_pNewTOCEntry->m_entry.m_chContainerName, sizeof( m_pNewTOCEntry->m_entry.m_chContainerName ), "%08X.SAV", idxContainerIndex ); Msg( "CSaveUtilV2Job_Save will create new file '%s'\n", m_pNewTOCEntry->m_entry.m_chContainerName ); }
|