Files
cstrike15_src/game/client/portal2/gameui/portal2/steamcloudsync.cpp
nephacks f12416cffd initial
2025-06-04 03:22:50 +02:00

963 lines
29 KiB
C++

//========= Copyright © 1996-2011, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=====================================================================================//
#include "basemodui.h"
#include "steamcloudsync.h"
#ifndef NO_STEAM
#include "steam/steam_api.h"
#endif
#include "filesystem.h"
#include "matchmaking/portal2/imatchext_portal2.h"
#include "memdbgon.h"
#ifdef GAME_STEAM_CLOUD_SYNC_SUPPORTED
ConVar ui_steam_cloud_enabled( "ui_steam_cloud_enabled", "1", FCVAR_DEVELOPMENTONLY );
ConVar ui_steam_cloud_start_delay( "ui_steam_cloud_start_delay", "1.3", FCVAR_DEVELOPMENTONLY );
ConVar ui_steam_cloud_maxcount( "ui_steam_cloud_maxcount", "7", FCVAR_DEVELOPMENTONLY );
#define CloudMsg( ... ) Msg( "[GAME STEAM CLOUD] " __VA_ARGS__ )
#ifdef _PS3
#define CloudTimePrintf "%016llx.sav"
#else
#error
#endif
#define CloudHddName( sPrefix, sName ) CFmtStr( "%s%s/steam/remote/%s", sPrefix, g_pPS3PathInfo->SystemCachePath(), sName )
enum BackOffTimeX_t
{
BACK_OFF_SUCCESS = 1,
BACK_OFF_CBCK_FAIL = 3,
BACK_OFF_LIST_FAIL = 10,
};
class CGameSteamCloudSync : public IGameSteamCloudSync
{
public:
CGameSteamCloudSync() :
m_flTimeStamp( 0.0f ),
m_CallbackOnServersConnected( this, &CGameSteamCloudSync::Steam_OnServersConnected ),
m_CallbackOnServersDisconnected( this, &CGameSteamCloudSync::Steam_OnServersDisconnected ),
m_bLoggedOn( false ),
m_ePhase( PHASE_BOOT ),
m_idxCloudWorkItem( 0 ),
m_uiSaveContainerVersion( 0 ),
m_numErrorPasses( 0 ),
m_flRandomErrorFactor( 1.0f )
{
m_gscp.m_numSaveGamesToSync = 3;
}
public:
virtual void Sync( Sync_t eSyncReason = SYNC_DEFAULT );
virtual void AbortAll();
virtual void RunFrame();
virtual bool IsSyncInProgress( GameSteamCloudSyncInfo_t *pGSCSI );
virtual void GetPreferences( GameSteamCloudPreferences_t &gscp );
virtual void OnEvent( KeyValues *pEvent );
virtual bool IsFileInCloud( char const *szInternalName );
protected:
STEAM_CALLBACK( CGameSteamCloudSync, Steam_OnServersConnected, SteamServersConnected_t, m_CallbackOnServersConnected );
STEAM_CALLBACK( CGameSteamCloudSync, Steam_OnServersDisconnected, SteamServersDisconnected_t, m_CallbackOnServersDisconnected );
protected:
bool CanStartNewRequest( bool bServerList = false );
void RequestCloudListFromCloudServer();
void UpdateSteamCloudTOC();
void ReconcileSteamCloudAndSaveContainer();
void PrepareWorkList();
void NoteFileSyncedToCloudHdd( time_t tFile );
void SetNumSaveGamesToSync( int numSaveGames );
void AbortImpl( bool bSteamServersDisconnected );
protected:
enum CloudSyncPhase_t
{
PHASE_BOOT,
PHASE_LIST_SERVER,
PHASE_AWAITING_SERVER_LIST,
PHASE_AWAITING_VALIDATION,
PHASE_WORK,
PHASE_AWAITING_WORK_CALLBACK,
PHASE_NEXT_WORK_ITEM,
PHASE_IDLE
};
CloudSyncPhase_t m_ePhase;
int m_numErrorPasses;
float m_flRandomErrorFactor;
uint32 m_uiSaveContainerVersion;
//////////////////////////////////////////////////////////////////////////
protected:
STEAM_CALLBACK_MANUAL( CGameSteamCloudSync, Steam_OnRemoteStorageAppSyncStatusCheck, RemoteStorageAppSyncStatusCheck_t, m_CallbackOnRemoteStorageAppSyncStatusCheck );
STEAM_CALLBACK_MANUAL( CGameSteamCloudSync, Steam_OnRemoteStorageAppSyncedClient, RemoteStorageAppSyncedClient_t, m_CallbackOnRemoteStorageAppSyncedClient );
STEAM_CALLBACK_MANUAL( CGameSteamCloudSync, Steam_OnRemoteStorageAppSyncedServer, RemoteStorageAppSyncedServer_t, m_CallbackOnRemoteStorageAppSyncedServer );
protected:
void RunFrame_DELETE_FROM_CONTAINER();
void RunFrame_EXTRACT_FROM_CONTAINER();
void RunFrame_STORE_INTO_CONTAINER();
//////////////////////////////////////////////////////////////////////////
protected:
float m_flTimeStamp;
bool m_bLoggedOn;
GameSteamCloudPreferences_t m_gscp, m_gscpInternal;
CPS3SaveRestoreAsyncStatus m_ps3saveStatus;
public:
struct CloudFile_t
{
char m_chName[32];
char m_chSave[32];
time_t m_timeModification;
bool m_bInCloud;
bool m_bOnHdd;
bool m_bInSave;
bool m_bCloudSave;
};
enum CloudWorkType_t
{
DELETE_FROM_CONTAINER,
DELETE_FROM_HDD,
DELETE_FROM_CLOUD,
DOWNLOAD_FROM_CLOUD,
EXTRACT_FROM_CONTAINER,
UPLOAD_INTO_CLOUD,
STORE_INTO_CONTAINER,
};
enum CloudWorkStateFlags_t
{
WORK_DEFAULT = 0,
WORK_DEPENDS_ON_PREV = ( 1 << 0 ),
WORK_ABORT_ALL_ON_FAIL = ( 1 << 1 ),
WORK_SUCCEEDED = ( 1 << 2 ),
WORK_FAILED = ( 1 << 3 ),
};
struct CloudWorkItem_t
{
CloudWorkType_t m_eWork;
int m_idxCloudFile;
uint32 m_eStateFlags;
int m_iProgress;
};
CUtlVector< CloudFile_t > m_arrCloudInfo;
CUtlVector< time_t > m_arrSyncedToHdd;
CUtlVector< CloudWorkItem_t > m_arrCloudWorkItems;
int m_idxCloudWorkItem;
};
static CGameSteamCloudSync g_GameSteamCloudSync;
IGameSteamCloudSync *g_pGameSteamCloudSync = &g_GameSteamCloudSync;
//////////////////////////////////////////////////////////////////////////
bool CGameSteamCloudSync::CanStartNewRequest( bool bServerList )
{
float flTimeout = ui_steam_cloud_start_delay.GetFloat();
if ( bServerList && ( m_numErrorPasses > 0 ) )
{
flTimeout *= (1+m_numErrorPasses)*3;
if ( m_flRandomErrorFactor == 1.0f )
m_flRandomErrorFactor = RandomFloat( 0.99f, 1.3f );
flTimeout *= m_flRandomErrorFactor;
}
if ( Plat_FloatTime() - m_flTimeStamp < flTimeout )
return false;
if ( !m_bLoggedOn ||
engine->IsDrawingLoadingImage() || engine->IsInGame() ||
ps3saveuiapi->IsSaveUtilBusy() )
{
m_flTimeStamp = Plat_FloatTime();
if ( bServerList )
m_numErrorPasses = 0;
return false;
}
if ( bServerList )
m_flRandomErrorFactor = 1.0f;
return true;
}
void CGameSteamCloudSync::Sync( Sync_t eSyncReason )
{
switch ( m_ePhase )
{
case PHASE_BOOT:
if ( eSyncReason != SYNC_GAMEBOOTREADY )
return;
if ( !steamapicontext || !steamapicontext->SteamRemoteStorage() )
{
CloudMsg( "GAMEBOOTREADY, SteamRemoteStorage interface missing, CLOUD DISABLED!\n" );
return;
}
else
{
// Need to update the value right away here, game profile not logged in yet
int32 iValue = 0;
steamapicontext->SteamUserStats()->GetStat( "PS3.CFG.sys.cloud_saves", &iValue );
SetNumSaveGamesToSync( iValue );
}
CloudMsg( "GAMEBOOTREADY, activating...\n" );
g_pFullFileSystem->CreateDirHierarchy( CloudHddName( "", "" ) );
m_ePhase = PHASE_LIST_SERVER;
m_flTimeStamp = Plat_FloatTime(); // let initialization happen before we do cloud work
return;
}
}
void CGameSteamCloudSync::AbortAll()
{
CloudMsg( "ABORTALL requested in phase %d @%.3f...\n", m_ePhase, Plat_FloatTime() );
AbortImpl( false );
}
bool CGameSteamCloudSync::IsSyncInProgress( GameSteamCloudSyncInfo_t *pGSCSI )
{
static GameSteamCloudSyncInfo_t gscsi;
if ( !pGSCSI )
pGSCSI = &gscsi;
switch ( m_ePhase )
{
case PHASE_LIST_SERVER:
if ( m_bLoggedOn )
{
pGSCSI->m_bUploadingToCloud = gscsi.m_bUploadingToCloud;
pGSCSI->m_flProgress = 0.0f;
return true;
}
else
{
return false;
}
case PHASE_AWAITING_SERVER_LIST:
pGSCSI->m_bUploadingToCloud = gscsi.m_bUploadingToCloud;
pGSCSI->m_flProgress = 0.1f;
return true;
case PHASE_AWAITING_VALIDATION:
pGSCSI->m_bUploadingToCloud = gscsi.m_bUploadingToCloud;
pGSCSI->m_flProgress = 0.98f;
return true;
case PHASE_WORK:
case PHASE_AWAITING_WORK_CALLBACK:
case PHASE_NEXT_WORK_ITEM:
// 0.1 - 1.0f distributed among m_arrCloudWorkItems.Count()
pGSCSI->m_flProgress = 0.1f + ( MAX( m_idxCloudWorkItem + 1, 1 ) ) * 1.0f /( m_arrCloudWorkItems.Count() + 1 );
pGSCSI->m_flProgress = MIN( pGSCSI->m_flProgress, 0.98f );
if ( ( m_idxCloudWorkItem >= 0 ) && ( m_idxCloudWorkItem < m_arrCloudWorkItems.Count() ) )
{
switch ( m_arrCloudWorkItems[m_idxCloudWorkItem].m_eWork )
{
case DELETE_FROM_CONTAINER:
case STORE_INTO_CONTAINER:
case DOWNLOAD_FROM_CLOUD:
pGSCSI->m_bUploadingToCloud = false;
break;
case DELETE_FROM_CLOUD:
case EXTRACT_FROM_CONTAINER:
case UPLOAD_INTO_CLOUD:
pGSCSI->m_bUploadingToCloud = true;
break;
default:
pGSCSI->m_bUploadingToCloud = gscsi.m_bUploadingToCloud;
break;
}
}
else
{
pGSCSI->m_bUploadingToCloud = gscsi.m_bUploadingToCloud;
}
gscsi.m_bUploadingToCloud = pGSCSI->m_bUploadingToCloud;
return true;
}
return false;
}
void CGameSteamCloudSync::GetPreferences( GameSteamCloudPreferences_t &gscp )
{
gscp = m_gscp;
}
void CGameSteamCloudSync::OnEvent( KeyValues *pEvent )
{
char const *szEvent = pEvent->GetName();
if ( !Q_stricmp( "OnProfileDataLoaded", szEvent ) )
{
// See what the current setting is in the profile
if ( !g_pMatchFramework )
return;
IPlayerLocal *pPlayer = g_pMatchFramework->GetMatchSystem()->GetPlayerManager()->GetLocalPlayer( XBX_GetPrimaryUserId() );
if ( !pPlayer )
return;
TitleData3 *pTitleData3 = ( TitleData3 * ) pPlayer->GetPlayerTitleData( TitleDataFieldsDescription_t::DB_TD3 );
SetNumSaveGamesToSync( pTitleData3->cvSystem.cloud_saves );
}
}
bool CGameSteamCloudSync::IsFileInCloud( char const *szInternalName )
{
for ( int k = 0; k < m_arrCloudInfo.Count(); ++ k )
{
if ( V_stricmp( m_arrCloudInfo[k].m_chSave, szInternalName ) )
continue;
return m_arrCloudInfo[k].m_bInCloud;
}
return false;
}
void CGameSteamCloudSync::Steam_OnServersConnected( SteamServersConnected_t *pParam )
{
if ( !steamapicontext->SteamUser()->BLoggedOn() )
return;
CSteamID sID = steamapicontext->SteamUser()->GetSteamID();
if ( !sID.IsValid() )
return;
if ( !sID.ConvertToUint64() )
return;
m_bLoggedOn = true;
m_flTimeStamp = Plat_FloatTime(); // let Steam do its work before we load it with cloud work
CloudMsg( "Steam servers connected @%.3f\n", Plat_FloatTime() );
static uint64 s_cryptokey = 0ull;
if ( !s_cryptokey )
{
CloudMsg( "Setting cloud crypto key to: 0x%016llX\n", sID.ConvertToUint64() );
ps3saveuiapi->SetCloudFileCryptoKey( s_cryptokey = sID.ConvertToUint64() );
}
else
{
#ifndef _CERT
if ( s_cryptokey != sID.ConvertToUint64() )
Error( "Steam Cloud Crypto Key mismatch!\n" );
#endif
}
}
void CGameSteamCloudSync::Steam_OnServersDisconnected( SteamServersDisconnected_t *pParam )
{
CloudMsg( "Steam servers disconnected (phase=%d) @%.3f\n", m_ePhase, Plat_FloatTime() );
m_bLoggedOn = false;
AbortImpl( true );
}
//////////////////////////////////////////////////////////////////////////
//
// Run every frame
//
void CGameSteamCloudSync::RunFrame()
{
switch ( m_ePhase )
{
case PHASE_LIST_SERVER:
if ( !CanStartNewRequest( true ) )
return;
if ( !ui_steam_cloud_enabled.GetBool() )
return;
RequestCloudListFromCloudServer();
m_ePhase = PHASE_AWAITING_SERVER_LIST;
return;
case PHASE_WORK:
if ( ( m_idxCloudWorkItem < 0 ) || ( m_idxCloudWorkItem >= m_arrCloudWorkItems.Count() ) )
{
CloudMsg( "WORK FINISHED, reconciling all storage...\n" );
m_flTimeStamp = Plat_FloatTime();
if ( m_numErrorPasses <= 0 )
{
RequestCloudListFromCloudServer();
m_ePhase = PHASE_AWAITING_VALIDATION;
}
else
{
m_ePhase = PHASE_LIST_SERVER;
}
return;
}
switch ( m_arrCloudWorkItems[m_idxCloudWorkItem].m_eWork )
{
case DELETE_FROM_CONTAINER:
RunFrame_DELETE_FROM_CONTAINER();
return;
case EXTRACT_FROM_CONTAINER:
RunFrame_EXTRACT_FROM_CONTAINER();
return;
case STORE_INTO_CONTAINER:
RunFrame_STORE_INTO_CONTAINER();
return;
case DELETE_FROM_HDD:
#ifndef _CERT
if ( ui_steam_cloud_enabled.GetInt() < 10 ) // in non-cert builds allow files to linger for inspection
#endif
g_pFullFileSystem->RemoveFile( CloudHddName( "", m_arrCloudInfo[ m_arrCloudWorkItems[m_idxCloudWorkItem].m_idxCloudFile ].m_chName ) );
m_arrCloudWorkItems[m_idxCloudWorkItem].m_eStateFlags |= WORK_SUCCEEDED;
m_arrSyncedToHdd.FindAndFastRemove( m_arrCloudInfo[ m_arrCloudWorkItems[m_idxCloudWorkItem].m_idxCloudFile ].m_timeModification );
m_ePhase = PHASE_NEXT_WORK_ITEM;
return;
case DELETE_FROM_CLOUD:
if ( !CanStartNewRequest() )
return;
m_CallbackOnRemoteStorageAppSyncedServer.Register( this, &CGameSteamCloudSync::Steam_OnRemoteStorageAppSyncedServer );
steamapicontext->SteamRemoteStorage()->ResetFileRequestState();
steamapicontext->SteamRemoteStorage()->FileForget( m_arrCloudInfo[ m_arrCloudWorkItems[m_idxCloudWorkItem].m_idxCloudFile ].m_chName );
m_arrCloudInfo[ m_arrCloudWorkItems[m_idxCloudWorkItem].m_idxCloudFile ].m_bInCloud = false;
steamapicontext->SteamRemoteStorage()->SynchronizeToServer();
m_ePhase = PHASE_AWAITING_WORK_CALLBACK;
return;
case DOWNLOAD_FROM_CLOUD:
if ( !CanStartNewRequest() )
return;
m_CallbackOnRemoteStorageAppSyncedClient.Register( this, &CGameSteamCloudSync::Steam_OnRemoteStorageAppSyncedClient );
steamapicontext->SteamRemoteStorage()->ResetFileRequestState();
steamapicontext->SteamRemoteStorage()->FileFetch( m_arrCloudInfo[ m_arrCloudWorkItems[m_idxCloudWorkItem].m_idxCloudFile ].m_chName );
steamapicontext->SteamRemoteStorage()->SynchronizeToClient();
m_ePhase = PHASE_AWAITING_WORK_CALLBACK;
return;
case UPLOAD_INTO_CLOUD:
if ( !CanStartNewRequest() )
return;
m_CallbackOnRemoteStorageAppSyncedServer.Register( this, &CGameSteamCloudSync::Steam_OnRemoteStorageAppSyncedServer );
steamapicontext->SteamRemoteStorage()->ResetFileRequestState();
steamapicontext->SteamRemoteStorage()->FilePersist( m_arrCloudInfo[ m_arrCloudWorkItems[m_idxCloudWorkItem].m_idxCloudFile ].m_chName );
steamapicontext->SteamRemoteStorage()->SetSyncPlatforms( m_arrCloudInfo[ m_arrCloudWorkItems[m_idxCloudWorkItem].m_idxCloudFile ].m_chName, k_ERemoteStoragePlatformPS3 );
m_arrCloudInfo[ m_arrCloudWorkItems[m_idxCloudWorkItem].m_idxCloudFile ].m_bInCloud = true;
steamapicontext->SteamRemoteStorage()->SynchronizeToServer();
m_ePhase = PHASE_AWAITING_WORK_CALLBACK;
return;
}
return;
case PHASE_NEXT_WORK_ITEM:
if ( !( m_arrCloudWorkItems[m_idxCloudWorkItem].m_eStateFlags & ( WORK_FAILED | WORK_SUCCEEDED ) ) )
{
CloudMsg( "WARNING: work%02d didn't set state, assuming failed!\n", m_idxCloudWorkItem + 1 );
m_arrCloudWorkItems[m_idxCloudWorkItem].m_eStateFlags |= WORK_FAILED;
}
if ( m_arrCloudWorkItems[m_idxCloudWorkItem].m_eStateFlags & WORK_FAILED )
{
if ( WORK_ABORT_ALL_ON_FAIL & m_arrCloudWorkItems[m_idxCloudWorkItem].m_eStateFlags )
{
CloudMsg( "WORK FAILED, ABORTING ALL WORKLOAD!\n" );
m_ePhase = PHASE_LIST_SERVER;
m_numErrorPasses += BACK_OFF_CBCK_FAIL;
return;
}
++ m_idxCloudWorkItem;
if ( ( m_idxCloudWorkItem < m_arrCloudWorkItems.Count() ) &&
( m_arrCloudWorkItems[m_idxCloudWorkItem].m_eStateFlags & WORK_DEPENDS_ON_PREV ) )
{
m_arrCloudWorkItems[m_idxCloudWorkItem].m_eStateFlags |= WORK_FAILED;
return;
}
}
else
{
++ m_idxCloudWorkItem;
}
if ( !m_bLoggedOn )
{
m_numErrorPasses = 0;
m_ePhase = PHASE_LIST_SERVER;
}
else
{
CloudMsg( " **** WORK[%02d]\n", m_idxCloudWorkItem + 1 );
m_ePhase = PHASE_WORK;
}
return;
case PHASE_IDLE:
if ( !ui_steam_cloud_enabled.GetBool() ||
( m_uiSaveContainerVersion != ps3saveuiapi->GetContainerModificationVersion() ) ||
( m_gscp.m_numSaveGamesToSync != m_gscpInternal.m_numSaveGamesToSync ) )
{
m_numErrorPasses = 0;
m_ePhase = PHASE_LIST_SERVER;
m_flTimeStamp = Plat_FloatTime();
}
return;
}
}
//////////////////////////////////////////////////////////////////////////
//
// Abort implementation
//
void CGameSteamCloudSync::AbortImpl( bool bSteamServersDisconnected )
{
if ( !bSteamServersDisconnected )
return;
m_CallbackOnRemoteStorageAppSyncStatusCheck.Unregister();
m_CallbackOnRemoteStorageAppSyncedClient.Unregister();
m_CallbackOnRemoteStorageAppSyncedServer.Unregister();
switch ( m_ePhase )
{
case PHASE_AWAITING_SERVER_LIST:
case PHASE_AWAITING_VALIDATION:
case PHASE_AWAITING_WORK_CALLBACK:
m_numErrorPasses = 0;
m_ePhase = PHASE_LIST_SERVER;
break;
}
}
//////////////////////////////////////////////////////////////////////////
//
// Get file list from server
//
void CGameSteamCloudSync::RequestCloudListFromCloudServer()
{
CloudMsg( "RequestCloudListFromCloudServer @%.3f...\n", Plat_FloatTime() );
m_CallbackOnRemoteStorageAppSyncStatusCheck.Register( this, &CGameSteamCloudSync::Steam_OnRemoteStorageAppSyncStatusCheck );
steamapicontext->SteamRemoteStorage()->ResetFileRequestState();
steamapicontext->SteamRemoteStorage()->GetFileListFromServer();
m_uiSaveContainerVersion = ps3saveuiapi->GetContainerModificationVersion();
}
//////////////////////////////////////////////////////////////////////////
//
// Awaiting RemoteStorageAppSyncStatusCheck_t
//
void CGameSteamCloudSync::Steam_OnRemoteStorageAppSyncStatusCheck( RemoteStorageAppSyncStatusCheck_t *pParam )
{
m_CallbackOnRemoteStorageAppSyncStatusCheck.Unregister();
CloudMsg( "Steam_OnRemoteStorageAppSyncStatusCheck( %d )\n", pParam->m_eResult );
if ( pParam->m_eResult == k_EResultOK )
{
m_numErrorPasses = MAX( 0, m_numErrorPasses - BACK_OFF_SUCCESS );
UpdateSteamCloudTOC();
if ( m_arrCloudWorkItems.Count() > 0 )
{
m_idxCloudWorkItem = 0;
m_ePhase = PHASE_WORK;
CloudMsg( " **** WORK[%02d]\n", m_idxCloudWorkItem + 1 );
}
else
{
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnSteamCloudStorageUpdated", "reason", "idle" ) );
m_ePhase = PHASE_IDLE;
}
}
else
{
m_numErrorPasses += BACK_OFF_LIST_FAIL;
m_ePhase = PHASE_LIST_SERVER;
}
}
//////////////////////////////////////////////////////////////////////////
//
// Container operations
//
void CGameSteamCloudSync::RunFrame_DELETE_FROM_CONTAINER()
{
CloudWorkItem_t &cwi = m_arrCloudWorkItems[m_idxCloudWorkItem];
switch ( cwi.m_iProgress )
{
case 0:
if ( !CanStartNewRequest() )
return;
ps3saveuiapi->Delete( &m_ps3saveStatus, m_arrCloudInfo[cwi.m_idxCloudFile].m_chSave );
cwi.m_iProgress = 1;
return;
case 1:
if ( !m_ps3saveStatus.m_bDone )
return;
if ( m_ps3saveStatus.GetSonyReturnValue() < 0 )
{
CloudMsg( "DELETE_FROM_CONTAINER failed (error=%d) @%.3f...\n", m_ps3saveStatus.GetSonyReturnValue(), Plat_FloatTime() );
cwi.m_eStateFlags |= WORK_FAILED;
}
else
{
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnSteamCloudStorageUpdated", "reason", "delete" ) );
m_arrCloudInfo[cwi.m_idxCloudFile].m_bInSave = false;
cwi.m_eStateFlags |= WORK_SUCCEEDED;
}
// fall through
default:
m_ePhase = PHASE_NEXT_WORK_ITEM;
return;
}
}
void CGameSteamCloudSync::RunFrame_EXTRACT_FROM_CONTAINER()
{
CloudWorkItem_t &cwi = m_arrCloudWorkItems[m_idxCloudWorkItem];
switch ( cwi.m_iProgress )
{
case 0:
if ( !CanStartNewRequest() )
return;
ps3saveuiapi->Load( &m_ps3saveStatus, m_arrCloudInfo[cwi.m_idxCloudFile].m_chSave,
CloudHddName( "@", m_arrCloudInfo[cwi.m_idxCloudFile].m_chName ) );
cwi.m_iProgress = 1;
return;
case 1:
if ( !m_ps3saveStatus.m_bDone )
return;
if ( m_ps3saveStatus.GetSonyReturnValue() < 0 )
{
CloudMsg( "EXTRACT_FROM_CONTAINER failed (error=%d) @%.3f...\n", m_ps3saveStatus.GetSonyReturnValue(), Plat_FloatTime() );
cwi.m_eStateFlags |= WORK_FAILED;
}
else
{
NoteFileSyncedToCloudHdd( m_arrCloudInfo[cwi.m_idxCloudFile].m_timeModification );
cwi.m_eStateFlags |= WORK_SUCCEEDED;
}
// fall through
default:
m_ePhase = PHASE_NEXT_WORK_ITEM;
return;
}
}
void CGameSteamCloudSync::RunFrame_STORE_INTO_CONTAINER()
{
CloudWorkItem_t &cwi = m_arrCloudWorkItems[m_idxCloudWorkItem];
switch ( cwi.m_iProgress )
{
case 0:
if ( !CanStartNewRequest() )
return;
ps3saveuiapi->WriteCloudFile( &m_ps3saveStatus, CloudHddName( "", m_arrCloudInfo[cwi.m_idxCloudFile].m_chName ),
m_gscpInternal.m_numSaveGamesToSync );
cwi.m_iProgress = 1;
return;
case 1:
if ( !m_ps3saveStatus.m_bDone )
return;
if ( m_ps3saveStatus.GetSonyReturnValue() < 0 )
{
CloudMsg( "STORE_INTO_CONTAINER failed (error=%d) @%.3f...\n", m_ps3saveStatus.GetSonyReturnValue(), Plat_FloatTime() );
cwi.m_eStateFlags |= WORK_FAILED;
}
else
{
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnSteamCloudStorageUpdated", "reason", "store" ) );
m_arrCloudInfo[cwi.m_idxCloudFile].m_bInSave = true;
cwi.m_eStateFlags |= WORK_SUCCEEDED;
}
// fall through
default:
m_ePhase = PHASE_NEXT_WORK_ITEM;
return;
}
}
//////////////////////////////////////////////////////////////////////////
//
// Awaiting RemoteStorageAppSyncedClient_t
//
void CGameSteamCloudSync::Steam_OnRemoteStorageAppSyncedClient( RemoteStorageAppSyncedClient_t *pParam )
{
m_CallbackOnRemoteStorageAppSyncedClient.Unregister();
CloudMsg( "Steam_OnRemoteStorageAppSyncedClient( %d )\n", pParam->m_eResult );
CloudWorkItem_t &cwi = m_arrCloudWorkItems[m_idxCloudWorkItem];
if ( pParam->m_eResult == k_EResultOK )
{
NoteFileSyncedToCloudHdd( m_arrCloudInfo[cwi.m_idxCloudFile].m_timeModification );
cwi.m_eStateFlags |= WORK_SUCCEEDED;
}
else
{
m_numErrorPasses += BACK_OFF_CBCK_FAIL;
cwi.m_eStateFlags |= WORK_FAILED;
}
m_ePhase = PHASE_NEXT_WORK_ITEM;
}
//////////////////////////////////////////////////////////////////////////
//
// Awaiting RemoteStorageAppSyncedServer_t
//
void CGameSteamCloudSync::Steam_OnRemoteStorageAppSyncedServer( RemoteStorageAppSyncedServer_t *pParam )
{
m_CallbackOnRemoteStorageAppSyncedServer.Unregister();
CloudMsg( "Steam_OnRemoteStorageAppSyncedServer( %d )\n", pParam->m_eResult );
CloudWorkItem_t &cwi = m_arrCloudWorkItems[m_idxCloudWorkItem];
if ( pParam->m_eResult == k_EResultOK )
{
cwi.m_eStateFlags |= WORK_SUCCEEDED;
g_pMatchFramework->GetEventsSubscription()->BroadcastEvent( new KeyValues( "OnSteamCloudStorageUpdated", "reason", "cloud" ) );
}
else
{
m_numErrorPasses += BACK_OFF_CBCK_FAIL;
cwi.m_eStateFlags |= WORK_FAILED;
}
m_ePhase = PHASE_NEXT_WORK_ITEM;
}
//////////////////////////////////////////////////////////////////////////
static int SortSteamCloudInfos( CGameSteamCloudSync::CloudFile_t const *a, CGameSteamCloudSync::CloudFile_t const *b )
{
if ( a->m_timeModification != b->m_timeModification )
return ( a->m_timeModification > b->m_timeModification ) ? -1 : 1;
else
return 0;
}
void CGameSteamCloudSync::UpdateSteamCloudTOC()
{
ISteamRemoteStorage *rs = steamapicontext->SteamRemoteStorage();
m_arrCloudInfo.RemoveAll();
CUtlVector< IPS3SaveRestoreToUI::PS3SaveGameInfo_t > arrSaveContainerFiles;
ps3saveuiapi->GetFileInfoSync( arrSaveContainerFiles, true );
int32 numFiles = rs->GetFileCount();
for ( int k = 0; k < numFiles; ++ k )
{
int32 nFileSize = 0;
char const *szFileName = rs->GetFileNameAndSize( k, &nFileSize );
if ( !szFileName || !*szFileName )
continue;
ERemoteStoragePlatform ePlatforms = rs->GetSyncPlatforms( szFileName );
bool bPS3platform = ( ePlatforms == k_ERemoteStoragePlatformPS3 );
CloudMsg( "%02d/%02d: %s = %d bytes (%s)\n", k + 1, numFiles, szFileName, nFileSize, bPS3platform?"ps3":"ignored" );
if ( !bPS3platform )
continue;
time_t tNameId;
if ( 1 != sscanf( szFileName, CloudTimePrintf, &tNameId ) )
continue;
// Reconcile cloud information
CloudFile_t cf = {0};
V_strncpy( cf.m_chName, szFileName, sizeof( cf.m_chName ) );
cf.m_timeModification = tNameId;
cf.m_bInCloud = true;
m_arrCloudInfo.AddToTail( cf );
}
CloudMsg( "Steam cloud TOC updated\n" );
ReconcileSteamCloudAndSaveContainer();
}
void CGameSteamCloudSync::ReconcileSteamCloudAndSaveContainer()
{
CUtlVector< IPS3SaveRestoreToUI::PS3SaveGameInfo_t > arrSaveContainerFiles;
ps3saveuiapi->GetFileInfoSync( arrSaveContainerFiles, true );
for ( int icf = 0; icf < m_arrCloudInfo.Count(); ++ icf )
{
CloudFile_t &cf = m_arrCloudInfo[icf];
for ( int k = 0; k < arrSaveContainerFiles.Count(); ++ k )
{
if ( cf.m_timeModification == arrSaveContainerFiles[k].m_nFileTime )
{
cf.m_bInSave = true;
V_strncpy( cf.m_chSave, arrSaveContainerFiles[k].m_InternalName.Get(), sizeof( cf.m_chSave ) );
cf.m_bCloudSave = V_stristr( arrSaveContainerFiles[k].m_Filename.Get(), "cloudsave" );
break;
}
}
cf.m_bOnHdd = ( m_arrSyncedToHdd.Find( cf.m_timeModification ) != m_arrSyncedToHdd.InvalidIndex() );
}
for ( int k = 0; k < arrSaveContainerFiles.Count(); ++ k )
{
bool bClouded = false;
for ( int icf = 0; icf < m_arrCloudInfo.Count(); ++ icf )
{
if ( m_arrCloudInfo[icf].m_timeModification == arrSaveContainerFiles[k].m_nFileTime )
{
bClouded = true;
break;
}
}
if ( bClouded )
continue;
CloudFile_t cf = {0};
V_snprintf( cf.m_chName, sizeof( cf.m_chName ), CloudTimePrintf, arrSaveContainerFiles[k].m_nFileTime );
V_strncpy( cf.m_chSave, arrSaveContainerFiles[k].m_InternalName.Get(), sizeof( cf.m_chSave ) );
cf.m_timeModification = arrSaveContainerFiles[k].m_nFileTime;
cf.m_bInSave = true;
cf.m_bCloudSave = V_stristr( arrSaveContainerFiles[k].m_Filename.Get(), "cloudsave" );
cf.m_bOnHdd = ( m_arrSyncedToHdd.Find( cf.m_timeModification ) != m_arrSyncedToHdd.InvalidIndex() );
m_arrCloudInfo.AddToTail( cf );
}
m_arrCloudInfo.Sort( SortSteamCloudInfos );
m_gscpInternal = m_gscp;
CloudMsg( "---CLOUD+LOCAL---\n" );
CloudMsg( "Cloud setting: numSaves = %d\n", m_gscpInternal.m_numSaveGamesToSync );
for ( int k = 0; k < m_arrCloudInfo.Count(); ++ k )
{
CloudMsg( " %02d/%02d: %016llX%s%s%s [^%s] [*%s]\n",
k+1, m_arrCloudInfo.Count(),
m_arrCloudInfo[k].m_timeModification,
m_arrCloudInfo[k].m_bInCloud ? " cloud" : "",
m_arrCloudInfo[k].m_bInSave ? " local" : "",
m_arrCloudInfo[k].m_bOnHdd ? " hdd" : "",
m_arrCloudInfo[k].m_chName,
m_arrCloudInfo[k].m_chSave );
}
for ( int k = m_arrCloudInfo.Count(); k --> m_gscp.m_numSaveGamesToSync; )
{
if ( m_arrCloudInfo[k].m_bInCloud )
continue;
if ( m_arrCloudInfo[k].m_bCloudSave )
continue;
m_arrCloudInfo.Remove( k );
}
CloudMsg( "-----------------\n" );
PrepareWorkList();
}
void CGameSteamCloudSync::PrepareWorkList()
{
m_arrCloudWorkItems.RemoveAll();
CloudMsg( "-------- PREPARING WORK LIST -----------\n" );
// Delete items from save container
for ( int k = m_arrCloudInfo.Count(); k --> m_gscpInternal.m_numSaveGamesToSync; )
{
if ( m_arrCloudInfo[k].m_bCloudSave )
{
CloudMsg( " %02d. Delete from container '%02d:%s'\n", m_arrCloudWorkItems.Count() + 1, k + 1, m_arrCloudInfo[k].m_chSave );
CloudWorkItem_t cwi = { DELETE_FROM_CONTAINER, k, WORK_ABORT_ALL_ON_FAIL };
m_arrCloudWorkItems.AddToTail( cwi );
}
}
// Download from cloud and store into container
for ( int k = 0; ( k < m_arrCloudInfo.Count() ) && ( k < m_gscpInternal.m_numSaveGamesToSync ); ++ k )
{
if ( !m_arrCloudInfo[k].m_bInSave )
{
uint32 uiFlags = 0;
if ( !m_arrCloudInfo[k].m_bOnHdd )
{
CloudMsg( " %02d. Download from cloud to hdd '%02d:%s'\n", m_arrCloudWorkItems.Count() + 1, k + 1, m_arrCloudInfo[k].m_chName );
CloudWorkItem_t cwi = { DOWNLOAD_FROM_CLOUD, k };
m_arrCloudWorkItems.AddToTail( cwi );
uiFlags |= WORK_DEPENDS_ON_PREV;
}
{
CloudMsg( " %02d. Store into container '%02d:%s'\n", m_arrCloudWorkItems.Count() + 1, k + 1, m_arrCloudInfo[k].m_chName );
CloudWorkItem_t cwi = { STORE_INTO_CONTAINER, k, uiFlags };
m_arrCloudWorkItems.AddToTail( cwi );
}
{
CloudMsg( " %02d. Delete from hdd cache '%02d:%s'\n", m_arrCloudWorkItems.Count() + 1, k + 1, m_arrCloudInfo[k].m_chName );
CloudWorkItem_t cwi = { DELETE_FROM_HDD, k, WORK_DEPENDS_ON_PREV };
m_arrCloudWorkItems.AddToTail( cwi );
}
}
}
// Delete aged cloud items
for ( int k = m_arrCloudInfo.Count(); k --> m_gscpInternal.m_numSaveGamesToSync; )
{
if ( m_arrCloudInfo[k].m_bInCloud )
{
CloudMsg( " %02d. Delete from cloud '%02d:%s'\n", m_arrCloudWorkItems.Count() + 1, k + 1, m_arrCloudInfo[k].m_chName );
CloudWorkItem_t cwi = { DELETE_FROM_CLOUD, k, WORK_ABORT_ALL_ON_FAIL };
m_arrCloudWorkItems.AddToTail( cwi );
}
}
// Extract from container and upload into cloud
for ( int k = 0; ( k < m_arrCloudInfo.Count() ) && ( k < m_gscpInternal.m_numSaveGamesToSync ); ++ k )
{
if ( !m_arrCloudInfo[k].m_bInCloud )
{
uint32 uiFlags = 0;
if ( !m_arrCloudInfo[k].m_bOnHdd )
{
CloudMsg( " %02d. Extract from container to hdd '%02d:%s'\n", m_arrCloudWorkItems.Count() + 1, k + 1, m_arrCloudInfo[k].m_chSave );
CloudWorkItem_t cwi = { EXTRACT_FROM_CONTAINER, k };
m_arrCloudWorkItems.AddToTail( cwi );
uiFlags |= WORK_DEPENDS_ON_PREV;
}
{
CloudMsg( " %02d. Upload into cloud '%02d:%s'\n", m_arrCloudWorkItems.Count() + 1, k + 1, m_arrCloudInfo[k].m_chSave );
CloudWorkItem_t cwi = { UPLOAD_INTO_CLOUD, k, uiFlags };
m_arrCloudWorkItems.AddToTail( cwi );
}
{
CloudMsg( " %02d. Delete from hdd cache '%02d:%s'\n", m_arrCloudWorkItems.Count() + 1, k + 1, m_arrCloudInfo[k].m_chName );
CloudWorkItem_t cwi = { DELETE_FROM_HDD, k, WORK_DEPENDS_ON_PREV };
m_arrCloudWorkItems.AddToTail( cwi );
}
}
}
CloudMsg( "-------- READY TO WORK -----------\n" );
}
void CGameSteamCloudSync::NoteFileSyncedToCloudHdd( time_t tFile )
{
m_arrSyncedToHdd.EnsureCapacity( ui_steam_cloud_maxcount.GetInt()*2 );
if ( m_arrSyncedToHdd.Count() >= ui_steam_cloud_maxcount.GetInt()*2 )
m_arrSyncedToHdd.SetCountNonDestructively( ui_steam_cloud_maxcount.GetInt()*2 - 1 );
m_arrSyncedToHdd.AddToHead( tFile );
}
void CGameSteamCloudSync::SetNumSaveGamesToSync( int numSaveGames )
{
m_gscp.m_numSaveGamesToSync = numSaveGames;
if ( !m_gscp.m_numSaveGamesToSync )
m_gscp.m_numSaveGamesToSync = 3;
else
-- m_gscp.m_numSaveGamesToSync;
m_gscp.m_numSaveGamesToSync = MAX( 0, m_gscp.m_numSaveGamesToSync );
m_gscp.m_numSaveGamesToSync = MIN( 7, m_gscp.m_numSaveGamesToSync );
}
#endif // GAME_STEAM_CLOUD_SYNC_SUPPORTED