This commit is contained in:
nephacks
2025-06-04 03:22:50 +02:00
parent f234f23848
commit f12416cffd
14243 changed files with 6446499 additions and 26 deletions

View File

@@ -0,0 +1,149 @@
//===== Copyright © 1996-2011, Valve Corporation, All rights reserved. ======//
#ifndef PS3_SAVEUTIL_V2_H
#define PS3_SAVEUTIL_V2_H
//////////////////////////////////////////////////////////////////////////
#include "tier1/utlbuffer.h"
#include "common.h"
#include "vstdlib/jobthread.h"
#include <sysutil/sysutil_userinfo.h>
#include <sysutil/sysutil_savedata.h>
#include <cell/sysmodule.h>
#include <sys/fs_external.h>
#include <ps3/saverestore_ps3_api_ui.h>
#include <vjobs_interface.h>
//////////////////////////////////////////////////////////////////////////
extern CPS3SaveRestoreAsyncStatus *g_pSaveUtilAsyncStatus;
extern IPS3SaveSteamInfoProvider *g_pSteamInfoProvider;
extern IThreadPool *g_pSaveUtilThreadPool;
extern char const *g_pszSaveUtilContainerName;
extern char const *g_pszSaveUtilSecureFileId;
extern uint64 g_uiSteamCloudCryptoKey;
#define VALVE_CONTAINER_FILE_STEAM "STEAMDAT.BIN"
#define VALVE_CONTAINER_STRLEN 128
#define VALVE_CONTAINER_FILENAME_LEN 64
#define VALVE_CONTAINER_8_3_LEN 16
#define VALVE_CONTAINER_COUNT 64
#define VALVE_CONTAINER_FPARTS 2
void SaveUtilV2_Initialize( CPS3SaveRestoreAsyncStatus *pAsync, IPS3SaveSteamInfoProvider *pSteamInfoProvider, int nKBRequired );
void SaveUtilV2_Shutdown();
bool SaveUtilV2_CanShutdown();
void SaveUtilV2_GetFileInfoSync( CUtlVector< IPS3SaveRestoreToUI::PS3SaveGameInfo_t > &saveGameInfos, bool bFindAll );
void SaveUtilV2_Write( CPS3SaveRestoreAsyncStatus *pAsync, const char *pSourcepath, const char *pScreenshotPath, const char *pComment );
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 );
void SaveUtilV2_WriteCloudFile( CPS3SaveRestoreAsyncStatus *pAsync,
const char *pSourcePath, // eg "/dev_hdd1/tempsave/autosave.ps3.sav"
const unsigned int nMaxNumCloudFiles );
void SaveUtilV2_WriteSteamInfo( CPS3SaveRestoreAsyncStatus *pAsync );
void SaveUtilV2_Load( CPS3SaveRestoreAsyncStatus *pAsync, const char *pFilename, const char *pDestFullPath );
void SaveUtilV2_Delete( CPS3SaveRestoreAsyncStatus *pAsync, const char *pFilename );
//////////////////////////////////////////////////////////////////////////
//
// Helper definitions and declarations
//
#define SONY_SAVEUTIL_STAT_PARAMS CellSaveDataCBResult *cbResult, CellSaveDataStatGet *get, CellSaveDataStatSet *set
#define SONY_SAVEUTIL_FILE_PARAMS CellSaveDataCBResult *cbResult, CellSaveDataFileGet *get, CellSaveDataFileSet *set
#define SONY_SAVEUTIL_PARAMS cbResult, get, set
class ISaveUtilV2Job : public CJob
{
public:
ISaveUtilV2Job() { m_pfnDoDataFileCallback = 0; }
public:
static void csDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS );
static void csDataFileCallback( SONY_SAVEUTIL_FILE_PARAMS );
public:
virtual void DoDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS ) = 0;
void (ISaveUtilV2Job::*m_pfnDoDataFileCallback)( SONY_SAVEUTIL_FILE_PARAMS );
template< typename T > inline void SetDataFileCallback( void (T::*pfnCallback)( SONY_SAVEUTIL_FILE_PARAMS ) ) { m_pfnDoDataFileCallback = reinterpret_cast< void (ISaveUtilV2Job::*)( SONY_SAVEUTIL_FILE_PARAMS ) >( pfnCallback ); }
inline void SetDataFileCallbackFinalize() { m_pfnDoDataFileCallback = 0; }
public:
CellSaveDataSetBuf m_SaveDirInfo;
CUtlBuffer m_bufSaveDirList;
};
bool SaveUtilV2_CanStartJob();
void SaveUtilV2_EnqueueJob( CPS3SaveRestoreAsyncStatus *pAsync, ISaveUtilV2Job *pJob );
JobStatus_t SaveUtilV2_JobDone( int nErrorCode );
uint32 SaveUtilV2_ComputeBufferHash( void const *pvData, uint32 numBytes );
//////////////////////////////////////////////////////////////////////////
//
// Container TOC
//
class CSaveUtilV2ContainerTOC
{
public:
CSaveUtilV2ContainerTOC() : m_idxNewSaveName( 0 ) {}
struct TocEntry_t
{
char m_chContainerName[VALVE_CONTAINER_8_3_LEN]; // 8.3 name inside container: 0000001A.SAV
char m_chComment[VALVE_CONTAINER_STRLEN]; // description of file without needing to open file
char m_chFile[VALVE_CONTAINER_FPARTS][VALVE_CONTAINER_FILENAME_LEN]; // names of the contained parts [sav+tga]
uint32 m_numBytesFile[VALVE_CONTAINER_FPARTS]; // sizes of the parts inside container file [sav+tga]
uint32 m_numBytesDecompressedFile[VALVE_CONTAINER_FPARTS]; // sizes of original decompressed parts [sav+tga], zero if uncompressed
time_t m_timeModification; // timestamp of the container file
};
union TocStorageReserved_t
{
TocEntry_t m_entry;
char m_chPadding[384];
};
CUtlVector< TocStorageReserved_t > m_arrEntries;
CThreadFastMutex m_mtx;
uint32 m_idxNewSaveName;
enum Capacity_t
{
kStorageCapacity =
sizeof(uint32) + // new save name
sizeof(uint32) + // number of valid entries
VALVE_CONTAINER_COUNT*sizeof(TocStorageReserved_t) // actual TOC entries
};
public:
void SerializeIntoTocBuffer( void *pvBuffer );
void SerializeFromTocBuffer( void *pvBuffer );
void CopyInto( CSaveUtilV2ContainerTOC *pOther );
int FindByEmbeddedFileName( char const *szFilename, int *pnPartIndex );
};
extern CSaveUtilV2ContainerTOC g_SaveUtilV2TOC;
extern uint32 g_SaveUtilV2TOCVersion;
class CSaveUtilVjobInstance : public VJobInstance
{
public:
void Init();
void Shutdown();
};
extern CSaveUtilVjobInstance g_saveUtilVjobInstance;
#endif

