// // 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 __PACKEDDEBUG #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 #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 g_RequestedFiles; extern atBinaryMap g_RequestedDevices; } enum { SHOW_FILES_PER_PAGE = 256 }; static atBinaryMap s_loadedFilesAbsolute; static atBinaryMap 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 &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 &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;idxm_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;im_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(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 changeSets; content->GetChangeSetHashesForGroup(changeSetGroup,changeSets); for(int i=0;iGetName()); atArray changeSets; content->GetChangeSetHashesForGroup(changeSetGroup,changeSets); for(int i=0;iGetName()); 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& overlayInfos, atHashString changeSetName) { for(int i=0;im_changeSet) { return overlayInfos[i]->m_nameId; } } return atHashString::Null(); } sOverlayInfo* CExtraContentManager::GetHighestOverlayInfoForVersionedChange(atArray& overlayInfos, atHashString versionedChange) { sOverlayInfo* retval = NULL; for(int i=0;im_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;im_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 (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* defaultChecks = rage_new atDelegate(); defaultChecks->Bind(this,&CExtraContentManager::GenericChangesetChecks); atDelegate* levelChecks = rage_new atDelegate(); 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;iGetLocalIndex(); 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, ¶m)) { 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 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& retArray) const { for (u32 i = 0; i < m_content.GetCount(); i++) { m_content[i].GetMapChangeHashes(retArray); } } u32 CExtraContentManager::GetMapChangesCRC() const { u32 retVal = 0; atArray 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 &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 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(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;iGetNameHash(),(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;iGetIsCompatibilityPack()||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 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; iGetHandle()); 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_workingDLCIndexAddCombo("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 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 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 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 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 (&EXTRACONTENT); ms_pBankContentTable->SetSingleClickItemFunc(clickItemHandler); bkList::ClickItemFuncType doubleClickItemHandler; doubleClickItemHandler.Reset (&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;im_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 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(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 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 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