Files
GTASource/game/scene/ExtraContent.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

4704 lines
141 KiB
C++

//
// filename: ExtraContent.cpp
// description: Class controlling the loading of extra content once game has been released
//
#include "ExtraContent.h"
// Rage headers
#include "bank/bkmgr.h"
#include "file/asset.h"
#include "file/cachepartition.h"
#include "file/default_paths.h"
#include "file/device.h"
#include "file/device_relative.h"
#include "file/device_crc.h"
#include "parser/manager.h"
#include "parser/tree.h"
#include "rline/rlpresence.h"
#include "fwanimation/clipsets.h"
#include "fwanimation/expressionsets.h"
#include "fwanimation/facialclipsetgroups.h"
#include "fwcommerce/CommerceConsumable.h"
#include "string/stringutil.h"
#include "system/appcontent.h"
#include "system/messagequeue.h"
#include "fragment/tune.h"
#include "system/platform.h"
// Game headers
#include "control/Gen9ExclusiveAssets.h"
#include "peds/PlayerSpecialAbility.h"
#include "rline/cloud/rlcloud.h"
#include "streaming/packfilemanager.h"
#include "network/network.h"
#include "network/Cloud/Tunables.h"
#include "frontend/WarningScreen.h"
#include "scene/dlc_channel.h"
#include "scene/DownloadableTextureManager.h"
#include "scene/fileloader.h"
#include "scene/loader/mapFileMgr.h"
#include "scene/portals/LayoutManager.h"
#include "scene/loader/mapFileMgr.h"
#include "script/script.h"
#include "streaming/streaming.h"
#include "streaming/streamingengine.h"
#include "streaming/streaminginstall_psn.h"
#include "task/system/Tuning.h"
#include "system/controlmgr.h"
#include "system/filemgr.h"
#include "system/device_xcontent.h"
#include "system/xtl.h"
#include "task/physics/BlendFromNMData.h"
#include "text/TextConversion.h"
#include "ExtraContent_parser.h"
#include "scene/ExtraMetadataMgr.h"
#include "scene/scene.h"
#include "file/device_installer.h"
#include "frontend/loadingscreens.h"
#include "Network/commerce/CommerceManager.h"
#include "camera/CamInterface.h"
#include "network/Live/NetworkTelemetry.h"
#include "network/General/NetworkAssetVerifier.h"
#include "renderer/MeshBlendManager.h"
#include "Cutscene/CutSceneManagerNew.h"
#include "Stats/StatsInterface.h"
#include "weapons/inventory/PedInventoryLoadOut.h"
#include "audio/northaudioengine.h"
// No need for the final block around these, they turn into stripped_param in FINAL
PARAM(ignorePacks, "Ignores a given set of packs based on their nameHash from setup2, delimit with ;");
PARAM(extracontent, "Extra content development path");
PARAM(extracloudmanifest, "specify path to local ExtracontentManager cloud manifest file wich will be used instead of real one");
PARAM(disableExtraCloudContent, "Disables extra cloud content for !final builds");
PARAM(netSessionIgnoreECHash, "Ignore the CExtraContentManager::GetCRC data hash");
PARAM(disablecompatpackcheck, "Disables compatibility pack configuration check for MP (non FINAL only)");
PARAM(delayDLCLoad, "Don't automatically load DLC");
PARAM(useDebugCloudPatch, "Use debug suffix for the cloud patch");
PARAM(enableCloudTestWidgets, "Force cloud/manifest check results through widgets");
PARAM(christmas, "set the xmas special trigger by default");
PARAM(usecompatpacks, "mount all available compatibility packs");
PARAM(disablepackordercheck, "Disables compatibility packs order check");
PARAM(loadMapDLCOnStart, "Automatically loads both SP and MP map changes at startup");
PARAM(enableMapCCS, "Automatically loads specified CCS at session init");
PARAM(enableSPMapCCS, "Automatically loads specified SP map CCS at session init");
PARAM(ignoreDeployedPacks, "Ignore deployed packs");
PARAM(buildDLCCacheData, "Build cache data for all SP & MP map data currently installed");
XPARAM(checkUnusedFiles);
#if RSG_ORBIS
PARAM(disableEntitlementCheck, "Disables entitlement check on PS4");
#endif
RAGE_DEFINE_CHANNEL(dlc, DIAG_SEVERITY_DISPLAY, DIAG_SEVERITY_DISPLAY, DIAG_SEVERITY_ASSERT)
#if __PPU || __XENON
#define TITLE_UPDATE_RPF_PATH "update:/update.rpf"
#define TITLE_UPDATE2_RPF_PATH "update:/update2.rpf"
#else // NG
#if __FINAL
#define TITLE_UPDATE_RPF_PATH "update/update.rpf"
#define TITLE_UPDATE2_RPF_PATH "update/update2.rpf"
#else
#define TITLE_UPDATE_RPF_PATH "update/update_debug.rpf"
#define TITLE_UPDATE2_RPF_PATH "update/update2_debug.rpf"
#endif // __FINAL
#endif
#define TITLE_UPDATE_MOUNT_PATH "update:/"
#define EC_REBOOT_ID ATSTRINGHASH("R*EC", 0xA579A802)
#define MAP_CHANGE_FADE_TIME 600
#define COMPATPACKS_SET_PATH "common:/data/debug/compatpacks.xml"
#define COMPATPACKS_DEPLOYED_PATH "common:/data/dlclist.xml"
#define DLC_PACK_MOUNT_TIMEOUT (20.0f)
#define DLC_PACKS_PATH "platform:/dlcPacks/"
#if RSG_XENON
#define LOAD_SCR_PACK_THRESHOLD 10
#endif
#undef DEFINE_CCS_GROUP
#define DEFINE_CCS_GROUP(enumName, stringName, hash) static atHashString gs_##enumName = atHashString(stringName);
// Macro magic to make sure all group names are registered with the string hash map
CCS_TITLES
static rlPresence::Delegate g_PresenceDlgt(CExtraContentManager::OnPresenceEvent);
static ServiceDelegate g_addContentServiceDelegate;
#if RSG_PS3
#include <stddef.h>
#include "fwutil/KeyGen.h"
extern const unsigned char __start__Ztext[];
extern const unsigned char __stop__Ztext[];
#elif RSG_XENON
#pragma const_seg("rodata1")
const unsigned char __start__Ztext = 0;
#pragma const_seg()
#pragma bss_seg("arsbss")
unsigned char __stop__Ztext;
#pragma bss_seg()
#endif
namespace rage
{
NOSTRIP_XPARAM(audiofolder);
}
#if RSG_ORBIS
sysIpcThreadId s_contentPollThread = sysIpcThreadIdInvalid;
#endif
sysIpcThreadId s_codeCheckThread = sysIpcThreadIdInvalid;
bool s_codeCheckRun = true;
bool s_codeCompromised = false;
#if __BANK
namespace rage {
extern atBinaryMap<atString, u32> g_RequestedFiles;
extern atBinaryMap<atString, u32> g_RequestedDevices;
}
enum {
SHOW_FILES_PER_PAGE = 256
};
static atBinaryMap<atString, u32> s_loadedFilesAbsolute;
static atBinaryMap<atString, u32> s_unusedFilesAbsolute;
static u32 s_loadedFilesPageNumber = 0;
static u32 s_loadedFilesPageCount = 0;
static u32 s_unusedFilesPageNumber = 0;
static u32 s_unusedFilesPageCount = 0;
// This is probably really, really slow. So much iterating and recursing! Gets all the files inside dev:/path,
// recursing into any directories
static void GetDeviceContentsRecursive(atBinaryMap<atString, u32> &map, const fiDevice *dev, const char *path) {
fiFindData data;
fiHandle handle = dev->FindFileBegin(path, data);
bool validHandle = fiIsValidHandle(handle);
while (validHandle) {
char currentPath[RAGE_MAX_PATH];
if (data.m_Name[0] != '.') { // skip over ., .., and UNIX-style hidden files
// Found a directory, so deal with it
if ((data.m_Attributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) {
formatf(currentPath, sizeof(currentPath), "%s\\%s", path, data.m_Name);
GetDeviceContentsRecursive(map, dev, currentPath);
} else {
// Build the new pathname to use
formatf(currentPath, "%s\\%s", path, data.m_Name);
atString currentPathString(currentPath);
atHashString currentPathHash = atStringHash(currentPath);
map.Insert(currentPathHash, currentPathString);
}
}
validHandle = dev->FindFileNext(handle, data);
}
dev->FindFileEnd(handle);
}
// Convenience form of above.
// Expects name in format of "device:"
static void GetDeviceContentsRecursive(atBinaryMap<atString, u32> &map, const char *deviceName) {
// append '/'
char fixedDeviceName[RAGE_MAX_PATH] = { 0 };
strncpy(fixedDeviceName, deviceName, RAGE_MAX_PATH);
fixedDeviceName[strlen(deviceName)] = '/';
// find device
if (const fiDevice *device = fiDevice::GetDevice(fixedDeviceName)) {
return GetDeviceContentsRecursive(map, device, fixedDeviceName);
}
}
#endif
const char * GetDeviceTypeString (CMountableContent::eDeviceType deviceType);
class CExtraContentFileMounter : public CDataFileMountInterface
{
virtual bool LoadDataFile(const CDataFileMgr::DataFile& file)
{
bool retValue = true;
dlcDebugf3("CExtraContentFileMounter::LoadDataFile - Found file %s", file.m_filename);
switch (file.m_fileType)
{
case CDataFileMgr::GTXD_PARENTING_DATA:
{
CMapFileMgr::GetInstance().ProcessGlobalTxdParentsFile(file.m_filename);
}
break;
case CDataFileMgr::OVERLAY_INFO_FILE:
EXTRACONTENT.LoadOverlayInfo(file.m_filename);
break;
case CDataFileMgr::CONTENT_UNLOCKING_META_FILE:
{
SContentUnlocks newUnlocks;
if (PARSER.LoadObject(file.m_filename, NULL, newUnlocks))
{
dlcDebugf3("CExtraContentFileMounter::LoadDataFile - Found %i new content unlocks from %s", newUnlocks.m_listOfUnlocks.GetCount(), file.m_filename);
for (int i = 0; i < newUnlocks.m_listOfUnlocks.GetCount(); i++)
EXTRACONTENT.ModifyContentLockState(newUnlocks.m_listOfUnlocks[i].GetHash(), false);
}
}
break;
case CDataFileMgr::CLIP_SETS_FILE:
{
fwClipSetManager::PatchClipSets(file.m_filename);
}
break;
case CDataFileMgr::EXPRESSION_SETS_FILE:
{
fwExpressionSetManager::Append(file.m_filename);
}
break;
case CDataFileMgr::FACIAL_CLIPSET_GROUPS_FILE:
{
fwFacialClipSetGroupManager::Append(file.m_filename);
}
break;
case CDataFileMgr::NM_BLEND_OUT_SETS_FILE:
{
CNmBlendOutSetManager::Append(file.m_filename);
}
break;
case CDataFileMgr::NM_TUNING_FILE:
{
CTuningManager::AppendPhysicsTasks(file.m_filename);
}
break;
case CDataFileMgr::TEXTFILE_METAFILE:
{
TheText.AddExtracontentTextFromMetafile(file.m_filename);
}
break;
case CDataFileMgr::MOVE_NETWORK_DEFS:
{
fwAnimDirector::LoadNetworkDefinitions(file.m_filename);
}
break;
case CDataFileMgr::EXTRA_TITLE_UPDATE_DATA:
{
CMountableContent::LoadExtraTitleUpdateData(file.m_filename);
}
break;
case CDataFileMgr::WORLD_HEIGHTMAP_FILE:
{
CGameWorldHeightMap::LoadExtraFile(file.m_filename);
}
break;
case CDataFileMgr::WATER_FILE:
{
// do nothing
}
break;
case CDataFileMgr::EXTRA_FOLDER_MOUNT_DATA:
{
CMountableContent::LoadExtraFolderMountData(file.m_filename);
}
break;
case CDataFileMgr::VEHICLE_SHOP_DLC_FILE:
{
EXTRAMETADATAMGR.AddVehiclesCollection(file.m_filename);
}
break;
case CDataFileMgr::LEVEL_STREAMING_FILE:
{
CStreaming::SetStreamingFile(file.m_filename);
}
break;
case CDataFileMgr::LOADOUTS_FILE:
{
CPedInventoryLoadOutManager::Append(file.m_filename);
}
break;
case CDataFileMgr::CARCOLS_GEN9_FILE:
{
CVehicleModelInfo::AppendVehicleColorsGen9(file.m_filename);
}
break;
case CDataFileMgr::CARMODCOLS_GEN9_FILE:
{
CVehicleModelInfo::LoadVehicleModColorsGen9(file.m_filename);
}
break;
case CDataFileMgr::GEN9_EXCLUSIVE_ASSETS_VEHICLES_FILE :
{
CGen9ExclusiveAssets::LoadGen9ExclusiveAssetsVehiclesData(file.m_filename);
}
break;
case CDataFileMgr::GEN9_EXCLUSIVE_ASSETS_PEDS_FILE :
{
CGen9ExclusiveAssets::LoadGen9ExclusiveAssetsPedsData(file.m_filename);
}
break;
default: Errorf("CExtraContentFileMounter::LoadDataFile Invalid file type! %s [%i]", file.m_filename, file.m_fileType); break;
}
return retValue;
}
virtual void UnloadDataFile(const CDataFileMgr::DataFile& file)
{
dlcDebugf3("CExtraContentFileMounter::UnloadDataFile - Found file %s", file.m_filename);
switch (file.m_fileType)
{
case CDataFileMgr::OVERLAY_INFO_FILE:
EXTRACONTENT.UnloadOverlayInfo(file.m_filename);
break;
case CDataFileMgr::TEXTFILE_METAFILE:
{
TheText.RemoveExtracontentTextFromMetafile(file.m_filename);
}
break;
case CDataFileMgr::NM_TUNING_FILE:
{
CTuningManager::RevertPhysicsTasks(file.m_filename);
}
break;
case CDataFileMgr::EXTRA_FOLDER_MOUNT_DATA:
{
CMountableContent::UnloadExtraFolderMountData(file.m_filename);
}
break;
case CDataFileMgr::VEHICLE_SHOP_DLC_FILE:
{
EXTRAMETADATAMGR.RemoveVehiclesCollection(file.m_filename);
}
break;
case CDataFileMgr::MOVE_NETWORK_DEFS:
{
fwAnimDirector::UnloadNetworkDefinitions(file.m_filename);
}
break;
default: Errorf("CExtraContentFileMounter::UnloadDataFile Invalid file type! %s [%i]", file.m_filename, file.m_fileType); break;
}
}
} g_extraContentFileMounter, g_extraContentFileMounterToRegisterUnload;
void CExtraContentWrapper::Init(u32 initMode)
{
if (initMode == INIT_CORE && !CExtraContentManagerSingleton::IsInstantiated())
CExtraContentManagerSingleton::Instantiate();
if (CExtraContentManagerSingleton::IsInstantiated())
EXTRACONTENT.Init(initMode);
}
void CExtraContentWrapper::Shutdown(u32 shutdownMode)
{
if (CExtraContentManagerSingleton::IsInstantiated())
{
EXTRACONTENT.ShutdownMetafiles(shutdownMode, eDFMI_UnloadLast);
EXTRACONTENT.Shutdown(shutdownMode);
if (shutdownMode == SHUTDOWN_CORE)
CExtraContentManagerSingleton::Destroy();
}
}
void CExtraContentWrapper::ShutdownStart(u32 shutdownMode)
{
if (CExtraContentManagerSingleton::IsInstantiated())
{
EXTRACONTENT.ShutdownMetafiles(shutdownMode, eDFMI_UnloadFirst);
EXTRACONTENT.ShutdownMapChanges(shutdownMode);
}
}
void CExtraContentWrapper::Update()
{
PF_START_TIMEBAR("extra content");
{
EXTRACONTENT.Update();
DOWNLOADABLETEXTUREMGR.Update();
}
}
void CExtraContentManager::LoadOverlayInfo(const char* fileName)
{
sOverlayInfos infos;
if(PARSER.LoadObject(fileName,"",infos))
{
for(int idx=0;idx<infos.m_overlayInfos.GetCount();idx++)
{
dlcDebugf3("overlay info: %s %s", infos.m_overlayInfos[idx].m_nameId.TryGetCStr(), infos.m_overlayInfos[idx].m_changeSet.TryGetCStr());
UpdateOverlayInfo(infos.m_overlayInfos[idx],fileName);
}
}
}
void CExtraContentManager::UnloadOverlayInfo(const char* fileName)
{
sOverlayInfos infos;
if(PARSER.LoadObject(fileName,"",infos))
{
for(int idx=0;idx<infos.m_overlayInfos.GetCount();idx++)
{
dlcDebugf3("overlay info: %s", infos.m_overlayInfos[idx].m_changeSet.TryGetCStr());
RemoveOverlayInfo(infos.m_overlayInfos[idx]);
}
}
}
void CExtraContentManager::RemoveOverlayInfo(sOverlayInfo &overlayInfo)
{
sOverlayInfo* toStart = NULL;
sOverlayInfo* currentOverlay = NULL;
for(int i=0;i<m_overlayInfo.GetCount();i++)
{
currentOverlay = m_overlayInfo[i];
if(currentOverlay->m_nameId == overlayInfo.m_nameId)
{
if(currentOverlay->m_changeSet == overlayInfo.m_changeSet)
{
if (currentOverlay->m_state == sOverlayInfo::ACTIVE)
{
CMountableContent* content = GetContentByHash(currentOverlay->m_content);
Assertf(content, "Mountable content doesn't exist!");
RevertContentChangeSet(content->GetNameHash(),currentOverlay->m_changeSetGroupToExecuteWith,currentOverlay->m_changeSet);
}
m_overlayInfo.Delete(i--);
delete currentOverlay;
}
else
{
if(toStart)
{
toStart = toStart->m_version < currentOverlay->m_version ? currentOverlay : toStart;
}
else
{
toStart = currentOverlay;
}
}
}
}
if(toStart && toStart->m_state != sOverlayInfo::ACTIVE)
{
toStart->m_state = sOverlayInfo::WILL_ACTIVATE;
}
}
void CExtraContentManager::UpdateOverlayInfo(sOverlayInfo &overlayInfo, const char* fileName)
{
char device[RAGE_MAX_PATH];
memset(device,0,RAGE_MAX_PATH);
sOverlayInfo* highestVersion = &overlayInfo;
for(int i=0;i<m_overlayInfo.GetCount();i++)
{
sOverlayInfo*& currentInfo = m_overlayInfo[i];
if(currentInfo->m_nameId == overlayInfo.m_nameId)
{
if(highestVersion->m_version>currentInfo->m_version)
{
CMountableContent* content = GetContentByHash(currentInfo->m_content);
Assertf(content, "Mountable content doesn't exist!");
if(currentInfo->m_state == sOverlayInfo::ACTIVE)
{
RevertContentChangeSet(content->GetNameHash(),currentInfo->m_changeSetGroupToExecuteWith,currentInfo->m_changeSet);
}
currentInfo->m_state = sOverlayInfo::INACTIVE;
}
else
{
highestVersion = m_overlayInfo[i];
}
}
}
if(highestVersion->m_state != sOverlayInfo::ACTIVE)
highestVersion->m_state = sOverlayInfo::WILL_ACTIVATE;
strncpy(device,fileName,static_cast<int>(strcspn(fileName,"/"))+1);
overlayInfo.m_content = GetContentByDevice(device)->GetNameHash();
Assertf(overlayInfo.m_content!=0,"Content not found for overlaid file!");
dlcDisplayf("New overlayinfo, versioned changeset: %s, content changeset: %s, content: %s, version: %d", overlayInfo.m_nameId.TryGetCStr(), overlayInfo.m_changeSet.TryGetCStr(), GetContentByHash(overlayInfo.m_content)->GetName(),overlayInfo.m_version);
dlcDisplayf("Highest version of the changeset for versioned changeset: %s, content changeset: %s, content: %s, version: %d", overlayInfo.m_nameId.TryGetCStr(), overlayInfo.m_changeSet.TryGetCStr(), GetContentByHash(overlayInfo.m_content)->GetName(),overlayInfo.m_version);
m_overlayInfo.PushAndGrow(rage_new sOverlayInfo(overlayInfo));
}
void CExtraContentManager::ExecuteContentChangeSetGroupInternal(CMountableContent* content, atHashString changeSetGroup)
{
if(content)
{
dlcDisplayf("Executing changeset group %s for %s",changeSetGroup.TryGetCStr(),content->GetName());
atArray<atHashString> changeSets;
content->GetChangeSetHashesForGroup(changeSetGroup,changeSets);
for(int i=0;i<changeSets.GetCount();i++)
{
ExecuteContentChangeSetInternal(content,changeSetGroup,changeSets[i], ECCS_FLAG_USE_LATEST_VERSION | ECCS_FLAG_USE_LOADING_SCREEN);
}
CMountableContent::CleanupAfterMapChange();
}
}
void CExtraContentManager::RevertContentChangeSetGroupInternal(CMountableContent* content, atHashString changeSetGroup, u32 actionMask)
{
if(content)
{
dlcDisplayf("Reverting changeset group %s for %s",changeSetGroup.TryGetCStr(),content->GetName());
atArray<atHashString> changeSets;
content->GetChangeSetHashesForGroup(changeSetGroup,changeSets);
for(int i=0;i<changeSets.GetCount();i++)
{
RevertContentChangeSetInternal(content,changeSetGroup,changeSets[i],actionMask, ECCS_FLAG_USE_LATEST_VERSION | ECCS_FLAG_USE_LOADING_SCREEN);
}
}
}
void CExtraContentManager::TriggerLoadingScreen(LoadingScreenContext loadingContext=LOADINGSCREEN_CONTEXT_MAPCHANGE)
{
if (!m_loadingScreenState.m_modified)
{
m_loadingScreenState.m_modified = true;
if (!CLoadingScreens::AreActive() DURANGO_ONLY(&& gRenderThreadInterface.IsUsingDefaultRenderFunction()) REPLAY_ONLY(&& !CVideoEditorPlayback::IsLoading()))
{
// Catch edge case where movie is still active.
CLoadingScreens::Shutdown(SHUTDOWN_CORE);
CLoadingScreens::Update();
CLoadingScreens::Init(loadingContext, 0);
CLoadingScreens::CommitToMpSp();
strStreamingEngine::GetLoader().CallKeepAliveCallback();
m_loadingScreenState.m_displayed = true;
gRenderThreadInterface.Flush(true);
}
}
}
void CExtraContentManager::CloseLoadingScreen()
{
if (m_loadingScreenState.m_modified)
{
if (m_loadingScreenState.m_displayed)
{
// Give the game one frame to gather streaming requests from the load
if (m_loadingScreenState.m_doneAPostFrame)
{
if (strStreamingEngine::GetInfo().GetNumberObjectsRequested() <= 0)
{
CLoadingScreens::Shutdown(SHUTDOWN_CORE);
m_loadingScreenState.Reset();
}
}
else
m_loadingScreenState.m_doneAPostFrame = true;
}
else
m_loadingScreenState.Reset();
}
}
void CExtraContentManager::ExecuteContentChangeSetInternal(
CMountableContent* content, atHashString changeSetGroup, atHashString changeSetName, u32 flags DURANGO_ONLY(, bool addPending/*=true*/))
{
BANK_ONLY(bool tempValue = strStreamingInfoManager::ms_bValidDlcOverlay;)
sOverlayInfo * info = NULL;
if(flags & ECCS_FLAG_USE_LATEST_VERSION)
{
atHashString versionedChange = GetVersionedChangeSetName(m_overlayInfo,changeSetName);
Displayf("Executing changeset %s in group %s for %s",changeSetName.TryGetCStr(),changeSetGroup.TryGetCStr(),content->GetName());
if(versionedChange != 0)
{
Displayf("This is a versioned changeset: %s",versionedChange.TryGetCStr());
info = GetHighestOverlayInfoForVersionedChange(m_overlayInfo,versionedChange);
if(info->m_state != sOverlayInfo::ACTIVE)
{
content = GetContentByHash(info->m_content);
changeSetName = info->m_changeSet;
changeSetGroup = info->m_changeSetGroupToExecuteWith;
BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = true;)
info->m_state = sOverlayInfo::ACTIVE;
Displayf("Executing highest version of the changeset %s in group %s in %s",changeSetName.TryGetCStr(),changeSetGroup.TryGetCStr(),content->GetName());
}
else
{
//already executed
return;
}
}
else if(changeSetGroup == CCS_GROUP_MAP || changeSetGroup == CCS_GROUP_MAP_SP)
{
Assertf(0, "Map changesets should be versioned, Content: %s", content->GetName());
#if !__NO_OUTPUT
Quitf("Map changesets should be versioned, Content: %s", content->GetName());
#endif // !__NO_OUTPUT
}
}
#if RSG_DURANGO
// Because XB1 doesn't have a persistent back buffer, we need to wait for it to copy out of ESRAM
if (addPending && (changeSetGroup == CCS_GROUP_MAP || changeSetGroup == CCS_GROUP_MAP_SP))
{
if (const CDataFileMgr::ContentChangeSet* csPtr = DATAFILEMGR.GetContentChangeSet(changeSetName))
{
// If we need a loading screen and we're not already showing one then prep for last frame
if (csPtr->m_requiresLoadingScreen && csPtr->m_loadingScreenContext == LOADINGSCREEN_CONTEXT_LAST_FRAME &&
gRenderThreadInterface.IsUsingDefaultRenderFunction() && !CLoadingScreens::AreActive() REPLAY_ONLY(&& !CVideoEditorPlayback::IsLoading()))
{
m_pendingActions.PushAndGrow(SPendingCSAction(content->GetNameHash(), changeSetGroup, changeSetName, flags, 0, true, true));
if (!m_waitingForBBCopy)
{
CPauseMenu::TogglePauseRenderPhases(false, OWNER_LOADING_SCR, __FUNCTION__ );
m_waitingForBBCopy = true;
}
if (info)
info->m_state = sOverlayInfo::INACTIVE;
return;
}
}
}
#endif
if(flags & ECCS_FLAG_USE_LOADING_SCREEN)
{
// Check to see if we should trigger the loading screen
if (const CDataFileMgr::ContentChangeSet* csPtr = DATAFILEMGR.GetContentChangeSet(changeSetName))
{
if (csPtr->m_requiresLoadingScreen)
TriggerLoadingScreen(csPtr->m_loadingScreenContext);
}
}
content->ExecuteContentChangeSet(changeSetGroup,changeSetName);
if (flags & ECCS_FLAG_MAP_CLEANUP)
{
CMountableContent::CleanupAfterMapChange();
}
BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = tempValue;)
}
void CExtraContentManager::RevertContentChangeSetInternal(
CMountableContent* content, atHashString changeSetGroup, atHashString changeSetName, u32 actionMask, u32 flags DURANGO_ONLY(, bool addPending/*=true*/))
{
BANK_ONLY(bool tempValue = strStreamingInfoManager::ms_bValidDlcOverlay;)
sOverlayInfo * info = NULL;
if(flags & ECCS_FLAG_USE_LATEST_VERSION)
{
atHashString versionedChange = GetVersionedChangeSetName(m_overlayInfo,changeSetName);
Displayf("Reverting changeset %s in group %s for %s",changeSetName.TryGetCStr(),changeSetGroup.TryGetCStr(),content->GetName());
if(versionedChange != 0)
{
Displayf("This is a versioned changeset: %s",versionedChange.TryGetCStr());
info = GetHighestOverlayInfoForVersionedChange(m_overlayInfo,versionedChange);
if(info->m_state == sOverlayInfo::ACTIVE)
{
content = GetContentByHash(info->m_content);
changeSetName = info->m_changeSet;
changeSetGroup = info->m_changeSetGroupToExecuteWith;
BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = true;)
info->m_state = sOverlayInfo::INACTIVE;
Displayf("Reverting highest version of the changeset %s in group %s in %s",changeSetName.TryGetCStr(),changeSetGroup.TryGetCStr(),content->GetName());
}
else
{
//already reverted
return;
}
}
else if(changeSetGroup == CCS_GROUP_MAP || changeSetGroup == CCS_GROUP_MAP_SP)
{
Assertf(0, "Map changesets should be versioned, Content: %s", content->GetName());
#if !__NO_OUTPUT
Quitf("Map changesets should be versioned, Content: %s", content->GetName());
#endif // !__NO_OUTPUT
}
}
#if RSG_DURANGO
// Because XB1 doesn't have a persistent back buffer, we need to wait for it to copy out of ESRAM
if (addPending && (changeSetGroup == CCS_GROUP_MAP || changeSetGroup == CCS_GROUP_MAP_SP))
{
if (const CDataFileMgr::ContentChangeSet* csPtr = DATAFILEMGR.GetContentChangeSet(changeSetName))
{
// If we need a loading screen and we're not already showing one then prep for last frame
if (csPtr->m_requiresLoadingScreen && csPtr->m_loadingScreenContext == LOADINGSCREEN_CONTEXT_LAST_FRAME &&
gRenderThreadInterface.IsUsingDefaultRenderFunction() && !CLoadingScreens::AreActive() REPLAY_ONLY(&& !CVideoEditorPlayback::IsLoading()))
{
m_pendingActions.PushAndGrow(SPendingCSAction(content->GetNameHash(), changeSetGroup, changeSetName, flags, actionMask, false, true));
if (!m_waitingForBBCopy)
{
CPauseMenu::TogglePauseRenderPhases(false, OWNER_LOADING_SCR, __FUNCTION__ );
m_waitingForBBCopy = true;
}
if (info)
info->m_state = sOverlayInfo::ACTIVE;
return;
}
}
}
#endif
if(flags & ECCS_FLAG_USE_LOADING_SCREEN)
{
if ((changeSetGroup == CCS_GROUP_MAP) || (changeSetGroup == CCS_GROUP_MAP_SP))
{
audNorthAudioEngine::PurgeInteriors();
}
// Check to see if we should trigger the loading screen
if (const CDataFileMgr::ContentChangeSet* csPtr = DATAFILEMGR.GetContentChangeSet(changeSetName))
{
if (csPtr->m_requiresLoadingScreen)
TriggerLoadingScreen(csPtr->m_loadingScreenContext);
}
}
content->RevertContentChangeSet(changeSetGroup,changeSetName,actionMask);
BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = tempValue; )
}
bool CExtraContentManager::AreAnyCCSPending()
{
// If we have pending actions, or if we're still showing a loading screen for some actions, we're pending
return DURANGO_ONLY(m_pendingActions.GetCount() > 0 || ) (m_loadingScreenState.m_displayed && CLoadingScreens::AreActive());
}
atHashString CExtraContentManager::GetVersionedChangeSetName(atArray<sOverlayInfo*>& overlayInfos, atHashString changeSetName)
{
for(int i=0;i<overlayInfos.GetCount();i++)
{
if(changeSetName == overlayInfos[i]->m_changeSet)
{
return overlayInfos[i]->m_nameId;
}
}
return atHashString::Null();
}
sOverlayInfo* CExtraContentManager::GetHighestOverlayInfoForVersionedChange(atArray<sOverlayInfo*>& overlayInfos, atHashString versionedChange)
{
sOverlayInfo* retval = NULL;
for(int i=0;i<overlayInfos.GetCount();i++)
{
if(overlayInfos[i]->m_nameId == versionedChange)
{
if(retval)
{
if(overlayInfos[i]->m_version > retval->m_version)
{
retval = overlayInfos[i];
}
}
else
{
retval = overlayInfos[i];
}
}
}
return retval;
}
void CExtraContentManager::ExecutePendingOverlays()
{
BANK_ONLY(bool tempValue = strStreamingInfoManager::ms_bValidDlcOverlay;)
BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = true;)
for(int i=0;i<m_overlayInfo.GetCount();i++)
{
sOverlayInfo*& overlayInfo = m_overlayInfo[i];
CMountableContent* content = GetContentByHash(overlayInfo->m_content);
if(Verifyf(content,"Content not found"))
{
if(overlayInfo->m_changeSetGroupToExecuteWith == CCS_GROUP_ON_DEMAND)
{
if(overlayInfo->m_state==sOverlayInfo::WILL_ACTIVATE )
{
Assertf(content, "Mountable content doesn't exist!");
dlcDisplayf("[%s]Activating %s : %s VERSION: %d", content->GetName(),overlayInfo->m_changeSet.TryGetCStr(),overlayInfo->m_nameId.TryGetCStr(), overlayInfo->m_version);
if(overlayInfo->m_content)
{
ExecuteContentChangeSet(content->GetNameHash(),(u32)CCS_GROUP_ON_DEMAND,overlayInfo->m_changeSet);
overlayInfo->m_state = sOverlayInfo::ACTIVE;
}
}
#if __BANK
else
{
dlcDisplayf("Changesets execution suppressed for %s in content %s, a higher version is available/executed ",overlayInfo->m_nameId.TryGetCStr(), content->GetName() );
}
#endif // __BANK
}
}
}
BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = tempValue;)
}
#if EC_CLOUD_MANIFEST
CExtraContentCloudListener::CExtraContentCloudListener()
: m_manifestReqID(INVALID_CLOUD_REQUEST_ID)
//, m_bParsingCloud(false)
{
}
CExtraContentCloudListener::~CExtraContentCloudListener()
{
CloudManager::GetInstance().RemoveListener(this);
}
void CExtraContentCloudListener::Init()
{
//CloudManager::GetInstance().AddListener(this);
}
void CExtraContentCloudListener::Shutdown()
{
//CloudManager::GetInstance().RemoveListener(this);
}
bool CExtraContentCloudListener::DoManifestRequest()
{
if(CloudManager::GetInstance().IsRequestActive(m_manifestReqID))
{
return false;
}
dlcDebugf3("DLC: Requesting cloud manifest");
const char *manifestPath;
if(sysAppContent::IsJapaneseBuild())
manifestPath = "extraContent/ExtraContentManifest_jpn.xml";
else // all other regions
manifestPath = "extraContent/ExtraContentManifest.xml";
EXTRACONTENT.SetCloudManifestState(CLOUDFILESTATE_CACHING);
m_manifestReqID = CloudManager::GetInstance().RequestGetTitleFile(manifestPath,10,eRequest_CacheAddAndEncrypt|eRequest_AlwaysQueue|eRequest_Critical);
return true;
}
void CExtraContentManager::BeginEnumerateCloudContent()
{
// We'll first check the master file that knows about all available content.
m_cloudTimedOut = false;
#if !__FINAL
if(PARAM_extracloudmanifest.Get())
LoadCloudManifestFromCommandLine();
else
if(!PARAM_disableExtraCloudContent.Get())
#endif // !__FINAL
{
m_cloudListener.DoManifestRequest();
}
return;
}
void CExtraContentManager::UpdateCloudStorage()
{
if (!NetworkInterface::IsCloudAvailable())
{
//if the cloud isn't available clear the TRANSFER_ERROR state. lavalley / bektas
if (m_cloudData.m_CloudManifest.m_State == CLOUDFILESTATE_TRANSFER_ERROR)
{
m_cloudData.m_CloudManifest.m_State = CLOUDFILESTATE_NOT_FOUND;
}
return;
}
#if !__FINAL
if(!PARAM_disableExtraCloudContent.Get())
#endif
{
if(m_cloudTimedOut)
{
if(m_cloudListener.DoManifestRequest())
{
m_cloudTimedOut = false;
}
}
}
// TODO: Move somewhere else.
UpdateCloudCacher();
}
#if !__FINAL
void CExtraContentManager::DebugDumpCloudManifestContents()
{
// Let's see what files there are.
for (int i = 0; i < m_cloudData.m_CloudManifest.m_Files.GetCount(); i++)
{
dlcDisplayf("ExtraContent CloudManifest: FILE: %s", m_cloudData.m_CloudManifest.m_Files[i].m_File.c_str());
}
for (int i = 0; i < m_cloudData.m_CloudManifest.m_CompatibilityPacks.GetCount(); i++)
{
const CExtraContentCloudPackDescriptor &packDesc = m_cloudData.m_CloudManifest.m_CompatibilityPacks[i];
dlcDisplayf("ExtraContent CloudManifest: Compatibility pack: '%s', product id: '%s'", packDesc.m_Name.c_str(), packDesc.m_ProductId.c_str());
}
for (int i = 0; i < m_cloudData.m_CloudManifest.m_PaidPacks.GetCount(); i++)
{
const CExtraContentCloudPackDescriptor &packDesc = m_cloudData.m_CloudManifest.m_PaidPacks[i];
dlcDisplayf("ExtraContent CloudManifest: Paid pack: '%s', product id: '%s'", packDesc.m_Name.c_str(), packDesc.m_ProductId.c_str());
}
}
#endif // !__FINAL
void CExtraContentManager::UpdateCloudCacher()
{
if (!NetworkInterface::IsCloudAvailable())
return;
if (Verifyf(fiCachePartition::IsAvailable(), "No cache partition available - we will not be able to use data from the cloud"))
{
for (int x=0; x < m_cloudData.m_CloudManifest.m_Files.GetCount(); x++)
{
CCloudStorageFile &file = m_cloudData.m_CloudManifest.m_Files[x];
switch (file.m_State)
{
case CLOUDFILESTATE_UNCACHED:
// If there's nothing else going on, let's start a new transfer.
if (m_cloudData.m_CloudTransfers == 0)
{
dlcDebugf1("Beginning cloud transfer of %s to the cache partition", file.m_File.c_str());
// Create our cache directory, in case it doesn't exist already.
char targetDirectory[RAGE_MAX_PATH];
formatf(targetDirectory, "%stitleStorage", fiCachePartition::GetCachePrefix());
const fiDevice *targetDevice = fiDevice::GetDevice(targetDirectory, false);
if (Verifyf(targetDevice, "Can't find device to manage cache partition at %s", targetDirectory))
{
// Create the directory - don't check the return value, the directory is likely to exist already.
targetDevice->MakeDirectory(targetDirectory);
// Create the target file.
char targetFilename[RAGE_MAX_PATH];
formatf(targetFilename, "%s/%s.rpf", targetDirectory, file.m_File.c_str());
file.m_LocalPath = targetFilename;
char titlePath[RAGE_MAX_PATH];
#if !__FINAL
formatf(titlePath, "extraContent/%s%s.rpf", file.m_File.c_str(), !PARAM_useDebugCloudPatch.Get()? "" :file.m_File!=m_cloudData.m_CloudManifest.m_ScriptPatchName ? "" : "_DBG");
#else // !__FINAL
formatf(titlePath, "extraContent/%s.rpf", file.m_File.c_str());
#endif
m_cloudData.m_CloudTransferHandle = targetDevice->Create(targetFilename);
if (Verifyf(fiIsValidHandle(m_cloudData.m_CloudTransferHandle), "Cannot create cached file %s", targetFilename))
{
dlcDebugf1("Begin transfer to %s", targetFilename);
if (Verifyf(rlCloud::GetTitleFile(titlePath,
RLROS_SECURITY_DEFAULT,
0, //ifModifiedSincePosixTime
targetDevice,
m_cloudData.m_CloudTransferHandle,
NULL, //rlCloudFileInfo
NULL, //allocator
&m_cloudData.m_CloudTransferStatus),
"Error requesting cloud transfer from %s to %s", titlePath, targetFilename))
{
m_cloudData.m_CloudTransfers++;
file.m_State = CLOUDFILESTATE_CACHING;
file.m_lastRequestTime = fwTimer::GetSystemTimeInMilliseconds();
}
else
{
targetDevice->Close(m_cloudData.m_CloudTransferHandle);
file.m_State =CLOUDFILESTATE_TRANSFER_ERROR;
}
}
}
}
break;
case CLOUDFILESTATE_CACHING:
// Are we done yet?
{
netStatus::StatusCode status = m_cloudData.m_CloudTransferStatus.GetStatus();
if (status != netStatus::NET_STATUS_PENDING && status != netStatus::NET_STATUS_NONE)
{
dlcDebugf1("Cloud transfer for %s finished, result=%d", file.m_File.c_str(), status);
// We are.
m_cloudData.m_CloudTransfers--;
const fiDevice *device = fiDevice::GetDevice(file.m_LocalPath.c_str(), false);
device->Close(m_cloudData.m_CloudTransferHandle);
if (status == netStatus::NET_STATUS_SUCCEEDED)
{
// And it worked!
file.m_State = CLOUDFILESTATE_CACHED;
// Next up - mount it and make it available to the game.
file.m_Packfile = rage_new fiPackfile();
if (Verifyf(file.m_MountPoint.c_str(), "Cloud DLC %s doesn't specify a mount point in the manifest file", file.m_File.c_str()))
{
if (Verifyf(file.m_Packfile->Init(file.m_LocalPath.c_str(), true, fiPackfile::CACHE_NONE), "Error mounting cloud RPF file %s", file.m_LocalPath.c_str()))
{
if (Verifyf(file.m_Packfile->MountAs(file.m_MountPoint.c_str()), "Error mounting %s to %s", file.m_LocalPath.c_str(), file.m_MountPoint.c_str()))
{
if (!IsContentFilenamePresent(file.m_MountPoint.c_str()))
{
// Now add it to our official list of DLC.
CMountableContent mount;
mount.SetFilename(file.m_MountPoint.c_str());
mount.SetNameHash(file.m_File.c_str());
mount.SetPrimaryDeviceType(CMountableContent::DT_FOLDER);
AddContent(mount);
LoadContent(false, false);
}
}
}
}
}
else
{
// An error occurred.
int result = m_cloudData.m_CloudTransferStatus.GetResultCode();
if(result == 404)
{
dlcWarningf("Could not find the file on the cloud : %s", file.m_File.c_str());
file.m_State = CLOUDFILESTATE_NOT_FOUND;
}
else
{
dlcWarningf("ERROR: %d , Could not download %s from the cloud - giving up",result, file.m_File.c_str());
file.m_State = CLOUDFILESTATE_TRANSFER_ERROR;
}
}
}
}
break;
case CLOUDFILESTATE_CACHED:
// It's cached. That's cool.
break;
case CLOUDFILESTATE_NOT_FOUND:
break;
case CLOUDFILESTATE_TRANSFER_ERROR:
// We couldn't download this file. Let's not try again.
// TODO: We could have some retry logic here, where we first try again after a few weconds, then after a few minutes, or
// whatever. But for now, let's sit still.
for (int x=0; x < m_cloudData.m_CloudManifest.m_Files.GetCount(); x++)
{
CCloudStorageFile& file = m_cloudData.m_CloudManifest.m_Files[x];
if(fwTimer::GetSystemTimeInMilliseconds() - file.m_lastRequestTime > 15000)
{
file.m_State = CLOUDFILESTATE_UNCACHED;
}
}
break;
}
}
}
}
void CExtraContentManager::OnReceivedExtraContentManifest()
{
}
void CExtraContentCloudListener::OnCloudEvent( const CloudEvent* pEvent )
{
if(!pEvent)
{
return;
}
switch(pEvent->GetType())
{
case CloudEvent::EVENT_REQUEST_FINISHED:
{
const CloudEvent::sRequestFinishedEvent* pEventData = pEvent->GetRequestFinishedData();
if( !pEventData )
{
return;
}
if(pEventData->nRequestID == m_manifestReqID)
{
// clear the manifest ID
m_manifestReqID = INVALID_CLOUD_REQUEST_ID;
if(pEventData->bDidSucceed)
{
if(pEventData->pData)
{
//handle manifest
dlcDebugf3("Extra Content manifest successfully downloaded!");
const void* const & data = pEventData->pData;
u32 bSize = pEventData->nDataSize;
Displayf("%s loaded ", pEventData->szFileName);
BANK_ONLY(if(!PARAM_extracloudmanifest.Get()))
EXTRACONTENT.SetCloudManifest(data,bSize);
}
else
{
Displayf("MANIFEST REQUEST SUCCEEDED - No data!");
EXTRACONTENT.SetCloudManifestState(CLOUDFILESTATE_TRANSFER_ERROR);
}
}
else
{
Displayf("MANIFEST REQUEST UNSUCCESSFUL, Result code :%d", pEventData->nResultCode);
//handle manifest
if(pEventData->nResultCode == 404)
{
Displayf("Manifest state set to CLOUDFILESTATE_NOT_FOUND");
EXTRACONTENT.SetCloudManifestState(CLOUDFILESTATE_NOT_FOUND);
}
else
{
Displayf("Manifest state set to CLOUDFILESTATE_TRANSFER_ERROR");
EXTRACONTENT.SetCloudManifestState(CLOUDFILESTATE_TRANSFER_ERROR);
}
}
}
}
break;
case CloudEvent::EVENT_AVAILABILITY_CHANGED:
{
const CloudEvent::sAvailabilityChangedEvent* pEventData = pEvent->GetAvailabilityChangedData();
// if we now have cloud and don't have the cloud file, request it
if(pEventData->bIsAvailable BANK_ONLY(&& !PARAM_extracloudmanifest.Get()))
{
dlcDebugf1("Availability Changed - Requesting Cloud File");
DoManifestRequest();
}
}
break;
}
}
bool CExtraContentManager::LoadCloudManifestFromCommandLine()
{
if(PARAM_extracloudmanifest.Get())
{
const char* pPath=NULL;
PARAM_extracloudmanifest.Get(pPath);
if(PARSER.LoadObject(pPath, "xml", m_cloudData.m_CloudManifest))
{
m_cloudData.m_CloudManifest.m_State = CLOUDFILESTATE_CACHED ;
#if !__FINAL
DebugDumpCloudManifestContents();
#endif
return true;
}
else
{
Errorf("Can't load %s manifest specified in -extracloudmanifest", pPath);
m_cloudData.m_CloudManifest.m_State = CLOUDFILESTATE_NOT_FOUND;
return false;
}
}
return false;
}
bool CExtraContentManager::HasCloudContentSuccessfullyLoaded()
{
#if __BANK
if(EXTRACONTENT.BANK_EnableCloudWidgetsOverride())
{
return EXTRACONTENT.BANK_GetForceCloudFileState();
}
#endif // __BANK
#if !__FINAL
if(!PARAM_disableExtraCloudContent.Get())
#endif
{
if(m_cloudData.m_CloudManifest.m_State == CLOUDFILESTATE_NOT_FOUND )
{
return true;
}
if(m_cloudData.m_CloudManifest.m_State == CLOUDFILESTATE_CACHED)
{
for(int i=0; i<m_cloudData.m_CloudManifest.m_Files.GetCount();++i)
{
eCloudFileState& fileState = m_cloudData.m_CloudManifest.m_Files[i].m_State;
Displayf("File state: %d",fileState);
//Handle transfer error?
if(fileState!=CLOUDFILESTATE_CACHED&&fileState!=CLOUDFILESTATE_NOT_FOUND)
return false;
}
}
else
{
return false;
}
}
return true;
}
eDlcCloudResult CExtraContentManager::GetCloudFileState(eCloudFileState state)
{
switch (state)
{
case CLOUDFILESTATE_CACHED:
case CLOUDFILESTATE_NOT_FOUND:
return DLCRESULT_OK;
break;
case CLOUDFILESTATE_CACHING:
case CLOUDFILESTATE_UNCACHED:
return DLCRESULT_NOT_READY;
break;
case CLOUDFILESTATE_TRANSFER_ERROR:
return DLCRESULT_CONNECTION_ERROR;
break;
default:
return DLCRESULT_OK;
}
}
eDlcCloudResult CExtraContentManager::GetCloudContentResult()
{
eDlcCloudResult state = DLCRESULT_OK;
#if !__FINAL
if(!PARAM_disableExtraCloudContent.Get())
#endif //!__FINAL
{
eCloudFileState& manifestState = m_cloudData.m_CloudManifest.m_State;
if(m_cloudTimedOut)
return DLCRESULT_TIMEOUT;
state = GetCloudFileState(manifestState);
if(state==DLCRESULT_OK)
{
for(int i=0;i<m_cloudData.m_CloudManifest.m_Files.GetCount();i++)
{
eDlcCloudResult fileState = GetCloudFileState(m_cloudData.m_CloudManifest.m_Files[i].m_State);
if(fileState != DLCRESULT_OK)
{
return fileState;
}
}
}
}
return state;
}
bool CExtraContentManager::GetCloudContentState(int &bTimedOut, int uWaitDuration)
{
u32 currentTime = sysTimer::GetSystemMsTime();
bTimedOut = (int)false;
bool result = GetCloudContentRequestsFinished();
if(result)
{
dlcDebugf1("EXTRACONTENTMANIFEST: State request finished in %d ms", m_cloudStatusTimer);
m_cloudStatusTimer = 0;
return result;
}
if(m_cloudStatusTimer == 0)
{
m_cloudStatusTimer = currentTime;
dlcDebugf1("EXTRACONTENTMANIFEST: Requesting state");
}
if(currentTime - m_cloudStatusTimer > (uWaitDuration > 0 ? uWaitDuration : 30000) )
{
m_cloudStatusTimer = 0;
bTimedOut = (int)true;
m_cloudTimedOut = true;
dlcDebugf1("EXTRACONTENTMANIFEST: Request timed out");
}
return result;
}
void CExtraContentManager::EnsureManifestLoaded()
{
if(GetCloudContentResult()==DLCRESULT_CONNECTION_ERROR)
{
m_cloudListener.DoManifestRequest();
}
}
bool CExtraContentManager::GetCloudContentRequestsFinished()
{
#if __BANK
if(BANK_EnableCloudWidgetsOverride())
{
return BANK_GetForceManifestFileState();
}
#endif //__BANK
#if !__FINAL
if(!PARAM_disableExtraCloudContent.Get())
#endif //!__FINAL
{
eDlcCloudResult currentState = GetCloudFileState(m_cloudData.m_CloudManifest.m_State);
if (currentState == DLCRESULT_NOT_READY)
{
Displayf("Manifest file not ready!");
return false;
}
for (int x=0; x < m_cloudData.m_CloudManifest.m_Files.GetCount(); x++)
{
CCloudStorageFile &file = m_cloudData.m_CloudManifest.m_Files[x];
currentState = GetCloudFileState(file.m_State);
Displayf("File state: %d", file.m_State );
if(currentState == DLCRESULT_NOT_READY)
{
Displayf("File not ready! %s",file.m_File.c_str());
return false;
}
}
}
return true;
}
void CExtraContentManager::SetCloudManifestState(eCloudFileState state)
{
m_cloudData.m_CloudManifest.m_State = state;
}
void CExtraContentManager::SetCloudManifest(const void* const & data, u32 size)
{
char fileName[RAGE_MAX_PATH];
fiDevice::MakeMemoryFileName(fileName, sizeof(fileName), data , size, false, "CloudExtraContent");
if(PARSER.LoadObject(fileName, "", m_cloudData.m_CloudManifest) )
{
#if !__FINAL
DebugDumpCloudManifestContents();
#endif
m_cloudData.m_CloudManifest.m_State = CLOUDFILESTATE_CACHED;
//RequestCloudFiles();
}
else
{
dlcDebugf3("Can't load manifest!");
// match the state change for the command line version
m_cloudData.m_CloudManifest.m_State = CLOUDFILESTATE_NOT_FOUND;
}
}
#endif // EC_DO_MANIFEST_CHECKS
void CExtraContentManager::InitialiseSpecialTriggers(void)
{
m_specialTriggerTunablesTested = false;
m_specialTriggers = 0;
#if !__FINAL
if (PARAM_christmas.Get())
{
SetSpecialTrigger(ST_XMAS, true);
}
#endif //!__FINAL
}
void CExtraContentManager::UpdateSpecialTriggers(void)
{
if (m_specialTriggerTunablesTested == false)
{
if (Tunables::GetInstance().HasCloudRequestFinished())
{
if (!GetSpecialTrigger(ST_XMAS))
{
bool bValue = Tunables::GetInstance().TryAccess(BASE_GLOBALS_HASH, ATSTRINGHASH("TURN_SNOW_ON_OFF", 0xbba8f5b0), false) ||
Tunables::GetInstance().TryAccess(MP_GLOBAL_HASH, ATSTRINGHASH("TURN_SNOW_ON_OFF", 0xbba8f5b0), false);
SetSpecialTrigger(ST_XMAS, bValue);
}
m_specialTriggerTunablesTested = true;
}
}
}
bool CExtraContentManager::GenericChangesetChecks(atHashString condition)
{
if(atHashString("jpn_build",0xBE885BB1)==condition)
{
return sysAppContent::IsJapaneseBuild();
}
return true;
}
bool CExtraContentManager::LevelChecks(atHashString condition)
{
return CScene::GetCurrentLevelNameHash()==condition;
}
void CExtraContentManager::Init(u32 initMode)
{
#if EC_CLOUD_MANIFEST
m_cloudStatusTimer = 0;
m_cloudTimedOut = false;
#endif // EC_DO_MANIFEST_CHECKS
m_everHadBadPackOrder = false;
m_loadingScreenState.Reset();
#if !__FINAL
m_enumerateCommandLines = true;
#endif
#if GTA_REPLAY
ResetReplayState();
#endif // GTA_REPLAY
if (initMode == INIT_CORE)
{
#if RSG_DURANGO
m_waitingForBBCopy = false;
InitDurangoContent();
#endif
#if RSG_ORBIS
Assert(sysAppContent::IsInitialized());
if (s_contentPollThread == sysIpcThreadIdInvalid)
s_contentPollThread = sysIpcCreateThread(&CExtraContentManager::ContentPoll, NULL, sysIpcMinThreadStackSize, PRIO_LOWEST, "OrbisAddContPoll");
#endif
#if RSG_PS3 || RSG_XENON
if (s_codeCheckThread == sysIpcThreadIdInvalid)
s_codeCheckThread = sysIpcCreateThread(&CExtraContentManager::CodeCheck, NULL, sysIpcMinThreadStackSize, PRIO_LOWEST, "CodeCheck", 4, "CodeCheck");
#endif
m_overlayInfo.Reset();
#if EC_CLOUD_MANIFEST
m_cloudListener.Init();
m_cloudData.m_CloudManifestFileRequest = NULL;
m_cloudData.m_CloudTransfers = 0;
#endif // EC_DO_MANIFEST_CHECKS
m_currentGamerId.Clear();
m_localGamerIndex = -1;
m_enumerating = false;
m_enumerateOnUpdate = false;
m_currMapChangeState = MCS_NONE;
m_allowMapChangeAnyTime = true;
atDelegate<bool (atHashString)>* defaultChecks = rage_new atDelegate<bool (atHashString)>();
defaultChecks->Bind(this,&CExtraContentManager::GenericChangesetChecks);
atDelegate<bool (atHashString)>* levelChecks = rage_new atDelegate<bool (atHashString)>();
levelChecks->Bind(this,&CExtraContentManager::LevelChecks);
CMountableContent::RegisterExecutionCheck(defaultChecks,atHashString("build",0x59859136));
CMountableContent::RegisterExecutionCheck(levelChecks,atHashString("level",0xF66D3B99));
fiCachePartition::Init();
CDownloadableTextureManager::InitClass();
#if __XENON
m_deviceEnumerating = false;
fiDeviceXContent::InitClass();
fiDeviceXContent::SetDeviceChangeCallback(MakeFunctor(*this, &CExtraContentManager::DeviceChanged));
m_downloadContentListener = XNotifyCreateListener(XNOTIFY_LIVE); // create listener to check for content being installed
#endif
rlPresence::AddDelegate(&g_PresenceDlgt);
g_addContentServiceDelegate.Bind(&OnServiceEvent);
g_SysService.AddDelegate(&g_addContentServiceDelegate);
// CDataFileMount::RegisterMountInterface doesn't make a copy of the CDataFileMountInterface that is passed to it. It only stores a pointer to the CDataFileMountInterface in an array.
// It also modifies m_registerUnload within the CDataFileMountInterface.
// For that reason, if you want to call RegisterMountInterface with registerUnload=false then use g_extraContentFileMounter.
// If you want to call RegisterMountInterface with registerUnload=true then use g_extraContentFileMounterToRegisterUnload.
CDataFileMount::RegisterMountInterface(CDataFileMgr::CONTENT_UNLOCKING_META_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::OVERLAY_INFO_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::CLIP_SETS_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::EXPRESSION_SETS_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::FACIAL_CLIPSET_GROUPS_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::NM_BLEND_OUT_SETS_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::NM_TUNING_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::EXTRA_TITLE_UPDATE_DATA, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::VEHICLE_SHOP_DLC_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::LOADOUTS_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::LEVEL_STREAMING_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::WORLD_HEIGHTMAP_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::WATER_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::CARCOLS_GEN9_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::CARMODCOLS_GEN9_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::GEN9_EXCLUSIVE_ASSETS_VEHICLES_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::GEN9_EXCLUSIVE_ASSETS_PEDS_FILE, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::GTXD_PARENTING_DATA, &g_extraContentFileMounter);
CDataFileMount::RegisterMountInterface(CDataFileMgr::MOVE_NETWORK_DEFS, &g_extraContentFileMounterToRegisterUnload, eDFMI_UnloadFirst);
CDataFileMount::RegisterMountInterface(CDataFileMgr::TEXTFILE_METAFILE, &g_extraContentFileMounterToRegisterUnload, eDFMI_UnloadFirst);
CDataFileMount::RegisterMountInterface(CDataFileMgr::EXTRA_FOLDER_MOUNT_DATA, &g_extraContentFileMounterToRegisterUnload, eDFMI_UnloadFirst);
if(fiDevice::GetDevice(TITLE_UPDATE_MOUNT_PATH))
{
CMountableContent mount;
mount.SetFilename("update:/");
mount.SetNameHash(TITLE_UPDATE_PACK_NAME);
mount.SetPrimaryDeviceType(CMountableContent::DT_FOLDER);
mount.SetPermanent(true);
mount.SetDatFileLoaded(true);
AddContent(mount);
DATAFILEMGR.SetEnableFilePatching(true);
LoadContent(false);
DATAFILEMGR.SetEnableFilePatching(false);
}
InitialiseSpecialTriggers();
#if __BANK
InitBank();
#endif
}
else if (initMode == INIT_SESSION)
{
ExecuteTitleUpdateDataPatch((u32)CCS_TITLE_UPDATE_DLC_METADATA, true);
ExecuteTitleUpdateDataPatchGroup((u32)CCS_GROUP_UPDATE_DLC_METADATA, true);
// When we have loaded the session, process any enumeration requests...
ProcessEnumerationRequests(true);
// Hack...force load the 'mph4_gtxd' for the island heist. Later cl should remove this
// and use the content type tag in content.xml to solve this better
CMapFileMgr::GetInstance().LoadGlobalTxdParents("mph4_gtxd");
#if __BANK
fragTuneStruct::PreSaveEntityFunctor preSaveEntityFunctor;
preSaveEntityFunctor.Bind(&CExtraContentManager::ReturnAssetPathForFragTuneCB);
FRAGTUNE->SetPreSaveEntityFunctor(preSaveEntityFunctor);
#endif // __BANK
#if !__FINAL
const char* dlcPacks;
if (PARAM_buildDLCCacheData.Get(dlcPacks))
{
char buffer[RAGE_MAX_PATH] = {0};
strcpy(buffer,dlcPacks);
char* cur = strtok(buffer,";");
while(cur)
{
atHashString contentHash(cur);
CMountableContent* content = GetContentByHash(contentHash);
if(content)
{
ExecuteContentChangeSetGroup(contentHash,(u32)CCS_GROUP_MAP_SP);
RevertContentChangeSetGroup(contentHash,(u32)CCS_GROUP_MAP_SP);
ExecuteContentChangeSetGroup(contentHash,(u32)CCS_GROUP_MAP);
RevertContentChangeSetGroup(contentHash,(u32)CCS_GROUP_MAP);
}
cur = strtok(NULL,";");
}
}
#endif
if (PARAM_loadMapDLCOnStart.Get())
{
ExecuteContentChangeSetGroupForAll((u32)CCS_GROUP_MAP_SP);
ExecuteContentChangeSetGroupForAll((u32)CCS_GROUP_MAP);
}
else
{
if (!CNetwork::HasMatchStarted())
ExecuteContentChangeSetGroupForAll((u32)CCS_GROUP_MAP_SP);
}
#if __BANK
const char* ccsToEnable;
if(PARAM_enableMapCCS.Get(ccsToEnable))
{
char buffer[RAGE_MAX_PATH] = {0};
strcpy(buffer,ccsToEnable);
char* cur = strtok(buffer,";");
while(cur)
{
atHashString contentHash(cur);
CMountableContent* content = GetContentByHash(contentHash);
if(content)
ExecuteContentChangeSetGroup(contentHash,(u32)CCS_GROUP_MAP);
cur = strtok(NULL,";");
}
}
if (PARAM_enableSPMapCCS.Get(ccsToEnable))
{
char buffer[RAGE_MAX_PATH] = {0};
strcpy(buffer,ccsToEnable);
char* cur = strtok(buffer,";");
while(cur)
{
atHashString contentHash(cur);
CMountableContent* content = GetContentByHash(contentHash);
if(content)
ExecuteContentChangeSetGroup(contentHash,(u32)CCS_GROUP_MAP_SP);
cur = strtok(NULL,";");
}
}
#endif // __BANK
ExecuteTitleUpdateDataPatchGroup(CCS_GROUP_POST_DLC_PATCH,true);
CModelIndex::MatchAllModelStrings();
}
}
void CExtraContentManager::LoadLevelPacks()
{
DATAFILEMGR.SetEnableFilePatching(true);
BeginEnumerateContent(true,true);
RemountUpdate();
DATAFILEMGR.SetEnableFilePatching(false);
}
void CExtraContentManager::Shutdown(u32 shutdownMode)
{
#if EC_CLOUD_MANIFEST
m_cloudStatusTimer = 0;
m_cloudTimedOut = false;
#endif // EC_DO_MANIFEST_CHECKS
CancelEnumerateContent(true);
pgStreamer::Drain();
strStreamingEngine::GetLoader().Flush();
gRenderThreadInterface.Flush();
strStreamingEngine::GetInfo().FlushLoadedList(STR_DONTDELETE_MASK);
for(u32 i=0;i<m_overlayInfo.GetCount();i++)
{
delete m_overlayInfo[i];
m_overlayInfo[i] = NULL;
}
m_overlayInfo.Reset();
for (u32 i = 0; i < m_content.GetCount(); i++)
{
if (!m_content[i].GetIsPermanent())
{
m_content[i].ShutdownContent();
m_content.Delete(i);
i--;
}
}
m_contentLocks.Reset();
m_corruptedContent.Reset();
#if RSG_ORBIS
m_ps4DupeEntitlements.Reset();
#endif
#if RSG_DURANGO
ShutdownDurangoContent();
#endif
CPlayerSpecialAbilityManager::UpdateDlcMultipliers();
if (shutdownMode == SHUTDOWN_CORE)
{
g_SysService.RemoveDelegate(&g_addContentServiceDelegate);
g_addContentServiceDelegate.Reset();
CDownloadableTextureManager::ShutdownClass();
rlPresence::RemoveDelegate(&g_PresenceDlgt);
#if RSG_ORBIS
sysIpcWaitThreadExit(s_contentPollThread);
#endif
#if RSG_PS3 || RSG_XENON
s_codeCheckRun = false;
sysIpcWaitThreadExit(s_codeCheckThread);
#endif
}
else if (shutdownMode == SHUTDOWN_SESSION)
{
ExecuteTitleUpdateDataPatchGroup((u32)CCS_GROUP_POST_DLC_PATCH,false);
ExecuteTitleUpdateDataPatch((u32)CCS_TITLE_UPDATE_DLC_METADATA, false);
ExecuteTitleUpdateDataPatchGroup((u32)CCS_GROUP_UPDATE_DLC_METADATA, false);
#if __BANK
Bank_UpdateContentDisplay();
#endif
}
if (m_currentGamerId.IsValid())
m_enumerateOnUpdate = true;
}
void CExtraContentManager::ShutdownMetafiles(u32 /*shutdownMode*/, eDFMI_UnloadType unloadType)
{
CDataFileMount::SetUnloadBehaviour(unloadType);
{
for (u32 i = 0; i < m_content.GetCount(); i++)
{
if (!m_content[i].GetIsPermanent())
m_content[i].StartRevertActiveChangeSets();
}
}
CDataFileMount::SetUnloadBehaviour(eDFMI_UnloadDefault);
}
void CExtraContentManager::ShutdownMapChanges(u32 /*shutdownMode*/)
{
RevertContentChangeSetGroupForAll((u32)CCS_GROUP_MAP_SP);
RevertContentChangeSetGroupForAll((u32)CCS_GROUP_MAP);
CMapFileMgr::GetInstance().Cleanup(); // ensure the map file dependency data is cleared up - bug 6177812
}
void CExtraContentManager::InitProfile()
{
if (const rlGamerInfo* pGamerInfo = NetworkInterface::GetActiveGamerInfo())
{
m_localGamerIndex = pGamerInfo->GetLocalIndex();
m_currentGamerId = pGamerInfo->GetGamerId();
#if __XENON
BeginDeviceEnumeration();
#endif
}
m_enumerateOnUpdate = true;
#if EC_CLOUD_MANIFEST
m_cloudStatusTimer = 0;
m_cloudTimedOut = false;
SetCloudManifestState(CLOUDFILESTATE_UNCACHED);
BeginEnumerateCloudContent();
#endif // EC_DO_MANIFEST_CHECKS
}
void CExtraContentManager::ShutdownProfile()
{
#if EC_CLOUD_MANIFEST
SetCloudManifestState(CLOUDFILESTATE_UNCACHED);
m_cloudStatusTimer = 0;
m_cloudTimedOut = false;
#endif // EC_DO_MANIFEST_CHECKS
m_enumerateOnUpdate = false;
m_localGamerIndex = -1;
m_currentGamerId.Clear();
#if __XENON
fiDeviceXContent::SetLocalIndex(-1);
#endif
}
void CExtraContentManager::UpdateRebootMessage()
{
if (fiDeviceInstaller::HasRebooted(EC_REBOOT_ID) && !CLoadingScreens::AreActive())
{
bool showMessage = CLoadingScreens::ShowRebootMessage("EC_REBOOT_MSG", EC_REBOOT_ID);
if (showMessage)
CWarningScreen::SetWarningMessageInUse();
}
}
void CExtraContentManager::Update()
{
bool waitForEnumeration = false;
#if RSG_DURANGO
m_waitingForBBCopy = false;
// :(
for (s32 i = 0; i < m_pendingActions.GetCount(); i++)
{
SPendingCSAction& currAction = m_pendingActions[i];
CMountableContent* content = EXTRACONTENT.GetContentByHash(currAction.m_content);
if (currAction.m_requiesLastFrame && !CPauseMenu::GetPauseRenderPhasesStatus())
{
m_waitingForBBCopy = true;
continue;
}
if (currAction.m_execute)
{
if (Verifyf(content, "ProcessPending - Invalid execution contentHash %s", currAction.m_content.GetCStr()))
{
ExecuteContentChangeSetInternal(content, currAction.m_group, currAction.m_changeSet, currAction.m_flags, false);
}
}
else
{
if (Verifyf(content, "ProcessPending - Invalid reversion contentHash %s", currAction.m_content.GetCStr()))
{
RevertContentChangeSetInternal(content, currAction.m_group, currAction.m_changeSet, currAction.m_actions, currAction.m_flags, false);
}
}
m_pendingActions.Delete(i);
i--;
}
#endif
UpdateRebootMessage();
#if EC_CLOUD_MANIFEST
UpdateCloudStorage();
#endif // EC_DO_MANIFEST_CHECKS
UpdateMapChangeState();
UpdateSpecialTriggers();
#if __XENON
// Check for content installed message and if it has then enumerate content again
DWORD id;
ULONG_PTR param;
if (XNotifyGetNext(m_downloadContentListener, XN_LIVE_CONTENT_INSTALLED, &id, &param))
{
OnContentDownloadCompleted();
waitForEnumeration = true;
}
#endif // __XENON
if (m_enumerating)
EndEnumerateContent(waitForEnumeration,false);
ProcessEnumerationRequests(false);
ProcessDeviceChangedMessages();
CloseLoadingScreen();
#if __BANK
Bank_ExecuteBankUpdate();
#endif // __BANK
}
void CExtraContentManager::UpdateMapChangeState()
{
#if __BANK
eMapChangeStates prevState = m_currMapChangeState;
#endif
switch (m_currMapChangeState)
{
case MCS_INIT:
{
if (camInterface::IsFadedIn())
camInterface::FadeOut(MAP_CHANGE_FADE_TIME, true);
else if (camInterface::IsFadedOut())
m_currMapChangeState = MCS_UPDATE;
} break;
case MCS_UPDATE:
{
} break;
case MCS_END:
{
camInterface::FadeIn(MAP_CHANGE_FADE_TIME);
m_currMapChangeState = MCS_NONE;
} break;
default: break;
}
#if __BANK
if (prevState != m_currMapChangeState)
Bank_UpdateMapChangeState();
#endif
}
void CExtraContentManager::ProcessEnumerationRequests(bool immediate)
{
if (m_enumerateOnUpdate || immediate)
{
BeginEnumerateContent(immediate,false);
m_enumerateOnUpdate = false;
}
}
enum eDeviceChangedMessage
{
DC_NONE = 0,
DC_SUCCESS,
DC_FAIL
};
sysMessageQueue<eDeviceChangedMessage, 16> s_deviceChangedMessages;
//
// name: ProcessDeviceChangedMessages
// description: Main thread function to deal with devices having changed
void CExtraContentManager::ProcessDeviceChangedMessages()
{
static bool bUnavailableContent = false;
static bool bShowWarningScreen = false;
static bool bPaused = false;
eDeviceChangedMessage msg = DC_NONE;
while (s_deviceChangedMessages.PopPoll(msg)) {}
switch (msg)
{
case DC_SUCCESS:
{
// If content wasn't available before
if(bUnavailableContent)
{
CWarningScreen::Remove();
bShowWarningScreen = false;
}
// check enumerated content to update device ids
UpdateContentArray();
bUnavailableContent = false;
if (bPaused)
{
fwTimer::EndUserPause();
bPaused = false;
}
}
break;
case DC_FAIL:
{
CWarningScreen::SetWarningMessageInUse();
bShowWarningScreen = true;
if (!fwTimer::IsGamePaused())
{
fwTimer::StartUserPause();
bPaused = true;
}
CControlMgr::StopPlayerPadShaking();
// If playing network then bail
if(NetworkInterface::IsNetworkOpen())
{
gnetDebug1("Bailing due to Extra content Device Change");
NetworkInterface::Bail(BAIL_NEW_CONTENT_INSTALLED, BAIL_CTX_NONE);
}
bUnavailableContent = true;
}
break;
default:
break;
}
if (bShowWarningScreen)
{
#if __XENON
// If we are showing the device removed warning screen and there is no profile (means they signed out on this screen) reboot...
if (m_localGamerIndex == -1)
fiDeviceInstaller::Reboot(EC_REBOOT_ID);
else
#endif
CWarningScreen::SetMessage(WARNING_MESSAGE_STANDARD, "FE_NODLC");
}
}
//
// name: DeviceChangedCB
// description: Function that is called when the storage device changes
void CExtraContentManager::DeviceChanged(bool bSuccess)
{
if (!bSuccess)
{
s_deviceChangedMessages.Push(DC_FAIL);
}
else
{
s_deviceChangedMessages.Push(DC_SUCCESS);
}
}
// Orbis calls this from the Polling thread
void CExtraContentManager::OnContentDownloadCompleted()
{
dlcDebugf1("OnContentDownloadCompleted");
#if __XENON
CLiveManager::GetCommerceMgr().GetConsumableManager()->SetOwnershipDataDirty();
#endif
CLiveManager::GetCommerceMgr().ResetAutoConsumeMessage();
m_enumerateOnUpdate = true;
}
void CExtraContentManager::BeginEnumerateContent(bool immediate, bool earlyStartup)
{
dlcDebugf1("CExtraContentManager::BeginEnumerateContent");
if (m_enumerating)
EndEnumerateContent(true,earlyStartup);
m_enumerating = true;
#if __XENON
BeginDeviceEnumeration();
#endif
if(immediate)
EndEnumerateContent(true,earlyStartup);
#if EC_CLOUD_MANIFEST
UpdateCloudStorage();
#endif // EC_DO_MANIFEST_CHECKS
}
#if __XENON
void CExtraContentManager::BeginDeviceEnumeration()
{
if (m_localGamerIndex != -1)
{
fiDeviceXContent::SetLocalIndex(m_localGamerIndex);
fiDeviceXContent::StartEnumerate();
m_deviceEnumerating = true;
}
}
#endif
#if !__FINAL
bool IsValidPath(fiFindData& find)
{
bool isValid = false;
if ((find.m_Attributes & FILE_ATTRIBUTE_DIRECTORY) && (find.m_Name[0] != '.'))
{
#if RSG_ORBIS || RSG_DURANGO || RSG_PC
const u32 MAX_INVALID_NAMES = 8;
const char* invalidNames[MAX_INVALID_NAMES] =
{ "Online", "outputPackages", "Packages", "dev", "art", "assets", "drm", "signage" };
#else
const u32 MAX_INVALID_NAMES = 8;
const char* invalidNames[MAX_INVALID_NAMES] =
{ "Online", "outputPackages", "Packages", "dev_ng", "art", "assets", "drm", "signage" };
#endif
isValid = true;
for (u32 i = 0; i < MAX_INVALID_NAMES; i++)
isValid &= !(strcmp(find.m_Name, invalidNames[i]) == 0);
}
return isValid;
}
int CExtraContentManager::RecursiveEnumExtraContent(const char* searchPath)
{
fiFindData find;
const fiDevice *pDevice = fiDevice::GetDevice(searchPath, true);
fiHandle handle = pDevice->FindFileBegin(searchPath, find);
int searchPathLen = istrlen(searchPath);
bool validSearchPath = !searchPathLen || searchPath[searchPathLen-1] == '/' || searchPath[searchPathLen-1] == '\\';
int foundDLCCount = 0;
Assertf(validSearchPath, "CExtraContentManager::RecursiveEnumExtraContent - searchPath must end with a slash! %s", searchPath);
if ( validSearchPath && fiIsValidHandle( handle ) )
{
#if __BANK
char assetFolder[RAGE_MAX_PATH] = { 0 };
#ifdef RS_BRANCHSUFFIX
formatf(assetFolder, RAGE_MAX_PATH, "%sart/ng/anim/export_mb", searchPath);
if(ASSET.Exists(assetFolder, ""))
{
CDebugClipDictionary::PushAssetFolder(assetFolder);
}
formatf(assetFolder, RAGE_MAX_PATH, "%sassets" RS_BRANCHSUFFIX "/cuts", searchPath);
if(ASSET.Exists(assetFolder, ""))
{
CDebugClipDictionary::PushAssetFolder(assetFolder);
CutSceneManager::PushAssetFolder(assetFolder);
}
#else
formatf(assetFolder, RAGE_MAX_PATH, "%sart/anim/export_mb", searchPath);
if(ASSET.Exists(assetFolder, ""))
{
CDebugClipDictionary::PushAssetFolder(assetFolder);
}
formatf(assetFolder, RAGE_MAX_PATH, "%sassets/cuts", searchPath);
if(ASSET.Exists(assetFolder, ""))
{
CDebugClipDictionary::PushAssetFolder(assetFolder);
CutSceneManager::PushAssetFolder(assetFolder);
}
#endif
#endif // __BANK
do
{
atString filePath;
if (IsValidPath(find))
{
filePath = searchPath;
filePath += find.m_Name;
ASSET.PushFolder(filePath.c_str());
// Is this a DLC folder?
if (ASSET.Exists(CMountableContent::GetSetupFileName(), ""))
{
if(!IsContentFilenamePresent(filePath.c_str()))
{
dlcDebugf3("CExtraContentManager::RecursiveEnumExtraContent - Looking for local DLC in %s", filePath.c_str());
CMountableContent newContent;
newContent.SetFilename(filePath);
newContent.SetUsesPackFile(false);
newContent.SetPrimaryDeviceType(CMountableContent::DT_FOLDER);
newContent.SetNameHash(newContent.GetFilename());
AddContent(newContent);
}
ASSET.PopFolder();
foundDLCCount++;
}
else
{
ASSET.PopFolder();
// It's not - let's recurse in here.
char nextPath[RAGE_MAX_PATH] = { 0 };
formatf(nextPath, "%s/", filePath.c_str());
foundDLCCount+=RecursiveEnumExtraContent(nextPath);
}
}
}
while (pDevice->FindFileNext( handle, find ));
pDevice->FindFileEnd(handle);
}
return foundDLCCount;
}
#endif // !__FINAL
void CExtraContentManager::CancelEnumerateContent(bool XENON_ONLY(bWait))
{
dlcDebugf1("End DLC enumeration (%s)", false XENON_ONLY(|| bWait) ? "Async" : "Block");
m_enumerating = false;
#if __XENON
if (m_deviceEnumerating)
fiDeviceXContent::FinishEnumerate(bWait);
m_deviceEnumerating = false;
#endif
}
bool CExtraContentManager::EndEnumerateContent(bool bWait, bool earlyStartup)
{
CancelEnumerateContent(bWait);
#if __XENON
fiDeviceXContent::LockEnumerate();
{
UpdateContentArray();
}
fiDeviceXContent::UnlockEnumerate();
#endif
#if __PPU || RSG_ORBIS || RSG_PC || RSG_DURANGO
UpdateContentArray();
#endif
// Load and execute any new content once we have added it to the array...
LoadContent(!PARAM_delayDLCLoad.Get(), earlyStartup);
return true;
}
//
// name: strcpy
// description: strcpy that takes a wide string input and an 8 bit output
void Strcpy(char* pDest, wchar_t* pSrc)
{
while(*pSrc != 0)
{
*pDest = (char)*pSrc;
pDest++;
pSrc++;
}
*pDest = 0;
}
void CExtraContentManager::NormalizePath(char *src_path, bool trailingSlash /* = true */)
{
Assert(src_path);
const char SEPARATOR = '/';
const char OTHER_SEPARATOR = '\\';
const char *s = src_path;
char *d = src_path;
bool has_dots = false;
while(*s)
{
if((*s == SEPARATOR) ||
(*s == OTHER_SEPARATOR))
{
*d++ = SEPARATOR;
while((*s == SEPARATOR) ||
(*s == OTHER_SEPARATOR))
s++;
}
else
{
has_dots |= (*s == '.');
*d++ = *s++;
}
}
if(trailingSlash && (d > src_path) && !has_dots)
{
if(*(d - 1) != SEPARATOR)
{
*d++ = SEPARATOR;
}
}
*d = '\0';
}
#if RSG_ORBIS
bool CExtraContentManager::IsContentFilenamePresent(atHashString& label) const
{
for (u32 i = 0; i < m_content.GetCount(); i++)
{
atHashString currLabelHash = atHashString(m_content[i].GetPS4EntitlementLabel().data);
if (currLabelHash == label)
return true;
}
return false;
}
#endif
bool CExtraContentManager::IsContentFilenamePresent(const char* fileName) const
{
char fname[RAGE_MAX_PATH] = { 0 };
char contentFname[RAGE_MAX_PATH] = { 0 };
strncpy(fname, fileName, RAGE_MAX_PATH);
NormalizePath(fname);
for (u32 i = 0; i < m_content.GetCount(); i++)
{
strncpy(contentFname, m_content[i].GetFilename(), RAGE_MAX_PATH);
NormalizePath(contentFname);
if (!strcmpi(fname, contentFname))
return true;
}
return false;
}
#if __PPU
void CExtraContentManager::UpdateContentArrayPs3(const char *path)
{
const fiDevice& localDevice = fiDeviceLocal::GetInstance(); // get path to usrdir folder
fiFindData findDlcData;
fiHandle DlcHandle;
DlcHandle = localDevice.FindFileBegin(path, findDlcData);
dlcDebugf2("CExtraContentManager::UpdateContentArray - Scanning %s for DLC", path);
if ( fiIsValidHandle( DlcHandle ) )
{
do
{
dlcDebugf3("CExtraContentManager::UpdateContentArray - Considering %s", findDlcData.m_Name);
// First find the folder named "dlc"
if (findDlcData.m_Attributes == FILE_ATTRIBUTE_DIRECTORY && strnicmp(findDlcData.m_Name, "dlc", 3) == 0)
{
fiFindData findExtraContentData;
fiHandle extraContentHandle;
char DlcPath[RAGE_MAX_PATH] = { 0 };
formatf(DlcPath, "%s/%s",path, findDlcData.m_Name);
extraContentHandle = localDevice.FindFileBegin(DlcPath, findExtraContentData);
dlcDebugf3("CExtraContentManager::UpdateContentArray - Looking into %s", DlcPath);
if( fiIsValidHandle( extraContentHandle ) )
{
do
{
dlcDebugf3("CExtraContentManager::UpdateContentArray - Checking out %s", findExtraContentData.m_Name);
// Then find any folders whose name begins with "episode" inside the "dlc" folder
if (findExtraContentData.m_Attributes == FILE_ATTRIBUTE_DIRECTORY &&
(strnicmp(findExtraContentData.m_Name, "episode", 7) == 0 || strnicmp(findExtraContentData.m_Name, "dlc", 3) == 0))
{
char filePath[RAGE_MAX_PATH] = { 0 };
sprintf(filePath, "%s/%s", DlcPath, findExtraContentData.m_Name);
if (!IsContentFilenamePresent(filePath))
{
CMountableContent mount;
mount.SetFilename(filePath);
#if !__FINAL
if (!PARAM_extracontent.Get())
#endif
mount.SetUsesPackFile(true);
AddContent(mount);
}
}
}
while(localDevice.FindFileNext( extraContentHandle, findExtraContentData ));
localDevice.FindFileEnd(extraContentHandle);
}
}
}
while(localDevice.FindFileNext( DlcHandle, findDlcData ));
localDevice.FindFileEnd(DlcHandle);
}
}
#endif // __PPU
//
// name: CExtraContentManager::UpdateContentArray
// description: Update content array based on enumerated content from fiDeviceXContent
void CExtraContentManager::UpdateContentArray()
{
#if !__FINAL
bool ignoreDeployed = PARAM_ignoreDeployedPacks.Get();
if(PARAM_usecompatpacks.Get())
{
if (m_enumerateCommandLines)
{
SMandatoryPacksData mandatoryPacksData;
if(Verifyf(PARSER.LoadObject(COMPATPACKS_SET_PATH, NULL, mandatoryPacksData),
"-usecompatpacks couldn't load %s.", COMPATPACKS_SET_PATH))
{
Displayf("Loaded MandatoryPacksData from %s", COMPATPACKS_SET_PATH);
for(int i = 0; i < mandatoryPacksData.m_Paths.GetCount(); i++)
{
int foundPacks = RecursiveEnumExtraContent(mandatoryPacksData.m_Paths[i].c_str());
Assertf(foundPacks>0,"Couldn't find any DLC in the provided path %s , check your compatpacks.xml",mandatoryPacksData.m_Paths[i].c_str());
Displayf("[DLC]Found %d DLC packs in %s",foundPacks, mandatoryPacksData.m_Paths[i].c_str());
}
}
}
ignoreDeployed = true;
}
if(PARAM_extracontent.Get())
{
if (m_enumerateCommandLines)
{
const char* pSearchPath = NULL;
PARAM_extracontent.Get(pSearchPath);
char buffer[RAGE_MAX_PATH] = {0};
safecpy(buffer,pSearchPath);
char* cur = strtok(buffer,";");
while(cur)
{
int foundPacks = RecursiveEnumExtraContent(cur);
Assertf(foundPacks>0,"Couldn't find any DLC in the provided path %s , check your -extracontent argument",cur);
Displayf("[DLC]Found %d DLC packs in %s",foundPacks,cur);
cur = strtok(NULL,";");
}
}
}
m_enumerateCommandLines = false;
// Make commandline enumerators mutually exclusive with deployed packages, this avoids dupes as we only use file path as the ID
if (ignoreDeployed)
return;
#endif
SMandatoryPacksData deployedPacksData;
if(Verifyf(PARSER.LoadObject(COMPATPACKS_DEPLOYED_PATH, NULL, deployedPacksData),
"dlcList.xml isn't present as %s.", COMPATPACKS_DEPLOYED_PATH))
{
Displayf("Loaded dlcList data from %s", COMPATPACKS_DEPLOYED_PATH);
for(int i = 0; i < deployedPacksData.m_Paths.GetCount(); i++)
{
char path[RAGE_MAX_PATH];
DATAFILEMGR.ExpandFilename(deployedPacksData.m_Paths[i].c_str(),path,RAGE_MAX_PATH);
AddContentFolder(path);
}
}
#if RSG_DURANGO
if(CLiveManager::IsSignedIn())
{
EnumerateDurangoContent();
UpdateContentArrayDurango();
}
#endif
#if __XENON
for (u32 i = 0; i < fiDeviceXContent::GetNumXContent(); i++)
{
XCONTENT_DATA* pContent = fiDeviceXContent::GetXContent(i);
if (!IsContentFilenamePresent(pContent->szFileName))
{
CMountableContent newContent;
char tempStr[XCONTENT_MAX_DISPLAYNAME_LENGTH] = { 0 };
Strcpy(tempStr, pContent->szDisplayName);
newContent.SetFilename(pContent->szFileName);
newContent.SetNameHash(tempStr);
newContent.SetPrimaryDeviceType(CMountableContent::DT_XCONTENT);
newContent.SetXContentData(pContent);
newContent.SetUsesPackFile(true);
AddContent(newContent);
}
}
#endif // __XENON
#if __PPU
UpdateContentArrayPs3(fiPsnExtraContentDevice::GetGameDataPath());
if(fiDeviceInstaller::GetIsBootedFromHdd())
{
UpdateContentArrayPs3(fiDeviceInstaller::GetHddBootPath());
}
#endif // __PPU
#if RSG_ORBIS
SceNpServiceLabel serviceLabel = 0;
SceAppContentAddcontInfo packages[SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM];
u32 packageCount = 0;
s32 errValue = 0;
memset(packages, 0, sizeof(SceAppContentAddcontInfo) * SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM);
Assertf(packageCount < SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM, "UpdateContentArray - At the limit of DLC packs! [%u / %u]", packageCount, SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM);
errValue = sceAppContentGetAddcontInfoList(serviceLabel, packages, SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM, &packageCount);
if (Verifyf(errValue == SCE_OK, "UpdateContentArray - Failed to get package count! %i", errValue) && packageCount > 0)
{
for (u32 i = 0; i < packageCount; i++)
{
SceNpUnifiedEntitlementLabel& entitlementLabel = packages[i].entitlementLabel;
atHashString ps4EntitlementHash = atHashString(entitlementLabel.data);
// SDK States this must be filled with zeros, now you would have thought Sony would follow their own rules but no.
memset(entitlementLabel.padding, 0, sizeof(entitlementLabel.padding));
// Only bother with packages that are actually installed
if (packages[i].status == SCE_APP_CONTENT_ADDCONT_DOWNLOAD_STATUS_INSTALLED &&
!IsContentFilenamePresent(ps4EntitlementHash) && m_ps4DupeEntitlements.Find(ps4EntitlementHash) == -1)
{
SceAppContentMountPoint mountPoint;
errValue = sceAppContentAddcontMount(serviceLabel, &entitlementLabel, &mountPoint);
if (Verifyf(errValue == SCE_OK, "UpdateContentArray - Failed to mount package %s! %i", entitlementLabel.data, errValue))
{
CMountableContent mount;
char filePath[SCE_KERNEL_PATH_MAX] = { 0 };
formatf(filePath, "%s/dlc.rpf", mountPoint.data);
mount.SetPrimaryDeviceType(CMountableContent::DT_PACKFILE);
mount.SetFilename(filePath);
mount.SetPS4MountPoint(mountPoint);
mount.SetPS4EntitlementLabel(entitlementLabel);
AddContent(mount);
}
}
}
}
#endif
#if RSG_PC
EnumerateContentPC();
#endif // RSG_PC
}
u32 CExtraContentManager::GetContentHash(u32 index) const
{
if (const CMountableContent* content = GetContent(index))
return content->GetNameHash();
return 0;
}
void CExtraContentManager::GetMapChangeArray(atArray<u32>& retArray) const
{
for (u32 i = 0; i < m_content.GetCount(); i++)
{
m_content[i].GetMapChangeHashes(retArray);
}
}
u32 CExtraContentManager::GetMapChangesCRC() const
{
u32 retVal = 0;
atArray<u32> activeMapChangeHashes;
GetMapChangeArray(activeMapChangeHashes);
std::sort(activeMapChangeHashes.begin(), activeMapChangeHashes.end());
for (u32 i = 0; i < activeMapChangeHashes.GetCount(); i++)
{
u32 currHashName = activeMapChangeHashes[i];
retVal = atDataHash((const unsigned int*)&currHashName, (s32)sizeof(currHashName), retVal);
}
dlcDebugf3("CExtraContentManager::GetMapChangesCRC - retVal = %u", retVal);
return retVal;
}
#if RSG_PC
void CExtraContentManager::EnumerateContentPC()
{
const fiDevice *pDevice = fiDevice::GetDevice(DLC_PACKS_PATH, true);
if(!Verifyf(pDevice, "Couldn't get device for %s", DLC_PACKS_PATH))
return;
fiFindData find;
fiHandle handle = pDevice->FindFileBegin(DLC_PACKS_PATH, find);
if ( fiIsValidHandle( handle ) )
{
do
{
atString filePath;
if((find.m_Attributes & FILE_ATTRIBUTE_DIRECTORY) &&
(find.m_Name[0] != '.'))
{
filePath = DLC_PACKS_PATH;
filePath += find.m_Name;
filePath += "/";
AddContentFolder(filePath.c_str());
}
}
while (pDevice->FindFileNext( handle, find ));
pDevice->FindFileEnd(handle);
}
}
#endif // RSG_PC
u32 CExtraContentManager::GetCRC(u32 initValue /*= 0*/) const
{
dlcDebugf3("CExtraContentManager::GetCRC - initValue = %u", initValue);
if(!PARAM_netSessionIgnoreECHash.Get())
{
for (u32 i = 0; i < m_content.GetCount(); i++)
{
if (m_content[i].GetIsCompatibilityPack())
{
u32 currHashName = m_content[i].GetNameHash();
initValue = atDataHash((const unsigned int*)&currHashName, (s32)sizeof(currHashName), initValue);
dlcDebugf3("CExtraContentManager::GetCRC - %s changed hash, initValue = %u", m_content[i].GetName(), initValue);
}
}
u32 weaponPatchHash = CWeaponInfoManager::CalculateWeaponPatchCRC(0);
if(weaponPatchHash)
{
initValue = atDataHash((const unsigned int*)&weaponPatchHash, (s32)sizeof(weaponPatchHash), initValue);
dlcDebugf3("CExtraContentManager::GetCRC - weaponPatchHash changed hash, initValue = %u", initValue);
}
}
dlcDebugf3("CExtraContentManager::GetCRC - return initValue = %u", initValue);
return initValue;
}
void CExtraContentManager::ExecuteScriptPatch()
{
#if EC_CLOUD_MANIFEST
u32 contentNameHash = atStringHash(m_cloudData.m_CloudManifest.m_ScriptPatchName);
if(!contentNameHash)
return;
if(CMountableContent *content = GetContentByHash(contentNameHash))
{
ExecuteContentChangeSetGroup(content->GetNameHash(),(u32)CCS_GROUP_ON_DEMAND);
}
else
{
Errorf("Cloud weapon patch content '%s' hasn't been mounted!", m_cloudData.m_CloudManifest.m_ScriptPatchName.c_str());
}
#endif // EC_DO_MANIFEST_CHECKS
}
#if EC_CLOUD_MANIFEST
void CExtraContentManager::ExecuteWeaponPatchMP(bool execute)
{
u32 contentNameHash = atStringHash(m_cloudData.m_CloudManifest.m_WeaponPatchNameMP);
if(!contentNameHash)
return;
if(GetContentByHash(contentNameHash))
{
if(execute)
{
ExecuteContentChangeSet(contentNameHash,(u32)CCS_GROUP_ON_DEMAND, (u32)CCS_TITLE_UPDATE_WEAPON_PATCH);
}
else
{
RevertContentChangeSet(contentNameHash,(u32)CCS_GROUP_ON_DEMAND, (u32)CCS_TITLE_UPDATE_WEAPON_PATCH);
}
}
else
{
Errorf("Cloud weapon patch content '%s' hasn't been mounted!", m_cloudData.m_CloudManifest.m_WeaponPatchNameMP.c_str());
}
#else
void CExtraContentManager::ExecuteWeaponPatchMP(bool /*execute*/)
{
return;
#endif // EC_DO_MANIFEST_CHECKS
}
bool CExtraContentManager::CheckCompatPackConfiguration() const
{
#if EC_CLOUD_MANIFEST
#if __BANK
if(EXTRACONTENT.BANK_EnableCloudWidgetsOverride())
{
return EXTRACONTENT.BANK_GetForceCompatState();
}
#endif
#if !__FINAL
if(PARAM_disablecompatpackcheck.Get())
return true;
#endif // !__FINAL
bool retVal = true;
for (u32 i = 0; i < m_cloudData.m_CloudManifest.m_CompatibilityPacks.GetCount(); i++)
{
if(!GetContentByHash(atStringHash(m_cloudData.m_CloudManifest.m_CompatibilityPacks[i].m_Name)))
{
retVal = false;
Assertf(0,"Missing compatibility pack %s",m_cloudData.m_CloudManifest.m_CompatibilityPacks[i].m_Name.c_str());
Displayf("Missing compatibility pack %s",m_cloudData.m_CloudManifest.m_CompatibilityPacks[i].m_Name.c_str());
}
}
return retVal;
#else
return true;
#endif // EC_DO_MANIFEST_CHECKS
}
bool CExtraContentManager::VerifySaveGameInstalledPackages() const
{
for (u32 i = 0; i < m_savegameInstalledPackages.GetCount(); i++)
{
if(!GetContentByHash(m_savegameInstalledPackages[i]))
return false;
}
return true;
}
void CExtraContentManager::ResetSaveGameInstalledPackagesInfo()
{
m_savegameInstalledPackages.ResetCount();
}
void CExtraContentManager::SetSaveGameInstalledPackage(u32 nameHash)
{
m_savegameInstalledPackages.PushAndGrow(nameHash);
}
bool CExtraContentManager::AddContentFolder(const char *path)
{
char filePathPreFixed[RAGE_MAX_PATH] = { 0 };
formatf(filePathPreFixed,"%sdlc.rpf",path);
if(Verifyf(ASSET.Exists(filePathPreFixed,""),"DLC pack at path %s not built correctly, dlc.rpf must be inside the package", path))
{
if(!IsContentFilenamePresent(filePathPreFixed))
{
CMountableContent mount;
mount.SetFilename(filePathPreFixed);
mount.SetUsesPackFile(false);
mount.SetPrimaryDeviceType(CMountableContent::DT_PACKFILE);
mount.SetNameHash(mount.GetFilename());
mount.SetIsShippedContent(true);
AddContent(mount);
}
return true;
}
else
{
Errorf("DLC pack not built correctly, DLC.rpf must be inside the package, path: %s",path);
}
return false;
}
int CExtraContentManager::GetMissingCompatibilityPacks(atArray<u32> &packNameHashes) const
{
#if EC_CLOUD_MANIFEST
for(int i = 0; i < m_cloudData.m_CloudManifest.m_CompatibilityPacks.GetCount(); i++)
{
u32 nameHash = atStringHash(m_cloudData.m_CloudManifest.m_CompatibilityPacks[i].m_Name.c_str());
const CMountableContent* content = GetContentByHash(nameHash);
if(!content)
{
packNameHashes.PushAndGrow(nameHash);
}
}
#endif // EC_DO_MANIFEST_CHECKS
return packNameHashes.GetCount();
}
//
// name: CExtraContentManager::AddContent
// description: Add new content
s32 CExtraContentManager::AddContent(CMountableContent& mountableData)
{
dlcDebugf3("CExtraContentManager::AddContent - Adding DLC content [%i] %s", m_content.GetCount(), mountableData.GetFilename());
mountableData.SetECPackFileIndex(m_content.GetCount() + 1);
m_content.PushAndGrow(mountableData, 1);
CPlayerSpecialAbilityManager::UpdateDlcMultipliers();
return m_content.GetCount() - 1;
}
// name: CExtraContentManager::OnPresenceEvent
// description: Deal with profiles signing in and out
void CExtraContentManager::OnPresenceEvent(const rlPresenceEvent* evt)
{
if (PRESENCE_EVENT_SIGNIN_STATUS_CHANGED == evt->GetId())
{
const rlGamerInfo* activeGamer = NetworkInterface::GetActiveGamerInfo();
const rlPresenceEventSigninStatusChanged* s = evt->m_SigninStatusChanged;
if(s->SignedIn() || s->SignedOnline()) // If signed in, init extra content (Signed online is sent instead of signed in, if we sign in and online together)
{
if(activeGamer && activeGamer->GetGamerId() == s->m_GamerInfo.GetGamerId() && activeGamer->GetGamerId() != EXTRACONTENT.m_currentGamerId)
EXTRACONTENT.InitProfile();
}
else if(s->SignedOut()) // If signing out shutdown extra content
{
if(EXTRACONTENT.m_currentGamerId == s->m_GamerInfo.GetGamerId())
EXTRACONTENT.ShutdownProfile();
}
}
}
void CExtraContentManager::OnServiceEvent(sysServiceEvent* evt)
{
if(evt != NULL)
{
if(evt->GetType() == sysServiceEvent::ENTITLEMENT_UPDATED)
{
EXTRACONTENT.OnContentDownloadCompleted();
}
}
}
#if !__FINAL
bool CExtraContentManager::IsContentIgnored(u32 nameHash)
{
const char* packsToIgnore;
if(PARAM_ignorePacks.Get(packsToIgnore))
{
char buffer[RAGE_MAX_PATH] = {0};
strcpy(buffer,packsToIgnore);
char* cur = strtok(buffer,";");
while(cur)
{
atHashString contentHash(cur);
if (contentHash == nameHash)
return true;
cur = strtok(NULL,";");
}
}
return false;
}
#endif
void CExtraContentManager::LoadContent(bool executeChangeSet/* = false*/, bool executeEarlyStartup /*=false*/)
{
sysTimer timeToAccessDLC;
static bool bFirstCall = true;
bool bSlowAccessWarningHasAlreadyBeenShown = false;
timeToAccessDLC.Reset();
#if RSG_XENON
s32 packsToLoad = 0;
for (u32 i = 0; i < m_content.GetCount(); i++)
{
if (CMountableContent* content = GetContent(i))
{
if (content->GetStatus() != CMountableContent::CS_FULLY_MOUNTED)
packsToLoad++;
}
}
if (packsToLoad >= LOAD_SCR_PACK_THRESHOLD)
TriggerLoadingScreen();
#endif
#if RSG_ORBIS
m_ps4DupeEntitlements.ResetCount();
#endif
dlcDebugf1("CExtraContentManager::LoadContent - Package count = %i executeChangeSet = %i", m_content.GetCount(), executeChangeSet);
// CELL_SYSMODULE_SYSUTIL_NP2 module has already been loaded in rage/base/src/system/main.h and
// sceNp2Init has already been called in rage/base/src/rline/rlnp.cpp
// so I don't need to do either of those before loading the encrypted EDATA file(s)
for (u32 i = 0; i < m_content.GetCount(); i++)
{
timeToAccessDLC.Reset();
if (CMountableContent* content = GetContent(i))
{
bool setup = content->Setup(m_localGamerIndex);
bool isDupe = setup ? IsContentDuplicate(content) : false;
NOTFINAL_ONLY(bool isIgnored = setup ? IsContentIgnored(content->GetNameHash()) : false;)
if (!setup || isDupe NOTFINAL_ONLY(|| isIgnored))
{
#if RSG_ORBIS
if (isDupe)
{
atHashString ps4EntitlementHash = atHashString(content->GetPS4EntitlementLabel().data);
if (m_ps4DupeEntitlements.Find(ps4EntitlementHash) == -1)
m_ps4DupeEntitlements.PushAndGrow(ps4EntitlementHash, 1);
}
#endif
if (content->GetStatus() == CMountableContent::CS_SETUP_MISSING)
Warningf("CMountableContent::Setup - DLC Pack is missing the proper setup data! [%s]", content->GetFilename());
else
Assertf(content->GetStatus() == CMountableContent::CS_SETUP_READ, "CMountableContent::Setup - There was an error loading the setup file! [%s] [%i]", content->GetFilename(), content->GetStatus());
if(content->GetStatus() == CMountableContent::CS_CORRUPTED)
{
m_corruptedContent.PushAndGrow(atString(content->GetFilename()));
}
m_content[i].ShutdownContent();
m_content.Delete(i);
i--;
continue;
}
}
if ((!bFirstCall && !bSlowAccessWarningHasAlreadyBeenShown && (timeToAccessDLC.GetTime() > DLC_PACK_MOUNT_TIMEOUT)))
{
#if __XENON
CLoadingScreens::Suspend();
CWarningScreen::SetAndWaitOnMessageScreen(WARNING_MESSAGE_STANDARD, "DLC_ACCESS_SLOW", FE_WARNING_OK); // Will wait in here until the player presses the Accept button
CWarningScreen::Update(); // one last update as we shutdown to switch off warningscreens that have been set
CLoadingScreens::Resume();
CGtaOldLoadingScreen::SetLoadingScreenIfRequired();
bSlowAccessWarningHasAlreadyBeenShown = true;
#endif //__XENON
}
}
std::sort(m_content.begin(), m_content.end());
#if !__FINAL
for(int i = 0; i < m_content.GetCount() - 1; i++)
{
if(m_content[i].GetIsCompatibilityPack() && m_content[i + 1].GetIsCompatibilityPack())
{
ASSERT_ONLY(bool dupe = m_content[i].GetOrder() == m_content[i + 1].GetOrder();)
Assertf(!dupe, "Compatibility pack: '%s' and compatibility pack: '%s' both have the same order: %d",
m_content[i].GetName(), m_content[i + 1].GetName(), m_content[i].GetOrder());
}
}
#endif
u32 compatIndex = 0;
for (u32 i = 0; i < m_content.GetCount(); i++)
{
if (CMountableContent* content = GetContent(i))
{
if(content->GetIsCompatibilityPack())
{
if (content->GetOrder() != (s32)compatIndex)
{
if(!PARAM_disablepackordercheck.Get())
{
m_everHadBadPackOrder = true;
}
Assertf(false, "CExtraContentManager::LoadContent compatibility pack configuration is not complete compatibility pack %s with index %d has order %d",
content->GetName(), compatIndex, content->GetOrder());
}
compatIndex++;
}
// Don't bother mounting packs that are setup only
if (!content->IsSetupOnly())
{
if(executeEarlyStartup)
{
if(content->IsLevelPack())
{
content->SetPermanent(true);
content->Mount(m_localGamerIndex);
if(executeChangeSet)
ExecuteContentChangeSetGroup(content->GetNameHash(),CCS_GROUP_EARLY_ON);
}
}
else
{
content->Mount(m_localGamerIndex);
if(executeChangeSet)
{
ExecuteContentChangeSetGroup(content->GetNameHash(),CCS_GROUP_STARTUP);
}
}
}
}
}
if(executeChangeSet)
ExecutePendingOverlays();
CPlayerSpecialAbilityManager::UpdateDlcMultipliers();
if(!bFirstCall)
{
CNetworkAssetVerifier& verifier = CNetwork::GetAssetVerifier();
verifier.RefreshFileCRC();
}
if(GetCorruptedContentCount() > 0)
{
CWarningScreen::SetAndWaitOnMessageScreen(WARNING_MESSAGE_STANDARD, "DLC_CORRUPT_ERR", FE_WARNING_OK); // Will wait in here until the player presses the Accept button
m_corruptedContent.Reset();
}
#if __BANK
Bank_UpdateContentDisplay();
#endif
#if !__NO_OUTPUT
PrintState();
#endif // !__NO_OUTPUT
bFirstCall = false;
}
#if !__NO_OUTPUT
void CExtraContentManager::PrintState() const
{
Displayf("ExtraContent state:");
Displayf("Mounted %d DLC packs:", m_content.GetCount());
const char *fmt = " %-16s %-56s %-22s %-12s %-22s %-10s";
Displayf(fmt, "Name", "Filename", "Device", "DeviceType", "TimeStamp", "Status");
for (u32 i = 0; i < m_content.GetCount(); i++)
{
if (const CMountableContent* content = GetContent(i))
{
Displayf(fmt,
content->GetName(),
content->GetFilename(),
content->GetDeviceName(),
GetDeviceTypeString(content->GetPrimaryDeviceType()),
content->GetTimeStamp(),
CMountableContent::GetContentStatusName((content->GetStatus())));
}
}
Displayf("DLC CRC:0x%X, DLC Map CRC:0x%X", GetCRC(0), GetMapChangesCRC());
}
#endif // !__NO_OUTPUT
bool CExtraContentManager::IsContentDuplicate(const CMountableContent *content) const
{
for (u32 i = 0; i < m_content.GetCount(); i++)
{
if (const CMountableContent* c = GetContent(i))
{
if(c == content)
continue;
if(strnicmp(c->GetDeviceName(), content->GetDeviceName(), RAGE_MAX_PATH) == 0)
{
// Let dev extra content take priority over deployed content
if (!content->GetIsShippedContent() && c->GetIsShippedContent())
return false;
Warningf("DLC with the same device name '%s' has been already mounted%s! This pack %s (%s) will be ignored.",
content->GetDeviceName(),
c->GetIsShippedContent()?" as a shipped DLC pack, you seem to have both shipped and installed packs, try removing the installed packs":"",
content->GetFilename(),
content->GetName());
return true;
}
}
}
return false;
}
//
// name: CExtraContentManager::InsertContentLock
// description: Compatibility packs should add content locks, per DLC item (clothes, weapons etc...) and paid packs will unlock them.
// You must have a valid index returned from AddOnContentItemUnlockedCB to supply as unlockedCBIndex
// usage:
// s32 contentLockCBIndex = AddOnContentItemChangedCB(myCBFunctor(u32));
// InsertContentLock(atStringHash("subsystemContentLock", contentLockCBIndex);
void CExtraContentManager::InsertContentLock(u32 hash, s16 unlockedCBIndex, bool locked)
{
if (hash != 0)
{
SContentLockData* lockData = m_contentLocks.SafeGet(hash);
if (!lockData)
{
SContentLockData newLockData;
newLockData.m_locked = locked;
newLockData.addCBIndex(unlockedCBIndex);
m_contentLocks.Insert(hash, newLockData);
m_contentLocks.FinishInsertion();
#if !__FINAL
OUTPUT_ONLY(atNonFinalHashString hashStr = hash;)
dlcDebugf3("InsertContentLockData :: Inserting new content lock! %s, %i, %i", hashStr.GetCStr(), newLockData.m_callBackIndices, locked);
#endif
}
else
{
OUTPUT_ONLY(s32 oldValue = lockData->m_callBackIndices;)
lockData->addCBIndex(unlockedCBIndex);
dlcDebugf3("InsertContentLockData :: Updating callback index! Old: %i - New: %i", oldValue, lockData->m_callBackIndices);
}
#if __BANK
DisplayContentLocks(m_currentContentLocksDebugPage, 0); // refresh bank display
#endif
}
}
void CExtraContentManager::RemoveContentLock(u32 hash)
{
if (SContentLockData* lockData = m_contentLocks.SafeGet(hash))
{
#if !__FINAL
OUTPUT_ONLY(atNonFinalHashString hashStr = hash;)
dlcDebugf3("RemoveContentLock :: Removing content lock! %s, %i, %i", hashStr.GetCStr(), lockData->m_callBackIndices, lockData->m_locked);
#endif
s32 index = m_contentLocks.GetIndexFromDataPtr(lockData);
m_contentLocks.Remove(index);
}
}
//
// name: CExtraContentManager::AddOnContentItemChangedCB
// description: Subsystems must register an unlocked callback before registering any content locks into the map. We return the index of the callback
// so we can attach this to a specific content lock, this means we can call only the relevant subsystem when a content lock state changes.
// usage:
// s32 contentLockCBIndex = AddOnContentItemChangedCB(myDelegate(u32, bool));
// InsertContentLock(atStringHash("subsystemContentLock", contentLockCBIndex);
s16 CExtraContentManager::AddOnContentItemChangedCB(atDelegate<void (u32, bool)> onChangedCB)
{
if (m_onContentLockStateChangedCBs.GetCount() < SContentLockData::MAX_CONTENT_LOCK_SUBSYSTEM_CBS)
{
m_onContentLockStateChangedCBs.Push(onChangedCB);
dlcDebugf3("AddOnContentItemUnlockedCB: index = %i", m_onContentLockStateChangedCBs.GetCount() - 1);
return (s16)(m_onContentLockStateChangedCBs.GetCount() - 1);
}
dlcErrorf("AddOnContentItemChangedCB :: Cannot allocate anymore onContentItemUnlockedCB entries!");
return SContentLockData::INVALID_CONTENT_LOCK_CB_INDEX;
}
bool CExtraContentManager::IsContentItemLocked(u32 hash) const
{
if (hash != 0)
{
const SContentLockData* lockData = m_contentLocks.SafeGet(hash);
return (lockData) ? lockData->m_locked : true;
}
return false;
}
//
// name: CExtraContentManager::ModifyContentLockState
// description: Modifies the state of a content lock and invalidates the subsystem tied to that lock to handle it's new state.
// This function will insert new content locks into the map with the given state if one does not already exist.
void CExtraContentManager::ModifyContentLockState(u32 hash, bool locked)
{
if (hash != 0)
{
SContentLockData* lockData = m_contentLocks.SafeGet(hash);
if (lockData)
{
lockData->m_locked = locked;
dlcDebugf3("ModifyContentLockState :: Items [%u] state has changed invoking call backs...", hash);
for (int i = 0; i < m_onContentLockStateChangedCBs.GetCount(); i++)
{
// Call back to any subsystem this content is linked with as the state has changed...
if (lockData->m_callBackIndices & (1 << i))
m_onContentLockStateChangedCBs[i].Invoke(hash, lockData->m_locked);
}
}
else
{
dlcDebugf3("ModifyContentLockState :: Inserting new content lock!");
InsertContentLock(hash, SContentLockData::INVALID_CONTENT_LOCK_CB_INDEX, locked);
}
}
#if __BANK
DisplayContentLocks(m_currentContentLocksDebugPage, 0); // refresh bank display
#endif
}
void CExtraContentManager::ExecuteContentChangeSetGroupForAll(u32 changeSetGroup)
{
for (u32 i = 0; i < m_content.GetCount(); i++)
{
ExecuteContentChangeSetGroup(m_content[i].GetNameHash(),changeSetGroup);
}
}
void CExtraContentManager::RevertContentChangeSetGroupForAll(u32 changeSetGroup)
{
for (u32 i = 0; i < m_content.GetCount(); i++)
{
RevertContentChangeSetGroup(m_content[i].GetNameHash(),changeSetGroup);
}
}
void CExtraContentManager::ExecuteContentChangeSet(u32 contentHash, u32 changeSetGroup, u32 changeSetName)
{
CMountableContent* content = EXTRACONTENT.GetContentByHash(contentHash);
if (Verifyf(content, "ExecuteContentChangeSet - Invalid contentHash %u", contentHash))
{
ExecuteContentChangeSetInternal(content,changeSetGroup,changeSetName, ECCS_FLAG_USE_LATEST_VERSION | ECCS_FLAG_USE_LOADING_SCREEN | ECCS_FLAG_MAP_CLEANUP);
}
}
void CExtraContentManager::RevertContentChangeSet(u32 contentHash, u32 changeSetGroup, u32 changeSetName)
{
CMountableContent* content = EXTRACONTENT.GetContentByHash(contentHash);
if (Verifyf(content, "ExecuteContentChangeSet - Invalid contentHash %u", contentHash))
{
RevertContentChangeSetInternal(content,changeSetGroup,changeSetName, CDataFileMgr::ChangeSetAction::CCS_ALL_ACTIONS, ECCS_FLAG_USE_LATEST_VERSION | ECCS_FLAG_USE_LOADING_SCREEN);
}
}
void CExtraContentManager::ExecuteContentChangeSetGroup(u32 contentHash, u32 changeSetGroup)
{
CMountableContent* content = EXTRACONTENT.GetContentByHash(contentHash);
if (Verifyf(content, "ExecuteContentChangeSetGroup - Invalid contentHash %u", contentHash))
{
ExecuteContentChangeSetGroupInternal(content,changeSetGroup);
}
}
void CExtraContentManager::RevertContentChangeSetGroup(u32 contentHash, u32 changeSetGroup)
{
CMountableContent* content = EXTRACONTENT.GetContentByHash(contentHash);
if (Verifyf(content, "RevertContentChangeSetGroup - Invalid contentHash %u", contentHash))
{
RevertContentChangeSetGroupInternal(content,changeSetGroup, CDataFileMgr::ChangeSetAction::CCS_ALL_ACTIONS);
}
}
//
// name: CExtraContentManager::GetContentIndex
// description: Get extra content index from its hash. Returns NULL if it doesn't exist
CMountableContent* CExtraContentManager::GetContentByHash(u32 nameHash)
{
return const_cast<CMountableContent*>(GetContentByHashImpl(nameHash));
}
CMountableContent* CExtraContentManager::GetContentByDevice(const char* device)
{
const char* isCrc = stristr(device,"CRC:");
atHashString deviceHash = atHashString::Null();
if(isCrc)
{
char tempDevice[RAGE_MAX_PATH] = {0};
char realDevice[RAGE_MAX_PATH] = {0};
strncpy(tempDevice,device,isCrc - device);
formatf(realDevice,RAGE_MAX_PATH,"%s:/",tempDevice);
Displayf("Real device: %s",realDevice);
deviceHash = atHashString(realDevice);
}
else
{
deviceHash = atHashString(device);
}
for(int i=0;i<m_content.GetCount();i++)
{
dlcDebugf3("content: %s device: %s", m_content[i].GetDeviceName(),deviceHash.TryGetCStr());
if(atStringHash(m_content[i].GetDeviceName())==deviceHash)
{
return &m_content[i];
}
}
return NULL;
}
const CMountableContent* CExtraContentManager::GetContentByHash(u32 nameHash) const
{
return GetContentByHashImpl(nameHash);
}
const CMountableContent* CExtraContentManager::GetContentByHashImpl(u32 nameHash) const
{
for(u32 i=0; i<m_content.GetCount(); i++)
{
if(m_content[i].GetNameHash() == nameHash)
return &m_content[i];
}
return NULL;
}
CMountableContent* CExtraContentManager::GetContent(u32 index)
{
if (index < m_content.GetCount())
return &m_content[index];
return NULL;
}
const CMountableContent* CExtraContentManager::GetContent(u32 index) const
{
if (index < m_content.GetCount())
return &m_content[index];
return NULL;
}
fiDeviceRelative gTitleUpdatePlatform, gTitleUpdatePlatform2, gTitleUpdateCommon, gTitleUpdateAudio, gTitleUpdateAudioSFX, gLocalTU;
fiDeviceCrc gTitleUpdateCommonCRC, gTitleUpdatePlatformCRC, gTitleUpdatePlatform2CRC;
fiPackfile gTUPackfile;
fiDeviceRelative gTitleUpdateCommon2;
fiDeviceCrc gTitleUpdateCommonCRC2;
fiPackfile gTUPackfile2;
const char *CExtraContentManager::GetPlatformTitleUpdatePath()
{
static char platformPath[RAGE_MAX_PATH] = { 0 };
if(!platformPath[0])
sprintf(platformPath, "update:/%s", RSG_PLATFORM_ID);
return platformPath;
}
const char *CExtraContentManager::GetPlatformTitleUpdate2Path()
{
static char platformPath[RAGE_MAX_PATH] = { 0 };
if( !platformPath[0] )
sprintf( platformPath, "update2:/%s", RSG_PLATFORM_ID );
return platformPath;
}
const char *CExtraContentManager::GetAudioTitleUpdatePath()
{
static char audioPath[RAGE_MAX_PATH] = { 0 };
if(!audioPath[0])
{
#if RSG_ORBIS
sprintf(audioPath, "update:/%s/audio", RSG_PLATFORM);
#else
sprintf(audioPath, "update:/%s/audio", RSG_PLATFORM_ID);
#endif
}
return audioPath;
}
void CExtraContentManager::MountUpdate()
{
fiDevice *overrideDevice = NULL, *tu2Device = nullptr;
if( ASSET.Exists( TITLE_UPDATE2_RPF_PATH, NULL ) )
{
if( Verifyf( gTUPackfile2.Init( TITLE_UPDATE2_RPF_PATH, true, fiPackfile::CACHE_NONE ), "CExtraContentManager::MountUpdate failed to init rpf : %s", TITLE_UPDATE2_RPF_PATH ) )
{
gTUPackfile2.MountAs( "update2:/" );
tu2Device = &gTUPackfile2;
}
else
{
Errorf( "CExtraContentManager::MountUpdate failed to init rpf : %s", TITLE_UPDATE2_RPF_PATH );
}
}
if(ASSET.Exists(TITLE_UPDATE_RPF_PATH, NULL))
{
if(Verifyf(gTUPackfile.Init(TITLE_UPDATE_RPF_PATH, true, fiPackfile::CACHE_NONE), "CExtraContentManager::MountUpdate failed to init rpf : %s", TITLE_UPDATE_RPF_PATH))
{
gTUPackfile.MountAs("update:/");
overrideDevice = &gTUPackfile;
gLocalTU.Init("update/" RSG_PLATFORM_ID "/dlcpacks/", true);
gLocalTU.MountAs("dlcpacks:/");
}
else
{
Errorf("CExtraContentManager::MountUpdate failed to init rpf : %s", TITLE_UPDATE_RPF_PATH);
}
}
else
{
if(fiDevice::IsAnythingMountedAtMountPoint(TITLE_UPDATE_MOUNT_PATH))
{
gLocalTU.Init("update:/" RSG_PLATFORM_ID "/dlcpacks/", true);
gLocalTU.MountAs("dlcpacks:/");
}
}
if(fiDevice::IsAnythingMountedAtMountPoint(TITLE_UPDATE_MOUNT_PATH))
{
//update2
{
gTitleUpdateCommon2.Init( "update2:/common", false, tu2Device );
gTitleUpdateCommon2.MountAs( "common:/" );
// mount crc device
gTitleUpdateCommonCRC2.Init( "update2:/common", true, tu2Device );
gTitleUpdateCommonCRC2.MountAs( "commoncrc:/" );
}
gTitleUpdateCommon.Init( "update:/common", false, overrideDevice);
gTitleUpdateCommon.MountAs("common:/");
// mount crc device
gTitleUpdateCommonCRC.Init("update:/common", true, overrideDevice);
gTitleUpdateCommonCRC.MountAs("commoncrc:/");
gTitleUpdatePlatform.Init( GetPlatformTitleUpdatePath(), false, overrideDevice);
gTitleUpdatePlatform.MountAs("platform:/");
gTitleUpdatePlatformCRC.Init(GetPlatformTitleUpdatePath(), true, overrideDevice);
gTitleUpdatePlatformCRC.MountAs("platformcrc:/");
gTitleUpdatePlatform2.Init( GetPlatformTitleUpdate2Path(), false, tu2Device );
gTitleUpdatePlatform2.MountAs( "platform:/" );
gTitleUpdatePlatform2CRC.Init( GetPlatformTitleUpdate2Path(), true, overrideDevice );
gTitleUpdatePlatform2CRC.MountAs( "platformcrc:/" );
if(!PARAM_audiofolder.Get())
{
gTitleUpdateAudio.Init( GetAudioTitleUpdatePath(), true, overrideDevice);
gTitleUpdateAudio.MountAs("audio:/");
char audioSfxPath[RAGE_MAX_PATH] = { 0 };
sprintf(audioSfxPath, "%s/%s", GetAudioTitleUpdatePath(), "sfx");
gTitleUpdateAudioSFX.Init(audioSfxPath, true, overrideDevice);
gTitleUpdateAudioSFX.MountAs("audio:/sfx/");
}
}
}
void CExtraContentManager::RemountUpdate()
{
if(fiDevice::IsAnythingMountedAtMountPoint(TITLE_UPDATE_MOUNT_PATH))
{
// Unmount and then mount the folders again, now that we remounted the files on the disk
fiDevice::Unmount(gTitleUpdateCommon);
gTitleUpdateCommon.MountAs("common:/");
fiDevice::Unmount(gTitleUpdateCommonCRC);
gTitleUpdateCommonCRC.MountAs("commoncrc:/");
//update2
{
fiDevice::Unmount( gTitleUpdateCommon2 );
gTitleUpdateCommon2.MountAs( "common:/" );
fiDevice::Unmount( gTitleUpdateCommonCRC2 );
gTitleUpdateCommonCRC2.MountAs( "commoncrc:/" );
}
fiDevice::Unmount(gTitleUpdatePlatform);
gTitleUpdatePlatform.MountAs("platform:/");
fiDevice::Unmount(gTitleUpdatePlatformCRC);
gTitleUpdatePlatformCRC.MountAs("platformcrc:/");
fiDevice::Unmount( gTitleUpdatePlatform2 );
gTitleUpdatePlatform2.MountAs( "platform:/" );
fiDevice::Unmount( gTitleUpdatePlatform2CRC );
gTitleUpdatePlatform2CRC.MountAs( "platformcrc:/" );
if(!PARAM_audiofolder.Get())
{
fiDevice::Unmount(gTitleUpdateAudio);
gTitleUpdateAudio.MountAs("audio:/");
fiDevice::Unmount(gTitleUpdateAudioSFX);
gTitleUpdateAudioSFX.MountAs("audio:/sfx/");
}
}
}
void CExtraContentManager::ExecuteTitleUpdateDataPatch(u32 changeSetHash, bool execute)
{
if(CMountableContent *content = GetContentByHash(atStringHash(TITLE_UPDATE_PACK_NAME)))
{
BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = true;)
if(execute)
{
ExecuteContentChangeSet(content->GetNameHash(),(u32)CCS_GROUP_ON_DEMAND, changeSetHash);
}
else
{
RevertContentChangeSet(content->GetNameHash(),(u32)CCS_GROUP_ON_DEMAND, changeSetHash);
}
BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = false;)
}
}
void CExtraContentManager::ExecuteTitleUpdateDataPatchGroup(u32 changeSetHash, bool execute)
{
for(int i=0;i<m_content.GetCount();++i)
{
CMountableContent& content = m_content[i];
if(content.GetIsPermanent())
{
BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = true;)
if(execute)
{
ExecuteContentChangeSetGroup(content.GetNameHash(),changeSetHash);
}
else
{
RevertContentChangeSetGroup(content.GetNameHash(),changeSetHash);
}
BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = false;)
}
}
}
const char * GetDeviceTypeString (CMountableContent::eDeviceType deviceType)
{
switch(deviceType)
{
case CMountableContent::DT_INVALID:
return "DT_INVALID";
case CMountableContent::DT_FOLDER:
return "DT_FOLDER";
case CMountableContent::DT_PACKFILE:
return "DT_PACKFILE";
case CMountableContent::DT_XCONTENT:
return "DT_XCONTENT";
case CMountableContent::DT_DLC_DISC:
return "DT_DLC_DISC";
default:
return "INVALID";
}
}
bool CExtraContentManager::GetPartOfCompatPack(const char* file)
{
CMountableContent* content = GetContentByDevice(file);
if(content)
return content->GetIsCompatibilityPack()||content->GetIsPermanent();
else
return false;
}
#if __BANK
#define MAX_CONTENT_LOCKS_PER_PAGE 10
static u32 CURR_CL_LIST[MAX_CONTENT_LOCKS_PER_PAGE] = { 0 };
bkBank* CExtraContentManager::ms_pBank = NULL;
bkCombo* CExtraContentManager::ms_pSelectedChangeSet = NULL;
bkCombo* CExtraContentManager::ms_pChangeSetGroups = NULL;
bkButton *CExtraContentManager::ms_pCreateBankButton = NULL;
bkToggle *CExtraContentManager::ms_pForcedCloudFilesStatus = NULL;
bkToggle *CExtraContentManager::ms_pForceManifestFail = NULL;
bkToggle *CExtraContentManager::ms_pForceCompatPackFail = NULL;
bkToggle *CExtraContentManager::ms_pForceLoadingScreen = NULL;
bkGroup* CExtraContentManager::ms_pBankContentStateGroup = NULL;
bkGroup* CExtraContentManager::ms_pBankContentLockGroup = NULL;
bkGroup* CExtraContentManager::ms_pSpecialTriggersGroup = NULL;
bkGroup* CExtraContentManager::ms_pBankExtraContentGroup = NULL;
bkList* CExtraContentManager::ms_pBankContentTable = NULL;
bkList* CExtraContentManager::ms_pBankVersionedContentTable = NULL;
bkGroup *CExtraContentManager::ms_pBankFilesGroup = NULL;
bkList *CExtraContentManager::ms_pBankInUseFilesTable = NULL;
bkList *CExtraContentManager::ms_pBankUnusedFilesTable = NULL;
bkButton *CExtraContentManager::ms_pBankFileListsPopulateButton = NULL;
bkButton *CExtraContentManager::ms_pBankInUsePrevButton = NULL;
bkButton *CExtraContentManager::ms_pBankInUseNextButton = NULL;
bkButton *CExtraContentManager::ms_pBankUnusedPrevButton = NULL;
bkButton *CExtraContentManager::ms_pBankUnusedNextButton = NULL;
int CExtraContentManager::ms_selectedContentIndex = -1;
int CExtraContentManager::ms_selectedChangeSetIndex = 0;
int CExtraContentManager::ms_changeSetGroupIndex = 0;
int CExtraContentManager::ms_displayedPackageCount = 0;
int CExtraContentManager::ms_displayedVersionedChangesetCount = 0;
bkList* CExtraContentManager::ms_contentLocksList = NULL;
atArray<const char *> CExtraContentManager::m_workingDLC; // for combo box
int CExtraContentManager::m_workingDLCIndex;
s32 CExtraContentManager::m_currentContentLocksDebugPage = 0;
char CExtraContentManager::m_contentLockSearchString[RAGE_MAX_PATH] = { 0 };
char CExtraContentManager::m_contentLockCountString[RAGE_MAX_PATH] = { 0 };
char CExtraContentManager::m_matchMakingCrc[RAGE_MAX_PATH] = { 0 };
char CExtraContentManager::m_activeMapChange[RAGE_MAX_PATH] = { 0 };
char CExtraContentManager::m_currMapChangeStateStr[RAGE_MAX_PATH] = { 0 };
bool CExtraContentManager::ms_bForcedCloudFilesStatus = false;
bool CExtraContentManager::ms_bForcedCompatPackStatus = false;
bool CExtraContentManager::ms_bForcedLoadingScreenStatus = false;
bool CExtraContentManager::ms_bForcedManifestStatus = false;
bool CExtraContentManager::ms_bDoBankUpdate = false;
// PURPOSE: "Normalise" filename by lowercasing, replacing `\' with `/',
// and morphing double slash into single slash.
// PARAMS: filename - Name of file to "normalise" (including device ID)
// RETURNS: atString of "normalised" filename
static atString NormaliseFileName(const char *src) {
char outPath[RAGE_MAX_PATH];
bool bWasSlash = false; // rudimentary double-slash stopper
char *q = outPath;
for (const char *p = src; *p != '\0'; ++p) {
if (*p == '\\') {
if (!bWasSlash) *q++ = '/';
bWasSlash = true;
} else if (*p == '/') {
if (!bWasSlash) *q++ = '/';
bWasSlash = true;
} else {
*q++ = (char)tolower(*p);
bWasSlash = false;
}
}
*q = '\0';
return atString(outPath);
}
bool CExtraContentManager::GetAssetPathFromDevice(char* deviceName)
{
if(CMountableContent* content = GetContentByDevice(deviceName))
{
char filePath[RAGE_MAX_PATH]={0};
strcpy(filePath,content->m_filename);
char* root = stristr(filePath,"build");
if(Verifyf(root,"You are trying to access DLC assets while running with packaged DLC, if you are going to modify DLC assets, please use the DLC command lines instead"))
{
*root = 0;
formatf(deviceName, RAGE_MAX_PATH, "%sassets_ng/",filePath);
return true;
}
}
return false;
}
void CExtraContentManager::ReturnAssetPathForFragTuneCB(char* assetPath, fragType* type)
{
strStreamingInfo* strInfo = NULL;
strLocalIndex strFileIdx = strLocalIndex(-1);
char physicalPath[RAGE_MAX_PATH];
for(int i = 0; i<g_FragmentStore.GetCount(); i++)
{
if(g_FragmentStore.Get(strLocalIndex(i)) == type)
{
strInfo = g_FragmentStore.GetStreamingInfo(strLocalIndex(i));
strFileIdx = strPackfileManager::GetImageFileIndexFromHandle(strInfo->GetHandle());
strStreamingFile* file = strPackfileManager::GetImageFile(strFileIdx.Get());
if(Verifyf(file,"Streaming file could not be found for %s",type->GetBaseName()))
{
if(!strnicmp(file->m_name.TryGetCStr(),"dlc",3))
{
const fiDevice* device = fiDevice::GetDevice(file->m_name.TryGetCStr(),false);
device->FixRelativeName(physicalPath,RAGE_MAX_PATH,file->m_name.TryGetCStr());
char* end = strstr(physicalPath,"/build/dev/");
if(end)
{
*end = '\0';
strcat(physicalPath,"/assets/fragments");
strcpy(assetPath,physicalPath);
}
}
break;
}
else
{
return;
}
}
}
return;
}
void CExtraContentManager::GetWorkingDLCDeviceName(char* deviceName)
{
if(m_workingDLCIndex>=0&&m_workingDLCIndex<m_workingDLC.GetCount())
{
strcpy(deviceName,m_workingDLC[m_workingDLCIndex]);
}
}
void CExtraContentManager::GetAssetPathForWorkingDLC(char* deviceName)
{
if(m_workingDLCIndex==0)
{
strcpy(deviceName,"assets:/");
}
else
{
strcpy(deviceName,m_workingDLC[m_workingDLCIndex]);
GetAssetPathFromDevice(deviceName);
}
}
bkCombo* CExtraContentManager::AddWorkingDLCCombo(bkBank* bank)
{
m_workingDLC.Reset();
m_workingDLC.PushAndGrow("platform:/");
for(int i=0;i<m_content.GetCount();i++)
{
m_workingDLC.PushAndGrow(m_content[i].GetDeviceName());
}
bkCombo* ret = bank->AddCombo("Current working DLC", &m_workingDLCIndex, m_workingDLC.GetCount(),m_workingDLC.GetElements(), datCallback(MFA(CExtraContentManager::CurrentWorkingDLCChanged),this));
return ret;
}
void CExtraContentManager::CurrentWorkingDLCChanged()
{
Displayf("DLC Combo: %d selected : %s", m_workingDLCIndex, m_workingDLC[m_workingDLCIndex]);
}
void CExtraContentManager::Bank_ExecuteBankUpdate()
{
if(ms_bDoBankUpdate && !ms_pCreateBankButton)
{
for (u32 i = 0; i < ms_displayedPackageCount; i++)
ms_pBankContentTable->RemoveItem(i);
for (u32 i = 0; i < ms_displayedVersionedChangesetCount; i++)
ms_pBankVersionedContentTable->RemoveItem(i);
PopulateContentTableWidget();
EXTRAMETADATAMGR.RefreshBankWidgets();
DisplayContentCRC();
Bank_UpdateActiveMapChangeDisplay();
ms_bDoBankUpdate = false;
}
}
void CExtraContentManager::Bank_UpdateContentDisplay()
{
ms_bDoBankUpdate = true;
}
void CExtraContentManager::Bank_ExecuteSelectedChangeSet()
{
if (CMountableContent* content = GetContent(ms_selectedContentIndex))
{
atHashString mapCCS = ms_pSelectedChangeSet->GetString(ms_selectedChangeSetIndex);
atHashString ccsGroup = ms_pChangeSetGroups->GetString(ms_changeSetGroupIndex);
if (mapCCS.IsNotNull() && ccsGroup.IsNotNull())
{
ExecuteContentChangeSet(content->GetNameHash(),ccsGroup,mapCCS);
Bank_UpdateContentDisplay();
ExecutePendingOverlays();
EXTRAMETADATAMGR.RefreshBankWidgets();
}
}
else
{
dlcDebugf2("Invalid DLC index for Bank_ExecuteSelectedChangeSet (%d)",ms_selectedContentIndex);
}
}
void CExtraContentManager::Bank_RevertSelectedChangeSet()
{
if (CMountableContent* content = GetContent(ms_selectedContentIndex))
{
atHashString mapCCS = ms_pSelectedChangeSet->GetString(ms_selectedChangeSetIndex);
atHashString ccsGroup = ms_pChangeSetGroups->GetString(ms_changeSetGroupIndex);
if (mapCCS.IsNotNull() && ccsGroup.IsNotNull())
{
RevertContentChangeSet(content->GetNameHash(),ccsGroup,mapCCS);
Bank_UpdateContentDisplay();
EXTRAMETADATAMGR.RefreshBankWidgets();
}
}
else
{
dlcDebugf2("Invalid DLC index for Bank_RevertSelectedChangeSet (%d)",ms_selectedContentIndex);
}
}
void CExtraContentManager::Bank_ExecuteSelectedGroup()
{
if (CMountableContent* content = GetContent(ms_selectedContentIndex))
{
atHashString ccsGroup = ms_pChangeSetGroups->GetString(ms_changeSetGroupIndex);
if (ccsGroup.IsNotNull())
{
ExecuteContentChangeSetGroup(content->GetNameHash(),ccsGroup);
ExecutePendingOverlays();
Bank_UpdateContentDisplay();
EXTRAMETADATAMGR.RefreshBankWidgets();
}
}
else
{
dlcDebugf2("Invalid DLC index for Bank_ExecuteSelectedGroup (%d)",ms_selectedContentIndex);
}
}
void CExtraContentManager::Bank_RevertSelectedGroup()
{
if (CMountableContent* content = GetContent(ms_selectedContentIndex))
{
atHashString ccsGroup = ms_pChangeSetGroups->GetString(ms_changeSetGroupIndex);
if (ccsGroup.IsNotNull())
{
RevertContentChangeSetGroup(content->GetNameHash(),ccsGroup);
Bank_UpdateContentDisplay();
EXTRAMETADATAMGR.RefreshBankWidgets();
}
}
else
{
dlcDebugf2("Invalid DLC index for Bank_RevertSelectedGroup (%d)",ms_selectedContentIndex);
}
}
void CExtraContentManager::Bank_ChangeSetGroupChanged()
{
if (CMountableContent* content = GetContent(ms_selectedContentIndex))
{
if (content->HasAnyChangesets())
{
atHashString selectedGroup = ms_pChangeSetGroups->GetString(ms_changeSetGroupIndex);
atArray<const char*> changeSets;
content->GetChangeSetsForGroup(selectedGroup, changeSets);
ms_selectedChangeSetIndex = 0;
ms_pSelectedChangeSet->UpdateCombo("Map Changes:", &ms_selectedChangeSetIndex, changeSets.GetCount(), &changeSets[0]);
}
}
}
void CExtraContentManager::Bank_PrintActiveMapChanges()
{
Displayf("[DLC-AMC] - BEGIN ACTIVE MAP CHANGES");
{
atArray<u32> m_activeMapChangeHashes;
for (u32 i = 0; i < m_content.GetCount(); i++)
m_content[i].GetMapChangeHashes(m_activeMapChangeHashes);
std::sort(m_activeMapChangeHashes.begin(), m_activeMapChangeHashes.end());
for (u32 i = 0; i < m_activeMapChangeHashes.GetCount(); i++)
{
atHashString currHashName = m_activeMapChangeHashes[i];
Displayf("[DLC-AMC] - %s", currHashName.GetCStr());
}
}
Displayf("[DLC-AMC] - END ACTIVE MAP CHANGES");
}
void CExtraContentManager::Bank_UpdateActiveMapChangeDisplay()
{
atArray<u32> mapChangeHashes;
for (s32 i = 0; i < m_content.GetCount() ;i++)
m_content[i].GetMapChangeHashes(mapChangeHashes);
formatf(m_activeMapChange, "%i active map changes", mapChangeHashes.GetCount());
}
void CExtraContentManager::Bank_ApplyShouldActivateContentChangeState()
{
int count = m_content.GetCount();
for (int i = 0; i < count ;i++)
{
if (m_content[i].GetBankShouldExecute())
ExecuteContentChangeSetGroup(m_content[i].GetNameHash(),CCS_GROUP_STARTUP);
else
RevertContentChangeSetGroup(m_content[i].GetNameHash(),CCS_GROUP_STARTUP);
}
}
void CExtraContentManager::Bank_ContentListClick(s32 index)
{
dlcDebugf3("Index %d selected in content list",index);
ms_selectedContentIndex = index;
if (CMountableContent* content = GetContent(index))
{
if (content->HasAnyChangesets())
{
atArray<const char*> groupNames;
content->GetChangesetGroups(groupNames);
ms_changeSetGroupIndex = 0;
ms_pChangeSetGroups->UpdateCombo("ChangeSet Groups:", &ms_changeSetGroupIndex, groupNames.GetCount(), &groupNames[0], datCallback(MFA(CExtraContentManager::Bank_ChangeSetGroupChanged),&EXTRACONTENT));
Bank_ChangeSetGroupChanged();
}
}
}
void CExtraContentManager::Bank_ContentListDoubleClick(s32 index)
{
if (CMountableContent* content = GetContent(index))
{
content->SetBankShouldExecute(!content->GetBankShouldExecute());
dlcDebugf3("Index %d in content list should activate set to %d", index, content->GetBankShouldExecute());
Bank_UpdateContentDisplay();
}
}
bool CExtraContentManager::BANK_EnableCloudWidgetsOverride()
{
return PARAM_enableCloudTestWidgets.Get();
}
void CExtraContentManager::InitBank()
{
//Create the cloud bank
// Create the weapons bank
ms_pBank = &BANKMGR.CreateBank("ExtraContent", 0, 0, false);
if(dlcVerifyf(ms_pBank, "Failed to create Extra content bank"))
{
if(BANK_EnableCloudWidgetsOverride())
{
ms_bForcedCompatPackStatus=false;
ms_bForcedCloudFilesStatus = false;
ms_bForcedLoadingScreenStatus = true;
ms_bForcedManifestStatus = true;
ms_pForceManifestFail = ms_pBank->AddToggle("Manifest Successfully Loaded?", &ms_bForcedManifestStatus);
ms_pForceLoadingScreen = ms_pBank->AddToggle("Loading screens active?", &ms_bForcedLoadingScreenStatus);
ms_pForcedCloudFilesStatus = ms_pBank->AddToggle("Cloud Files Loaded?", &ms_bForcedCloudFilesStatus);
ms_pForceCompatPackFail = ms_pBank->AddToggle("Compatibility Pack Configuration correct?", &ms_bForcedCompatPackStatus);
}
ms_pCreateBankButton = ms_pBank->AddButton("Create Extra Content widgets", &CExtraContentManager::CreateBankWidgets);
}
}
void CExtraContentManager::CreateBankWidgets()
{
Assertf(ms_pBank, "Extra content bank needs to be created first");
if(ms_pCreateBankButton) //delete the create bank button
{
ms_pCreateBankButton->Destroy();
ms_pCreateBankButton = NULL;
}
else
{
//bank must already be setup as the create button doesn't exist so just return.
return;
}
EXTRAMETADATAMGR.CreateBankWidgets(*ms_pBank);
ms_pBank->PushGroup("Extra Content", false);
{
EXTRACONTENT.CreateSpecialTriggersWidgets(ms_pBank);
EXTRACONTENT.CreateContentLocksWidgets(ms_pBank);
EXTRACONTENT.CreateContentStatusWidgets(ms_pBank);
EXTRACONTENT.CreateUnusedFilesWidgets(ms_pBank);
}
ms_pBank->PopGroup();
g_LayoutManager.InitWidgets();
}
void CExtraContentManager::ShowPreviousContentLockPage()
{
EXTRACONTENT.DisplayContentLocks(m_currentContentLocksDebugPage - 1);
}
void CExtraContentManager::ShowNextContentLockPage()
{
EXTRACONTENT.DisplayContentLocks(m_currentContentLocksDebugPage + 1);
}
void ConcatCBIndices(SContentLockData* currentItem, char* strOutput)
{
char strCatTemp[16] = { 0 };
for (int i = 0; i < SContentLockData::MAX_CONTENT_LOCK_SUBSYSTEM_CBS; i++)
{
if (currentItem->m_callBackIndices & (1 << i))
{
formatf(strCatTemp, sizeof(strCatTemp), "%i,", i);
strcat(strOutput, strCatTemp);
}
}
}
void CExtraContentManager::AddContentLockToBankList(u32 hash, SContentLockData* currentItem, s32 index)
{
if (currentItem)
{
char strCBIndices[RAGE_MAX_PATH] = { 0 };
atNonFinalHashString hashStr = hash;
ms_contentLocksList->AddItem(hash, 0, hashStr.GetCStr());
ms_contentLocksList->AddItem(hash, 1, currentItem->m_locked ? "true" : "false");
ConcatCBIndices(currentItem, strCBIndices);
ms_contentLocksList->AddItem(hash, 2, strCBIndices);
if (Verifyf(index < MAX_CONTENT_LOCKS_PER_PAGE, "CExtraContentManager::AddContentLockToBankList - Index out of range! %u / %u", index, MAX_CONTENT_LOCKS_PER_PAGE))
CURR_CL_LIST[index] = hash;
}
}
void CExtraContentManager::DisplayContentLocks(s32 nextPage, u32 lookupItemHash/*=0*/)
{
if (ms_contentLocksList)
{
s32 maxPage = 1;
// Remove all the items first...
for (u32 i = 0; i < MAX_CONTENT_LOCKS_PER_PAGE; i++)
{
if (CURR_CL_LIST[i] != 0)
ms_contentLocksList->RemoveItem(CURR_CL_LIST[i]);
}
memset(CURR_CL_LIST, 0, sizeof(CURR_CL_LIST));
if (m_contentLocks.GetCount() > 0)
maxPage = ((m_contentLocks.GetCount() - 1) / MAX_CONTENT_LOCKS_PER_PAGE) + 1;
m_currentContentLocksDebugPage = nextPage;
m_currentContentLocksDebugPage = rage::Clamp(m_currentContentLocksDebugPage, 0, maxPage - 1);
formatf(m_contentLockCountString, "Count: [%i] // Page: [%i / %i]", m_contentLocks.GetCount(), (m_currentContentLocksDebugPage + 1), maxPage);
if (lookupItemHash == 0)
{
int startIndex = m_currentContentLocksDebugPage * MAX_CONTENT_LOCKS_PER_PAGE;
for (int i = startIndex; (i < (startIndex + MAX_CONTENT_LOCKS_PER_PAGE)) && (i < m_contentLocks.GetCount()); i++)
AddContentLockToBankList(*m_contentLocks.GetKey(i), m_contentLocks.GetItem(i), i - startIndex);
}
else
{
AddContentLockToBankList(lookupItemHash, m_contentLocks.SafeGet(lookupItemHash), 0);
}
}
}
void CExtraContentManager::ContentLockListDblClickCB(s32 hash)
{
if (SContentLockData* currentItem = m_contentLocks.SafeGet(hash))
ModifyContentLockState(hash, !currentItem->m_locked);
}
void CExtraContentManager::LookupContentLock()
{
DisplayContentLocks(0, atStringHash(m_contentLockSearchString));
}
void CExtraContentManager::DisplayContentCRC()
{
u32 initValue = 0;
u32 mapChangeCRC = 0;
initValue = EXTRACONTENT.GetCRC(initValue);
mapChangeCRC = EXTRACONTENT.GetMapChangesCRC();
formatf(m_matchMakingCrc, "Matchmaking: 0x%x, Map Change: 0x%x", initValue, mapChangeCRC);
}
void CExtraContentManager::Bank_InitMapChange()
{
EXTRACONTENT.SetMapChangeState(MCS_INIT);
Bank_UpdateMapChangeState();
}
void CExtraContentManager::Bank_EndMapChange()
{
EXTRACONTENT.SetMapChangeState(MCS_END);
Bank_UpdateMapChangeState();
}
void CExtraContentManager::Bank_UpdateMapChangeState()
{
if (ms_pBankContentStateGroup)
{
static const char* mcStateNames[MCS_COUNT] = { "MCS_NONE",
"MCS_INIT",
"MCS_UPDATE",
"MCS_END" };
strcpy(m_currMapChangeStateStr, mcStateNames[m_currMapChangeState]);
}
}
void CExtraContentManager::CreateContentStatusWidgets(bkBank* parentBank)
{
const char* emptyData = "";
if (ms_pBankContentStateGroup)
parentBank->Remove(*ms_pBankContentStateGroup);
ms_pBankContentStateGroup = ms_pBank->AddGroup("State",false);
ms_pBankContentStateGroup->AddText("Content CRCs:", m_matchMakingCrc, sizeof(m_matchMakingCrc), true);
ms_pBankContentStateGroup->AddButton("Refresh CRC", datCallback(MFA(CExtraContentManager::DisplayContentCRC), &EXTRACONTENT));
ms_pBankContentStateGroup->AddButton("Refresh content display", datCallback(MFA(CExtraContentManager::Bank_UpdateContentDisplay), &EXTRACONTENT));
ms_pBankContentStateGroup->AddText("Active Map Changes:", m_activeMapChange, sizeof(m_activeMapChange), true);
ms_pBankContentStateGroup->AddButton("Print active map changes - Search for [DLC-AMC]", datCallback(MFA(CExtraContentManager::Bank_PrintActiveMapChanges), &EXTRACONTENT));
ms_pBankContentStateGroup->AddText("Current Map Change State:", m_currMapChangeStateStr, sizeof(m_currMapChangeStateStr), true);
ms_pBankContentStateGroup->AddToggle("Allow map change anytime:", &m_allowMapChangeAnyTime, NullCB);
ms_pBankContentStateGroup->AddButton("Init map change", datCallback(MFA(CExtraContentManager::Bank_InitMapChange),&EXTRACONTENT));
ms_pBankContentStateGroup->AddButton("End map change", datCallback(MFA(CExtraContentManager::Bank_EndMapChange),&EXTRACONTENT));
ms_pChangeSetGroups = ms_pBankContentStateGroup->AddCombo("ChangeSet Groups:", &ms_changeSetGroupIndex, 0, &emptyData, datCallback(MFA(CExtraContentManager::Bank_ChangeSetGroupChanged),&EXTRACONTENT));
ms_pSelectedChangeSet = ms_pBankContentStateGroup->AddCombo("ChangeSet:", &ms_selectedChangeSetIndex, 0, &emptyData);
ms_pBankContentStateGroup->AddButton("Execute Selected ChangeSet", datCallback(MFA(CExtraContentManager::Bank_ExecuteSelectedChangeSet),&EXTRACONTENT));
ms_pBankContentStateGroup->AddButton("Revert Selected ChangeSet", datCallback(MFA(CExtraContentManager::Bank_RevertSelectedChangeSet),&EXTRACONTENT));
ms_pBankContentStateGroup->AddButton("Execute Selected Group", datCallback(MFA(CExtraContentManager::Bank_ExecuteSelectedGroup),&EXTRACONTENT));
ms_pBankContentStateGroup->AddButton("Revert Selected Group", datCallback(MFA(CExtraContentManager::Bank_RevertSelectedGroup),&EXTRACONTENT));
ms_pBankContentStateGroup->AddTitle("Double click items in the content list to set as Should-Activate");
ms_pBankContentStateGroup->AddButton("Apply Should-Activate State", datCallback(MFA(CExtraContentManager::Bank_ApplyShouldActivateContentChangeState), &EXTRACONTENT));
ms_pBankContentTable = ms_pBankContentStateGroup->AddList("Content");
s32 column = 0;
ms_pBankContentTable->AddColumnHeader(column++,"Name",bkList::STRING);
ms_pBankContentTable->AddColumnHeader(column++,"Timestamp",bkList::STRING);
ms_pBankContentTable->AddColumnHeader(column++,"CCS Should activate",bkList::STRING);
ms_pBankContentTable->AddColumnHeader(column++,"CCS Active",bkList::STRING);
ms_pBankContentTable->AddColumnHeader(column++,"Filename",bkList::STRING);
ms_pBankContentTable->AddColumnHeader(column++,"DatFile",bkList::STRING);
ms_pBankContentTable->AddColumnHeader(column++,"Uses Packfile",bkList::STRING);
ms_pBankContentTable->AddColumnHeader(column++,"Device Name",bkList::STRING);
ms_pBankContentTable->AddColumnHeader(column++,"Device Type",bkList::STRING);
ms_pBankContentTable->AddColumnHeader(column++,"Status",bkList::STRING);
bkList::ClickItemFuncType clickItemHandler;
clickItemHandler.Reset <CExtraContentManager, &CExtraContentManager::Bank_ContentListClick> (&EXTRACONTENT);
ms_pBankContentTable->SetSingleClickItemFunc(clickItemHandler);
bkList::ClickItemFuncType doubleClickItemHandler;
doubleClickItemHandler.Reset <CExtraContentManager, &CExtraContentManager::Bank_ContentListDoubleClick> (&EXTRACONTENT);
ms_pBankContentTable->SetDoubleClickItemFunc(doubleClickItemHandler);
ms_pBankVersionedContentTable = ms_pBankContentStateGroup->AddList("Versioned Changesets");
column = 0;
ms_pBankVersionedContentTable->AddColumnHeader(column++,"Versioned Changeset",bkList::STRING);
ms_pBankVersionedContentTable->AddColumnHeader(column++,"Active Changeset",bkList::STRING);
ms_pBankVersionedContentTable->AddColumnHeader(column++,"Active Content",bkList::STRING);
ms_pBankVersionedContentTable->AddColumnHeader(column++,"Version",bkList::INT);
ms_pBankVersionedContentTable->AddColumnHeader(column++,"State",bkList::STRING);
ms_bDoBankUpdate = true;
PopulateContentTableWidget();
DisplayContentCRC();
Bank_UpdateMapChangeState();
}
void CExtraContentManager::CreateUnusedFilesWidgets(bkBank* parentBank)
{
if (ms_pBankFilesGroup)
parentBank->Remove(*ms_pBankFilesGroup);
ms_pBankFilesGroup = ms_pBank->AddGroup("Content Files", false); {
ms_pBankFileListsPopulateButton = ms_pBankFilesGroup->AddButton("Populate All (will freeze game for ~10secs)", &CExtraContentManager::PopulateFileTableWidgets);
ms_pBankInUseFilesTable = ms_pBankFilesGroup->AddList("All Opened Files");
ms_pBankInUseFilesTable->AddColumnHeader(0, "Absolute Path", bkList::STRING);
ms_pBankInUsePrevButton = ms_pBankFilesGroup->AddButton("<--", &CExtraContentManager::PrevPageOfOpenFiles, "Previous", 0, true);
ms_pBankInUseNextButton = ms_pBankFilesGroup->AddButton("-->", &CExtraContentManager::NextPageOfOpenFiles, "Next", 0, true);
ms_pBankUnusedFilesTable = ms_pBankFilesGroup->AddList("Unused Files");
ms_pBankUnusedFilesTable->AddColumnHeader(0, "Absolute Path", bkList::STRING);
ms_pBankUnusedPrevButton = ms_pBankFilesGroup->AddButton("<--", &CExtraContentManager::PrevPageOfUnusedFiles, "Previous", 0, true);
ms_pBankUnusedNextButton = ms_pBankFilesGroup->AddButton("-->", &CExtraContentManager::NextPageOfUnusedFiles, "Next", 0, true);
}
}
void CExtraContentManager::PopulateContentTableWidget()
{
const char * states[sOverlayInfo::NUM_OVERLAY_STATES] = {"INACTIVE","WILL_ACTIVATE","ACTIVE"};
CompileTimeAssert(NELEM(states) == sOverlayInfo::NUM_OVERLAY_STATES);
ms_displayedPackageCount = m_content.GetCount();
for (u32 i = 0; i < m_content.GetCount(); i++)
{
int column = 0;
CMountableContent& mount = m_content[i];
char tempStr[RAGE_MAX_PATH] = { 0 };
formatf(tempStr, RAGE_MAX_PATH, "%u/%u", mount.GetActiveChangeSetCount(), mount.GetTotalContentChangeSetCount());
ms_pBankContentTable->AddItem(i,column++,mount.GetName());
ms_pBankContentTable->AddItem(i,column++,mount.GetTimeStamp());
ms_pBankContentTable->AddItem(i,column++,mount.GetBankShouldExecute()?"YES":"NO");
ms_pBankContentTable->AddItem(i,column++,tempStr);
ms_pBankContentTable->AddItem(i,column++,mount.GetFilename());
ms_pBankContentTable->AddItem(i,column++,mount.GetDatFileName());
ms_pBankContentTable->AddItem(i,column++,mount.GetUsesPackfile()?"YES":"NO");
ms_pBankContentTable->AddItem(i,column++,mount.GetDeviceName());
ms_pBankContentTable->AddItem(i,column++,GetDeviceTypeString(mount.GetPrimaryDeviceType()));
ms_pBankContentTable->AddItem(i,column++,CMountableContent::GetContentStatusName((mount.GetStatus())));
}
ms_displayedVersionedChangesetCount = m_overlayInfo.GetCount();
for(int i=0;i<m_overlayInfo.GetCount();i++)
{
int column = 0;
sOverlayInfo* info = m_overlayInfo[i];
CMountableContent* content = GetContentByHash(m_overlayInfo[i]->m_content);
Assertf(content, "Mountable content doesn't exist!");
ms_pBankVersionedContentTable->AddItem(i,column++,info->m_nameId.TryGetCStr());
ms_pBankVersionedContentTable->AddItem(i,column++,info->m_changeSet.TryGetCStr());
ms_pBankVersionedContentTable->AddItem(i,column++,content?content->GetName():"");
ms_pBankVersionedContentTable->AddItem(i,column++,info->m_version);
ms_pBankVersionedContentTable->AddItem(i,column++,states[info->m_state]);
}
}
void CExtraContentManager::PopulateFileTableWidgets() {
// Remove populate button
ms_pBankFileListsPopulateButton->Destroy();
ms_pBankFileListsPopulateButton = NULL;
// Check param first
if (!PARAM_checkUnusedFiles.Get()) {
ms_pBankInUseFilesTable->AddItem(0, 0, "Re-run with -checkUnusedFiles to populate them here");
ms_pBankUnusedFilesTable->AddItem(0, 0, "Re-run with -checkUnusedFiles to populate them here");
return;
}
// Add files requested
for (auto it = g_RequestedFiles.Begin(); it != g_RequestedFiles.End(); ++it) {
const char *originalPath = (*it).c_str();
// work out absolute path and store
char noPlatformPercent[RAGE_MAX_PATH];
DATAFILEMGR.ExpandFilename(originalPath, noPlatformPercent, RAGE_MAX_PATH);
atString finalPathString = NormaliseFileName(noPlatformPercent);
atHashString hashed = atStringHash(finalPathString);
if (!s_loadedFilesAbsolute.Has(hashed)) {
s_loadedFilesAbsolute.Insert(hashed, finalPathString);
}
}
s_loadedFilesPageCount = (int)rage::FPCeil((float)s_loadedFilesAbsolute.GetCount() / (float)SHOW_FILES_PER_PAGE);
PopulateOpenFilesAtPage(0);
// Add files existing on disk
for (auto it = g_RequestedDevices.Begin(); it != g_RequestedDevices.End(); ++it) {
const char *deviceName = (*it).c_str();
// we only really care about platform:, common:, dlc*:, and update:
if (strnicmp(deviceName, "platform:", 9) != 0 &&
strnicmp(deviceName, "common:", 7) != 0 &&
strnicmp(deviceName, "dlc", 3) != 0 && // no colon on purpose
strnicmp(deviceName, "update:", 7) != 0) {
continue;
}
// add slash
char deviceAndSlash[RAGE_MAX_PATH] = { 0 };
strncpy(deviceAndSlash, deviceName, RAGE_MAX_PATH);
deviceAndSlash[strlen(deviceName)] = '/';
// find device
if (const fiDevice *device = fiDevice::GetDevice(deviceAndSlash)) {
atBinaryMap<atString, u32> allTheFiles;
GetDeviceContentsRecursive(allTheFiles, deviceName); // SLOOOOOW
for (auto it = allTheFiles.Begin(); it != allTheFiles.End(); ++it) {
const char *file = (*it).c_str();
// Compute absolute file path
char fullPath[RAGE_MAX_PATH];
char absFile[RAGE_MAX_PATH];
device->FixRelativeName(absFile, RAGE_MAX_PATH, file);
DATAFILEMGR.ExpandFilename(absFile, fullPath, RAGE_MAX_PATH);
atString strFileName = NormaliseFileName(fullPath);
atHashString strFileNameHash = atStringHash(strFileName);
const char *fileName = strFileName.c_str();
// Is this file open? If not, add to list!
if (!s_loadedFilesAbsolute.Has(strFileNameHash)) {
const char *colonPtr = strchr(fileName, ':');
size_t deviceLen = colonPtr - fileName;
if (deviceLen > 1) {
// this is not an X:\ or C:\ path, ignore
continue;
}
if (!s_unusedFilesAbsolute.Has(strFileNameHash)) {
s_unusedFilesAbsolute.Insert(strFileNameHash, strFileName);
}
}
}
}
}
// Populate Unused
s_unusedFilesPageCount = (int)rage::FPCeil((float)s_unusedFilesAbsolute.GetCount() / (float)SHOW_FILES_PER_PAGE);
PopulateUnusedFilesAtPage(0);
}
void CExtraContentManager::PopulateOpenFilesAtPage(u32 pageNum) {
s_loadedFilesPageNumber = rage::Min(pageNum, s_loadedFilesPageCount - 1);
u32 startIndex = s_loadedFilesPageNumber * SHOW_FILES_PER_PAGE;
auto it = s_loadedFilesAbsolute.Begin();
// Fast-forward to start index of page
while (startIndex --> 0 && it != s_loadedFilesAbsolute.End()) {
++it;
}
// Run for up to SHOW_FILES_PER_PAGE files
u32 i = 0;
for ( ; it != s_loadedFilesAbsolute.End() && i < SHOW_FILES_PER_PAGE; ++it) {
const char *finalPath = (*it).c_str();
ms_pBankInUseFilesTable->AddItem(i++, 0, finalPath);
}
ms_pBankInUsePrevButton->SetReadOnly(pageNum == 0);
ms_pBankInUseNextButton->SetReadOnly(pageNum == s_loadedFilesPageCount - 1);
}
void CExtraContentManager::PrevPageOfOpenFiles() {
if (s_loadedFilesPageNumber == 0) {
return;
}
PopulateOpenFilesAtPage(s_loadedFilesPageNumber-1);
}
void CExtraContentManager::NextPageOfOpenFiles() {
if (s_loadedFilesPageNumber == s_loadedFilesPageCount - 1) {
return;
}
PopulateOpenFilesAtPage(s_loadedFilesPageNumber+1);
}
void CExtraContentManager::PopulateUnusedFilesAtPage(u32 pageNum) {
s_unusedFilesPageNumber = rage::Min(pageNum, s_unusedFilesPageCount - 1);
u32 startIndex = s_unusedFilesPageNumber * SHOW_FILES_PER_PAGE;
auto it = s_unusedFilesAbsolute.Begin();
// Fast-forward to start index of page
while (startIndex --> 0 && it != s_unusedFilesAbsolute.End()) {
++it;
}
// Run for up to SHOW_FILES_PER_PAGE files
u32 i = 0;
for ( ; it != s_unusedFilesAbsolute.End() && i < SHOW_FILES_PER_PAGE; ++it) {
const char *finalPath = (*it).c_str();
ms_pBankUnusedFilesTable->AddItem(i++, 0, finalPath);
}
ms_pBankUnusedPrevButton->SetReadOnly(pageNum == 0);
ms_pBankUnusedNextButton->SetReadOnly(pageNum == s_unusedFilesPageCount - 1);
}
void CExtraContentManager::PrevPageOfUnusedFiles() {
if (s_unusedFilesPageNumber == 0) {
return;
}
PopulateUnusedFilesAtPage(s_unusedFilesPageNumber-1);
}
void CExtraContentManager::NextPageOfUnusedFiles() {
if (s_unusedFilesPageNumber == s_unusedFilesPageCount- 1) {
return;
}
PopulateUnusedFilesAtPage(s_unusedFilesPageNumber+1);
}
void CExtraContentManager::CreateContentLocksWidgets(bkBank* parentBank)
{
if (ms_pBankContentLockGroup)
parentBank->Remove(*ms_pBankContentLockGroup);
ms_pBankContentLockGroup = parentBank->AddGroup("Content Locks",false);
bkList::ClickItemFuncType contentLockDblClickCB;
contentLockDblClickCB.Reset<CExtraContentManager, &CExtraContentManager::ContentLockListDblClickCB>(this);
ms_pBankContentLockGroup->AddText("Indices:", m_contentLockCountString, sizeof(m_contentLockCountString), true);
ms_pBankContentLockGroup->AddText("Lookup:", m_contentLockSearchString, sizeof(m_contentLockSearchString), false, datCallback(MFA(CExtraContentManager::LookupContentLock), (datBase*)this));
ms_contentLocksList = ms_pBankContentLockGroup->AddList("Content Locks (double click to toggle lock)");
ms_contentLocksList->SetDoubleClickItemFunc(contentLockDblClickCB);
ms_contentLocksList->AddColumnHeader(0, "Name", bkList::STRING);
ms_contentLocksList->AddColumnHeader(1, "Locked", bkList::STRING);
ms_contentLocksList->AddColumnHeader(2, "Callback Index", bkList::STRING);
ms_pBankContentLockGroup->AddButton("<< Previous Page", &ShowPreviousContentLockPage);
ms_pBankContentLockGroup->AddButton("Next Page >>", &ShowNextContentLockPage);
DisplayContentLocks(0);
}
void CExtraContentManager::Bank_UpdateSpecialTriggers(eSpecialTrigger trigger)
{
SetSpecialTrigger(trigger, !GetSpecialTrigger(trigger));
}
void CExtraContentManager::CreateSpecialTriggersWidgets(bkBank* parentBank)
{
if (ms_pSpecialTriggersGroup)
parentBank->Remove(*ms_pSpecialTriggersGroup);
ms_pSpecialTriggersGroup = parentBank->AddGroup("Special Triggers",false);
ms_pSpecialTriggersGroup->AddSlider("Special Triggers: ", EXTRACONTENT.BANK_GetSpecialTriggers(), 0, 0, 0);
ms_pSpecialTriggersGroup->AddButton("Toggle ST_XMAS", datCallback(MFA1(CExtraContentManager::Bank_UpdateSpecialTriggers), &EXTRACONTENT, (void*)ST_XMAS));
//bank.AddButton("Simplify 10%", datCallback(MFA1(demeshView::SimplifyPercentCb), this, (void*)10));
}
#endif // __BANK
class MetricCCRC : public MetricPlayStat
{
RL_DECLARE_METRIC(CODE_CRC, TELEMETRY_CHANNEL_MISC, LOGLEVEL_VERYHIGH_PRIORITY);
};
#if RSG_ORBIS
void CExtraContentManager::ContentPoll(void* UNUSED_PARAM(ptr))
{
// TODO: Change this for an event when Sony add one.
// Poll because SCE_SYSTEM_SERVICE_EVENT_ENTITLEMENT_UPDATE only happens when entitlements change not each time DLC is installed.
// https://ps4.scedev.net/forums/thread/33348/
// This is really lame, I want this code to die and one day soon it will.
SceNpServiceLabel serviceLabel = 0;
SceAppContentAddcontInfo packages[SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM];
u32 packageCount = 0;
s32 errValue = 0;
s32 prevInstalled = 0;
s32 currInstalled = 0;
memset(packages, 0, sizeof(SceAppContentAddcontInfo) * SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM);
while (true)
{
if (!PARAM_disableEntitlementCheck.Get() && !PARAM_usecompatpacks.Get() && !PARAM_extracontent.Get())
{
errValue = sceAppContentGetAddcontInfoList(serviceLabel, packages, SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM, &packageCount);
if (Verifyf(errValue == SCE_OK, "Update - Failed to get package count! %i", errValue))
{
currInstalled = 0;
// Assume install only, not accounting for deletion
for (u32 i = 0; i < packageCount; i++)
{
if (packages[i].status == SCE_APP_CONTENT_ADDCONT_DOWNLOAD_STATUS_INSTALLED)
currInstalled++;
}
if (currInstalled != prevInstalled)
EXTRACONTENT.OnContentDownloadCompleted();
prevInstalled = currInstalled;
}
}
sysIpcSleep(5000);
}
}
#endif
void CExtraContentManager::CodeCheck(void* UNUSED_PARAM(ptr))
{
#if RSG_PS3
const char* codeStart = (const char*)&__start__Ztext[0];
const s32 codeSize = (s32)(&__stop__Ztext[0] - &__start__Ztext[0]);
#elif RSG_XENON
const char* codeStart = (const char*)&__start__Ztext;
const s32 codeSize = (s32)(&__stop__Ztext - &__start__Ztext);
#endif
#if RSG_PS3 || RSG_XENON
sysTimer timer;
const u32 initialKey = fwKeyGen::GetKey(codeStart, codeSize);
Displayf("[CODE_CHECK] Initial code crc duration: %fs, code: %d", timer.GetTime(), initialKey);
u32 testKey = 0;
if (NetworkInterface::IsGameInProgress())
{
StatId crcStat("CODE_CRC");
if (StatsInterface::IsKeyValid(crcStat)) //are stats loaded?
StatsInterface::SetStatData(crcStat, initialKey);
}
else
{
StatId crcStat("SP_CODE_CRC");
if (StatsInterface::IsKeyValid(crcStat)) //are stats loaded?
StatsInterface::SetStatData(crcStat, initialKey);
}
const u32 numBatches = 8;
const u32 batchSize = ((codeSize / numBatches) + 3) & ~3;
const u32 lastBatchSize = codeSize - (batchSize * (numBatches - 1));
const u32 timeBetweenChecks = 1 * 60 * 1000; // run check every 1 min
u32 numBatch = 8;
u32 nextCheckTime = fwTimer::GetTimeInMilliseconds() + timeBetweenChecks;
while (s_codeCheckRun)
{
if (numBatch < numBatches)
{
if (CNetwork::IsGameInProgress())
{
// do next batch
timer.Reset();
Displayf("[CODE_CHECK] Doing batch %d of %d...", numBatch + 1, numBatches);
if (numBatch == 0)
testKey = fwKeyGen::GetKey(codeStart, batchSize);
else if (numBatch == numBatches - 1)
testKey = fwKeyGen::AppendDataToKey(testKey, codeStart + (batchSize * numBatch), lastBatchSize);
else
testKey = fwKeyGen::AppendDataToKey(testKey, codeStart + (batchSize * numBatch), batchSize);
dlcDisplayf("[CODE_CHECK] Batch %d of %d completed in %fs, key: %d", numBatch + 1, numBatches, timer.GetTime(), testKey);
numBatch++;
if (numBatch == numBatches)
{
nextCheckTime = fwTimer::GetTimeInMilliseconds() + timeBetweenChecks;
if (testKey != initialKey)
{
if (!s_codeCompromised)
{
MetricCCRC m;
CNetworkTelemetry::AppendMetric(m);
}
Warningf("[CODE_CHECK] Code crc verification failed! Initial key: %d, latest key: %d", initialKey, testKey);
s_codeCompromised = true;
}
}
}
}
else
{
u32 curTime = fwTimer::GetTimeInMilliseconds();
if (nextCheckTime < curTime)
numBatch = 0;
}
sysIpcSleep(1000);
}
#endif
}
bool CExtraContentManager::IsCodeCompromised()
{
return s_codeCompromised;
}
#if GTA_REPLAY
void CExtraContentManager::SetReplayState(const u32 *extraContentHashes, u32 hashCount)
{
SYS_CS_SYNC(m_replayLock);
Assert(extraContentHashes);
Assert(hashCount < MAX_EXTRACONTENTHASHES);
m_replayChangeSetHashCount = Min(hashCount, MAX_EXTRACONTENTHASHES);
memcpy(m_replayChangeSetHashes, extraContentHashes, m_replayChangeSetHashCount * sizeof(u32));
}
void CExtraContentManager::ResetReplayState()
{
SYS_CS_SYNC(m_replayLock);
memset(m_replayChangeSetHashes, 0, MAX_EXTRACONTENTHASHES * sizeof(u32));
m_replayChangeSetHashCount = 0;
}
void CExtraContentManager::ExecuteReplayMapChanges()
{
SYS_CS_SYNC(m_replayLock);
atArray<u32> activeMapChangeHashes;
GetMapChangeArray(activeMapChangeHashes);
bool diff = activeMapChangeHashes.GetCount() != (int) m_replayChangeSetHashCount;
if(!diff)
{
for(int i = 0; i < activeMapChangeHashes.GetCount(); i++)
{
if(activeMapChangeHashes[i] != m_replayChangeSetHashes[i])
{
diff = true;
break;
}
}
}
if(!diff)
return;
RevertCurrentMapChanges();
for(u32 i = 0; i < m_replayChangeSetHashCount; i++)
{
u32 changeSetHash = m_replayChangeSetHashes[i];
if(CMountableContent *c = CMountableContent::GetContentForChangeSet(changeSetHash))
{
if(u32 group = c->GetChangeSetGroup(changeSetHash))
{
Assert((group == CCS_GROUP_MAP) || (group == CCS_GROUP_MAP_SP));
ExecuteContentChangeSetInternal(c, group, changeSetHash, ECCS_FLAG_USE_LOADING_SCREEN);
}
}
}
if(m_replayChangeSetHashCount > 0)
{
CMountableContent::CleanupAfterMapChange();
}
}
void CExtraContentManager::RevertCurrentMapChanges()
{
atArray<u32> activeMapChangeHashes;
GetMapChangeArray(activeMapChangeHashes);
for(u32 i = 0; i < activeMapChangeHashes.GetCount(); i++)
{
u32 changeSetHash = activeMapChangeHashes[i];
if(CMountableContent *c = CMountableContent::GetContentForChangeSet(changeSetHash))
{
if(u32 group = c->GetChangeSetGroup(changeSetHash))
{
Assert((group == CCS_GROUP_MAP) || (group == CCS_GROUP_MAP_SP));
RevertContentChangeSetInternal(c, group, changeSetHash, CDataFileMgr::ChangeSetAction::CCS_ALL_ACTIONS, ECCS_FLAG_USE_LOADING_SCREEN);
}
}
}
}
#endif // GTA_REPLAY