View File

@@ -0,0 +1,117 @@
//===== Copyright © 1996-2011, Valve Corporation, All rights reserved. ======//
#include "ps3_saveutil_v2.h"
#include "memdbgon.h"
class CSaveUtilV2Job_Delete : public ISaveUtilV2Job
{
public: // Job entry point
virtual JobStatus_t DoExecute();
public: // Data resolved from the main thread
CSaveUtilV2ContainerTOC::TocEntry_t *m_pTocEntry;
int m_nTocEntryIndex;
protected: // Stat callback
virtual void DoDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS );
protected: // Delete the file and update TOC
void DoDataFile_Delete( SONY_SAVEUTIL_FILE_PARAMS );
void DoDataFile_UpdateTOC( SONY_SAVEUTIL_FILE_PARAMS );
};
//////////////////////////////////////////////////////////////////////////
void SaveUtilV2_Delete( CPS3SaveRestoreAsyncStatus *pAsync, const char *pFilename )
{
if ( !SaveUtilV2_CanStartJob() )
return;
// Find the file that the caller wants
int k = g_SaveUtilV2TOC.FindByEmbeddedFileName( pFilename, NULL );
if ( ( k < 0 ) || ( k >= g_SaveUtilV2TOC.m_arrEntries.Count() ) )
{
pAsync->m_nSonyRetValue = CELL_SAVEDATA_ERROR_FAILURE;
pAsync->m_bDone = 1;
Warning( "ERROR: SaveUtilV2_Delete: attempted to delete file '%s' which doesn't exist in container!\n", pFilename );
return;
}
// Start the job
CSaveUtilV2Job_Delete *pJob = new CSaveUtilV2Job_Delete;
// It is safe to hold this pointer into the TOC for the duration of this job
// Only jobs update TOC and this is the current job, main thread accesses TOC for read only
pJob->m_pTocEntry = &g_SaveUtilV2TOC.m_arrEntries[k].m_entry;
pJob->m_nTocEntryIndex = k;
SaveUtilV2_EnqueueJob( pAsync, pJob );
}
//////////////////////////////////////////////////////////////////////////
JobStatus_t CSaveUtilV2Job_Delete::DoExecute()
{
float flTimeStamp = Plat_FloatTime();
Msg( "CSaveUtilV2Job_Delete @%.3f\n", flTimeStamp );
// Call saveutil
int retv = cellSaveDataAutoSave2(
CELL_SAVEDATA_VERSION_CURRENT,
g_pszSaveUtilContainerName,
g_pSaveUtilAsyncStatus->m_bUseSystemDialogs ? CELL_SAVEDATA_ERRDIALOG_ALWAYS : CELL_SAVEDATA_ERRDIALOG_NONE,
&m_SaveDirInfo,
csDataStatCallback,
csDataFileCallback,
SYS_MEMORY_CONTAINER_ID_INVALID,
this );
float flEndTimeStamp = Plat_FloatTime();
Msg( "CSaveUtilV2Job_Delete: cellSaveDataAutoSave2 returned %x @%.3f ( total time = %.3f sec )\n", retv, flEndTimeStamp, flEndTimeStamp - flTimeStamp );
++ g_SaveUtilV2TOCVersion;
return SaveUtilV2_JobDone( retv );
}
void CSaveUtilV2Job_Delete::DoDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS )
{
Msg( "CSaveUtilV2Job_Delete::DoDataStatCallback @%.3f\n", Plat_FloatTime() );
SetDataFileCallback( &CSaveUtilV2Job_Delete::DoDataFile_Delete );
cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;
}
void CSaveUtilV2Job_Delete::DoDataFile_Delete( SONY_SAVEUTIL_FILE_PARAMS )
{
Msg( "CSaveUtilV2Job_Delete::DoDataFile_Delete @%.3f\n", Plat_FloatTime() );
// Perform the delete
set->fileOperation = CELL_SAVEDATA_FILEOP_DELETE;
set->fileBuf = NULL;
set->fileSize = set->fileBufSize = 0;
set->fileName = m_pTocEntry->m_chContainerName;
set->fileOffset = 0;
set->fileType = CELL_SAVEDATA_FILETYPE_SECUREFILE;
memcpy( set->secureFileId, g_pszSaveUtilSecureFileId, CELL_SAVEDATA_SECUREFILEID_SIZE );
set->reserved = NULL;
// final write
SetDataFileCallback( &CSaveUtilV2Job_Delete::DoDataFile_UpdateTOC );
cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;
Msg( "CSaveUtilV2Job_Delete::DoDataFile_Delete will delete '%s'...\n", m_pTocEntry->m_chContainerName );
}
void CSaveUtilV2Job_Delete::DoDataFile_UpdateTOC( SONY_SAVEUTIL_FILE_PARAMS )
{
Msg( "CSaveUtilV2Job_Delete::DoDataFile_UpdateTOC @%.3f\n", Plat_FloatTime() );
// Update TOC since we successfully deleted the file
{
AUTO_LOCK( g_SaveUtilV2TOC.m_mtx );
// Deleting an entry doesn't reallocate the memory
g_SaveUtilV2TOC.m_arrEntries.Remove( m_nTocEntryIndex );
}
cbResult->result = CELL_SAVEDATA_CBRESULT_OK_LAST;
}

