//
// 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 <stddef.h>
#include "fwutil/KeyGen.h"
extern const unsigned char __start__Ztext[];
extern const unsigned char __stop__Ztext[];
#elif RSG_XENON
#pragma const_seg("rodata1")
const unsigned char __start__Ztext = 0;
#pragma const_seg()
#pragma bss_seg("arsbss")
unsigned char __stop__Ztext;
#pragma bss_seg()
#endif

namespace rage
{
	NOSTRIP_XPARAM(audiofolder);
}

#if RSG_ORBIS
sysIpcThreadId s_contentPollThread = sysIpcThreadIdInvalid;
#endif

sysIpcThreadId s_codeCheckThread = sysIpcThreadIdInvalid;
bool s_codeCheckRun = true;
bool s_codeCompromised = false;

#if __BANK 
namespace rage {
	extern atBinaryMap<atString, u32> g_RequestedFiles;
	extern atBinaryMap<atString, u32> g_RequestedDevices;
}

enum {
	SHOW_FILES_PER_PAGE = 256
};

static atBinaryMap<atString, u32> s_loadedFilesAbsolute;
static atBinaryMap<atString, u32> s_unusedFilesAbsolute;
static u32 s_loadedFilesPageNumber = 0;
static u32 s_loadedFilesPageCount = 0;
static u32 s_unusedFilesPageNumber = 0;
static u32 s_unusedFilesPageCount = 0;

// This is probably really, really slow. So much iterating and recursing! Gets all the files inside dev:/path,
// recursing into any directories
static void GetDeviceContentsRecursive(atBinaryMap<atString, u32> &map, const fiDevice *dev, const char *path) {
	fiFindData data;
	fiHandle handle = dev->FindFileBegin(path, data);
	bool validHandle = fiIsValidHandle(handle);

	while (validHandle) {
		char currentPath[RAGE_MAX_PATH];

		if (data.m_Name[0] != '.') { // skip over ., .., and UNIX-style hidden files
			// Found a directory, so deal with it
			if ((data.m_Attributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) {
				formatf(currentPath, sizeof(currentPath), "%s\\%s", path, data.m_Name);

				GetDeviceContentsRecursive(map, dev, currentPath);
			} else {
				// Build the new pathname to use
				formatf(currentPath, "%s\\%s", path, data.m_Name);

				atString currentPathString(currentPath);
				atHashString currentPathHash = atStringHash(currentPath);

				map.Insert(currentPathHash, currentPathString);
			}
		}

		validHandle = dev->FindFileNext(handle, data);
	}

	dev->FindFileEnd(handle);
}

// Convenience form of above.
// Expects name in format of "device:"
static void GetDeviceContentsRecursive(atBinaryMap<atString, u32> &map, const char *deviceName) {
	// append '/'
	char fixedDeviceName[RAGE_MAX_PATH] = { 0 };
	strncpy(fixedDeviceName, deviceName, RAGE_MAX_PATH);
	fixedDeviceName[strlen(deviceName)] = '/';

	// find device
	if (const fiDevice *device = fiDevice::GetDevice(fixedDeviceName)) {
		return GetDeviceContentsRecursive(map, device, fixedDeviceName);
	}
}

#endif

const char * GetDeviceTypeString (CMountableContent::eDeviceType deviceType);

class CExtraContentFileMounter : public CDataFileMountInterface
{
	virtual bool LoadDataFile(const CDataFileMgr::DataFile& file)
	{
		bool retValue = true;

		dlcDebugf3("CExtraContentFileMounter::LoadDataFile - Found file %s", file.m_filename);

		switch (file.m_fileType)
		{
			case CDataFileMgr::GTXD_PARENTING_DATA:
				{
					CMapFileMgr::GetInstance().ProcessGlobalTxdParentsFile(file.m_filename);
				}
			break;
			case CDataFileMgr::OVERLAY_INFO_FILE:
				EXTRACONTENT.LoadOverlayInfo(file.m_filename);
			break;
			case CDataFileMgr::CONTENT_UNLOCKING_META_FILE:
			{
				SContentUnlocks newUnlocks;

				if (PARSER.LoadObject(file.m_filename, NULL, newUnlocks))
				{
					dlcDebugf3("CExtraContentFileMounter::LoadDataFile - Found %i new content unlocks from %s", newUnlocks.m_listOfUnlocks.GetCount(), file.m_filename);

					for (int i = 0; i < newUnlocks.m_listOfUnlocks.GetCount(); i++)
						EXTRACONTENT.ModifyContentLockState(newUnlocks.m_listOfUnlocks[i].GetHash(), false);
				}
			}
			break;

			case CDataFileMgr::CLIP_SETS_FILE:
			{
				fwClipSetManager::PatchClipSets(file.m_filename);
			}
			break;

			case CDataFileMgr::EXPRESSION_SETS_FILE:
			{
				fwExpressionSetManager::Append(file.m_filename);
			}
			break;

			case CDataFileMgr::FACIAL_CLIPSET_GROUPS_FILE:
			{
				fwFacialClipSetGroupManager::Append(file.m_filename);
			}
			break;

			case CDataFileMgr::NM_BLEND_OUT_SETS_FILE:
			{
				CNmBlendOutSetManager::Append(file.m_filename);
			}
			break;

			case CDataFileMgr::NM_TUNING_FILE:
			{
				CTuningManager::AppendPhysicsTasks(file.m_filename);
			}
			break;
			case CDataFileMgr::TEXTFILE_METAFILE:
			{
				TheText.AddExtracontentTextFromMetafile(file.m_filename);
			}
			break;

			case CDataFileMgr::MOVE_NETWORK_DEFS:
			{
				fwAnimDirector::LoadNetworkDefinitions(file.m_filename);
			}
			break;

			case CDataFileMgr::EXTRA_TITLE_UPDATE_DATA:
			{
				CMountableContent::LoadExtraTitleUpdateData(file.m_filename);
			}
			break;

			case CDataFileMgr::WORLD_HEIGHTMAP_FILE:
			{
				CGameWorldHeightMap::LoadExtraFile(file.m_filename);
			}
			break;

			case CDataFileMgr::WATER_FILE:
			{
				// do nothing
			}
			break;

			case CDataFileMgr::EXTRA_FOLDER_MOUNT_DATA:
			{
				CMountableContent::LoadExtraFolderMountData(file.m_filename);
			}
			break;

			case CDataFileMgr::VEHICLE_SHOP_DLC_FILE:
			{
				EXTRAMETADATAMGR.AddVehiclesCollection(file.m_filename);
			}
			break;

			case CDataFileMgr::LEVEL_STREAMING_FILE:
			{
				CStreaming::SetStreamingFile(file.m_filename);
			}
			break;

			case CDataFileMgr::LOADOUTS_FILE:
			{
				CPedInventoryLoadOutManager::Append(file.m_filename);
			}
			break;

			case CDataFileMgr::CARCOLS_GEN9_FILE:
			{
				CVehicleModelInfo::AppendVehicleColorsGen9(file.m_filename);
			}
			break;

			case CDataFileMgr::CARMODCOLS_GEN9_FILE:
			{
				CVehicleModelInfo::LoadVehicleModColorsGen9(file.m_filename);
			}
			break;

			case CDataFileMgr::GEN9_EXCLUSIVE_ASSETS_VEHICLES_FILE :
			{
				CGen9ExclusiveAssets::LoadGen9ExclusiveAssetsVehiclesData(file.m_filename);
			}
			break;

			case CDataFileMgr::GEN9_EXCLUSIVE_ASSETS_PEDS_FILE :
			{
				CGen9ExclusiveAssets::LoadGen9ExclusiveAssetsPedsData(file.m_filename);
			}
			break;

			default: Errorf("CExtraContentFileMounter::LoadDataFile Invalid file type! %s [%i]", file.m_filename, file.m_fileType); break;
		}

		return retValue;
	}

	virtual void UnloadDataFile(const CDataFileMgr::DataFile& file) 
	{
		dlcDebugf3("CExtraContentFileMounter::UnloadDataFile - Found file %s", file.m_filename);

		switch (file.m_fileType)
		{
			case CDataFileMgr::OVERLAY_INFO_FILE:
			EXTRACONTENT.UnloadOverlayInfo(file.m_filename);
			break;
			case CDataFileMgr::TEXTFILE_METAFILE:
			{
				TheText.RemoveExtracontentTextFromMetafile(file.m_filename);
			}
			break;
			case CDataFileMgr::NM_TUNING_FILE:
			{
				CTuningManager::RevertPhysicsTasks(file.m_filename);
			}
			break;

			case CDataFileMgr::EXTRA_FOLDER_MOUNT_DATA:
			{
				CMountableContent::UnloadExtraFolderMountData(file.m_filename);
			}
			break;

			case CDataFileMgr::VEHICLE_SHOP_DLC_FILE:
			{
				EXTRAMETADATAMGR.RemoveVehiclesCollection(file.m_filename);
			}
			break;

 			case CDataFileMgr::MOVE_NETWORK_DEFS:
			{
 				fwAnimDirector::UnloadNetworkDefinitions(file.m_filename);
 			}
			break;

			default: Errorf("CExtraContentFileMounter::UnloadDataFile Invalid file type! %s [%i]", file.m_filename, file.m_fileType); break;
		}
	}
} g_extraContentFileMounter, g_extraContentFileMounterToRegisterUnload;

void CExtraContentWrapper::Init(u32 initMode)
{
	if (initMode == INIT_CORE && !CExtraContentManagerSingleton::IsInstantiated())
		CExtraContentManagerSingleton::Instantiate();

	if (CExtraContentManagerSingleton::IsInstantiated())
		EXTRACONTENT.Init(initMode);
}

void CExtraContentWrapper::Shutdown(u32 shutdownMode)
{
	if (CExtraContentManagerSingleton::IsInstantiated())
	{
		EXTRACONTENT.ShutdownMetafiles(shutdownMode, eDFMI_UnloadLast);
		EXTRACONTENT.Shutdown(shutdownMode);

		if (shutdownMode == SHUTDOWN_CORE)
			CExtraContentManagerSingleton::Destroy();
	}
}

void CExtraContentWrapper::ShutdownStart(u32 shutdownMode)
{
	if (CExtraContentManagerSingleton::IsInstantiated())
	{
		EXTRACONTENT.ShutdownMetafiles(shutdownMode, eDFMI_UnloadFirst);
		EXTRACONTENT.ShutdownMapChanges(shutdownMode);
	}
}

void CExtraContentWrapper::Update()
{
	PF_START_TIMEBAR("extra content");
	{
		EXTRACONTENT.Update();
		DOWNLOADABLETEXTUREMGR.Update();
	}
}

void CExtraContentManager::LoadOverlayInfo(const char* fileName)
{
	sOverlayInfos infos;
	if(PARSER.LoadObject(fileName,"",infos))
	{
		for(int idx=0;idx<infos.m_overlayInfos.GetCount();idx++)
		{
			dlcDebugf3("overlay info: %s %s", infos.m_overlayInfos[idx].m_nameId.TryGetCStr(), infos.m_overlayInfos[idx].m_changeSet.TryGetCStr());
			UpdateOverlayInfo(infos.m_overlayInfos[idx],fileName);
		}
	}
}

void CExtraContentManager::UnloadOverlayInfo(const char* fileName)
{
	sOverlayInfos infos;
	if(PARSER.LoadObject(fileName,"",infos))
	{
		for(int idx=0;idx<infos.m_overlayInfos.GetCount();idx++)
		{
			dlcDebugf3("overlay info: %s", infos.m_overlayInfos[idx].m_changeSet.TryGetCStr());
			RemoveOverlayInfo(infos.m_overlayInfos[idx]);
		}
	}
}

void CExtraContentManager::RemoveOverlayInfo(sOverlayInfo &overlayInfo)
{
	sOverlayInfo* toStart = NULL;
	sOverlayInfo* currentOverlay = NULL;
	for(int i=0;i<m_overlayInfo.GetCount();i++)
	{
		currentOverlay = m_overlayInfo[i];
		if(currentOverlay->m_nameId == overlayInfo.m_nameId)
		{
			if(currentOverlay->m_changeSet == overlayInfo.m_changeSet)
			{
				if (currentOverlay->m_state == sOverlayInfo::ACTIVE)
				{
					CMountableContent* content = GetContentByHash(currentOverlay->m_content);
					Assertf(content, "Mountable content doesn't exist!");
					RevertContentChangeSet(content->GetNameHash(),currentOverlay->m_changeSetGroupToExecuteWith,currentOverlay->m_changeSet);
				}
				m_overlayInfo.Delete(i--);
				delete currentOverlay;
			}
			else
			{
				if(toStart)
				{
					toStart = toStart->m_version < currentOverlay->m_version ? currentOverlay : toStart; 
				}
				else
				{
					toStart = currentOverlay;
				}
			}
		}
	}
	if(toStart && toStart->m_state != sOverlayInfo::ACTIVE)
	{
		toStart->m_state = sOverlayInfo::WILL_ACTIVATE;
	}

}

void CExtraContentManager::UpdateOverlayInfo(sOverlayInfo &overlayInfo, const char* fileName)
{
	char device[RAGE_MAX_PATH];
	memset(device,0,RAGE_MAX_PATH);
	sOverlayInfo* highestVersion = &overlayInfo;
	for(int i=0;i<m_overlayInfo.GetCount();i++)
	{
		sOverlayInfo*& currentInfo = m_overlayInfo[i];
		if(currentInfo->m_nameId == overlayInfo.m_nameId)
		{
			if(highestVersion->m_version>currentInfo->m_version)
			{
				CMountableContent* content = GetContentByHash(currentInfo->m_content);
				Assertf(content, "Mountable content doesn't exist!");
				if(currentInfo->m_state == sOverlayInfo::ACTIVE)
				{
					RevertContentChangeSet(content->GetNameHash(),currentInfo->m_changeSetGroupToExecuteWith,currentInfo->m_changeSet);
				}
				currentInfo->m_state = sOverlayInfo::INACTIVE;
			}
			else
			{
				highestVersion = m_overlayInfo[i];
			}
		}
	}
	if(highestVersion->m_state != sOverlayInfo::ACTIVE)
		highestVersion->m_state = sOverlayInfo::WILL_ACTIVATE;
	strncpy(device,fileName,static_cast<int>(strcspn(fileName,"/"))+1);
	overlayInfo.m_content = GetContentByDevice(device)->GetNameHash();
	Assertf(overlayInfo.m_content!=0,"Content not found for overlaid file!");
	dlcDisplayf("New overlayinfo, versioned changeset: %s, content changeset: %s, content: %s, version: %d", overlayInfo.m_nameId.TryGetCStr(), overlayInfo.m_changeSet.TryGetCStr(), GetContentByHash(overlayInfo.m_content)->GetName(),overlayInfo.m_version);
	dlcDisplayf("Highest version of the changeset for versioned changeset: %s,  content changeset: %s, content: %s, version: %d", overlayInfo.m_nameId.TryGetCStr(), overlayInfo.m_changeSet.TryGetCStr(), GetContentByHash(overlayInfo.m_content)->GetName(),overlayInfo.m_version);
	m_overlayInfo.PushAndGrow(rage_new sOverlayInfo(overlayInfo));	
}
void CExtraContentManager::ExecuteContentChangeSetGroupInternal(CMountableContent* content, atHashString changeSetGroup)
{
	if(content)
	{
		dlcDisplayf("Executing changeset group %s for %s",changeSetGroup.TryGetCStr(),content->GetName());
		atArray<atHashString> changeSets;
		content->GetChangeSetHashesForGroup(changeSetGroup,changeSets);
		for(int i=0;i<changeSets.GetCount();i++)
		{
			ExecuteContentChangeSetInternal(content,changeSetGroup,changeSets[i], ECCS_FLAG_USE_LATEST_VERSION | ECCS_FLAG_USE_LOADING_SCREEN);
		}

		CMountableContent::CleanupAfterMapChange();
	}
}
void CExtraContentManager::RevertContentChangeSetGroupInternal(CMountableContent* content, atHashString changeSetGroup, u32 actionMask)
{
	if(content)
	{
		dlcDisplayf("Reverting changeset group %s for %s",changeSetGroup.TryGetCStr(),content->GetName());
		atArray<atHashString> changeSets;
		content->GetChangeSetHashesForGroup(changeSetGroup,changeSets);
		for(int i=0;i<changeSets.GetCount();i++)
		{
			RevertContentChangeSetInternal(content,changeSetGroup,changeSets[i],actionMask, ECCS_FLAG_USE_LATEST_VERSION | ECCS_FLAG_USE_LOADING_SCREEN);
		}
	}
}

void CExtraContentManager::TriggerLoadingScreen(LoadingScreenContext loadingContext=LOADINGSCREEN_CONTEXT_MAPCHANGE)
{
	if (!m_loadingScreenState.m_modified)
	{
		m_loadingScreenState.m_modified = true;

		if (!CLoadingScreens::AreActive() DURANGO_ONLY(&& gRenderThreadInterface.IsUsingDefaultRenderFunction()) REPLAY_ONLY(&& !CVideoEditorPlayback::IsLoading()))
		{
			// Catch edge case where movie is still active.
			CLoadingScreens::Shutdown(SHUTDOWN_CORE);
			CLoadingScreens::Update();

			CLoadingScreens::Init(loadingContext, 0);
			CLoadingScreens::CommitToMpSp();
			strStreamingEngine::GetLoader().CallKeepAliveCallback();
			m_loadingScreenState.m_displayed = true;

			gRenderThreadInterface.Flush(true);
		}
	}
}

void CExtraContentManager::CloseLoadingScreen()
{
	if (m_loadingScreenState.m_modified)
	{
		if (m_loadingScreenState.m_displayed)
		{
			// Give the game one frame to gather streaming requests from the load
			if (m_loadingScreenState.m_doneAPostFrame)
			{
				if (strStreamingEngine::GetInfo().GetNumberObjectsRequested() <= 0)
				{
					CLoadingScreens::Shutdown(SHUTDOWN_CORE);
					m_loadingScreenState.Reset();
				}
			}
			else
				m_loadingScreenState.m_doneAPostFrame = true;
		}
		else
			m_loadingScreenState.Reset();
	}
}

void CExtraContentManager::ExecuteContentChangeSetInternal(
	CMountableContent* content, atHashString changeSetGroup, atHashString changeSetName, u32 flags DURANGO_ONLY(, bool addPending/*=true*/))
{
	BANK_ONLY(bool tempValue = strStreamingInfoManager::ms_bValidDlcOverlay;)
	sOverlayInfo * info = NULL;
	if(flags & ECCS_FLAG_USE_LATEST_VERSION)
	{
		atHashString versionedChange = GetVersionedChangeSetName(m_overlayInfo,changeSetName);
		Displayf("Executing changeset %s in group %s for %s",changeSetName.TryGetCStr(),changeSetGroup.TryGetCStr(),content->GetName());
		if(versionedChange != 0)
		{
			Displayf("This is a versioned changeset: %s",versionedChange.TryGetCStr());
			info = GetHighestOverlayInfoForVersionedChange(m_overlayInfo,versionedChange);
			if(info->m_state != sOverlayInfo::ACTIVE)
			{
				content = GetContentByHash(info->m_content);
				changeSetName = info->m_changeSet;
				changeSetGroup = info->m_changeSetGroupToExecuteWith;
				BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = true;)
					info->m_state = sOverlayInfo::ACTIVE;
				Displayf("Executing highest version of the changeset %s in group %s in %s",changeSetName.TryGetCStr(),changeSetGroup.TryGetCStr(),content->GetName());
			}
			else
			{
				//already executed
				return;
			}
		}
		else if(changeSetGroup == CCS_GROUP_MAP || changeSetGroup == CCS_GROUP_MAP_SP)
		{
			Assertf(0, "Map changesets should be versioned, Content: %s", content->GetName());
#if !__NO_OUTPUT
			Quitf("Map changesets should be versioned, Content: %s", content->GetName());
#endif // !__NO_OUTPUT
		}
	}

#if RSG_DURANGO
	// Because XB1 doesn't have a persistent back buffer, we need to wait for it to copy out of ESRAM
	if (addPending && (changeSetGroup == CCS_GROUP_MAP || changeSetGroup == CCS_GROUP_MAP_SP))
	{
		if (const CDataFileMgr::ContentChangeSet* csPtr = DATAFILEMGR.GetContentChangeSet(changeSetName))
		{
			// If we need a loading screen and we're not already showing one then prep for last frame
			if (csPtr->m_requiresLoadingScreen && csPtr->m_loadingScreenContext == LOADINGSCREEN_CONTEXT_LAST_FRAME &&
				gRenderThreadInterface.IsUsingDefaultRenderFunction() && !CLoadingScreens::AreActive() REPLAY_ONLY(&& !CVideoEditorPlayback::IsLoading()))
			{
				m_pendingActions.PushAndGrow(SPendingCSAction(content->GetNameHash(), changeSetGroup, changeSetName, flags, 0, true, true));

				if (!m_waitingForBBCopy)
				{
					CPauseMenu::TogglePauseRenderPhases(false, OWNER_LOADING_SCR, __FUNCTION__ );
					m_waitingForBBCopy = true;
				}

				if (info)
					info->m_state = sOverlayInfo::INACTIVE;

				return;
			}
		}
	}
#endif

	if(flags & ECCS_FLAG_USE_LOADING_SCREEN)
	{
		// Check to see if we should trigger the loading screen
		if (const CDataFileMgr::ContentChangeSet* csPtr = DATAFILEMGR.GetContentChangeSet(changeSetName))
		{
			if (csPtr->m_requiresLoadingScreen)
				TriggerLoadingScreen(csPtr->m_loadingScreenContext);
		}
	}
	content->ExecuteContentChangeSet(changeSetGroup,changeSetName);

	if (flags & ECCS_FLAG_MAP_CLEANUP)
	{
		CMountableContent::CleanupAfterMapChange();
	}

	BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = tempValue;)
}
void CExtraContentManager::RevertContentChangeSetInternal(
	CMountableContent* content, atHashString changeSetGroup, atHashString changeSetName, u32 actionMask, u32 flags DURANGO_ONLY(, bool addPending/*=true*/))
{
	BANK_ONLY(bool tempValue = strStreamingInfoManager::ms_bValidDlcOverlay;)
	sOverlayInfo * info = NULL;
	if(flags & ECCS_FLAG_USE_LATEST_VERSION)
	{
		atHashString versionedChange = GetVersionedChangeSetName(m_overlayInfo,changeSetName);
		Displayf("Reverting changeset %s in group %s for %s",changeSetName.TryGetCStr(),changeSetGroup.TryGetCStr(),content->GetName());
		if(versionedChange != 0)
		{
			Displayf("This is a versioned changeset: %s",versionedChange.TryGetCStr());
			info = GetHighestOverlayInfoForVersionedChange(m_overlayInfo,versionedChange);
			if(info->m_state == sOverlayInfo::ACTIVE)
			{
				content = GetContentByHash(info->m_content);
				changeSetName = info->m_changeSet;
				changeSetGroup = info->m_changeSetGroupToExecuteWith;
				BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = true;)
					info->m_state = sOverlayInfo::INACTIVE;
				Displayf("Reverting highest version of the changeset %s in group %s in %s",changeSetName.TryGetCStr(),changeSetGroup.TryGetCStr(),content->GetName());
			}
			else
			{
				//already reverted
				return;
			}
		}
		else if(changeSetGroup == CCS_GROUP_MAP || changeSetGroup == CCS_GROUP_MAP_SP)
		{
			Assertf(0, "Map changesets should be versioned, Content: %s", content->GetName());
#if !__NO_OUTPUT
			Quitf("Map changesets should be versioned, Content: %s", content->GetName());
#endif // !__NO_OUTPUT
		}
	}

