2273 lines
69 KiB
C++
2273 lines
69 KiB
C++
//
|
|
// StatsSavesMgr.h
|
|
//
|
|
// Copyright (C) 1999-2014 Rockstar Games. All Rights Reserved.
|
|
//
|
|
|
|
//Rage Headers
|
|
#include <time.h>
|
|
#include "system/param.h"
|
|
#include "rline/rl.h"
|
|
#include "rline/rltelemetry.h"
|
|
#include "rline/ros/rlroscommon.h"
|
|
#include "rline/ros/rlros.h"
|
|
#include "string/stringutil.h"
|
|
|
|
#if __BANK
|
|
#include "bank/bank.h"
|
|
#endif
|
|
|
|
//Stats headers
|
|
#include "Stats/StatsSavesMgr.h"
|
|
#include "Stats/stats_channel.h"
|
|
#include "Stats/StatsInterface.h"
|
|
|
|
//Game headers
|
|
#include "SaveLoad/GenericGameStorage.h"
|
|
#include "SaveLoad/savegame_list.h"
|
|
#include "SaveLoad/savegame_defines.h"
|
|
#include "Network/NetworkInterface.h"
|
|
#include "Network/Network.h"
|
|
#include "Network/Sessions/NetworkSession.h"
|
|
#include "scene/playerswitch/PlayerSwitchInterface.h"
|
|
#include "script/script_channel.h"
|
|
#include "script/script.h"
|
|
#include "Network/Cloud/Tunables.h"
|
|
#include "Network/Live/NetworkTelemetry.h"
|
|
|
|
#if !__NO_OUTPUT
|
|
#include "stats/StatsMgr.h"
|
|
#include "stats/StatsDataMgr.h"
|
|
XPARAM(spewDirtySavegameStats);
|
|
#endif // !__NO_OUTPUT
|
|
|
|
|
|
FRONTEND_STATS_OPTIMISATIONS()
|
|
SAVEGAME_OPTIMISATIONS()
|
|
|
|
XPARAM(disableOnlineStatsAreSynched);
|
|
PARAM(nompsavegame, "[stat_savemgr] Disable all savegames for multiplayer.");
|
|
XPARAM(lan);
|
|
XPARAM(nonetwork);
|
|
XPARAM(cloudForceAvailable);
|
|
PARAM(failmpload, "[stat_savemgr] Make any savegame fail to load.");
|
|
PARAM(failmploadalways, "[stat_savemgr] Make any savegame fail to load.");
|
|
PARAM(nompsavesometimes, "[stat_savemgr] Simulate save fail.");
|
|
PARAM(nomploadsometimes, "[stat_savemgr] Simulate load fail.");
|
|
PARAM(enableLocalMPSaveCache, "[stat_savemgr] Enable local cache of multiplayer savegames.");
|
|
PARAM(mpactivechar, "[stat_savemgr] Override active character.");
|
|
PARAM(mpactiveslots, "[stat_savemgr] Override numver of active slots.");
|
|
PARAM(changeLocalProfileTime, "[stat_savemgr] Override local profile stats flush time.");
|
|
PARAM(dirtyreadtimeoutPeriod, "[stat_savemgr] Override local profile stats flush timeout time.");
|
|
PARAM(changeLocalCloudTime, "[stat_savemgr] Override local cloud save time.");
|
|
PARAM(dirtycloudreadtimeoutPeriod, "[stat_savemgr] Override local cloud save timeout time.");
|
|
|
|
#if !__FINAL
|
|
PARAM(assertOnCanSaveFail, "[stat_savemgr] Enable asserts when saving during activity/transition.");
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// TELEM_SAVETYPE
|
|
class MetricTELEM_SAVETYPE : public MetricPlayStat
|
|
{
|
|
RL_DECLARE_METRIC(TELEM_SAVETYPE, TELEMETRY_CHANNEL_JOB, LOGLEVEL_HIGH_PRIORITY);
|
|
|
|
public:
|
|
MetricTELEM_SAVETYPE(const int savetype, const int saveReason, const bool failed, const bool cloud)
|
|
{
|
|
m_SaveType = savetype;
|
|
m_SaveReason = saveReason;
|
|
m_Failed = failed;
|
|
m_Cloud = cloud;
|
|
}
|
|
|
|
virtual bool Write(RsonWriter* rw) const {
|
|
return MetricPlayStat::Write(rw)
|
|
&& rw->WriteInt("a", m_SaveType)
|
|
&& rw->WriteInt("b", m_SaveReason)
|
|
&& rw->WriteBool("c", m_Failed)
|
|
&& rw->WriteBool("d", m_Cloud);
|
|
}
|
|
|
|
int m_SaveType;
|
|
int m_SaveReason;
|
|
bool m_Failed;
|
|
bool m_Cloud;
|
|
};
|
|
|
|
void CSaveTelemetryStats::FlushTelemetry(const bool requestFailed, const bool isCloudRequest)
|
|
{
|
|
bool sendTelemSavetype = true;
|
|
Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("SEND_METRIC_TELEM_SAVETYPE", 0x016eea50), sendTelemSavetype);
|
|
if (sendTelemSavetype) //Send savegame telemetry.
|
|
{
|
|
if (m_Type != STAT_SAVETYPE_INVALID)
|
|
{
|
|
MetricTELEM_SAVETYPE m(m_Type, m_Reason, requestFailed, isCloudRequest);
|
|
APPEND_METRIC(m);
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// TELEM_SAVETYPE
|
|
|
|
#if !__FINAL
|
|
static mthRandom s_cloudSavesRng;
|
|
#endif
|
|
|
|
CompileTimeAssert(CStatsSavesMgr::TOTAL_NUMBER_OF_FILES == NUM_MULTIPLAYER_STATS_SAVE_SLOTS);
|
|
|
|
static bool s_FlushProfileStats = false;
|
|
|
|
class MetricDirtyCloudRead : public MetricPlayStat
|
|
{
|
|
RL_DECLARE_METRIC(DR_CLOUD, TELEMETRY_CHANNEL_MISC, LOGLEVEL_VERYHIGH_PRIORITY);
|
|
|
|
private:
|
|
s64 m_timestamp;
|
|
|
|
public:
|
|
explicit MetricDirtyCloudRead(const s64 timestamp) : m_timestamp(timestamp) {;}
|
|
virtual bool Write(RsonWriter* rw) const
|
|
{
|
|
return MetricPlayStat::Write(rw)
|
|
&& rw->WriteInt64("ts", m_timestamp);
|
|
}
|
|
};
|
|
|
|
class MetricDirtyProfileStatRead : public MetricPlayStat
|
|
{
|
|
RL_DECLARE_METRIC(DR_PS, TELEMETRY_CHANNEL_MISC, LOGLEVEL_VERYHIGH_PRIORITY);
|
|
|
|
private:
|
|
s64 m_timestamp;
|
|
|
|
public:
|
|
explicit MetricDirtyProfileStatRead(const s64 timestamp) : m_timestamp(timestamp) {;}
|
|
virtual bool Write(RsonWriter* rw) const
|
|
{
|
|
return MetricPlayStat::Write(rw)
|
|
&& rw->WriteInt64("ts", m_timestamp);
|
|
}
|
|
};
|
|
|
|
void SPEW_POSIX_TIME( const u64 OUTPUT_ONLY(currentPosixTime), const char* OUTPUT_ONLY(text) )
|
|
{
|
|
#if !__NO_OUTPUT
|
|
int year = 0;int month = 0;int day = 0;int hour = 0;int min = 0;int sec = 0;
|
|
time_t date = (time_t)currentPosixTime;
|
|
struct tm* ptm;
|
|
ptm = gmtime(&date);
|
|
|
|
if (ptm)
|
|
{
|
|
year = ptm->tm_year+1900;
|
|
month = ptm->tm_mon+1;
|
|
day = ptm->tm_mday;
|
|
hour = ptm->tm_hour+1;
|
|
min = ptm->tm_min;
|
|
sec = ptm->tm_sec;
|
|
statDebugf1("........ %s ='%" I64FMT "u', date='%d:%d:%d, %d-%d-%d'",text, currentPosixTime,hour,min,sec,day,month,year);
|
|
}
|
|
else
|
|
{
|
|
statDebugf1(" ........ %s = '%" I64FMT "u'", text, currentPosixTime);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
RAGE_DEFINE_SUBCHANNEL(stat, savemgr, DIAG_SEVERITY_DEBUG3, DIAG_SEVERITY_DEBUG1)
|
|
#undef __stat_channel
|
|
#define __stat_channel stat_savemgr
|
|
|
|
// -------------------- CStatsSaveHistory
|
|
|
|
bool CStatsSaveHistory::CanSave(const int NOTFINAL_ONLY(file), const eSaveTypes savetype) const
|
|
{
|
|
if (savetype == STAT_SAVETYPE_INVALID || savetype == STAT_SAVETYPE_MAX_NUMBER)
|
|
return false;
|
|
|
|
// Always instasave a char deletion / immediate flush, no matter what
|
|
if( savetype == STAT_SAVETYPE_DELETE_CHAR || savetype == STAT_SAVETYPE_IMMEDIATE_FLUSH || savetype == STAT_SAVETYPE_COMMERCE_DEPOSIT )
|
|
return true;
|
|
|
|
#if __ALLOW_LOCAL_MP_STATS_SAVES
|
|
static const u32 MIN_SAVE_TIME_INTERVAL = 10*1000;
|
|
#else
|
|
static const u32 MIN_SAVE_TIME_INTERVAL = 30*1000;
|
|
#endif
|
|
|
|
int minSaveInterval = MIN_SAVE_TIME_INTERVAL;
|
|
Tunables::GetInstance().Access(CD_GLOBAL_HASH, ATSTRINGHASH("CANSAVE_MIN_INTERVAL", 0xb5e2dced), minSaveInterval);
|
|
|
|
const u32 currtime = fwTimer::GetSystemTimeInMilliseconds();
|
|
|
|
//Check time against same eSaveTypes.
|
|
if (savetype != STAT_SAVETYPE_END_MISSION_CREATOR
|
|
&& savetype != STAT_SAVETYPE_AMBIENT
|
|
&& savetype != STAT_SAVETYPE_PHOTOS
|
|
&& savetype != STAT_SAVETYPE_AMB_PROFILE_AWARD_TRACKER
|
|
&& m_records[savetype].m_lastSaveTime>0
|
|
&& currtime-m_records[savetype].m_lastSaveTime<=((u32)minSaveInterval))
|
|
{
|
|
scriptAssertf(0, "%s:STAT_SAVE - SAVE requested for file=%d, savetype=%s - FAILED. Because time from last save is %d less than the minimum interval %d."
|
|
,CTheScripts::GetCurrentScriptNameAndProgramCounter()
|
|
,file, GET_STAT_SAVETYPE_NAME(savetype)
|
|
,currtime-m_records[savetype].m_lastSaveTime, ((u32)minSaveInterval));
|
|
|
|
NOTFINAL_ONLY(statErrorf("Cloud storage: SAVE requested for file=%d, savetype=%s - FAILED. (last save less than the minimum interval)", file, GET_STAT_SAVETYPE_NAME(savetype));)
|
|
return false;
|
|
}
|
|
|
|
#if !__FINAL
|
|
|
|
#if __ALLOW_LOCAL_MP_STATS_SAVES
|
|
static const u32 MIN_SAVE_TIME_INTERVAL_END_MATCH = 20*1000;
|
|
#else
|
|
static const u32 MIN_SAVE_TIME_INTERVAL_END_MATCH = 40*1000;
|
|
#endif
|
|
|
|
int minSaveIntervalEndMatch = MIN_SAVE_TIME_INTERVAL_END_MATCH;
|
|
Tunables::GetInstance().Access(CD_GLOBAL_HASH, ATSTRINGHASH("CANSAVE_MIN_INTERVAL_END_MATCH", 0xdde9468f), minSaveIntervalEndMatch);
|
|
|
|
if (STAT_SAVETYPE_END_SESSION != savetype)
|
|
{
|
|
if (m_records[STAT_SAVETYPE_END_MATCH].m_lastSaveTime>0
|
|
&& currtime-m_records[STAT_SAVETYPE_END_MATCH].m_lastSaveTime<=((u32)minSaveIntervalEndMatch))
|
|
{
|
|
scriptAssertf(0, "%s:STAT_SAVE - SAVE requested for file=%d, savetype=%s - FAILED, because STAT_SAVETYPE_END_MATCH last save is %d less than the minimum interval %d"
|
|
,CTheScripts::GetCurrentScriptNameAndProgramCounter()
|
|
,file, GET_STAT_SAVETYPE_NAME(savetype)
|
|
,currtime-m_records[STAT_SAVETYPE_END_MATCH].m_lastSaveTime, ((u32)minSaveIntervalEndMatch));
|
|
|
|
statErrorf("Cloud storage: SAVE requested for file=%d, savetype=%s - FAILED. (last STAT_SAVETYPE_END_MATCH less than the minimum interval)", file, GET_STAT_SAVETYPE_NAME(savetype));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (m_records[STAT_SAVETYPE_END_SESSION].m_lastSaveTime>0
|
|
&& currtime-m_records[STAT_SAVETYPE_END_SESSION].m_lastSaveTime<=((u32)minSaveIntervalEndMatch))
|
|
{
|
|
scriptAssertf(0, "%s:STAT_SAVE - SAVE requested for file=%d, savetype=%s - FAILED, because STAT_SAVETYPE_END_SESSION last save is %d less than the minimum interval %d"
|
|
,CTheScripts::GetCurrentScriptNameAndProgramCounter()
|
|
,file, GET_STAT_SAVETYPE_NAME(savetype)
|
|
,currtime-m_records[STAT_SAVETYPE_END_SESSION].m_lastSaveTime, ((u32)minSaveIntervalEndMatch));
|
|
|
|
statErrorf("Cloud storage: SAVE requested for file=%d, savetype=%s - FAILED. (last STAT_SAVETYPE_END_SESSION less than the minimum interval)", file, GET_STAT_SAVETYPE_NAME(savetype));
|
|
return false;
|
|
}
|
|
|
|
#endif // !__FINAL
|
|
|
|
#if __ALLOW_LOCAL_MP_STATS_SAVES
|
|
static const u32 MIN_SAVE_TIME_INTERVAL_START_SESSION = 20*1000;
|
|
#else
|
|
static const u32 MIN_SAVE_TIME_INTERVAL_START_SESSION = 40*1000;
|
|
#endif
|
|
|
|
int minSaveIntervalStartSession = MIN_SAVE_TIME_INTERVAL_START_SESSION;
|
|
Tunables::GetInstance().Access(CD_GLOBAL_HASH, ATSTRINGHASH("CANSAVE_MIN_INTERVAL_START_SESSION", 0x2746f763), minSaveIntervalStartSession);
|
|
|
|
if (savetype == STAT_SAVETYPE_START_SESSION || savetype == STAT_SAVETYPE_END_GAMER_SETUP)
|
|
{
|
|
if (m_records[STAT_SAVETYPE_END_CREATE_NEWCHAR].m_lastSaveTime>0
|
|
&& currtime-m_records[STAT_SAVETYPE_END_CREATE_NEWCHAR].m_lastSaveTime<=((u32)minSaveIntervalStartSession))
|
|
{
|
|
scriptAssertf(0, "%s:STAT_SAVE - SAVE requested for file=%d, savetype=%s - FAILED, because STAT_SAVETYPE_END_CREATE_NEWCHAR last save is %d less than the minimum interval %d."
|
|
,CTheScripts::GetCurrentScriptNameAndProgramCounter()
|
|
,file, GET_STAT_SAVETYPE_NAME(savetype)
|
|
,currtime-m_records[STAT_SAVETYPE_END_CREATE_NEWCHAR].m_lastSaveTime, ((u32)minSaveIntervalStartSession));
|
|
|
|
NOTFINAL_ONLY(statErrorf("Cloud storage: savetype=%s - FAILED.", GET_STAT_SAVETYPE_NAME(savetype));)
|
|
return false;
|
|
}
|
|
|
|
if (m_records[STAT_SAVETYPE_END_GAMER_SETUP].m_lastSaveTime>0
|
|
&& currtime-m_records[STAT_SAVETYPE_END_GAMER_SETUP].m_lastSaveTime<=((u32)minSaveIntervalStartSession))
|
|
{
|
|
scriptAssertf(0, "%s:STAT_SAVE - SAVE requested for file=%d, savetype=%s - FAILED, because STAT_SAVETYPE_END_GAMER_SETUP last save is %d less than the minimum interval %d."
|
|
,CTheScripts::GetCurrentScriptNameAndProgramCounter()
|
|
,file, GET_STAT_SAVETYPE_NAME(savetype)
|
|
,currtime-m_records[STAT_SAVETYPE_END_GAMER_SETUP].m_lastSaveTime, ((u32)minSaveIntervalStartSession));
|
|
|
|
NOTFINAL_ONLY(statErrorf("Cloud storage: SAVE requested for file=%d, savetype=%s - FAILED. (last STAT_SAVETYPE_END_GAMER_SETUP less than the minimum interval)", file, GET_STAT_SAVETYPE_NAME(savetype));)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//During an activity we don't care about saving unless script enabled it.
|
|
if (CNetwork::GetNetworkSession().IsActivitySession())
|
|
{
|
|
if (!m_records[savetype].m_canSaveDuringJob)
|
|
{
|
|
#if !__FINAL
|
|
if (PARAM_assertOnCanSaveFail.Get() && savetype != STAT_SAVETYPE_AMBIENT && savetype != STAT_SAVETYPE_AMB_PROFILE_AWARD_TRACKER && savetype != STAT_SAVETYPE_PHOTOS)
|
|
{
|
|
scriptAssertf(0, "%s:STAT_SAVE - SAVE requested for file=%d, savetype=%s - FAILED, During an activity we don't care about saving unless STAT_SET_OPEN_SAVETYPE_IN_JOB was called."
|
|
,CTheScripts::GetCurrentScriptNameAndProgramCounter()
|
|
,file, GET_STAT_SAVETYPE_NAME(savetype));
|
|
}
|
|
#endif
|
|
|
|
NOTFINAL_ONLY(statErrorf("Cloud storage: SAVE requested for file=%d, savetype=%s - FAILED. (save during activity)", file, GET_STAT_SAVETYPE_NAME(savetype));)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//During transition we don't care about saving unless we are ending.
|
|
if (CNetwork::GetNetworkSession().IsTransitionActive())
|
|
{
|
|
if (savetype != STAT_SAVETYPE_END_MATCH
|
|
&& savetype != STAT_SAVETYPE_END_SESSION
|
|
&& savetype != STAT_SAVETYPE_END_MISSION
|
|
&& savetype != STAT_SAVETYPE_PROLOGUE)
|
|
{
|
|
#if !__FINAL
|
|
if (PARAM_assertOnCanSaveFail.Get() && savetype != STAT_SAVETYPE_AMBIENT && savetype != STAT_SAVETYPE_AMB_PROFILE_AWARD_TRACKER)
|
|
{
|
|
scriptAssertf(0, "%s:STAT_SAVE - SAVE requested for file=%d, savetype=%s - FAILED, During transition we don't care about saving unless we are ending."
|
|
,CTheScripts::GetCurrentScriptNameAndProgramCounter()
|
|
,file, GET_STAT_SAVETYPE_NAME(savetype));
|
|
}
|
|
#endif
|
|
|
|
NOTFINAL_ONLY(statErrorf("Cloud storage: SAVE requested for file=%d, savetype=%s - FAILED (save during transition).", file, GET_STAT_SAVETYPE_NAME(savetype));)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
switch(savetype)
|
|
{
|
|
case STAT_SAVETYPE_DEFAULT:
|
|
case STAT_SAVETYPE_CASH:
|
|
case STAT_SAVETYPE_END_ATM:
|
|
case STAT_SAVETYPE_CONTACTS:
|
|
case STAT_SAVETYPE_START_MATCH:
|
|
case STAT_SAVETYPE_WEAPON_DROP:
|
|
case STAT_SAVETYPE_JOIN_SC:
|
|
return false;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void CStatsSaveHistory::SaveRequested(const eSaveTypes savetype)
|
|
{
|
|
if (savetype == STAT_SAVETYPE_INVALID || savetype == STAT_SAVETYPE_MAX_NUMBER)
|
|
return;
|
|
|
|
m_records[savetype].m_lastSaveTime = fwTimer::GetSystemTimeInMilliseconds();
|
|
|
|
//We finished an activity or are ending the session
|
|
if (savetype == STAT_SAVETYPE_END_MATCH || savetype == STAT_SAVETYPE_END_SESSION || savetype == STAT_SAVETYPE_END_MISSION || savetype == STAT_SAVETYPE_PROLOGUE)
|
|
{
|
|
ResetAfterJob( );
|
|
}
|
|
}
|
|
|
|
bool CStatsSaveHistory::DeferredCloudSave(const eSaveTypes savetype) const
|
|
{
|
|
//We are in UGC no profile stats flush.
|
|
if (CNetwork::GetNetworkSession().IsActivitySession())
|
|
return false;
|
|
|
|
//We are in UGC Transition no profile stats flush.
|
|
if (CNetwork::GetNetworkSession().IsTransitionActive())
|
|
return false;
|
|
|
|
switch(savetype)
|
|
{
|
|
case STAT_SAVETYPE_STUNTJUMP:
|
|
case STAT_SAVETYPE_AMBIENT:
|
|
case STAT_SAVETYPE_AMB_PROFILE_AWARD_TRACKER:
|
|
return true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CStatsSaveHistory::DeferredProfileStatsFlush(const eSaveTypes savetype) const
|
|
{
|
|
//We are in UGC no profile stats flush.
|
|
if (CNetwork::GetNetworkSession().IsActivitySession())
|
|
return false;
|
|
|
|
//We are in UGC Transition no profile stats flush.
|
|
if (CNetwork::GetNetworkSession().IsTransitionActive())
|
|
return false;
|
|
|
|
switch(savetype)
|
|
{
|
|
case STAT_SAVETYPE_STUNTJUMP:
|
|
case STAT_SAVETYPE_EXPLOITS:
|
|
case STAT_SAVETYPE_AMBIENT:
|
|
case STAT_SAVETYPE_INTERACTION_MENU:
|
|
case STAT_SAVETYPE_AMB_PROFILE_AWARD_TRACKER:
|
|
case STAT_SAVETYPE_RANKUP:
|
|
case STAT_SAVETYPE_END_MISSION_CREATOR:
|
|
case STAT_SAVETYPE_END_MISSION:
|
|
case STAT_SAVETYPE_PHOTOS:
|
|
return true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
bool CStatsSaveHistory::RequestProfileStatsFlush(const eSaveTypes savetype) const
|
|
{
|
|
//Flush Profile Stats if there where EARN/SPEND transactions with CLIENT cash ON.
|
|
if (s_FlushProfileStats && RequestCloudSave(savetype) && !CloudSaveOnly(savetype))
|
|
return true;
|
|
|
|
switch(savetype)
|
|
{
|
|
case STAT_SAVETYPE_PRE_STARTSTORE:
|
|
case STAT_SAVETYPE_END_SESSION:
|
|
case STAT_SAVETYPE_END_MATCH:
|
|
case STAT_SAVETYPE_END_GAMER_SETUP:
|
|
case STAT_SAVETYPE_END_SHOPPING:
|
|
case STAT_SAVETYPE_END_CREATE_NEWCHAR:
|
|
case STAT_SAVETYPE_DELETE_CHAR:
|
|
case STAT_SAVETYPE_IMMEDIATE_FLUSH:
|
|
case STAT_SAVETYPE_COMMERCE_DEPOSIT:
|
|
return true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
bool CStatsSaveHistory::ProfileStatsFlushOnly(const eSaveTypes savetype) const
|
|
{
|
|
switch(savetype)
|
|
{
|
|
case STAT_SAVETYPE_PHOTOS:
|
|
case STAT_SAVETYPE_END_SHOPPING:
|
|
case STAT_SAVETYPE_COMMERCE_DEPOSIT:
|
|
return true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
bool CStatsSaveHistory::CloudSaveOnly(const eSaveTypes savetype) const
|
|
{
|
|
switch(savetype)
|
|
{
|
|
case STAT_SAVETYPE_END_MISSION:
|
|
case STAT_SAVETYPE_PROLOGUE:
|
|
case STAT_SAVETYPE_START_SESSION:
|
|
case STAT_SAVETYPE_INTERACTION_MENU:
|
|
case STAT_SAVETYPE_AMB_PROFILE_AWARD_TRACKER:
|
|
case STAT_SAVETYPE_END_MISSION_CREATOR:
|
|
case STAT_SAVETYPE_STORE:
|
|
return true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
bool CStatsSaveHistory::RequestCloudSave(const eSaveTypes savetype) const
|
|
{
|
|
switch(savetype)
|
|
{
|
|
case STAT_SAVETYPE_CHEATER_CHANGE:
|
|
case STAT_SAVETYPE_END_GARAGE:
|
|
case STAT_SAVETYPE_END_MISSION:
|
|
case STAT_SAVETYPE_SCRIPT_MP_GLOBALS:
|
|
case STAT_SAVETYPE_PROLOGUE:
|
|
case STAT_SAVETYPE_START_SESSION:
|
|
case STAT_SAVETYPE_INTERACTION_MENU:
|
|
case STAT_SAVETYPE_AMB_PROFILE_AWARD_TRACKER:
|
|
case STAT_SAVETYPE_END_MISSION_CREATOR:
|
|
case STAT_SAVETYPE_RANKUP:
|
|
case STAT_SAVETYPE_IMMEDIATE_FLUSH:
|
|
case STAT_SAVETYPE_STORE:
|
|
case STAT_SAVETYPE_PRE_STARTSTORE:
|
|
case STAT_SAVETYPE_END_SESSION:
|
|
case STAT_SAVETYPE_END_MATCH:
|
|
case STAT_SAVETYPE_END_CREATE_NEWCHAR:
|
|
case STAT_SAVETYPE_END_GAMER_SETUP:
|
|
case STAT_SAVETYPE_DELETE_CHAR:
|
|
return true;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
|
|
#if __BANK
|
|
static int s_BankFailSlot = -1;
|
|
static bool s_bankResynchProfileStats = false;
|
|
|
|
void
|
|
CStatsSavesMgr::Bank_InitWidgets( bkBank& bank )
|
|
{
|
|
bank.PushGroup("Savegame", false);
|
|
{
|
|
bank.AddSlider("nompsavesometimes", &m_save.m_SimulateFailPct, 0, 100, 1);
|
|
bank.AddSlider("nomploadsometimes", &m_load.m_SimulateFailPct, 0, 100, 1);
|
|
|
|
bank.AddSeparator();
|
|
|
|
bank.AddSlider("Command Value - failmploadalways", &s_BankFailSlot, -1, 5, 1);
|
|
bank.AddButton("Set command Value - failmploadalways", datCallback(MFA(CStatsSavesMgr::Bank_SetFailSlot), (datBase*)this));
|
|
bank.AddButton("Clear command Value - failmploadalways", datCallback(MFA(CStatsSavesMgr::Bank_ClearFailSlot), (datBase*)this));
|
|
bank.AddToggle("ReSynch Multiplayer Profile Stats on next Game Load", &s_bankResynchProfileStats);
|
|
}
|
|
bank.PopGroup();
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::Bank_SetFailSlot()
|
|
{
|
|
if (-1 == s_BankFailSlot)
|
|
PARAM_failmploadalways.Set("-1");
|
|
else if (0 == s_BankFailSlot)
|
|
PARAM_failmploadalways.Set("0");
|
|
else if (1 == s_BankFailSlot)
|
|
PARAM_failmploadalways.Set("1");
|
|
else if (2 == s_BankFailSlot)
|
|
PARAM_failmploadalways.Set("2");
|
|
else if (3 == s_BankFailSlot)
|
|
PARAM_failmploadalways.Set("3");
|
|
else if (4 == s_BankFailSlot)
|
|
PARAM_failmploadalways.Set("4");
|
|
else if (5 == s_BankFailSlot)
|
|
PARAM_failmploadalways.Set("5");
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::Bank_ClearFailSlot()
|
|
{
|
|
PARAM_failmploadalways.Set(NULL);
|
|
}
|
|
|
|
#endif
|
|
|
|
void
|
|
CStatsSavesMgr::Initialize()
|
|
{
|
|
if (m_Initialized)
|
|
return;
|
|
|
|
m_Initialized = true;
|
|
|
|
m_load.Reset();
|
|
m_save.Reset();
|
|
|
|
#if !__FINAL
|
|
XPARAM(level);
|
|
const char* levelName = NULL;
|
|
if (PARAM_level.Get(levelName))
|
|
{
|
|
if (atHashString(levelName) != atHashString("gta5",0x6D2855E0))
|
|
{
|
|
PARAM_nompsavegame.Set("0");
|
|
PARAM_disableOnlineStatsAreSynched.Set("0");
|
|
}
|
|
}
|
|
|
|
if (!PARAM_nompsavesometimes.Get(m_save.m_SimulateFailPct))
|
|
{
|
|
m_save.m_SimulateFailPct = 0;
|
|
}
|
|
|
|
if (!PARAM_nomploadsometimes.Get(m_load.m_SimulateFailPct))
|
|
{
|
|
m_load.m_SimulateFailPct = 0;
|
|
}
|
|
|
|
if (PARAM_nonetwork.Get() || PARAM_lan.Get() || PARAM_nompsavegame.Get() || PARAM_cloudForceAvailable.Get())
|
|
{
|
|
for (int i=0; i<TOTAL_NUMBER_OF_FILES; i++)
|
|
{
|
|
ClearLoadPending(i);
|
|
StatsInterface::SetAllStatsToSynched(i, true, true);
|
|
}
|
|
|
|
CProfileStats& profileStatsMgr = CLiveManager::GetProfileStatsMgr();
|
|
profileStatsMgr.SetSynchronized( CProfileStats::PS_SYNC_MP NOTFINAL_ONLY(, true) );
|
|
}
|
|
|
|
int slot = -1;
|
|
if (PARAM_failmpload.Get(slot) || PARAM_failmploadalways.Get(slot))
|
|
{
|
|
if (-1 < slot)
|
|
{
|
|
ClearLoadPending(slot);
|
|
SetLoadFailed(slot, LOAD_FAILED_REASON_FAILED_TO_LOAD);
|
|
}
|
|
else
|
|
{
|
|
for (int i=0; i<TOTAL_NUMBER_OF_FILES; i++)
|
|
{
|
|
ClearLoadPending(i);
|
|
SetLoadFailed(i, LOAD_FAILED_REASON_FAILED_TO_LOAD);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (PARAM_nonetwork.Get() || PARAM_lan.Get() || PARAM_nompsavegame.Get() || PARAM_cloudForceAvailable.Get() || PARAM_failmpload.Get() || PARAM_failmploadalways.Get())
|
|
{
|
|
statWarningf("Cloud operations disable:");
|
|
statWarningf(" .... PARAM_nonetwork = %s", PARAM_nonetwork.Get() ? "True":"False");
|
|
statWarningf(" .... PARAM_lan = %s", PARAM_lan.Get() ? "True":"False");
|
|
statWarningf(" .... PARAM_nompsavegame = %s", PARAM_nompsavegame.Get() ? "True":"False");
|
|
statWarningf(" .... PARAM_cloudForceAvailable = %s", PARAM_cloudForceAvailable.Get() ? "True":"False");
|
|
statWarningf(" .... PARAM_failmpload = %s, %d", PARAM_failmpload.Get() ? "True":"False", slot);
|
|
statWarningf(" .... PARAM_failmploadalways = %s, %d", PARAM_failmploadalways.Get() ? "True":"False", slot);
|
|
}
|
|
#endif // !__FINAL
|
|
|
|
#if __MPCLOUDSAVES_ONLY_ONEFILE
|
|
for (int i=STAT_MP_CATEGORY_CHAR0; i<TOTAL_NUMBER_OF_FILES; i++)
|
|
{
|
|
ClearLoadPending(i);
|
|
ClearLoadFailed(i);
|
|
}
|
|
#endif // __MPSAVES_ONLY_ONEFILE
|
|
|
|
int minProfileFlush = eSaveOperations::MIN_PROFILESTATS_FLUSH;
|
|
Tunables::GetInstance().Access(CD_GLOBAL_HASH, ATSTRINGHASH("MPSAVE_MIN_PROFILESTATS_FLUSH", 0x8d400e25), minProfileFlush);
|
|
m_save.m_minForceFlushTime = (u32)minProfileFlush;
|
|
|
|
int delaySaveFailed = eSaveOperations::DELAY_NEXT_SAVE;
|
|
Tunables::GetInstance().Access(CD_GLOBAL_HASH, ATSTRINGHASH("MPSAVE_DELAY_NEXT_SAVE", 0x53e403b9), delaySaveFailed);
|
|
m_save.m_delaySaveFailed = (u32)delaySaveFailed;
|
|
|
|
m_DirtyProfileStatsReadDetected = false;
|
|
m_DirtyCloudReadDetected = false;
|
|
m_DirtyReadServerTime = 0;
|
|
m_DirtyReadCloudFile = -1;
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::Init(const unsigned initMode)
|
|
{
|
|
if(INIT_CORE == initMode)
|
|
{
|
|
statAssert(MEM_CARD_BUSY != CGenericGameStorage::GetMpStatsLoadStatus());
|
|
statAssert(!IsLoadInProgress(STAT_INVALID_SLOT));
|
|
|
|
statAssert(MEM_CARD_BUSY != CGenericGameStorage::GetMpStatsSaveStatus());
|
|
statAssert(!IsSaveInProgress());
|
|
}
|
|
else if(INIT_AFTER_MAP_LOADED == initMode)
|
|
{
|
|
}
|
|
else if (INIT_SESSION == initMode)
|
|
{
|
|
statAssert( MEM_CARD_BUSY != CGenericGameStorage::GetMpStatsSaveStatus() );
|
|
statAssert(!IsSaveInProgress());
|
|
|
|
Initialize();
|
|
}
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::Shutdown(unsigned shutdownMode)
|
|
{
|
|
if (!m_Initialized)
|
|
return;
|
|
|
|
if(shutdownMode == SHUTDOWN_CORE || shutdownMode == SHUTDOWN_SESSION)
|
|
{
|
|
SignedOffline();
|
|
m_Initialized = false;
|
|
}
|
|
else if(shutdownMode == SHUTDOWN_WITH_MAP_LOADED)
|
|
{
|
|
}
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::SignedOffline()
|
|
{
|
|
statDebugf1("SignedOffline - m_Initialized=%s", m_Initialized?"true":"false");
|
|
|
|
if (m_Initialized)
|
|
{
|
|
statDebugf1("SignedOffline - m_save.m_InProgress=%s", m_save.m_InProgress?"true":"false");
|
|
statDebugf1("SignedOffline - m_load.m_InProgress=%s", m_load.m_InProgress?"true":"false");
|
|
|
|
if (m_save.m_InProgress && (MEM_CARD_BUSY == CGenericGameStorage::GetMpStatsSaveStatus()))
|
|
CGenericGameStorage::CancelMpStatsSave();
|
|
|
|
if (m_load.m_InProgress && (MEM_CARD_BUSY == CGenericGameStorage::GetMpStatsLoadStatus()))
|
|
CGenericGameStorage::CancelMpStatsLoad();
|
|
|
|
m_save.Shutdown();
|
|
m_load.Shutdown();
|
|
}
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::Update()
|
|
{
|
|
// No network
|
|
if (PARAM_nonetwork.Get() || PARAM_lan.Get() || PARAM_nompsavegame.Get() || PARAM_cloudForceAvailable.Get())
|
|
return;
|
|
|
|
//Only load if the player is signed in.
|
|
if (!NetworkInterface::IsLocalPlayerSignedIn())
|
|
return;
|
|
|
|
//Cloud Storage
|
|
UpdateCloudStorage( );
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::CheckForProfileStatsDirtyRead( )
|
|
{
|
|
statDebugf1(" Check For Profile Stats Dirty Read ");
|
|
|
|
bool dirtyRead = false;
|
|
|
|
//Set Local value for last MP flush.
|
|
CProfileSettings& settings = CProfileSettings::GetInstance();
|
|
if(!settings.AreSettingsValid())
|
|
{
|
|
statDebugf1("Abort CheckForProfileStatsDirtyRead, Profile settings not valid, probably a login or logout in the same frame.");
|
|
return true;
|
|
}
|
|
|
|
u64 low32 = 0;
|
|
if(settings.Exists(CProfileSettings::MP_FLUSH_POSIXTIME_LOW32))
|
|
low32 = static_cast< u64 >( settings.GetInt(CProfileSettings::MP_FLUSH_POSIXTIME_LOW32) );
|
|
|
|
u64 high32 = 0;
|
|
if(settings.Exists(CProfileSettings::MP_FLUSH_POSIXTIME_HIGH32))
|
|
high32 = static_cast< u64 >( settings.GetInt(CProfileSettings::MP_FLUSH_POSIXTIME_HIGH32) );
|
|
|
|
u64 localflushPosixTime = high32 << 32;
|
|
localflushPosixTime = localflushPosixTime | low32;
|
|
|
|
#if !__FINAL
|
|
int overrideTime = 0;
|
|
if (PARAM_changeLocalProfileTime.Get(overrideTime))
|
|
{
|
|
//Change the sign of overrideTime
|
|
const int nosignvalue = (-1 * overrideTime);
|
|
|
|
if (overrideTime > 0)
|
|
localflushPosixTime += (u64)overrideTime;
|
|
else if (overrideTime < 0 && localflushPosixTime > (u64)(nosignvalue))
|
|
localflushPosixTime -= (u64)(nosignvalue);
|
|
}
|
|
#endif //!__FINAL
|
|
|
|
const u64 flushPosixTime = static_cast< u64 >( StatsInterface::GetInt64Stat( StatId("PROFILE_STATS_LAST_FLUSH") ) );
|
|
|
|
m_DirtyProfileStatsReadDetected = false;
|
|
|
|
//Dirty read detected
|
|
if (localflushPosixTime > flushPosixTime)
|
|
{
|
|
const u64 currentPosixTime = rlGetPosixTime();
|
|
|
|
statDebugf1(" .... Dirty Profile Stats read detected: ");
|
|
SPEW_POSIX_TIME( localflushPosixTime, "Local Time" );
|
|
SPEW_POSIX_TIME( flushPosixTime, "Server Time" );
|
|
SPEW_POSIX_TIME( currentPosixTime, "Current Time" );
|
|
NOTFINAL_ONLY(statDebugf1("........ Override local Time = '%d'", overrideTime);)
|
|
|
|
//4 hours max wait time.
|
|
int timeoutPeriod = 14400;
|
|
Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("MP_DIRTY_READ_TIMEOUT_SECONDS", 0x5854f55c), timeoutPeriod);
|
|
NOTFINAL_ONLY( bool overrideTimeoutPeriod = PARAM_dirtyreadtimeoutPeriod.Get(timeoutPeriod); )
|
|
|
|
s64 diff = currentPosixTime - (localflushPosixTime+timeoutPeriod);
|
|
statDebugf1("........ Timeout='%d', Diff='%" I64FMT "d'", timeoutPeriod, diff);
|
|
|
|
//Only set to failed due to dirty reads if we are still under a certain time period.
|
|
dirtyRead = (diff < 0);
|
|
|
|
NOTFINAL_ONLY( if ( overrideTimeoutPeriod && timeoutPeriod == 0) )
|
|
NOTFINAL_ONLY( dirtyRead = false; )
|
|
}
|
|
|
|
if (dirtyRead)
|
|
{
|
|
m_DirtyProfileStatsReadDetected = true;
|
|
m_DirtyReadServerTime = flushPosixTime;
|
|
|
|
MetricDirtyProfileStatRead m((s64)localflushPosixTime);
|
|
CNetworkTelemetry::AppendMetric(m);
|
|
|
|
statWarningf(" Dirty Profile Stats read detected and set to true. ");
|
|
}
|
|
|
|
return dirtyRead;
|
|
}
|
|
|
|
u64
|
|
CStatsSavesMgr::GetLocalCloudDirtyReadValue( const u32 file )
|
|
{
|
|
u64 low32 = 0;
|
|
u64 high32 = 0;
|
|
|
|
//Set Local value for last MP flush.
|
|
CProfileSettings& settings = CProfileSettings::GetInstance();
|
|
statAssert( settings.AreSettingsValid() );
|
|
|
|
switch ( file )
|
|
{
|
|
case STAT_MP_CATEGORY_CHAR0:
|
|
{
|
|
if(settings.Exists(CProfileSettings::MP_CLOUD0_POSIXTIME_LOW32))
|
|
low32 = static_cast< u64 >( settings.GetInt(CProfileSettings::MP_CLOUD0_POSIXTIME_LOW32) );
|
|
if(settings.Exists(CProfileSettings::MP_CLOUD0_POSIXTIME_HIGH32))
|
|
high32 = static_cast< u64 >( settings.GetInt(CProfileSettings::MP_CLOUD0_POSIXTIME_HIGH32) );
|
|
}
|
|
break;
|
|
|
|
case STAT_MP_CATEGORY_CHAR1:
|
|
{
|
|
if(settings.Exists(CProfileSettings::MP_CLOUD1_POSIXTIME_LOW32))
|
|
low32 = static_cast< u64 >( settings.GetInt(CProfileSettings::MP_CLOUD1_POSIXTIME_LOW32) );
|
|
if(settings.Exists(CProfileSettings::MP_CLOUD1_POSIXTIME_HIGH32))
|
|
high32 = static_cast< u64 >( settings.GetInt(CProfileSettings::MP_CLOUD1_POSIXTIME_HIGH32) );
|
|
}
|
|
break;
|
|
|
|
case STAT_MP_CATEGORY_DEFAULT:
|
|
{
|
|
if(settings.Exists(CProfileSettings::MP_CLOUD_POSIXTIME_LOW32))
|
|
low32 = static_cast< u64 >( settings.GetInt(CProfileSettings::MP_CLOUD_POSIXTIME_LOW32) );
|
|
if(settings.Exists(CProfileSettings::MP_CLOUD_POSIXTIME_HIGH32))
|
|
high32 = static_cast< u64 >( settings.GetInt(CProfileSettings::MP_CLOUD_POSIXTIME_HIGH32) );
|
|
}
|
|
break;
|
|
}
|
|
|
|
u64 result = high32 << 32;
|
|
result = result | low32;
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::ClearLocalDirtyReadProfileSettings()
|
|
{
|
|
CProfileSettings& settings = CProfileSettings::GetInstance();
|
|
if (!settings.AreSettingsValid())
|
|
{
|
|
statDebugf1("Abort ClearLocalDirtyReadProfileSettings, settings are not valid.");
|
|
return;
|
|
}
|
|
|
|
statWarningf(" **** Clear local Dirty Read profile settings **** ");
|
|
settings.Set(CProfileSettings::MP_FLUSH_POSIXTIME_LOW32, 0);
|
|
settings.Set(CProfileSettings::MP_FLUSH_POSIXTIME_HIGH32, 0);
|
|
settings.Set(CProfileSettings::MP_CLOUD0_POSIXTIME_LOW32, 0);
|
|
settings.Set(CProfileSettings::MP_CLOUD0_POSIXTIME_HIGH32, 0);
|
|
settings.Set(CProfileSettings::MP_CLOUD1_POSIXTIME_LOW32, 0);
|
|
settings.Set(CProfileSettings::MP_CLOUD1_POSIXTIME_HIGH32, 0);
|
|
settings.Set(CProfileSettings::MP_CLOUD_POSIXTIME_LOW32, 0);
|
|
settings.Set(CProfileSettings::MP_CLOUD_POSIXTIME_HIGH32, 0);
|
|
|
|
settings.Write(true);
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::CheckForCloudDirtyRead( const u32 file )
|
|
{
|
|
statDebugf1(" Check For Cloud Dirty Read ");
|
|
|
|
CProfileSettings& settings = CProfileSettings::GetInstance();
|
|
if(!settings.AreSettingsValid())
|
|
{
|
|
statDebugf1("Abort CheckForProfileStatsDirtyRead, Profile settings not valid, probably a login or logout in the same frame.");
|
|
return true;
|
|
}
|
|
|
|
bool dirtyRead = false;
|
|
|
|
u64 localCloudPosixTime = GetLocalCloudDirtyReadValue( file );
|
|
|
|
#if !__FINAL
|
|
int overrideTime = 0;
|
|
if (PARAM_changeLocalCloudTime.Get(overrideTime))
|
|
{
|
|
//Change the sign of overrideTime
|
|
const int nosignvalue = (-1 * overrideTime);
|
|
|
|
if (overrideTime > 0)
|
|
localCloudPosixTime += (u64)overrideTime;
|
|
else if (overrideTime < 0 && localCloudPosixTime > (u64)(nosignvalue))
|
|
localCloudPosixTime -= (u64)(nosignvalue);
|
|
}
|
|
#endif //!__FINAL
|
|
|
|
//Check timestamp for Multi Player
|
|
const char* statName = "_SaveMpTimestamp_0";
|
|
switch ( file )
|
|
{
|
|
case STAT_MP_CATEGORY_DEFAULT: statName = "_SaveMpTimestamp_0"; break;
|
|
case STAT_MP_CATEGORY_CHAR0: statName = "_SaveMpTimestamp_1"; break;
|
|
case STAT_MP_CATEGORY_CHAR1: statName = "_SaveMpTimestamp_2"; break;
|
|
}
|
|
StatId statTimestamp(statName);
|
|
|
|
const u64 cloudFilePosixTime = StatsInterface::GetUInt64Stat( statTimestamp );
|
|
|
|
m_DirtyCloudReadDetected = false;
|
|
m_DirtyReadCloudFile = -1;
|
|
|
|
//Dirty read detected
|
|
if (localCloudPosixTime > cloudFilePosixTime)
|
|
{
|
|
const u64 currentPosixTime = rlGetPosixTime();
|
|
|
|
statDebugf1(" .... Dirty Cloud read detected: ");
|
|
SPEW_POSIX_TIME( localCloudPosixTime, "Local Time" );
|
|
SPEW_POSIX_TIME( cloudFilePosixTime, "Server Time" );
|
|
SPEW_POSIX_TIME( currentPosixTime, "Current Time" );
|
|
NOTFINAL_ONLY(statDebugf1("........ Override local Time = '%d'", overrideTime);)
|
|
|
|
//4 hours max wait time.
|
|
int timeoutPeriod = 14400;
|
|
Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("MP_DIRTY_READ_TIMEOUT_SECONDS", 0x5854f55c), timeoutPeriod);
|
|
NOTFINAL_ONLY( bool overrideTimeoutPeriod = PARAM_dirtycloudreadtimeoutPeriod.Get( timeoutPeriod ); )
|
|
|
|
s64 diff = currentPosixTime - (localCloudPosixTime+timeoutPeriod);
|
|
statDebugf1("........ Timeout='%d', Diff='%" I64FMT "d'", timeoutPeriod, diff);
|
|
|
|
//Only set to failed due to dirty reads if we are still under a certain time period.
|
|
dirtyRead = (diff < 0);
|
|
|
|
NOTFINAL_ONLY( if ( overrideTimeoutPeriod && timeoutPeriod == 0) )
|
|
NOTFINAL_ONLY( dirtyRead = false; )
|
|
}
|
|
|
|
if (dirtyRead)
|
|
{
|
|
m_DirtyCloudReadDetected = true;
|
|
m_DirtyReadCloudFile = file;
|
|
m_DirtyReadServerTime = cloudFilePosixTime;
|
|
|
|
MetricDirtyCloudRead m((s64)localCloudPosixTime);
|
|
CNetworkTelemetry::AppendMetric(m);
|
|
|
|
statWarningf(" Dirty Cloud read detected and set to true. ");
|
|
}
|
|
|
|
return dirtyRead;
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::UpdateCloudStorage( )
|
|
{
|
|
if (!NetworkInterface::IsCloudAvailable())
|
|
return;
|
|
|
|
if (!NetworkInterface::HasValidRockstarId() && StatsInterface::GetCloudSavegameService() == RL_CLOUD_ONLINE_SERVICE_SC)
|
|
return;
|
|
|
|
if (!CLiveManager::CheckOnlinePrivileges())
|
|
return;
|
|
|
|
//Loading Profile stats...
|
|
if (m_SynchProfileStats)
|
|
{
|
|
CProfileStats& profileStatsMgr = CLiveManager::GetProfileStatsMgr();
|
|
m_SynchProfileStats = profileStatsMgr.SynchronizePending(CProfileStats::PS_SYNC_MP);
|
|
|
|
if (!m_SynchProfileStats)
|
|
{
|
|
CSavingMessage::Clear();
|
|
statDebugf1("Cloud storage: Profile Stats LOAD Ended.");
|
|
|
|
int errorCode = 0;
|
|
if ( profileStatsMgr.SynchronizedFailed(CProfileStats::PS_SYNC_MP, errorCode) )
|
|
{
|
|
statErrorf("Cloud storage: Profile Stats LOAD Failed.");
|
|
|
|
APPEND_SERVICE_FAILURE(m_LoadUid, MetricServiceFailed::PS_HTTP_FAIL, errorCode)
|
|
|
|
ClearLoadPending(STAT_MP_CATEGORY_DEFAULT);
|
|
SetLoadFailed(STAT_MP_CATEGORY_DEFAULT, LOAD_FAILED_REASON_FAILED_TO_LOAD);
|
|
m_load.m_OverrideSlot = m_load.m_CurrentSlot = STAT_MP_CATEGORY_DEFAULT;
|
|
profileStatsMgr.ForceMPProfileStatsGetFromCloud();
|
|
return;
|
|
}
|
|
else if ( CheckForProfileStatsDirtyRead( ) )
|
|
{
|
|
statErrorf("Cloud storage: Profile Stats DIRTY READ Detected.");
|
|
|
|
APPEND_SERVICE_FAILURE(m_LoadUid, MetricServiceFailed::PS_DIRTY_READ, 0)
|
|
|
|
ClearLoadPending(STAT_MP_CATEGORY_DEFAULT);
|
|
SetLoadFailed(STAT_MP_CATEGORY_DEFAULT, LOAD_FAILED_REASON_DIRTY_PROFILE_STAT_READ);
|
|
m_load.m_OverrideSlot = m_load.m_CurrentSlot = STAT_MP_CATEGORY_DEFAULT;
|
|
profileStatsMgr.ForceMPProfileStatsGetFromCloud();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
m_load.m_OverrideSlot = m_load.m_CurrentSlot = STAT_MP_CATEGORY_DEFAULT;
|
|
}
|
|
}
|
|
}
|
|
|
|
//Loading...
|
|
if (m_load.m_InProgress)
|
|
{
|
|
MemoryCardError status = CGenericGameStorage::GetMpStatsLoadStatus();
|
|
|
|
m_load.m_InProgress = (MEM_CARD_BUSY == status);
|
|
|
|
if (!m_load.m_InProgress)
|
|
{
|
|
statDebugf1("Cloud storage: LOAD finished for category=%d.", m_load.m_CurrentSlot);
|
|
|
|
CSavingMessage::Clear();
|
|
|
|
ClearLoadPending(m_load.m_CurrentSlot);
|
|
|
|
eReasonForFailureToLoadSavegame failureCode = CGenericGameStorage::GetReasonForFailureToLoadSavegame();
|
|
|
|
#if !__FINAL
|
|
if (MEM_CARD_COMPLETE == status)
|
|
{
|
|
bool forceFail = false;
|
|
int slot = -1;
|
|
if (PARAM_failmploadalways.Get(slot))
|
|
{
|
|
forceFail = (slot == -1 || slot == (int)m_load.m_CurrentSlot);
|
|
}
|
|
|
|
if(!forceFail && m_load.m_SimulateFailPct)
|
|
{
|
|
statDebugf1("Cloud storage: LOAD finished - Simulating failure %d%% of the time...", m_load.m_SimulateFailPct);
|
|
if(s_cloudSavesRng.GetRanged(1, 100) <= m_load.m_SimulateFailPct)
|
|
{
|
|
statDebugf1("Cloud storage: LOAD finished - Failure simulated!");
|
|
forceFail = true;
|
|
status = MEM_CARD_ERROR;
|
|
failureCode = LOAD_FAILED_REASON_FILE_CORRUPT;
|
|
}
|
|
else
|
|
{
|
|
statDebugf1("Cloud storage: LOAD finished - Failure averted!");
|
|
}
|
|
}
|
|
}
|
|
#endif // !__FINAL
|
|
|
|
//Check for error file not found.
|
|
if (MEM_CARD_ERROR == status && LOAD_FAILED_REASON_FILE_NOT_FOUND == failureCode)
|
|
{
|
|
switch (m_load.m_CurrentSlot)
|
|
{
|
|
case STAT_MP_CATEGORY_DEFAULT:
|
|
{
|
|
status = MEM_CARD_COMPLETE;
|
|
statDebugf1("Cloud storage: LOAD finished - Clear DEFAULT error code LOAD_FAILED_REASON_FILE_NOT_FOUND!");
|
|
ClearLocalDirtyReadProfileSettings();
|
|
}
|
|
break;
|
|
|
|
case STAT_MP_CATEGORY_CHAR0:
|
|
{
|
|
if (!StatsInterface::GetBooleanStat(STAT_MP0_CHAR_ISACTIVE))
|
|
{
|
|
status = MEM_CARD_COMPLETE;
|
|
statDebugf1("Cloud storage: LOAD finished - Clear MP0 error code LOAD_FAILED_REASON_FILE_NOT_FOUND!");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case STAT_MP_CATEGORY_CHAR1:
|
|
{
|
|
if (!StatsInterface::GetBooleanStat(STAT_MP1_CHAR_ISACTIVE))
|
|
{
|
|
status = MEM_CARD_COMPLETE;
|
|
statDebugf1("Cloud storage: LOAD finished - Clear MP1 error code LOAD_FAILED_REASON_FILE_NOT_FOUND!");
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (status == MEM_CARD_COMPLETE)
|
|
{
|
|
//CGenericGameStorage::SetReasonForFailureToLoadSavegame( LOAD_FAILED_REASON_NONE );
|
|
failureCode = LOAD_FAILED_REASON_NONE;
|
|
}
|
|
}
|
|
|
|
//Check for Dirty Reads
|
|
if ( MEM_CARD_ERROR != status && CheckForCloudDirtyRead( m_load.m_CurrentSlot ) )
|
|
{
|
|
statErrorf("Cloud storage: Cloud Dirty Read Detected on slot='%d'", m_load.m_CurrentSlot);
|
|
failureCode = LOAD_FAILED_REASON_DIRTY_CLOUD_READ;
|
|
status = MEM_CARD_ERROR;
|
|
}
|
|
|
|
//Failed to load the cload savegame
|
|
if (MEM_CARD_ERROR == status)
|
|
{
|
|
APPEND_SERVICE_FAILURE(m_LoadUid, failureCode, CGenericGameStorage::GetCloudSaveResultcode())
|
|
|
|
statWarningf("Cloud storage: LOAD finished - FAILED.");
|
|
|
|
SetLoadFailed(m_load.m_CurrentSlot, failureCode);
|
|
|
|
CGenericGameStorage::ClearMpStatsLoadStatus();
|
|
}
|
|
|
|
#if __MPCLOUDSAVES_ONLY_ONEFILE
|
|
for (int i=STAT_MP_CATEGORY_CHAR0; i<TOTAL_NUMBER_OF_FILES; i++)
|
|
{
|
|
ClearLoadPending(i);
|
|
ClearLoadFailed(i);
|
|
}
|
|
#endif // __MPSAVES_ONLY_ONEFILE
|
|
|
|
#if !__FINAL
|
|
if (STAT_MP_CATEGORY_DEFAULT == m_load.m_CurrentSlot && !CloudLoadFailed(STAT_MP_CATEGORY_DEFAULT))
|
|
{
|
|
int numActiveSlots = 0;
|
|
if(PARAM_mpactiveslots.Get(numActiveSlots))
|
|
{
|
|
if (numActiveSlots > 1)
|
|
{
|
|
StatsInterface::SetStatData(STAT_MP0_CHAR_ISACTIVE, true);
|
|
StatsInterface::SetStatData(STAT_MP1_CHAR_ISACTIVE, true);
|
|
}
|
|
}
|
|
|
|
int activeChar = 0;
|
|
if(PARAM_mpactivechar.Get(activeChar))
|
|
{
|
|
int activeSlot = activeChar+1;
|
|
|
|
if (statVerify(STAT_MP_CATEGORY_DEFAULT < activeSlot) && statVerify(activeSlot < MAX_NUM_MP_CHARS))
|
|
{
|
|
StatsInterface::SetStatData(STAT_MPPLY_LAST_MP_CHAR, activeChar);
|
|
switch (activeSlot)
|
|
{
|
|
case STAT_MP_CATEGORY_CHAR0: StatsInterface::SetStatData(STAT_MP0_CHAR_ISACTIVE, true); break;
|
|
case STAT_MP_CATEGORY_CHAR1: StatsInterface::SetStatData(STAT_MP1_CHAR_ISACTIVE, true); break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//Load 2nd file - current character selected slot.
|
|
if (STAT_MP_CATEGORY_DEFAULT == m_load.m_CurrentSlot && !CloudLoadFailed(STAT_MP_CATEGORY_DEFAULT) && QueueCheckCloudFileLoadPending())
|
|
{
|
|
// -------- Do nothing carry on with load.
|
|
QueueLoad(STATS_LOAD_CLOUD);
|
|
}
|
|
else
|
|
{
|
|
m_load.m_OverrideSlot = m_load.m_CurrentSlot = STAT_MP_CATEGORY_DEFAULT;
|
|
}
|
|
|
|
//Now that 2nd file has completed, sync profile stats if we need to.
|
|
if (!m_load.m_InProgress && !IsLoadPending(STAT_MP_CATEGORY_DEFAULT) && !DirtyCloudReadDetected( ))
|
|
{
|
|
CProfileStats& profileStatsMgr = CLiveManager::GetProfileStatsMgr();
|
|
|
|
#if __BANK
|
|
if (s_bankResynchProfileStats)
|
|
{
|
|
s_bankResynchProfileStats = false;
|
|
|
|
if (profileStatsMgr.SynchronizePending(CProfileStats::PS_SYNC_MP))
|
|
profileStatsMgr.CanSynchronize( true );
|
|
|
|
if (profileStatsMgr.Synchronized(CProfileStats::PS_SYNC_MP))
|
|
profileStatsMgr.ClearSynchronized( CProfileStats::PS_SYNC_MP );
|
|
}
|
|
#endif // __BANK
|
|
|
|
if (!profileStatsMgr.SynchronizePending(CProfileStats::PS_SYNC_MP) && !profileStatsMgr.Synchronized(CProfileStats::PS_SYNC_MP))
|
|
{
|
|
if (!statVerify(profileStatsMgr.CanSynchronize( true )))
|
|
{
|
|
m_SynchProfileStats = false;
|
|
}
|
|
else
|
|
{
|
|
m_SynchProfileStats = profileStatsMgr.Synchronize(true, true);
|
|
}
|
|
|
|
statAssertf(m_SynchProfileStats, "Failed to start synch profile of profile stats");
|
|
if (!m_SynchProfileStats)
|
|
{
|
|
ClearLoadPending(m_load.m_CurrentSlot);
|
|
SetLoadFailed(m_load.m_CurrentSlot, LOAD_FAILED_REASON_FAILED_TO_LOAD);
|
|
}
|
|
else
|
|
{
|
|
CSavingMessage::BeginDisplaying( CSavingMessage::STORAGE_DEVICE_MESSAGE_CLOUD_LOADING );
|
|
statDebugf1("Cloud storage: Profile Stats LOAD Started");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//Saving...
|
|
else if (m_save.m_InProgress)
|
|
{
|
|
const MemoryCardError status = CGenericGameStorage::GetMpStatsSaveStatus();
|
|
m_save.m_InProgress = (MEM_CARD_BUSY == status);
|
|
|
|
if (!m_save.m_InProgress)
|
|
{
|
|
CSavingMessage::Clear();
|
|
|
|
bool saveFailed = (MEM_CARD_ERROR == status);
|
|
//Retry save for 408, 429, and 504.
|
|
int resultcode = CGenericGameStorage::GetCloudSaveResultcode();
|
|
|
|
#if !__FINAL
|
|
if(!saveFailed && m_save.m_SimulateFailPct)
|
|
{
|
|
statDebugf1("Cloud storage: SAVE finished - Simulating failure %d%% of the time...", m_save.m_SimulateFailPct);
|
|
if(s_cloudSavesRng.GetRanged(1, 100) <= m_save.m_SimulateFailPct)
|
|
{
|
|
statDebugf1("Cloud storage: SAVE finished - Failure simulated!");
|
|
saveFailed = true;
|
|
resultcode = HTTP_CODE_REQUESTTIMEOUT;
|
|
}
|
|
else
|
|
{
|
|
statDebugf1("Cloud storage: SAVE finished - Failure averted!");
|
|
}
|
|
}
|
|
#endif // !__FINAL
|
|
|
|
CSaveTelemetryStats cachedSaveTelemetry;
|
|
cachedSaveTelemetry = m_save.m_InProgressSaveTelemetry;
|
|
|
|
if (saveFailed)
|
|
{
|
|
//Send savegame telemetry.
|
|
m_save.m_InProgressSaveTelemetry.FlushTelemetry(true, true);
|
|
|
|
SetSaveFailed(m_save.m_CurrentSlot, resultcode);
|
|
|
|
if (m_save.m_RetryFailedSave<eSaveOperations::MAX_NUMBER_RETRY_SAVE && RetrySaveOnHttpErrorCode(resultcode))
|
|
{
|
|
m_save.m_RetryFailedSave += 1;
|
|
m_save.m_NextSaveTime = sysTimer::GetSystemMsTime() + (eSaveOperations::DELAY_FAILED_SAVE*m_save.m_RetryFailedSave);
|
|
m_save.m_RequestSlot |= (1<<m_save.m_CurrentSlot);
|
|
|
|
if (m_save.m_CurrentSlotIsCritical)
|
|
{
|
|
m_save.m_IsCritical |= (1<<m_save.m_CurrentSlot);
|
|
}
|
|
|
|
statWarningf("Cloud storage: SAVE finished - FAILED, result code is %d, retry save in %ds.", resultcode, (eSaveOperations::DELAY_FAILED_SAVE*m_save.m_RetryFailedSave)/1000);
|
|
}
|
|
else
|
|
{
|
|
statAssertf(0, "Cloud storage: SAVE finished - FAILED, result code is %d", resultcode);
|
|
|
|
//We are not going to retry any more saves so clear the Critical.
|
|
m_save.m_CurrentSlotIsCritical = false;
|
|
}
|
|
|
|
m_save.m_CurrentSlot = STAT_MP_CATEGORY_DEFAULT;
|
|
|
|
CGenericGameStorage::ClearMpStatsSaveStatus();
|
|
}
|
|
else
|
|
{
|
|
statDebugf1("Cloud storage: SAVE finished.");
|
|
|
|
//Send savegame telemetry.
|
|
if (STAT_MP_CATEGORY_DEFAULT < m_save.m_CurrentSlot)
|
|
{
|
|
m_save.m_InProgressSaveTelemetry.FlushTelemetry(false, true);
|
|
}
|
|
|
|
//Clear Failed Save
|
|
ClearSaveFailed(m_save.m_CurrentSlot);
|
|
|
|
//Reset save retries.
|
|
m_save.m_RetryFailedSave = 0;
|
|
|
|
//Only clear the critical if it was successful.
|
|
m_save.m_CurrentSlotIsCritical = false;
|
|
|
|
//Set local slot Dirty Read timestamp.
|
|
SaveLocalCloudDirtyReadTimestamp();
|
|
}
|
|
|
|
#if !__MPCLOUDSAVES_ONLY_ONEFILE
|
|
//Save 2nd file - current character selected slot.
|
|
if (!saveFailed && STAT_MP_CATEGORY_DEFAULT == m_save.m_CurrentSlot)
|
|
{
|
|
SetupSavegameSlot(true);
|
|
QueueSave(STATS_SAVE_CLOUD);
|
|
}
|
|
else
|
|
#endif // __MPSAVES_ONLY_ONEFILE
|
|
{
|
|
m_save.m_OverrideSlot = m_save.m_CurrentSlot = STAT_MP_CATEGORY_DEFAULT;
|
|
|
|
if(!saveFailed && PendingSaveRequests())
|
|
{
|
|
const int slot = GetNextPendingSaveRequest();
|
|
if (STAT_INVALID_SLOT < slot)
|
|
{
|
|
statDebugf1("Cloud storage: SAVE request pending for file %d.", slot);
|
|
m_save.m_CurrentSlot = slot;
|
|
QueueSave(STATS_SAVE_CLOUD);
|
|
}
|
|
}
|
|
else if (!saveFailed && (m_save.m_flushProfileStat))
|
|
{
|
|
#if __ASSERT
|
|
const u32 currentTime = fwTimer::GetSystemTimeInMilliseconds();
|
|
if (currentTime-m_save.m_lastFlushProfileStatTime <= m_save.m_minForceFlushTime && !m_save.m_lastFlushProfileStatWasDeferred)
|
|
{
|
|
statAssertf(!m_save.m_IsCritical, "Profile stats being flushed in the again, time since last flush=%d, min flush is %d seconds", currentTime-m_save.m_lastFlushProfileStatTime, m_save.m_minForceFlushTime/1000);
|
|
}
|
|
|
|
statDebugf1("Cloud storage: SAVE Performing a profile stats FLUSH - Time since last flush %d.", currentTime - m_save.m_lastFlushProfileStatTime);
|
|
#endif //__ASSERT
|
|
|
|
TriggerProfileStatsFlush(cachedSaveTelemetry);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Idle Mode...
|
|
else
|
|
{
|
|
const u32 currtime = sysTimer::GetSystemMsTime();
|
|
|
|
//Check if there are any pending saves.
|
|
if (m_save.m_RequestSlot!=0 && m_save.m_NextSaveTime<currtime)
|
|
{
|
|
if (PendingSaveRequests())
|
|
{
|
|
const int slot = GetNextPendingSaveRequest();
|
|
|
|
if (STAT_INVALID_SLOT < slot)
|
|
{
|
|
statDebugf1("Cloud storage: SAVE request pending for file %d.", slot);
|
|
m_save.m_CurrentSlot = slot;
|
|
QueueSave(STATS_SAVE_CLOUD);
|
|
}
|
|
}
|
|
//Delay further the save
|
|
else
|
|
{
|
|
if (m_save.m_RetryFailedSave > 0)
|
|
{
|
|
m_save.m_NextSaveTime = currtime + (eSaveOperations::DELAY_FAILED_SAVE*m_save.m_RetryFailedSave);
|
|
statDebugf1("Cloud storage: Delay SAVE request further time=%ds.", (eSaveOperations::DELAY_FAILED_SAVE*m_save.m_RetryFailedSave)/1000);
|
|
}
|
|
else
|
|
{
|
|
m_save.m_NextSaveTime = currtime + m_save.m_delaySaveFailed;
|
|
statDebugf1("Cloud storage: Delay SAVE request further time=%ds.", m_save.m_delaySaveFailed/1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
//We only have a DEFERRED Cloud save pending.
|
|
if (m_save.m_deferredCloudSave > 0)
|
|
{
|
|
const u32 currentTime = fwTimer::GetSystemTimeInMilliseconds();
|
|
|
|
if (currentTime-m_save.m_lastcloudSavegame >= m_save.m_deferredCloudSave)
|
|
{
|
|
//Clear cash transactions blocking.
|
|
if (rlTelemetry::GetDeferredFlushBlocked())
|
|
{
|
|
statWarningf("UnBlock Immediate Flush - Deferred Cloud Save being done.");
|
|
rlTelemetry::SetDeferredFlushBlocked( false );
|
|
}
|
|
|
|
statDebugf1("Cloud storage: SAVE Performing Deferred Cloud Save - Time since last flush %d.", currentTime - m_save.m_lastcloudSavegame);
|
|
m_save.m_CurrentSlot = STAT_MP_CATEGORY_DEFAULT;
|
|
QueueSave(STATS_SAVE_CLOUD);
|
|
}
|
|
}
|
|
|
|
CheckForPendingProfileStatsFlush();
|
|
}
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::CheckForPendingProfileStatsFlush( )
|
|
{
|
|
//We only have a DEFERRED Profile stats flush pending.
|
|
if (m_save.m_deferredProfileStatsFlush > 0 && CanFlushProfileStats())
|
|
{
|
|
const u32 currentTime = fwTimer::GetSystemTimeInMilliseconds();
|
|
if ((currentTime-m_save.m_lastFlushProfileStatTime)+1 >= m_save.m_deferredProfileStatsFlush)
|
|
{
|
|
#if __ASSERT
|
|
const u32 currentTime = fwTimer::GetSystemTimeInMilliseconds();
|
|
if (currentTime-m_save.m_lastFlushProfileStatTime <= m_save.m_minForceFlushTime)
|
|
{
|
|
statAssertf(!m_save.m_IsCritical, "Profile stats being flushed in the again, time since last flush=%d, min flush is %d seconds", currentTime-m_save.m_lastFlushProfileStatTime, m_save.m_minForceFlushTime/1000);
|
|
}
|
|
#endif
|
|
|
|
statDebugf1("Cloud storage: SAVE Performing Deferred Profile Stats FLUSH - Time since last flush %d.", currentTime - m_save.m_lastFlushProfileStatTime);
|
|
|
|
//Clear cash transactions blocking.
|
|
if (rlTelemetry::GetDeferredFlushBlocked( ))
|
|
{
|
|
statWarningf("UnBlock Immediate Flush - Deferred Profile Stats Flush being done.");
|
|
rlTelemetry::SetDeferredFlushBlocked( false );
|
|
}
|
|
|
|
TriggerProfileStatsFlush(m_save.m_RequestedSaveTelemetry);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
void
|
|
CStatsSavesMgr::FlushProfileStats( )
|
|
{
|
|
bool bIgnoreServerAuthSync = true;
|
|
Tunables::GetInstance().Access(CD_GLOBAL_HASH, ATSTRINGHASH("IGNORE_CASH_SERVER_VALUES", 0xbd11bd22), bIgnoreServerAuthSync);
|
|
if (bIgnoreServerAuthSync && !GetBlockSaveRequests())
|
|
{
|
|
s_FlushProfileStats = true;
|
|
|
|
//Make an ambient Save, 5 minutes from now!
|
|
if (m_save.m_deferredProfileStatsFlush == 0)
|
|
{
|
|
CPed* player = CGameWorld::FindLocalPlayer();
|
|
if(player && !player->GetIsInInterior() && CStatsMgr::PlayerCharIsValidAndPlayingFreemode())
|
|
{
|
|
Save(STATS_SAVE_CLOUD, STAT_MP_CATEGORY_DEFAULT, STAT_SAVETYPE_PHOTOS);
|
|
|
|
//Wait for a minimun of 60 seconds in case we were going to do it now.
|
|
const u32 currentTime = fwTimer::GetSystemTimeInMilliseconds();
|
|
if (currentTime-m_save.m_lastFlushProfileStatTime >= m_save.m_deferredProfileStatsFlush)
|
|
m_save.m_deferredProfileStatsFlush = ((currentTime-m_save.m_lastFlushProfileStatTime) + 60000);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::SetupSavegameSlot(const bool save, const int slot)
|
|
{
|
|
if (slot < STAT_MP_CATEGORY_DEFAULT)
|
|
{
|
|
//Retrieve the current slot from the chosen character slot.
|
|
if (save)
|
|
{
|
|
m_save.m_CurrentSlot = StatsInterface::GetCurrentMultiplayerCharaterSlot();
|
|
++m_save.m_CurrentSlot;
|
|
}
|
|
else
|
|
{
|
|
m_load.m_CurrentSlot = StatsInterface::GetCurrentMultiplayerCharaterSlot();
|
|
|
|
//Make sure the character slot 0 is really being used
|
|
if (m_load.m_CurrentSlot != 0 || StatsInterface::GetBooleanStat(STAT_MP0_CHAR_ISACTIVE))
|
|
{
|
|
++m_load.m_CurrentSlot;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (save)
|
|
{
|
|
m_save.m_CurrentSlot = slot;
|
|
}
|
|
else
|
|
{
|
|
m_load.m_CurrentSlot = slot;
|
|
}
|
|
}
|
|
|
|
if (save)
|
|
{
|
|
if (m_save.m_OverrideSlot > STAT_MP_CATEGORY_DEFAULT)
|
|
{
|
|
m_save.m_CurrentSlot = m_save.m_OverrideSlot;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (m_load.m_OverrideSlot > STAT_MP_CATEGORY_DEFAULT)
|
|
{
|
|
m_load.m_CurrentSlot = m_load.m_OverrideSlot;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::QueueCheckCloudFileLoadPending()
|
|
{
|
|
CheckCloudFileLoadPending();
|
|
SetupSavegameSlot(false);
|
|
|
|
return IsLoadPending(m_load.m_CurrentSlot);
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::IsLoadInProgress(const int slot) const
|
|
{
|
|
if (!m_Initialized)
|
|
return false;
|
|
|
|
if (m_SynchProfileStats)
|
|
return true;
|
|
|
|
bool pending = m_load.m_InProgress;
|
|
|
|
//Check pending loading for a certain slot.
|
|
if(pending && STAT_INVALID_SLOT < slot && slot < TOTAL_NUMBER_OF_FILES)
|
|
{
|
|
pending = (slot == (int)m_load.m_CurrentSlot);
|
|
}
|
|
|
|
return pending;
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::IsSaveInProgress() const
|
|
{
|
|
if (!m_Initialized)
|
|
return false;
|
|
|
|
if (!NetworkInterface::IsCloudAvailable())
|
|
return false;
|
|
|
|
return m_save.m_InProgress;
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::Load(const eStatsLoadType type, const int file)
|
|
{
|
|
bool result = false;
|
|
|
|
// No network
|
|
if (PARAM_nonetwork.Get() || PARAM_lan.Get() || PARAM_nompsavegame.Get() || PARAM_cloudForceAvailable.Get())
|
|
{
|
|
statErrorf("Cloud storage: LOAD - PARAM_nonetwork.Get() || PARAM_lan.Get() || PARAM_nompsavegame.Get() || PARAM_cloudForceAvailable.Get()");
|
|
return result;
|
|
}
|
|
|
|
if (!m_Initialized)
|
|
{
|
|
statErrorf("Cloud storage: LOAD - !m_Initialized");
|
|
return result;
|
|
}
|
|
|
|
|
|
if (!statVerify(!IsLoadInProgress(STAT_INVALID_SLOT)))
|
|
{
|
|
statErrorf("Cloud storage: LOAD - IsLoadInProgress(STAT_INVALID_SLOT)()");
|
|
return result;
|
|
}
|
|
|
|
if (!statVerify(!IsSaveInProgress()))
|
|
{
|
|
statErrorf("Cloud storage: LOAD - IsSaveInProgress()");
|
|
return result;
|
|
}
|
|
|
|
if (CGenericGameStorage::IsMpStatsLoadAtTopOfSavegameQueue())
|
|
{
|
|
statErrorf("Cloud storage: LOAD - CGenericGameStorage::IsMpStatsLoadAtTopOfSavegameQueue()");
|
|
return result;
|
|
}
|
|
|
|
statAssertf(!GetBlockSaveRequests(), "Cloud storage: LOAD - saves are blocked - you can NOT any loads");
|
|
if (GetBlockSaveRequests())
|
|
{
|
|
statErrorf("Cloud storage: LOAD - saves are blocked - you can NOT any loads");
|
|
return result;
|
|
}
|
|
|
|
statAssertf(!m_save.HasRequests(file), "Cloud storage: LOAD - there are pending saves for file %d.", file);
|
|
if (m_save.HasRequests(file))
|
|
{
|
|
statErrorf("Cloud storage: LOAD - there are pending saves for file %d.", file);
|
|
return result;
|
|
}
|
|
|
|
//Setup the slot Number
|
|
if (file > STAT_MP_CATEGORY_DEFAULT && statVerify(file < TOTAL_NUMBER_OF_FILES))
|
|
{
|
|
m_load.m_CurrentSlot = file;
|
|
}
|
|
|
|
if (type == STATS_LOAD_CLOUD && !m_SynchProfileStats && statVerify(NetworkInterface::IsCloudAvailable()))
|
|
{
|
|
result = QueueLoad(type);
|
|
|
|
if (!result)
|
|
{
|
|
statErrorf("Cloud storage: LOAD failed - QueueLoad(type);.");
|
|
}
|
|
}
|
|
|
|
//Setup the slot Number
|
|
if (result && file > STAT_MP_CATEGORY_DEFAULT && statVerify(file < TOTAL_NUMBER_OF_FILES))
|
|
{
|
|
m_load.m_OverrideSlot = file;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::QueueLoad(const eStatsLoadType type)
|
|
{
|
|
bool result = false;
|
|
|
|
if (!statVerify(m_Initialized))
|
|
{
|
|
statErrorf("Cloud storage: LOAD - !m_Initialized");
|
|
return result;
|
|
}
|
|
|
|
// No network
|
|
if (PARAM_nonetwork.Get() || PARAM_lan.Get() || PARAM_nompsavegame.Get() || PARAM_cloudForceAvailable.Get())
|
|
{
|
|
statErrorf("Cloud storage: LOAD - PARAM_nonetwork.Get() || PARAM_lan.Get() || PARAM_nompsavegame.Get() || PARAM_cloudForceAvailable.Get()");
|
|
return result;
|
|
}
|
|
|
|
if (!statVerify(!IsLoadInProgress(STAT_INVALID_SLOT)))
|
|
{
|
|
statErrorf("Cloud storage: LOAD - IsLoadInProgress(STAT_INVALID_SLOT)");
|
|
return result;
|
|
}
|
|
|
|
if (!statVerify(!IsSaveInProgress() || m_load.m_CurrentSlot != m_save.m_CurrentSlot))
|
|
{
|
|
statErrorf("Cloud storage: LOAD - !statVerify(!IsSaveInProgress() || m_load.m_CurrentSlot != m_save.m_CurrentSlot)");
|
|
return result;
|
|
}
|
|
|
|
if (CGenericGameStorage::IsMpStatsLoadAtTopOfSavegameQueue())
|
|
{
|
|
statErrorf("Cloud storage: LOAD - IsMpStatsLoadAtTopOfSavegameQueue()");
|
|
return result;
|
|
}
|
|
|
|
statAssertf(!GetBlockSaveRequests(), "Cloud storage: LOAD - saves are blocked - you can NOT any loads");
|
|
if (GetBlockSaveRequests())
|
|
{
|
|
statErrorf("Cloud storage: LOAD - LOAD - saves are blocked - you can NOT any loads()");
|
|
return result;
|
|
}
|
|
|
|
statAssertf(!m_save.HasRequests(m_load.m_CurrentSlot), "Cloud storage: LOAD - there are pending save for file %d.", m_load.m_CurrentSlot);
|
|
if (m_save.HasRequests(m_load.m_CurrentSlot))
|
|
{
|
|
statErrorf("Cloud storage: LOAD - there are pending save for file %d.", m_load.m_CurrentSlot);
|
|
return result;
|
|
}
|
|
|
|
if (statVerify(type == STATS_LOAD_CLOUD) && !m_load.m_InProgress && (IsLoadPending(m_load.m_CurrentSlot) || CloudLoadFailed(m_load.m_CurrentSlot)))
|
|
{
|
|
if (!NetworkInterface::IsCloudAvailable())
|
|
return result;
|
|
|
|
if (!NetworkInterface::HasValidRockstarId() && StatsInterface::GetCloudSavegameService() == RL_CLOUD_ONLINE_SERVICE_SC)
|
|
return result;
|
|
|
|
#if __MPCLOUDSAVES_ONLY_ONEFILE
|
|
statAssertf(m_load.m_CurrentSlot == STAT_MP_CATEGORY_DEFAULT, "Cloud storage: LOAD start for category=%d, can't do this whne we only have one file.", m_load.m_CurrentSlot);
|
|
#endif // __MPSAVES_ONLY_ONEFILE
|
|
|
|
result = m_load.m_InProgress = CGenericGameStorage::QueueMpStatsLoad(m_load.m_CurrentSlot);
|
|
|
|
if (!m_load.m_InProgress)
|
|
{
|
|
statErrorf("Cloud storage: LOAD start has failed for category=%d.", m_load.m_CurrentSlot);
|
|
}
|
|
else
|
|
{
|
|
statDebugf1("Cloud storage: LOAD started for category=%d.", m_load.m_CurrentSlot);
|
|
|
|
CSavingMessage::BeginDisplaying(CSavingMessage::STORAGE_DEVICE_MESSAGE_CLOUD_LOADING);
|
|
|
|
ClearLoadFailed(m_load.m_CurrentSlot);
|
|
|
|
SetLoadPending(m_load.m_CurrentSlot);
|
|
|
|
if ( m_load.m_CurrentSlot == STAT_MP_CATEGORY_DEFAULT )
|
|
{
|
|
rlCreateUUID(&m_LoadUid);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
statErrorf("Cloud storage: LOAD - if (statVerify(type == STATS_LOAD_CLOUD) && !m_load.m_InProgress && (IsLoadPending(m_load.m_CurrentSlot) || CloudLoadFailed(m_load.m_CurrentSlot))).");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::Save(const eStatsSaveType /*type*/, const u32 file, const eSaveTypes savetype, const u32 uSaveDelay, const int saveReason)
|
|
{
|
|
if (!statVerify(m_Initialized))
|
|
{
|
|
NOTFINAL_ONLY(statWarningf("Cloud storage: Not initialized - SAVE requested and FAILED for file slot=%d.", file));
|
|
return false;
|
|
}
|
|
|
|
// No network
|
|
if (PARAM_nonetwork.Get() || PARAM_lan.Get() || PARAM_nompsavegame.Get() || PARAM_cloudForceAvailable.Get())
|
|
{
|
|
NOTFINAL_ONLY(statWarningf("Cloud storage: Bad Command Args (nonetwork/lan/nompsavegame/cloudforceavailable) - SAVE requested and FAILED for file slot=%d.", file));
|
|
return false;
|
|
}
|
|
|
|
statAssertf(file<TOTAL_NUMBER_OF_FILES, "FAIL SAVE REQUEST: Invalid file number %d", file);
|
|
if (file>=TOTAL_NUMBER_OF_FILES)
|
|
{
|
|
NOTFINAL_ONLY(statWarningf("Cloud storage: Invalid file number - SAVE requested and FAILED for file slot=%d.", file));
|
|
return false;
|
|
}
|
|
|
|
statAssertf(!IsLoadPending(file), "FAIL SAVE REQUEST: Load has not been done for file slot < %d >", file);
|
|
if (IsLoadPending(file))
|
|
{
|
|
NOTFINAL_ONLY(statWarningf("Cloud storage: Load Pending - SAVE requested and FAILED for file slot=%d.", file));
|
|
return false;
|
|
}
|
|
|
|
statAssertf(!CloudLoadFailed(file), "FAIL SAVE REQUEST: Load Failed for file slot < %d >", file);
|
|
if (CloudLoadFailed(file))
|
|
{
|
|
NOTFINAL_ONLY(statWarningf("Cloud storage: Load Failed - SAVE requested and FAILED for file slot=%d.", file));
|
|
return false;
|
|
}
|
|
|
|
statAssertf(!GetBlockSaveRequests(), "FAIL SAVE REQUEST: Save requests are blocked, SAVE requested for file slot=< %d >", file);
|
|
if (GetBlockSaveRequests())
|
|
{
|
|
statWarningf("Cloud storage: SAVES ARE BLOCKED - SAVE requested for file slot=%d.", file);
|
|
return false;
|
|
}
|
|
|
|
statAssertf(!DirtyProfileStatsReadDetected(), "FAIL SAVE REQUEST: Dirty read has been detected in profile stats, SAVE requested for file slot=< %d >", file);
|
|
if (DirtyProfileStatsReadDetected())
|
|
{
|
|
statWarningf("Cloud storage: DIRTY READ DETECTED IN PROFILE STATS - SAVE requested for file slot=%d.", file);
|
|
return false;
|
|
}
|
|
|
|
statAssertf(!DirtyCloudReadDetected(), "FAIL SAVE REQUEST: Dirty read has been detected in cloud, SAVE requested for file slot=< %d >", file);
|
|
if (DirtyCloudReadDetected())
|
|
{
|
|
statWarningf("Cloud storage: DIRTY READ DETECTED IN cloud - SAVE requested for file slot=%d.", file);
|
|
return false;
|
|
}
|
|
|
|
CProfileStats& profileStatsMgr = CLiveManager::GetProfileStatsMgr();
|
|
if (!profileStatsMgr.Synchronized(CProfileStats::PS_SYNC_MP))
|
|
{
|
|
statWarningf("Cloud storage: NEED TO SYNCHRONIZE PROFILE STATS BEFORE - SAVE requested for file slot=%d.", file);
|
|
|
|
if (NetworkInterface::IsNetworkOpen())
|
|
{
|
|
int errorCode = 0;
|
|
if (profileStatsMgr.SynchronizedFailed( CProfileStats::PS_SYNC_MP, errorCode ) && !profileStatsMgr.SynchronizePending( CProfileStats::PS_SYNC_MP ) && profileStatsMgr.FlushFailed( ))
|
|
{
|
|
statWarningf("Cloud storage: NEED TO SYNCHRONIZE PROFILE STATS BEFORE - FORCING A SYNCH - SAVE requested for file slot=%d.", file);
|
|
profileStatsMgr.Synchronize( true, true );
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if __MPCLOUDSAVES_ONLY_ONEFILE
|
|
if (file > STAT_MP_CATEGORY_DEFAULT)
|
|
{
|
|
statWarningf("Cloud storage: USING ONLY ONE FILE - IGNORE SAVE requested for file slot=%d.", file);
|
|
return true;
|
|
}
|
|
#endif // __MPSAVES_ONLY_ONEFILE
|
|
|
|
//No SAVING
|
|
if (!m_saveHistory.CanSave(file, savetype))
|
|
{
|
|
NOTFINAL_ONLY(statWarningf("Cloud storage: SAVE IGNORED for file slot=%d, savetype=%s.", file, GET_STAT_SAVETYPE_NAME(savetype));)
|
|
return true;
|
|
}
|
|
|
|
//Perform a profile stats flush when 10m have passed since the last flush.
|
|
if (0 == m_save.m_deferredProfileStatsFlush && m_saveHistory.DeferredProfileStatsFlush(savetype))
|
|
{
|
|
NOTFINAL_ONLY(statDebugf1("Cloud storage: SAVE requested for file slot=%d, savetype=%s - Deferred ProfileStats Flush.", file, GET_STAT_SAVETYPE_NAME(savetype));)
|
|
const u32 PROFILE_STATS_FLUSH_INTERVAL = 10*60*1000;
|
|
m_save.m_deferredProfileStatsFlush = PROFILE_STATS_FLUSH_INTERVAL;
|
|
}
|
|
|
|
//Perform a Cloud save when 10m have passed since the last save.
|
|
if (0 == m_save.m_deferredCloudSave && m_saveHistory.DeferredCloudSave(savetype) && file == STAT_MP_CATEGORY_DEFAULT)
|
|
{
|
|
NOTFINAL_ONLY(statDebugf1("Cloud storage: SAVE requested for file slot=%d, savetype=%s - Deferred Cloud Save.", file, GET_STAT_SAVETYPE_NAME(savetype));)
|
|
const u32 PROFILE_STATS_CLOUDSAVE_INTERVAL = 10*60*1000;
|
|
m_save.m_deferredCloudSave = PROFILE_STATS_CLOUDSAVE_INTERVAL;
|
|
}
|
|
|
|
//We want a profile stats flush NOW.
|
|
if (m_saveHistory.RequestProfileStatsFlush(savetype) && statVerify(!m_saveHistory.CloudSaveOnly(savetype)))
|
|
{
|
|
NOTFINAL_ONLY(statDebugf1("Cloud storage: SAVE requested for file slot=%d, savetype=%s - Requested a profile stats FLUSH.", file, GET_STAT_SAVETYPE_NAME(savetype));)
|
|
m_save.m_flushProfileStatOnNextCloudSave = false;
|
|
m_save.m_flushProfileStat = true;
|
|
m_saveHistory.SaveRequested(savetype);
|
|
|
|
m_save.m_RequestedSaveTelemetry.SetFrom(savetype, saveReason);
|
|
|
|
//Clear cash transactions blocking.
|
|
if (rlTelemetry::GetDeferredFlushBlocked( ))
|
|
{
|
|
NOTFINAL_ONLY( statWarningf("UnBlock Immediate Flush - save called slot=%d, savetype=%s.", file, GET_STAT_SAVETYPE_NAME(savetype)); )
|
|
rlTelemetry::SetDeferredFlushBlocked( false );
|
|
}
|
|
}
|
|
|
|
//We want a cloud save NOW.
|
|
if (m_saveHistory.RequestCloudSave(savetype) && statVerify(!m_saveHistory.ProfileStatsFlushOnly(savetype)))
|
|
{
|
|
//Clear cash transactions blocking.
|
|
if (rlTelemetry::GetDeferredFlushBlocked( ))
|
|
{
|
|
NOTFINAL_ONLY( statWarningf("UnBlock Immediate Flush - save called slot=%d, savetype=%s.", file, GET_STAT_SAVETYPE_NAME(savetype)); )
|
|
rlTelemetry::SetDeferredFlushBlocked( false );
|
|
}
|
|
|
|
m_saveHistory.SaveRequested(savetype);
|
|
|
|
m_save.m_RequestSlot |= (1<<file);
|
|
|
|
m_save.m_RequestedSaveTelemetry.SetFrom(savetype, saveReason);
|
|
|
|
//Flush Profile stats on next cloud Save requested.
|
|
if (file == STAT_MP_CATEGORY_DEFAULT && m_save.m_flushProfileStatOnNextCloudSave)
|
|
{
|
|
m_save.m_flushProfileStatOnNextCloudSave = false;
|
|
m_save.m_flushProfileStat = true;
|
|
}
|
|
|
|
if (savetype >= STAT_SAVETYPE_CRITICAL)
|
|
{
|
|
m_save.m_IsCritical |= (1<<file);
|
|
}
|
|
|
|
const u32 currTime = sysTimer::GetSystemMsTime();
|
|
if (m_save.m_RetryFailedSave > 0)
|
|
{
|
|
NOTFINAL_ONLY(statDebugf1("Cloud storage: SAVE requested for file slot=%d, savetype=%s, will be started in time=%ds.", file, GET_STAT_SAVETYPE_NAME(savetype), m_save.m_NextSaveTime - currTime));
|
|
}
|
|
else
|
|
{
|
|
NOTFINAL_ONLY(statDebugf1("Cloud storage: SAVE requested for file slot=%d, savetype=%s, will be started in time=%ds.", file, GET_STAT_SAVETYPE_NAME(savetype), uSaveDelay/1000));
|
|
m_save.m_NextSaveTime = currTime + uSaveDelay;
|
|
}
|
|
}
|
|
else if (m_save.m_flushProfileStat)
|
|
{
|
|
NOTFINAL_ONLY(statDebugf1("Cloud storage: SAVE Profile Stats flush requested for file slot=%d, savetype=%s,.", file, GET_STAT_SAVETYPE_NAME(savetype)));
|
|
|
|
//m_deferredProfileStatsFlush can not be 0, because 0 means disabled.
|
|
m_save.m_deferredProfileStatsFlush = 1 + uSaveDelay;
|
|
|
|
m_save.m_flushProfileStatOnNextCloudSave = false;
|
|
|
|
bool result = CheckForPendingProfileStatsFlush( );
|
|
|
|
if (uSaveDelay == 0)
|
|
return result;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::QueueSave(const eStatsSaveType type, const u32 file)
|
|
{
|
|
bool result = false;
|
|
|
|
if (!m_Initialized)
|
|
return result;
|
|
|
|
// No network
|
|
if (PARAM_nonetwork.Get() || PARAM_lan.Get() || PARAM_nompsavegame.Get() || PARAM_cloudForceAvailable.Get())
|
|
return result;
|
|
|
|
if (!statVerify(!IsLoadInProgress(file)))
|
|
return result;
|
|
|
|
if (!statVerify(!IsSaveInProgress()))
|
|
return result;
|
|
|
|
if (CGenericGameStorage::IsMpStatsSaveAtTopOfSavegameQueue())
|
|
return result;
|
|
|
|
if (GetBlockSaveRequests())
|
|
return result;
|
|
|
|
if (DirtyProfileStatsReadDetected())
|
|
return result;
|
|
|
|
if (DirtyCloudReadDetected())
|
|
return result;
|
|
|
|
//Cloud Storage
|
|
//Save to Cloud if we don't have pending load and if it did not fail to load.
|
|
// - If the cloud load failed we check if the local one has succeeded and save anyway.
|
|
if (statVerify(type == STATS_SAVE_CLOUD) && !m_save.m_InProgress && statVerifyf(!IsLoadPending(m_save.m_CurrentSlot), "slot %d", m_save.m_CurrentSlot) && statVerifyf(!CloudLoadFailed(m_save.m_CurrentSlot), "slot %d", m_save.m_CurrentSlot))
|
|
{
|
|
if (!NetworkInterface::IsCloudAvailable())
|
|
return result;
|
|
|
|
if (!NetworkInterface::HasValidRockstarId() && StatsInterface::GetCloudSavegameService() == RL_CLOUD_ONLINE_SERVICE_SC)
|
|
return result;
|
|
|
|
//Check timestamp for Multi Player
|
|
const char* statName = "_SaveMpTimestamp_0";
|
|
switch (m_save.m_CurrentSlot)
|
|
{
|
|
case STAT_MP_CATEGORY_DEFAULT: statName = "_SaveMpTimestamp_0"; break;
|
|
case STAT_MP_CATEGORY_CHAR0: statName = "_SaveMpTimestamp_1"; break;
|
|
case STAT_MP_CATEGORY_CHAR1: statName = "_SaveMpTimestamp_2"; break;
|
|
}
|
|
StatId statTimestamp(statName);
|
|
|
|
m_save.m_CurrentSlotTimestamp = rlGetPosixTime();
|
|
|
|
//Setup save timestamp
|
|
StatsInterface::SetStatData(statTimestamp, m_save.m_CurrentSlotTimestamp, STATUPDATEFLAG_DIRTY_PROFILE);
|
|
|
|
static bool s_TryCachingCloudSavegameLocally = false;
|
|
if (m_save.m_CurrentSlot == STAT_MP_CATEGORY_DEFAULT)
|
|
{
|
|
s_TryCachingCloudSavegameLocally = false;
|
|
m_save.m_deferredCloudSave = 0;
|
|
m_save.m_InProgressSaveTelemetry = m_save.m_RequestedSaveTelemetry;
|
|
}
|
|
|
|
if (m_save.m_lastcloudSavegame > 0 && m_save.m_CurrentSlot == STAT_MP_CATEGORY_DEFAULT && !m_save.IsCritical(STAT_MP_CATEGORY_DEFAULT))
|
|
{
|
|
int minSavegameInterval = eSaveOperations::MIN_CLOUD_SAVE_INTERVAL;
|
|
Tunables::GetInstance().Access(CD_GLOBAL_HASH, ATSTRINGHASH("MIN_CLOUD_SAVE_INTERVAL", 0x1b6a88a4), minSavegameInterval);
|
|
|
|
const u32 currTime = fwTimer::GetSystemTimeInMilliseconds();
|
|
if (currTime-m_save.m_lastcloudSavegame < minSavegameInterval)
|
|
{
|
|
statDebugf1("Cloud storage: SAVE request for file %d, has been set to be cached locally.", m_save.m_CurrentSlot);
|
|
s_TryCachingCloudSavegameLocally = true;
|
|
}
|
|
}
|
|
|
|
#if __ALLOW_LOCAL_MP_STATS_SAVES
|
|
CSavegameQueuedOperation_MPStats_Save::eMPStatsSaveDestination saveDestination = CSavegameQueuedOperation_MPStats_Save::MP_STATS_SAVE_TO_CLOUD;
|
|
|
|
//We want to cache the savegame locally on the console.
|
|
if (s_TryCachingCloudSavegameLocally)
|
|
{
|
|
#if !__FINAL
|
|
if (PARAM_enableLocalMPSaveCache.Get())
|
|
#endif
|
|
{
|
|
saveDestination = CSavegameQueuedOperation_MPStats_Save::MP_STATS_SAVE_TO_CONSOLE;
|
|
}
|
|
}
|
|
#endif // __ALLOW_LOCAL_MP_STATS_SAVES
|
|
|
|
result = m_save.m_InProgress = CGenericGameStorage::QueueMpStatsSave(m_save.m_CurrentSlot
|
|
#if __ALLOW_LOCAL_MP_STATS_SAVES
|
|
, saveDestination
|
|
#endif
|
|
);
|
|
|
|
m_save.m_CurrentSlotIsCritical = m_save.IsCritical(m_save.m_CurrentSlot);
|
|
m_save.ClearRequest(m_save.m_CurrentSlot);
|
|
|
|
#if __MPCLOUDSAVES_ONLY_ONEFILE
|
|
statAssertf(m_save.m_CurrentSlot == STAT_MP_CATEGORY_DEFAULT, "Cloud storage: SAVE started for category=%d, cant do this when we only have one file.", m_save.m_CurrentSlot);
|
|
#endif // __MPSAVES_ONLY_ONEFILE
|
|
|
|
if (m_save.m_InProgress)
|
|
{
|
|
statDebugf1("Cloud storage: SAVE started for category=%d.", m_save.m_CurrentSlot);
|
|
CSavingMessage::BeginDisplaying(CSavingMessage::STORAGE_DEVICE_MESSAGE_CLOUD_SAVING);
|
|
|
|
if (!s_TryCachingCloudSavegameLocally)
|
|
{
|
|
m_save.m_lastcloudSavegame = fwTimer::GetSystemTimeInMilliseconds();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
statErrorf("Cloud storage: SAVE started has failed for category=%d.", m_save.m_CurrentSlot);
|
|
s_TryCachingCloudSavegameLocally = false;
|
|
}
|
|
}
|
|
|
|
//Setup the slot Number
|
|
if (result && file > STAT_MP_CATEGORY_DEFAULT && statVerify(file < TOTAL_NUMBER_OF_FILES))
|
|
{
|
|
m_save.m_OverrideSlot = file;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::ClearLoadPending(const int file)
|
|
{
|
|
//Set stats as synched so they can be synched with profile stats server.
|
|
StatsInterface::SetAllStatsToSynched(file, true);
|
|
|
|
m_load.m_PendingSlot &= (~(1<<file));
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::CheckCloudFileLoadPending()
|
|
{
|
|
if (!StatsInterface::GetBooleanStat(STAT_MP0_CHAR_ISACTIVE))
|
|
{
|
|
statDebugf1("Cloud - Character Slot 1 not being used remove need to load savegame files.");
|
|
ClearLoadPending(STAT_MP_CATEGORY_CHAR0);
|
|
}
|
|
|
|
if (!StatsInterface::GetBooleanStat(STAT_MP1_CHAR_ISACTIVE))
|
|
{
|
|
statDebugf1("Cloud - Character Slot 2 not being used remove need to load savegame files.");
|
|
ClearLoadPending(STAT_MP_CATEGORY_CHAR1);
|
|
}
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::GetCharacterActive(const u32 character)
|
|
{
|
|
char statName[20];
|
|
sysMemSet(statName, 0, 20);
|
|
sprintf(statName, "MP%1d_CHAR_ISACTIVE", character);
|
|
|
|
return StatsInterface::GetBooleanStat(StatId(statName));
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::PendingSaveRequests( ) const
|
|
{
|
|
bool isPending = false;
|
|
|
|
NOTFINAL_ONLY( int slot = -1; )
|
|
|
|
//We can Only save if there are no pending load requests.
|
|
for (int i=0; i<TOTAL_NUMBER_OF_FILES && !isPending; i++)
|
|
{
|
|
if (m_save.HasRequests(i) && !IsLoadPending(i))
|
|
{
|
|
NOTFINAL_ONLY( slot = i; )
|
|
isPending = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if !__FINAL
|
|
static bool s_isPending = false;
|
|
|
|
if (isPending && !s_isPending)
|
|
statDebugf1("PendingSaveRequests: slot='%d', pending='%s'", slot, isPending?"true":"false");
|
|
else if (!isPending && s_isPending)
|
|
statDebugf1("PendingSaveRequests: slot='%d', pending='%s'", slot, isPending?"true":"false");
|
|
|
|
s_isPending = isPending;
|
|
#endif // !__FINAL
|
|
|
|
return isPending;
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::SetNoRetryOnFail(bool value)
|
|
{
|
|
m_NoRetryOnFail = value;
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::CanFlushProfileStats( ) const
|
|
{
|
|
if (IsLoadPending( STAT_MP_CATEGORY_DEFAULT ))
|
|
return false;
|
|
if (CloudSaveFailed( STAT_MP_CATEGORY_DEFAULT ))
|
|
return false;
|
|
|
|
const int selectedSlot = StatsInterface::GetCurrentMultiplayerCharaterSlot() + 1;
|
|
if (IsLoadPending( selectedSlot ))
|
|
return false;
|
|
|
|
if (CloudSaveFailed( selectedSlot ))
|
|
return false;
|
|
|
|
if (IsSaveInProgress( ))
|
|
return false;
|
|
|
|
if (PendingSaveRequests( ))
|
|
return false;
|
|
|
|
statAssertf(!GetBlockSaveRequests(), "CanFlushProfileStats: Save requests are blocked.");
|
|
if (GetBlockSaveRequests())
|
|
return false;
|
|
|
|
statAssertf(!DirtyProfileStatsReadDetected(), "CanFlushProfileStats: Dirty read has been detected in profile stats.");
|
|
if (DirtyProfileStatsReadDetected())
|
|
return false;
|
|
|
|
statAssertf(!DirtyCloudReadDetected(), "CanFlushProfileStats: Dirty read has been detected in cloud.");
|
|
if (DirtyCloudReadDetected())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::CancelAndClearSaveRequests( )
|
|
{
|
|
statDebugf1("CancelAndClearSaveRequests: Canceling save requests.");
|
|
|
|
ClearPendingFlushRequests();
|
|
|
|
for (int i = 0; i <= STAT_MP_CATEGORY_MAX; i++)
|
|
ClearPendingSaveRequests(i);
|
|
|
|
CLiveManager::GetProfileStatsMgr().CancelPendingFlush();
|
|
}
|
|
|
|
int
|
|
CStatsSavesMgr::GetNextPendingSaveRequest() const
|
|
{
|
|
for (int i=0; i<TOTAL_NUMBER_OF_FILES; i++)
|
|
{
|
|
if (m_save.HasRequests(i) && !IsLoadPending(i))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return STAT_INVALID_SLOT;
|
|
}
|
|
|
|
bool
|
|
CStatsSavesMgr::RetrySaveOnHttpErrorCode(const int resultcode) const
|
|
{
|
|
//Dont retry saving if we are not in Multiplayer. Saves might have failed and we want to stay like that.
|
|
if (!NetworkInterface::IsNetworkOpen())
|
|
return false;
|
|
|
|
if(m_NoRetryOnFail)
|
|
return false;
|
|
|
|
if (resultcode == HTTP_CODE_REQUESTTIMEOUT)
|
|
return true;
|
|
else if (resultcode == HTTP_CODE_TOOMANYREQUESTS)
|
|
return true;
|
|
else if (resultcode == HTTP_CODE_GATEWAYTIMEOUT)
|
|
return true;
|
|
//All 5xx error codes
|
|
else if (resultcode >= HTTP_CODE_ANYSERVERERROR && resultcode <= HTTP_CODE_NETCONNECTTIMEOUT)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::TriggerProfileStatsFlush(CSaveTelemetryStats saveTelemetry)
|
|
{
|
|
if (!GetBlockSaveRequests())
|
|
{
|
|
CSavingMessage::BeginDisplaying(CSavingMessage::STORAGE_DEVICE_MESSAGE_CLOUD_SAVING);
|
|
CLiveManager::GetProfileStatsMgr().Flush( true, true );
|
|
|
|
//Send save game telemetry.
|
|
saveTelemetry.FlushTelemetry(false, false);
|
|
}
|
|
|
|
s_FlushProfileStats = false;
|
|
|
|
m_save.m_lastFlushProfileStatTime = fwTimer::GetSystemTimeInMilliseconds();
|
|
m_save.m_flushProfileStat = false;
|
|
m_save.m_deferredProfileStatsFlush = 0;
|
|
m_save.m_flushProfileStatOnNextCloudSave = false;
|
|
|
|
ASSERT_ONLY(m_save.m_lastFlushProfileStatWasDeferred = false);
|
|
#if !__NO_OUTPUT
|
|
if (PARAM_spewDirtySavegameStats.Get())
|
|
CStatsMgr::GetStatsDataMgr().SpewDirtyCloudOnlySats( true );
|
|
#endif // !__NO_OUTPUT
|
|
}
|
|
|
|
void
|
|
CStatsSavesMgr::SaveLocalCloudDirtyReadTimestamp()
|
|
{
|
|
CProfileSettings& settings = CProfileSettings::GetInstance();
|
|
statAssert( settings.AreSettingsValid() );
|
|
|
|
CProfileSettings::ProfileSettingId idLow32 = CProfileSettings::MP_CLOUD_POSIXTIME_LOW32;
|
|
CProfileSettings::ProfileSettingId idHigh32 = CProfileSettings::MP_CLOUD_POSIXTIME_HIGH32;
|
|
|
|
switch(m_save.m_CurrentSlot)
|
|
{
|
|
case STAT_MP_CATEGORY_CHAR0: idLow32 = CProfileSettings::MP_CLOUD0_POSIXTIME_LOW32; idHigh32 = CProfileSettings::MP_CLOUD0_POSIXTIME_HIGH32; break;
|
|
case STAT_MP_CATEGORY_CHAR1: idLow32 = CProfileSettings::MP_CLOUD1_POSIXTIME_LOW32; idHigh32 = CProfileSettings::MP_CLOUD1_POSIXTIME_HIGH32; break;
|
|
|
|
case STAT_MP_CATEGORY_DEFAULT:
|
|
break;
|
|
}
|
|
|
|
settings.Set(idLow32, (int)(m_save.m_CurrentSlotTimestamp & 0xFFFFFFFF));
|
|
settings.Set(idHigh32, (int)(m_save.m_CurrentSlotTimestamp >> 32));
|
|
|
|
settings.Write( true );
|
|
}
|
|
|
|
// EOF
|
|
|