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

1152 lines
34 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// 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 <stdio.h>
// 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<char*>(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; i<GTA5_GLOBAL_TEXT_SLOT; i++)
{
if(m_PatchTextState[i] == TEXT_LOADED && m_PatchText[i])
{
foundText = SearchForText(hashcode,m_PatchText[i]);
if(foundText)
{
m_TextBlockContainingTheLastStringReturnedByGetFunction = i;
return foundText;
}
}
// Explicitly check loaded state first in case the pointer is valid but it's still being streamed in.
if (m_TextState[i]==TEXT_LOADED && m_Text[i])
{
#if !__FINAL
if (!sysMemAllocator::GetMaster().GetAllocator(MEMTYPE_GAME_VIRTUAL)->GetSize(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 __FINAL
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<char*, u32>::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<char*, u32>::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<char*, u32>::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<int>(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; i<MAX_LANGUAGES; i++)
{
if(!strcmp(pLanguage, g_LanguageFiles[i]))
return i;
}
return LANGUAGE_UNDEFINED;
}
void CTextFile::Load(bool bUseSystemLanguage)
{
USE_MEMBUCKET(MEMBUCKET_SCRIPT);
Unload();
#if __BANK
bShowTextIdOnly = false;
#endif // __BANK
m_Packfile = rage_aligned_new(16) fiPackfile;
char fileName[RAGE_MAX_PATH];
GetLanguageFile(fileName, sizeof(fileName), bUseSystemLanguage, TEXT_SOURCE_TU2); // text packfile can't mount from update2: without explicit path, so check the tu2 first...
if(!ASSET.Exists(fileName,""))
{
GetLanguageFile(fileName, sizeof(fileName), bUseSystemLanguage, TEXT_SOURCE_TU); // text packfile can't mount from update: without explicit path, so check the tu first...
if(!ASSET.Exists(fileName,""))
{
GetLanguageFile(fileName, sizeof(fileName), bUseSystemLanguage, TEXT_SOURCE_DISK); // ...if no replacement, load the original
}
}
Debugf2(2,"Language file %s",fileName);
if (!m_Packfile->Init(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<atHashString, CExtraTextMetaFile*>::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<NUM_TEXT_SLOTS; i++)
{
if (m_Text[i])
{
delete[] (char*) m_Text[i];
m_Text[i] = NULL;
}
if (m_PatchText[i])
{
delete[] (char*) m_PatchText[i];
m_PatchText[i] = NULL;
}
}
if(m_Packfile)
{
fiDevice::Unmount(*m_Packfile);
delete (m_Packfile);
m_Packfile = NULL;
}
memset(&m_TextState[0], TEXT_UNUSED, sizeof(m_TextState));
memset(&m_PatchTextState[0], TEXT_UNUSED, sizeof(m_PatchTextState));
}
else
{
for(int i=DLC_TEXT_SLOT0; i<=DLC_TEXT_SLOT2;i++)
{
if(m_Text[i])
{
delete[] (char*) m_Text[i];
delete[] (char*) m_PatchText[i];
m_Text[i] = NULL;
m_PatchText[i] = NULL;
m_TextState[i] = TEXT_UNUSED;
m_PatchTextState[i] = TEXT_UNUSED;
}
}
}
for(atMap<atHashString, CExtraTextFile*>::Iterator it=m_extraText.CreateIterator(); !it.AtEnd();it.Next())
{
CExtraTextFile*& text = it.GetData();
text->Shutdown();
}
for(atBinaryMap<char*, u32>::Iterator it=m_extraGlobals.Begin(); it != m_extraGlobals.End();++it)
{
char*& text = *it;
delete[] text;
text = NULL;
}
for(atBinaryMap<char*, u32>::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<atHashString,CExtraTextFile*>::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<atHashString,CExtraTextFile*>::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<u32, CDebugString>::Iterator MapIterator = m_DebugStringMap.CreateIterator();
while (!MapIterator.AtEnd())
{
MapIterator.GetData().SetFlaggedForDeletion(true);
MapIterator.Next();
}
}
}
void CDebugStrings::DeleteAllFlagged()
{
atMap<u32, CDebugString>::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