#if RSG_DURANGO
	// Because XB1 doesn't have a persistent back buffer, we need to wait for it to copy out of ESRAM
	if (addPending && (changeSetGroup == CCS_GROUP_MAP || changeSetGroup == CCS_GROUP_MAP_SP))
	{
		if (const CDataFileMgr::ContentChangeSet* csPtr = DATAFILEMGR.GetContentChangeSet(changeSetName))
		{
			// If we need a loading screen and we're not already showing one then prep for last frame
			if (csPtr->m_requiresLoadingScreen && csPtr->m_loadingScreenContext == LOADINGSCREEN_CONTEXT_LAST_FRAME &&
				gRenderThreadInterface.IsUsingDefaultRenderFunction() && !CLoadingScreens::AreActive() REPLAY_ONLY(&& !CVideoEditorPlayback::IsLoading()))
			{
				m_pendingActions.PushAndGrow(SPendingCSAction(content->GetNameHash(), changeSetGroup, changeSetName, flags, actionMask, false, true));

				if (!m_waitingForBBCopy)
				{
					CPauseMenu::TogglePauseRenderPhases(false, OWNER_LOADING_SCR, __FUNCTION__ );
					m_waitingForBBCopy = true;
				}

				if (info)
					info->m_state = sOverlayInfo::ACTIVE;

				return;
			}
		}
	}
#endif

	if(flags & ECCS_FLAG_USE_LOADING_SCREEN)
	{
		if ((changeSetGroup == CCS_GROUP_MAP) || (changeSetGroup == CCS_GROUP_MAP_SP))
		{
			audNorthAudioEngine::PurgeInteriors();
		}

		// Check to see if we should trigger the loading screen
		if (const CDataFileMgr::ContentChangeSet* csPtr = DATAFILEMGR.GetContentChangeSet(changeSetName))
		{
			if (csPtr->m_requiresLoadingScreen)
				TriggerLoadingScreen(csPtr->m_loadingScreenContext);
		}
	}

	content->RevertContentChangeSet(changeSetGroup,changeSetName,actionMask);

	BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = tempValue; )
}

bool CExtraContentManager::AreAnyCCSPending()
{
	// If we have pending actions, or if we're still showing a loading screen for some actions, we're pending
	return DURANGO_ONLY(m_pendingActions.GetCount() > 0 || ) (m_loadingScreenState.m_displayed && CLoadingScreens::AreActive());
}

atHashString CExtraContentManager::GetVersionedChangeSetName(atArray<sOverlayInfo*>& overlayInfos, atHashString changeSetName)
{	
	for(int i=0;i<overlayInfos.GetCount();i++)
	{
		if(changeSetName == overlayInfos[i]->m_changeSet)
		{
			return overlayInfos[i]->m_nameId;
		}
	}
	return atHashString::Null();
}
sOverlayInfo* CExtraContentManager::GetHighestOverlayInfoForVersionedChange(atArray<sOverlayInfo*>& overlayInfos, atHashString versionedChange)
{
	sOverlayInfo* retval = NULL;
	for(int i=0;i<overlayInfos.GetCount();i++)
	{
		if(overlayInfos[i]->m_nameId == versionedChange)
		{
			if(retval)
			{
				if(overlayInfos[i]->m_version > retval->m_version)
				{
					retval = overlayInfos[i];					
				}
			}
			else
			{
				retval = overlayInfos[i];
			}
		}
	}
	return retval;
}
void CExtraContentManager::ExecutePendingOverlays()
{
	BANK_ONLY(bool tempValue = strStreamingInfoManager::ms_bValidDlcOverlay;)
	BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = true;)
	for(int i=0;i<m_overlayInfo.GetCount();i++)
	{
		sOverlayInfo*& overlayInfo = m_overlayInfo[i];
		CMountableContent* content = GetContentByHash(overlayInfo->m_content);
		if(Verifyf(content,"Content not found"))
		{
			if(overlayInfo->m_changeSetGroupToExecuteWith == CCS_GROUP_ON_DEMAND)
			{
				if(overlayInfo->m_state==sOverlayInfo::WILL_ACTIVATE )
				{
					Assertf(content, "Mountable content doesn't exist!");
					dlcDisplayf("[%s]Activating %s : %s VERSION: %d", content->GetName(),overlayInfo->m_changeSet.TryGetCStr(),overlayInfo->m_nameId.TryGetCStr(), overlayInfo->m_version);
					if(overlayInfo->m_content)
					{
						ExecuteContentChangeSet(content->GetNameHash(),(u32)CCS_GROUP_ON_DEMAND,overlayInfo->m_changeSet);
						overlayInfo->m_state = sOverlayInfo::ACTIVE;
					}
				}
			#if __BANK
				else
				{
					dlcDisplayf("Changesets execution suppressed for %s in content %s, a higher version is available/executed ",overlayInfo->m_nameId.TryGetCStr(), content->GetName() );
				}
			#endif // __BANK
			}
		}
	}
	BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = tempValue;)
}
#if EC_CLOUD_MANIFEST
CExtraContentCloudListener::CExtraContentCloudListener()
	: m_manifestReqID(INVALID_CLOUD_REQUEST_ID)
	//, m_bParsingCloud(false)
{	
}
CExtraContentCloudListener::~CExtraContentCloudListener()
{
	CloudManager::GetInstance().RemoveListener(this);
}

void CExtraContentCloudListener::Init()
{
	//CloudManager::GetInstance().AddListener(this);
}

void CExtraContentCloudListener::Shutdown()
{
	//CloudManager::GetInstance().RemoveListener(this);
}

bool CExtraContentCloudListener::DoManifestRequest()
{
	if(CloudManager::GetInstance().IsRequestActive(m_manifestReqID))
	{
		return false;
	}
	dlcDebugf3("DLC: Requesting cloud manifest");

	const char *manifestPath;
	if(sysAppContent::IsJapaneseBuild())
		manifestPath = "extraContent/ExtraContentManifest_jpn.xml";
	else // all other regions
		manifestPath = "extraContent/ExtraContentManifest.xml";

	EXTRACONTENT.SetCloudManifestState(CLOUDFILESTATE_CACHING);
	m_manifestReqID = CloudManager::GetInstance().RequestGetTitleFile(manifestPath,10,eRequest_CacheAddAndEncrypt|eRequest_AlwaysQueue|eRequest_Critical);
	return true;
}

void CExtraContentManager::BeginEnumerateCloudContent()
{
	// We'll first check the master file that knows about all available content.
	m_cloudTimedOut = false;
#if !__FINAL
	if(PARAM_extracloudmanifest.Get())
		LoadCloudManifestFromCommandLine();
	else
		if(!PARAM_disableExtraCloudContent.Get())
#endif // !__FINAL
		{
			m_cloudListener.DoManifestRequest();
		}

		return;
}

void CExtraContentManager::UpdateCloudStorage()
{
	if (!NetworkInterface::IsCloudAvailable())
	{
		//if the cloud isn't available clear the TRANSFER_ERROR state. lavalley / bektas
		if (m_cloudData.m_CloudManifest.m_State == CLOUDFILESTATE_TRANSFER_ERROR)
		{
			m_cloudData.m_CloudManifest.m_State = CLOUDFILESTATE_NOT_FOUND;
		}

		return;
	}

#if !__FINAL
	if(!PARAM_disableExtraCloudContent.Get())
#endif
	{
		if(m_cloudTimedOut)
		{	
			if(m_cloudListener.DoManifestRequest())
			{
				m_cloudTimedOut = false;
			}
		}
	}

	// TODO: Move somewhere else.
	UpdateCloudCacher();
}

#if !__FINAL

void CExtraContentManager::DebugDumpCloudManifestContents()
{
	// Let's see what files there are.

	for (int i = 0; i < m_cloudData.m_CloudManifest.m_Files.GetCount(); i++)
	{
		dlcDisplayf("ExtraContent CloudManifest: FILE: %s", m_cloudData.m_CloudManifest.m_Files[i].m_File.c_str());
	}

	for (int i = 0; i < m_cloudData.m_CloudManifest.m_CompatibilityPacks.GetCount(); i++)
	{
		const CExtraContentCloudPackDescriptor &packDesc = m_cloudData.m_CloudManifest.m_CompatibilityPacks[i];
		dlcDisplayf("ExtraContent CloudManifest: Compatibility pack: '%s', product id: '%s'", packDesc.m_Name.c_str(), packDesc.m_ProductId.c_str());
	}

	for (int i = 0; i < m_cloudData.m_CloudManifest.m_PaidPacks.GetCount(); i++)
	{
		const CExtraContentCloudPackDescriptor &packDesc = m_cloudData.m_CloudManifest.m_PaidPacks[i];
		dlcDisplayf("ExtraContent CloudManifest: Paid pack: '%s', product id: '%s'", packDesc.m_Name.c_str(), packDesc.m_ProductId.c_str());
	}
}

#endif // !__FINAL

void CExtraContentManager::UpdateCloudCacher()
{
	if (!NetworkInterface::IsCloudAvailable())
		return;
	if (Verifyf(fiCachePartition::IsAvailable(), "No cache partition available - we will not be able to use data from the cloud"))
	{
		for (int x=0; x < m_cloudData.m_CloudManifest.m_Files.GetCount(); x++)
		{
			CCloudStorageFile &file = m_cloudData.m_CloudManifest.m_Files[x];
			switch (file.m_State)
			{
			case CLOUDFILESTATE_UNCACHED:
				// If there's nothing else going on, let's start a new transfer.
				if (m_cloudData.m_CloudTransfers == 0)
				{
					dlcDebugf1("Beginning cloud transfer of %s to the cache partition", file.m_File.c_str());

					// Create our cache directory, in case it doesn't exist already.
					char targetDirectory[RAGE_MAX_PATH];
					formatf(targetDirectory, "%stitleStorage", fiCachePartition::GetCachePrefix());

					const fiDevice *targetDevice = fiDevice::GetDevice(targetDirectory, false);

					if (Verifyf(targetDevice, "Can't find device to manage cache partition at %s", targetDirectory))
					{
						// Create the directory - don't check the return value, the directory is likely to exist already.
						targetDevice->MakeDirectory(targetDirectory);

						// Create the target file.
						char targetFilename[RAGE_MAX_PATH];
						formatf(targetFilename, "%s/%s.rpf", targetDirectory, file.m_File.c_str());

						file.m_LocalPath = targetFilename;

						char titlePath[RAGE_MAX_PATH];
#if !__FINAL
						formatf(titlePath, "extraContent/%s%s.rpf", file.m_File.c_str(), !PARAM_useDebugCloudPatch.Get()? "" :file.m_File!=m_cloudData.m_CloudManifest.m_ScriptPatchName ? "" : "_DBG");
#else // !__FINAL
						formatf(titlePath, "extraContent/%s.rpf", file.m_File.c_str());
#endif

						m_cloudData.m_CloudTransferHandle = targetDevice->Create(targetFilename);

						if (Verifyf(fiIsValidHandle(m_cloudData.m_CloudTransferHandle), "Cannot create cached file %s", targetFilename))
						{
							dlcDebugf1("Begin transfer to %s", targetFilename);

							if (Verifyf(rlCloud::GetTitleFile(titlePath,
								RLROS_SECURITY_DEFAULT,
								0,  //ifModifiedSincePosixTime
								targetDevice,
								m_cloudData.m_CloudTransferHandle,
								NULL,   //rlCloudFileInfo
								NULL,   //allocator
								&m_cloudData.m_CloudTransferStatus),
								"Error requesting cloud transfer from %s to %s", titlePath, targetFilename))
							{
								m_cloudData.m_CloudTransfers++;
								file.m_State = CLOUDFILESTATE_CACHING;
								file.m_lastRequestTime = fwTimer::GetSystemTimeInMilliseconds();
							}
							else
							{
								targetDevice->Close(m_cloudData.m_CloudTransferHandle);
								file.m_State =CLOUDFILESTATE_TRANSFER_ERROR;
							}
						}
					}
				}
				break;

			case CLOUDFILESTATE_CACHING:
				// Are we done yet?
				{
					netStatus::StatusCode status = m_cloudData.m_CloudTransferStatus.GetStatus();
					if (status != netStatus::NET_STATUS_PENDING && status != netStatus::NET_STATUS_NONE)
					{
						dlcDebugf1("Cloud transfer for %s finished, result=%d", file.m_File.c_str(), status);

						// We are.
						m_cloudData.m_CloudTransfers--;

						const fiDevice *device = fiDevice::GetDevice(file.m_LocalPath.c_str(), false);
						device->Close(m_cloudData.m_CloudTransferHandle);

						if (status == netStatus::NET_STATUS_SUCCEEDED)
						{
							// And it worked!
							file.m_State = CLOUDFILESTATE_CACHED;

							// Next up - mount it and make it available to the game.
							file.m_Packfile = rage_new fiPackfile();

							if (Verifyf(file.m_MountPoint.c_str(), "Cloud DLC %s doesn't specify a mount point in the manifest file", file.m_File.c_str()))
							{
								if (Verifyf(file.m_Packfile->Init(file.m_LocalPath.c_str(), true, fiPackfile::CACHE_NONE), "Error mounting cloud RPF file %s", file.m_LocalPath.c_str()))
								{
									if (Verifyf(file.m_Packfile->MountAs(file.m_MountPoint.c_str()), "Error mounting %s to %s", file.m_LocalPath.c_str(), file.m_MountPoint.c_str()))
									{
										if (!IsContentFilenamePresent(file.m_MountPoint.c_str()))
										{
											// Now add it to our official list of DLC.
											CMountableContent mount;

											mount.SetFilename(file.m_MountPoint.c_str());
											mount.SetNameHash(file.m_File.c_str());
											mount.SetPrimaryDeviceType(CMountableContent::DT_FOLDER);

											AddContent(mount);
											LoadContent(false, false);
										}
									}
								}
							}
						}
						else
						{
							// An error occurred.
							int result = m_cloudData.m_CloudTransferStatus.GetResultCode();
							if(result == 404)
							{
								dlcWarningf("Could not find the file on the cloud : %s", file.m_File.c_str());
								file.m_State = CLOUDFILESTATE_NOT_FOUND;
							}
							else
							{
								dlcWarningf("ERROR: %d , Could not download %s from the cloud - giving up",result, file.m_File.c_str());
								file.m_State = CLOUDFILESTATE_TRANSFER_ERROR;
							}
						}
					}
				}
				break;

			case CLOUDFILESTATE_CACHED:
				// It's cached. That's cool.
				break;

			case CLOUDFILESTATE_NOT_FOUND:
				break;
			case CLOUDFILESTATE_TRANSFER_ERROR:
				// We couldn't download this file. Let's not try again.
				// TODO: We could have some retry logic here, where we first try again after a few weconds, then after a few minutes, or
				// whatever. But for now, let's sit still.
				for (int x=0; x < m_cloudData.m_CloudManifest.m_Files.GetCount(); x++)
				{
					CCloudStorageFile& file = m_cloudData.m_CloudManifest.m_Files[x];
					if(fwTimer::GetSystemTimeInMilliseconds() - file.m_lastRequestTime > 15000)
					{
						file.m_State = CLOUDFILESTATE_UNCACHED;
					}
				}
				break;
			}
		}
	}
}

void CExtraContentManager::OnReceivedExtraContentManifest()
{

}

void CExtraContentCloudListener::OnCloudEvent( const CloudEvent* pEvent )
{	
	if(!pEvent)
	{
		return;
	}
	switch(pEvent->GetType())
	{
		case CloudEvent::EVENT_REQUEST_FINISHED:
		{
			const CloudEvent::sRequestFinishedEvent* pEventData = pEvent->GetRequestFinishedData();
			if( !pEventData )
			{
				return;
			}

			if(pEventData->nRequestID == m_manifestReqID)
			{
				// clear the manifest ID
				m_manifestReqID = INVALID_CLOUD_REQUEST_ID;

				if(pEventData->bDidSucceed)
				{
					if(pEventData->pData)
					{
						//handle manifest
						dlcDebugf3("Extra Content manifest successfully downloaded!");
						const void* const & data = pEventData->pData;
						u32 bSize = pEventData->nDataSize;
						Displayf("%s loaded ", pEventData->szFileName);
						BANK_ONLY(if(!PARAM_extracloudmanifest.Get()))
							EXTRACONTENT.SetCloudManifest(data,bSize);
					}
					else
					{
						Displayf("MANIFEST REQUEST SUCCEEDED - No data!");
						EXTRACONTENT.SetCloudManifestState(CLOUDFILESTATE_TRANSFER_ERROR);
					}
				}
				else
				{
					Displayf("MANIFEST REQUEST UNSUCCESSFUL, Result code :%d", pEventData->nResultCode);

					//handle manifest
					if(pEventData->nResultCode == 404)
					{
						Displayf("Manifest state set to CLOUDFILESTATE_NOT_FOUND");
						EXTRACONTENT.SetCloudManifestState(CLOUDFILESTATE_NOT_FOUND);
					}
					else
					{
						Displayf("Manifest state set to CLOUDFILESTATE_TRANSFER_ERROR");
						EXTRACONTENT.SetCloudManifestState(CLOUDFILESTATE_TRANSFER_ERROR);
					}
				}
			}
		}
		break;
		case CloudEvent::EVENT_AVAILABILITY_CHANGED:
		{
			const CloudEvent::sAvailabilityChangedEvent* pEventData = pEvent->GetAvailabilityChangedData();

			// if we now have cloud and don't have the cloud file, request it
			if(pEventData->bIsAvailable BANK_ONLY(&& !PARAM_extracloudmanifest.Get()))
			{
				dlcDebugf1("Availability Changed - Requesting Cloud File");
				DoManifestRequest(); 
			}
		}
		break;
	}
}


bool CExtraContentManager::LoadCloudManifestFromCommandLine()
{
	if(PARAM_extracloudmanifest.Get())
	{	
		const char* pPath=NULL;
		PARAM_extracloudmanifest.Get(pPath);

		if(PARSER.LoadObject(pPath, "xml", m_cloudData.m_CloudManifest))
		{
			m_cloudData.m_CloudManifest.m_State = CLOUDFILESTATE_CACHED ;
#if !__FINAL
			DebugDumpCloudManifestContents();
#endif
			return true;
		}
		else
		{
			Errorf("Can't load %s manifest specified in -extracloudmanifest", pPath);
			m_cloudData.m_CloudManifest.m_State = CLOUDFILESTATE_NOT_FOUND;
			return false;
		}
	}
	return false;
}

bool CExtraContentManager::HasCloudContentSuccessfullyLoaded()
{
#if __BANK
	if(EXTRACONTENT.BANK_EnableCloudWidgetsOverride())
	{
		return EXTRACONTENT.BANK_GetForceCloudFileState();
	}
#endif // __BANK

#if !__FINAL	
	if(!PARAM_disableExtraCloudContent.Get())
#endif
	{
		if(m_cloudData.m_CloudManifest.m_State == CLOUDFILESTATE_NOT_FOUND )
		{
			return true;
		}
		if(m_cloudData.m_CloudManifest.m_State == CLOUDFILESTATE_CACHED)
		{
			for(int i=0; i<m_cloudData.m_CloudManifest.m_Files.GetCount();++i)
			{
				eCloudFileState& fileState = m_cloudData.m_CloudManifest.m_Files[i].m_State;
				Displayf("File state: %d",fileState);
				//Handle transfer error?
				if(fileState!=CLOUDFILESTATE_CACHED&&fileState!=CLOUDFILESTATE_NOT_FOUND)
					return false;
			}
		}
		else
		{	
			return false;
		}
	}
	return true;
}

eDlcCloudResult CExtraContentManager::GetCloudFileState(eCloudFileState state)
{
	switch (state)
	{
	case CLOUDFILESTATE_CACHED:
	case CLOUDFILESTATE_NOT_FOUND:
		return DLCRESULT_OK;
		break;
	case CLOUDFILESTATE_CACHING:
	case CLOUDFILESTATE_UNCACHED:
		return DLCRESULT_NOT_READY;
		break;
	case CLOUDFILESTATE_TRANSFER_ERROR:
		return DLCRESULT_CONNECTION_ERROR;			
		break;
	default:
		return DLCRESULT_OK;
	}
}