View File

@@ -0,0 +1,649 @@
//===== 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<char *>(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 );
}

View File

@@ -0,0 +1,205 @@
//===== Copyright © 1996-2011, Valve Corporation, All rights reserved. ======//
#include "ps3_saveutil_v2.h"
#include "memdbgon.h"
// the interface class
class CPS3SaveRestoreToUI : public CTier2AppSystem< IPS3SaveRestoreToUI >
{
public: // IAppSystem
virtual bool Connect( CreateInterfaceFn factory );
virtual void Disconnect();
virtual void *QueryInterface( const char *pInterfaceName );
virtual InitReturnVal_t Init();
virtual void Shutdown();
public: // IPS3SaveRestoreToUI
/// You have to call this before doing any other save operation. In particular,
/// there may be an error opening the container. Poll on Async, and when it's done,
/// look in the return value to see if it succeeded, or if not, why not.
/// When bCreateIfMissing is set, it will create a new container where none exists.
virtual void Initialize( CPS3SaveRestoreAsyncStatus *pAsync, IPS3SaveSteamInfoProvider *pSteamInfoProvider, bool bCreateIfMissing, int nKBRequired )
{
SaveUtilV2_Initialize( pAsync, pSteamInfoProvider, nKBRequired );
}
// Save the given file into the container. (ie /dev_hdd1/tempsave/foo.ps3.sav will
// be written into the container as foo.ps3.sav ). You can optionally specify a second
// file to be written at the same time, eg a screenshot, because batching two writes
// to happen at once is far far faster than having two batches one after another.
// ALL game progress files must be
// written as secure; for now, even the screenshots should be, as that puts the work
// of CRC onto the operating system.
virtual void Write( CPS3SaveRestoreAsyncStatus *pAsync, const char *pSourcepath, const char *pScreenshotPath, const char *pComment, FileSecurity_t nSecurity = kSECURE )
{
SaveUtilV2_Write( pAsync, pSourcepath, pScreenshotPath, pComment );
}
// A more complicated and intelligent form of save writing, specifically for the case of autosaves.
// The source filename given (as an absolute path) will be written to "autosave.ps3.sav".
// Meanwhile, the existing "autosave.ps3.sav" will be renamed "autosave01.ps3.sav",
// any "autosave01.ps3.sav" will be renamed "autosave02.ps3.sav", and so on up to a maximum
// number of autosaves N, plus the base autosave.ps3.sav. The highest "autosave%02d.ps3.sav"
// will therefore have a number N. Excess autosaves with numbers >N will be deleted.
// If you specify a ScreenshotExtension (such as "tga"), the same operation is performed
// for every file above, where ."sav" is replaced with the given extension.
virtual void 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 ) // should be at least 1; the highest numbered autosave will be N-1.
{
SaveUtilV2_WriteAutosave( pAsync, pSourcePath, pComment, nMaxNumAutosaves );
}
// A way of writing clouded files into container, clouded files over
// a certain age are purged from container
virtual void WriteCloudFile( CPS3SaveRestoreAsyncStatus *pAsync,
const char *pSourcePath,
const unsigned int nMaxNumCloudFiles ) // should be at least 1; the highest numbered cloud file will be N-1.
{
SaveUtilV2_WriteCloudFile( pAsync, pSourcePath, nMaxNumCloudFiles );
}
// Load a file from the container into the given directory.
// give it a pointer to a CPS3SaveRestoreAsyncStatus struct that you have created and
// intend to poll.
virtual void Load( CPS3SaveRestoreAsyncStatus *pAsync, const char *pFilename, const char *pDestFullPath )
{
SaveUtilV2_Load( pAsync, pFilename, pDestFullPath );
}
// kill one or two files (eg, save and screenshot).
// async will respond when done.
virtual void Delete( CPS3SaveRestoreAsyncStatus *pAsync, const char *pFilename, const char *pOtherFilename = NULL )
{
SaveUtilV2_Delete( pAsync, pFilename );
}
// synchronously retrieve information on the files in the container. Lacks some of the container-wide'
// info of the above function, and may have slightly out of date information, but is a synchronous call
// and returns precisely the structure needed by CBaseModPanel::GetSaveGameInfos().
virtual void GetFileInfoSync( CUtlVector< PS3SaveGameInfo_t > &saveGameInfos, bool bFindAll )
{
SaveUtilV2_GetFileInfoSync( saveGameInfos, bFindAll );
}
// try to find Steam's schema file for the user and stuff it into the container.
// returns false if it couldn't find the file locally (in which case you should
// not wait for the async object to be "done", as the job wasn't initiated);
// true if it found it locally and queued up an async job to write it.
virtual void WriteSteamInfo( CPS3SaveRestoreAsyncStatus *pAsync )
{
SaveUtilV2_WriteSteamInfo( pAsync );
}
// returns whether save thread is busy
virtual bool IsSaveUtilBusy()
{
return !!g_pSaveUtilAsyncStatus;
}
// returns the m_nCurrentOperationTag field of the most recent async op to
// have run, or kSAVE_TAG_UNKNOWN if none has been enqueued yet. This tag
// changes the moment a job is made active and remains until the next job
// starts.
virtual uint32 GetCurrentOpTag()
{
CPS3SaveRestoreAsyncStatus *pAsync = g_pSaveUtilAsyncStatus;
if ( pAsync )
return pAsync->m_nCurrentOperationTag;
else
return kSAVE_TAG_UNKNOWN;
}
// returns the version of container, used to fire off events when container
// contents changes.
virtual uint32 GetContainerModificationVersion()
{
return g_SaveUtilV2TOCVersion;
}
// sets the cloud crypto key.
virtual void SetCloudFileCryptoKey( uint64 uiCloudCryptoKey )
{
g_uiSteamCloudCryptoKey = uiCloudCryptoKey;
}
};
//////////////////////////////////////////////////////////////////////////
CPS3SaveRestoreToUI g_PS3SaveToUI;
EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CPS3SaveRestoreToUI, IPS3SaveRestoreToUI,
IPS3SAVEUIAPI_VERSION_STRING, g_PS3SaveToUI );
static CreateInterfaceFn s_pfnDelegateFactory;
static void * InternalFactory( const char *pName, int *pReturnCode )
{
if ( pReturnCode )
{
*pReturnCode = IFACE_OK;
}
// Try to get interface via delegate
if ( void *pInterface = s_pfnDelegateFactory ? s_pfnDelegateFactory( pName, pReturnCode ) : NULL )
{
return pInterface;
}
// Try to get internal interface
if ( void *pInterface = Sys_GetFactoryThis()( pName, pReturnCode ) )
{
return pInterface;
}
// Failed
if ( pReturnCode )
{
*pReturnCode = IFACE_FAILED;
}
return NULL;
}
bool CPS3SaveRestoreToUI::Connect( CreateInterfaceFn factory )
{
Assert( !s_pfnDelegateFactory );
s_pfnDelegateFactory = factory;
CreateInterfaceFn ourFactory = InternalFactory;
ConnectTier1Libraries( &ourFactory, 1 );
ConnectTier2Libraries( &ourFactory, 1 );
s_pfnDelegateFactory = NULL;
return true;
}
void CPS3SaveRestoreToUI::Disconnect()
{
DisconnectTier2Libraries();
DisconnectTier1Libraries();
}
void * CPS3SaveRestoreToUI::QueryInterface( const char *pInterfaceName )
{
if ( !Q_stricmp( pInterfaceName, IPS3SAVEUIAPI_VERSION_STRING ) )
return static_cast< IPS3SaveRestoreToUI * >( this );
return NULL;
}
InitReturnVal_t CPS3SaveRestoreToUI::Init()
{
return INIT_OK;
}
void CPS3SaveRestoreToUI::Shutdown()
{
return;
}

