//===== Copyright © 1996-2011, Valve Corporation, All rights reserved. ======// #include "ps3_saveutil_v2.h" #include "vgui/ILocalize.h" #include "vstdlib/vstrtools.h" #include "memdbgon.h" ////////////////////////////////////////////////////////////////////////// CPS3SaveRestoreAsyncStatus *g_pSaveUtilAsyncStatus = NULL; IPS3SaveSteamInfoProvider *g_pSteamInfoProvider = NULL; IThreadPool *g_pSaveUtilThreadPool = NULL; CSaveUtilVjobInstance g_saveUtilVjobInstance; static char g_chSaveUtilContainerName[64]; char const *g_pszSaveUtilContainerName = g_chSaveUtilContainerName; CSaveUtilV2ContainerTOC g_SaveUtilV2TOC; uint32 g_SaveUtilV2TOCVersion; extern IVJobs * g_pVJobs; // this is our "SECURE FILE ID" static const char s_SaveUtilSecureFileId[CELL_SAVEDATA_SECUREFILEID_SIZE] = { 0xDD, 0xE3, 0x80, 0x72, 0xC7, 0x9F, 0xAF, 0x2A, 0x2B, 0x68, 0xAF, 0xC9, 0x6D, 0x6A, 0xED, 0xC1 } ; char const *g_pszSaveUtilSecureFileId = s_SaveUtilSecureFileId; uint64 g_uiSteamCloudCryptoKey = 0ull; ASSERT_INVARIANT( sizeof( CSaveUtilV2ContainerTOC::TocEntry_t ) < sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t ) ); ////////////////////////////////////////////////////////////////////////// class CSaveUtilV2Job_Initialize : public ISaveUtilV2Job { public: // Job entry point virtual JobStatus_t DoExecute(); public: // Caller passes data from main thread int m_nKBRequired; protected: // Our buffer for interacting with filesystem CUtlBuffer m_bufScratch; CSaveUtilV2ContainerTOC m_newTOC; CUtlVector< CellSaveDataFileStat > m_arrFilesInContainer; protected: // Stat callback virtual void DoDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS ); bool DoDataStat_ValidateFreeSpace( SONY_SAVEUTIL_STAT_PARAMS ); void DoDataStat_NewContainer( SONY_SAVEUTIL_STAT_PARAMS ); protected: // When Steam file exists we load it first CellSaveDataFileStat m_LoadSteamFileStat; void DoDataFile_LoadSteam( SONY_SAVEUTIL_FILE_PARAMS ); protected: // When container exists we load TOC CellSaveDataFileStat m_LoadTocFileStat; void DoDataFile_LoadToc( SONY_SAVEUTIL_FILE_PARAMS ); void DoDataFile_DisplayToc( SONY_SAVEUTIL_FILE_PARAMS ); protected: // TOC post-processing bool PostProcessToc_DeletedFiles(); bool PostProcessToc_Validate(); protected: // When container is newly created we write initial data void DoDataFile_WriteEmptySteamFile( SONY_SAVEUTIL_FILE_PARAMS ); void DoDataFile_WriteIcon0png( SONY_SAVEUTIL_FILE_PARAMS ); void DoDataFile_WriteIcon1pam( SONY_SAVEUTIL_FILE_PARAMS ); void DoDataFile_WritePic1png( SONY_SAVEUTIL_FILE_PARAMS ); }; ////////////////////////////////////////////////////////////////////////// static void SaveUtilV2_InitAndClearSaveShadow() { char const *pszSaveDir = g_pPS3PathInfo->SaveShadowPath(); g_pFullFileSystem->CreateDirHierarchy( pszSaveDir ); // delete any files that are in the save dir already FileFindHandle_t handle; const char *pfname; char searchpath[255]; V_snprintf( searchpath, sizeof(searchpath), "%s*", pszSaveDir ); char deletepath[255]; const int dirnamelen = V_strlen( pszSaveDir ); Assert(dirnamelen < 255); memcpy(deletepath, pszSaveDir, dirnamelen); for ( pfname = g_pFullFileSystem->FindFirst( searchpath, &handle ) ; pfname ; pfname = g_pFullFileSystem->FindNext( handle ) ) { V_strncpy( deletepath + dirnamelen, pfname, sizeof(deletepath) - dirnamelen ); Msg( "Removing %s\n", deletepath ); g_pFullFileSystem->RemoveFile( deletepath, NULL ); } } void SaveUtilV2_Initialize( CPS3SaveRestoreAsyncStatus *pAsync, IPS3SaveSteamInfoProvider *pSteamInfoProvider, int nKBRequired ) { if ( g_pSaveUtilThreadPool ) return; // Initialize SPU jobs instance g_saveUtilVjobInstance.Init(); // Indicate that we are running V2 V_snprintf( g_chSaveUtilContainerName, 32, "%s-%s", g_pPS3PathInfo->GetWWMASTER_TitleID(), "PORTAL2-AUTOSAVE3" ); SaveUtilV2_InitAndClearSaveShadow(); // First of all start our thread pool ThreadPoolStartParams_t params; params.nThreads = 1; params.nStackSize = 64*1024; params.fDistribute = TRS_FALSE; g_pSaveUtilThreadPool = CreateNewThreadPool(); g_pSaveUtilThreadPool->Start( params, "SaveUtilV2" ); // Remember launch parameters g_pSteamInfoProvider = pSteamInfoProvider; // Start the job CSaveUtilV2Job_Initialize *pJob = new CSaveUtilV2Job_Initialize; pJob->m_nKBRequired = nKBRequired; SaveUtilV2_EnqueueJob( pAsync, pJob ); } void SaveUtilV2_Shutdown() { if ( !g_pSaveUtilThreadPool ) return; g_pSaveUtilThreadPool->Stop(); DestroyThreadPool( g_pSaveUtilThreadPool ); g_pSaveUtilThreadPool = NULL; // Shutdown jobs instance after thread pool has been // stopped which ensures that jobs were finished and released g_saveUtilVjobInstance.Shutdown(); } bool SaveUtilV2_CanShutdown() { if ( !g_pSaveUtilThreadPool ) return true; if ( g_pSaveUtilAsyncStatus ) // job in progress return false; return true; } ////////////////////////////////////////////////////////////////////////// JobStatus_t CSaveUtilV2Job_Initialize::DoExecute() { float flTimeStamp = Plat_FloatTime(); Msg( "CSaveUtilV2Job_Initialize( %d KB ) @%.3f\n", m_nKBRequired, flTimeStamp ); // Prepare data for calling saveutil (we cannot allocate memory in the callbacks!) m_bufScratch.EnsureCapacity( 5 * 1024 * 1024 ); // we always have 5 MB on startup m_newTOC.m_arrEntries.EnsureCapacity( VALVE_CONTAINER_COUNT ); m_arrFilesInContainer.EnsureCapacity( VALVE_CONTAINER_COUNT ); // Call saveutil int retv = cellSaveDataAutoSave2( CELL_SAVEDATA_VERSION_CURRENT, g_pszSaveUtilContainerName, CELL_SAVEDATA_ERRDIALOG_NONE, &m_SaveDirInfo, csDataStatCallback, csDataFileCallback, SYS_MEMORY_CONTAINER_ID_INVALID, this ); // Set the global TOC as loaded { AUTO_LOCK( g_SaveUtilV2TOC.m_mtx ); m_newTOC.CopyInto( &g_SaveUtilV2TOC ); } // Job finished float flEndTimeStamp = Plat_FloatTime(); Msg( "CSaveUtilV2Job_Initialize: cellSaveDataAutoSave2 returned %x @%.3f ( total time = %.3f sec )\n", retv, flEndTimeStamp, flEndTimeStamp - flTimeStamp ); return SaveUtilV2_JobDone( retv ); } void CSaveUtilV2Job_Initialize::DoDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS ) { Msg( "CSaveUtilV2Job_Initialize::DoDataStatCallback @%.3f\n", Plat_FloatTime() ); Msg("\tnKBRequired %d\n", m_nKBRequired ); Msg("\tisNewData %d\n", get->isNewData ); Msg("\thddFreeSizeKB %d\n", get->hddFreeSizeKB); Msg("\tsizeKB %d\n", get->sizeKB); Msg("\tsysSizeKB %d\n", get->sysSizeKB); Msg("\tbind %d\n", get->bind); bool bCreateNew = false; // do we need to create a new container? if ( get->isNewData || get->bind != CELL_SAVEDATA_BINDSTAT_OK ) { if ( get->isNewData ) { // if the data is just absent, we can always make a new container. bCreateNew = true; } else if ( ( get->bind & ( CELL_SAVEDATA_BINDSTAT_ERR_NOACCOUNTID | CELL_SAVEDATA_BINDSTAT_ERR_LOCALOWNER ) ) == get->bind ) { // this is actually owned by the current user; since the account // id will added on the next write, we can safely keep going. get->bind = 0; } else { // this is an ownership error set->reCreateMode = CELL_SAVEDATA_RECREATE_NO; g_pSaveUtilAsyncStatus->m_nSonyRetValue = CPS3SaveRestoreAsyncStatus::CELL_SAVEDATA_ERROR_WRONG_USER; cbResult->result = CELL_SAVEDATA_CBRESULT_ERR_INVALID; return; } } set->setParam = NULL; // don't edit the PARAM.SFO // Validate free space if ( !DoDataStat_ValidateFreeSpace( SONY_SAVEUTIL_PARAMS ) ) return; // if there's a toc, read it. if ( !get->isNewData ) { V_memset( &m_LoadSteamFileStat, 0, sizeof( m_LoadSteamFileStat ) ); V_memset( &m_LoadTocFileStat, 0, sizeof( m_LoadTocFileStat ) ); for ( int i = 0 ; i < get->fileListNum ; ++i ) { // Icons and video have special filetypes, we are interested only in our data // our data has file type "securefile" if ( get->fileList[i].fileType != CELL_SAVEDATA_FILETYPE_SECUREFILE ) continue; // Steam configuration file if ( !V_strcmp( get->fileList[i].fileName, VALVE_CONTAINER_FILE_STEAM ) ) { Msg( "CSaveUtilV2Job_Initialize found Steam file: %u bytes\n", get->fileList[i].st_size ); V_memcpy( &m_LoadSteamFileStat, &get->fileList[i], sizeof( m_LoadSteamFileStat ) ); continue; } // Every file will have full TOC prepended, makes no sense // to have files in container that are smaller than required size if ( get->fileList[i].st_size < CSaveUtilV2ContainerTOC::kStorageCapacity ) continue; // Remember the file as present in container m_arrFilesInContainer.AddToTail( get->fileList[i] ); // Add this file for TOC discovery (the latest TOC will be at the start of the file with the alphanumeric-highest filename) Msg( "CSaveUtilV2Job_Initialize found file '%s', %u bytes\n", get->fileList[i].fileName, get->fileList[i].st_size ); if ( !m_LoadTocFileStat.fileName[0] || // TOC filename not set yet ( V_strcmp( get->fileList[i].fileName, m_LoadTocFileStat.fileName ) > 0 ) ) // or bigger filename ( -> more recent file) { V_memcpy( &m_LoadTocFileStat, &get->fileList[i], sizeof( m_LoadTocFileStat ) ); } } SetDataFileCallback( &CSaveUtilV2Job_Initialize::DoDataFile_LoadSteam ); cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT; return; } else if ( bCreateNew ) { // Create a new container DoDataStat_NewContainer( SONY_SAVEUTIL_PARAMS ); SetDataFileCallback( &CSaveUtilV2Job_Initialize::DoDataFile_WriteEmptySteamFile ); cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT; return; } // this means "okay, I'm done" cbResult->result = CELL_SAVEDATA_CBRESULT_OK_LAST; } bool CSaveUtilV2Job_Initialize::DoDataStat_ValidateFreeSpace( SONY_SAVEUTIL_STAT_PARAMS ) { // do we have enough space? // system overhead + caller max size ? HDD free space + current container file int numKbRequired = ( get->sysSizeKB + m_nKBRequired ) - ( get->hddFreeSizeKB + get->sizeKB ); if ( numKbRequired > 0 ) { // inadequate space. cbResult->result = CELL_SAVEDATA_CBRESULT_ERR_NOSPACE; g_pSaveUtilAsyncStatus->m_nSonyRetValue = CELL_SAVEDATA_ERROR_NOSPACE; cbResult->errNeedSizeKB = numKbRequired; g_pSaveUtilAsyncStatus->m_uiAdditionalDetails = numKbRequired; return false; } return true; } ////////////////////////////////////////////////////////////////////////// void CSaveUtilV2Job_Initialize::DoDataStat_NewContainer( SONY_SAVEUTIL_STAT_PARAMS ) { set->reCreateMode = CELL_SAVEDATA_RECREATE_YES_RESET_OWNER; get->bind = 0; // the convention for editing param.sfo is pointing at the struct in the get param, and then editing that. (jeez...) set->setParam = &get->getParam; /* Set parameters of PARAM.SFO */ V_strncpy( set->setParam->title, g_pPS3PathInfo->GetParamSFO_Title(), CELL_SAVEDATA_SYSP_TITLE_SIZE ); // the save game title wchar_t *szLocalizedSaveDataTitle = g_pLocalize->Find( "#CSGOPS3_SaveData" ); if ( szLocalizedSaveDataTitle ) { V_UnicodeToUTF8( szLocalizedSaveDataTitle, set->setParam->subTitle, CELL_SAVEDATA_SYSP_SUBTITLE_SIZE ); } else // failsafe { V_strncpy( set->setParam->subTitle, "Counter Strike: Global Offensive" , CELL_SAVEDATA_SYSP_SUBTITLE_SIZE ); } // the save game caption -- if missing, we just use an empty string here. szLocalizedSaveDataTitle = g_pLocalize->Find( "#CSGOPS3_SaveDetail" ); if ( szLocalizedSaveDataTitle ) { V_UnicodeToUTF8( szLocalizedSaveDataTitle, set->setParam->detail, CELL_SAVEDATA_SYSP_DETAIL_SIZE ); } else { memset( set->setParam->detail, 0, CELL_SAVEDATA_SYSP_DETAIL_SIZE ); } set->setParam->attribute = CELL_SAVEDATA_ATTR_NORMAL; // listparam is available to the application inside stat callback Q_memset( set->setParam->listParam, 0, CELL_SAVEDATA_SYSP_LPARAM_SIZE ); memset( set->setParam->reserved, 0x0, sizeof(set->setParam->reserved) ); /* The reserved member must be zero-filled */ memset( set->setParam->reserved2, 0x0, sizeof(set->setParam->reserved2) ); /* The reserved member must be zero-filled */ } void CSaveUtilV2Job_Initialize::DoDataFile_WriteEmptySteamFile( SONY_SAVEUTIL_FILE_PARAMS ) { Msg( "CSaveUtilV2Job_Initialize::DoDataFile_WriteEmptySteamFile @%.3f\n", Plat_FloatTime() ); const static char szEmptyToc[] = "EMPTY"; set->fileOperation = CELL_SAVEDATA_FILEOP_WRITE; set->fileBuf = const_cast(szEmptyToc); set->fileSize = set->fileBufSize = sizeof(szEmptyToc); set->fileName = VALVE_CONTAINER_FILE_STEAM; set->fileOffset = 0; set->fileType = CELL_SAVEDATA_FILETYPE_SECUREFILE; memcpy( set->secureFileId, g_pszSaveUtilSecureFileId, CELL_SAVEDATA_SECUREFILEID_SIZE ); set->reserved = NULL; // call back again to write icon SetDataFileCallback( &CSaveUtilV2Job_Initialize::DoDataFile_WriteIcon0png ); cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT; Msg( "CSaveUtilV2Job_Initialize::DoDataFile_WriteEmptySteamFile will write %d bytes...\n", set->fileSize ); } void CSaveUtilV2Job_Initialize::DoDataFile_WriteIcon0png( SONY_SAVEUTIL_FILE_PARAMS ) { Msg( "CSaveUtilV2Job_Initialize::DoDataFile_WriteIcon0png @%.3f\n", Plat_FloatTime() ); // Load ICON0.PS3.PNG m_bufScratch.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); if ( !g_pFullFileSystem->ReadFile( "ps3/ICON0.PS3.PNG", "GAME", m_bufScratch ) ) { DoDataFile_WriteIcon1pam( SONY_SAVEUTIL_PARAMS ); return; } // Write it set->fileOperation = CELL_SAVEDATA_FILEOP_WRITE; set->fileBuf = m_bufScratch.Base(); set->fileSize = set->fileBufSize = m_bufScratch.TellPut(); set->fileOffset = 0; set->fileType = CELL_SAVEDATA_FILETYPE_CONTENT_ICON0; set->reserved = NULL; // call back again to write icon SetDataFileCallback( &CSaveUtilV2Job_Initialize::DoDataFile_WriteIcon1pam ); cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT; Msg( "CSaveUtilV2Job_Initialize::DoDataFile_WriteIcon0png will write %d bytes...\n", set->fileSize ); } void CSaveUtilV2Job_Initialize::DoDataFile_WriteIcon1pam( SONY_SAVEUTIL_FILE_PARAMS ) { Msg( "CSaveUtilV2Job_Initialize::DoDataFile_WriteIcon1pam @%.3f\n", Plat_FloatTime() ); // Load ICON1.PS3.PAM m_bufScratch.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); #if defined( CSTRIKE15 ) // TODO($msmith): Remove this and put the PAM back in for CS:GO once it has been updated. if ( !g_pFullFileSystem->ReadFile( "ps3/not_found.PAM", "GAME", m_bufScratch ) ) #else if ( !g_pFullFileSystem->ReadFile( "ps3/ICON1.PS3.PAM", "GAME", m_bufScratch ) ) #endif { DoDataFile_WritePic1png( SONY_SAVEUTIL_PARAMS ); return; } // Write it set->fileOperation = CELL_SAVEDATA_FILEOP_WRITE; set->fileBuf = m_bufScratch.Base(); set->fileSize = set->fileBufSize = m_bufScratch.TellPut(); set->fileOffset = 0; set->fileType = CELL_SAVEDATA_FILETYPE_CONTENT_ICON1; set->reserved = NULL; // call back again to write pic1 SetDataFileCallback( &CSaveUtilV2Job_Initialize::DoDataFile_WritePic1png ); cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT; Msg( "CSaveUtilV2Job_Initialize::DoDataFile_WriteIcon1pam will write %d bytes...\n", set->fileSize ); } void CSaveUtilV2Job_Initialize::DoDataFile_WritePic1png( SONY_SAVEUTIL_FILE_PARAMS ) { Msg( "CSaveUtilV2Job_Initialize::DoDataFile_WritePic1png @%.3f\n", Plat_FloatTime() ); // Load PIC1.PS3.PNG m_bufScratch.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); if ( !g_pFullFileSystem->ReadFile( "ps3/PIC1.PS3.PNG", "GAME", m_bufScratch ) ) { cbResult->result = CELL_SAVEDATA_CBRESULT_OK_LAST; return; } // Write it set->fileOperation = CELL_SAVEDATA_FILEOP_WRITE; set->fileBuf = m_bufScratch.Base(); set->fileSize = set->fileBufSize = m_bufScratch.TellPut(); set->fileOffset = 0; set->fileType = CELL_SAVEDATA_FILETYPE_CONTENT_PIC1; set->reserved = NULL; // final write SetDataFileCallbackFinalize(); cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT; Msg( "CSaveUtilV2Job_Initialize::DoDataFile_WritePic1png will write %d bytes...\n", set->fileSize ); } ////////////////////////////////////////////////////////////////////////// void CSaveUtilV2Job_Initialize::DoDataFile_LoadSteam( SONY_SAVEUTIL_FILE_PARAMS ) { Msg( "CSaveUtilV2Job_Initialize::DoDataFile_LoadSteam @%.3f\n", Plat_FloatTime() ); // Check that Steam file is present if ( m_LoadSteamFileStat.st_size < 16 ) { DoDataFile_LoadToc( SONY_SAVEUTIL_PARAMS ); return; } // Read into Steam buffer CUtlBuffer *pBuffer = g_pSteamInfoProvider->GetInitialLoadBuffer(); if ( !pBuffer || ( pBuffer->Size() < m_LoadSteamFileStat.st_size ) ) { Warning( "ERROR: CSaveUtilV2Job_Initialize::DoDataFile_LoadSteam: cannot load Steam config (size %llu bytes)!\n", m_LoadSteamFileStat.st_size ); DoDataFile_LoadToc( SONY_SAVEUTIL_PARAMS ); return; } // Read Steam info file set->fileOperation = CELL_SAVEDATA_FILEOP_READ; set->fileBuf = pBuffer->Base(); set->fileSize = m_LoadSteamFileStat.st_size; set->fileBufSize = pBuffer->Size(); set->fileName = VALVE_CONTAINER_FILE_STEAM; set->fileOffset = 0; set->fileType = CELL_SAVEDATA_FILETYPE_SECUREFILE; memcpy( set->secureFileId, g_pszSaveUtilSecureFileId, CELL_SAVEDATA_SECUREFILEID_SIZE ); set->reserved = NULL; // Set the buffer size pBuffer->SeekPut( CUtlBuffer::SEEK_HEAD, set->fileSize ); // final read SetDataFileCallback( &CSaveUtilV2Job_Initialize::DoDataFile_LoadToc ); cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT; Msg( "CSaveUtilV2Job_Initialize::DoDataFile_LoadSteam will load %d bytes...\n", set->fileSize ); } void CSaveUtilV2Job_Initialize::DoDataFile_LoadToc( SONY_SAVEUTIL_FILE_PARAMS ) { Msg( "CSaveUtilV2Job_Initialize::DoDataFile_LoadToc @%.3f\n", Plat_FloatTime() ); if ( m_LoadTocFileStat.st_size < CSaveUtilV2ContainerTOC::kStorageCapacity ) { Msg( "CSaveUtilV2Job_Initialize::DoDataFile_LoadToc -- no TOC available.\n" ); cbResult->result = CELL_SAVEDATA_CBRESULT_OK_LAST; return; } // Load the TOC entry set->fileOperation = CELL_SAVEDATA_FILEOP_READ; set->fileBuf = m_bufScratch.Base(); set->fileBufSize = set->fileSize = CSaveUtilV2ContainerTOC::kStorageCapacity; set->fileName = m_LoadTocFileStat.fileName; set->fileOffset = 0; set->fileType = CELL_SAVEDATA_FILETYPE_SECUREFILE; memcpy( set->secureFileId, g_pszSaveUtilSecureFileId, CELL_SAVEDATA_SECUREFILEID_SIZE ); set->reserved = NULL; m_bufScratch.SeekPut( CUtlBuffer::SEEK_HEAD, CSaveUtilV2ContainerTOC::kStorageCapacity ); // keep reading SetDataFileCallback( &CSaveUtilV2Job_Initialize::DoDataFile_DisplayToc ); cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT; Msg( "CSaveUtilV2Job_Initialize::DoDataFile_LoadToc will load %u bytes of TOC from '%s'...\n", set->fileSize, set->fileName ); } void CSaveUtilV2Job_Initialize::DoDataFile_DisplayToc( SONY_SAVEUTIL_FILE_PARAMS ) { Msg( "CSaveUtilV2Job_Initialize::DoDataFile_DisplayToc @%.3f\n", Plat_FloatTime() ); m_newTOC.SerializeFromTocBuffer( m_bufScratch.Base() ); bool bTocValidationResult = PostProcessToc_DeletedFiles(); for ( int k = 0; k < m_newTOC.m_arrEntries.Count(); ++ k ) { Msg( " %s %s %d/%d %s\n", m_newTOC.m_arrEntries[k].m_entry.m_chContainerName, m_newTOC.m_arrEntries[k].m_entry.m_chFile[0], m_newTOC.m_arrEntries[k].m_entry.m_numBytesFile[0], m_newTOC.m_arrEntries[k].m_entry.m_numBytesDecompressedFile[0], m_newTOC.m_arrEntries[k].m_entry.m_chComment ); } Msg( " new save game will have index %u\n", m_newTOC.m_idxNewSaveName + 1 ); Msg( " END OF TOC REPORT\n" ); if ( !PostProcessToc_Validate() || !bTocValidationResult ) { // TOC is not matching contents of the container! Warning( " ERROR: TOC DOES NOT MATCH SAVE CONTAINER!\n" ); g_pSaveUtilAsyncStatus->m_nSonyRetValue = CELL_SAVEDATA_CBRESULT_ERR_BROKEN; cbResult->result = CELL_SAVEDATA_CBRESULT_ERR_BROKEN; } else { cbResult->result = CELL_SAVEDATA_CBRESULT_OK_LAST; } } bool CSaveUtilV2Job_Initialize::PostProcessToc_DeletedFiles() { // Iterate over TOC and remove entries that were deleted from container previously bool bTocAndContainerValid = true; for ( int k = m_newTOC.m_arrEntries.Count(); k --> 0; ) { bool bFoundInContainer = false; int jc = 0; for ( ; jc < m_arrFilesInContainer.Count(); ++ jc ) { if ( !V_strcmp( m_newTOC.m_arrEntries[k].m_entry.m_chContainerName, m_arrFilesInContainer[jc].fileName ) ) { bFoundInContainer = true; break; } } if ( !bFoundInContainer ) { Msg( "CSaveUtilV2Job_Initialize::PostProcessToc_DeletedFiles discovered deleted file '%s'\n", m_newTOC.m_arrEntries[k].m_entry.m_chContainerName ); m_newTOC.m_arrEntries.Remove( k ); } else { // Make sure the TOC information matches container stat information int nExpectedSize = CSaveUtilV2ContainerTOC::kStorageCapacity; for ( int ipart = 0; ipart < VALVE_CONTAINER_FPARTS; ++ ipart ) nExpectedSize += m_newTOC.m_arrEntries[k].m_entry.m_numBytesFile[ipart]; if ( m_arrFilesInContainer[jc].st_size != nExpectedSize ) { Msg( "CSaveUtilV2Job_Initialize::PostProcessToc_DeletedFiles discovered size inconsistency in '%s' (TOC size = %d; Container size = %llu)\n", m_newTOC.m_arrEntries[k].m_entry.m_chContainerName, nExpectedSize, m_arrFilesInContainer[jc].st_size ); bTocAndContainerValid = false; } } } return bTocAndContainerValid; } bool CSaveUtilV2Job_Initialize::PostProcessToc_Validate() { // Iterate over TOC and make sure every container file is in TOC for ( int jc = m_arrFilesInContainer.Count(); jc --> 0; ) { bool bFoundInTOC = false; for ( int k = 0; k < m_newTOC.m_arrEntries.Count(); ++ k ) { if ( !V_strcmp( m_newTOC.m_arrEntries[k].m_entry.m_chContainerName, m_arrFilesInContainer[jc].fileName ) ) { bFoundInTOC = true; break; } } if ( bFoundInTOC ) { m_arrFilesInContainer.Remove( jc ); } else { Msg( "CSaveUtilV2Job_Initialize::PostProcessToc_Validate discovered extra file '%s'\n", m_arrFilesInContainer[jc].fileName ); } } // We shouldn't have any extra unaccounted files in the container return ( !m_arrFilesInContainer.Count() ); } void CSaveUtilVjobInstance::Init() { g_pVJobs->Register( this ); } void CSaveUtilVjobInstance::Shutdown() { g_pVJobs->Unregister( this ); }