eDlcCloudResult CExtraContentManager::GetCloudContentResult()
{
	eDlcCloudResult state = DLCRESULT_OK; 
#if !__FINAL
	if(!PARAM_disableExtraCloudContent.Get())
#endif //!__FINAL
	{
		eCloudFileState& manifestState = m_cloudData.m_CloudManifest.m_State;
		if(m_cloudTimedOut)
			return DLCRESULT_TIMEOUT;
		state = GetCloudFileState(manifestState);
		if(state==DLCRESULT_OK)
		{
			for(int i=0;i<m_cloudData.m_CloudManifest.m_Files.GetCount();i++)
			{
				eDlcCloudResult fileState = GetCloudFileState(m_cloudData.m_CloudManifest.m_Files[i].m_State);
				if(fileState != DLCRESULT_OK)
				{
					return fileState;
				}
			}
		}	
	}
	return state;
}

bool CExtraContentManager::GetCloudContentState(int &bTimedOut, int uWaitDuration)
{
	u32 currentTime = sysTimer::GetSystemMsTime();
	bTimedOut = (int)false;
	bool result = GetCloudContentRequestsFinished();
	if(result)
	{
		dlcDebugf1("EXTRACONTENTMANIFEST: State request finished in %d ms", m_cloudStatusTimer);
		m_cloudStatusTimer = 0;
		return result;
	}
	if(m_cloudStatusTimer == 0)
	{
		m_cloudStatusTimer = currentTime;
		dlcDebugf1("EXTRACONTENTMANIFEST: Requesting state");
	}
	if(currentTime - m_cloudStatusTimer > (uWaitDuration > 0 ? uWaitDuration : 30000)  )
	{
		m_cloudStatusTimer = 0;
		bTimedOut = (int)true;
		m_cloudTimedOut = true;
		dlcDebugf1("EXTRACONTENTMANIFEST: Request timed out");
	}

	return result;
}

void CExtraContentManager::EnsureManifestLoaded()
{
	if(GetCloudContentResult()==DLCRESULT_CONNECTION_ERROR)
	{
		m_cloudListener.DoManifestRequest();
	}
}

bool CExtraContentManager::GetCloudContentRequestsFinished()
{
#if __BANK
	if(BANK_EnableCloudWidgetsOverride())
	{
		return BANK_GetForceManifestFileState();
	}
#endif //__BANK
#if !__FINAL
	if(!PARAM_disableExtraCloudContent.Get())
#endif //!__FINAL
	{
		eDlcCloudResult currentState = GetCloudFileState(m_cloudData.m_CloudManifest.m_State);
		if (currentState == DLCRESULT_NOT_READY)
		{
			Displayf("Manifest file not ready!");
			return false;
		}
		for (int x=0; x < m_cloudData.m_CloudManifest.m_Files.GetCount(); x++)
		{
			CCloudStorageFile &file = m_cloudData.m_CloudManifest.m_Files[x];
			currentState = GetCloudFileState(file.m_State);
			Displayf("File state: %d", file.m_State );
			if(currentState == DLCRESULT_NOT_READY)
			{
				Displayf("File not ready! %s",file.m_File.c_str());
				return false;
			}
		}	
	}
	return true;
}

void CExtraContentManager::SetCloudManifestState(eCloudFileState state)
{
	m_cloudData.m_CloudManifest.m_State = state;
}
void CExtraContentManager::SetCloudManifest(const void* const & data, u32 size)
{
	char fileName[RAGE_MAX_PATH];
	fiDevice::MakeMemoryFileName(fileName, sizeof(fileName), data , size, false, "CloudExtraContent");

	if(PARSER.LoadObject(fileName, "", m_cloudData.m_CloudManifest) )
	{
#if !__FINAL
		DebugDumpCloudManifestContents();
#endif
		m_cloudData.m_CloudManifest.m_State = CLOUDFILESTATE_CACHED;
		//RequestCloudFiles();
	}
	else
    {
		dlcDebugf3("Can't load manifest!");

        // match the state change for the command line version
        m_cloudData.m_CloudManifest.m_State = CLOUDFILESTATE_NOT_FOUND;
    }

}
#endif // EC_DO_MANIFEST_CHECKS

void CExtraContentManager::InitialiseSpecialTriggers(void)
{
	m_specialTriggerTunablesTested = false;
	m_specialTriggers = 0;

#if !__FINAL
	if (PARAM_christmas.Get()) 
	{ 
		SetSpecialTrigger(ST_XMAS, true); 
	}
#endif //!__FINAL
}

void CExtraContentManager::UpdateSpecialTriggers(void)
{
	if (m_specialTriggerTunablesTested == false)
	{
		if (Tunables::GetInstance().HasCloudRequestFinished())
		{
			if (!GetSpecialTrigger(ST_XMAS))
			{
				bool bValue =	Tunables::GetInstance().TryAccess(BASE_GLOBALS_HASH, ATSTRINGHASH("TURN_SNOW_ON_OFF", 0xbba8f5b0), false) ||
								Tunables::GetInstance().TryAccess(MP_GLOBAL_HASH, ATSTRINGHASH("TURN_SNOW_ON_OFF", 0xbba8f5b0), false);
				SetSpecialTrigger(ST_XMAS, bValue);
			}

			m_specialTriggerTunablesTested = true;
		}
	}
}

bool CExtraContentManager::GenericChangesetChecks(atHashString condition)
{
	if(atHashString("jpn_build",0xBE885BB1)==condition)
	{
		return sysAppContent::IsJapaneseBuild();
	}
	return true;
}

bool CExtraContentManager::LevelChecks(atHashString condition)
{
    return CScene::GetCurrentLevelNameHash()==condition;

}



void CExtraContentManager::Init(u32 initMode)
{
#if EC_CLOUD_MANIFEST
	m_cloudStatusTimer = 0;
	m_cloudTimedOut = false;
#endif // EC_DO_MANIFEST_CHECKS
	m_everHadBadPackOrder = false;
	m_loadingScreenState.Reset();

#if !__FINAL
	m_enumerateCommandLines = true;
#endif

#if GTA_REPLAY
	ResetReplayState();
#endif // GTA_REPLAY

	if (initMode == INIT_CORE)
	{
#if RSG_DURANGO
		m_waitingForBBCopy = false;
		InitDurangoContent();
#endif

	#if RSG_ORBIS
		Assert(sysAppContent::IsInitialized());

		if (s_contentPollThread == sysIpcThreadIdInvalid)
			s_contentPollThread = sysIpcCreateThread(&CExtraContentManager::ContentPoll, NULL, sysIpcMinThreadStackSize, PRIO_LOWEST, "OrbisAddContPoll");
	#endif

#if RSG_PS3 || RSG_XENON
        if (s_codeCheckThread == sysIpcThreadIdInvalid)
			s_codeCheckThread = sysIpcCreateThread(&CExtraContentManager::CodeCheck, NULL, sysIpcMinThreadStackSize, PRIO_LOWEST, "CodeCheck", 4, "CodeCheck");
#endif

		m_overlayInfo.Reset();
#if EC_CLOUD_MANIFEST
		m_cloudListener.Init();
		m_cloudData.m_CloudManifestFileRequest = NULL;
		m_cloudData.m_CloudTransfers = 0;
#endif // EC_DO_MANIFEST_CHECKS
		m_currentGamerId.Clear();
		m_localGamerIndex = -1;
		m_enumerating = false;
		m_enumerateOnUpdate = false;
		m_currMapChangeState = MCS_NONE;
		m_allowMapChangeAnyTime = true;
		atDelegate<bool (atHashString)>* defaultChecks = rage_new atDelegate<bool (atHashString)>();
		defaultChecks->Bind(this,&CExtraContentManager::GenericChangesetChecks);
		atDelegate<bool (atHashString)>* levelChecks = rage_new atDelegate<bool (atHashString)>();
		levelChecks->Bind(this,&CExtraContentManager::LevelChecks);
		CMountableContent::RegisterExecutionCheck(defaultChecks,atHashString("build",0x59859136));
		CMountableContent::RegisterExecutionCheck(levelChecks,atHashString("level",0xF66D3B99));
		fiCachePartition::Init();
		CDownloadableTextureManager::InitClass();

	#if __XENON
		m_deviceEnumerating = false;
		fiDeviceXContent::InitClass();
		fiDeviceXContent::SetDeviceChangeCallback(MakeFunctor(*this, &CExtraContentManager::DeviceChanged));
		m_downloadContentListener = XNotifyCreateListener(XNOTIFY_LIVE); // create listener to check for content being installed
	#endif

		rlPresence::AddDelegate(&g_PresenceDlgt);

		g_addContentServiceDelegate.Bind(&OnServiceEvent);
		g_SysService.AddDelegate(&g_addContentServiceDelegate);

// CDataFileMount::RegisterMountInterface doesn't make a copy of the CDataFileMountInterface that is passed to it. It only stores a pointer to the CDataFileMountInterface in an array.
//	It also modifies m_registerUnload within the CDataFileMountInterface.
//	For that reason, if you want to call RegisterMountInterface with registerUnload=false then use g_extraContentFileMounter.
//	If you want to call RegisterMountInterface with registerUnload=true then use g_extraContentFileMounterToRegisterUnload.
		CDataFileMount::RegisterMountInterface(CDataFileMgr::CONTENT_UNLOCKING_META_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::OVERLAY_INFO_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::CLIP_SETS_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::EXPRESSION_SETS_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::FACIAL_CLIPSET_GROUPS_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::NM_BLEND_OUT_SETS_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::NM_TUNING_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::EXTRA_TITLE_UPDATE_DATA, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::VEHICLE_SHOP_DLC_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::LOADOUTS_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::LEVEL_STREAMING_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::WORLD_HEIGHTMAP_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::WATER_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::CARCOLS_GEN9_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::CARMODCOLS_GEN9_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::GEN9_EXCLUSIVE_ASSETS_VEHICLES_FILE, &g_extraContentFileMounter);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::GEN9_EXCLUSIVE_ASSETS_PEDS_FILE, &g_extraContentFileMounter);

		CDataFileMount::RegisterMountInterface(CDataFileMgr::GTXD_PARENTING_DATA, &g_extraContentFileMounter);

		CDataFileMount::RegisterMountInterface(CDataFileMgr::MOVE_NETWORK_DEFS, &g_extraContentFileMounterToRegisterUnload, eDFMI_UnloadFirst);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::TEXTFILE_METAFILE, &g_extraContentFileMounterToRegisterUnload, eDFMI_UnloadFirst);
		CDataFileMount::RegisterMountInterface(CDataFileMgr::EXTRA_FOLDER_MOUNT_DATA, &g_extraContentFileMounterToRegisterUnload, eDFMI_UnloadFirst);

		if(fiDevice::GetDevice(TITLE_UPDATE_MOUNT_PATH))
		{
			CMountableContent mount;
			mount.SetFilename("update:/");
			mount.SetNameHash(TITLE_UPDATE_PACK_NAME);
			mount.SetPrimaryDeviceType(CMountableContent::DT_FOLDER);
			mount.SetPermanent(true);
			mount.SetDatFileLoaded(true);
			AddContent(mount);
			DATAFILEMGR.SetEnableFilePatching(true);
			LoadContent(false);
			DATAFILEMGR.SetEnableFilePatching(false);
		}


		InitialiseSpecialTriggers();

	#if __BANK
		InitBank();
	#endif
	}
	else if (initMode == INIT_SESSION)
	{
		ExecuteTitleUpdateDataPatch((u32)CCS_TITLE_UPDATE_DLC_METADATA, true);
		ExecuteTitleUpdateDataPatchGroup((u32)CCS_GROUP_UPDATE_DLC_METADATA, true);
		// When we have loaded the session, process any enumeration requests...
		ProcessEnumerationRequests(true);

		// Hack...force load the 'mph4_gtxd' for the island heist. Later cl should remove this
		// and use the content type tag in content.xml to solve this better
		CMapFileMgr::GetInstance().LoadGlobalTxdParents("mph4_gtxd");

	#if __BANK
		fragTuneStruct::PreSaveEntityFunctor preSaveEntityFunctor;
		preSaveEntityFunctor.Bind(&CExtraContentManager::ReturnAssetPathForFragTuneCB);
		FRAGTUNE->SetPreSaveEntityFunctor(preSaveEntityFunctor);
	#endif // __BANK

	#if !__FINAL
		const char* dlcPacks;
		if (PARAM_buildDLCCacheData.Get(dlcPacks))
		{
			char buffer[RAGE_MAX_PATH] = {0}; 
			strcpy(buffer,dlcPacks);
			char* cur = strtok(buffer,";");
			while(cur)
			{
				atHashString contentHash(cur);
				CMountableContent* content = GetContentByHash(contentHash);
				if(content)
				{
					ExecuteContentChangeSetGroup(contentHash,(u32)CCS_GROUP_MAP_SP);
					RevertContentChangeSetGroup(contentHash,(u32)CCS_GROUP_MAP_SP);

					ExecuteContentChangeSetGroup(contentHash,(u32)CCS_GROUP_MAP);
					RevertContentChangeSetGroup(contentHash,(u32)CCS_GROUP_MAP);
				}
				cur = strtok(NULL,";");
			}			
		}
	#endif

		if (PARAM_loadMapDLCOnStart.Get())
		{
			ExecuteContentChangeSetGroupForAll((u32)CCS_GROUP_MAP_SP);
			ExecuteContentChangeSetGroupForAll((u32)CCS_GROUP_MAP);
		}
		else
		{
			if (!CNetwork::HasMatchStarted())
				ExecuteContentChangeSetGroupForAll((u32)CCS_GROUP_MAP_SP);
		}
	#if __BANK
		const char* ccsToEnable;
		if(PARAM_enableMapCCS.Get(ccsToEnable))
		{
			char buffer[RAGE_MAX_PATH] = {0}; 
			strcpy(buffer,ccsToEnable);
			char* cur = strtok(buffer,";");
			while(cur)
			{
				atHashString contentHash(cur);
				CMountableContent* content = GetContentByHash(contentHash);
				if(content)
					ExecuteContentChangeSetGroup(contentHash,(u32)CCS_GROUP_MAP);
				cur = strtok(NULL,";");
			}			
		}

		if (PARAM_enableSPMapCCS.Get(ccsToEnable))
		{
			char buffer[RAGE_MAX_PATH] = {0}; 
			strcpy(buffer,ccsToEnable);
			char* cur = strtok(buffer,";");
			while(cur)
			{
				atHashString contentHash(cur);
				CMountableContent* content = GetContentByHash(contentHash);
				if(content)
					ExecuteContentChangeSetGroup(contentHash,(u32)CCS_GROUP_MAP_SP);
				cur = strtok(NULL,";");
			}			
		}
	#endif // __BANK
		ExecuteTitleUpdateDataPatchGroup(CCS_GROUP_POST_DLC_PATCH,true);

		CModelIndex::MatchAllModelStrings();
	}
}
void CExtraContentManager::LoadLevelPacks()
{
	DATAFILEMGR.SetEnableFilePatching(true);
	BeginEnumerateContent(true,true);
	RemountUpdate();
	DATAFILEMGR.SetEnableFilePatching(false);
}
void CExtraContentManager::Shutdown(u32 shutdownMode)
{
#if EC_CLOUD_MANIFEST
	m_cloudStatusTimer = 0;
	m_cloudTimedOut = false;
#endif // EC_DO_MANIFEST_CHECKS
	CancelEnumerateContent(true);
	pgStreamer::Drain();
	strStreamingEngine::GetLoader().Flush();
	gRenderThreadInterface.Flush();
	strStreamingEngine::GetInfo().FlushLoadedList(STR_DONTDELETE_MASK);

	for(u32 i=0;i<m_overlayInfo.GetCount();i++)
	{
		delete m_overlayInfo[i];
		m_overlayInfo[i] = NULL;
	}
	m_overlayInfo.Reset();
	for (u32 i = 0; i < m_content.GetCount(); i++)
	{
		if (!m_content[i].GetIsPermanent())
		{
			m_content[i].ShutdownContent();
			m_content.Delete(i);
			i--;
		}
	}
	m_contentLocks.Reset();
	m_corruptedContent.Reset();
#if RSG_ORBIS
	m_ps4DupeEntitlements.Reset();
#endif
#if RSG_DURANGO
	ShutdownDurangoContent();
#endif
	CPlayerSpecialAbilityManager::UpdateDlcMultipliers();

	if (shutdownMode == SHUTDOWN_CORE)
	{
		g_SysService.RemoveDelegate(&g_addContentServiceDelegate);
		g_addContentServiceDelegate.Reset();

		CDownloadableTextureManager::ShutdownClass();
		rlPresence::RemoveDelegate(&g_PresenceDlgt);

	#if RSG_ORBIS
		sysIpcWaitThreadExit(s_contentPollThread);
	#endif

#if RSG_PS3 || RSG_XENON
        s_codeCheckRun = false;
        sysIpcWaitThreadExit(s_codeCheckThread);
#endif
	}
	else if (shutdownMode == SHUTDOWN_SESSION)
	{
		ExecuteTitleUpdateDataPatchGroup((u32)CCS_GROUP_POST_DLC_PATCH,false);
		ExecuteTitleUpdateDataPatch((u32)CCS_TITLE_UPDATE_DLC_METADATA, false);
		ExecuteTitleUpdateDataPatchGroup((u32)CCS_GROUP_UPDATE_DLC_METADATA, false);
	#if __BANK
		Bank_UpdateContentDisplay();
	#endif		
	}

	if (m_currentGamerId.IsValid())
		m_enumerateOnUpdate = true;
}

void CExtraContentManager::ShutdownMetafiles(u32 /*shutdownMode*/, eDFMI_UnloadType unloadType)
{
	CDataFileMount::SetUnloadBehaviour(unloadType);
	{
		for (u32 i = 0; i < m_content.GetCount(); i++)
		{
			if (!m_content[i].GetIsPermanent())
				m_content[i].StartRevertActiveChangeSets();
		}
	}
	CDataFileMount::SetUnloadBehaviour(eDFMI_UnloadDefault);
}

void CExtraContentManager::ShutdownMapChanges(u32 /*shutdownMode*/)
{
	RevertContentChangeSetGroupForAll((u32)CCS_GROUP_MAP_SP);
	RevertContentChangeSetGroupForAll((u32)CCS_GROUP_MAP);

	CMapFileMgr::GetInstance().Cleanup();		// ensure the map file dependency data is cleared up	- bug 6177812
}

void CExtraContentManager::InitProfile()
{
	if (const rlGamerInfo* pGamerInfo = NetworkInterface::GetActiveGamerInfo())
	{
		m_localGamerIndex = pGamerInfo->GetLocalIndex();
		m_currentGamerId = pGamerInfo->GetGamerId();

	#if __XENON
		BeginDeviceEnumeration();
	#endif
	}
	m_enumerateOnUpdate = true;
#if EC_CLOUD_MANIFEST
	m_cloudStatusTimer = 0;
	m_cloudTimedOut = false;
	SetCloudManifestState(CLOUDFILESTATE_UNCACHED);
	BeginEnumerateCloudContent();
#endif // EC_DO_MANIFEST_CHECKS
}

void CExtraContentManager::ShutdownProfile()
{
#if EC_CLOUD_MANIFEST
	SetCloudManifestState(CLOUDFILESTATE_UNCACHED);
	m_cloudStatusTimer = 0;
	m_cloudTimedOut = false;
#endif // EC_DO_MANIFEST_CHECKS
	m_enumerateOnUpdate = false;
	m_localGamerIndex = -1;
	m_currentGamerId.Clear();
	#if __XENON
		fiDeviceXContent::SetLocalIndex(-1);
	#endif
}

void CExtraContentManager::UpdateRebootMessage()
{
	if (fiDeviceInstaller::HasRebooted(EC_REBOOT_ID) && !CLoadingScreens::AreActive())
	{
		bool showMessage = CLoadingScreens::ShowRebootMessage("EC_REBOOT_MSG", EC_REBOOT_ID);

		if (showMessage)
			CWarningScreen::SetWarningMessageInUse();
	}
}

void CExtraContentManager::Update()
{
	bool waitForEnumeration = false;

#if RSG_DURANGO
	m_waitingForBBCopy = false;

	// :(
	for (s32 i = 0; i < m_pendingActions.GetCount(); i++)
	{
		SPendingCSAction& currAction = m_pendingActions[i];
		CMountableContent* content = EXTRACONTENT.GetContentByHash(currAction.m_content);

		if (currAction.m_requiesLastFrame && !CPauseMenu::GetPauseRenderPhasesStatus())
		{
			m_waitingForBBCopy = true;
			continue;
		}

		if (currAction.m_execute)
		{
			if (Verifyf(content, "ProcessPending - Invalid execution contentHash %s", currAction.m_content.GetCStr()))
			{
				ExecuteContentChangeSetInternal(content, currAction.m_group, currAction.m_changeSet, currAction.m_flags, false);
			}
		}
		else
		{
			if (Verifyf(content, "ProcessPending - Invalid reversion contentHash %s", currAction.m_content.GetCStr()))
			{
				RevertContentChangeSetInternal(content, currAction.m_group, currAction.m_changeSet, currAction.m_actions, currAction.m_flags, false);
			}
		}

		m_pendingActions.Delete(i);
		i--;
	}
#endif

	UpdateRebootMessage();
#if EC_CLOUD_MANIFEST
	UpdateCloudStorage();
#endif // EC_DO_MANIFEST_CHECKS
	UpdateMapChangeState();

	UpdateSpecialTriggers();

#if __XENON
	// Check for content installed message and if it has then enumerate content again
	DWORD id;
	ULONG_PTR param;
	if (XNotifyGetNext(m_downloadContentListener, XN_LIVE_CONTENT_INSTALLED, &id, &param))
	{
		OnContentDownloadCompleted();
		waitForEnumeration = true;
	}
#endif // __XENON

	if (m_enumerating)
		EndEnumerateContent(waitForEnumeration,false);

	ProcessEnumerationRequests(false);
	ProcessDeviceChangedMessages();
	CloseLoadingScreen();

#if __BANK
	Bank_ExecuteBankUpdate();
#endif // __BANK
}