View File

@@ -0,0 +1,298 @@
//===== 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>
class CSaveUtilV2Job_Load : public ISaveUtilV2Job
{
public: // Job entry point
virtual JobStatus_t DoExecute();
public: // Data passed from main thread
char m_chFileName[VALVE_CONTAINER_FILENAME_LEN];
char m_chFullPathOut[MAX_PATH];
bool m_bForCloud;
public: // Data resolved from the main thread
CSaveUtilV2ContainerTOC::TocEntry_t *m_pTocEntry;
int m_nSubFileIndex;
job_zlibinflate::JobDescriptor_t * m_pJobInflate;
protected: // Data used for loading file contents
CUtlBuffer m_bufScratch;
int WriteFile( char const *szFile );
protected: // Stat callback
virtual void DoDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS );
protected: // Load and write to disk
void DoDataFile_LoadToBuffer( SONY_SAVEUTIL_FILE_PARAMS );
void DoDataFile_WriteToDisk( SONY_SAVEUTIL_FILE_PARAMS );
};
//////////////////////////////////////////////////////////////////////////
void SaveUtilV2_Load( CPS3SaveRestoreAsyncStatus *pAsync, const char *pFilename, const char *pDestFullPath )
{
if ( !SaveUtilV2_CanStartJob() )
return;
// Find the file that the caller wants
int nSubFileIndex = -1;
int k = g_SaveUtilV2TOC.FindByEmbeddedFileName( pFilename, &nSubFileIndex );
if ( ( nSubFileIndex < 0 ) || ( k < 0 ) || ( k >= g_SaveUtilV2TOC.m_arrEntries.Count() ) )
{
pAsync->m_nSonyRetValue = CELL_SAVEDATA_ERROR_FAILURE;
pAsync->m_bDone = 1;
Warning( "ERROR: SaveUtilV2_Load: attempted to load file '%s' which doesn't exist in container!\n", pFilename );
return;
}
// Start the job
CSaveUtilV2Job_Load *pJob = new CSaveUtilV2Job_Load;
V_strncpy( pJob->m_chFileName, pFilename, sizeof( pJob->m_chFileName ) );
pJob->m_bForCloud = false;
switch ( pDestFullPath[0] )
{
case '@':
pJob->m_bForCloud = true;
++ pDestFullPath;
break;
}
V_strncpy( pJob->m_chFullPathOut, pDestFullPath, sizeof( pJob->m_chFullPathOut ) );
pJob->m_pTocEntry = &g_SaveUtilV2TOC.m_arrEntries[k].m_entry;
pJob->m_nSubFileIndex = nSubFileIndex;
SaveUtilV2_EnqueueJob( pAsync, pJob );
}
//////////////////////////////////////////////////////////////////////////
JobStatus_t CSaveUtilV2Job_Load::DoExecute()
{
float flTimeStamp = Plat_FloatTime();
Msg( "CSaveUtilV2Job_Load @%.3f\n", flTimeStamp );
// Allocate required buffer
if ( m_bForCloud )
{
int numBytesRequired = sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t );
for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart )
numBytesRequired += m_pTocEntry->m_numBytesFile[iPart];
m_bufScratch.EnsureCapacity( numBytesRequired );
}
else
{
m_bufScratch.EnsureCapacity( m_pTocEntry->m_numBytesFile[m_nSubFileIndex] + m_pTocEntry->m_numBytesDecompressedFile[m_nSubFileIndex] );
}
m_pJobInflate = NewJob128( *g_saveUtilVjobInstance.m_pRoot->m_pJobZlibInflate );
m_pJobInflate->header.sizeScratch = ( 16 * 1024 ) / 16 ;
// 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 );
DeleteJob( m_pJobInflate );
float flEndTimeStamp = Plat_FloatTime();
Msg( "CSaveUtilV2Job_Load: cellSaveDataAutoSave2 returned %x @%.3f ( total time = %.3f sec )\n", retv, flEndTimeStamp, flEndTimeStamp - flTimeStamp );
return SaveUtilV2_JobDone( retv );
}
void CSaveUtilV2Job_Load::DoDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS )
{
Msg( "CSaveUtilV2Job_Load::DoDataStatCallback @%.3f\n", Plat_FloatTime() );
SetDataFileCallback( &CSaveUtilV2Job_Load::DoDataFile_LoadToBuffer );
cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;
}
void CSaveUtilV2Job_Load::DoDataFile_LoadToBuffer( SONY_SAVEUTIL_FILE_PARAMS )
{
Msg( "CSaveUtilV2Job_Load::DoDataFile_LoadToBuffer @%.3f\n", Plat_FloatTime() );
// Load the file contents
set->fileOperation = CELL_SAVEDATA_FILEOP_READ;
set->fileName = m_pTocEntry->m_chContainerName;
set->fileType = CELL_SAVEDATA_FILETYPE_SECUREFILE;
memcpy( set->secureFileId, g_pszSaveUtilSecureFileId, CELL_SAVEDATA_SECUREFILEID_SIZE );
set->reserved = NULL;
set->fileOffset = CSaveUtilV2ContainerTOC::kStorageCapacity;
if ( m_bForCloud )
{
set->fileSize = 0;
for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart )
set->fileSize += m_pTocEntry->m_numBytesFile[iPart];
m_bufScratch.SeekPut( CUtlBuffer::SEEK_HEAD, sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t ) + set->fileSize );
set->fileBuf = ( ( uint8 * ) m_bufScratch.Base() ) + sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t );
set->fileBufSize = m_bufScratch.Size() - sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t );
}
else
{
for ( int k = 0; k < m_nSubFileIndex; ++ k )
set->fileOffset += m_pTocEntry->m_numBytesFile[k];
set->fileSize = m_pTocEntry->m_numBytesFile[m_nSubFileIndex];
m_bufScratch.SeekPut( CUtlBuffer::SEEK_HEAD, set->fileSize );
set->fileBuf = m_bufScratch.Base();
set->fileBufSize = m_bufScratch.Size();
}
// keep reading
SetDataFileCallback( &CSaveUtilV2Job_Load::DoDataFile_WriteToDisk );
cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;
Msg( "CSaveUtilV2Job_Load::DoDataFile_LoadToBuffer will load %u bytes of '%s' from '%s'...\n", set->fileSize, m_chFileName, set->fileName );
}
void CSaveUtilV2Job_Load::DoDataFile_WriteToDisk( SONY_SAVEUTIL_FILE_PARAMS )
{
Msg( "CSaveUtilV2Job_Load::DoDataFile_WriteToDisk '%s' @%.3f\n", m_chFileName, Plat_FloatTime() );
int ret = WriteFile( m_chFullPathOut );
if ( ret < 0 )
{
Msg( "ERROR: CSaveUtilV2Job_Load::DoDataFile_WriteToDisk failed to write file to disk!\n" );
g_pSaveUtilAsyncStatus->m_nSonyRetValue = CELL_SAVEDATA_ERROR_FAILURE;
cbResult->result = CELL_SAVEDATA_CBRESULT_ERR_FAILURE;
return;
}
cbResult->result = CELL_SAVEDATA_CBRESULT_OK_LAST;
}
int CSaveUtilV2Job_Load::WriteFile( char const *szFile )
{
if ( !szFile || !*szFile )
return 0;
float flTimeStamp = Plat_FloatTime();
Msg( "CSaveUtilV2Job_Load::WriteFile : %s @%.3f\n", szFile, flTimeStamp );
unsigned char *pWriteData = ( unsigned char * ) m_bufScratch.Base();
unsigned int numBytesWrite = m_pTocEntry->m_numBytesFile[m_nSubFileIndex]; // the compressed size
if ( m_bForCloud )
{
numBytesWrite = m_bufScratch.TellPut();
V_memcpy( pWriteData, m_pTocEntry, sizeof( CSaveUtilV2ContainerTOC::TocStorageReserved_t ) );
//
// Signature
//
// Generate sult into filename field
CSaveUtilV2ContainerTOC::TocEntry_t *pSignature = ( CSaveUtilV2ContainerTOC::TocEntry_t * ) pWriteData;
for ( int isult = 0; isult < sizeof( pSignature->m_chFile[0] ); ++ isult )
pSignature->m_chFile[0][isult] = ( 1 + rand() ) % 220;
// Put the version of our save header
V_memset( pSignature->m_chContainerName, 0, sizeof( pSignature->m_chContainerName ) );
pSignature->m_chContainerName[0] = 'S';
pSignature->m_chContainerName[1] = 'A';
pSignature->m_chContainerName[2] = 'V';
pSignature->m_chContainerName[3] = '1';
// Temporarily put our cryptokey in place of hash
V_memcpy( pWriteData + 8, &g_uiSteamCloudCryptoKey, sizeof( g_uiSteamCloudCryptoKey ) );
uint32 uiHash = SaveUtilV2_ComputeBufferHash( pWriteData, numBytesWrite );
// Store the hash
for ( int isult = 0; isult < sizeof( g_uiSteamCloudCryptoKey ) - sizeof( uiHash ); ++ isult )
pWriteData[8 + isult] = ( 1 + rand() ) % 220;
V_memcpy( pWriteData + 8 + sizeof( g_uiSteamCloudCryptoKey ) - sizeof( uiHash ), &uiHash, sizeof( uiHash ) );
}
else if ( m_pTocEntry->m_numBytesDecompressedFile[m_nSubFileIndex] )
{
// The file is actually compressed
if( g_saveUtilVjobInstance.m_pRoot )
{
double flStartInflateJob = Plat_FloatTime();
job_zlibinflate::JobParams_t * pJobParams = job_zlibinflate::GetJobParams( m_pJobInflate );
pJobParams->m_eaUncompressedOutput = pWriteData + numBytesWrite;
pJobParams->m_eaCompressed = pWriteData;
pJobParams->m_nCompressedSize = numBytesWrite;
pJobParams->m_nExpectedUncompressedSize = m_pTocEntry->m_numBytesDecompressedFile[m_nSubFileIndex];
int nError = g_saveUtilVjobInstance.m_pRoot->m_queuePortSound.pushJob( &m_pJobInflate->header, sizeof( *m_pJobInflate ), 0, CELL_SPURS_JOBQUEUE_FLAG_SYNC_JOB );
if( nError != CELL_OK )
{
Warning("job_zlibinflate failed to push through port, error 0x%X\n", nError );
return -1;
}
while( !pJobParams->IsDone() )
{
ThreadSleep( 1 );
}
double flEndInflateJob = Plat_FloatTime();
if( pJobParams->m_nError != 0 )
{
Warning( "CSaveUtilV2Job_Load::WriteFile failed to uncompress!\n" );
return -1;
}
else
{
Msg( "job_zlibInflate took %.3f sec : %u -> %u KiB (%.2f MiB/s)\n", flEndInflateJob - flStartInflateJob, pJobParams->m_nCompressedSize/1024, pJobParams->m_nExpectedUncompressedSize/1024, pJobParams->m_nExpectedUncompressedSize / ( 1024 * 1024 * ( flEndInflateJob - flStartInflateJob ) ) );
pWriteData += numBytesWrite;
numBytesWrite = m_pTocEntry->m_numBytesDecompressedFile[m_nSubFileIndex];
}
}
else
{
return -1;
}
}
int ret;
int fd;
ret = cellFsOpen( szFile, CELL_FS_O_CREAT | CELL_FS_O_TRUNC | CELL_FS_O_WRONLY, &fd, NULL, 0 );
if ( ret < 0 )
{
Msg( "ERROR: CSaveUtilV2Job_Load::DoDataFile_WriteToDisk : %s : cellFsOpen failed : %d\n", szFile, ret );
return ret;
}
uint64_t numBytesActuallyWritten = 0;
ret = cellFsWrite( fd, pWriteData, numBytesWrite, &numBytesActuallyWritten );
cellFsClose( fd );
if ( ret < 0 )
{
Msg( "ERROR: CSaveUtilV2Job_Load::DoDataFile_WriteToDisk : %s : cellFsWrite failed : %d\n", szFile, ret );
return ret;
}
if ( numBytesActuallyWritten != numBytesWrite )
{
Msg( "ERROR: CSaveUtilV2Job_Load::DoDataFile_WriteToDisk : %s : cellFsWrite wrote incorrect file : %ull bytes written, %d bytes expected\n",
szFile, numBytesActuallyWritten, numBytesWrite );
return -1;
}
float flEndTimeStamp = Plat_FloatTime();
Msg( "CSaveUtilV2Job_Load::WriteFile finished writing %s @%.3f (%.3f sec)\n", szFile, flEndTimeStamp, flEndTimeStamp - flTimeStamp );
return 0;
}

