///////////////////////////////////////////////////////////////////////////////// // // FILE : TextFile.cpp // PURPOSE : Deals with reading text from the text files // AUTHOR : KRH 13/05/98 // NICKED BY : Obbe. (Thanks Keith) // NICKED FURTHER BY: Derek Payne - modifying all this 17/04/09 // ///////////////////////////////////////////////////////////////////////////////// // NOTES - this is still work in progress - a lot of stuff still needs shifting around // C headers #include // Rage headers #include "string\stringhash.h" #include "system/criticalsection.h" // Game headers #include "Text\TextConversion.h" #include "Text\TextFile.h" #include "Text\Text.h" #include "chunk.h" #include "cutscene/cutscene_channel.h" #include "cutscene/cutscenemanagernew.h" #include "cutscene/CutSceneAssetManager.h" #include "debug\Debug.h" #include "frontend\pausemenu.h" #include "SaveLoad/GenericGameStorage.h" #include "scene/datafilemgr.h" #include "script\script.h" #include "system\filemgr.h" #include "system/param.h" #include "system/memory.h" #include "system/system.h" #include "fwsys/timer.h" #include "text\messages.h" RAGE_DEFINE_CHANNEL(text); #undef __rage_channel #define __rage_channel text #ifdef _DEBUG const s32 WrappedTextMaxWidth = 640; #endif TEXT_OPTIMISATIONS() //OPTIMISATIONS_OFF() XPARAM(langfilesuffix); CTextFile TheText; // For all our text needs // matches sysLanguage static const char* g_LanguageFiles[] = { "american", // LANGUAGE_ENGLISH=0, "french", // LANGUAGE_FRENCH, "german", // LANGUAGE_GERMAN, "italian", // LANGUAGE_ITALIAN, "spanish", // LANGUAGE_SPANISH, "portuguese", // LANGUAGE_PORTUGUESE, "polish", // LANGUAGE_POLISH, "russian", // LANGUAGE_RUSSIAN, "korean", // LANGUAGE_KOREAN, "chinese", // LANGUAGE_CHINESE_TRADITIONAL, "japanese", // LANGUAGE_JAPANESE, "mexican", // LANGUAGE_MEXICAN, "chinesesimp" // LANGUAGE_CHINESE_SIMPLIFIED }; enum { TEXT_UNUSED, TEXT_REQUESTED, TEXT_LOADED }; char ErrorString[2][64]; sysCriticalSectionToken g_textFileAccessCS; CTextFile::CTextFile() { m_TextBlockContainingTheLastStringReturnedByGetFunction = -1; m_LanguageCode = 'e'; // default to English m_bUsesMultipleTextChunks = false; m_bTextIsLoaded = false; memset(&m_Text[0], 0, sizeof(m_Text)); } CTextFile::~CTextFile() { if (m_bTextIsLoaded) Unload(); m_extraTextMetafiles.Reset(); } char *CTextFile::Get(const char *textcode) { if (!textcode) { if (CTheScripts::GetCurrentGtaScriptThread()) Assertf(0, "%s:CTextFile::Get - text key is a NULL pointer", CTheScripts::GetCurrentScriptNameAndProgramCounter()); else Assertf(0, "CTextFile::Get - text key is a NULL pointer - current script thread is NULL"); } if (!textcode || textcode[0] == 0 || textcode[0] == 32) { bool isRender = CSystem::IsThisThreadId(SYS_THREAD_RENDER); ErrorString[isRender][0] = 0; return ErrorString[isRender]; } return Get(atStringHash(textcode), textcode); } struct SearchByHash { bool operator()( const CGxt2Entry& a, u32 b ) const { return a.Hash < b; } }; char* CTextFile::SearchForText(u32 hashcode, CGxt2Header* textSlot) { CGxt2Entry *start = textSlot->Entries, *end = textSlot->Entries + textSlot->Count; CGxt2Entry *r = std::lower_bound(start,end,hashcode,SearchByHash()); // The end test almost isn't necessary because there's a dummy entry with a hash code of GXT2 in it // that is extremely unlikely to ever match anything :-) if (r != end && r->Hash == hashcode) { return (char*)textSlot + r->Offset; } return NULL; } // These should really be const char* or const char* but a lot of calling code needs to be repaired first. char *CTextFile::GetInternal(u32 hashcode) { sysCriticalSection criticalSection(g_textFileAccessCS); m_TextBlockContainingTheLastStringReturnedByGetFunction = -1; #if __ALLOW_EXPORT_OF_SP_SAVEGAMES const char *pCloudText = CGenericGameStorage::SearchForSaveGameMigrationCloudText(hashcode); if (pCloudText) { return const_cast(pCloudText); } #endif // __ALLOW_EXPORT_OF_SP_SAVEGAMES // Search for updated error text first char** updatedErrorText = m_updatedErrorText.Access(hashcode); if(updatedErrorText) { if (*updatedErrorText) { m_TextBlockContainingTheLastStringReturnedByGetFunction = GTA5_GLOBAL_TEXT_SLOT; return *updatedErrorText; } } #if __BANK char *pGxtString = m_DebugStrings.GetString(hashcode); if (pGxtString) { return pGxtString; } #endif // __BANK char** dlcText = m_extraGlobals.Access(hashcode); if(dlcText) { if (*dlcText) { m_TextBlockContainingTheLastStringReturnedByGetFunction = GTA5_GLOBAL_TEXT_SLOT; return *dlcText; } } // Search Global text slots first char* foundText = NULL; if (m_TextState[GTA5_GLOBAL_TEXT_SLOT]==TEXT_LOADED && m_Text[GTA5_GLOBAL_TEXT_SLOT]) { #if !__FINAL if (!sysMemAllocator::GetMaster().GetAllocator(MEMTYPE_GAME_VIRTUAL)->GetSize(m_Text[GTA5_GLOBAL_TEXT_SLOT])) Quitf("CTextFile::m_Text is accessing invalid memory: %p (slot=%d)", m_Text[GTA5_GLOBAL_TEXT_SLOT], GTA5_GLOBAL_TEXT_SLOT); #endif foundText = SearchForText(hashcode,m_Text[GTA5_GLOBAL_TEXT_SLOT]); if(foundText) { m_TextBlockContainingTheLastStringReturnedByGetFunction = GTA5_GLOBAL_TEXT_SLOT; return foundText; } } // Search all additional text slots for (int i=0; iGetSize(m_Text[i])) Quitf("CTextFile::m_Text is accessing invalid memory: %p (slot=%d)", m_Text[i], i); #endif foundText = SearchForText(hashcode,m_Text[i]); if(foundText) { m_TextBlockContainingTheLastStringReturnedByGetFunction = i; return foundText; } } } return NULL; } char *CTextFile::Get(u32 hashcode,const char *NOTFINAL_ONLY(textcode)) { #if __BANK if (bShowTextIdOnly) { const char* pAttempt = textcode && textcode[0] != '\0' ? textcode : atHashString::TryGetString(hashcode); bool isRender = CSystem::IsThisThreadId(SYS_THREAD_RENDER); formatf(ErrorString[isRender],"%s",pAttempt); return ErrorString[isRender]; } #endif // __BANK char *result = GetInternal(hashcode); if (result) return result; #if __ASSERT if (textcode != NULL && hashcode != ATSTRINGHASH("Streetname",0x2dad6942)) { // Willie asked me to take this assert out for Streetnames but it's still useful for other text char fileName[RAGE_MAX_PATH]; GetLanguageFile(fileName, sizeof(fileName), false, TEXT_SOURCE_DISK); rageDisplayf("CTextFile::Get - %s label missing from %s GXT file", textcode, fileName); rageAssertf(0, "CTextFile::Get - %s label missing from %s GXT file", textcode, fileName); } #endif // __ASSERT bool isRender = CSystem::IsThisThreadId(SYS_THREAD_RENDER); #if __FINAL ErrorString[isRender][0] = 0; #else // __FINAL formatf(ErrorString[isRender],"%s missing",textcode); #endif // __FINAL return ErrorString[isRender]; } bool CTextFile::DoesTextLabelExist(const char *Ident) { return GetInternal(atStringHash(Ident)) != 0; } bool CTextFile::DoesTextLabelExist(u32 hashcode) { return GetInternal(hashcode) != 0; } bool CTextFile::IsDLCDevice(const char* deviceName) const { return (strnicmp(deviceName, "dlc", 3) == 0); } const char* CTextFile::GetLanguageFile(char *buffer, size_t bufferSize, bool bUseSystemLanguage, eSource eTextSource, bool isDLCpatch) { u32 iLanguage; const char *suffix = ""; PARAM_langfilesuffix.Get(suffix); #if __PACKEDDEBUG suffix = "_rel"; #endif if (bUseSystemLanguage) { iLanguage = CPauseMenu::GetLanguageFromSystemLanguage(); // If system language isnt available use English if(iLanguage == LANGUAGE_UNDEFINED) iLanguage = LANGUAGE_ENGLISH; } else { iLanguage = CPauseMenu::GetMenuPreference(PREF_CURRENT_LANGUAGE); } switch(eTextSource) { case TEXT_SOURCE_DISK: formatf(buffer, bufferSize, "platform:/data/lang/%s%s.rpf", g_LanguageFiles[iLanguage], suffix ); break; case TEXT_SOURCE_TU: formatf(buffer, bufferSize, "update:/" RSG_PLATFORM_ID "/data/lang/%s%s.rpf", g_LanguageFiles[iLanguage], suffix ); break; case TEXT_SOURCE_TU2: formatf( buffer, bufferSize, "update2:/" RSG_PLATFORM_ID "/data/lang/%s%s.rpf", g_LanguageFiles[iLanguage], suffix ); break; case TEXT_SOURCE_PATCH: formatf(buffer, bufferSize, "/patch/data/lang/%s%s.rpf", g_LanguageFiles[iLanguage], (isDLCpatch ? "" : suffix) ); break; case TEXT_SOURCE_DLC: formatf(buffer, bufferSize, "/data/lang/%sdlc.rpf", g_LanguageFiles[iLanguage] ); break; } return buffer; } void CTextFile::AppendToExtraGlobals(const char* deviceName, bool remove, bool titleUpdate) { fiPackfile* packFile = rage_aligned_new(16) fiPackfile; char fileName[RAGE_MAX_PATH]; GetLanguageFile(fileName,sizeof(fileName),false,titleUpdate?TEXT_SOURCE_PATCH:TEXT_SOURCE_DLC, IsDLCDevice(deviceName)); char finalTarget[RAGE_MAX_PATH]; formatf(finalTarget,sizeof(finalTarget),"%s:/%%PLATFORM%%%s",deviceName,fileName); DATAFILEMGR.ExpandFilename(finalTarget,fileName,sizeof(fileName)); if (!Verifyf(packFile->Init(fileName,true,fiPackfile::CACHE_NONE),"Extra content localization pack file missing: %s",fileName)) { delete packFile; packFile = NULL; return; } packFile->MountAs("dlcTempText:/"); safecpy(fileName,"dlcTempText:/global.gxt2"); CGxt2Header* text; u32 fileSize; pgStreamer::Handle h = pgStreamer::Open(fileName,&fileSize,0); if (h == pgStreamer::Error) { Errorf("CTextFile - Cannot find %s",fileName); delete packFile; packFile = NULL; return; } text = (CGxt2Header*) rage_aligned_new(16) char[fileSize]; datResourceChunk destList = { 0, text, fileSize }; Displayf("Loading DLC Globals from device : %s", deviceName); sysIpcSema s = sysIpcCreateSema(false); while (pgStreamer::Read(h,&destList,1,0,s,0) == NULL) sysIpcSleep(10); sysIpcWaitSema(s); sysIpcDeleteSema(s); Displayf("Finished loading DLC Globals"); CGxt2Entry *start = text->Entries, *end = text->Entries + text->Count; for(CGxt2Entry* entry = start, *nextEntry = start+1; entry!= end;entry++,nextEntry++) { bool alreadyExists = m_extraGlobals.Has(entry->Hash); if(!remove&&!alreadyExists) { u32 entryLen = nextEntry->Offset-entry->Offset; char* temp = rage_aligned_new(16) char[entryLen]; safecpy(temp,(char*)text+entry->Offset, entryLen); m_extraGlobals.Insert(entry->Hash, temp); } else if(remove && alreadyExists) { int idx = 0; // Need to do a linear search because m_extraGlobals may not be sorted, so SafeGet, Access, etc. won't work for(atBinaryMap::Iterator iter = m_extraGlobals.Begin(); iter != m_extraGlobals.End(); ++iter) { if (iter.GetKey() == entry->Hash) { delete *iter; m_extraGlobals.Remove(idx); break; } idx++; } } } m_extraGlobals.FinishInsertion(); delete[] (char*)text; fiDevice::Unmount(*packFile); delete packFile; } void CTextFile::SetUpdatedErrorText(CGxt2Header* text, bool remove) { if(text == NULL && remove) { for(atBinaryMap::Iterator it=m_updatedErrorText.Begin(); it != m_updatedErrorText.End();++it) { char*& text = *it; delete[] text; text = NULL; } m_updatedErrorText.Reset(); return; } CGxt2Entry *start = text->Entries, *end = text->Entries + text->Count; for(CGxt2Entry* entry = start, *nextEntry = start+1; entry!= end;entry++,nextEntry++) { bool alreadyExists = m_updatedErrorText.Has(entry->Hash); if(!remove&&!alreadyExists) { u32 entryLen = nextEntry->Offset-entry->Offset; char* temp = rage_aligned_new(16) char[entryLen]; safecpy(temp,(char*)text+entry->Offset, entryLen); m_updatedErrorText.Insert(entry->Hash, temp); } else if(remove && alreadyExists) { int idx = 0; // Need to do a linear search because m_updatedErrorText may not be sorted, so SafeGet, Access, etc. won't work for(atBinaryMap::Iterator iter = m_updatedErrorText.Begin(); iter != m_updatedErrorText.End(); ++iter) { if (iter.GetKey() == entry->Hash) { delete *iter; m_updatedErrorText.Remove(idx); break; } idx++; } } } } void CTextFile::RemoveExtracontentTextFromMetafile(const char* fileName) { atHashString fileNameHash = atHashString(fileName); if(m_extraTextMetafiles.Access(fileNameHash)) { sysCriticalSection criticalSection(g_textFileAccessCS); atHashString deviceNameHash = atHashString(m_extraTextMetafiles[fileNameHash]->m_deviceName); if(m_extraText.Access(deviceNameHash)) { //Don't remove extra globals //AppendToExtraGlobals(m_extraTextMetafiles[fileNameHash]->m_deviceName,true,m_extraTextMetafiles[fileNameHash]->m_isTitleUpdate); m_extraText[deviceNameHash]->Shutdown(); m_extraText.Delete(deviceNameHash); } m_extraTextMetafiles.Delete(fileNameHash); } } void CTextFile::AddExtracontentTextFromMetafile(const char* fileName) { atHashString fileNameHash = atHashString(fileName); if(!m_extraTextMetafiles.Access(fileNameHash)) { sysCriticalSection criticalSection(g_textFileAccessCS); CExtraTextMetaFile* metaFile = rage_new CExtraTextMetaFile; if(PARSER.LoadObject(fileName,NULL,*metaFile)) { metaFile->m_deviceName.Set(fileName,RAGE_MAX_PATH,0,static_cast(strcspn(fileName,":"))); AddExtracontentText(metaFile->m_deviceName,metaFile->m_hasAdditionalText,metaFile->m_hasGlobalTextFile, metaFile->m_isTitleUpdate); m_extraTextMetafiles.Insert(fileNameHash,metaFile); } else { delete metaFile; } } } void CTextFile::AddExtracontentText(const char* deviceName, bool hasAdditionalTextSlots, bool hasGlobalTextSlot, bool isTitleUpdate) { if(!m_extraText.Access(atHashString(deviceName))) { CExtraTextFile* extraText = rage_aligned_new(16) CExtraTextFile(hasAdditionalTextSlots,hasGlobalTextSlot, isTitleUpdate); extraText->Init(deviceName); m_extraText.Insert(atHashString(deviceName),extraText); } } void CTextFile::RemoveExtracontentText(const char* deviceName) { atHashString deviceNameHash = atHashString(deviceName); CExtraTextFile** textFile = m_extraText.Access(deviceNameHash); if(textFile) { //Don't remove globals // if((*textFile)->GetHasGlobalText()) // AppendToExtraGlobals(deviceName,true,(*textFile)->GetIsTitleUpdate()); (*textFile)->Shutdown(); m_extraText.Delete(deviceNameHash); } } CExtraTextFile::CExtraTextFile(bool hasAdditionalTextSlots, bool hasGlobalTextSlot, bool isTitleUpdate) :m_bIsLoaded(false),m_bHasAdditionalTextSlots(hasAdditionalTextSlots), m_bHasGlobalTextSlot(hasGlobalTextSlot),m_bIsTitleUpdate(isTitleUpdate),m_PackFile(NULL) { } CExtraTextFile::~CExtraTextFile() { Shutdown(); } void CExtraTextFile::Shutdown() { if(m_bHasAdditionalTextSlots) { if(m_PackFile) { fiDevice::Unmount(*m_PackFile); delete (m_PackFile); m_PackFile = NULL; } } m_bIsLoaded = false; } void CExtraTextFile::Init(const char* deviceName) { if(m_bHasGlobalTextSlot) { TheText.AppendToExtraGlobals(deviceName,false,m_bIsTitleUpdate); } if(m_bHasAdditionalTextSlots) { m_bIsLoaded = false; m_PackFile = rage_aligned_new(16) fiPackfile; char fileName[RAGE_MAX_PATH]; TheText.GetLanguageFile(fileName,sizeof(fileName),false,m_bIsTitleUpdate?CTextFile::TEXT_SOURCE_PATCH:CTextFile::TEXT_SOURCE_DLC, TheText.IsDLCDevice(deviceName)); char finalTarget[RAGE_MAX_PATH]; formatf(finalTarget,sizeof(finalTarget),"%s:/%%PLATFORM%%%s",deviceName,fileName); DATAFILEMGR.ExpandFilename(finalTarget,fileName,sizeof(fileName)); if (!Verifyf(m_PackFile->Init(fileName,true,fiPackfile::CACHE_NONE),"Extra content localization pack file missing: %s",fileName)) { delete m_PackFile; m_PackFile = NULL; return; } formatf(m_mountName,sizeof(m_mountName),"dlcTXT%u:/",atHashString(deviceName).GetHash()); m_PackFile->MountAs(m_mountName); } m_bIsLoaded = true; } int CTextFile::GetLanguageFromName(const char* pLanguage) { for(int i=0; iInit(fileName,true,fiPackfile::CACHE_NONE)) { delete m_Packfile; m_Packfile = NULL; return; } m_Packfile->MountAs("language:/"); m_bTextIsLoaded = true; LoadTextIntoSlot(GTA5_GLOBAL_TEXT_SLOT, "GLOBAL", true, TEXT_LOCATION_GTA5_WITH_POSSIBLE_PATCH); for(atMap::Iterator it=m_extraTextMetafiles.CreateIterator(); !it.AtEnd();it.Next()) { CExtraTextMetaFile*& text = it.GetData(); AddExtracontentText(text->m_deviceName,text->m_hasAdditionalText,text->m_hasGlobalTextFile,text->m_isTitleUpdate); } } void CTextFile::Unload(bool unloadExtra /* = false */) { // Make sure that any messages being displayed are removed. CMessages::ClearAllMessagesDisplayedByGame(); if(!unloadExtra) { m_bTextIsLoaded = false; for (int i=0; i::Iterator it=m_extraText.CreateIterator(); !it.AtEnd();it.Next()) { CExtraTextFile*& text = it.GetData(); text->Shutdown(); } for(atBinaryMap::Iterator it=m_extraGlobals.Begin(); it != m_extraGlobals.End();++it) { char*& text = *it; delete[] text; text = NULL; } for(atBinaryMap::Iterator it=m_updatedErrorText.Begin(); it != m_updatedErrorText.End();++it) { char*& text = *it; delete[] text; text = NULL; } m_extraText.Reset(); m_extraGlobals.Reset(); m_updatedErrorText.Reset(); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFile::ReloadAfterLanguageChange() // PURPOSE: deals with reloading text after a language change and reloads the slots // that may be loaded in aswell ///////////////////////////////////////////////////////////////////////////////////// void CTextFile::ReloadAfterLanguageChange() { s32 slot_loop; // check and store locally what text is currently loading into what slots: char textBlockLoaded[GTA5_GLOBAL_TEXT_SLOT][MISSION_NAME_LENGTH]; u8 textStatus[GTA5_GLOBAL_TEXT_SLOT]; for (slot_loop = 0; slot_loop < GTA5_GLOBAL_TEXT_SLOT; slot_loop++) { strncpy(textBlockLoaded[slot_loop], m_TextBlockLoadedIntoThisSlot[slot_loop], MISSION_NAME_LENGTH); textStatus[slot_loop] = m_TextState[slot_loop]; } // we are now safe to delete the text: Load(); // reload the main text (this also unloads any current text slots, so now they are gone) // because Load() deletes the text slots, we need to reload them again: for (slot_loop = 0; slot_loop < GTA5_GLOBAL_TEXT_SLOT; slot_loop++) { // re-load the additional text slot: if (textStatus[slot_loop] == TEXT_LOADED) { LoadTextIntoSlot(slot_loop,textBlockLoaded[slot_loop], true, slot_loop>=DLC_TEXT_SLOT0?TEXT_LOCATION_ADDED_FOR_DLC:TEXT_LOCATION_GTA5_WITH_POSSIBLE_PATCH); } // re-request the additional text slot: if (textStatus[slot_loop] == TEXT_REQUESTED) { LoadTextIntoSlot(slot_loop,textBlockLoaded[slot_loop], false, slot_loop>=DLC_TEXT_SLOT0?TEXT_LOCATION_ADDED_FOR_DLC:TEXT_LOCATION_GTA5_WITH_POSSIBLE_PATCH); } } // all text should now be re-loaded (or re-requested) at this point } void CTextFile::RequestAdditionalText(const char *pMissionTextStem, s32 SlotNumber, eLocationOfTextBlock textLocation) { Assert(SlotNumber >= 0 && SlotNumber < GTA5_GLOBAL_TEXT_SLOT); if(textLocation != TEXT_LOCATION_ADDED_FOR_DLC) LoadTextIntoSlot(SlotNumber,pMissionTextStem,false,textLocation); else { Assertf( (SlotNumber>=DLC_TEXT_SLOT0&&SlotNumber<=DLC_AMBIENT_DIALOGUE_TEXT_SLOT) || (SlotNumber==DLC_MISSION_DIALOGUE_TEXT_SLOT2),"Trying to load text in GTA5 slots for DLC"); LoadTextIntoSlot(SlotNumber, pMissionTextStem, false, textLocation); } } void CTextFile::LoadAdditionalText(const char *pMissionTextStem, s32 SlotNumber, eLocationOfTextBlock textLocation) { Assert(SlotNumber >= 0 && SlotNumber < GTA5_GLOBAL_TEXT_SLOT); if(textLocation != TEXT_LOCATION_ADDED_FOR_DLC) LoadTextIntoSlot(SlotNumber,pMissionTextStem,true, textLocation); else { Assertf(SlotNumber>=DLC_TEXT_SLOT0&&SlotNumber<=DLC_TEXT_SLOT2,"Trying to load text in GTA5 slots for DLC"); LoadTextIntoSlot(SlotNumber, pMissionTextStem, true, textLocation); } } static void setStateLoaded(void *userArg, void *,u32 ,u32 ) { *(u8*)userArg = TEXT_LOADED; Displayf("REQUEST_ADDITIONAL_TEXT - finished load"); // It would be good to print the slot number and stem here } void CTextFile::FreeTextSlot(s32 SlotNumber, bool /*extraText*/) { USE_MEMBUCKET(MEMBUCKET_SCRIPT); Assertf(m_TextState[SlotNumber] == TEXT_LOADED, "ERROR: CTextFile - Trying to free an unloaded text slot"); if (SlotNumber < GTA5_GLOBAL_TEXT_SLOT) // The old text system didn't attempt to clear global text so don't do that here either (global text is in slot 0) { CMessages::ClearAllDisplayedMessagesThatBelongToThisTextBlock(SlotNumber, true); } if (m_Text[SlotNumber]) { delete [] (char*) m_Text[SlotNumber]; m_Text[SlotNumber] = NULL; } if (m_PatchText[SlotNumber]) { delete [] (char*) m_PatchText[SlotNumber]; m_PatchText[SlotNumber] = NULL; } m_TextState[SlotNumber] = TEXT_UNUSED; m_PatchTextState[SlotNumber] = TEXT_UNUSED; m_TextBlockLoadedIntoThisSlot[SlotNumber][0] = 0; } void CTextFile::LoadTextIntoSlot(s32 slot, const char *stem, bool blocking, eLocationOfTextBlock textLocation) { char name[64]; char patchName[64]; bool bFoundInMainText=false; if(textLocation != TEXT_LOCATION_ADDED_FOR_DLC) { // Search the main GTA5 text #if __ASSERT Assert(m_Packfile); #endif // __ASSERT formatf(name,"language:/%s.gxt2",stem); if(ASSET.Exists(name,"")) { // TEXT_LOCATION_CHECK_BOTH_GTA5_AND_DLC will make use of bFoundInMainText later bFoundInMainText = true; } else { if (textLocation == TEXT_LOCATION_GTA5_WITH_POSSIBLE_PATCH) { Assertf(false, "GXT2 File missing: %s", name); return; } } } bool foundInDLC=false; for(atMap::Iterator it=m_extraText.CreateIterator(); !it.AtEnd();it.Next()) { CExtraTextFile*& textFile = it.GetData(); if(textFile->GetHasAdditionalText()&&textFile->GetPackFile()) { #if __ASSERT Assert(textFile->GetPackFile()); #endif // __ASSERT const char *pNameToCheck = NULL; switch (textLocation) { case TEXT_LOCATION_GTA5_WITH_POSSIBLE_PATCH : Assertf(bFoundInMainText, "CTextFile::LoadTextIntoSlot - Just a safety check. We should have returned before now if the GXT2 File %s was missing from the main GTA5 text", stem); formatf(patchName,"%s%s.gxt2", textFile->GetMountName(),stem); pNameToCheck = patchName; break; case TEXT_LOCATION_ADDED_FOR_DLC : Assertf(!bFoundInMainText, "CTextFile::LoadTextIntoSlot - Just a safety check. The GXT2 File %s shouldn't have been found in the main GTA5 text", stem); formatf(name,"%s%s.gxt2", textFile->GetMountName(),stem); pNameToCheck = name; break; case TEXT_LOCATION_CHECK_BOTH_GTA5_AND_DLC : if (bFoundInMainText) { formatf(patchName,"%s%s.gxt2", textFile->GetMountName(),stem); pNameToCheck = patchName; } else { formatf(name,"%s%s.gxt2", textFile->GetMountName(),stem); pNameToCheck = name; } break; } if(ASSET.Exists(pNameToCheck,"")) { foundInDLC = true; break; } } } if(!foundInDLC) { switch (textLocation) { case TEXT_LOCATION_GTA5_WITH_POSSIBLE_PATCH : // We've already found the block in the GTA5 text so it doesn't matter if we didn't find the block name in DLC Assertf(bFoundInMainText, "CTextFile::LoadTextIntoSlot - Just a safety check. We should have returned before now if the GXT2 File %s was missing from the main GTA5 text", name); break; case TEXT_LOCATION_ADDED_FOR_DLC : // Return if we expected to find the text in DLC and we didn't find it return; // break; case TEXT_LOCATION_CHECK_BOTH_GTA5_AND_DLC : if (!bFoundInMainText) { Assertf(0, "CTextFile::LoadTextIntoSlot - The GXT2 File %s was not found in the main GTA5 text or in any DLC text", name); return; } break; } } #if __ASSERT if (CutSceneManager::GetInstancePtr()) { CCutSceneAssetMgrEntity* pCutAssetMgr = CutSceneManager::GetInstancePtr()->GetAssetManager(); if (pCutAssetMgr) { cutsceneAssertf((slot)!=MISSION_DIALOGUE_TEXT_SLOT || !pCutAssetMgr->IsLoadingSubtitles() || pCutAssetMgr->IsLoadingAdditionalText(stem),"Requesting new additional dialogue text '%s' whilst the old text '%s' is still in use by the cutscene!", stem, pCutAssetMgr->GetAdditionalTextBlockName()); } } #endif // __ASSERT if (IsRequestingAdditionalText(slot)) { if (stricmp((m_TextBlockLoadedIntoThisSlot[slot]), stem)) { Assertf(0, "REQUEST_ADDITIONAL_TEXT - Attempting to request %s for slot %d when %s has already been requested", stem, slot, m_TextBlockLoadedIntoThisSlot[slot]); } else { Displayf("REQUEST_ADDITIONAL_TEXT - %s is already requested into slot %d", stem, slot); } return; } if (HasThisAdditionalTextLoaded(stem,slot)) { Displayf("REQUEST_ADDITIONAL_TEXT - %s is already loaded into slot %d", stem, slot); return; } USE_MEMBUCKET(MEMBUCKET_SCRIPT); if (slot < GTA5_GLOBAL_TEXT_SLOT) // The old text system didn't attempt to clear global text so don't do that here either (global text is in slot 0) { // Pass in the slot number so that only those messages and previous messages from the old slot are cleared CMessages::ClearAllDisplayedMessagesThatBelongToThisTextBlock(slot, true); } m_TextState[slot] = TEXT_REQUESTED; if(bFoundInMainText&&foundInDLC) m_PatchTextState[slot] = TEXT_REQUESTED; else m_PatchTextState[slot] = TEXT_UNUSED; if (m_Text[slot]) { delete [] (char*) m_Text[slot]; m_Text[slot] = NULL; } if (m_PatchText[slot]) { delete [] (char*) m_PatchText[slot]; m_PatchText[slot] = NULL; } m_TextBlockLoadedIntoThisSlot[slot][0] = 0; StreamIntoSlots(name,blocking,m_Text[slot],m_TextState[slot], m_TextBlockLoadedIntoThisSlot[slot],stem BANK_ONLY(,slot)); if(bFoundInMainText&&foundInDLC) { StreamIntoSlots(patchName,blocking,m_PatchText[slot],m_PatchTextState[slot],m_TextBlockLoadedIntoThisSlot[slot],stem BANK_ONLY(,slot)); } } void CTextFile::StreamIntoSlots(const char* name, bool blocking,CGxt2Header *& _textSlot, u8 &_TextState,char* _TextBlockLoadedIntoThisSlot,const char* stem BANK_ONLY(, s32 slot)) { u32 fileSize; pgStreamer::Handle h = pgStreamer::Open(name,&fileSize,0); if (h == pgStreamer::Error) { Errorf("CTextFile - Cannot find %s",name); return; } safecpy(_TextBlockLoadedIntoThisSlot, stem, MISSION_NAME_LENGTH); _textSlot = (CGxt2Header*) rage_aligned_new(16) char[fileSize]; datResourceChunk destList = { 0, _textSlot, fileSize }; if (blocking) { BANK_ONLY(Displayf("LOAD_ADDITIONAL_TEXT - starting blocking load of %s into slot %d", stem, slot)); sysIpcSema s = sysIpcCreateSema(false); while (pgStreamer::Read(h,&destList,1,0,s,0) == NULL) sysIpcSleep(10); sysIpcWaitSema(s); sysIpcDeleteSema(s); _TextState = TEXT_LOADED; BANK_ONLY(Displayf("LOAD_ADDITIONAL_TEXT - finished blocking load of %s into slot %d", stem, slot)); } else { BANK_ONLY(Displayf("REQUEST_ADDITIONAL_TEXT - starting load of %s into slot %d", stem, slot)); while (pgStreamer::Read(h,&destList,1,0,setStateLoaded,&_TextState,pgStreamer::HARDDRIVE) == NULL) sysIpcSleep(10); } } s32 CTextFile::GetTextIndexInternal(const char* pMissionTextStem, fiPackfile* packFile) { char buf[64]; safecpy(buf,pMissionTextStem); safecat(buf,".gxt2"); const fiPackEntry *i = packFile->FindEntry(buf), *begin = packFile->GetEntries(); if(i) { return ptrdiff_t_to_int(i-begin); } else return -1; } s32 CTextFile::GetAdditionalTextIndex(const char* pMissionTextStem) { if (!m_Packfile) return -1; fiPackfile* curPackFile = m_Packfile; s32 curIdx = GetTextIndexInternal(pMissionTextStem,curPackFile); if(curIdx != -1) { return curIdx; } else { for(atMap::Iterator it=m_extraText.CreateIterator(); !it.AtEnd();it.Next()) { CExtraTextFile*& textFile = it.GetData(); if(textFile->GetHasAdditionalText()&&textFile->GetPackFile()) { curPackFile = textFile->GetPackFile(); #if __ASSERT Assert(curPackFile); #endif // __ASSERT curIdx = GetTextIndexInternal(pMissionTextStem,curPackFile); if(curIdx != -1) { return curIdx; } } } } return -1; } bool CTextFile::HasThisAdditionalTextLoaded(const char *pMissionTextStem, s32 SlotNumber) { Assert(SlotNumber >= 0 && SlotNumber < NUM_TEXT_SLOTS); bool hasTextLoaded = ((m_TextState[SlotNumber] == TEXT_LOADED) && ((m_PatchTextState[SlotNumber] == TEXT_LOADED)||(m_PatchTextState[SlotNumber]== TEXT_UNUSED))); return (hasTextLoaded && !stricmp(pMissionTextStem,m_TextBlockLoadedIntoThisSlot[SlotNumber])); } bool CTextFile::HasAdditionalTextLoaded(s32 SlotNumber) { Assert(SlotNumber >= 0 && SlotNumber < NUM_TEXT_SLOTS); bool hasTextLoaded = ((m_TextState[SlotNumber] == TEXT_LOADED) &&((m_PatchTextState[SlotNumber] == TEXT_LOADED) || (m_PatchTextState[SlotNumber] == TEXT_UNUSED))); return hasTextLoaded; } bool CTextFile::IsRequestingAdditionalText(int SlotNumber) { Assert(SlotNumber >= 0 && SlotNumber < NUM_TEXT_SLOTS); bool hasTextRequested = (m_TextState[SlotNumber] == TEXT_REQUESTED) || (m_PatchTextState[SlotNumber] == TEXT_REQUESTED); return hasTextRequested; } bool CTextFile::IsRequestingThisAdditionalText(const char *pMissionTextStem, s32 SlotNumber) { Assert(SlotNumber >= 0 && SlotNumber < NUM_TEXT_SLOTS); bool hasTextRequested = (m_TextState[SlotNumber] == TEXT_REQUESTED) || (m_PatchTextState[SlotNumber] == TEXT_REQUESTED); return (hasTextRequested && !stricmp(pMissionTextStem,m_TextBlockLoadedIntoThisSlot[SlotNumber])); } const char* CTextFile::GetTextStemForAdditonalSlot(const int idx) const { return Verifyf(idx < NUM_TEXT_SLOTS && idx >= 0, "Trying to find the text stem of a TextSlot that is out of bounds idx=%i, min=%i, max=%i.", idx, CREDITS_TEXT_SLOT, NUM_TEXT_SLOTS) ? m_TextBlockLoadedIntoThisSlot[idx] : ""; } void CTextFile::ProcessStreamingAdditionalText() { } void CTextFileWrapper::Load(unsigned) { TheText.Load(); } #if __BANK void CDebugString::Set(const char* pText) { if(m_string) delete[] m_string; s32 string_length = istrlen(pText); m_string = rage_aligned_new(16) char[string_length+1]; // +1 for the NULL terminator m_string[0] = 0; safecpy( m_string, pText, string_length+1 ); m_bFlaggedForDeletion = false; } sysCriticalSectionToken g_DebugStringCriticalToken; void CDebugStrings::Add(const char *pTextKey, const char *pTextToDisplay) { sysCriticalSection criticalSection(g_DebugStringCriticalToken); u32 HashKey = atStringHash(pTextKey); if (Verifyf(!CSystem::IsThisThreadId(SYS_THREAD_RENDER), "CDebugStrings::Add - didn't expect this to be called by the render thread")) { m_DebugStringMap[HashKey].Set(pTextToDisplay); } } void CDebugStrings::FlagForDeletion(const char *pTextKey) { if (Verifyf(!CSystem::IsThisThreadId(SYS_THREAD_RENDER), "CDebugStrings::FlagForDeletion - didn't expect this to be called by the render thread")) { CDebugString *pDebugString = m_DebugStringMap.Access(atStringHash(pTextKey)); if (pDebugString) { return pDebugString->SetFlaggedForDeletion(true); } } } void CDebugStrings::FlagAllForDeletion() { if (Verifyf(!CSystem::IsThisThreadId(SYS_THREAD_RENDER), "CTextFile::ClearDebugStringMap - didn't expect this to be called by the render thread")) { atMap::Iterator MapIterator = m_DebugStringMap.CreateIterator(); while (!MapIterator.AtEnd()) { MapIterator.GetData().SetFlaggedForDeletion(true); MapIterator.Next(); } } } void CDebugStrings::DeleteAllFlagged() { atMap::Iterator MapIterator = m_DebugStringMap.CreateIterator(); while (!MapIterator.AtEnd()) { if (MapIterator.GetData().GetFlaggedForDeletion()) { u32 KeyOfEntryToBeDeleted = MapIterator.GetKey(); m_DebugStringMap.Delete(KeyOfEntryToBeDeleted); MapIterator.Start(); // Start from the beginning of the map again after deleting an entry } else { MapIterator.Next(); } } } char *CDebugStrings::GetString(u32 textHash) { sysCriticalSection criticalSection(g_DebugStringCriticalToken); const CDebugString *pDebugString = m_DebugStringMap.Access(textHash); if (pDebugString) { return pDebugString->GetString(); } return NULL; } void CTextFile::AddToDebugStringMap(const char *pTextKey, const char *pTextToDisplay) { m_DebugStrings.Add(pTextKey, pTextToDisplay); } void CTextFile::FlagForDeletionFromDebugStringMap(const char *pTextKey) { m_DebugStrings.FlagForDeletion(pTextKey); } void CTextFile::FlagAllForDeletionFromDebugStringMap() { m_DebugStrings.FlagAllForDeletion(); } void CTextFile::DeleteAllFlaggedEntriesFromDebugStringMap() { m_DebugStrings.DeleteAllFlagged(); } #endif // __BANK #undef __rage_channel // eof