void CExtraContentManager::UpdateMapChangeState()
{
#if __BANK
	eMapChangeStates prevState = m_currMapChangeState;
#endif

	switch (m_currMapChangeState)
	{
		case MCS_INIT:
		{
			if (camInterface::IsFadedIn())
				camInterface::FadeOut(MAP_CHANGE_FADE_TIME, true);
			else if (camInterface::IsFadedOut())
				m_currMapChangeState = MCS_UPDATE;
		} break;

		case MCS_UPDATE:
		{

		} break;

		case MCS_END:
		{
			camInterface::FadeIn(MAP_CHANGE_FADE_TIME);
			m_currMapChangeState = MCS_NONE;
		} break;

		default: break;
	}

#if __BANK
	if (prevState != m_currMapChangeState)
		Bank_UpdateMapChangeState();
#endif
}

void CExtraContentManager::ProcessEnumerationRequests(bool immediate)
{
	if (m_enumerateOnUpdate || immediate)
	{
		BeginEnumerateContent(immediate,false);
		m_enumerateOnUpdate = false;
	}
}

enum eDeviceChangedMessage
{
	DC_NONE = 0,
	DC_SUCCESS,
	DC_FAIL
};
sysMessageQueue<eDeviceChangedMessage, 16> s_deviceChangedMessages;

//
// name:		ProcessDeviceChangedMessages
// description:	Main thread function to deal with devices having changed
void CExtraContentManager::ProcessDeviceChangedMessages()
{
	static bool bUnavailableContent = false;
	static bool bShowWarningScreen = false;
	static bool bPaused = false;

	eDeviceChangedMessage msg = DC_NONE;
	while (s_deviceChangedMessages.PopPoll(msg)) {}

	switch (msg)
	{
	case DC_SUCCESS:
		{
			// If content wasn't available before
			if(bUnavailableContent)
			{
				CWarningScreen::Remove();
				bShowWarningScreen = false;
			}

			// check enumerated content to update device ids
			UpdateContentArray();

			bUnavailableContent = false;
			if (bPaused)
			{
				fwTimer::EndUserPause();
				bPaused = false;
			}
		}
		break;
	case DC_FAIL:
		{
			CWarningScreen::SetWarningMessageInUse();
			bShowWarningScreen = true;

			if (!fwTimer::IsGamePaused())
			{
				fwTimer::StartUserPause();
				bPaused = true;
			}
			CControlMgr::StopPlayerPadShaking();

			// If playing network then bail
			if(NetworkInterface::IsNetworkOpen())
			{
				gnetDebug1("Bailing due to Extra content Device Change");
				NetworkInterface::Bail(BAIL_NEW_CONTENT_INSTALLED, BAIL_CTX_NONE);
			}

			bUnavailableContent = true;
		}
		break;
	default:
		break;
	}

	if (bShowWarningScreen)
	{
	#if __XENON
		// If we are showing the device removed warning screen and there is no profile (means they signed out on this screen) reboot...
		if (m_localGamerIndex == -1)
			fiDeviceInstaller::Reboot(EC_REBOOT_ID);
		else
	#endif
			CWarningScreen::SetMessage(WARNING_MESSAGE_STANDARD, "FE_NODLC");
	}
}

//
// name:		DeviceChangedCB
// description:	Function that is called when the storage device changes
void CExtraContentManager::DeviceChanged(bool bSuccess)
{
	if (!bSuccess)
	{
		s_deviceChangedMessages.Push(DC_FAIL);
	}
	else
	{
		s_deviceChangedMessages.Push(DC_SUCCESS);
	}
}

// Orbis calls this from the Polling thread
void CExtraContentManager::OnContentDownloadCompleted()
{
	dlcDebugf1("OnContentDownloadCompleted");

#if __XENON
	CLiveManager::GetCommerceMgr().GetConsumableManager()->SetOwnershipDataDirty();
#endif
	CLiveManager::GetCommerceMgr().ResetAutoConsumeMessage();

	m_enumerateOnUpdate = true;
}

void CExtraContentManager::BeginEnumerateContent(bool immediate, bool earlyStartup)
{
	dlcDebugf1("CExtraContentManager::BeginEnumerateContent");
	if (m_enumerating)
		EndEnumerateContent(true,earlyStartup);

	m_enumerating = true;

#if __XENON
	BeginDeviceEnumeration();
#endif

	if(immediate)
		EndEnumerateContent(true,earlyStartup);

#if EC_CLOUD_MANIFEST
	UpdateCloudStorage();
#endif // EC_DO_MANIFEST_CHECKS
}

#if __XENON
void CExtraContentManager::BeginDeviceEnumeration()
{
	if (m_localGamerIndex != -1)
	{
		fiDeviceXContent::SetLocalIndex(m_localGamerIndex);
		fiDeviceXContent::StartEnumerate();
		m_deviceEnumerating = true;
	}
}
#endif

#if !__FINAL
bool IsValidPath(fiFindData& find)
{
	bool isValid = false;

	if ((find.m_Attributes & FILE_ATTRIBUTE_DIRECTORY) && (find.m_Name[0] != '.'))
	{
	#if RSG_ORBIS || RSG_DURANGO || RSG_PC
		const u32 MAX_INVALID_NAMES = 8;
		const char* invalidNames[MAX_INVALID_NAMES] = 
			{ "Online", "outputPackages", "Packages", "dev", "art", "assets", "drm", "signage" };
	#else
		const u32 MAX_INVALID_NAMES = 8;
		const char* invalidNames[MAX_INVALID_NAMES] = 
			{ "Online", "outputPackages", "Packages", "dev_ng", "art", "assets", "drm", "signage" };
	#endif

		isValid = true;

		for (u32 i = 0; i < MAX_INVALID_NAMES; i++)
			isValid &= !(strcmp(find.m_Name, invalidNames[i]) == 0);
	}

	return isValid;
}

int CExtraContentManager::RecursiveEnumExtraContent(const char* searchPath)
{
	fiFindData find;
	const fiDevice *pDevice = fiDevice::GetDevice(searchPath, true);
	fiHandle handle = pDevice->FindFileBegin(searchPath, find);
	int searchPathLen = istrlen(searchPath);
	bool validSearchPath = !searchPathLen || searchPath[searchPathLen-1] == '/' || searchPath[searchPathLen-1] == '\\';
	int foundDLCCount = 0;
	Assertf(validSearchPath, "CExtraContentManager::RecursiveEnumExtraContent - searchPath must end with a slash! %s", searchPath);
	
	if ( validSearchPath && fiIsValidHandle( handle ) )
	{
#if __BANK
		char assetFolder[RAGE_MAX_PATH] = { 0 };
#ifdef RS_BRANCHSUFFIX
		formatf(assetFolder, RAGE_MAX_PATH, "%sart/ng/anim/export_mb", searchPath);
		if(ASSET.Exists(assetFolder, ""))
		{
			CDebugClipDictionary::PushAssetFolder(assetFolder);
		}
		formatf(assetFolder, RAGE_MAX_PATH, "%sassets" RS_BRANCHSUFFIX "/cuts", searchPath);
		if(ASSET.Exists(assetFolder, ""))
		{
			CDebugClipDictionary::PushAssetFolder(assetFolder);
			CutSceneManager::PushAssetFolder(assetFolder);
		}
#else
		formatf(assetFolder, RAGE_MAX_PATH, "%sart/anim/export_mb", searchPath);
		if(ASSET.Exists(assetFolder, ""))
		{
			CDebugClipDictionary::PushAssetFolder(assetFolder);
		}
		formatf(assetFolder, RAGE_MAX_PATH, "%sassets/cuts", searchPath);
		if(ASSET.Exists(assetFolder, ""))
		{
			CDebugClipDictionary::PushAssetFolder(assetFolder);
			CutSceneManager::PushAssetFolder(assetFolder);
		}
#endif
#endif // __BANK

		do 
		{
			atString filePath;

			if (IsValidPath(find))
			{
				filePath = searchPath;
				filePath += find.m_Name;

				ASSET.PushFolder(filePath.c_str());

				// Is this a DLC folder?
				if (ASSET.Exists(CMountableContent::GetSetupFileName(), ""))
				{
					if(!IsContentFilenamePresent(filePath.c_str()))
					{
						dlcDebugf3("CExtraContentManager::RecursiveEnumExtraContent - Looking for local DLC in %s", filePath.c_str());

						CMountableContent newContent;

						newContent.SetFilename(filePath);
						newContent.SetUsesPackFile(false);
						newContent.SetPrimaryDeviceType(CMountableContent::DT_FOLDER);
						newContent.SetNameHash(newContent.GetFilename());

						AddContent(newContent);

					}
					ASSET.PopFolder();
					foundDLCCount++;
				}
				else
				{
					ASSET.PopFolder();
					// It's not - let's recurse in here.
					
					char nextPath[RAGE_MAX_PATH] = { 0 };
					formatf(nextPath, "%s/", filePath.c_str());

					foundDLCCount+=RecursiveEnumExtraContent(nextPath);
				}
			}
		} 
		while (pDevice->FindFileNext( handle, find ));

		pDevice->FindFileEnd(handle);

	}
	return foundDLCCount;
}
#endif // !__FINAL

void CExtraContentManager::CancelEnumerateContent(bool XENON_ONLY(bWait))
{
	dlcDebugf1("End DLC enumeration (%s)", false XENON_ONLY(|| bWait) ? "Async" : "Block");

	m_enumerating = false;

#if __XENON
	if (m_deviceEnumerating)
		fiDeviceXContent::FinishEnumerate(bWait);

	m_deviceEnumerating = false;
#endif
}

bool CExtraContentManager::EndEnumerateContent(bool bWait, bool earlyStartup)
{
	CancelEnumerateContent(bWait);

#if __XENON
	fiDeviceXContent::LockEnumerate();
	{
		UpdateContentArray();
	}
	fiDeviceXContent::UnlockEnumerate();
#endif

#if __PPU || RSG_ORBIS || RSG_PC || RSG_DURANGO
	UpdateContentArray();
#endif

	// Load and execute any new content once we have added it to the array...
	LoadContent(!PARAM_delayDLCLoad.Get(), earlyStartup);

	return true;
}

//
// name:		strcpy
// description:	strcpy that takes a wide string input and an 8 bit output
void Strcpy(char* pDest, wchar_t* pSrc)
{
	while(*pSrc != 0)
	{
		*pDest = (char)*pSrc;
		pDest++;
		pSrc++;
	}
    *pDest = 0;
}

void CExtraContentManager::NormalizePath(char *src_path, bool trailingSlash /* = true */)
{
	Assert(src_path);

	const char SEPARATOR = '/';
	const char OTHER_SEPARATOR = '\\';

	const char *s = src_path;
	char *d = src_path;
	bool has_dots = false;

	while(*s) 
	{
		if((*s == SEPARATOR) || 
		   (*s == OTHER_SEPARATOR))
		{
			*d++ = SEPARATOR;

			while((*s == SEPARATOR) || 
				  (*s == OTHER_SEPARATOR)) 
				s++;
		}
		else
		{
			has_dots |= (*s == '.');
			*d++ = *s++;
		}
	}

	if(trailingSlash && (d > src_path) && !has_dots)
	{
		if(*(d - 1) != SEPARATOR)
		{
			*d++ = SEPARATOR;
		}
	}

	*d = '\0';
}

#if RSG_ORBIS
bool CExtraContentManager::IsContentFilenamePresent(atHashString& label) const
{
	for (u32 i = 0; i < m_content.GetCount(); i++)
	{
		atHashString currLabelHash = atHashString(m_content[i].GetPS4EntitlementLabel().data);

		if (currLabelHash == label)
			return true;
	}

	return false;
}
#endif

bool CExtraContentManager::IsContentFilenamePresent(const char* fileName) const
{
	char fname[RAGE_MAX_PATH] = { 0 };	
	char contentFname[RAGE_MAX_PATH] = { 0 };	

	strncpy(fname, fileName, RAGE_MAX_PATH);
	NormalizePath(fname);

	for (u32 i = 0; i < m_content.GetCount(); i++)
	{		
		strncpy(contentFname, m_content[i].GetFilename(), RAGE_MAX_PATH);
		NormalizePath(contentFname);

		if (!strcmpi(fname, contentFname))
			return true;
	}

	return false;
}

#if __PPU
void CExtraContentManager::UpdateContentArrayPs3(const char *path)
{
	const fiDevice& localDevice = fiDeviceLocal::GetInstance(); //	get path to usrdir folder
	fiFindData findDlcData;
	fiHandle DlcHandle;
	DlcHandle = localDevice.FindFileBegin(path, findDlcData);
	dlcDebugf2("CExtraContentManager::UpdateContentArray - Scanning %s for DLC", path);

	if ( fiIsValidHandle( DlcHandle ) )
	{
		do 
		{
			dlcDebugf3("CExtraContentManager::UpdateContentArray - Considering %s", findDlcData.m_Name);

			//	First find the folder named "dlc"
			if (findDlcData.m_Attributes == FILE_ATTRIBUTE_DIRECTORY && strnicmp(findDlcData.m_Name, "dlc", 3) == 0)
			{
				fiFindData findExtraContentData;
				fiHandle extraContentHandle;
				char DlcPath[RAGE_MAX_PATH] = { 0 };

				formatf(DlcPath, "%s/%s",path, findDlcData.m_Name);
				extraContentHandle = localDevice.FindFileBegin(DlcPath, findExtraContentData);

				dlcDebugf3("CExtraContentManager::UpdateContentArray - Looking into %s", DlcPath);

				if( fiIsValidHandle( extraContentHandle ) )
				{
					do 
					{
						dlcDebugf3("CExtraContentManager::UpdateContentArray - Checking out %s", findExtraContentData.m_Name);

						//	Then find any folders whose name begins with "episode" inside the "dlc" folder
						if (findExtraContentData.m_Attributes == FILE_ATTRIBUTE_DIRECTORY && 
							(strnicmp(findExtraContentData.m_Name, "episode", 7) == 0 || strnicmp(findExtraContentData.m_Name, "dlc", 3) == 0))
						{
							char filePath[RAGE_MAX_PATH] = { 0 };

							sprintf(filePath, "%s/%s", DlcPath, findExtraContentData.m_Name);

							if (!IsContentFilenamePresent(filePath))
							{
								CMountableContent mount;

								mount.SetFilename(filePath);

#if !__FINAL
								if (!PARAM_extracontent.Get())
#endif
									mount.SetUsesPackFile(true);

								AddContent(mount);
							}
						}
					} 
					while(localDevice.FindFileNext( extraContentHandle, findExtraContentData ));

					localDevice.FindFileEnd(extraContentHandle);
				}
			}
		}
		while(localDevice.FindFileNext( DlcHandle, findDlcData ));

		localDevice.FindFileEnd(DlcHandle);
	}
}

#endif	//	__PPU

//
// name:		CExtraContentManager::UpdateContentArray
// description:	Update content array based on enumerated content from fiDeviceXContent
void CExtraContentManager::UpdateContentArray()
{
#if !__FINAL
	bool ignoreDeployed = PARAM_ignoreDeployedPacks.Get();

	if(PARAM_usecompatpacks.Get())
	{
		if (m_enumerateCommandLines)
		{
			SMandatoryPacksData mandatoryPacksData;

			if(Verifyf(PARSER.LoadObject(COMPATPACKS_SET_PATH, NULL, mandatoryPacksData), 
				"-usecompatpacks couldn't load %s.", COMPATPACKS_SET_PATH))
			{
				Displayf("Loaded MandatoryPacksData from %s", COMPATPACKS_SET_PATH);

				for(int i = 0; i < mandatoryPacksData.m_Paths.GetCount(); i++)
				{
					int foundPacks = RecursiveEnumExtraContent(mandatoryPacksData.m_Paths[i].c_str());
					Assertf(foundPacks>0,"Couldn't find any DLC in the provided path %s , check your compatpacks.xml",mandatoryPacksData.m_Paths[i].c_str());
					Displayf("[DLC]Found %d DLC packs in %s",foundPacks, mandatoryPacksData.m_Paths[i].c_str());
				}
			}
		}

		ignoreDeployed = true;
	}

	if(PARAM_extracontent.Get())
	{
		if (m_enumerateCommandLines)
		{
			const char* pSearchPath = NULL;
			PARAM_extracontent.Get(pSearchPath);
			char buffer[RAGE_MAX_PATH] = {0}; 
			safecpy(buffer,pSearchPath);
			char* cur = strtok(buffer,";");
			while(cur)
			{
				int foundPacks = RecursiveEnumExtraContent(cur);
				Assertf(foundPacks>0,"Couldn't find any DLC in the provided path %s , check your -extracontent argument",cur);
				Displayf("[DLC]Found %d DLC packs in %s",foundPacks,cur);
				cur = strtok(NULL,";");
			}
		}
	}

	m_enumerateCommandLines = false;

	// Make commandline enumerators mutually exclusive with deployed packages, this avoids dupes as we only use file path as the ID
	if (ignoreDeployed)
		return;
#endif

	SMandatoryPacksData deployedPacksData;

	if(Verifyf(PARSER.LoadObject(COMPATPACKS_DEPLOYED_PATH, NULL, deployedPacksData), 
		"dlcList.xml isn't present as %s.", COMPATPACKS_DEPLOYED_PATH))
	{
		Displayf("Loaded dlcList data from %s", COMPATPACKS_DEPLOYED_PATH);

		for(int i = 0; i < deployedPacksData.m_Paths.GetCount(); i++)
		{
			char path[RAGE_MAX_PATH];
			DATAFILEMGR.ExpandFilename(deployedPacksData.m_Paths[i].c_str(),path,RAGE_MAX_PATH);
			AddContentFolder(path);
		}
	}

#if RSG_DURANGO
	if(CLiveManager::IsSignedIn())
	{
		EnumerateDurangoContent();
		UpdateContentArrayDurango();
	}
#endif
#if __XENON
	for (u32 i = 0; i < fiDeviceXContent::GetNumXContent(); i++)
	{
		XCONTENT_DATA* pContent = fiDeviceXContent::GetXContent(i);

		if (!IsContentFilenamePresent(pContent->szFileName))
		{
			CMountableContent newContent;
			char tempStr[XCONTENT_MAX_DISPLAYNAME_LENGTH] = { 0 };

			Strcpy(tempStr, pContent->szDisplayName);
			newContent.SetFilename(pContent->szFileName);
			newContent.SetNameHash(tempStr);
			newContent.SetPrimaryDeviceType(CMountableContent::DT_XCONTENT);
			newContent.SetXContentData(pContent);
			newContent.SetUsesPackFile(true);

			AddContent(newContent);
		}
	}
#endif	//	__XENON

#if __PPU
	 UpdateContentArrayPs3(fiPsnExtraContentDevice::GetGameDataPath());

	 if(fiDeviceInstaller::GetIsBootedFromHdd())
	 {
		UpdateContentArrayPs3(fiDeviceInstaller::GetHddBootPath());
	 }
#endif	//	__PPU

#if RSG_ORBIS

	 SceNpServiceLabel serviceLabel = 0;
	 SceAppContentAddcontInfo packages[SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM];
	 u32 packageCount = 0;
	 s32 errValue = 0;

	 memset(packages, 0, sizeof(SceAppContentAddcontInfo) * SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM);

	 Assertf(packageCount < SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM, "UpdateContentArray - At the limit of DLC packs! [%u / %u]", packageCount, SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM);

	 errValue = sceAppContentGetAddcontInfoList(serviceLabel, packages, SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM, &packageCount);

	 if (Verifyf(errValue == SCE_OK, "UpdateContentArray - Failed to get package count! %i", errValue) && packageCount > 0)
	 {
		 for (u32 i = 0; i < packageCount; i++)
		 {
			 SceNpUnifiedEntitlementLabel& entitlementLabel = packages[i].entitlementLabel;
			 atHashString ps4EntitlementHash = atHashString(entitlementLabel.data);

			 // SDK States this must be filled with zeros, now you would have thought Sony would follow their own rules but no.
			 memset(entitlementLabel.padding, 0, sizeof(entitlementLabel.padding));

			 // Only bother with packages that are actually installed
			 if (packages[i].status == SCE_APP_CONTENT_ADDCONT_DOWNLOAD_STATUS_INSTALLED && 
				 !IsContentFilenamePresent(ps4EntitlementHash) && m_ps4DupeEntitlements.Find(ps4EntitlementHash) == -1)
			 {
				 SceAppContentMountPoint mountPoint;

				 errValue = sceAppContentAddcontMount(serviceLabel, &entitlementLabel, &mountPoint);

				 if (Verifyf(errValue == SCE_OK, "UpdateContentArray - Failed to mount package %s! %i", entitlementLabel.data, errValue))
				 {
					 CMountableContent mount;
					 char filePath[SCE_KERNEL_PATH_MAX] = { 0 };

					 formatf(filePath, "%s/dlc.rpf", mountPoint.data);

					 mount.SetPrimaryDeviceType(CMountableContent::DT_PACKFILE);
					 mount.SetFilename(filePath);
					 mount.SetPS4MountPoint(mountPoint);
					 mount.SetPS4EntitlementLabel(entitlementLabel);

					 AddContent(mount);
				 }
			 }
		 }
	 }
#endif

#if RSG_PC
	 EnumerateContentPC();
#endif // RSG_PC
}

u32 CExtraContentManager::GetContentHash(u32 index) const
{
	if (const CMountableContent* content = GetContent(index))
		return content->GetNameHash();

	return 0;
}