View File

@@ -0,0 +1,589 @@
//===== 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 );
}

View File

@@ -0,0 +1,95 @@
//===== Copyright © 1996-2011, Valve Corporation, All rights reserved. ======//
#include "ps3_saveutil_v2.h"
#include "memdbgon.h"
class CSaveUtilV2Job_WriteSteamInfo : public ISaveUtilV2Job
{
public: // Job entry point
virtual JobStatus_t DoExecute();
protected: // Stat callback
virtual void DoDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS );
protected: // Write Steam info
void DoDataFile_WriteSteamInfo( SONY_SAVEUTIL_FILE_PARAMS );
};
//////////////////////////////////////////////////////////////////////////
void SaveUtilV2_WriteSteamInfo( CPS3SaveRestoreAsyncStatus *pAsync )
{
if ( !SaveUtilV2_CanStartJob() )
return;
// Make sure that Steam info is prepared on the main thread
if ( !g_pSteamInfoProvider->PrepareSaveBufferForCommit() )
return;
// Start the job
CSaveUtilV2Job_WriteSteamInfo *pJob = new CSaveUtilV2Job_WriteSteamInfo;
SaveUtilV2_EnqueueJob( pAsync, pJob );
}
//////////////////////////////////////////////////////////////////////////
JobStatus_t CSaveUtilV2Job_WriteSteamInfo::DoExecute()
{
float flTimeStamp = Plat_FloatTime();
Msg( "CSaveUtilV2Job_WriteSteamInfo @%.3f\n", flTimeStamp );
// Call saveutil
int retv = cellSaveDataAutoSave2(
CELL_SAVEDATA_VERSION_CURRENT,
g_pszSaveUtilContainerName,
CELL_SAVEDATA_ERRDIALOG_ALWAYS,
&m_SaveDirInfo,
csDataStatCallback,
csDataFileCallback,
SYS_MEMORY_CONTAINER_ID_INVALID,
this );
float flEndTimeStamp = Plat_FloatTime();
Msg( "CSaveUtilV2Job_WriteSteamInfo: cellSaveDataAutoSave2 returned %x @%.3f ( total time = %.3f sec )\n", retv, flEndTimeStamp, flEndTimeStamp - flTimeStamp );
return SaveUtilV2_JobDone( retv );
}
void CSaveUtilV2Job_WriteSteamInfo::DoDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS )
{
Msg( "CSaveUtilV2Job_WriteSteamInfo::DoDataStatCallback @%.3f\n", Plat_FloatTime() );
SetDataFileCallback( &CSaveUtilV2Job_WriteSteamInfo::DoDataFile_WriteSteamInfo );
cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;
}
void CSaveUtilV2Job_WriteSteamInfo::DoDataFile_WriteSteamInfo( SONY_SAVEUTIL_FILE_PARAMS )
{
Msg( "CSaveUtilV2Job_WriteSteamInfo::DoDataFile_WriteSteamInfo @%.3f\n", Plat_FloatTime() );
// Obtain steam buffer
CUtlBuffer *pBuffer = g_pSteamInfoProvider->GetSaveBufferForCommit();
if ( !pBuffer )
{
cbResult->result = CELL_SAVEDATA_CBRESULT_OK_LAST;
return;
}
// Perform the write
set->fileOperation = CELL_SAVEDATA_FILEOP_WRITE;
set->fileBuf = pBuffer->Base();
set->fileSize = set->fileBufSize = pBuffer->TellPut();
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;
// final write
SetDataFileCallbackFinalize();
cbResult->result = CELL_SAVEDATA_CBRESULT_OK_NEXT;
Msg( "CSaveUtilV2Job_WriteSteamInfo::DoDataFile_WriteSteamInfo will write %d bytes...\n", pBuffer->TellPut() );
}

