#include "scene/DataFileMgr.h" #include "scene/DataFileMgr_parser.h" // Rage headers #include "fwsys/fileExts.h" #include "parser/manager.h" #include "parsercore/stream.h" #include "streaming/streamingengine.h" #include "streaming/streaminginfo.h" #include "streaming/streamingmodule.h" #include "streaming/streamingmodulemgr.h" #include "string/stringutil.h" #include "system/param.h" #include "system/platform.h" #include "system/nelem.h" #include "scene/dlc_channel.h" #include "scene/ExtraContent.h" #include "scene/scene.h" #include "control/leveldata.h" #include "control/gamelogic.h" #include "file/device_relative.h" eDFMI_UnloadType CDataFileMount::sm_UnloadBehaviours[CDataFileMgr::INVALID_FILE]; atRangeArray CDataFileMount::sm_Interfaces; bool CDataFileMount::sm_overrideUnload = true; eDFMI_UnloadType CDataFileMount::sm_unloadBehaviour = eDFMI_UnloadDefault; XPARAM(usefatcuts); bool CDataFileMgr::ChangeSetData::IsValid(bool executing) const { bool returnValue = true; // Check if this changeset has any map associations if (m_associatedMap.length() > 0) { s32 currLevelIdx = CGameLogic::GetCurrentLevelIndex(); s32 reqLevelIdx = CGameLogic::GetRequestedLevelIndex(); s32 associatedMapIdx = CScene::GetLevelsData().GetLevelIndexFromTitleName(m_associatedMap.c_str()); // If there's no transition just check for association if (currLevelIdx == reqLevelIdx) returnValue = associatedMapIdx == currLevelIdx; else { // There's some form of transition, only allow changes for the current map to be reverted if (associatedMapIdx == currLevelIdx) returnValue = !executing; else returnValue = associatedMapIdx == reqLevelIdx; } if (!returnValue) { NOTFINAL_ONLY(Displayf("ChangeSetData::IsValid - change set is not valid due to map association with [%s] [%s]", m_associatedMap.c_str(), CScene::GetLevelsData().GetFriendlyName(associatedMapIdx));) } } return returnValue; } strIndex CDataFileMgr::ResourceReference::Resolve() const { // Get the module first. char extension[sizeof(m_Extension)]; memcpy(extension, m_Extension, sizeof(m_Extension)); if (extension[0] == '#') { extension[0] = g_sysPlatform; } strStreamingModule *module = strStreamingEngine::GetInfo().GetModuleMgr().GetModuleFromFileExtension(extension); if (module) { strLocalIndex slot = module->FindSlot(m_AssetName.c_str()); if (slot != -1) { return module->GetStreamingIndex(slot); } } Assertf(false, "Unable to resolve %s.%s", m_AssetName.c_str(), m_Extension); return strIndex(); } CDataFileMgr::CDataFileMgr() { // Set up the environment variables. m_EnvVariableMap.Insert(ATSTRINGHASH("PLATFORM", 0x02fbe482d), ConstString(RSG_PLATFORM_ID)); m_addingPatchFiles = false; } #if __BANK bool CDataFileMgr::GetFatcuts() { return PARAM_usefatcuts.Get(); } #endif // __BANK FASTRETURNCHECK(const char *) CDataFileMgr::RemapDataFileName(const char *origFile, char *NOTFINAL_ONLY(outTargetBuffer), size_t NOTFINAL_ONLY(targetBufferSize)) { #if !__FINAL if (PARAM_usefatcuts.Get()) { const char *filename = ASSET.FileName(origFile); size_t string_length_of_path = filename - origFile; if (PARAM_usefatcuts.Get()) { Displayf("Testing %s", filename); if (stricmp(filename, "common_cutscene.meta") == 0) { memcpy(outTargetBuffer, origFile, Min(string_length_of_path, targetBufferSize)); outTargetBuffer[Min(string_length_of_path, targetBufferSize)] = 0; safecat(outTargetBuffer, "/", targetBufferSize); safecat(outTargetBuffer, "common_cutscene_fat.meta", targetBufferSize); return outTargetBuffer; } else if (stricmp(filename, "cuts.meta") == 0) { memcpy(outTargetBuffer, origFile, Min(string_length_of_path, targetBufferSize)); outTargetBuffer[Min(string_length_of_path, targetBufferSize)] = 0; safecat(outTargetBuffer, "/", targetBufferSize); safecat(outTargetBuffer, "cuts_fat.meta", targetBufferSize); return outTargetBuffer; } } } #endif // !__FINAL return origFile; } #if __ASSERT void CDataFileMgr::VerifyXmlContents(ContentsOfDataFileXml& XmlContents, const char* pFilename) const { // Fill temporary array with change sets' enabled and disabled files. atArray tempArray; for(int j=0; jm_filesToEnable.GetCount(); ++i) { tempArray.PushAndGrow(pChangeSet->m_filesToEnable[i].c_str()); } for(int i=0; im_filesToDisable.GetCount(); ++i) { tempArray.PushAndGrow(pChangeSet->m_filesToDisable[i].c_str()); } for(int k=0; km_mapChangeSetData.GetCount(); ++k) { const ChangeSetData* pData = &(pChangeSet->m_mapChangeSetData[k]); for(int i=0; im_filesToEnable.GetCount(); ++i) { tempArray.PushAndGrow(pData->m_filesToEnable[i].c_str()); } for(int i=0; im_mapChangeSetData[k].m_filesToDisable.GetCount(); ++i) { tempArray.PushAndGrow(pData->m_filesToDisable[i].c_str()); } } } // Check for actual data file dupes. Let's hope not. for (int i = 0; i < XmlContents.m_dataFiles.GetCount(); ++i) { for (int j = i+1; j < XmlContents.m_dataFiles.GetCount(); ++j) { bool bFilesMatch = (strnicmp(XmlContents.m_dataFiles[i].m_filename, XmlContents.m_dataFiles[j].m_filename, 128) == 0); Assertf(!bFilesMatch, "Duplicate data file '%s' in file '%s' (indices %i and %i)", XmlContents.m_dataFiles[i].m_filename, pFilename, i, j); } } // Check if content.xml belongs to DLC device or title update. bool isDLCorUpdate = ( !strnicmp(pFilename, "DLC", 3) || !strnicmp(pFilename, "update", 6) ); if(isDLCorUpdate) { // Automatically add the content.xml's folder to allowed ones. AddDefaultAllowedFolders(XmlContents, pFilename); } // Check the temporary array for issues. for(int i=0; i(pChar-pFilename)+1); } if(const char* pChar = stristr(pFilename, "CRC:")) { atString &pStr = XmlContents.m_allowedFolders.Grow(); pStr.Set(pFilename, RAGE_MAX_PATH, 0, static_cast(pChar-pFilename)); pStr += ":"; } } bool CDataFileMgr::IsInAllowedFolder(const ContentsOfDataFileXml& XmlContents, const char* filePath) const { // If there are no specified folders then assume the file is setup correctly (for backwards compatibility) if(XmlContents.m_allowedFolders.empty()) { return true; } // Check if file lies within specified folders. for(int k=0; km_fileType); if( !strnicmp(pFile->m_filename, "commoncrc:", 10) && !strnicmp(dataFile->m_filename, "dlc", 3) ) { char device[RAGE_MAX_PATH] = {0}; strncpy(device, dataFile->m_filename, static_cast(strcspn(dataFile->m_filename, "/")) + 1); bool isCompatPack = EXTRACONTENT.GetContentByDevice(device)->GetIsCompatibilityPack(); if(!stristr(dataFile->m_filename, "CRC:") && isCompatPack) { return false; } } return true; } #endif // __ASSERT void CDataFileMgr::Load(const char *pFilename, bool bCullOtherPlatformData /*= true*/) { ContentsOfDataFileXml XmlContents; char FilenameWithoutExtension[256]; ASSET.RemoveExtensionFromPath(FilenameWithoutExtension, 256, pFilename); parSettings s = PARSER.Settings(); s.SetFlag(parSettings::CULL_OTHER_PLATFORM_DATA, bCullOtherPlatformData); //@@: location CDATAFILEMGR_LOAD_META_EXISTS if (ASSET.Exists(FilenameWithoutExtension, "meta")) { PARSER.LoadObject(FilenameWithoutExtension, "meta", XmlContents, &s); } else if (ASSET.Exists(pFilename, META_FILE_EXT)) { Verifyf(fwPsoStoreLoader::LoadDataIntoObject(pFilename, META_FILE_EXT, XmlContents), "Failed to load %s", pFilename); } else { PARSER.LoadObject(FilenameWithoutExtension, "xml", XmlContents, &s); } #if __ASSERT // Check contents of content.xml for errors. VerifyXmlContents(XmlContents, pFilename); #endif // __ASSERT for(int i=0;im_filesToEnable.GetCount() == changeSet.m_filesToEnable.GetCount()) { char highOrderFile[256] = { 0 }; char lowOrderFile[256] = { 0 }; char* highOrderFileNoDevice = NULL; char* lowOrderFileNoDevice = NULL; for (u32 i = 0; i < higherOrderCCS->m_filesToEnable.GetCount(); i++) { formatf(highOrderFile, sizeof(highOrderFile), "%s", higherOrderCCS->m_filesToEnable[i].c_str()); formatf(lowOrderFile, sizeof(lowOrderFile), "%s", changeSet.m_filesToEnable[i].c_str()); highOrderFileNoDevice = strstr(highOrderFile, ":/"); lowOrderFileNoDevice = strstr(lowOrderFile, ":/"); if (strcmp(highOrderFileNoDevice, lowOrderFileNoDevice) != 0) { Assertf(0, "CDataFileMgr::Load - %s CCSs Collide but they are not identical, check order of files! [%s vs %s]", changeSet.m_changeSetName.c_str(), highOrderFileNoDevice, lowOrderFileNoDevice); break; } } } else { Assertf(0, "CDataFileMgr::Load - %s CCSs Collide but they are not identical, check file counts! [%i vs %i]", changeSet.m_changeSetName.c_str(), higherOrderCCS->m_filesToEnable.GetCount(), changeSet.m_filesToEnable.GetCount()); } #endif atHashString newCollision = changeSet.m_changeSetName.c_str(); dlcDebugf3("CDataFileMgr::Load - There is a collision between CCSs [%s]", changeSet.m_changeSetName.c_str()); m_changeSetCollisions.PushAndGrow(newCollision, 1); for (u32 i = 0; i < changeSet.m_filesToEnable.GetCount(); i++) { for (u32 j = 0; j < XmlContents.m_dataFiles.GetCount(); j++) { if (strcmp(XmlContents.m_dataFiles[j].m_filename, changeSet.m_filesToEnable[i].c_str()) == 0) { dlcDebugf3("CDataFileMgr::Load - Deleting file reference [%s]", XmlContents.m_dataFiles[j].m_filename); XmlContents.m_dataFiles.Delete(j); j--; } } } XmlContents.m_contentChangeSets.DeleteFast(x); x--; } } s32 index = 0; const s32 numOfDisabledFiles = XmlContents.m_disabledFiles.GetCount(); while(index < numOfDisabledFiles) { // Printf("CDataFileMgr::Load - index = %d disabled_filename = %s\n", index, XmlContents.m_disabledFiles[index].c_str()); AddDisabledFile(XmlContents.m_disabledFiles[index].c_str()); index++; } const s32 numOfIncludedDataFiles = XmlContents.m_includedDataFiles.GetCount(); for (int x=0; x& archives) { if (ContentChangeSet* ccs = m_contentChangeSets.Access(changeSetName)) { s32 imgIdx = -1; char expandedFilename[RAGE_MAX_PATH] = { 0 }; for (s32 i = 0; i < ccs->m_filesToEnable.GetCount(); i++) { DATAFILEMGR.ExpandFilename(ccs->m_filesToEnable[i].c_str(), expandedFilename, sizeof(expandedFilename)); imgIdx = strPackfileManager::FindImage(expandedFilename); if (imgIdx != -1 && archives.Access(imgIdx) == NULL) archives.Insert(imgIdx, true); } for (s32 i = 0; i < ccs->m_mapChangeSetData.GetCount(); i++) { ChangeSetData& currData = ccs->m_mapChangeSetData[i]; if (currData.IsValid(execute)) { for (s32 j = 0; j < currData.m_filesToEnable.GetCount(); j++) { DATAFILEMGR.ExpandFilename(currData.m_filesToEnable[j].c_str(), expandedFilename, sizeof(expandedFilename)); imgIdx = strPackfileManager::FindImage(expandedFilename); if (imgIdx != -1 && archives.Access(imgIdx) == NULL) archives.Insert(imgIdx, true); } } } } } void CDataFileMgr::DestroyDependentsGraph() { strStreamingEngine::GetInfo().DestroyDependentGraphs(); } void CDataFileMgr::CreateDependentsGraph(atHashString changeSetName, bool execute) { if (ContentChangeSet* ccs = m_contentChangeSets.Access(changeSetName)) { atArray filesToEnableCache; atArray filesToInvalidateCache; s32 imgIdx = -1; char expandedFilename[RAGE_MAX_PATH] = { 0 }; for (s32 i = 0; i < ccs->m_mapChangeSetData.GetCount(); i++) { ChangeSetData& currData = ccs->m_mapChangeSetData[i]; if (currData.IsValid(execute)) { for (s32 j = 0; j < currData.m_filesToEnable.GetCount(); j++) { DATAFILEMGR.ExpandFilename(currData.m_filesToEnable[j].c_str(), expandedFilename, sizeof(expandedFilename)); imgIdx = strPackfileManager::FindImage(expandedFilename); if (imgIdx != -1) { filesToEnableCache.PushAndGrow(imgIdx, 1); } } for (s32 j = 0; j < currData.m_filesToInvalidate.GetCount(); j++) { DATAFILEMGR.ExpandFilename(currData.m_filesToInvalidate[j].c_str(), expandedFilename, sizeof(expandedFilename)); imgIdx = strPackfileManager::FindImage(expandedFilename); if (imgIdx != -1) { if (const atArray* associatedFiles = strStreamingEngine::GetInfo().GetPackfileRelationships(imgIdx)) { for (s32 k = 0; k < associatedFiles->GetCount(); k++) { imgIdx = (*associatedFiles)[k]; if (imgIdx != -1) filesToInvalidateCache.PushAndGrow(imgIdx, (u16)associatedFiles->GetCount()); } } else { filesToInvalidateCache.PushAndGrow(imgIdx, 1); } } } } } strStreamingEngine::GetInfo().CreateDependentsGraph(filesToEnableCache); strStreamingEngine::GetInfo().CreateDependentsGraph(filesToInvalidateCache); } } void CDataFileMgr::Unload(const char *pFilename, bool bCullOtherPlatformData /*= true*/) { ContentsOfDataFileXml XmlContents; char FilenameWithoutExtension[256]; ASSET.RemoveExtensionFromPath(FilenameWithoutExtension, 256, pFilename); parSettings s = PARSER.Settings(); s.SetFlag(parSettings::CULL_OTHER_PLATFORM_DATA, bCullOtherPlatformData); if (ASSET.Exists(FilenameWithoutExtension, "meta")) PARSER.LoadObject(FilenameWithoutExtension, "meta", XmlContents, &s); else PARSER.LoadObject(FilenameWithoutExtension, "xml", XmlContents, &s); s32 index = 0; const s32 numOfDisabledFiles = XmlContents.m_disabledFiles.GetCount(); while(index < numOfDisabledFiles) { RemoveDisabledFile(XmlContents.m_disabledFiles[index].c_str()); index++; } index = 0; const s32 numOfDataFiles = XmlContents.m_dataFiles.GetCount(); while(index < numOfDataFiles) { RemoveFile(XmlContents.m_dataFiles[index].m_filename); index++; } s32 xml_file_index = 0; const s32 numOfIncludedXmlFiles = XmlContents.m_includedXmlFiles.GetCount(); while(xml_file_index < numOfIncludedXmlFiles) { index = 0; const s32 numOfDataFilesInThisXmlFile = XmlContents.m_includedXmlFiles[xml_file_index].m_dataFiles.GetCount(); while (index < numOfDataFilesInThisXmlFile) { const DataFile &dataFile = XmlContents.m_includedXmlFiles[xml_file_index].m_dataFiles[index]; RemoveFile(dataFile.m_filename); index++; } xml_file_index++; } for (int x = 0; x < XmlContents.m_contentChangeSets.GetCount(); x++) { ContentChangeSet& changeSet = XmlContents.m_contentChangeSets[x]; m_contentChangeSets.Delete(changeSet.m_changeSetName.c_str()); } } const CDataFileMgr::ContentChangeSet *CDataFileMgr::GetContentChangeSet(atHashString contentChangeSetName) const { return m_contentChangeSets.Access(contentChangeSetName); } CDataFileMgr::DataFile *CDataFileMgr::FindDataFile(atHashString fileName) { int count = m_dataFiles.GetCount(); for (int x=0; x 0) { char chr = *(filename++); if (chr != '%') { *(outBuffer++) = chr; bufferSize--; } else { // This could be the beginning or end of a variable. if (!lastVarBegin) { // It's the beginning of a variable. lastVarBegin = outBuffer; *(outBuffer++) = chr; bufferSize--; } else { // It's the end of a variable. Let's look it up. // If it's two % signs right after one another, let's assume the user intended to // to escape a single % sign. if (lastVarBegin + 1 != outBuffer) { // We still need to terminate the buffer, it could contain data if we previously expanded // a longer variable name with a shorter string. *outBuffer = 0; ConstString *var = m_EnvVariableMap.Access(atStringHash(&lastVarBegin[1])); // If this doesn't resolve to a variable, just leave the string untouched, as DOS would do. if (var) { // It IS a proper variable, so replace it. size_t varLength = strlen(var->c_str()); bufferSize += outBuffer - lastVarBegin; strncpy(lastVarBegin, var->c_str(), bufferSize); bufferSize = Max((size_t) 0, bufferSize - varLength); outBuffer = lastVarBegin + varLength; } } lastVarBegin = NULL; } } } *outBuffer = 0; } void CDataFileMgr::RemoveMapDataFiles() { atArray dataFilesToKeep; s32 numberOfEntries = m_dataFiles.GetCount(); s32 loop = 0; for (loop = 0; loop < numberOfEntries; loop++) { if (m_dataFiles[loop].m_persistent) { dataFilesToKeep.PushAndGrow(m_dataFiles[loop]); } else { switch (m_dataFiles[loop].m_fileType) { case RPF_FILE : case IDE_FILE : case DELAYED_IDE_FILE : case IPL_FILE : case PERMANENT_ITYP_FILE : // don't copy these types across to dataFilesToKeep break; default: dataFilesToKeep.PushAndGrow(m_dataFiles[loop]); break; } } } // Fix up the m_handle of all the entries in dataFilesToKeep numberOfEntries = dataFilesToKeep.GetCount(); for (loop = 0; loop < numberOfEntries; loop++) { dataFilesToKeep[loop].m_handle = loop; } m_dataFiles.Reset(); m_dataFiles = dataFilesToKeep; // Also clear the array of disabled files? m_disabledFiles.Reset(); } void CDataFileMgr::Clear() { m_dataFiles.Reset(); m_disabledFiles.Reset(); m_LockedFilesCount = 0; m_LockedDisabledFilesCount = 0; } void CDataFileMgr::SnapshotFiles() { m_LockedFilesCount = m_dataFiles.GetCount(); m_LockedDisabledFilesCount = m_disabledFiles.GetCount(); } void CDataFileMgr::RecallSnapshot() { m_dataFiles.Resize(m_LockedFilesCount); m_disabledFiles.Resize(m_LockedDisabledFilesCount); } #if __BANK void CDataFileMount::DebugOutputTable() { Displayf("============================================="); Displayf("======= CDataFileMount interface table ======"); Displayf("============================================="); #if PARSER_ALL_METADATA_HAS_NAMES for(int i=0; i < sm_Interfaces.GetMaxCount(); ++i) { Displayf("[%s]: %s", parser_DataFileType_Strings[i], sm_Interfaces[i]? "valid":"NULL"); } #else Displayf("CAN'T DISPLAY LIST IF !PARSER_ALL_METADATA_HAS_NAMES"); #endif Displayf("============================================="); } #endif