void CExtraContentManager::GetMapChangeArray(atArray<u32>& retArray) const
{
	for (u32 i = 0; i < m_content.GetCount(); i++)
	{
		m_content[i].GetMapChangeHashes(retArray);
	}
}


u32 CExtraContentManager::GetMapChangesCRC() const
{
	u32 retVal = 0;
	atArray<u32> activeMapChangeHashes;

	GetMapChangeArray(activeMapChangeHashes);

	std::sort(activeMapChangeHashes.begin(), activeMapChangeHashes.end());

	for (u32 i = 0; i < activeMapChangeHashes.GetCount(); i++)
	{
		u32 currHashName = activeMapChangeHashes[i];

		retVal = atDataHash((const unsigned int*)&currHashName, (s32)sizeof(currHashName), retVal);
	}

	dlcDebugf3("CExtraContentManager::GetMapChangesCRC - retVal = %u", retVal);

	return retVal;
}

#if RSG_PC

void CExtraContentManager::EnumerateContentPC()
{
	const fiDevice *pDevice = fiDevice::GetDevice(DLC_PACKS_PATH, true);
	if(!Verifyf(pDevice, "Couldn't get device for %s", DLC_PACKS_PATH))
		return;

	fiFindData find;
	fiHandle handle = pDevice->FindFileBegin(DLC_PACKS_PATH, find);

	if ( fiIsValidHandle( handle ) )
	{
		do 
		{
			atString filePath;

			if((find.m_Attributes & FILE_ATTRIBUTE_DIRECTORY) && 
			   (find.m_Name[0] != '.'))
			{
				filePath = DLC_PACKS_PATH;
				filePath += find.m_Name;
				filePath += "/";

				AddContentFolder(filePath.c_str());
			}
		} 
		while (pDevice->FindFileNext( handle, find ));

		pDevice->FindFileEnd(handle);
	}	
}

#endif // RSG_PC

u32 CExtraContentManager::GetCRC(u32 initValue /*= 0*/) const 
{
	dlcDebugf3("CExtraContentManager::GetCRC - initValue = %u", initValue);

	if(!PARAM_netSessionIgnoreECHash.Get())
	{
		for (u32 i = 0; i < m_content.GetCount(); i++)
		{
			if (m_content[i].GetIsCompatibilityPack())
			{
				u32 currHashName = m_content[i].GetNameHash();

				initValue = atDataHash((const unsigned int*)&currHashName, (s32)sizeof(currHashName), initValue);

				dlcDebugf3("CExtraContentManager::GetCRC - %s changed hash, initValue = %u", m_content[i].GetName(), initValue);
			}
		}

		u32 weaponPatchHash = CWeaponInfoManager::CalculateWeaponPatchCRC(0);

		if(weaponPatchHash)
		{
			initValue = atDataHash((const unsigned int*)&weaponPatchHash, (s32)sizeof(weaponPatchHash), initValue);

			dlcDebugf3("CExtraContentManager::GetCRC - weaponPatchHash changed hash, initValue = %u", initValue);
		}
	}

	dlcDebugf3("CExtraContentManager::GetCRC - return initValue = %u", initValue);

	return initValue;
}

void CExtraContentManager::ExecuteScriptPatch()
{
#if EC_CLOUD_MANIFEST
	u32 contentNameHash = atStringHash(m_cloudData.m_CloudManifest.m_ScriptPatchName);

	if(!contentNameHash)
		return;

	if(CMountableContent *content = GetContentByHash(contentNameHash)) 
	{
		ExecuteContentChangeSetGroup(content->GetNameHash(),(u32)CCS_GROUP_ON_DEMAND);
	}
	else
	{
		Errorf("Cloud weapon patch content '%s' hasn't been mounted!", m_cloudData.m_CloudManifest.m_ScriptPatchName.c_str());
	}
#endif // EC_DO_MANIFEST_CHECKS
}

#if EC_CLOUD_MANIFEST
void CExtraContentManager::ExecuteWeaponPatchMP(bool execute)
{
	u32 contentNameHash = atStringHash(m_cloudData.m_CloudManifest.m_WeaponPatchNameMP);

	if(!contentNameHash)
		return;

	if(GetContentByHash(contentNameHash)) 
	{
		if(execute)
		{
			ExecuteContentChangeSet(contentNameHash,(u32)CCS_GROUP_ON_DEMAND, (u32)CCS_TITLE_UPDATE_WEAPON_PATCH);
		}
		else
		{
			RevertContentChangeSet(contentNameHash,(u32)CCS_GROUP_ON_DEMAND, (u32)CCS_TITLE_UPDATE_WEAPON_PATCH);
		}
	}
	else
	{
		Errorf("Cloud weapon patch content '%s' hasn't been mounted!", m_cloudData.m_CloudManifest.m_WeaponPatchNameMP.c_str());
	}
#else
void CExtraContentManager::ExecuteWeaponPatchMP(bool /*execute*/)
{
	return;
#endif // EC_DO_MANIFEST_CHECKS
}

bool CExtraContentManager::CheckCompatPackConfiguration() const
{
#if EC_CLOUD_MANIFEST
#if __BANK
	if(EXTRACONTENT.BANK_EnableCloudWidgetsOverride())
	{
		return EXTRACONTENT.BANK_GetForceCompatState();
	}
#endif
#if !__FINAL
	if(PARAM_disablecompatpackcheck.Get())
		return true;
#endif // !__FINAL
	bool retVal = true;
	for (u32 i = 0; i < m_cloudData.m_CloudManifest.m_CompatibilityPacks.GetCount(); i++)
	{
		if(!GetContentByHash(atStringHash(m_cloudData.m_CloudManifest.m_CompatibilityPacks[i].m_Name)))
		{
			retVal = false;
			Assertf(0,"Missing compatibility pack %s",m_cloudData.m_CloudManifest.m_CompatibilityPacks[i].m_Name.c_str());
			Displayf("Missing compatibility pack %s",m_cloudData.m_CloudManifest.m_CompatibilityPacks[i].m_Name.c_str());		
		}
	}

	return retVal;
#else
	return true;
#endif // EC_DO_MANIFEST_CHECKS
}

bool CExtraContentManager::VerifySaveGameInstalledPackages() const
{
	for (u32 i = 0; i < m_savegameInstalledPackages.GetCount(); i++)
	{		
		if(!GetContentByHash(m_savegameInstalledPackages[i]))
			return false;
	}

	return true;
}

void CExtraContentManager::ResetSaveGameInstalledPackagesInfo()
{
	m_savegameInstalledPackages.ResetCount();
}

void CExtraContentManager::SetSaveGameInstalledPackage(u32 nameHash)
{
	m_savegameInstalledPackages.PushAndGrow(nameHash);
}

bool CExtraContentManager::AddContentFolder(const char *path)
{
	char filePathPreFixed[RAGE_MAX_PATH] = { 0 };
	formatf(filePathPreFixed,"%sdlc.rpf",path);

	if(Verifyf(ASSET.Exists(filePathPreFixed,""),"DLC pack at path %s not built correctly, dlc.rpf must be inside the package", path))
	{
		if(!IsContentFilenamePresent(filePathPreFixed))
		{
			CMountableContent mount;
			mount.SetFilename(filePathPreFixed);
			mount.SetUsesPackFile(false);
			mount.SetPrimaryDeviceType(CMountableContent::DT_PACKFILE);
			mount.SetNameHash(mount.GetFilename());
			mount.SetIsShippedContent(true);
			AddContent(mount);
		}
		return true;
	}
	else
	{
		Errorf("DLC pack not built correctly, DLC.rpf must be inside the package, path: %s",path);
	}
	return false;
}

int CExtraContentManager::GetMissingCompatibilityPacks(atArray<u32> &packNameHashes) const
{
#if EC_CLOUD_MANIFEST
	for(int i = 0; i < m_cloudData.m_CloudManifest.m_CompatibilityPacks.GetCount(); i++)
	{
		u32 nameHash = atStringHash(m_cloudData.m_CloudManifest.m_CompatibilityPacks[i].m_Name.c_str());
		const CMountableContent* content = GetContentByHash(nameHash);

		if(!content)

		{
			packNameHashes.PushAndGrow(nameHash);
		}
	}
#endif // EC_DO_MANIFEST_CHECKS

	return packNameHashes.GetCount();
}

//
// name:		CExtraContentManager::AddContent
// description:	Add new content
s32 CExtraContentManager::AddContent(CMountableContent& mountableData)
{
	dlcDebugf3("CExtraContentManager::AddContent - Adding DLC content [%i] %s", m_content.GetCount(), mountableData.GetFilename());

	mountableData.SetECPackFileIndex(m_content.GetCount() + 1);
	m_content.PushAndGrow(mountableData, 1);

	CPlayerSpecialAbilityManager::UpdateDlcMultipliers();

	return m_content.GetCount() - 1;
}

// name:		CExtraContentManager::OnPresenceEvent
// description:	Deal with profiles signing in and out
void CExtraContentManager::OnPresenceEvent(const rlPresenceEvent* evt)
{
	if (PRESENCE_EVENT_SIGNIN_STATUS_CHANGED == evt->GetId())
	{
		const rlGamerInfo* activeGamer = NetworkInterface::GetActiveGamerInfo();
		const rlPresenceEventSigninStatusChanged* s = evt->m_SigninStatusChanged;

		if(s->SignedIn() || s->SignedOnline()) // If signed in, init extra content (Signed online is sent instead of signed in, if we sign in and online together)
		{
			if(activeGamer && activeGamer->GetGamerId() == s->m_GamerInfo.GetGamerId() && activeGamer->GetGamerId() != EXTRACONTENT.m_currentGamerId)
				EXTRACONTENT.InitProfile();
		}
		else if(s->SignedOut()) // If signing out shutdown extra content
		{
			if(EXTRACONTENT.m_currentGamerId == s->m_GamerInfo.GetGamerId())
				EXTRACONTENT.ShutdownProfile();
		}
	}
}

void CExtraContentManager::OnServiceEvent(sysServiceEvent* evt)
{
	if(evt != NULL)
	{
		if(evt->GetType() == sysServiceEvent::ENTITLEMENT_UPDATED)
		{
			EXTRACONTENT.OnContentDownloadCompleted();
		}
	}
}

#if !__FINAL
bool CExtraContentManager::IsContentIgnored(u32 nameHash)
{
	const char* packsToIgnore;

	if(PARAM_ignorePacks.Get(packsToIgnore))
	{
		char buffer[RAGE_MAX_PATH] = {0}; 
		strcpy(buffer,packsToIgnore);
		char* cur = strtok(buffer,";");
		while(cur)
		{
			atHashString contentHash(cur);

			if (contentHash == nameHash)
				return true;

			cur = strtok(NULL,";");
		}			
	}

	return false;
}
#endif

void CExtraContentManager::LoadContent(bool executeChangeSet/* = false*/, bool executeEarlyStartup /*=false*/)
{
	sysTimer timeToAccessDLC;

	static bool	bFirstCall = true;
	bool bSlowAccessWarningHasAlreadyBeenShown = false;

	timeToAccessDLC.Reset();

#if RSG_XENON
	s32 packsToLoad = 0;

	for (u32 i = 0; i < m_content.GetCount(); i++)
	{
		if (CMountableContent* content = GetContent(i))
		{
			if (content->GetStatus() != CMountableContent::CS_FULLY_MOUNTED)
				packsToLoad++;
		}
	}

	if (packsToLoad >= LOAD_SCR_PACK_THRESHOLD)
		TriggerLoadingScreen();
#endif

#if RSG_ORBIS
	m_ps4DupeEntitlements.ResetCount();
#endif

	dlcDebugf1("CExtraContentManager::LoadContent - Package count = %i executeChangeSet = %i", m_content.GetCount(), executeChangeSet);

	//	CELL_SYSMODULE_SYSUTIL_NP2 module has already been loaded in rage/base/src/system/main.h and
	//	sceNp2Init has already been called in rage/base/src/rline/rlnp.cpp 
	//	so I don't need to do either of those before loading the encrypted EDATA file(s)

	for (u32 i = 0; i < m_content.GetCount(); i++)
	{
		timeToAccessDLC.Reset();

		if (CMountableContent* content = GetContent(i))
		{
			bool setup = content->Setup(m_localGamerIndex);
			bool isDupe = setup ? IsContentDuplicate(content) : false;
			NOTFINAL_ONLY(bool isIgnored = setup ? IsContentIgnored(content->GetNameHash()) : false;)

			if (!setup || isDupe NOTFINAL_ONLY(|| isIgnored))
			{
			#if RSG_ORBIS
				if (isDupe)
				{
					atHashString ps4EntitlementHash = atHashString(content->GetPS4EntitlementLabel().data);

					if (m_ps4DupeEntitlements.Find(ps4EntitlementHash) == -1)
						m_ps4DupeEntitlements.PushAndGrow(ps4EntitlementHash, 1);
				}
			#endif

				if (content->GetStatus() == CMountableContent::CS_SETUP_MISSING)
					Warningf("CMountableContent::Setup - DLC Pack is missing the proper setup data! [%s]", content->GetFilename());
				else
					Assertf(content->GetStatus() == CMountableContent::CS_SETUP_READ, "CMountableContent::Setup - There was an error loading the setup file! [%s] [%i]", content->GetFilename(), content->GetStatus());

				if(content->GetStatus() == CMountableContent::CS_CORRUPTED)
				{
					m_corruptedContent.PushAndGrow(atString(content->GetFilename()));
				}

				m_content[i].ShutdownContent();
				m_content.Delete(i);
				i--;
				continue;
			}
		}

		if ((!bFirstCall && !bSlowAccessWarningHasAlreadyBeenShown && (timeToAccessDLC.GetTime() > DLC_PACK_MOUNT_TIMEOUT)))
		{
		#if __XENON
			CLoadingScreens::Suspend();
			CWarningScreen::SetAndWaitOnMessageScreen(WARNING_MESSAGE_STANDARD, "DLC_ACCESS_SLOW", FE_WARNING_OK);	//	Will wait in here until the player presses the Accept button

			CWarningScreen::Update();  // one last update as we shutdown to switch off warningscreens that have been set
			CLoadingScreens::Resume();
			CGtaOldLoadingScreen::SetLoadingScreenIfRequired();
			bSlowAccessWarningHasAlreadyBeenShown = true;
		#endif //__XENON
		}
	}

	std::sort(m_content.begin(), m_content.end());

#if !__FINAL
	for(int i = 0; i < m_content.GetCount() - 1; i++)
	{
		if(m_content[i].GetIsCompatibilityPack() && m_content[i + 1].GetIsCompatibilityPack()) 
		{
			ASSERT_ONLY(bool dupe = m_content[i].GetOrder() == m_content[i + 1].GetOrder();)

			Assertf(!dupe, "Compatibility pack: '%s' and compatibility pack: '%s' both have the same order: %d", 
				m_content[i].GetName(),  m_content[i + 1].GetName(), m_content[i].GetOrder());
		}
	}
#endif

	u32 compatIndex = 0;

	for (u32 i = 0; i < m_content.GetCount(); i++)
	{
		if (CMountableContent* content = GetContent(i))
		{
			if(content->GetIsCompatibilityPack())
			{
				if (content->GetOrder() != (s32)compatIndex)
				{
					if(!PARAM_disablepackordercheck.Get())
					{
						m_everHadBadPackOrder = true;
					}

					Assertf(false, "CExtraContentManager::LoadContent compatibility pack configuration is not complete compatibility pack %s with index %d has order %d", 
						content->GetName(), compatIndex, content->GetOrder());
				}

				compatIndex++;
			}

			// Don't bother mounting packs that are setup only
			if (!content->IsSetupOnly())
			{
				if(executeEarlyStartup)
				{
					if(content->IsLevelPack())
					{
						content->SetPermanent(true);
						content->Mount(m_localGamerIndex);

						if(executeChangeSet)
							ExecuteContentChangeSetGroup(content->GetNameHash(),CCS_GROUP_EARLY_ON);
					}
				}
				else
				{
					content->Mount(m_localGamerIndex);

					if(executeChangeSet)
					{
						ExecuteContentChangeSetGroup(content->GetNameHash(),CCS_GROUP_STARTUP);
					}
				}
			}
		}
	}

	if(executeChangeSet)
 		ExecutePendingOverlays();

	CPlayerSpecialAbilityManager::UpdateDlcMultipliers();
	if(!bFirstCall)
	{
		CNetworkAssetVerifier& verifier = CNetwork::GetAssetVerifier();
		verifier.RefreshFileCRC();
	}
	if(GetCorruptedContentCount() > 0)
	{
		CWarningScreen::SetAndWaitOnMessageScreen(WARNING_MESSAGE_STANDARD, "DLC_CORRUPT_ERR", FE_WARNING_OK);	//	Will wait in here until the player presses the Accept button
		m_corruptedContent.Reset();
	}

#if __BANK
	Bank_UpdateContentDisplay();
#endif

#if !__NO_OUTPUT
	PrintState();
#endif // !__NO_OUTPUT

	bFirstCall = false;
}

#if !__NO_OUTPUT

void CExtraContentManager::PrintState() const
{
	Displayf("ExtraContent state:");

	Displayf("Mounted %d DLC packs:", m_content.GetCount());

	const char *fmt = " %-16s %-56s %-22s %-12s %-22s %-10s";

	Displayf(fmt, "Name", "Filename", "Device", "DeviceType", "TimeStamp", "Status"); 

	for (u32 i = 0; i < m_content.GetCount(); i++)
	{
		if (const CMountableContent* content = GetContent(i))
		{
			Displayf(fmt, 
				content->GetName(), 
				content->GetFilename(), 
				content->GetDeviceName(), 
				GetDeviceTypeString(content->GetPrimaryDeviceType()), 
				content->GetTimeStamp(), 
				CMountableContent::GetContentStatusName((content->GetStatus())));
		}
	}

	Displayf("DLC CRC:0x%X, DLC Map CRC:0x%X", GetCRC(0), GetMapChangesCRC());
}

#endif // !__NO_OUTPUT

bool CExtraContentManager::IsContentDuplicate(const CMountableContent *content) const
{
	for (u32 i = 0; i < m_content.GetCount(); i++)
	{
		if (const CMountableContent* c = GetContent(i))
		{
			if(c == content)
				continue;

			if(strnicmp(c->GetDeviceName(), content->GetDeviceName(), RAGE_MAX_PATH) == 0)
			{
				// Let dev extra content take priority over deployed content
				if (!content->GetIsShippedContent() && c->GetIsShippedContent())
					return false;

				Warningf("DLC with the same device name '%s' has been already mounted%s! This pack %s (%s) will be ignored.", 
					content->GetDeviceName(),
					c->GetIsShippedContent()?" as a shipped DLC pack, you seem to have both shipped and installed packs, try removing the installed packs":"",
					content->GetFilename(), 
					content->GetName());

				return true;
			}
		}
	}

	return false;
}

//
// name:		CExtraContentManager::InsertContentLock
// description:	Compatibility packs should add content locks, per DLC item (clothes, weapons etc...) and paid packs will unlock them.
//				You must have a valid index returned from AddOnContentItemUnlockedCB to supply as unlockedCBIndex
// usage:
//				s32 contentLockCBIndex = AddOnContentItemChangedCB(myCBFunctor(u32));
//				InsertContentLock(atStringHash("subsystemContentLock", contentLockCBIndex);
void CExtraContentManager::InsertContentLock(u32 hash, s16 unlockedCBIndex, bool locked)
{
	if (hash != 0)
	{
		SContentLockData* lockData = m_contentLocks.SafeGet(hash);

		if (!lockData)
		{
			SContentLockData newLockData;

			newLockData.m_locked = locked;
			newLockData.addCBIndex(unlockedCBIndex);

			m_contentLocks.Insert(hash, newLockData);
			m_contentLocks.FinishInsertion();

		#if !__FINAL
			OUTPUT_ONLY(atNonFinalHashString hashStr = hash;)

			dlcDebugf3("InsertContentLockData :: Inserting new content lock! %s, %i, %i", hashStr.GetCStr(), newLockData.m_callBackIndices, locked);
		#endif
		}
		else
		{
			OUTPUT_ONLY(s32 oldValue = lockData->m_callBackIndices;)

			lockData->addCBIndex(unlockedCBIndex);

			dlcDebugf3("InsertContentLockData :: Updating callback index! Old: %i - New: %i", oldValue, lockData->m_callBackIndices);
		}

		#if __BANK
			DisplayContentLocks(m_currentContentLocksDebugPage, 0); // refresh bank display
		#endif
	}
}

void CExtraContentManager::RemoveContentLock(u32 hash)
{
	if (SContentLockData* lockData = m_contentLocks.SafeGet(hash))
	{
	#if !__FINAL
		OUTPUT_ONLY(atNonFinalHashString hashStr = hash;)

		dlcDebugf3("RemoveContentLock :: Removing content lock! %s, %i, %i", hashStr.GetCStr(), lockData->m_callBackIndices, lockData->m_locked);
	#endif

		s32 index = m_contentLocks.GetIndexFromDataPtr(lockData);

		m_contentLocks.Remove(index);
	}
}

//
// name:		CExtraContentManager::AddOnContentItemChangedCB
// description:	Subsystems must register an unlocked callback before registering any content locks into the map. We return the index of the callback 
//				so we can attach this to a specific content lock, this means we can call only the relevant subsystem when a content lock state changes.
// usage:
//				s32 contentLockCBIndex = AddOnContentItemChangedCB(myDelegate(u32, bool));
//				InsertContentLock(atStringHash("subsystemContentLock", contentLockCBIndex);
s16 CExtraContentManager::AddOnContentItemChangedCB(atDelegate<void (u32, bool)> onChangedCB)
{
	if (m_onContentLockStateChangedCBs.GetCount() < SContentLockData::MAX_CONTENT_LOCK_SUBSYSTEM_CBS)
	{
		m_onContentLockStateChangedCBs.Push(onChangedCB);

		dlcDebugf3("AddOnContentItemUnlockedCB: index = %i", m_onContentLockStateChangedCBs.GetCount() - 1);

		return (s16)(m_onContentLockStateChangedCBs.GetCount() - 1);
	}

	dlcErrorf("AddOnContentItemChangedCB :: Cannot allocate anymore onContentItemUnlockedCB entries!");

	return SContentLockData::INVALID_CONTENT_LOCK_CB_INDEX;
}

bool CExtraContentManager::IsContentItemLocked(u32 hash) const
{
	if (hash != 0)
	{
		const SContentLockData* lockData = m_contentLocks.SafeGet(hash);

		return (lockData) ? lockData->m_locked : true;
	}

	return false;
}

//
// name:		CExtraContentManager::ModifyContentLockState
// description:	Modifies the state of a content lock and invalidates the subsystem tied to that lock to handle it's new state.
//				This function will insert new content locks into the map with the given state if one does not already exist.
void CExtraContentManager::ModifyContentLockState(u32 hash, bool locked)
{
	if (hash != 0)
	{
		SContentLockData* lockData = m_contentLocks.SafeGet(hash);

		if (lockData)
		{
			lockData->m_locked = locked;

			dlcDebugf3("ModifyContentLockState :: Items [%u] state has changed invoking call backs...", hash);

			for (int i = 0; i < m_onContentLockStateChangedCBs.GetCount(); i++)
			{
				// Call back to any subsystem this content is linked with as the state has changed...
				if (lockData->m_callBackIndices & (1 << i))
					m_onContentLockStateChangedCBs[i].Invoke(hash, lockData->m_locked);
			}
		}
		else
		{
			dlcDebugf3("ModifyContentLockState :: Inserting new content lock!");

			InsertContentLock(hash, SContentLockData::INVALID_CONTENT_LOCK_CB_INDEX, locked);
		}
	}

#if __BANK
	DisplayContentLocks(m_currentContentLocksDebugPage, 0); // refresh bank display
#endif
}

void CExtraContentManager::ExecuteContentChangeSetGroupForAll(u32 changeSetGroup)
{
	for (u32 i = 0; i < m_content.GetCount(); i++)
	{
		ExecuteContentChangeSetGroup(m_content[i].GetNameHash(),changeSetGroup);
	}
}

void CExtraContentManager::RevertContentChangeSetGroupForAll(u32 changeSetGroup)
{
	for (u32 i = 0; i < m_content.GetCount(); i++)
	{
		RevertContentChangeSetGroup(m_content[i].GetNameHash(),changeSetGroup);
	}
}

void CExtraContentManager::ExecuteContentChangeSet(u32 contentHash, u32 changeSetGroup, u32 changeSetName)
{
	CMountableContent* content = EXTRACONTENT.GetContentByHash(contentHash);
	if (Verifyf(content, "ExecuteContentChangeSet - Invalid contentHash %u", contentHash))
	{
		ExecuteContentChangeSetInternal(content,changeSetGroup,changeSetName, ECCS_FLAG_USE_LATEST_VERSION | ECCS_FLAG_USE_LOADING_SCREEN | ECCS_FLAG_MAP_CLEANUP);
	}

}

void CExtraContentManager::RevertContentChangeSet(u32 contentHash, u32 changeSetGroup, u32 changeSetName)
{
	CMountableContent* content = EXTRACONTENT.GetContentByHash(contentHash);
	if (Verifyf(content, "ExecuteContentChangeSet - Invalid contentHash %u", contentHash))
	{
		RevertContentChangeSetInternal(content,changeSetGroup,changeSetName, CDataFileMgr::ChangeSetAction::CCS_ALL_ACTIONS, ECCS_FLAG_USE_LATEST_VERSION | ECCS_FLAG_USE_LOADING_SCREEN);
	}
}

void CExtraContentManager::ExecuteContentChangeSetGroup(u32 contentHash, u32 changeSetGroup)
{
	CMountableContent* content = EXTRACONTENT.GetContentByHash(contentHash);
	if (Verifyf(content, "ExecuteContentChangeSetGroup - Invalid contentHash %u", contentHash))
	{
		ExecuteContentChangeSetGroupInternal(content,changeSetGroup);
	}
}

void CExtraContentManager::RevertContentChangeSetGroup(u32 contentHash, u32 changeSetGroup)
{
	CMountableContent* content = EXTRACONTENT.GetContentByHash(contentHash);
	if (Verifyf(content, "RevertContentChangeSetGroup - Invalid contentHash %u", contentHash))
	{
		RevertContentChangeSetGroupInternal(content,changeSetGroup, CDataFileMgr::ChangeSetAction::CCS_ALL_ACTIONS);
	}
}

//
// name:		CExtraContentManager::GetContentIndex
// description:	Get extra content index from its hash. Returns NULL if it doesn't exist
CMountableContent* CExtraContentManager::GetContentByHash(u32 nameHash)
{
	return const_cast<CMountableContent*>(GetContentByHashImpl(nameHash));
}

CMountableContent* CExtraContentManager::GetContentByDevice(const char* device)
{
	const char* isCrc = stristr(device,"CRC:");
	atHashString deviceHash = atHashString::Null();
	if(isCrc)
	{
		char tempDevice[RAGE_MAX_PATH] = {0};
		char realDevice[RAGE_MAX_PATH] = {0};
		strncpy(tempDevice,device,isCrc - device);
		formatf(realDevice,RAGE_MAX_PATH,"%s:/",tempDevice);
		Displayf("Real device: %s",realDevice);
		deviceHash = atHashString(realDevice);
	}
	else
	{
		deviceHash = atHashString(device);		
	}
	for(int i=0;i<m_content.GetCount();i++)
	{
		dlcDebugf3("content: %s device: %s", m_content[i].GetDeviceName(),deviceHash.TryGetCStr());
		if(atStringHash(m_content[i].GetDeviceName())==deviceHash)
		{
			return &m_content[i];
		}
	}
	return NULL;
}

const CMountableContent* CExtraContentManager::GetContentByHash(u32 nameHash) const
{
	return GetContentByHashImpl(nameHash);
}

const CMountableContent* CExtraContentManager::GetContentByHashImpl(u32 nameHash) const
{
	for(u32 i=0; i<m_content.GetCount(); i++)
	{
		if(m_content[i].GetNameHash() == nameHash)
			return &m_content[i];
	}

	return NULL;
}

CMountableContent* CExtraContentManager::GetContent(u32 index)
{
	if (index < m_content.GetCount())
		return &m_content[index];

	return NULL;
}

const CMountableContent* CExtraContentManager::GetContent(u32 index) const
{
	if (index < m_content.GetCount())
		return &m_content[index];

	return NULL;
}

fiDeviceRelative gTitleUpdatePlatform, gTitleUpdatePlatform2, gTitleUpdateCommon, gTitleUpdateAudio, gTitleUpdateAudioSFX, gLocalTU;
fiDeviceCrc gTitleUpdateCommonCRC, gTitleUpdatePlatformCRC, gTitleUpdatePlatform2CRC;
fiPackfile gTUPackfile;

fiDeviceRelative gTitleUpdateCommon2;
fiDeviceCrc gTitleUpdateCommonCRC2;
fiPackfile gTUPackfile2;

const char *CExtraContentManager::GetPlatformTitleUpdatePath()
{
	static char platformPath[RAGE_MAX_PATH] = { 0 };
	if(!platformPath[0])
		sprintf(platformPath, "update:/%s", RSG_PLATFORM_ID);
	return platformPath;
}

const char *CExtraContentManager::GetPlatformTitleUpdate2Path()
{
	static char platformPath[RAGE_MAX_PATH] = { 0 };
	if( !platformPath[0] )
		sprintf( platformPath, "update2:/%s", RSG_PLATFORM_ID );
	return platformPath;
}

const char *CExtraContentManager::GetAudioTitleUpdatePath()
{
	static char audioPath[RAGE_MAX_PATH] = { 0 };
	if(!audioPath[0])
	{
#if RSG_ORBIS
		sprintf(audioPath, "update:/%s/audio", RSG_PLATFORM);
#else
		sprintf(audioPath, "update:/%s/audio", RSG_PLATFORM_ID);
#endif
	}
	return audioPath;
}

void CExtraContentManager::MountUpdate()
{
	fiDevice *overrideDevice = NULL, *tu2Device = nullptr;

	if( ASSET.Exists( TITLE_UPDATE2_RPF_PATH, NULL ) )
	{
		if( Verifyf( gTUPackfile2.Init( TITLE_UPDATE2_RPF_PATH, true, fiPackfile::CACHE_NONE ), "CExtraContentManager::MountUpdate failed to init rpf : %s", TITLE_UPDATE2_RPF_PATH ) )
		{
			gTUPackfile2.MountAs( "update2:/" );
			tu2Device = &gTUPackfile2;
		}
		else
		{
			Errorf( "CExtraContentManager::MountUpdate failed to init rpf : %s", TITLE_UPDATE2_RPF_PATH );
		}
	}

	if(ASSET.Exists(TITLE_UPDATE_RPF_PATH, NULL))
	{
		if(Verifyf(gTUPackfile.Init(TITLE_UPDATE_RPF_PATH, true, fiPackfile::CACHE_NONE), "CExtraContentManager::MountUpdate failed to init rpf : %s", TITLE_UPDATE_RPF_PATH))
		{
			gTUPackfile.MountAs("update:/");
			overrideDevice = &gTUPackfile;
			gLocalTU.Init("update/" RSG_PLATFORM_ID "/dlcpacks/", true);
			gLocalTU.MountAs("dlcpacks:/");
		}
		else
		{
			Errorf("CExtraContentManager::MountUpdate failed to init rpf : %s", TITLE_UPDATE_RPF_PATH);
		}
	}
	else
	{
		if(fiDevice::IsAnythingMountedAtMountPoint(TITLE_UPDATE_MOUNT_PATH))
		{
			gLocalTU.Init("update:/" RSG_PLATFORM_ID "/dlcpacks/", true);
			gLocalTU.MountAs("dlcpacks:/");
		}
	}

	if(fiDevice::IsAnythingMountedAtMountPoint(TITLE_UPDATE_MOUNT_PATH))
	{
		//update2
		{
			gTitleUpdateCommon2.Init( "update2:/common", false, tu2Device );
			gTitleUpdateCommon2.MountAs( "common:/" );

			// mount crc device
			gTitleUpdateCommonCRC2.Init( "update2:/common", true, tu2Device );
			gTitleUpdateCommonCRC2.MountAs( "commoncrc:/" );
		}

		gTitleUpdateCommon.Init( "update:/common", false, overrideDevice);
		gTitleUpdateCommon.MountAs("common:/");	
	
		// mount crc device
		gTitleUpdateCommonCRC.Init("update:/common", true, overrideDevice);
		gTitleUpdateCommonCRC.MountAs("commoncrc:/");
	
		gTitleUpdatePlatform.Init( GetPlatformTitleUpdatePath(), false, overrideDevice);
		gTitleUpdatePlatform.MountAs("platform:/");
	
		gTitleUpdatePlatformCRC.Init(GetPlatformTitleUpdatePath(), true, overrideDevice);
		gTitleUpdatePlatformCRC.MountAs("platformcrc:/");
	
		gTitleUpdatePlatform2.Init( GetPlatformTitleUpdate2Path(), false, tu2Device );
		gTitleUpdatePlatform2.MountAs( "platform:/" );

		gTitleUpdatePlatform2CRC.Init( GetPlatformTitleUpdate2Path(), true, overrideDevice );
		gTitleUpdatePlatform2CRC.MountAs( "platformcrc:/" );

		if(!PARAM_audiofolder.Get())
		{
			gTitleUpdateAudio.Init( GetAudioTitleUpdatePath(), true, overrideDevice);
			gTitleUpdateAudio.MountAs("audio:/");
			char audioSfxPath[RAGE_MAX_PATH] = { 0 };
			sprintf(audioSfxPath, "%s/%s", GetAudioTitleUpdatePath(), "sfx");
			gTitleUpdateAudioSFX.Init(audioSfxPath, true, overrideDevice);
			gTitleUpdateAudioSFX.MountAs("audio:/sfx/");
		}
	}
}

void CExtraContentManager::RemountUpdate()
{
	if(fiDevice::IsAnythingMountedAtMountPoint(TITLE_UPDATE_MOUNT_PATH))
	{
		// Unmount and then mount the folders again, now that we remounted the files on the disk
		fiDevice::Unmount(gTitleUpdateCommon);
		gTitleUpdateCommon.MountAs("common:/");
		fiDevice::Unmount(gTitleUpdateCommonCRC);
		gTitleUpdateCommonCRC.MountAs("commoncrc:/");
		//update2
		{
			fiDevice::Unmount( gTitleUpdateCommon2 );
			gTitleUpdateCommon2.MountAs( "common:/" );
			fiDevice::Unmount( gTitleUpdateCommonCRC2 );
			gTitleUpdateCommonCRC2.MountAs( "commoncrc:/" );
		}

		fiDevice::Unmount(gTitleUpdatePlatform);
		gTitleUpdatePlatform.MountAs("platform:/");
		fiDevice::Unmount(gTitleUpdatePlatformCRC);
		gTitleUpdatePlatformCRC.MountAs("platformcrc:/");

		fiDevice::Unmount( gTitleUpdatePlatform2 );
		gTitleUpdatePlatform2.MountAs( "platform:/" );
		fiDevice::Unmount( gTitleUpdatePlatform2CRC );
		gTitleUpdatePlatform2CRC.MountAs( "platformcrc:/" );

		if(!PARAM_audiofolder.Get())
		{
			fiDevice::Unmount(gTitleUpdateAudio);
			gTitleUpdateAudio.MountAs("audio:/");
			fiDevice::Unmount(gTitleUpdateAudioSFX);
			gTitleUpdateAudioSFX.MountAs("audio:/sfx/");
		}
	}
}

void CExtraContentManager::ExecuteTitleUpdateDataPatch(u32 changeSetHash, bool execute)
{
	if(CMountableContent *content = GetContentByHash(atStringHash(TITLE_UPDATE_PACK_NAME)))
	{
		BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = true;)

		if(execute)
		{
			ExecuteContentChangeSet(content->GetNameHash(),(u32)CCS_GROUP_ON_DEMAND, changeSetHash);
		}
		else
		{
			RevertContentChangeSet(content->GetNameHash(),(u32)CCS_GROUP_ON_DEMAND, changeSetHash);
		}

		BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = false;)
	}
}