View File

@@ -0,0 +1,198 @@
//===== Copyright © 1996-2011, Valve Corporation, All rights reserved. ======//
#include "ps3_saveutil_v2.h"
#include "fmtstr.h"
#include "checksum_crc.h"
#include "memdbgon.h"
CON_COMMAND( ps3_saveutil_showtoc, "" )
{
AUTO_LOCK( g_SaveUtilV2TOC.m_mtx );
int numTocEntries = g_SaveUtilV2TOC.m_arrEntries.Count();
Msg( "--------- SAVEUTILTOC -----------\n" );
for ( int k = 0; k < numTocEntries; ++ k )
{
CSaveUtilV2ContainerTOC::TocEntry_t &e = g_SaveUtilV2TOC.m_arrEntries[k].m_entry;
Msg( "%02d : %016llx %s\n"
" '%s' %u/%u\n"
" '%s' %u/%u\n"
" %s\n",
k + 1, e.m_timeModification, e.m_chContainerName,
e.m_chFile[0], e.m_numBytesFile[0], e.m_numBytesDecompressedFile[0],
e.m_chFile[1], e.m_numBytesFile[1], e.m_numBytesDecompressedFile[1],
e.m_chComment
);
}
Msg( "--------- %02d ENTRIES -----------\n", numTocEntries );
}
void SaveUtilV2_GetFileInfoSync( CUtlVector< IPS3SaveRestoreToUI::PS3SaveGameInfo_t > &saveGameInfos, bool bFindAll )
{
// This can be called after starting a save op but before it completes, so this will return old data in that case.
// The caller should be aware that if SaveUtil is busy then it can check the operation TAG and know what is in
// progress and whether it can affect the TOC after it's finished.
// Currently only UI queries the TOC and ensures that writes of savegames are completed before queries.
AUTO_LOCK( g_SaveUtilV2TOC.m_mtx );
if ( !g_SaveUtilV2TOC.m_arrEntries.Count() )
{
saveGameInfos.RemoveAll();
return;
}
int numTocEntries = bFindAll ? g_SaveUtilV2TOC.m_arrEntries.Count() : 1;
saveGameInfos.SetCount( numTocEntries );
for ( int k = 0; k < numTocEntries; ++ k )
{
saveGameInfos[k].m_InternalName = CFmtStr( "!%s", g_SaveUtilV2TOC.m_arrEntries[k].m_entry.m_chContainerName );
saveGameInfos[k].m_Comment = g_SaveUtilV2TOC.m_arrEntries[k].m_entry.m_chComment;
saveGameInfos[k].m_Filename = g_SaveUtilV2TOC.m_arrEntries[k].m_entry.m_chFile[0];
saveGameInfos[k].m_ScreenshotFilename = g_SaveUtilV2TOC.m_arrEntries[k].m_entry.m_chFile[1];
saveGameInfos[k].m_nFileTime = g_SaveUtilV2TOC.m_arrEntries[k].m_entry.m_timeModification;
}
}
//////////////////////////////////////////////////////////////////////////
bool SaveUtilV2_CanStartJob()
{
bool bResult = ( g_pSaveUtilThreadPool && !g_pSaveUtilAsyncStatus );
if ( !bResult )
{
Warning( "SaveUtilV2_CanStartJob : cannot start job now! Invalid usage!\n" );
Assert( 0 );
}
return bResult;
}
void SaveUtilV2_EnqueueJob( CPS3SaveRestoreAsyncStatus *pAsync, ISaveUtilV2Job *pJob )
{
if ( g_pSaveUtilAsyncStatus )
Error( "SaveUtilV2_EnqueueJob while job already running ( %p running, %p attempted )!\n", g_pSaveUtilAsyncStatus, pAsync );
g_pSaveUtilAsyncStatus = pAsync;
// Prepare for saveutil operation
const int numContainers = VALVE_CONTAINER_COUNT;
pJob->m_bufSaveDirList.EnsureCapacity( numContainers * MAX( sizeof( CellSaveDataFileStat ), sizeof( CellSaveDataDirList ) ) );
// Prepare save dir info
memset( &pJob->m_SaveDirInfo, 0, sizeof(CellSaveDataSetBuf) );
pJob->m_SaveDirInfo.dirListMax = numContainers;
pJob->m_SaveDirInfo.fileListMax = numContainers;
pJob->m_SaveDirInfo.bufSize = pJob->m_bufSaveDirList.Size();
pJob->m_SaveDirInfo.buf = pJob->m_bufSaveDirList.Base();
// Mark the job as pending
g_pSaveUtilAsyncStatus->m_nSonyRetValue = CELL_SAVEDATA_ERROR_NOTSUPPORTED;
g_pSaveUtilAsyncStatus->m_bDone = 0;
// Let's notify the file system that a save is starting, that way the file system can try to reduce HDD accesses and use BluRay instead
g_pFullFileSystem->OnSaveStateChanged( true );
// Add the job to thread pool
pJob->SetFlags( JF_SERIAL | JF_QUEUE );
g_pSaveUtilThreadPool->AddJob( pJob );
pJob->Release();
}
JobStatus_t SaveUtilV2_JobDone( int nErrorCode )
{
// Let's notify the file system that a save is finished, that way the file system can restart using the HDD
g_pFullFileSystem->OnSaveStateChanged( false );
// Set the job error code and set that the job is done
if ( nErrorCode != CELL_SAVEDATA_ERROR_CBRESULT )
g_pSaveUtilAsyncStatus->m_nSonyRetValue = nErrorCode;
else if ( g_pSaveUtilAsyncStatus->m_nSonyRetValue >= 0 )
g_pSaveUtilAsyncStatus->m_nSonyRetValue = CELL_SAVEDATA_ERROR_FAILURE;
CPS3SaveRestoreAsyncStatus *pAsync = g_pSaveUtilAsyncStatus;
g_pSaveUtilAsyncStatus = NULL;
pAsync->m_bDone = 1;
return JOB_OK;
}
uint32 SaveUtilV2_ComputeBufferHash( void const *pvData, uint32 numBytes )
{
return CRC32_ProcessSingleBuffer( pvData, numBytes );
}
//////////////////////////////////////////////////////////////////////////
void ISaveUtilV2Job::csDataStatCallback( SONY_SAVEUTIL_STAT_PARAMS )
{
ISaveUtilV2Job *pSelf = static_cast<ISaveUtilV2Job*>( cbResult->userdata );
pSelf->DoDataStatCallback( SONY_SAVEUTIL_PARAMS );
}
void ISaveUtilV2Job::csDataFileCallback( SONY_SAVEUTIL_FILE_PARAMS )
{
ISaveUtilV2Job *pSelf = static_cast<ISaveUtilV2Job*>( cbResult->userdata );
if ( pSelf->m_pfnDoDataFileCallback )
{
(pSelf->*(pSelf->m_pfnDoDataFileCallback))( SONY_SAVEUTIL_PARAMS );
}
else
{
Msg( "ISaveUtilV2Job::csDataFileCallback finalizing save operation @%.3f\n", Plat_FloatTime() );
cbResult->result = CELL_SAVEDATA_CBRESULT_OK_LAST;
}
}
//////////////////////////////////////////////////////////////////////////
void CSaveUtilV2ContainerTOC::SerializeIntoTocBuffer( void *pvBuffer )
{
uint32 *pui32 = (uint32*) pvBuffer;
*( pui32 ++ ) = m_idxNewSaveName;
*( pui32 ++ ) = m_arrEntries.Count();
V_memcpy( pui32, m_arrEntries.Base(), m_arrEntries.Count() * sizeof( TocStorageReserved_t ) );
}
void CSaveUtilV2ContainerTOC::SerializeFromTocBuffer( void *pvBuffer )
{
uint32 *pui32 = (uint32*) pvBuffer;
m_idxNewSaveName = *( pui32 ++ );
uint32 uiEntriesCount = *( pui32 ++ );
uiEntriesCount = MIN( uiEntriesCount, VALVE_CONTAINER_COUNT );
m_arrEntries.AddMultipleToTail( uiEntriesCount, reinterpret_cast< TocStorageReserved_t * >( pui32 ) );
}
void CSaveUtilV2ContainerTOC::CopyInto( CSaveUtilV2ContainerTOC *pOther )
{
pOther->m_idxNewSaveName = m_idxNewSaveName;
pOther->m_arrEntries.RemoveAll();
pOther->m_arrEntries.AddMultipleToTail( m_arrEntries.Count(), m_arrEntries.Base() );
}
int CSaveUtilV2ContainerTOC::FindByEmbeddedFileName( char const *szFilename, int *pnPartIndex )
{
for ( int k = 0; k < m_arrEntries.Count(); ++ k )
{
if ( szFilename[0] == '!' )
{
if ( V_stricmp( m_arrEntries[k].m_entry.m_chContainerName, szFilename + 1 ) )
continue;
if ( pnPartIndex )
*pnPartIndex = 0;
return k;
}
for ( int iPart = 0; iPart < VALVE_CONTAINER_FPARTS; ++ iPart )
{
if ( !V_stricmp( m_arrEntries[k].m_entry.m_chFile[iPart], szFilename ) )
{
if ( pnPartIndex )
*pnPartIndex = iPart;
return k;
}
}
}
if ( pnPartIndex )
*pnPartIndex = -1;
return -1;
}