void CExtraContentManager::ExecuteTitleUpdateDataPatchGroup(u32 changeSetHash, bool execute)
{

	for(int i=0;i<m_content.GetCount();++i)
	{
		CMountableContent& content = m_content[i]; 
		if(content.GetIsPermanent())
		{
			BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = true;)
			if(execute)
			{
				ExecuteContentChangeSetGroup(content.GetNameHash(),changeSetHash);
			}
			else
			{
				RevertContentChangeSetGroup(content.GetNameHash(),changeSetHash);
			}
			BANK_ONLY(strStreamingInfoManager::ms_bValidDlcOverlay = false;)
		}
	}
}

const char * GetDeviceTypeString (CMountableContent::eDeviceType deviceType)
{
	switch(deviceType)
	{
		case CMountableContent::DT_INVALID:
			return "DT_INVALID";
		case CMountableContent::DT_FOLDER:
			return "DT_FOLDER";
		case CMountableContent::DT_PACKFILE:
			return "DT_PACKFILE";
		case CMountableContent::DT_XCONTENT:
			return "DT_XCONTENT";
		case CMountableContent::DT_DLC_DISC:
			return "DT_DLC_DISC";
		default:
			return "INVALID";
	}
}

bool CExtraContentManager::GetPartOfCompatPack(const char* file)
{
	CMountableContent* content = GetContentByDevice(file);
	if(content)
		return content->GetIsCompatibilityPack()||content->GetIsPermanent();	
	else
		return false;
}

#if __BANK

#define MAX_CONTENT_LOCKS_PER_PAGE 10

static u32 CURR_CL_LIST[MAX_CONTENT_LOCKS_PER_PAGE] = { 0 };

bkBank* CExtraContentManager::ms_pBank = NULL;
bkCombo* CExtraContentManager::ms_pSelectedChangeSet = NULL;
bkCombo* CExtraContentManager::ms_pChangeSetGroups = NULL;
bkButton *CExtraContentManager::ms_pCreateBankButton = NULL;
bkToggle *CExtraContentManager::ms_pForcedCloudFilesStatus = NULL;
bkToggle *CExtraContentManager::ms_pForceManifestFail = NULL;
bkToggle *CExtraContentManager::ms_pForceCompatPackFail = NULL;
bkToggle *CExtraContentManager::ms_pForceLoadingScreen = NULL;
bkGroup* CExtraContentManager::ms_pBankContentStateGroup = NULL;
bkGroup* CExtraContentManager::ms_pBankContentLockGroup = NULL;
bkGroup* CExtraContentManager::ms_pSpecialTriggersGroup = NULL;
bkGroup* CExtraContentManager::ms_pBankExtraContentGroup = NULL;
bkList* CExtraContentManager::ms_pBankContentTable = NULL;
bkList* CExtraContentManager::ms_pBankVersionedContentTable = NULL;

bkGroup		*CExtraContentManager::ms_pBankFilesGroup = NULL;
bkList		*CExtraContentManager::ms_pBankInUseFilesTable = NULL;
bkList		*CExtraContentManager::ms_pBankUnusedFilesTable = NULL;
bkButton	*CExtraContentManager::ms_pBankFileListsPopulateButton = NULL;
bkButton	*CExtraContentManager::ms_pBankInUsePrevButton = NULL;
bkButton	*CExtraContentManager::ms_pBankInUseNextButton = NULL;
bkButton	*CExtraContentManager::ms_pBankUnusedPrevButton = NULL;
bkButton	*CExtraContentManager::ms_pBankUnusedNextButton = NULL;

int CExtraContentManager::ms_selectedContentIndex = -1;
int CExtraContentManager::ms_selectedChangeSetIndex = 0;
int CExtraContentManager::ms_changeSetGroupIndex = 0;
int CExtraContentManager::ms_displayedPackageCount = 0;
int CExtraContentManager::ms_displayedVersionedChangesetCount = 0;
bkList* CExtraContentManager::ms_contentLocksList = NULL;
atArray<const char *>	CExtraContentManager::m_workingDLC;							// for combo box
int						CExtraContentManager::m_workingDLCIndex;
s32 CExtraContentManager::m_currentContentLocksDebugPage = 0;
char CExtraContentManager::m_contentLockSearchString[RAGE_MAX_PATH] = { 0 };
char CExtraContentManager::m_contentLockCountString[RAGE_MAX_PATH] = { 0 };
char CExtraContentManager::m_matchMakingCrc[RAGE_MAX_PATH] = { 0 };
char CExtraContentManager::m_activeMapChange[RAGE_MAX_PATH] = { 0 };
char CExtraContentManager::m_currMapChangeStateStr[RAGE_MAX_PATH] = { 0 };

bool CExtraContentManager::ms_bForcedCloudFilesStatus = false;
bool CExtraContentManager::ms_bForcedCompatPackStatus = false;
bool CExtraContentManager::ms_bForcedLoadingScreenStatus = false;
bool CExtraContentManager::ms_bForcedManifestStatus = false;
bool CExtraContentManager::ms_bDoBankUpdate = false;


// PURPOSE:	"Normalise" filename by lowercasing, replacing `\' with `/', 
//			and morphing double slash into single slash.
// PARAMS:	filename - Name of file to "normalise" (including device ID)
// RETURNS:	atString of "normalised" filename
static atString NormaliseFileName(const char *src) {
	char outPath[RAGE_MAX_PATH];

	bool bWasSlash = false; // rudimentary double-slash stopper
	char *q = outPath;

	for (const char *p = src; *p != '\0'; ++p) {
		if (*p == '\\') {
			if (!bWasSlash) *q++ = '/';
			bWasSlash = true;
		} else if (*p == '/') {
			if (!bWasSlash) *q++ = '/';
			bWasSlash = true;
		} else {
			*q++ = (char)tolower(*p);
			bWasSlash = false;
		}
	}

	*q = '\0';

	return atString(outPath);
}

bool CExtraContentManager::GetAssetPathFromDevice(char* deviceName)
{
	if(CMountableContent* content = GetContentByDevice(deviceName))
	{
		char filePath[RAGE_MAX_PATH]={0};
		strcpy(filePath,content->m_filename);
		char* root = stristr(filePath,"build");
		if(Verifyf(root,"You are trying to access DLC assets while running with packaged DLC, if you are going to modify DLC assets, please use the DLC command lines instead"))
		{
			*root = 0;
			formatf(deviceName, RAGE_MAX_PATH, "%sassets_ng/",filePath);
			return true;
		}
	}
	return false;
}

void CExtraContentManager::ReturnAssetPathForFragTuneCB(char* assetPath, fragType* type)
{
	strStreamingInfo* strInfo = NULL;
	strLocalIndex strFileIdx = strLocalIndex(-1);
	char physicalPath[RAGE_MAX_PATH];
	for(int i = 0; i<g_FragmentStore.GetCount(); i++)
	{
		if(g_FragmentStore.Get(strLocalIndex(i)) == type)
		{
			strInfo = g_FragmentStore.GetStreamingInfo(strLocalIndex(i));
			strFileIdx = strPackfileManager::GetImageFileIndexFromHandle(strInfo->GetHandle());
			strStreamingFile* file = strPackfileManager::GetImageFile(strFileIdx.Get());
			if(Verifyf(file,"Streaming file could not be found for %s",type->GetBaseName()))
			{
				if(!strnicmp(file->m_name.TryGetCStr(),"dlc",3))
				{
					const fiDevice* device = fiDevice::GetDevice(file->m_name.TryGetCStr(),false);
					device->FixRelativeName(physicalPath,RAGE_MAX_PATH,file->m_name.TryGetCStr());
					char* end =  strstr(physicalPath,"/build/dev/");
					if(end)
					{
						*end  = '\0';
						strcat(physicalPath,"/assets/fragments");
						strcpy(assetPath,physicalPath);
					}
				}
				break;
			}
			else
			{
				return;
			}
		}
	}
	return;	
}

void CExtraContentManager::GetWorkingDLCDeviceName(char* deviceName)
{
	if(m_workingDLCIndex>=0&&m_workingDLCIndex<m_workingDLC.GetCount())
	{
		strcpy(deviceName,m_workingDLC[m_workingDLCIndex]);
	}
}

void CExtraContentManager::GetAssetPathForWorkingDLC(char* deviceName)
{
	if(m_workingDLCIndex==0)
	{
		strcpy(deviceName,"assets:/");
	}
	else
	{
		strcpy(deviceName,m_workingDLC[m_workingDLCIndex]);
		GetAssetPathFromDevice(deviceName);
	}
}

bkCombo* CExtraContentManager::AddWorkingDLCCombo(bkBank* bank)
{
	m_workingDLC.Reset();
	m_workingDLC.PushAndGrow("platform:/");
	for(int i=0;i<m_content.GetCount();i++)
	{
		m_workingDLC.PushAndGrow(m_content[i].GetDeviceName());
	}
	bkCombo* ret = bank->AddCombo("Current working DLC", &m_workingDLCIndex, m_workingDLC.GetCount(),m_workingDLC.GetElements(), datCallback(MFA(CExtraContentManager::CurrentWorkingDLCChanged),this));
	return ret;
}

void CExtraContentManager::CurrentWorkingDLCChanged()
{
	Displayf("DLC Combo: %d selected : %s", m_workingDLCIndex, m_workingDLC[m_workingDLCIndex]);
}

void CExtraContentManager::Bank_ExecuteBankUpdate()
{
	if(ms_bDoBankUpdate && !ms_pCreateBankButton)
	{
		for (u32 i = 0; i < ms_displayedPackageCount; i++)
			ms_pBankContentTable->RemoveItem(i);
		for (u32 i = 0; i < ms_displayedVersionedChangesetCount; i++)
			ms_pBankVersionedContentTable->RemoveItem(i);
		PopulateContentTableWidget();
		EXTRAMETADATAMGR.RefreshBankWidgets();
		DisplayContentCRC();
		Bank_UpdateActiveMapChangeDisplay();
		ms_bDoBankUpdate = false;
	}
}
void CExtraContentManager::Bank_UpdateContentDisplay()
{
	ms_bDoBankUpdate = true;
}

void CExtraContentManager::Bank_ExecuteSelectedChangeSet()
{
	if (CMountableContent* content = GetContent(ms_selectedContentIndex))
	{
		atHashString mapCCS = ms_pSelectedChangeSet->GetString(ms_selectedChangeSetIndex);
		atHashString ccsGroup = ms_pChangeSetGroups->GetString(ms_changeSetGroupIndex);

		if (mapCCS.IsNotNull() && ccsGroup.IsNotNull())
		{
			ExecuteContentChangeSet(content->GetNameHash(),ccsGroup,mapCCS);
			Bank_UpdateContentDisplay();
			ExecutePendingOverlays();
			EXTRAMETADATAMGR.RefreshBankWidgets();
		}
	}
	else
	{
		dlcDebugf2("Invalid DLC index for Bank_ExecuteSelectedChangeSet (%d)",ms_selectedContentIndex);
	}
}

void CExtraContentManager::Bank_RevertSelectedChangeSet()
{
	if (CMountableContent* content = GetContent(ms_selectedContentIndex))
	{
		atHashString mapCCS = ms_pSelectedChangeSet->GetString(ms_selectedChangeSetIndex);
		atHashString ccsGroup = ms_pChangeSetGroups->GetString(ms_changeSetGroupIndex);

		if (mapCCS.IsNotNull() && ccsGroup.IsNotNull())
		{
			RevertContentChangeSet(content->GetNameHash(),ccsGroup,mapCCS);
			Bank_UpdateContentDisplay();
			EXTRAMETADATAMGR.RefreshBankWidgets();
		}
	}
	else
	{
		dlcDebugf2("Invalid DLC index for Bank_RevertSelectedChangeSet (%d)",ms_selectedContentIndex);
	}
}

void CExtraContentManager::Bank_ExecuteSelectedGroup()
{
	if (CMountableContent* content = GetContent(ms_selectedContentIndex))
	{
		atHashString ccsGroup = ms_pChangeSetGroups->GetString(ms_changeSetGroupIndex);

		if (ccsGroup.IsNotNull())
		{
			ExecuteContentChangeSetGroup(content->GetNameHash(),ccsGroup);
			ExecutePendingOverlays();
			Bank_UpdateContentDisplay();
			EXTRAMETADATAMGR.RefreshBankWidgets();
		}
	}
	else
	{
		dlcDebugf2("Invalid DLC index for Bank_ExecuteSelectedGroup (%d)",ms_selectedContentIndex);
	}
}

void CExtraContentManager::Bank_RevertSelectedGroup()
{
	if (CMountableContent* content = GetContent(ms_selectedContentIndex))
	{
		atHashString ccsGroup = ms_pChangeSetGroups->GetString(ms_changeSetGroupIndex);

		if (ccsGroup.IsNotNull())
		{
			RevertContentChangeSetGroup(content->GetNameHash(),ccsGroup);
			Bank_UpdateContentDisplay();
			EXTRAMETADATAMGR.RefreshBankWidgets();
		}
	}
	else
	{
		dlcDebugf2("Invalid DLC index for Bank_RevertSelectedGroup (%d)",ms_selectedContentIndex);
	}
}

void CExtraContentManager::Bank_ChangeSetGroupChanged()
{
	if (CMountableContent* content = GetContent(ms_selectedContentIndex))
	{
		if (content->HasAnyChangesets())
		{
			atHashString selectedGroup = ms_pChangeSetGroups->GetString(ms_changeSetGroupIndex);
			atArray<const char*> changeSets;

			content->GetChangeSetsForGroup(selectedGroup, changeSets);

			ms_selectedChangeSetIndex = 0;
			ms_pSelectedChangeSet->UpdateCombo("Map Changes:", &ms_selectedChangeSetIndex, changeSets.GetCount(), &changeSets[0]);
		}
	}
}

void CExtraContentManager::Bank_PrintActiveMapChanges()
{
	Displayf("[DLC-AMC] - BEGIN ACTIVE MAP CHANGES");
	{
		atArray<u32> m_activeMapChangeHashes;

		for (u32 i = 0; i < m_content.GetCount(); i++)
			m_content[i].GetMapChangeHashes(m_activeMapChangeHashes);

		std::sort(m_activeMapChangeHashes.begin(), m_activeMapChangeHashes.end());

		for (u32 i = 0; i < m_activeMapChangeHashes.GetCount(); i++)
		{
			atHashString currHashName = m_activeMapChangeHashes[i];

			Displayf("[DLC-AMC] - 		%s", currHashName.GetCStr());
		}
	}
	Displayf("[DLC-AMC] - END ACTIVE MAP CHANGES");
}

void CExtraContentManager::Bank_UpdateActiveMapChangeDisplay()
{
	atArray<u32> mapChangeHashes;

	for (s32 i = 0; i < m_content.GetCount() ;i++)
		m_content[i].GetMapChangeHashes(mapChangeHashes);

	formatf(m_activeMapChange, "%i active map changes", mapChangeHashes.GetCount());
}

void CExtraContentManager::Bank_ApplyShouldActivateContentChangeState()
{
    int count = m_content.GetCount();
	
    for (int i = 0; i < count ;i++)
	{
        if (m_content[i].GetBankShouldExecute())
			ExecuteContentChangeSetGroup(m_content[i].GetNameHash(),CCS_GROUP_STARTUP);
		else
			RevertContentChangeSetGroup(m_content[i].GetNameHash(),CCS_GROUP_STARTUP);
	}
}

 void CExtraContentManager::Bank_ContentListClick(s32 index)
 {
     dlcDebugf3("Index %d selected in content list",index);

     ms_selectedContentIndex = index;

	 if (CMountableContent* content = GetContent(index))
	 {
		 if (content->HasAnyChangesets())
		 {
			 atArray<const char*> groupNames;

			 content->GetChangesetGroups(groupNames);
			 ms_changeSetGroupIndex = 0;
			 ms_pChangeSetGroups->UpdateCombo("ChangeSet Groups:", &ms_changeSetGroupIndex, groupNames.GetCount(), &groupNames[0], datCallback(MFA(CExtraContentManager::Bank_ChangeSetGroupChanged),&EXTRACONTENT));
			 Bank_ChangeSetGroupChanged();
		 }
	 }
 }

 void CExtraContentManager::Bank_ContentListDoubleClick(s32 index)
 {
	 if (CMountableContent* content = GetContent(index))
	 {
		 content->SetBankShouldExecute(!content->GetBankShouldExecute());
		 dlcDebugf3("Index %d in content list should activate set to %d", index, content->GetBankShouldExecute());
		 Bank_UpdateContentDisplay();
	 }
 }

 bool CExtraContentManager::BANK_EnableCloudWidgetsOverride()
 {
	return PARAM_enableCloudTestWidgets.Get();
 }

 void CExtraContentManager::InitBank()
{
	//Create the cloud bank
	
	// Create the weapons bank
	ms_pBank = &BANKMGR.CreateBank("ExtraContent", 0, 0, false); 

	if(dlcVerifyf(ms_pBank, "Failed to create Extra content bank"))
	{
		if(BANK_EnableCloudWidgetsOverride())
		{
			ms_bForcedCompatPackStatus=false;
			ms_bForcedCloudFilesStatus = false;
			ms_bForcedLoadingScreenStatus = true;
			ms_bForcedManifestStatus	= true;
			ms_pForceManifestFail = ms_pBank->AddToggle("Manifest Successfully Loaded?", &ms_bForcedManifestStatus);
			ms_pForceLoadingScreen = ms_pBank->AddToggle("Loading screens active?", &ms_bForcedLoadingScreenStatus);
			ms_pForcedCloudFilesStatus = ms_pBank->AddToggle("Cloud Files Loaded?", &ms_bForcedCloudFilesStatus);
			ms_pForceCompatPackFail = ms_pBank->AddToggle("Compatibility Pack Configuration correct?", &ms_bForcedCompatPackStatus);
		}
		ms_pCreateBankButton = ms_pBank->AddButton("Create Extra Content widgets", &CExtraContentManager::CreateBankWidgets);
	}
}

void CExtraContentManager::CreateBankWidgets()
{
	Assertf(ms_pBank, "Extra content bank needs to be created first");

	if(ms_pCreateBankButton) //delete the create bank button
	{
		ms_pCreateBankButton->Destroy();
		ms_pCreateBankButton = NULL;
	}
	else
	{
		//bank must already be setup as the create button doesn't exist so just return.
		return;
	}

	EXTRAMETADATAMGR.CreateBankWidgets(*ms_pBank);
	ms_pBank->PushGroup("Extra Content", false);
	{
		EXTRACONTENT.CreateSpecialTriggersWidgets(ms_pBank);
		EXTRACONTENT.CreateContentLocksWidgets(ms_pBank);
		EXTRACONTENT.CreateContentStatusWidgets(ms_pBank);
		EXTRACONTENT.CreateUnusedFilesWidgets(ms_pBank);
	}
	ms_pBank->PopGroup();

	g_LayoutManager.InitWidgets();
}

void CExtraContentManager::ShowPreviousContentLockPage()
{
	EXTRACONTENT.DisplayContentLocks(m_currentContentLocksDebugPage - 1);
}

void CExtraContentManager::ShowNextContentLockPage()
{
	EXTRACONTENT.DisplayContentLocks(m_currentContentLocksDebugPage + 1);
}

void ConcatCBIndices(SContentLockData* currentItem, char* strOutput)
{
	char strCatTemp[16] = { 0 };

	for (int i = 0; i < SContentLockData::MAX_CONTENT_LOCK_SUBSYSTEM_CBS; i++)
	{
		if (currentItem->m_callBackIndices & (1 << i))
		{
			formatf(strCatTemp, sizeof(strCatTemp), "%i,", i);
			strcat(strOutput, strCatTemp);
		}
	}
}

void CExtraContentManager::AddContentLockToBankList(u32 hash, SContentLockData* currentItem, s32 index)
{
	if (currentItem)
	{
		char strCBIndices[RAGE_MAX_PATH] = { 0 };
		atNonFinalHashString hashStr = hash;

		ms_contentLocksList->AddItem(hash, 0, hashStr.GetCStr());
		ms_contentLocksList->AddItem(hash, 1, currentItem->m_locked ? "true" : "false");

		ConcatCBIndices(currentItem, strCBIndices);

		ms_contentLocksList->AddItem(hash, 2, strCBIndices);

		if (Verifyf(index < MAX_CONTENT_LOCKS_PER_PAGE, "CExtraContentManager::AddContentLockToBankList - Index out of range! %u / %u", index, MAX_CONTENT_LOCKS_PER_PAGE))
			CURR_CL_LIST[index] = hash;
	}
}

void CExtraContentManager::DisplayContentLocks(s32 nextPage, u32 lookupItemHash/*=0*/)
{
	if (ms_contentLocksList)
	{
		s32 maxPage = 1;

		// Remove all the items first...
		for (u32 i = 0; i < MAX_CONTENT_LOCKS_PER_PAGE; i++)
		{
			if (CURR_CL_LIST[i] != 0)
				ms_contentLocksList->RemoveItem(CURR_CL_LIST[i]);
		}

		memset(CURR_CL_LIST, 0, sizeof(CURR_CL_LIST));

		if (m_contentLocks.GetCount() > 0)
			maxPage = ((m_contentLocks.GetCount() - 1) / MAX_CONTENT_LOCKS_PER_PAGE) + 1;

		m_currentContentLocksDebugPage = nextPage;
		m_currentContentLocksDebugPage = rage::Clamp(m_currentContentLocksDebugPage, 0, maxPage - 1);

		formatf(m_contentLockCountString, "Count: [%i] // Page: [%i / %i]", m_contentLocks.GetCount(), (m_currentContentLocksDebugPage + 1),  maxPage);

		if (lookupItemHash == 0)
		{
			int startIndex = m_currentContentLocksDebugPage * MAX_CONTENT_LOCKS_PER_PAGE;

			for (int i = startIndex; (i < (startIndex + MAX_CONTENT_LOCKS_PER_PAGE)) && (i < m_contentLocks.GetCount()); i++)
				AddContentLockToBankList(*m_contentLocks.GetKey(i), m_contentLocks.GetItem(i), i - startIndex);
		}
		else
		{
			AddContentLockToBankList(lookupItemHash, m_contentLocks.SafeGet(lookupItemHash), 0);
		}
	}
}

void CExtraContentManager::ContentLockListDblClickCB(s32 hash)
{
	if (SContentLockData* currentItem = m_contentLocks.SafeGet(hash))
		ModifyContentLockState(hash, !currentItem->m_locked);
}

void CExtraContentManager::LookupContentLock()
{
	DisplayContentLocks(0, atStringHash(m_contentLockSearchString));
}

void CExtraContentManager::DisplayContentCRC() 
{
	u32 initValue = 0;
	u32 mapChangeCRC = 0;

	initValue = EXTRACONTENT.GetCRC(initValue);
	mapChangeCRC = EXTRACONTENT.GetMapChangesCRC();

	formatf(m_matchMakingCrc, "Matchmaking: 0x%x, Map Change: 0x%x", initValue, mapChangeCRC);
}

void CExtraContentManager::Bank_InitMapChange()
{
	EXTRACONTENT.SetMapChangeState(MCS_INIT);
	Bank_UpdateMapChangeState();
}

void CExtraContentManager::Bank_EndMapChange()
{
	EXTRACONTENT.SetMapChangeState(MCS_END);
	Bank_UpdateMapChangeState();
}

void CExtraContentManager::Bank_UpdateMapChangeState()
{
	if (ms_pBankContentStateGroup)
	{
		static const char* mcStateNames[MCS_COUNT] = { "MCS_NONE",
														"MCS_INIT",
														"MCS_UPDATE",
														"MCS_END" };

		strcpy(m_currMapChangeStateStr, mcStateNames[m_currMapChangeState]);
	}
}

void CExtraContentManager::CreateContentStatusWidgets(bkBank* parentBank)
{
	const char* emptyData = "";

	if (ms_pBankContentStateGroup)
		parentBank->Remove(*ms_pBankContentStateGroup);

	ms_pBankContentStateGroup = ms_pBank->AddGroup("State",false);

	ms_pBankContentStateGroup->AddText("Content CRCs:", m_matchMakingCrc, sizeof(m_matchMakingCrc), true);
	ms_pBankContentStateGroup->AddButton("Refresh CRC", datCallback(MFA(CExtraContentManager::DisplayContentCRC), &EXTRACONTENT));
	ms_pBankContentStateGroup->AddButton("Refresh content display", datCallback(MFA(CExtraContentManager::Bank_UpdateContentDisplay), &EXTRACONTENT));

	ms_pBankContentStateGroup->AddText("Active Map Changes:", m_activeMapChange, sizeof(m_activeMapChange), true);
	ms_pBankContentStateGroup->AddButton("Print active map changes - Search for [DLC-AMC]", datCallback(MFA(CExtraContentManager::Bank_PrintActiveMapChanges), &EXTRACONTENT));
	ms_pBankContentStateGroup->AddText("Current Map Change State:", m_currMapChangeStateStr, sizeof(m_currMapChangeStateStr), true);
	ms_pBankContentStateGroup->AddToggle("Allow map change anytime:", &m_allowMapChangeAnyTime, NullCB);
	ms_pBankContentStateGroup->AddButton("Init map change", datCallback(MFA(CExtraContentManager::Bank_InitMapChange),&EXTRACONTENT));
	ms_pBankContentStateGroup->AddButton("End map change", datCallback(MFA(CExtraContentManager::Bank_EndMapChange),&EXTRACONTENT));

	ms_pChangeSetGroups = ms_pBankContentStateGroup->AddCombo("ChangeSet Groups:", &ms_changeSetGroupIndex, 0, &emptyData, datCallback(MFA(CExtraContentManager::Bank_ChangeSetGroupChanged),&EXTRACONTENT));
	ms_pSelectedChangeSet = ms_pBankContentStateGroup->AddCombo("ChangeSet:", &ms_selectedChangeSetIndex, 0, &emptyData);
	ms_pBankContentStateGroup->AddButton("Execute Selected ChangeSet", datCallback(MFA(CExtraContentManager::Bank_ExecuteSelectedChangeSet),&EXTRACONTENT));
	ms_pBankContentStateGroup->AddButton("Revert Selected ChangeSet", datCallback(MFA(CExtraContentManager::Bank_RevertSelectedChangeSet),&EXTRACONTENT));
	ms_pBankContentStateGroup->AddButton("Execute Selected Group", datCallback(MFA(CExtraContentManager::Bank_ExecuteSelectedGroup),&EXTRACONTENT));
	ms_pBankContentStateGroup->AddButton("Revert Selected Group", datCallback(MFA(CExtraContentManager::Bank_RevertSelectedGroup),&EXTRACONTENT));

	ms_pBankContentStateGroup->AddTitle("Double click items in the content list to set as Should-Activate");
	ms_pBankContentStateGroup->AddButton("Apply Should-Activate State", datCallback(MFA(CExtraContentManager::Bank_ApplyShouldActivateContentChangeState), &EXTRACONTENT));

	ms_pBankContentTable = ms_pBankContentStateGroup->AddList("Content");

	s32 column = 0;
	ms_pBankContentTable->AddColumnHeader(column++,"Name",bkList::STRING);
	ms_pBankContentTable->AddColumnHeader(column++,"Timestamp",bkList::STRING);
	ms_pBankContentTable->AddColumnHeader(column++,"CCS Should activate",bkList::STRING);
	ms_pBankContentTable->AddColumnHeader(column++,"CCS Active",bkList::STRING);
	ms_pBankContentTable->AddColumnHeader(column++,"Filename",bkList::STRING);
	ms_pBankContentTable->AddColumnHeader(column++,"DatFile",bkList::STRING);
	ms_pBankContentTable->AddColumnHeader(column++,"Uses Packfile",bkList::STRING);
	ms_pBankContentTable->AddColumnHeader(column++,"Device Name",bkList::STRING);
	ms_pBankContentTable->AddColumnHeader(column++,"Device Type",bkList::STRING);
	ms_pBankContentTable->AddColumnHeader(column++,"Status",bkList::STRING);

	bkList::ClickItemFuncType clickItemHandler;
	clickItemHandler.Reset <CExtraContentManager, &CExtraContentManager::Bank_ContentListClick> (&EXTRACONTENT);
	ms_pBankContentTable->SetSingleClickItemFunc(clickItemHandler);

	bkList::ClickItemFuncType doubleClickItemHandler;
	doubleClickItemHandler.Reset <CExtraContentManager, &CExtraContentManager::Bank_ContentListDoubleClick> (&EXTRACONTENT);
	ms_pBankContentTable->SetDoubleClickItemFunc(doubleClickItemHandler);

	ms_pBankVersionedContentTable = ms_pBankContentStateGroup->AddList("Versioned Changesets");
	column = 0;
	ms_pBankVersionedContentTable->AddColumnHeader(column++,"Versioned Changeset",bkList::STRING);
	ms_pBankVersionedContentTable->AddColumnHeader(column++,"Active Changeset",bkList::STRING);
	ms_pBankVersionedContentTable->AddColumnHeader(column++,"Active Content",bkList::STRING);
	ms_pBankVersionedContentTable->AddColumnHeader(column++,"Version",bkList::INT);
	ms_pBankVersionedContentTable->AddColumnHeader(column++,"State",bkList::STRING);
	ms_bDoBankUpdate = true;
	PopulateContentTableWidget();
	DisplayContentCRC();
	Bank_UpdateMapChangeState();
}

void CExtraContentManager::CreateUnusedFilesWidgets(bkBank* parentBank)
{
	if (ms_pBankFilesGroup)
		parentBank->Remove(*ms_pBankFilesGroup);

	ms_pBankFilesGroup = ms_pBank->AddGroup("Content Files", false); {
		ms_pBankFileListsPopulateButton = ms_pBankFilesGroup->AddButton("Populate All (will freeze game for ~10secs)", &CExtraContentManager::PopulateFileTableWidgets);

		ms_pBankInUseFilesTable = ms_pBankFilesGroup->AddList("All Opened Files");
		ms_pBankInUseFilesTable->AddColumnHeader(0, "Absolute Path", bkList::STRING);

		ms_pBankInUsePrevButton = ms_pBankFilesGroup->AddButton("<--", &CExtraContentManager::PrevPageOfOpenFiles, "Previous", 0, true);
		ms_pBankInUseNextButton = ms_pBankFilesGroup->AddButton("-->", &CExtraContentManager::NextPageOfOpenFiles, "Next", 0, true);
		
		ms_pBankUnusedFilesTable = ms_pBankFilesGroup->AddList("Unused Files");
		ms_pBankUnusedFilesTable->AddColumnHeader(0, "Absolute Path", bkList::STRING);

		ms_pBankUnusedPrevButton = ms_pBankFilesGroup->AddButton("<--", &CExtraContentManager::PrevPageOfUnusedFiles, "Previous", 0, true);
		ms_pBankUnusedNextButton = ms_pBankFilesGroup->AddButton("-->", &CExtraContentManager::NextPageOfUnusedFiles, "Next", 0, true);
	}
}

void CExtraContentManager::PopulateContentTableWidget()
{
	const char * states[sOverlayInfo::NUM_OVERLAY_STATES] = {"INACTIVE","WILL_ACTIVATE","ACTIVE"};
	CompileTimeAssert(NELEM(states) == sOverlayInfo::NUM_OVERLAY_STATES);
	ms_displayedPackageCount = m_content.GetCount();

	for (u32 i = 0; i < m_content.GetCount(); i++)
	{
		int column = 0;
		CMountableContent& mount = m_content[i];

		char tempStr[RAGE_MAX_PATH] = { 0 };
		formatf(tempStr, RAGE_MAX_PATH, "%u/%u", mount.GetActiveChangeSetCount(), mount.GetTotalContentChangeSetCount());

		ms_pBankContentTable->AddItem(i,column++,mount.GetName());
		ms_pBankContentTable->AddItem(i,column++,mount.GetTimeStamp());
		ms_pBankContentTable->AddItem(i,column++,mount.GetBankShouldExecute()?"YES":"NO");
		ms_pBankContentTable->AddItem(i,column++,tempStr);
		ms_pBankContentTable->AddItem(i,column++,mount.GetFilename());
		ms_pBankContentTable->AddItem(i,column++,mount.GetDatFileName());
		ms_pBankContentTable->AddItem(i,column++,mount.GetUsesPackfile()?"YES":"NO");
		ms_pBankContentTable->AddItem(i,column++,mount.GetDeviceName());
		ms_pBankContentTable->AddItem(i,column++,GetDeviceTypeString(mount.GetPrimaryDeviceType()));
		ms_pBankContentTable->AddItem(i,column++,CMountableContent::GetContentStatusName((mount.GetStatus())));
	}
	ms_displayedVersionedChangesetCount = m_overlayInfo.GetCount();
	for(int i=0;i<m_overlayInfo.GetCount();i++)
	{
		int column = 0;
		sOverlayInfo* info = m_overlayInfo[i];
		CMountableContent* content = GetContentByHash(m_overlayInfo[i]->m_content);
		Assertf(content, "Mountable content doesn't exist!");
		ms_pBankVersionedContentTable->AddItem(i,column++,info->m_nameId.TryGetCStr());
		ms_pBankVersionedContentTable->AddItem(i,column++,info->m_changeSet.TryGetCStr());
		ms_pBankVersionedContentTable->AddItem(i,column++,content?content->GetName():"");
		ms_pBankVersionedContentTable->AddItem(i,column++,info->m_version);
		ms_pBankVersionedContentTable->AddItem(i,column++,states[info->m_state]);
	}
}

void CExtraContentManager::PopulateFileTableWidgets()  {
	// Remove populate button
	ms_pBankFileListsPopulateButton->Destroy();
	ms_pBankFileListsPopulateButton = NULL;

	// Check param first
	if (!PARAM_checkUnusedFiles.Get()) {
		ms_pBankInUseFilesTable->AddItem(0, 0, "Re-run with -checkUnusedFiles to populate them here");
		ms_pBankUnusedFilesTable->AddItem(0, 0, "Re-run with -checkUnusedFiles to populate them here");

		return;
	}
	
	// Add files requested		
	for (auto it = g_RequestedFiles.Begin(); it != g_RequestedFiles.End(); ++it) {
		const char *originalPath = (*it).c_str();

		// work out absolute path and store
		char noPlatformPercent[RAGE_MAX_PATH];
		DATAFILEMGR.ExpandFilename(originalPath, noPlatformPercent, RAGE_MAX_PATH);

		atString finalPathString = NormaliseFileName(noPlatformPercent);
		atHashString hashed = atStringHash(finalPathString);

		if (!s_loadedFilesAbsolute.Has(hashed)) {
			s_loadedFilesAbsolute.Insert(hashed, finalPathString);
		}
	}

	s_loadedFilesPageCount = (int)rage::FPCeil((float)s_loadedFilesAbsolute.GetCount() / (float)SHOW_FILES_PER_PAGE);
	PopulateOpenFilesAtPage(0);

	// Add files existing on disk
	for (auto it = g_RequestedDevices.Begin(); it != g_RequestedDevices.End(); ++it) {
		const char *deviceName = (*it).c_str();			
		
		// we only really care about platform:, common:, dlc*:, and update:
		if (strnicmp(deviceName, "platform:",	9) != 0 &&
			strnicmp(deviceName, "common:",		7) != 0 &&
			strnicmp(deviceName, "dlc",			3) != 0 && // no colon on purpose
			strnicmp(deviceName, "update:",		7) != 0) {
				continue;
		}
		
		// add slash
		char deviceAndSlash[RAGE_MAX_PATH] = { 0 };
		strncpy(deviceAndSlash, deviceName, RAGE_MAX_PATH);
		deviceAndSlash[strlen(deviceName)] = '/';

		// find device
		if (const fiDevice *device = fiDevice::GetDevice(deviceAndSlash)) {
			atBinaryMap<atString, u32> allTheFiles;
			GetDeviceContentsRecursive(allTheFiles, deviceName); // SLOOOOOW

			for (auto it = allTheFiles.Begin(); it != allTheFiles.End(); ++it) {
				const char *file = (*it).c_str();
				
				// Compute absolute file path
				char fullPath[RAGE_MAX_PATH];
				char absFile[RAGE_MAX_PATH];
				device->FixRelativeName(absFile, RAGE_MAX_PATH, file);
				DATAFILEMGR.ExpandFilename(absFile, fullPath, RAGE_MAX_PATH);
				
				atString strFileName = NormaliseFileName(fullPath);
				atHashString strFileNameHash = atStringHash(strFileName);

				const char *fileName = strFileName.c_str();

				// Is this file open? If not, add to list!
				if (!s_loadedFilesAbsolute.Has(strFileNameHash)) {
					const char *colonPtr = strchr(fileName, ':');
					size_t deviceLen = colonPtr - fileName;
					
					if (deviceLen > 1) {
						// this is not an X:\ or C:\ path, ignore
						continue;
					}

					if (!s_unusedFilesAbsolute.Has(strFileNameHash)) {
						s_unusedFilesAbsolute.Insert(strFileNameHash, strFileName);
					}
				}
			}
		}
	} 

	// Populate Unused 
	s_unusedFilesPageCount = (int)rage::FPCeil((float)s_unusedFilesAbsolute.GetCount() / (float)SHOW_FILES_PER_PAGE);
	PopulateUnusedFilesAtPage(0);
}

void CExtraContentManager::PopulateOpenFilesAtPage(u32 pageNum) {
	s_loadedFilesPageNumber = rage::Min(pageNum, s_loadedFilesPageCount - 1);

	u32 startIndex = s_loadedFilesPageNumber * SHOW_FILES_PER_PAGE;

	auto it = s_loadedFilesAbsolute.Begin();

	// Fast-forward to start index of page
	while (startIndex --> 0 && it != s_loadedFilesAbsolute.End()) {
		++it;
	}
		
	// Run for up to SHOW_FILES_PER_PAGE files
	u32 i = 0;
	for ( ; it != s_loadedFilesAbsolute.End() && i < SHOW_FILES_PER_PAGE; ++it) {
		const char *finalPath = (*it).c_str();

		ms_pBankInUseFilesTable->AddItem(i++, 0, finalPath);
	}

	ms_pBankInUsePrevButton->SetReadOnly(pageNum == 0);
	ms_pBankInUseNextButton->SetReadOnly(pageNum == s_loadedFilesPageCount - 1);
}

void CExtraContentManager::PrevPageOfOpenFiles() {
	if (s_loadedFilesPageNumber == 0) {
		return; 
	}

	PopulateOpenFilesAtPage(s_loadedFilesPageNumber-1);
}
void CExtraContentManager::NextPageOfOpenFiles() {
	if (s_loadedFilesPageNumber == s_loadedFilesPageCount - 1) {
		return;
	}
	
	PopulateOpenFilesAtPage(s_loadedFilesPageNumber+1);
}

void CExtraContentManager::PopulateUnusedFilesAtPage(u32 pageNum) {
	s_unusedFilesPageNumber = rage::Min(pageNum, s_unusedFilesPageCount - 1);

	u32 startIndex = s_unusedFilesPageNumber * SHOW_FILES_PER_PAGE;

	auto it = s_unusedFilesAbsolute.Begin();

	// Fast-forward to start index of page
	while (startIndex --> 0 && it != s_unusedFilesAbsolute.End()) {
		++it;
	}

	// Run for up to SHOW_FILES_PER_PAGE files
	u32 i = 0;
	for ( ; it != s_unusedFilesAbsolute.End() && i < SHOW_FILES_PER_PAGE; ++it) {
		const char *finalPath = (*it).c_str();

		ms_pBankUnusedFilesTable->AddItem(i++, 0, finalPath);
	}

	ms_pBankUnusedPrevButton->SetReadOnly(pageNum == 0);
	ms_pBankUnusedNextButton->SetReadOnly(pageNum == s_unusedFilesPageCount - 1);
}

void CExtraContentManager::PrevPageOfUnusedFiles() {
	if (s_unusedFilesPageNumber == 0) {
		return; 
	}

	PopulateUnusedFilesAtPage(s_unusedFilesPageNumber-1);
}
void CExtraContentManager::NextPageOfUnusedFiles() {
	if (s_unusedFilesPageNumber == s_unusedFilesPageCount- 1) {
		return;
	}

	PopulateUnusedFilesAtPage(s_unusedFilesPageNumber+1);
}


void CExtraContentManager::CreateContentLocksWidgets(bkBank* parentBank)
{
	if (ms_pBankContentLockGroup)
		parentBank->Remove(*ms_pBankContentLockGroup);

	ms_pBankContentLockGroup = parentBank->AddGroup("Content Locks",false);
	bkList::ClickItemFuncType contentLockDblClickCB;
	contentLockDblClickCB.Reset<CExtraContentManager, &CExtraContentManager::ContentLockListDblClickCB>(this);

	ms_pBankContentLockGroup->AddText("Indices:", m_contentLockCountString, sizeof(m_contentLockCountString), true);
	ms_pBankContentLockGroup->AddText("Lookup:", m_contentLockSearchString, sizeof(m_contentLockSearchString), false, datCallback(MFA(CExtraContentManager::LookupContentLock), (datBase*)this));

	ms_contentLocksList = ms_pBankContentLockGroup->AddList("Content Locks (double click to toggle lock)");
	ms_contentLocksList->SetDoubleClickItemFunc(contentLockDblClickCB);
	ms_contentLocksList->AddColumnHeader(0, "Name", bkList::STRING);
	ms_contentLocksList->AddColumnHeader(1, "Locked", bkList::STRING);
	ms_contentLocksList->AddColumnHeader(2, "Callback Index", bkList::STRING);

	ms_pBankContentLockGroup->AddButton("<< Previous Page", &ShowPreviousContentLockPage);
	ms_pBankContentLockGroup->AddButton("Next Page >>", &ShowNextContentLockPage);
	DisplayContentLocks(0);
}

void CExtraContentManager::Bank_UpdateSpecialTriggers(eSpecialTrigger trigger)
{
	SetSpecialTrigger(trigger, !GetSpecialTrigger(trigger));
}

void CExtraContentManager::CreateSpecialTriggersWidgets(bkBank* parentBank)
{
	if (ms_pSpecialTriggersGroup)
		parentBank->Remove(*ms_pSpecialTriggersGroup);

	ms_pSpecialTriggersGroup = parentBank->AddGroup("Special Triggers",false);
	ms_pSpecialTriggersGroup->AddSlider("Special Triggers: ", EXTRACONTENT.BANK_GetSpecialTriggers(), 0, 0, 0);
	ms_pSpecialTriggersGroup->AddButton("Toggle ST_XMAS", datCallback(MFA1(CExtraContentManager::Bank_UpdateSpecialTriggers), &EXTRACONTENT, (void*)ST_XMAS));
	//bank.AddButton("Simplify 10%", datCallback(MFA1(demeshView::SimplifyPercentCb), this, (void*)10));
}

#endif // __BANK

class MetricCCRC : public MetricPlayStat
{
	RL_DECLARE_METRIC(CODE_CRC, TELEMETRY_CHANNEL_MISC, LOGLEVEL_VERYHIGH_PRIORITY);
};

#if RSG_ORBIS
void CExtraContentManager::ContentPoll(void* UNUSED_PARAM(ptr))
{
	// TODO: Change this for an event when Sony add one.
	// Poll because SCE_SYSTEM_SERVICE_EVENT_ENTITLEMENT_UPDATE only happens when entitlements change not each time DLC is installed.
	// https://ps4.scedev.net/forums/thread/33348/
	// This is really lame, I want this code to die and one day soon it will.
	SceNpServiceLabel serviceLabel = 0;
	SceAppContentAddcontInfo packages[SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM];
	u32 packageCount = 0;
	s32 errValue = 0;
	s32 prevInstalled = 0;
	s32 currInstalled = 0;

	memset(packages, 0, sizeof(SceAppContentAddcontInfo) * SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM);

	while (true)
	{
		if (!PARAM_disableEntitlementCheck.Get() && !PARAM_usecompatpacks.Get() && !PARAM_extracontent.Get())
		{
			errValue = sceAppContentGetAddcontInfoList(serviceLabel, packages, SCE_APP_CONTENT_ADDCONT_MOUNT_MAXNUM, &packageCount);

			if (Verifyf(errValue == SCE_OK, "Update - Failed to get package count! %i", errValue))
			{
				currInstalled = 0;

				// Assume install only, not accounting for deletion
				for (u32 i = 0; i < packageCount; i++)
				{
					if (packages[i].status == SCE_APP_CONTENT_ADDCONT_DOWNLOAD_STATUS_INSTALLED)
						currInstalled++;
				}

				if (currInstalled != prevInstalled)
					EXTRACONTENT.OnContentDownloadCompleted();

				prevInstalled = currInstalled;
			}
		}

		sysIpcSleep(5000);
	}
}
#endif

void CExtraContentManager::CodeCheck(void* UNUSED_PARAM(ptr))
{
#if RSG_PS3
    const char* codeStart = (const char*)&__start__Ztext[0];
    const s32 codeSize = (s32)(&__stop__Ztext[0] - &__start__Ztext[0]);
#elif RSG_XENON
    const char* codeStart = (const char*)&__start__Ztext;
    const s32 codeSize = (s32)(&__stop__Ztext - &__start__Ztext);
#endif

#if RSG_PS3 || RSG_XENON
    sysTimer timer;
    const u32 initialKey = fwKeyGen::GetKey(codeStart, codeSize);
    Displayf("[CODE_CHECK] Initial code crc duration: %fs, code: %d", timer.GetTime(), initialKey);

    u32 testKey = 0;

	if (NetworkInterface::IsGameInProgress())
	{
		StatId crcStat("CODE_CRC");
		if (StatsInterface::IsKeyValid(crcStat)) //are stats loaded?
			StatsInterface::SetStatData(crcStat, initialKey);
	}
	else
	{
		StatId crcStat("SP_CODE_CRC");
		if (StatsInterface::IsKeyValid(crcStat)) //are stats loaded?
			StatsInterface::SetStatData(crcStat, initialKey);
	}

    const u32 numBatches = 8;
    const u32 batchSize = ((codeSize / numBatches) + 3) & ~3;
    const u32 lastBatchSize = codeSize - (batchSize * (numBatches - 1));
    const u32 timeBetweenChecks = 1 * 60 * 1000; // run check every 1 min
    u32 numBatch = 8;
    u32 nextCheckTime = fwTimer::GetTimeInMilliseconds() + timeBetweenChecks;
    while (s_codeCheckRun)
	{
        if (numBatch < numBatches)
		{
            if (CNetwork::IsGameInProgress())
			{
				// do next batch
				timer.Reset();
				Displayf("[CODE_CHECK] Doing batch %d of %d...", numBatch + 1, numBatches);

				if (numBatch == 0)
					testKey = fwKeyGen::GetKey(codeStart, batchSize);
				else if (numBatch == numBatches - 1)
					testKey = fwKeyGen::AppendDataToKey(testKey, codeStart + (batchSize * numBatch), lastBatchSize);
				else
					testKey = fwKeyGen::AppendDataToKey(testKey, codeStart + (batchSize * numBatch), batchSize);

				dlcDisplayf("[CODE_CHECK] Batch %d of %d completed in %fs, key: %d", numBatch + 1, numBatches, timer.GetTime(), testKey);
				numBatch++;

				if (numBatch == numBatches)
				{
					nextCheckTime = fwTimer::GetTimeInMilliseconds() + timeBetweenChecks;
					if (testKey != initialKey)
					{
						if (!s_codeCompromised)
						{
							MetricCCRC m;
							CNetworkTelemetry::AppendMetric(m);
						}

						Warningf("[CODE_CHECK] Code crc verification failed! Initial key: %d, latest key: %d", initialKey, testKey);
						s_codeCompromised = true;
					}
				}
			}
		}
		else
		{
			u32 curTime = fwTimer::GetTimeInMilliseconds();
			if (nextCheckTime < curTime)
                numBatch = 0;
		}

        sysIpcSleep(1000);
	}
#endif
}

bool CExtraContentManager::IsCodeCompromised()
{
    return s_codeCompromised;
}

#if GTA_REPLAY

void CExtraContentManager::SetReplayState(const u32 *extraContentHashes, u32 hashCount)
{
	SYS_CS_SYNC(m_replayLock);
	Assert(extraContentHashes);
	Assert(hashCount < MAX_EXTRACONTENTHASHES);
	m_replayChangeSetHashCount = Min(hashCount, MAX_EXTRACONTENTHASHES);
	memcpy(m_replayChangeSetHashes, extraContentHashes, m_replayChangeSetHashCount * sizeof(u32));
}

void CExtraContentManager::ResetReplayState()
{
	SYS_CS_SYNC(m_replayLock);
	memset(m_replayChangeSetHashes, 0, MAX_EXTRACONTENTHASHES * sizeof(u32));
	m_replayChangeSetHashCount = 0;
}

void CExtraContentManager::ExecuteReplayMapChanges()
{
	SYS_CS_SYNC(m_replayLock);

	atArray<u32> activeMapChangeHashes;
	GetMapChangeArray(activeMapChangeHashes);

	bool diff = activeMapChangeHashes.GetCount() != (int) m_replayChangeSetHashCount;

	if(!diff)
	{
		for(int i = 0; i < activeMapChangeHashes.GetCount(); i++)
		{
			if(activeMapChangeHashes[i] != m_replayChangeSetHashes[i])
			{
				diff = true;
				break;
			}
		}
	}

	if(!diff)
		return;

	RevertCurrentMapChanges();

	for(u32 i = 0; i < m_replayChangeSetHashCount; i++)
	{
		u32 changeSetHash = m_replayChangeSetHashes[i];

		if(CMountableContent *c = CMountableContent::GetContentForChangeSet(changeSetHash))
		{
			if(u32 group = c->GetChangeSetGroup(changeSetHash))
			{
				Assert((group == CCS_GROUP_MAP) || (group == CCS_GROUP_MAP_SP));
				ExecuteContentChangeSetInternal(c, group, changeSetHash, ECCS_FLAG_USE_LOADING_SCREEN);
			}
		}
	}

	if(m_replayChangeSetHashCount > 0)
	{
		CMountableContent::CleanupAfterMapChange();
	}
}

void CExtraContentManager::RevertCurrentMapChanges()
{
	atArray<u32> activeMapChangeHashes;
	GetMapChangeArray(activeMapChangeHashes);

	for(u32 i = 0; i < activeMapChangeHashes.GetCount(); i++)
	{
		u32 changeSetHash = activeMapChangeHashes[i];

		if(CMountableContent *c = CMountableContent::GetContentForChangeSet(changeSetHash))
		{
			if(u32 group = c->GetChangeSetGroup(changeSetHash))
			{
				Assert((group == CCS_GROUP_MAP) || (group == CCS_GROUP_MAP_SP));
				RevertContentChangeSetInternal(c, group, changeSetHash, CDataFileMgr::ChangeSetAction::CCS_ALL_ACTIONS, ECCS_FLAG_USE_LOADING_SCREEN);
			}
		}
	}
}

#endif // GTA_REPLAY


