nfsu2-re

Prologue #

This is just me documenting while I have fun attempting to reverse-engineer the game Need For Speed Underground 2, otherwise knows as nfsu2.

This normally belongs to https://github.com/yugecin/nfsu2-re.

Other pages: (fpu) cheatsheet

Index

Index #

Index

Injected code #

The code for all injected things I did can be found in nfsu2-re-hooks/*.c. It's configured as a VC2005 project in a VC2005 solution. The project emits a compiled file to ..\NeedForSpeed U2\scripts\nfsu2-re-hooks.asi. I'm using the Ultimate ASI Loader by ThirteenAG, which loads the library nicely for me (download the ZIP and place the dll in the game folder, name it dinput8.dll).

Index

Display settings #

Display settings get saved in the windows registry. See Registry.

TODO. I was writing this when I got distracted by the language stuff.

Symbols:

Index

Registry #

Many values (mainly display settings) are loaded/saved in procs 5BEA20:LoadDisplaySettings() and 5BEEA0:SaveDisplaySettings().

They are saved in key Software\EA Games\Need for Speed Underground 2 in HKEY_CURRENT_USER. On my machine this translates to: HKEY_CURRENT_USER\Software\Classes\VirtualStore\MACHINE\SOFTWARE\Wow6432Node\EA Games\Need for Speed Underground 2. I'm guessing the extra path in between is because of WOW64.

Variables loaded from there:

VariableKey
870CB0:VERSION VERSION
870CB4:SIZE SIZE
870CB8:_optCarReflectionUpdateRate g_CarEnvironmentMapEnable
870CBC:_optCarReflectionDetail g_CarEnvironmentMapUpdateData
870CC0:_optCarShadowNeon g_CarShadowEnable
870CC4:_optCarHeadlight g_CarHeadlightEnable
870CC8:_optCarLightingEnableUNUSED g_CarLightingEnable
870CCC:_optCarDamageEnableUNUSED g_CarDamageEnable
870CD0:_optCrowds g_CrowdEnable
870CD4:_optWorldReflectionDetail g_RoadReflectionEnable
870CD8:_optFog g_FogEnable
870CDC:_optMotionBlur g_MotionBlurEnable
870CE0:_optLightTrails g_LightStreaksEnable
870CE4:_optLightGlow g_LightGlowEnable
870CE8:_optAnimatedTextureEnable g_AnimatedTextureEnable
870CEC:_optParticleSystem g_ParticleSystemEnable
870CF0:_optDepthOfField g_DepthOfFieldEnable
870CF4:_optWorldDetail g_WorldLodLevel
870CF8:_optCarGeometryDetail g_CarLodLevel
870CFC:_optOverBright g_OverBrightEnable
870D00:_optEnchancedContrast g_BleachByPassEnable
870D04:_optTinting g_TintingEnable
870D08:_optFSAALevel g_FSAALevel
870D0C:_optHorizonFog g_HorizonFogEnable
870D10:_optRainSplatter g_RainEnable
870D18:_optTextureFiltering g_TextureFiltering
870D1C:_optRacingResolutionIdx g_RacingResolution
87099C:_optLevelOfDetail g_PerformanceLevel
870D24:_optVsync g_VSyncOn

5B76F0:LoadOtherRegistrySettings() also loads registry settings, but only once at boot. It seems to load a special key that I haven't seen before: Software\Electronic Arts\EA Games\Need for Speed Underground 2\er. This key is not present on my system, so this seems interesting.

HKEY key;
DWORD type, lenData;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\E...2\\er", 0, KEY_READ, &key)) {
	type = REG_SZ; // a null-terminated string
	lenData = 21;
	if (RegQueryValueExA(key, 0, 0, &type, &someRegValue_er, &lenData)) {
		someRegValue_er[20] = 0;
	} else {
		someRegValue_er[0] = 0;
	}
}
RegCloseKey(&key);

Then it loads more stuff from the normal key:

Install Dir is read. If set, passed to (winapi) GetLongPathNameA. and if that succeeds, the value is copied to buffer at 86E9B4:installDirPath.

CD Drive is read. If set, passed to (winapi) GetLongPathNameA. passed to (winapi) GetLastError and if that succeeds, the value is copied to 86E8B0:CDDrivePath.

NotFirstTime is read. If set, 870D20:notFirstTime gets set to 1, otherwise 0. It doesn't check the value in the register key. Then 1 is written to the register key.

CacheSize is read. If set, 75D65B:sub_75D65B is called with the value. TODO

SwapSize is read. If set, proc at 75D65B:sub_75D65B is called with the value. TODO

Language is read. Then it does something with looping through a language. This took some deciphering but I found out that there is some struct with language information and associated data. See Language metadata.

StreamingInstall is read. Then something with 75DD6F:sub_75DD6F. TODO

Symbols:

Index

Game region #

List by 571380:GetCountryCode():

List by 5B7C40:OpenPatchesWebsite():

Then some network region stuff at 7FA740 showed a 14th entry:

Symbols:

Index

Bin data #

Lots of data is in .bin files. These files can contain multiple struct BinSection entries.

struct BinSectionHeader {
        int magic;
        int size;
};

struct BinSection {
        struct BinSectionHeader header;
        char data[size];
};

struct BinSectionHandler {
        int magic;
        int (*loadfunc)(struct BinSection*));
        int (*unloadfunc)(struct BinSection*));
};

struct BinSectionMagicMapEntry {
        struct BinSectionMagicMapEntry *nextEntry;
        struct BinSectionHandler value;
};

These are all the sections that are in the LANGUAGES/English.bin file:

offset 00000: section 00039000 size 3D230
offset 3D238: section 00000000 size 40
offset 3D280: section 00030201 size FF0
offset 3E278: section 00000000 size 0
offset 3E280: section 00030201 size 1270
offset 3F4F8: section 00000000 size 0
offset 3F500: section 00030201 size 370
offset 3F878: section 00000000 size 0
offset 3F880: section 00030201 size 1070
offset 408F8: section 00000000 size 0
offset 40900: section 00030201 size A70
offset 41378: section 00000000 size 0
offset 41380: section 00030201 size CF0

This is output by an early version of nfsu2-re-binfiles/main.c.

Note that the offset also increments by 8, being sizeof struct BinSectionHeader.

Section magic 0 seems to be just for padding/alignment uses.

Symbols:

Index

Language #

There seems to be no way to change the language, except for changing the value in the registry? See Registry. Perhaps it is only set when the game is installed?

Index

Language > Language metadata

Language metadata #

Thanks to code where it reads the Registry, I found following data:

struct LanguageData {
	char *pName;
	int val1; //??
	int region;
};

struct LanguageData languageData[] = {
        { "English US",            0,        0 },
        { "English UK",            0,        5 },
        { "French",                1,        6 },
        { "German",                2,        7 },
        { "Italian",               3,        8 },
        { "Spanish",               4,        9 },
        { "Swedish",               6,        5 },
        { "Danish",                7,        5 },
        { "Dutch",                 5,        5 },
        { "Korean",                8,        2 },
        { "Chinese (Traditional)", 9,        4 },
        { "English China",         9,        11 },
        { "Japanese",              10,       3 },
        { "Thai",                  11,       12 },
        { 0,                       0,        0 },
};

The value stored in the registry for Language is checked against all language strings in the above data. The matching language's val1 gets stored in 7FCA00:languageIndex, or 0 when the name stored in the registry didn't match any names.

After chasing usages of a region related proc, I found more language data which is being used in 4FF680:LoadLanguageSomething(). They seem to fit into three more structs.

/*from hashes, prefixed with HASH_*/
#define HASH_CONDUITMDITC_TT21I 0x5B9D88B9
#define HASH_font_impact36 0x0920075C
#define HASH_CONDUITMDITC_TT14I 0x5B9D84DB
#define HASH_FONT_CONDUITMDITCTT38BI 0x9583AA1A
#define HASH_arial 0xA87927BE
#define HASH_arial12 0xAB6215C1
#define HASH_lcd_let48 0xBBBA71C2
#define HASH_conduitmditc_tt14i_korean 0xDCA5485A
#define HASH_conduitmditc_tt21i_korean 0x833A8678
#define HASH_conduitmditc_tt14i_chinese 0xF88A75F9
#define HASH_conduitmditc_tt21i_chinese 0x71C777D7
#define HASH_conduitmditc_tt14i_japanese 0x743BF1C1
#define HASH_conduitmditc_tt21i_japanese 0x15192F5F
#define HASH_conduitmditc_tt14i_thailand 0xA73823FF
#define HASH_conduitmditc_tt21i_thailand 0x4815619D

struct LanguageStruct7F6DE8 {
	int fonthash;
	int field_1;
};

struct LanguageStruct7F6DE8 7F6DE8:struct7F6DE8[] = {
        { HASH_CONDUITMDITC_TT21I,          0x0 },
        { HASH_font_impact36,               0x40000 },
        { HASH_CONDUITMDITC_TT14I,          0x10000 },
        { HASH_FONT_CONDUITMDITCTT38BI,     0x100000 },
        { HASH_arial,                       0x10000 },
        { HASH_arial12,                     0x10000 },
        { HASH_lcd_let48,                   0x4000 },
        { HASH_conduitmditc_tt14i_korean,   0x40000 },
        { HASH_conduitmditc_tt21i_korean,   0x80000 },
        { HASH_conduitmditc_tt14i_chinese,  0x40000 },
        { HASH_conduitmditc_tt21i_chinese,  0x80000 },
        { HASH_conduitmditc_tt14i_japanese, 0x40000 },
        { HASH_conduitmditc_tt21i_japanese, 0x80000 },
        { HASH_conduitmditc_tt14i_thailand, 0x40000 },
        { HASH_conduitmditc_tt21i_thailand, 0x80000 },
};

struct LanguageStruct7F6E60 {
	int data[28];
};

struct LanguageFileData {
	int id;
	char *filecode;
	char *binfile;
	struct LanguageStruct7F6E60 *pStruct7F6E60;
};

struct7F6E60[0][0] = HASH_CONDUITMDITC_TT21I;
struct7F6E60[0][1] = HASH_arial;
struct7F6E60[0][2] = HASH_CONDUITMDITC_TT14I;
struct7F6E60[0][3] = HASH_lcd_let48;
struct7F6E60[0][8] = HASH_FONT_CONDUITMDITCTT38BI;
struct7F6E60[0][16] = HASH_arial12;
struct7F6E60[0][17] = HASH_font_impact36;
struct7F6E60[1][0] = HASH_conduitmditc_tt21i_korean;
struct7F6E60[1][1] = HASH_arial;
struct7F6E60[1][2] = HASH_conduitmditc_tt14i_korean;
struct7F6E60[1][3] = HASH_lcd_let48;
struct7F6E60[1][16] = HASH_arial12;
struct7F6E60[1][17] = HASH_font_impact36;
struct7F6E60[2][0] = HASH_conduitmditc_tt21i_japanese;
struct7F6E60[2][1] = HASH_arial;
struct7F6E60[2][2] = HASH_conduitmditc_tt14i_japanese;
struct7F6E60[2][3] = HASH_lcd_let48;
struct7F6E60[2][8] = HASH_FONT_CONDUITMDITCTT38BI;
struct7F6E60[2][16] = HASH_arial12;
struct7F6E60[2][17] = HASH_font_impact36;
struct7F6E60[3][0] = HASH_conduitmditc_tt21i_chinese;
struct7F6E60[3][1] = HASH_arial;
struct7F6E60[3][2] = HASH_conduitmditc_tt14i_chinese;
struct7F6E60[3][3] = HASH_lcd_let48;
struct7F6E60[3][16] = HASH_arial12;
struct7F6E60[3][17] = HASH_font_impact36;

struct LanguageFileData languageFileData[] = {
	{  0, "ENGLISH", "LANGUAGES\\ENGLISH.BIN", struct7F6E60 + 0 },
	{  1, "FRENCH", "LANGUAGES\\FRENCH.BIN", struct7F6E60 + 0 },
	{  2, "GERMAN", "LANGUAGES\\GERMAN.BIN", struct7F6E60 + 0 },
	{  3, "ITALIAN", "LANGUAGES\\ITALIAN.BIN", struct7F6E60 + 0 },
	{  4, "SPANISH", "LANGUAGES\\SPANISH.BIN", struct7F6E60 + 0 },
	{  5, "DUTCH", "LANGUAGES\\DUTCH.BIN", struct7F6E60 + 0 },
	{  6, "SWEDISH", "LANGUAGES\\SWEDISH.BIN", struct7F6E60 + 0 },
	{  7, "DANISH", "LANGUAGES\\DANISH.BIN", struct7F6E60 + 0 },
	{  8, "KOREAN", "LANGUAGES\\KOREAN.BIN", struct7F6E60 + 1 },
	{  9, "CHINESE", "LANGUAGES\\CHINESE.BIN", struct7F6E60 + 3 },
	{ 10, "JAPANESE", "LANGUAGES\\JAPANESE.BIN", struct7F6E60 + 2 },
	/*there is no Thai entry here?*/
};

In 4FF680:LoadLanguageSomething(), 7F7020:languageFileData is searched. If 570A30:GetGameRegion() is 2, the entry with id 8 is searched, otherwise id 0 is searched. When it's not found the game will segfault because of the code below:

.text:004FF6A2                 jl      short loc_4FF693
.text:004FF6A4 loc_4FF6A4:
.text:004FF6A4                 xor     edx, edx
.text:004FF6A6 loc_4FF6A6:
.text:004FF6A6                 mov     ecx, [edx+0Ch]

Then struct LanguageFileData.pStruct7F6E60 is read. Its data[0] value is read and then a matching entry in 7F6DE8:struct7F6DE8 is searched. More stuff is read is a loop and reset and I lost track. Eventually something is called with string LanguageMemoryPool.

7983D0 aLanguageselect db 'LanguageSelectScreen',0 TODO

Symbols:

Index

Language > Language files

Language files #

The language strings are in a bin section with magic 0x39000, and is structured like this:

struct BinDataLanguage39000 header;
char unknown[header.tableOffset - sizeof(header)];
struct LanguageTableEntry tableEntries[header.numStrings];
char *strings; /*all zero-terminated strings after each other*/

The tableEntries are sorted (low hash to high hash), required because a binary search is used at lookup.

.text:00497766                 push    04B5DE3E9h ; hash (SMS_SUBJECT_HEADER)
.text:0049776B                 lea     eax, [esp+104h+buffer]
.text:00497772                 push    80h             ; destlen
.text:00497777                 push    eax             ; dest
.text:00497778                 call    GetLanguageStringIntoBuf

Loading is done in 5125B0:LoadLanguageBinSection39000(). See language_english.txt for all LANGUAGES/English.bin strings by their hash. (This was generated by an early version of nfsu2-re-binfiles/main.c)

The hashes seem to be made with the case insensitive hash function 43DB50:SomeHashCS43DB50(). The original labels are also stored as a language file in LANGUAGES/Labels.bin

struct BinDataLanguage39000 {
        int field_0;
        int numStrings;
        int tableOffset;
        int stringsOffset;
};

struct LanguageTableEntry {
	unsigned int hash;
	char *string;
};

struct LoadedLanguage {
        int numStrings;
        char **ptrStrings;
        struct LanguageTableEntry *ptrTable;
        struct LanguageConversionTable *ptrConversionTable;
};

struct LanguageConversionTable {
        int field_0;
        wchar_t table[256]; /*size unknown*/
};

TODO: language files also contain bin sections with magic 0x30201

Symbols:

Index

Pools #

I hooked 440B40:AllocateAndInitPool() and collected some output: createpoollog.txt This will probably help a lot when trying to figure out other things. Note that the first time the AUD: NFS3DMixCtl Pool is made, it has an element amount of zero. The dump is from just starting the game and starting a circuit race.

Pools can be enlarged by 440BB0:Pool::Extend(), but only if flags has the POOL_FLAG_EXTENDABLE flag. When extending, the newly created pool gets added as last in the main pool's nextLinkedPool. The main pool's firstAvailableElement will point to the first allocated element of the next pool, and the last allocated element of the new pool will point to the first element of the main pool.

struct PoolLink {
        struct PoolLink *prev;
        struct PoolLink *next;
};
/*link is a circle*/

struct PoolControl {
        int inited;
        struct PoolLink link;
};

#define POOL_FLAG_EXTENDABLE 1
#define POOL_FLAG_DONT_ZERO_INIT 2 /*used in 440D40:PoolGetNextFreeItem() etc*/

struct Pool {
	struct PoolLink __parent;
	struct Pool *nextLinkedPool;
	char* name;
	struct PoolEntry *firstAvailableElement;
	int flags;
	int usedElements;
	int maxUsedElements; /*max amount of elements used at one time*/
	int field_20;
	int elementAmount;
	int elementSize;
	int elementAmountOverAllLinkedPools;
        /*pool entries come here*/
};

struct PoolEntry {
        struct PoolEntry *nextEntry;
        struct PoolEntry *prevEntry?;
};

struct bFileSystemPoolEntry {

};
EXPECT_SIZE(struct bFileSystemPoolEntry, 0xF0);

Symbols:

Index

Speedy boot #

I want a fast startup because I'm booting the game so many times (not to mention the numerious crashes)

During startup, with the debug string output enabled, there are a few entries showing loading and unloading states, such as LS_THXMovie.fng. I already sped up boot by replacing the MOVIES/THX_logo.vp6 with MOVIES/blank.vp6. While that skips the unskippable intro logos, it still takes some time because it switches to a state where it tries loading and it takes slightly under a second to switch to the next state.

So by following the debug prints, I found following section:

.data:007F65E8 off_7F65E8  dd offset aDiscerrorpc_fn
.data:007F65EC             dd offset byte_783FE5
.data:007F65F0             dd offset aMc_bootup_fng ; "MC_Bootup.fng"
.data:007F65F4             dd offset byte_783FE5
.data:007F65F8             dd offset byte_783FE5
.data:007F65FC             dd offset aLs_blankmovie_ ; "LS_BlankMovie.fng"
.data:007F6600             dd offset aLs_ealogo_fng ; "LS_EAlogo.fng"
.data:007F6604             dd offset byte_783FE5
.data:007F6608             dd offset aLs_thxmovie_fn ; "LS_THXMovie.fng"
.data:007F660C             dd offset aLs_psamovie_fn ; "LS_PSAMovie.fng"
.data:007F6610             dd offset aUg_ls_introfmv ; "UG_LS_IntroFMV.fng"
.data:007F6614             dd offset aUg_ls_splash_f ; "UG_LS_Splash.fng"
.data:007F6618             dd offset aMc_background_ ; "MC_Background.fng"
.data:007F661C             dd offset aUi_main_fng  ; "UI_Main.fng"

This very much looks like the boot states the game goes through. byte_783FE5 is basically an empty string, so maybe there used to be more states, or some are for different game versions?

Putting all of them to a reference to an empty string makes the boot pretty speedy but then it doesn't do much. After initial boot, there's a black screen for a short moment, then the basic loading progressbar screen with the cars and Rachel shows, then it goes to a black screen and you can move the cursor. So the game is running but nothing of UI is running so you can't do much.

Only leaving UG_LS_Splash.fng shows the splash screen with Rachel and the cars, the famous music starts playing and when the beat drops the EA TRAX message shows and "Press enter" shows. When pressing enter, we're back to the black screen with a moveable cursor.

Only leaving MC_BACKGROUND.fng shows the splash screen with Rachel and the cars and the prompt if I want to create a new profile. When pressing 'no', we're back to the black screen. When pressing 'yes' I get to enter a name but when it then asks to save the profile, it says 'Unable to save profile' so I have to choose 'no'. Then black screen again.

Only leaving UI_MAIN.fng directly jumps to the main menu where you see Rachel's car after a short moment after the loading screen showed. It didn't ask for a profile to load and when I try to load a profile it says that no profiles were found. Scary. I can create a new profile but it says 'Unable to save'. There is no music in the menu. Music does start when starting a race but going back to the menus stops the music again. Going to EA TRAX in the option does suddenly start the music in the menus.

When only leaving LS_EALogo.fng, the ea logo movie sound starts to play while the 'loading' screen is still showing, then there's one frame of the logo movie when it's about halfway the sound of the movie and then it goes to black screen with cursor. Leaving LS_BlankMovie.fng in as well fixes that problem.

Leaving just MC_Bootup.fng doesn't seem to change much, not even timing wise. It does sound important, so I tried this in combination with UI_MAIN.fng. It jumps into the main menu, but when I go to the profile manager now, I can load my profiles. Nice. The main menu music still doesn't load without going to EA TRAX, but I'm okay with that. One weird thing is that the Q button doesn't show the 'quit game' dialog. After searching a bit, it seems like LS_PSAMovie.fng is the one that makes the Q to quit keybind happen. That's quite interesting, definitely something to research when/if I get more into the workings of the UI.

(Later I found the PSA screen(s) put 836494:canUseQToExit on 1 after they're done, I'm guessing for some reason they don't want us to exit while the PSA is playing, or before the intro movies end.)

So my final setup is to only leave in MC_Bootup (because it makes profiles work), LS_PSAMovie (because I like the Q button and it can be a good dialog test, it means I still need to skip the PSA movie but I can handle that), MC_Background (so I can load a profile at start) and UI_Main (obviously). Code in nfsu2-re-hooks/speedyboot.c.

Index

Console leftovers #

Note: console as in somewhere you type in, not a videogame console

In the hooks project, I "enabled" this console in the functions initConsolePOC in the file nfsu2-re-hooks/faux-enable-console.c. As it doesn't seem to be used anywhere, the only effect you can see is that whatever you type is written into the console_real_text buffer (use CheatEngine or something you like to inspect it).

Index

Console leftovers > ConsoleConsumeKey

ConsoleConsumeKey #

When looking at the 5CCD60:MainWndProc() (which is not to hard too find because it's passed in a struct to RegisterClassExA), I saw a call to a function that is called on every WM_CHAR event. The function does nothing when the value in 8709BC:consoleEnabledFlag is zero, and there are no write references to it, so it seems like it will always be zero.

First it seems like the input is dropped if it's a CR (carriage return) and when the value at 8709C4:consoleIgnoreNextCR is non-zero. Again, this is always zero so CR's are never dropped.

Then it has four different handlers. The first one handles a backspace character, second one does escape, third one does tab and CR, and the last one does everything else. Basically one character is deleted when pressing backspace and one character is added in all other cases. I named following variables:

Basically, on backspace it checks if 8709B8:consoleTextCaretPosition is bigger than zero, copies everything in 8709B0:consoleTextString starting from 8709B8:consoleTextCaretPosition to a zero byte into a newly allocated buffer, decrements 8709B8:consoleTextCaretPosition and 8709B4:consoleTextStringLength, and copies everything from the allocated buffer back into 8709B0:consoleTextString at position 8709B8:consoleTextCaretPosition.

When inserting, it checks if 8709B4:consoleTextStringLength is less than 8709C0:consoleTextStringMaxLength, copies everything in 8709B0:consoleTextString starting from 8709B8:consoleTextCaretPosition to a zero byte into a newly allocated buffer, inserts the character in 8709B0:consoleTextString at position 8709B8:consoleTextCaretPosition, increments 8709B8:consoleTextCaretPosition and 8709B4:consoleTextStringLength by one, and copies everything from the allocated buffer back into 8709B0:consoleTextString at position 8709B8:consoleTextCaretPosition.

Index

Console leftovers > ConsoleConsumeKey > A hardcoded key check

A hardcoded key check #

At the end of looking if a character is valid, when it hasn't passed any of the criteria above, it checks one last function if 8709C7:consoleFilterSpecialChars is 1. The function seems to check if the key is one of 55 configured values and if so, the char is accepted.

Index

Console leftovers > ConsoleMoveCaret

ConsoleMoveCaret #

This function is also called from 5CCD60:MainWndProc(), this time when a WM_KEYDOWN message is received. One parameter is passed based on the keycode of the event:

This is the only other function that checks if 8709BC:consoleEnabledFlag is non-zero.

Index

Console leftovers (continuation)

I need text here or my parser breaks.

Symbols:

Index

Hash functions #

While scrolling and looking at references to some strings, I found some parts that look like following excerpt:

.text:005121AE     push    offset aGenericdialo_2 ; "GenericDialog_Animate_SMALL.fng"
.text:005121B3     call    sub_505450
.text:005121B8     add     esp, 4
.text:005121BB     cmp     esi, eax
.text:005121BD     jz      short loc_512218
.text:005121BF     push    offset aGenericdialog_ ; "GenericDialog_SMALL.fng"
.text:005121C4     call    sub_505450
.text:005121C9     add     esp, 4
.text:005121CC     cmp     esi, eax
.text:005121CE     jz      short loc_512218
.text:005121D0     push    offset aGenericdialo_3 ; "GenericDialog_Animate_MED.fng"
.text:005121D5     call    sub_505450
.text:005121DA     add     esp, 4
.text:005121DD     cmp     esi, eax
.text:005121DF     jz      short loc_512218
.text:005121E1     push    offset aGenericdialo_4 ; "GenericDialog_MED.fng"
.text:005121E6     call    sub_505450
.text:005121EB     add     esp, 4
.text:005121EE     cmp     esi, eax
.text:005121F0     jz      short loc_512218
.text:005121F2     push    offset aGenericdialo_5 ; "GenericDialog_Animate_LARGE.fng"
.text:005121F7     call    sub_505450
.text:005121FC     add     esp, 4
.text:005121FF     cmp     esi, eax
.text:00512201     jz      short loc_512218
.text:00512203     push    offset aGenericdialo_0 ; "GenericDialog_LARGE.fng"
.text:00512208     call    sub_505450
.text:0051220D     add     esp, 4
.text:00512210     cmp     esi, eax
.text:00512212     jz      short loc_512218

The proc 505450:SomeHashCI505450() doesn't look very complicated; it has one argument and doesn't perform any calls. It dereferences the given argument and increment its position, until a zero has been found. While it's doing that, eax is modified based on the read value. So I assumed it is some hashing function and hooked it to see what kind of things are passed through it.

As it turns out, it gets called many, many times.

time        input                       result
53.42743683 GenericDialog_SMALL.fng     6962C0CD
53.42753220 UI_PC_Help_Bar.fng          33AC1CB4
53.42756271 OL_ICON_GROUP               2BAC0CEE
53.42758179 UI_PC_Help_Bar.fng          33AC1CB4
53.42760086 Hide                        0016A259
53.42761993 UI_Main.fng                 C343126A
53.42766190 GarageMain.fng              4CDD8B14
53.42769241 GarageMain.fng              4CDD8B14
53.42771149 GarageMain.fng              4CDD8B14
53.42773056 GenericDialog.fng           F68A7675
53.42774963 UI_GenericParts_Browser.fng AF09F84F
53.42776871 GenericDialog.fng           F68A7675
53.42779160 GenericDialog_SMALL.fng     6962C0CD
53.42781067 UI_PC_Help_Bar.fng          33AC1CB4
53.42782974 UI_Main.fng                 C343126A
53.42784882 GarageMain.fng              4CDD8B14
53.42790222 UI_MagazineBack.fng         FA8CC482
53.42796707 GenericDialog_SMALL.fng     6962C0CD
53.42798615 UI_PC_Help_Bar.fng          33AC1CB4
53.42800522 UI_Main.fng                 C343126A
53.42802429 GarageMain.fng              4CDD8B14
53.42922211 GenericDialog_SMALL.fng     6962C0CD
53.42924881 UI_PC_Help_Bar.fng          33AC1CB4
53.42926788 UI_Main.fng                 C343126A
53.42928696 GarageMain.fng              4CDD8B14

The excerpt above is probably from one cycle in the update loop, and is repeated a lot.

Some time later I found a very similar looking proc: 43DB50:SomeHashCS43DB50(), so I copied the previous hook for this function. This one gets called many times seemingly at startup and when car parts or maps get loaded.

When writing this I took a better look at the procedures and saw that the only difference really is that the first one is case insensitive while the second one is case sensitive. In the 505450:SomeHashCI505450() one, it checks for every character if it is in the range 'a'-'z' and subtracts 0x20 if so, making the character uppercase before updating the hash.

See hashes.txt (< 1MB) for list of hashes, each line is formatted as hash\tinput\tresult\tproc\n. This file is updated at random times. 8 minutes of playing gives about a 277MB file, all output was piped to sort | uniq.

How I collected these can be seen in the nfsu2-re-hooks/hook-*-hash-*.c files.

Symbols:

Index

Hash functions > Messing with hash results

Messing with hash results #

I had an impulse to try to return different results for certain hashes. For example, return the result of FIRETRUCK when the input is 240SX. The result is .. difficult to describe. Here's some short points:

jumping firetruck
Hop hop skippity hop

dyno chart
Here's a dyno chart of the firetruck

While the firetruck is unused in the game, I also tried to do this with the taxi, but it gave the same results. Then I tried to replace it with a "normal" car, the Mustang.

driving invisible mustang
Car?

The behavior isn't always the same, just give it a try and see for yourself. Replacing the taxi with the ambulance just lags out the game whenever a taxi comes in sight.

Check the function SomeHashCS43DB50Print in the file nfsu2-re-hooks/hook-43DB50-hash-cs.c to see how I did this, the code to replace cars is in comments.

Index

Debug print #

While scrolling through the data segment, I found these interesting strings:

.rdata:0079B160 aDeletingPackag db 'Deleting package [%s]',0Ah,0
.rdata:0079B177                 align 4
.rdata:0079B178 aUnActivateXX   db 'Un-Activate!!! %x %x',0Ah,0
.rdata:0079B18E                 db 2 dup(0)
.rdata:0079B190 aWillBeUnloaded db 'Will be unloaded [%s]',0Ah,0

Even more interesting is that these strings are using by pushing them on to the stack, followed by a call to a specific proc that looks like this:

.text:0050D510 sub_50D510  proc near
.text:0050D510                 xor     eax, eax
.text:0050D512                 retn
.text:0050D512 sub_50D510  endp

It appears as if this used to be some kind of debug printf function, so I named it sub_50D510_DebugPrint. I hooked the function and made it output to a log file, see nfsu2-re-hooks/replace-50D510-DebugPrint.c.

It also seems to pass data that is not a pointer to a string. I'm guessing since the implementation of this function was removed (and now just returns zero), and there were probably other removed functions, so some calls that are supposed to be done to different functions may now all point to this one function that also happened to be used for the debug print stuff. My simple solution is to check if the first passed argument points to memory in the data section, and print if so.

I did a short session browsing customization options and performance tuning with this enabled, the results can be seen in the log file debugstring50D510.txt. Each line is formatted as debugstr\t50D510\tcallee\tstring\n. Here is a 'small' excerpt:

DIALOG :: --------- ShowDialog -----------
DIALOG :: |_ Do you want to convert your trunk to carbon fiber?
DIALOG :: Oh shit - the control mask is zero.  This is bad.  Try to use Top Package's control mask.
DIALOG ::  |_ TopPackage = UI_GenericParts_Browser.fng, mask = 0.
DIALOG ::    |_ Crap - mask is zero, forcing to 0xff.
DIALOG :: Success : Control mask = 255, Handle = 2
DIALOG :: --------- ShowDialog Finished -----------
Init Package GenericDialog_SMALL.fng
DIALOG :: Constructor
Joy Event: FEPad_Accept[0]
Joy Event: FEPad_Accept[0]
Joy Event: FEPad_Back[1]
Joy Event: FEPad_Back[1]
DIALOG :: Tick, ReturnWithMessage Set, Dismissing: [GenericDialog_SMALL.fng] [b4623f67]
DIALOG :: DismissDialog.  Handle(2).  Current handle(2)
DIALOG ::  |_ Found, popping.
Queue popping GenericDialog_SMALL.fng
Will unload -----------GenericDialog_SMALL.fng------------------
Will be unloaded [GenericDialog_SMALL.fng]
DIALOG :: Closing, sending message (b4623f67) to (UI_GenericParts_Browser.fng).
Send message[b4623f67] to package [UI_GenericParts_Browser.fng]
Message was queued
Un-Activate!!! 37a52f0 37e69c0
Deleting package [GenericDialog_SMALL.fng]
package[GenericDialog_SMALL.fng] will unload

Most of the messages seem to be about dialogs, joy (keyboard) events, FEng/fng packages (I'm guessing those are ui resources/designs?).

Index

UI #

Names with .FNG seem to be occuring a lot, it seems like that are UI resources or some kind of UI widget descriptors.

In text strings, ^ is a line feed (but \n might work as well, TODO).

UI things seem to work on a 640x480 canvas. Most things (especially when mouse is involved) seem to be positioned ([-320,+320],[-240,+240]).

Index

UI > Dialogs

Dialogs #

Thanks to the Debug print discovery, my attention got grabbed by something that looked like dialog code. These leftover debug strings helped so much.

Someone left a trace here:

.text:005541FC                 call    Dialog__ctor
.text:00554201                 push    offset aDialogConstruc ; "DIALOG :: Constructor\n"
.text:00554206                 call    sub_50D510_DebugPrint

One of the first thing 558020:ShowDialog() does is calling 526C40:GetFNGforDialog(), which looks like initializes the dialog maybe? One argument is passed, seemingly the dialog name. But then it checks something at the passed dialog name +324h. Conclusion: it's passing a struct with dialog info, of which the first member is the name.

Index

UI > Dialogs > GetFNGForDialog

GetFNGForDialog #

This function is only used once, by the 558020:ShowDialog() function. It checks the value at +324h, which seems to be a pointer to a string, looks like maybe a type of dialog? It's checked if that value is either NULL or empty string or equal to animating or 3button. I decided to reimplement that function. Fun fact: while doing this I managed to write an infinite loop which caused a BSOD.

At the end I decided upon the name 526C40:GetFNGforDialog(). It returns a pointer to a string that says what FNG to display for the dialog that was passed.

The dialog info may already have the FNG set, this function will return that unless it's not set or it's not either animating or 3button.

See the nfsu2-re-hooks/replace-526C40-GetFNGForDialog.c function for the complete reimplementation.

Here's me messing with the returned value:

dialog
HelpDialog_SMALL.fng

dialog
GenericDialog_MED.fng

dialog
GenericDialog_ThreeButton.fng

dialog
GenericDialog_Animate_SMALL.fng

I can't remember seeing this animate style dialog... But when adding some debug prints, it seems like the online/LAN play uses this: dialog 'Retrieving updated games list^from the server...' has type animating but that dialog disappears so fast I probably never noticed it.

Then I tried replacing it with something totally different, UI_GenericParts_Browser.fng. I half expected the game to crash, so I was surprised when it showed me this:

main menu ui on splash screen
:)

Seems like this is a nice place to test loading other screens. Some do crash, but here's UI_OLEAMessenger.fng:

online messenger menu with placeholder text
No idea why that text is .. Spanish?

Printing the dialog info struct address sadly showed that it wasn't stored in the executable itself so it's probably somewhere deep in a bin file.

Index

UI > Dialogs (continuation)

DialogInfo struct made by looking at all of the above and more: (Especially 4FF250:Dialog__ctor() helped here.)

struct DialogInfo {
	char text[768];
	unsigned int leftButtonLanguageString;
	unsigned int leftButtonMessage;
	unsigned int rightButtonLanguageString;
	unsigned int righttButtonMessage;
	unsigned int middleButtonLanguageString;
	unsigned int middleButtonMessage;
	int field_318;
	int field_31C;
	char *parentFNGName;
	char *myFNGName;
	int numButtons;
	char field_32C;
	char isHelpDialog;
	char field_32E;
	char textNeedsSomeWcharConversion; /*set to 0 in 55806D*/
	int field_330; /*set to 0 in 558074*/
	int field_334;
	int field_338;
};
EXPECT_SIZE(struct DialogInfo, 0x338);
ASSERT_OFFSET(struct DialogInfo, myFNGName, 0x324);
ASSERT_OFFSET(struct DialogInfo, isHelpDialog, 0x32D);

Symbols:

Index

UI > FNG things

FNG things #

Thanks to 558020:ShowDialog() code, following things were discovered:

struct FNGShowData {
	char *fngname;
	int arg1;
	unsigned int helpTextLanguageString; /*set to {structFNGData+8}*/
	unsigned int helpBarMask; /*set to {structFNGData+C}*/
	int arg2;
};
EXPECT_SIZE(struct FNGMessage, 0x14);

typedef int (fnginithandler)(struct FNGShowData *msg);

struct FNGData {
	char *name;
	fnginithandler *initializeHandler;
	unsigned int helpTextLanguageString;
	unsigned int helpBarMask;
	int field_10;
	int field_14;
	int field_18;
};
EXPECT_SIZE(struct FNGData, 0x1C);

struct FNGInfo {
        int field_0;
        struct FNGInfo *child;
        int field_8;
        char *fngName;
        unsigned int hash;
        int field_14;
        int field_18;
        int controlMask;
        int field_20;
        int field_24;
        int field_28;
        int field_2C;
        int field_30;
        int field_34;
        int field_38;
        int field_3C;
        int field_40;
        int field_44;
        int field_48;
        struct UIElement *rootUIElement;
};

struct UIData {
        /*TODO*/
        struct UIData_Field8 *field_8;
};


struct UIData_Field8 {
        /*incomplete*/
        /*+E4*/
        struct FNGInfo *topPackage;
};

struct FNGData fngdata[] = {
collapsed data /*7F7DC8*/{ "UI_Main.fng", // 0 0x4ED6B0, HELP_MAIN_MENU, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE, 0x0, 0x100, 0x0 }, /*7F7DE4*/{ "UI_OptionsMain.fng", // 1 0x4EFDE0, HELP_OPTIONS_MAIN, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F7E00*/{ "UI_Options.fng", // 2 0x4E3710, HELP_OPTIONS, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK, 0x0, 0x1CA, 0x0 }, /*7F7E1C*/{ "UI_Wheel_Options.fng", // 3 0x4E3770, HELP_WHEEL, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK, 0x0, 0x1CA, 0x0 }, /*7F7E38*/{ "UI_PC_Customize_Options.fng", // 4 0x4E36B0, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK, 0x0, 0x1CA, 0x0 }, /*7F7E54*/{ "UI_Options_PC_Controller.fng", // 5 0x4F92E0, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK | BUTTON_DYNO_RESET_OR_PC_NAV_RESET_KEYS, 0x0, 0x1CA, 0x0 }, /*7F7E70*/{ "UI_PC_LAN_ServerSelect.fng", // 6 0x4FD4B0, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK | BUTTON_OL_HOST_LAN_SERVER, 0x0, 0x1CA, 0x0 }, /*7F7E8C*/{ "UI_PC_LAN.fng", // 7 0x4D6C90, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x1CA, 0x0 }, /*7F7EA8*/{ "UI_PC_Help_Bar.fng", // 8 0x552280, 0, 0, 0x0, 0x80, 0x0 }, /*7F7EC4*/{ "UI_Trailers.fng", // 9 0x4CFB20, HELP_OPTIONS_TRAILERS, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F7EE0*/{ "Credits.fng", // 10 0x4B8720, HELP_OPTIONS_CREDITS, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F7EFC*/{ "ScreenPrintf.fng", // 11 0x0, 0, 0, 0x0, 0x100, 0x1 }, /*7F7F18*/{ "loading_boot.fng", // 12 0x0, 0, 0, 0x0, 0x80, 0x2 }, /*7F7F34*/{ "UI_CareerCrib.fng", // 13 0x4ED780, HELP_CRIB, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0xC0, 0x0 }, /*7F7F50*/{ "UI_CribRewardOptionsMain.fng", // 14 0x4ED7E0, HELP_CRIB_REWARD_OPTIONS, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0xC0, 0x0 }, /*7F7F6C*/{ "UI_CareerCarSelect.fng", // 15 0x4FC110, HELP_CRIB_CAR_SELECT, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0xC0, 0x0 }, /*7F7F88*/{ "UI_StartCareer.fng", // 16 0x4ED710, HELP_CAREER_OPTIONS, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0xC0, 0x0 }, /*7F7FA4*/{ "UI_MagazineBack.fng", // 17 0x554E60, 0, 0, 0x0, 0xC0, 0x0 }, /*7F7FC0*/{ "UI_MagazineReward.fng", // 18 0x56B140, 0, 0, 0x0, 0xC0, 0x0 }, /*7F7FDC*/{ "UI_MagazineSelect.fng", // 19 0x55B520, HELP_CRIB_MAGAZINE, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0xC0, 0x0 }, /*7F7FF8*/{ "UI_MagazineView.fng", // 20 0x554EC0, 0, 0, 0x0, 0xC0, 0x0 }, /*7F8014*/{ "UI_RewardsSponsor.fng", // 21 0x4ED840, HELP_CRIB_REWARD_SPONSORS, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK, 0x0, 0xC0, 0x0 }, /*7F8030*/{ "UI_CareerWorldMap.fng", // 22 0x4F9D30, HELP_CRIB_WORLD_MAP, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK | LAYOUT_WORLDMAP_POSITION, 0x0, 0x80, 0x0 }, /*7F804C*/{ "UI_Status_Master.fng", // 23 0x4E1B80, 0, BUTTON_PC_NAV_BACK | LAYOUT_WORLDMAP_POSITION | LAYOUT_ONE_LINE_NO_BG, 0x0, 0x80, 0x0 }, /*7F8068*/{ "UI_Status_Career.fng", // 24 0x4EEB10, 0, 0, 0x0, 0x80, 0x0 }, /*7F8084*/{ "UI_Status_Region.fng", // 25 0x4B1C90, 0, 0, 0x0, 0x80, 0x0 }, /*7F80A0*/{ "UI_Status_DVD.fng", // 26 0x4B1D90, 0, 0, 0x0, 0x80, 0x0 }, /*7F80BC*/{ "EA_Trax_Jukebox.fng", // 27 0x554F20, HELP_OPTIONS_EATRAX, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F80D8*/{ "UI_Menu_Asset_Reputation.fng", // 28 0x553B00, 0, 0, 0x0, 0x80, 0x0 }, /*7F80F4*/{ "UI_CareerCarLot.fng", // 29 0x4FC0B0, HELP_CAREER_CARLOT, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0xC0, 0x0 }, /*7F8110*/{ "UI_EngageEventDialog.fng", // 30 0x4CE650, 0, 0, 0x0, 0xC0, 0x0 }, /*7F812C*/{ "UI_EngageRaceDialog.fng", // 31 0x4E34A0, 0, 0, 0x0, 0xC0, 0x0 }, /*7F8148*/{ "UI_EngageShopDialog.fng", // 32 0x4CE6B0, 0, 0, 0x0, 0xC0, 0x0 }, /*7F8164*/{ "UI_Showcase_Preview.fng", // 33 0x4E3500, 0, 0, 0x0, 0xC0, 0x0 }, /*7F8180*/{ "UI_Showcase_DPAD.fng", // 34 0x4CF5C0, 0, 0, 0x0, 0xC0, 0x0 }, /*7F819C*/{ "IG_PlayMovie.fng", // 35 0x554E00, 0, 0, 0x0, 0xC0, 0x0 }, /*7F81B8*/{ "UI_Pause.fng", // 36 0x4F4790, 0, BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK | LAYOUT_ONE_LINE_NO_BG | LAYOUT_PAUSEMENU_POSITION, 0x0, 0xC0, 0x0 }, /*7F81D4*/{ "UI_PauseOptionsMain.fng", // 37 0x4F47F0, 0, BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK | LAYOUT_ONE_LINE_NO_BG | LAYOUT_PAUSEMENU_POSITION, 0x0, 0x80, 0x0 }, /*7F81F0*/{ "UI_PauseOptions.fng", // 38 0x4EB650, 0, BUTTON_PC_NAV_BACK | LAYOUT_ONE_LINE_NO_BG, 0x0, 0x440, 0x0 }, /*7F820C*/{ "UI_ReplayControl.fng", // 39 0x4D7AD0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8228*/{ "HUD_SingleRace.fng", // 40 0x0, 0, 0, 0x0, 0x168, 0x1 }, /*7F8244*/{ "HUD_Drift.fng", // 41 0x0, 0, 0, 0x0, 0x80, 0x1 }, /*7F8260*/{ "HUD_Drag.fng", // 42 0x0, 0, 0, 0x0, 0x80, 0x1 }, /*7F827C*/{ "UI_InGame_WorldMap.fng", // 43 0x4F9C10, 0, BUTTON_PC_NAV_BACK | LAYOUT_WORLDMAP_POSITION | LAYOUT_ONE_LINE_NO_BG, 0x0, 0x80, 0x0 }, /*7F8298*/{ "UI_EngageMessageDialog.fng", // 44 0x4B2240, 0, BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK | LAYOUT_WORLDMAP_POSITION | LAYOUT_ONE_LINE_NO_BG, 0x0, 0x80, 0x0 }, /*7F82B4*/{ "UI_SMS_Mailbox.fng", // 45 0x4E2050, 0, BUTTON_PC_NAV_READ_MESSAGE | BUTTON_PC_NAV_BACK | BUTTON_PC_NAV_DELETE | LAYOUT_WORLDMAP_POSITION | LAYOUT_ONE_LINE_NO_BG, 0x0, 0x80, 0x0 }, /*7F82D0*/{ "GarageMain.fng", // 46 0x4EB130, 0, 0, 0x0, 0x80, 0x0 }, /*7F82EC*/{ "DiscError.fng", // 47 0x5522D0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8308*/{ "GenericDialog.fng", // 48 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8324*/{ "GenericDialog_LARGE.fng", // 49 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8340*/{ "GenericDialog_MED.fng", // 50 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F835C*/{ "GenericDialog_SMALL.fng", // 51 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8378*/{ "GenericDialog_Animate_LARGE.fng", // 52 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8394*/{ "GenericDialog_Animate_MED.fng", // 53 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F83B0*/{ "GenericDialog_Animate_SMALL.fng", // 54 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F83CC*/{ "GenericDialog_ThreeButton.fng", // 55 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F83E8*/{ "IG_GenericDialog_LARGE.fng", // 56 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8404*/{ "IG_GenericDialog_MED.fng", // 57 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8420*/{ "IG_GenericDialog_SMALL.fng", // 58 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F843C*/{ "HelpDialog_LARGE.fng", // 59 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8458*/{ "HelpDialog_MED.fng", // 60 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8474*/{ "HelpDialog_SMALL.fng", // 61 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8490*/{ "GenericerDialog.fng", // 62 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F84AC*/{ "MU_QRTransmissionSelect.fng", // 63 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F84C8*/{ "MU_QuickRaceCarSelect.fng", // 64 0x4EB1A0, 0, 0, 0x0, 0x80, 0x0 }, /*7F84E4*/{ "Chyron_FE.fng", // 65 0x4CB180, 0, 0, 0x0, 0x80, 0x0 }, /*7F8500*/{ "Chyron_IG.fng", // 66 0x4CB180, 0, 0, 0x0, 0x80, 0x0 }, /*7F851C*/{ "UI_DebugTest.fng", // 67 0x4B89B0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8538*/{ "UI_InGameDialog.fng", // 68 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8554*/{ "UI_VirtualKeyboard.fng", // 69 0x4EFD40, HELP_VIRTUAL_KEYBOARD, 0, 0x0, 0x258, 0x0 }, /*7F8570*/{ "UI_QRModeSelect.fng", // 70 0x4EF280, HELP_QR_MODE_SELECT, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F858C*/{ "UI_QRModeOptions.fng", // 71 0x4FA550, HELP_QR_MODE_OPTIONS, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x200, 0x0 }, /*7F85A8*/{ "UI_QRTrackSelect.fng", // 72 0x4EF9B0, HELP_TRACK_SELECT, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F85C4*/{ "UI_QRCarSelect.fng", // 73 0x4FC190, HELP_QR_CAR_SELECT, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK | BUTTON_PC_NAV_CUSTOMIZE, 0x0, 0x100, 0x0 }, /*7F85E0*/{ "UI_OLCarSelect.fng", // 74 0x4FC190, HELP_OL_CAR_SELECT, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK | BUTTON_PC_NAV_CUSTOMIZE, 0x0, 0x100, 0x0 }, /*7F85FC*/{ "2P_PressStart.fng", // 75 0x4CCED0, 0, 0, 0x0, 0x100, 0x0 }, /*7F8618*/{ "UI_DebugCarCustomize.fng", // 76 0x554A00, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8634*/{ "UI_ChooseCustomizeCategory.fng", // 77 0x5591E0, HELP_SHOP_SELECT, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8650*/{ "UI_ChoosePerformanceCategory.fng", // 78 0x559C30, HELP_PERFORMANCE_SHOP_MAIN, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F866C*/{ "UI_GenericParts_Browser.fng", // 79 0x566430, HELP_CRIB_CHANGEPARTS_MAIN, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0xFFFFFFFF, 0x80, 0x0 }, /*7F8688*/{ "UI_ChoosePaintCategory.fng", // 80 0x55A400, HELP_GRAPHICS_SHOP_MAIN, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_CANCEL_CHANGES | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F86A4*/{ "UI_ChoosePerformancePackage.fng", // 81 0x55C8D0, HELP_PERFORMANCE_SHOP_MAIN, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_INSTALL_PACKAGE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F86C0*/{ "UI_BuyPerformanceParts.fng", // 82 0x56C150, HELP_PERFORMANCE_SHOP_PART_SELECT, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_INSTALL_PART | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F86DC*/{ "UI_PerformanceBrandSelect.fng", // 83 0x554B80, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE, 0x0, 0x80, 0x0 }, /*7F86F8*/{ "UI_Paint.fng", // 84 0x56C1B0, HELP_GRAPHICS_SHOP_PAINT_SELECT, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8714*/{ "UI_ChooseVinylLayer.fng", // 85 0x555440, HELP_GRAPHICS_SHOP_VINYL_LAYER_SELECT, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8730*/{ "UI_ChooseUniquePart.fng", // 86 0x554B20, HELP_UNIQUES_BROWSER, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_INSTALL_PART | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F874C*/{ "UI_DecalMain.fng", // 87 0x554AC0, HELP_GRAPHICS_SHOP_DECAL_SELECT_CATEGORY, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8768*/{ "UI_DecalsOverlay.fng", // 88 0x56B900, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8784*/{ "UI_DecalsOverlayInvis.fng", // 89 0x56B900, 0, 0, 0x0, 0x80, 0x0 }, /*7F87A0*/{ "UI_ChooseCustomHUD.fng", // 90 0x554DA0, HELP_CHANGE_HUD_COLOR, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F87BC*/{ "UI_ChooseRimBrand.fng", // 91 0x554A60, HELP_BODY_SHOP_MAIN, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F87D8*/{ "UI_Rims_Browser.fng", // 92 0x56B350, HELP_BODY_SHOP_MAIN, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F87F4*/{ "UI_ChooseSpinner.fng", // 93 0x56C0F0, HELP_BODY_SHOP_MAIN, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8810*/{ "UI_PerformanceDyno_MAIN.fng", // 94 0x55D7F0, 0, BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x100, 0x0 }, /*7F882C*/{ "UI_PerformanceTuning_Master.fng", // 95 0x55D8B0, 0, BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8848*/{ "UI_PerformanceTuning_Graph.fng", // 96 0x55DB70, 0, BUTTON_PC_NAV_BACK | BUTTON_DYNO_RESET_OR_PC_NAV_RESET_KEYS | BUTTON_DYNO_TIP, 0x0, 0x100, 0x0 }, /*7F8864*/{ "UI_PerformanceTuning_NOS.fng", // 97 0x55DB70, 0, BUTTON_PC_NAV_BACK | BUTTON_DYNO_RESET_OR_PC_NAV_RESET_KEYS | BUTTON_DYNO_TIP, 0x0, 0x100, 0x0 }, /*7F8880*/{ "UI_PerformanceTuning_Sliders.fng", // 98 0x55DB10, 0, BUTTON_PC_NAV_BACK | BUTTON_DYNO_RESET_OR_PC_NAV_RESET_KEYS, 0x0, 0x100, 0x0 }, /*7F889C*/{ "UI_PerformanceTuning_Drivetrain.fng", // 99 0x55DB10, 0, BUTTON_PC_NAV_BACK | BUTTON_DYNO_RESET_OR_PC_NAV_RESET_KEYS | BUTTON_DYNO_TIP, 0x0, 0x100, 0x0 }, /*7F88B8*/{ "UI_PerformanceTuning_Setting.fng", // 100 0x55D850, 0, BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x100, 0x0 }, /*7F88D4*/{ "UI_PerformanceDyno_Chart.fng", // 101 0x55AF20, 0, BUTTON_PC_NAV_CONTINUE, 0x0, 0x100, 0x0 }, /*7F88F0*/{ "UI_PerformanceDyno_Results.fng", // 102 0x55AF80, 0, BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x100, 0x0 }, /*7F890C*/{ "UI_ICEMAIN.fng", // 103 0x560550, HELP_CARSPECIALTIES_SHOP_MAIN, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x100, 0x0 }, /*7F8928*/{ "UI_IcePartsOverlay.fng", // 104 0x56C3C0, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_INSTALL_PART | BUTTON_PC_NAV_BACK, 0x0, 0x100, 0x0 }, /*7F8944*/{ "UI_CustomNeonMain.fng", // 105 0x554C40, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x100, 0x0 }, /*7F8960*/{ "UI_NeonPartsOverlay.fng", // 106 0x554BE0, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_INSTALL_PART | BUTTON_PC_NAV_BACK, 0x0, 0x100, 0x0 }, /*7F897C*/{ "UI_CustomHUDOverlay.fng", // 107 0x56C420, HELP_HUD_SELECT, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_INSTALL_PART | BUTTON_PC_NAV_BACK, 0x0, 0x100, 0x0 }, /*7F8998*/{ "UI_PostRaceResults.fng", // 108 0x4FBB60, 0, BUTTON_PC_NAV_CONTINUE | LAYOUT_ONE_LINE_NO_BG | LAYOUT_RIGHT_POSITION, 0x0, 0x80, 0x0 }, /*7F89B4*/{ "UI_PostRaceReward.fng", // 109 0x4D72F0, 0, BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F89D0*/{ "UI_SponsorPopup.fng", // 110 0x4C1280, 0, BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F89EC*/{ "UI_Sponsorship_new.fng", // 111 0x4F4730, 0, BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8A08*/{ "UI_PostRace_TournResults.fng", // 112 0x4FBF00, 0, BUTTON_PC_NAV_CONTINUE | LAYOUT_ONE_LINE_NO_BG | LAYOUT_RIGHT_POSITION, 0x0, 0x80, 0x0 }, /*7F8A24*/{ "UI_PostRace_TournStandings.fng", // 113 0x4FBF60, 0, BUTTON_PC_NAV_CONTINUE, 0x0, 0x80, 0x0 }, /*7F8A40*/{ "UI_PostRaceStats.fng", // 114 0x4FD6C0, 0, BUTTON_PC_NAV_BACK | LAYOUT_ONE_LINE_NO_BG, 0x0, 0x80, 0x0 }, /*7F8A5C*/{ "UI_PostRace.fng", // 115 0x4F3940, 0, BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK | LAYOUT_ONE_LINE_NO_BG | LAYOUT_PAUSEMENU_POSITION, 0x0, 0x80, 0x0 }, /*7F8A78*/{ "MU_PostRaceConfirm.fng", // 116 0x0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8A94*/{ "LS_BlankMovie.fng", // 117 0x4A8AD0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8AB0*/{ "LS_EALogo.fng", // 118 0x4A89D0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8ACC*/{ "MW_LS_IntroFMV.fng", // 119 0x4C52D0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8AE8*/{ "MW_LS_Splash.fng", // 120 0x4C5500, 0, 0, 0x0, 0x80, 0x0 }, /*7F8B04*/{ "UG_LS_IntroFMV.fng", // 121 0x4C52D0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8B20*/{ "UG_LS_Splash.fng", // 122 0x4C5500, 0, 0, 0x0, 0x80, 0x0 }, /*7F8B3C*/{ "LS_THXMovie.fng", // 123 0x4A8D90, 0, 0, 0x0, 0x80, 0x0 }, /*7F8B58*/{ "LS_PSAMovie.fng", // 124 0x4A8C60, 0, 0, 0x0, 0x80, 0x0 }, /*7F8B74*/{ "LS_Demo_Legal.fng", // 125 0x4C50C0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8B90*/{ "LS_Demo_PSA.fng", // 126 0x4A8EC0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8BAC*/{ "LS_Demo_ESRB.fng", // 127 0x4A8F50, 0, 0, 0x0, 0x80, 0x0 }, /*7F8BC8*/{ "LS_Demo_Warning.fng", // 128 0x4A9080, 0, 0, 0x0, 0x80, 0x0 }, /*7F8BE4*/{ "UI_EngageMessageDialog.fng", // 129 0x4B2240, 0, BUTTON_PC_NAV_BACK | BUTTON_PC_NAV_DELETE | LAYOUT_ONE_LINE_NO_BG, 0x0, 0x80, 0x0 }, /*7F8C00*/{ "", // 130 0x4D9480, 0, 0, 0x0, 0x80, 0x0 }, /*7F8C1C*/{ "LS_LangSelect.fng", // 131 0x4F48A0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8C38*/{ "LS_Chinese_Health.fng", // 132 0x4A87E0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8C54*/{ "UI_OL_Disconnect.fng", // 133 0x4D0390, 0, 0, 0x0, 0x80, 0x0 }, /*7F8C70*/{ "UI_OL_Disconnect_BG.fng", // 134 0x4D03F0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8C8C*/{ "UI_OL_WebOffer.fng", // 135 0x49D550, 0, 0, 0x0, 0x80, 0x0 }, /*7F8CA8*/{ "UI_OL_WebOffer2.fng", // 136 0x4B9E80, 0, 0, 0x0, 0x0, 0x0 }, /*7F8CC4*/{ "UI_OL_News.fng", // 137 0x49D4F0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8CE0*/{ "UI_OLLobbyRoom.fng", // 138 0x4FA990, HELP_OL_LOBBY, 0, 0x0, 0x200, 0x0 }, /*7F8CFC*/{ "UI_OLGameRoom.fng", // 139 0x4F1820, HELP_OL_GAMEROOM, 0, 0x0, 0x200, 0x0 }, /*7F8D18*/{ "UI_OLGameRoom_host.fng", // 140 0x4F1880, 0, 0, 0x0, 0x200, 0x0 }, /*7F8D34*/{ "UI_OLGameRoom_client.fng", // 141 0x4F18E0, 0, 0, 0x0, 0x200, 0x0 }, /*7F8D50*/{ "UI_OLPreRaceStart.fng", // 142 0x4D3430, 0, 0, 0x0, 0x80, 0x0 }, /*7F8D6C*/{ "UI_OL_ViewCar.fng", // 143 0x4FB260, HELP_OL_VIEWCAR, BUTTON_PC_NAV_BACK, 0x0, 0x100, 0x0 }, /*7F8D88*/{ "UI_OL_ViewTrack.fng", // 144 0x4FB2C0, HELP_OL_VIEWTRACK, BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8DA4*/{ "UI_OLCarLot.fng", // 145 0x4FD450, HELP_OL__TRADECAR_MAIN, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8DC0*/{ "UI_OLMAIN.fng", // 146 0x4F1940, HELP_OL_MAIN_MENU, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK | BUTTON_PC_NAV_EA_MESSENGER, 0x0, 0x80, 0x0 }, /*7F8DDC*/{ "UI_OLFilters.fng", // 147 0x4E6530, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK | BUTTON_PC_NAV_EA_MESSENGER, 0x0, 0x80, 0x0 }, /*7F8DF8*/{ "PC_OL_Lobby.fng", // 148 0x4FA990, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK | BUTTON_PC_NAV_EA_MESSENGER, 0x0, 0x80, 0x0 }, /*7F8E14*/{ "PC_OL_GameRoom.fng", // 149 0x4F1820, HELP_OL_GAME_ROOM, BUTTON_PC_NAV_BACK | BUTTON_PC_NAV_EA_MESSENGER, 0x0, 0x200, 0x0 }, /*7F8E30*/{ "UI_OLPassword.fng", // 150 0x4E42C0, 0, 0, 0x0, 0x80, 0x0 }, /*7F8E4C*/{ "UI_OLRankings_Personal.fng", // 151 0x4F1BC0, HELP_OL_PERSONAL_RANK, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK | BUTTON_PC_NAV_EA_MESSENGER, 0x0, 0x80, 0x0 }, /*7F8E68*/{ "UI_OLRankings_Overall.fng", // 152 0x4FB1A0, HELP_OL_OVERALL_RANK, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK | BUTTON_PC_NAV_EA_MESSENGER, 0x0, 0x80, 0x0 }, /*7F8E84*/{ "UI_OLRankings_Monthly.fng", // 153 0x4E64D0, HELP_OL_MONTHLY_RANK, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK | BUTTON_PC_NAV_EA_MESSENGER, 0x0, 0x80, 0x0 }, /*7F8EA0*/{ "UI_OLEAMessenger.fng", // 154 0x4F22F0, HELP_OL_MESSENGER, 0, 0x0, 0x80, 0x0 }, /*7F8EBC*/{ "PC_OL_SEARCH.fng", // 155 0x4D5070, 0, 0, 0x0, 0x80, 0x0 }, /*7F8ED8*/{ "UI_OL_FriendDialogue.fng", // 156 0x4FB200, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8EF4*/{ "UI_OLX_Message.fng", // 157 0x4F24E0, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8F10*/{ "UI_OLRankings.fng", // 158 0x4F2540, HELP_OL_RANKINGS_MENU, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK | BUTTON_PC_NAV_EA_MESSENGER, 0x0, 0x80, 0x0 }, /*7F8F2C*/{ "UI_OLX_FindResults.fng", // 159 0x4F25A0, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8F48*/{ "UI_OL_Feedback.fng", // 160 0x4F27E0, HELP_OL_MESSENGER_FEEDBACK, 0, 0x0, 0x80, 0x0 }, /*7F8F64*/{ "UI_OL_Challenge.fng", // 161 0x4D6940, 0, 0, 0x0, 0x80, 0x0 }, /*7F8F80*/{ "UI_OLViewCareer.fng", // 162 0x4E88C0, 0, BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F8F9C*/{ "UI_OLISPConnect.fng", // 163 0x4B9750, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x0, 0x0 }, /*7F8FB8*/{ "UI_OLSelectPersona.fng", // 164 0x4E3CD0, HELP_OL_PS2_PERSONA_SELECT, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK | BUTTON_PC_NAV_DELETE, 0x0, 0x100, 0x0 }, /*7F8FD4*/{ "UI_OLCreateUser.fng", // 165 0x4E3D50, HELP_OL_PS2_CREATEACCOUNT, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK, 0x0, 0x0, 0x0 }, /*7F8FF0*/{ "UI_OLCreateUser_2.fng", // 166 0x4BAA60, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_BACK, 0x0, 0x0, 0x0 }, /*7F900C*/{ "UI_OLAgeVerif.fng", // 167 0x4E3DD0, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x0, 0x0 }, /*7F9028*/{ "UI_OLAgeTooYoung.fng", // 168 0x49E780, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x0, 0x0 }, /*7F9044*/{ "UI_OLUseExisting.fng", // 169 0x4E3E30, HELP_OL_PS2_USEEXISTING, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x100, 0x0 }, /*7F9060*/{ "UI_OLForgotAccountName.fng", // 170 0x4E3BD0, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x100, 0x0 }, /*7F907C*/{ "UI_OLEALogin.fng", // 171 0x4E3C50, HELP_OL_PS2_EALOGIN, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x100, 0x0 }, /*7F9098*/{ "UI_DateEntry.fng", // 172 0x552720, HELP_OL_PS2_DATEWIDGET, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x0, 0x0 }, /*7F90B4*/{ "DiscErrorPC.fng", // 173 0x4D97A0, 0, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F90D0*/{ "UI_ProfileManager.fng", // 174 0x4F4900, HELP_OPTIONS_MAIN, BUTTON_PC_NAV_QUIT | BUTTON_PC_NAV_CONTINUE | BUTTON_PC_NAV_BACK, 0x0, 0x80, 0x0 }, /*7F90EC*/{ "UI_Deleteprofile.fng", // 175 0x4F4960, HELP_OPTIONS_MAIN, 0, 0x0, 0x80, 0x0 }, /*7F9108*/{ "MC_Bootup.fng", // 176 0x4F32B0, 0, 0, 0x0, 0x80, 0x0 }, /*7F9124*/{ "MC_List.fng", // 177 0x4D7070, HELP_LOAD_PROFILE_BOOT, BUTTON_PC_NAV_QUIT, 0x0, 0x80, 0x0 }, /*7F9140*/{ "MC_Main.fng", // 178 0x4F3320, 0, BUTTON_PC_NAV_QUIT, 0x0, 0x80, 0x0 }, /*7F915C*/{ "MC_Background.fng", // 179 0x4A8740, 0, 0, 0x0, 0x80, 0x0 },
}; struct PCHelpBarFNGObject { // todo }

Short excerpt from hooking 50B790:ShowFNG()

arg2 for the dialogs are a pointer to the text string

ShowFNG(AB447B38 (""), 037864A0, 00000000) = 00000000
ShowFNG(33AC1CB4 ("UI_PC_Help_Bar.fng"), 0378BD30, 00000000) = 03D7EB60
ShowFNG(E503D390 ("MC_Bootup.fng"), 0379ADE0, 00000000) = 03786140
ShowFNG(23F4270A ("LS_PSAMovie.fng"), 03786120, 00000000) = 03787AD0
ShowFNG(F774ED37 ("MC_Background.fng"), 03786120, 00000000) = 03786290
ShowFNG(801E019C ("MC_Main.fng"), 03787F40, 00000000) = 0379F4B0
ShowFNG(136E3C13 ("MC_List.fng"), 0379C8C0, 00000000) = 037A86F0
ShowFNG(4CDD8B14 ("GarageMain.fng"), 037AE470, 00000000) = 037AFD70
ShowFNG(C343126A ("UI_Main.fng"), 037A8EC0, 00000000) = 037AE880
ShowFNG(8E2B101E ("UI_QRCarSelect.fng"), 037879D0, 00000000) = 0379D3B0
ShowFNG(887BBEA7 ("UI_ChooseCustomizeCategory.fng"), 0379BF40, 00000000) = 03787570
ShowFNG(DF3D7763 ("UI_Menu_Asset_Reputation.fng"), 03789730, 00000000) = 0378A940
ShowFNG(AF09F84F ("UI_GenericParts_Browser.fng"), 0378A860, 00000000) = 037BBBB0
ShowFNG(887BBEA7 ("UI_ChooseCustomizeCategory.fng"), 037B9F40, 00000000) = 037BE620
ShowFNG(8E2B101E ("UI_QRCarSelect.fng"), 037BE4A0, 00000000) = 03787660
ShowFNG(C343126A ("UI_Main.fng"), 0378A570, 00000000) = 0379F260
ShowFNG(F6C99F4D ("UI_QRModeSelect.fng"), 0379F0F0, 00000000) = 03788380
ShowFNG(8E2B101E ("UI_QRCarSelect.fng"), 0378BF40, 00000000) = 0379B7C0
ShowFNG(6962C0CD ("GenericDialog_SMALL.fng"), 0379B620, 00838640) = 037A80E0
ShowFNG(6962C0CD ("GenericDialog_SMALL.fng"), 037AE720, 00838640) = 037ABA50
ShowFNG(6962C0CD ("GenericDialog_SMALL.fng"), 037AE720, 00838640) = 037ABA50
ShowFNG(AB447B38 (""), 03767010, 00000000) = 00000000
ShowFNG(0EB1F7E5 ("HUD_SingleRace.fng"), 037A45B0, 00000000) = 00000000
ShowFNG(26691D4A ("Chyron_IG.fng"), 03793AE0, 00000000) = 03D7EBF0
ShowFNG(33AC1CB4 ("UI_PC_Help_Bar.fng"), 037B8870, 00000000) = 03D7EB60
ShowFNG(32124083 ("UI_Pause.fng"), 03796280, 00000000) = 037B8B90
ShowFNG(33AC1CB4 ("UI_PC_Help_Bar.fng"), 037C7D30, 00000000) = 03D7EB60
ShowFNG(73F51C74 ("UI_PauseOptionsMain.fng"), 037C3EF0, 00000000) = 037C6080
ShowFNG(AEA9078F ("UI_PauseOptions.fng"), 037BAE10, 00000000) = 037D8430
ShowFNG(73F51C74 ("UI_PauseOptionsMain.fng"), 037D8F40, 00000000) = 037DE0C0
ShowFNG(33AC1CB4 ("UI_PC_Help_Bar.fng"), 037DFF40, 00000000) = 03D7EB60
ShowFNG(32124083 ("UI_Pause.fng"), 037DDF40, 00000000) = 037DE2B0
ShowFNG(AA469286 ("UI_InGameDialog.fng"), 037C1120, 00838640) = 037C2AB0

struct FERenderObject {
        struct PoolEntry __parent;
        int field_8;
        int field_C;
        int field_10;
        int field_14;
        struct ObjectLink FERenderEPolyLink;
        /*size 0xA0*/
};
TODO:
.rdata:0079C118 aIconscroller_5 db 'IconScrollerMenu : Switch/Pop/Direct Pop called on me (0x%x).',0Ah
.rdata:0079C118                                         ; DATA XREF: sub_543D40+3E9?o
.rdata:0079C118                 db 0
.rdata:0079C157                 align 4
.rdata:0079C158 aIconscroller_4 db 'IconScrollerMenu : EXIT/INIT_COMPLETE called on me (0x%x).',0Ah,0
.rdata:0079C158                                         ; DATA XREF: sub_543D40+38B?o
.rdata:0079C194 aIconscroller_2 db 'IconScrollerMenu : UNDIM_COMPLETE.',0Ah,0

Index

UI > Some FNG screens

Some FNG screens #

This was done by putting pointers to their strings into the boot flow entries (see Speedy boot).

2P_PressStart.fng

2player press start
2P_PressStart.fng

loading_boot.fng

alternative loading screen with nfsu2 logo
loading_boot.fng

Index

UI > Some FNG screens > Replacing the loading screen

Replacing the loading screen #

I found loading_boot.fng pretty cool, so I wondered if that could be used instead for the loading screens (on boot, when loading race, going into freeroam, ..).

I've seen lots of places refer to a string with content PC_Loading.fng, so I thought maybe replacing that with loading_boot.fng will just work. And it does.

strcpy((char*) 0x790734, "loading_boot.fng");

Since the string is longer, it will override this string with an empty string though:

.rdata:00790744 aLoadingTipsFng db 'Loading_Tips.fng',0

It seems unused, so I guess it's fine. That made me wonder if that one could be used as loading screen, maybe it shows tips? But when trying it just shows a black screen. Maybe it's a cut feature?

Index

UI > Some FNG screens (continuation)

UI_DebugTest.fng

pulsing text and different colored circles
UI_DebugTest.fng

UI_OLAgeTooYoung.fng

age too young
UI_OLAgeTooYoung.fng

UI_OLMAIN.fng

online main menu
UI_OLMAIN.fng

UI_OL_Feedback.fng

online player feedback menu
UI_OL_Feedback.fng

UI_OL_FriendDialogue.fng

online friend dialogue
UI_OL_FriendDialogue.fng

UI_OL_Voice_Chat.fng

online voice chat
UI_OL_Voice_Chat.fng

Index

UI > ScreenPrintf.fng

ScreenPrintf.fng #

So this is an interesting name, but doesn't really seem used (it makes sense, it shouldn't be used in a release build of the game). Though now with the few functions I documented maybe I can make it show?

I tried following code:

static
void hook_on_msg_WM_CHAR(int wparam)
{
	if (wparam == 121) { // y
		AddFngToUIObject_1("ScreenPrintf.fng", 0);
	
}

It didn't crash, so that was a win already. The second parameter for that func is what I suspect some data to initialize the fng screen. It fortunately seems like either the ScreenPrintf one doesn't initialize anything or it has a null check. for the passed data It doesn't seem to show anything though. Then I thought of the UI elements and their visitor methods (like 504880:FNGInfo::VisitUIRecursively()). So I tried to print all the UI elements that are on screen.

Code nfsu2-re-hooks/debug-custom-uielementvisitor.c
static
void debug_custom_uielementvisitor_visit_element(struct UIElement *element, char *prefix)
{
	struct UIElement *containerchild;
	struct UILabel *label;
	char locbuf[100];
	char locbuf2[100];

	sprintf(locbuf, "%s    ", prefix);
	while (element) {
		locbuf2[0] = 0;
		if (element->type == 2) {
			label = (void*) element;
			if (label->string.ptrString) {
				locbuf2[0] = ' ';
				wchar2char(locbuf2 + 1, label->string.ptrString);
			}
		}
		log(buf, sprintf(buf, "%selement type %d hash %08X flags %8X:%s",
			prefix, element->type, element->hash,
			element->someFlags, locbuf2));
		if (element->type == 5) {
			containerchild = ((struct UIContainer*) element)->children;
			debug_custom_uielementvisitor_visit_element(containerchild, locbuf);
		}
		element = element->nextSibling;
	}
}

static
void debug_custom_uielementvisitor_visit_fng(struct FNGInfo *fng, char *prefix)
{
	char locbuf[100];
	struct UIElement *element;

	sprintf(locbuf, "%s    ", prefix);
	do {
		log(buf, sprintf(buf, "%sfng %s:", prefix, fng->fngName));
		element = fng->rootUIElement;
		debug_custom_uielementvisitor_visit_element(element, locbuf);
		fng = fng->child;
	} while (fng);
}

And here's the output when in the options menu in the pause menu while in career freeroam:

Output
fng UI_PauseOptionsMain.fng:
    element type 5 hash 8D7E0C53 flags 40000000:
        element type 2 hash 42ADB44C flags 40000000: Pause Options Main
        element type 1 hash B62648B1 flags 40000000:
        element type 1 hash 7CA40D0F flags 40000000:
        element type 5 hash 371F1C93 flags 40000000:
            element type 1 hash 0A570002 flags 40000000:
            element type 1 hash A67C3FE2 flags 40000000:
            element type 1 hash A67C3FE2 flags 40000000:
            element type 1 hash A67C3FE2 flags 40000000:
            element type 1 hash 5E11FBFB flags 40000000:
        element type 5 hash DE2F4E52 flags 40000000:
            element type 1 hash A67C3FE2 flags 40000000:
            element type 1 hash C8C5F8A6 flags 40000000:
            element type 1 hash C8C5F8A6 flags 40000000:
            element type 1 hash A67C3FE2 flags 40000000:
            element type 1 hash A67C3FE2 flags 40000000:
        element type 2 hash 5E7B09C9 flags 40000000: Option title
        element type 1 hash 687FDA31 flags 40000000:
        element type 1 hash DF422345 flags 43800000:
        element type 5 hash 02DDF58C flags 40000041:
            element type 5 hash 02DDF58C flags 40000001:
            element type 5 hash 812A09D4 flags 40000001:
                element type 2 hash 1C7FCF8B flags 40000001: Back
                element type 2 hash B02B46B7 flags 40000001: $JOY_EVENT_FENG_CANCEL$
            element type 5 hash 6A218478 flags 40000001:
                element type 2 hash 93B5083A flags 40000001: Select
                element type 2 hash 9D752086 flags 40000001: $JOY_EVENT_FENG_SELECT$
        element type 5 hash 0DE769B5 flags 40000000:
            element type 1 hash 5E1208DF flags 40000000:
            element type 1 hash 0894389E flags 40000000:
            element type 1 hash A67C3FE2 flags 40000000:
            element type 1 hash A67C3FE2 flags 40000000:
            element type 1 hash 5E11FBFB flags 40000000:
            element type 1 hash 0A570002 flags 40000000:
            element type 1 hash 0A570002 flags 40000000:
        element type 5 hash 52BDEE1D flags 40000000:
            element type 1 hash 39CC2F66 flags 40000000:
            element type 1 hash DDE4A5E5 flags 40000000:
            element type 1 hash DDE4A5E8 flags 40000000:
            element type 1 hash BE9AB445 flags 40000000:
            element type 1 hash 39CC2F45 flags 40000000:
            element type 1 hash DDE4A5E7 flags 40000000:
            element type 1 hash 39CC2F24 flags 40000000:
            element type 1 hash DDE4A5E6 flags 40000000:
    element type 1 hash B51549B8 flags 50080000:
    element type 1 hash 4B4E3890 flags 50080000:
    element type 1 hash 4B4E388F flags 50080000:
    element type 1 hash 4B4E388E flags 50080000:
    element type 1 hash 4B4E388D flags 50080000:
    element type 1 hash 4B4E388C flags 50080000:
    element type 1 hash 4B4E388B flags 57C80001:
    element type 1 hash 4B4E388A flags 57C80001:
    element type 1 hash 4B4E3889 flags 57C80000:
    element type 2 hash 47FF4E7C flags 40000000: EVENT HANDLER

    element type 1 hash 444969FE flags 40000000:
    element type 1 hash 444969FD flags 40000001:
    element type 1 hash 3FBB7A4E flags 40000000:
    element type 1 hash 688A1551 flags 40000000:
    element type 5 hash BE77206B flags 40000000:
        element type 5 hash 550E571C flags 48000000:
            element type 1 hash 1F3B164E flags 40000000:
    element type 1 hash B04669E3 flags        1:
    element type 1 hash 4B4E3888 flags 53C80000:
fng UI_PC_Help_Bar.fng:
    element type 2 hash 47FF4E7C flags 40000000: Event Handler
    element type 2 hash B8A7C6CF flags 50080001: WWWWWWWWWWWWW
    element type 1 hash 62376D1B flags 40000001:
    element type 2 hash B8A7C6CE flags 10080001: WWWWWWWWWWWWW
    element type 1 hash 62376D1A flags        1:
    element type 1 hash 62376D19 flags        0:
    element type 2 hash B8A7C6CD flags 10080000: WWWWWWWWWWWWW
    element type 2 hash B8A7C6CC flags 10080000: WWWWWWWWWWWWW
    element type 1 hash 62376D18 flags        0:
    element type 2 hash B8A7C6D0 flags 50080001: WWWWWWWWWWWWW
    element type 1 hash 62376D1C flags 40000001:
    element type 1 hash 62376D1D flags 40000001:
    element type 2 hash B8A7C6D1 flags 50080001: WWWWWWWWWWWWW
    element type 1 hash A444D171 flags 40000000:
    element type 1 hash A444D172 flags 40000000:
    element type 1 hash A444D170 flags 40000000:
    element type 1 hash A444D16F flags        0:
    element type 1 hash A444D16E flags 40000000:
    element type 1 hash E0AE8220 flags        0:
    element type 5 hash A22ED3B7 flags 40000000:
        element type 5 hash 2BAC0CEE flags 40000000:
            element type 1 hash E8E1E298 flags 40000001:
            element type 1 hash E8E1E29A flags 40000001:
            element type 1 hash E8E1E297 flags 40000001:
            element type 5 hash 12DF3004 flags 40000000:
                element type 1 hash 5145A2BF flags 40000000:
                element type 1 hash 5145A2BD flags 40000000:
                element type 1 hash 5145A2BC flags 40000000:
        element type 5 hash 3B949CD0 flags 40000001:
            element type 5 hash 84EB1500 flags 40000001:
                element type 2 hash EB406FEC flags 42800003: AA
                element type 2 hash EB3A688A flags 43800003: 0
                element type 5 hash 2253BC50 flags 40000001:
                    element type 1 hash A67C3FE2 flags 40000001:
                    element type 1 hash A67C3FE2 flags 40000001:
                element type 5 hash 6D2A1AE9 flags 40000001:
                    element type 1 hash D253B8F3 flags 40000001:
                    element type 1 hash 0894449B flags 40000001:
                element type 5 hash 22534096 flags 40000001:
                    element type 1 hash D6B3DA2D flags 40000001:
                    element type 1 hash A67C3FE2 flags 40000001:
fng HUD_SingleRace.fng:
    element type 1 hash 79498F08 flags 40000001:
    element type 1 hash 79498F07 flags 40000001:
    element type 1 hash 79498F06 flags 40000001:
    element type 5 hash 697D2BCA flags 40000001:
        element type 1 hash 4F649313 flags 43800001:
        element type 1 hash 4F649314 flags 43800001:
    element type 2 hash 77CE0364 flags 40000001: Disconnect in 15s
    element type 2 hash 192F96D9 flags        1: Bank Cash
    element type 2 hash 49106CE2 flags        1: Cash Reward: 333
    element type 2 hash 2A37A685 flags 40000001: DEFAULT STRING
    element type 2 hash 2A37A684 flags 40000001: DEFAULT STRING
    element type 5 hash 29F0A455 flags 40000001:
    element type 2 hash 2A37A683 flags 40000001: DEFAULT STRING
    element type 2 hash 2A37A682 flags 40000001: DEFAULT STRING
    element type 2 hash 2A37A681 flags 40000001: DEFAULT STRING
    element type 2 hash 2A37A680 flags 40000001: DEFAULT STRING
    element type 1 hash EB24E169 flags 40000001:
    element type 1 hash A71E35FB flags 40000001:
    element type 1 hash 62451575 flags 40000001:
    element type 1 hash B65634BC flags 40000001:
    element type 1 hash 84C55E14 flags 40000001:
    element type 1 hash C11804C6 flags 40000001:
    element type 1 hash 409C6EF0 flags 40000001:
    element type 1 hash 9E59FCF0 flags 40000001:
    element type 1 hash 79498F05 flags 40000001:
    element type 1 hash F7EC30B2 flags 40000001:
    element type 1 hash E24C838B flags 40000001:
    element type 1 hash 19EF4E89 flags 40000001:
    element type 1 hash 6E8333B0 flags 40000001:
    element type 1 hash 2C225885 flags 40000001:
    element type 1 hash 9E59FCEF flags 40000001:
    element type 1 hash 79498F04 flags 40000001:
    element type 1 hash F7EC30B1 flags 40000001:
    element type 1 hash E24C838A flags 40000001:
    element type 1 hash 19EF4E88 flags 40000001:
    element type 1 hash 6E8333AF flags 40000001:
    element type 1 hash 2C225884 flags 40000001:
    element type 2 hash 668C9CD6 flags        1: x3
    element type 1 hash 9E59FCEE flags 40000001:
    element type 1 hash 9E59FCED flags 40000001:
    element type 1 hash 9E59FCEC flags 40000001:
    element type 1 hash 9E59FCEB flags 40000001:
    element type 1 hash 79498F03 flags 40000001:
    element type 1 hash 79498F02 flags 40000001:
    element type 1 hash 79498F01 flags 40000001:
    element type 1 hash 79498F00 flags 40000001:
    element type 1 hash F7EC30B0 flags 40000001:
    element type 1 hash F7EC30AF flags 40000001:
    element type 1 hash F7EC30AE flags 40000001:
    element type 1 hash F7EC30AD flags 42400001:
    element type 1 hash E24C8389 flags 40000001:
    element type 1 hash 19EF4E87 flags 40000001:
    element type 1 hash 6E8333AE flags 40000001:
    element type 1 hash 2C225883 flags 40000001:
    element type 2 hash 8EB730A7 flags        3: Fort Union
    element type 1 hash 18DDF914 flags 40000001:
    element type 1 hash FE2467E3 flags 40000001:
    element type 2 hash 407919AF flags        1:
    element type 1 hash A71E35FA flags 40000001:
    element type 1 hash A71E35F9 flags 40000001:
    element type 1 hash A71E35F8 flags 40000001:
    element type 1 hash A71E35F7 flags 40000001:
    element type 1 hash A71E35F6 flags 40000001:
    element type 1 hash 1149C7DA flags 40000001:
    element type 1 hash 62451574 flags 40000001:
    element type 1 hash B65634BB flags 40000001:
    element type 1 hash 84C55E13 flags 40000001:
    element type 1 hash C11804C5 flags 40000001:
    element type 1 hash 409C6EEF flags 40000001:
    element type 1 hash 62451573 flags 40000001:
    element type 1 hash B65634BA flags 40000001:
    element type 1 hash 84C55E12 flags 40000001:
    element type 1 hash C11804C4 flags 40000001:
    element type 1 hash 409C6EEE flags 40000001:
    element type 1 hash 62451572 flags 40000001:
    element type 1 hash B65634B9 flags 40000001:
    element type 1 hash 84C55E11 flags 40000001:
    element type 1 hash C11804C3 flags 40000001:
    element type 1 hash 409C6EED flags 40000001:
    element type 1 hash 62451571 flags 40000001:
    element type 1 hash B65634B8 flags 40000001:
    element type 1 hash 84C55E10 flags 40000001:
    element type 1 hash C11804C2 flags 40000001:
    element type 1 hash 409C6EEC flags 40000001:
    element type 1 hash 62451570 flags 40000001:
    element type 1 hash B65634B7 flags 40000001:
    element type 1 hash 84C55E0F flags 40000001:
    element type 1 hash C11804C1 flags 40000001:
    element type 2 hash 565F28AE flags 40000001: TIME
    element type 2 hash 22465E2B flags 40000001: LAPY TIMER
    element type 2 hash 27754C7F flags 40000001: HANGY TIMEY!
    element type 2 hash 15D0E247 flags 40000001: + 25
    element type 1 hash 409C6EEB flags 42400001:
    element type 1 hash F2A54430 flags 42400001:
    element type 1 hash 7AF1CA63 flags 40000001:
    element type 1 hash E24C8388 flags 40000001:
    element type 1 hash 7AF1CA62 flags 40000001:
    element type 1 hash E24C8387 flags 40000001:
    element type 1 hash 19EF4E86 flags 40000001:
    element type 1 hash 19EF4E85 flags 40000001:
    element type 1 hash 6E8333AD flags 40000001:
    element type 1 hash 6E8333AC flags 40000001:
    element type 1 hash 2C225882 flags 40000001:
    element type 1 hash 2C225881 flags 40000001:
    element type 1 hash E24C8386 flags 40000001:
    element type 1 hash 7AF1CA61 flags 40000001:
    element type 1 hash 19EF4E84 flags 42400001:
    element type 1 hash 6E8333AB flags 42400001:
    element type 1 hash 2C225880 flags 42400001:
    element type 1 hash 024C680E flags 40000001:
    element type 1 hash 024C680B flags 40000001:
    element type 1 hash 024C680C flags 40000001:
    element type 1 hash 024C680D flags 40000001:
    element type 1 hash 024C680A flags 40000001:
    element type 1 hash 024C6809 flags 40000001:
    element type 1 hash 024C6808 flags 40000001:
    element type 1 hash EB24E168 flags 40000001:
    element type 1 hash EB24E165 flags 40000001:
    element type 1 hash EB24E166 flags 40000001:
    element type 1 hash EB24E167 flags 40000001:
    element type 1 hash EB24E164 flags 40000001:
    element type 1 hash EB24E163 flags 40000001:
    element type 1 hash EB24E162 flags 40000001:
    element type 1 hash 1B259972 flags 40000001:
    element type 1 hash C1347E2F flags 42400001:
    element type 1 hash 40CDC515 flags 42400001:
    element type 5 hash 382D2FC9 flags 40000001:
        element type 1 hash 142EDB6C flags 40000001:
        element type 1 hash 99EBA9C7 flags 40000001:
        element type 1 hash 142EDA64 flags 40000001:
        element type 1 hash 92A75587 flags 40000001:
    element type 1 hash 90FE80F3 flags 42400001:
    element type 2 hash 2DA220AA flags 40000001: +9999
    element type 2 hash 29AF04FD flags 40000001: 9.99
    element type 2 hash C18C12FE flags 40000001: 9.99
    element type 2 hash C18C12FD flags 40000001: WWWWWWWWWWWW DISCONNECTED
    element type 1 hash 411301E3 flags 40000001:
    element type 2 hash F59D909F flags 40000001: xxx:xxx:xxx
    element type 1 hash C82FA200 flags 40000001:
    element type 5 hash E8434EB4 flags 40000001:
        element type 2 hash 9C183BF8 flags 40000001: 8
        element type 2 hash 44F24239 flags 40000001: nd
        element type 2 hash 3BBD6268 flags 40000001: /8
    element type 5 hash 035B0E22 flags 40000001:
    element type 5 hash AFFD0354 flags 40000001:
    element type 5 hash 02DDF58C flags 40000001:
    element type 5 hash 5164D4EA flags 40000001:
        element type 1 hash 05D19F25 flags 40000001:
        element type 1 hash 61D30442 flags 40000001:
        element type 2 hash C1D5FF50 flags 40000003:   0
        element type 2 hash C3383B63 flags 40000003: KM/H
        element type 1 hash F0250DAC flags 40000001:
        element type 2 hash B84589BE flags 40000003: 1
        element type 5 hash C5D551B7 flags 40000001:
            element type 1 hash 6D5FE871 flags 40000001:
            element type 1 hash 19C99185 flags 40000001:
            element type 1 hash 1A33CBB5 flags 40000001:
            element type 1 hash 1A33CBB6 flags 40000001:
            element type 1 hash 6D5ECE44 flags 40000001:
            element type 1 hash 00B78D9E flags 40000001:
            element type 1 hash 19C97422 flags 40000001:
        element type 1 hash 25CF2F74 flags 42800001:
        element type 1 hash 1D9051AA flags 40000001:
        element type 1 hash 11495CD6 flags 40000001:
        element type 1 hash 11461255 flags 40000001:
        element type 1 hash 39E180C4 flags 40000001:
    element type 5 hash 84CCAB08 flags 40000001:
    element type 2 hash A7806DDB flags 40000001: Race Over Message
Goes Here
    element type 1 hash 4E4566E5 flags 40000001:
    element type 5 hash 3345911D flags 40000001:
        element type 1 hash A206A0B4 flags 43800001:
        element type 1 hash 0A729B1B flags 43800001:
        element type 1 hash 99F665F4 flags 40000001:
        element type 1 hash B996A84C flags 40000001:
        element type 1 hash 3B1B624A flags 43800001:
        element type 1 hash 50910822 flags 43800001:
        element type 5 hash ED16ECE6 flags 40000001:
    element type 5 hash 54BE67BC flags 40000001:
        element type 2 hash 05092833 flags 40000001: Time:
        element type 2 hash 30D9802E flags 40000001: 99999999
        element type 1 hash A86C3C9C flags 40000001:
    element type 5 hash 5ED609A1 flags 40000001:
        element type 1 hash FA3D7B71 flags 40000001:
        element type 1 hash FA3D7B72 flags 40000001:
        element type 1 hash FA3D7B73 flags 40000001:
        element type 1 hash FA3D7B74 flags 40000001:
        element type 1 hash 4FDDD64C flags 40000001:
        element type 1 hash 4FDDD64D flags 40000001:
        element type 1 hash 4FDDD64E flags 40000001:
        element type 1 hash 4FDDD64F flags 40000001:
        element type 2 hash 4183E0A1 flags 40000001: 1
        element type 2 hash 4183E0A2 flags 40000001: 2
        element type 2 hash 4183E0A3 flags 40000001: 3
        element type 2 hash 4183E0A4 flags 40000001: 4
        element type 2 hash 2AF89FD7 flags 40000001: wwwwwwwwwww
        element type 2 hash 2AF89FD8 flags 40000001: wwwwwwwwwww
        element type 2 hash 2AF89FD9 flags 40000001: wwwwwwwwwww
        element type 2 hash 2AF89FDA flags 40000001: wwwwwwwwwww
    element type 5 hash 70F90BD4 flags 40000001:
        element type 2 hash 8B7E6EA1 flags 40000001: Laps
        element type 1 hash A86C3C9B flags 40000001:
        element type 2 hash F2685238 flags 40000001: /80
        element type 2 hash 57F0E3A5 flags 40000001: 10
    element type 5 hash 8CA76279 flags C0000001:
        element type 1 hash 238F4B29 flags 40000001:
        element type 1 hash C81A81F5 flags 40000001:
        element type 1 hash ACBAF9EF flags 40000001:
        element type 2 hash 92CDC512 flags 40000001: + 500
        element type 2 hash BCEA10CE flags 40000001: Meters
    element type 5 hash 30D59B88 flags C0000001:
        element type 1 hash 9A4E84A9 flags 40000001:
        element type 2 hash CD3A501F flags 40000001: 00:00:00
        element type 2 hash F7CFC1FC flags 40000001: Forhanosgranska
        element type 2 hash 6BF40D64 flags 40000001: Time:
    element type 1 hash 4B9B8F5A flags 42800001:
    element type 5 hash D292009B flags C0000001:
        element type 1 hash F11AFFD1 flags 40000001:
        element type 1 hash 9C355B23 flags 40000001:
        element type 1 hash F11AFFD2 flags 40000001:
        element type 1 hash 9C355B24 flags 40000001:
        element type 1 hash F11AFFD3 flags 40000001:
        element type 1 hash 9C355B25 flags 40000001:
        element type 1 hash B2D2E745 flags 40000001:
        element type 2 hash 7121F270 flags 40000001: Circuit Stats
        element type 2 hash C8D50791 flags 40000001: Best Lap:
        element type 2 hash C8D50792 flags 40000001: Current G's:
        element type 2 hash C8D50793 flags 40000001: Maximum G's:
        element type 2 hash F3828848 flags 40000001: 00000
        element type 2 hash F3828849 flags 40000001: 00000
        element type 2 hash F382884A flags 40000001: 00000
        element type 5 hash 29F0A455 flags 40000001:
            element type 2 hash 4635CA2C flags 40000001: Time:
            element type 2 hash E1FA0DA7 flags 40000001: 99999999
            element type 1 hash 9C355B26 flags 40000001:
        element type 1 hash B2751131 flags 40000001:
        element type 5 hash 0B470B80 flags 40000001:
            element type 1 hash 968971D0 flags 40000001:
            element type 1 hash 968971D3 flags 40000001:
            element type 1 hash 718D020B flags 40000001:
            element type 1 hash 718D0209 flags 40000001:
            element type 1 hash 718D020A flags 40000001:
            element type 1 hash 968971D2 flags 40000001:
            element type 1 hash 718D020C flags 40000001:
            element type 1 hash 968971D1 flags 40000001:
    element type 5 hash D80C97E1 flags 40000001:
        element type 1 hash 053A5193 flags 40000001:
        element type 1 hash BF9CC87C flags 40000001:
        element type 2 hash 0D94B769 flags 40000001: Filter
    element type 5 hash 06254D8A flags 40000001:
        element type 1 hash EED72BCE flags 40000001:
        element type 2 hash 0FBA78C2 flags 40000001: Searching
Connection
        element type 2 hash 06012887 flags 43800001:
        element type 5 hash E2848973 flags 40000001:
            element type 1 hash 4F649313 flags 43800001:
            element type 1 hash 4F649314 flags 43800001:
    element type 5 hash 3F8644CA flags C0000001:
        element type 2 hash 1930B057 flags 40000003: 0
        element type 2 hash 31B0D166 flags 40000001: Bank:
fng ScreenPrintf.fng:
    element type 2 hash B9D457DE flags 42000000: DEFAULT STRING
    element type 2 hash B9D457DF flags 42000000: DEFAULT STRING
    element type 2 hash B9D457F7 flags 42000000: DEFAULT STRING
    element type 2 hash B9D457F8 flags 42000000: DEFAULT STRING
    element type 2 hash B9D457F9 flags 42000000: DEFAULT STRING
    element type 2 hash B9D457FA flags 42000000: DEFAULT STRING
    element type 2 hash B9D457FB flags 42000000: DEFAULT STRING
    element type 2 hash B9D457FC flags 42000000: DEFAULT STRING
    element type 2 hash B9D457FD flags 42000000: DEFAULT STRING
    element type 2 hash B9D457FE flags 42000000: DEFAULT STRING
    element type 2 hash B9D457FF flags 42000000: DEFAULT STRING
    element type 2 hash B9D45800 flags 42000000: DEFAULT STRING
    element type 2 hash B9D45818 flags 42000000: DEFAULT STRING
    element type 2 hash B9D45819 flags 42000000: DEFAULT STRING
    element type 2 hash B9D4581A flags 42000000: DEFAULT STRING
    element type 2 hash B9D4581B flags 42000000: DEFAULT STRING
    element type 2 hash B9D457B7 flags 42000000: DEFAULT STRING
    element type 2 hash B9D457B8 flags 42000000: DEFAULT STRING
    element type 2 hash B9D457B9 flags 42000000: DEFAULT STRING
    element type 2 hash B9D457BA flags 42000000: DEFAULT STRING
    element type 2 hash B9D457BB flags 42000000: DEFAULT STRING
    element type 2 hash B9D457BC flags 42000000: DEFAULT STRING
    element type 2 hash B9D457BD flags 42000000: DEFAULT STRING
    element type 2 hash B9D457BE flags 42000000: DEFAULT STRING
    element type 2 hash B9D457D6 flags 42000000: DEFAULT STRING
    element type 2 hash B9D457D7 flags 42000000: DEFAULT STRING
    element type 2 hash B9D457D8 flags 42000000: DEFAULT STRING
    element type 2 hash B9D457D9 flags 42000000: DEFAULT STRING
    element type 2 hash B9D457DA flags 42000000: DEFAULT STRING
    element type 2 hash B9D457DB flags 42000000: DEFAULT STRING
    element type 2 hash B9D457DC flags 42000000: DEFAULT STRING
    element type 2 hash B9D457DD flags 42000000: DEFAULT STRING
    element type 2 hash 5334FAE2 flags 42000000: DEFAULT STRING
    element type 2 hash 5334FAE3 flags 42000000: DEFAULT STRING
    element type 2 hash 5334FAE4 flags 42000000: DEFAULT STRING
    element type 2 hash 5334FAE5 flags 42000000: DEFAULT STRING
    element type 2 hash 5334FAE6 flags 42000000: DEFAULT STRING
    element type 2 hash 5334FAE7 flags 42000000: DEFAULT STRING
    element type 2 hash 5334FAE8 flags 42000000: DEFAULT STRING
    element type 2 hash 5334FAE9 flags 42000000: DEFAULT STRING
    element type 2 hash 5334FAEA flags 42000000: DEFAULT STRING
    element type 2 hash 5334FAEB flags 42000000: DEFAULT STRING
    element type 2 hash B9D45773 flags 42000000: DEFAULT STRING
    element type 2 hash B9D45774 flags 42000000: DEFAULT STRING
    element type 2 hash B9D45775 flags 42000000: DEFAULT STRING
    element type 2 hash B9D45776 flags 42000000: DEFAULT STRING
    element type 2 hash B9D45777 flags 42000000: DEFAULT STRING
    element type 2 hash B9D45778 flags 42000000: DEFAULT STRING
    element type 2 hash B9D45779 flags 42000000: DEFAULT STRING
    element type 2 hash B9D4577A flags 42000000: DEFAULT STRING
    element type 2 hash B9D4577B flags 42000000: DEFAULT STRING
    element type 2 hash B9D4577C flags 42000000: DEFAULT STRING
    element type 2 hash B9D45794 flags 42000000: DEFAULT STRING
    element type 2 hash B9D45795 flags 42000000: DEFAULT STRING
    element type 2 hash B9D45796 flags 42000000: DEFAULT STRING
    element type 2 hash B9D45797 flags 42000000: DEFAULT STRING
    element type 2 hash B9D45798 flags 42000000: DEFAULT STRING
    element type 2 hash B9D45799 flags 42000000: DEFAULT STRING
    element type 2 hash B9D4579A flags 42000000: DEFAULT STRING
    element type 2 hash B9D4579B flags 42000000: DEFAULT STRING
    element type 2 hash B9D4579C flags 42000000: DEFAULT STRING
    element type 2 hash B9D4579D flags 42000000: DEFAULT STRING
    element type 2 hash B9D457B5 flags 42000000: DEFAULT STRING
    element type 2 hash B9D457B6 flags 42000000: DEFAULT STRING

At the end we can see all the elements of the ScreenPrintf.fng package. Seems like it just has a bunch of labels. But this is a nice confirmation that my previous code to load it, did actually work. From the other elements, I guessed that flag 2 means it should be visible, so I tried adding that.

default string
Cool.

Then the text can be changed easily by one of the many funcs, for example:

SetUILabelByHashFormattedString("ScreenPrintf.fng", 0xB9D457DE, "0xB9D457DE", "");

custom strings
Nice.

A quick search of all the label hashes did not give any result, so it seems like there is no code left in the program where it would set any of the labels' text.

Index

UI > PC help bar

PC help bar #

This is the button bar at the bottom of the screen, with the player name and money text. It also has some elements that are only shown when you're connected online (TODO).

Buttons are changed by using 54E6E0:PCHelpBarFNGObject::SyncByMask(). Buttons are predetermined, you can only specify which buttons you want and not set an arbitrary text label. The positions are shown below. See 54E6E0:PCHelpBarFNGObject::SyncByMask() for the button masks. The lowest matching mask gets placed on button1, and so on.

two line button bar with 6 buttons
Normal bar

wide button bar with 3 buttons
LAYOUT_ONE_LINE_NO_BG

wide button bar with 3 buttons, slighty up
LAYOUT_ONE_LINE_NO_BG | LAYOUT_WORLDMAP_POSITION

wide button bar with 3 buttons, up
LAYOUT_ONE_LINE_NO_BG | LAYOUT_PAUSEMENU_POSITION

right aligned button bar with 2 buttons
LAYOUT_ONE_LINE_NO_BG | LAYOUT_RIGHT_POSITION

Symbols:

Index

UI (continuation)

Stuff

UI keys seem to be hashed with the case insensitive hash.

struct U2RECT {
        float left;
        float top
        float right
        float bottom
};

#define UIELEMENT_FLAG_HIDDEN 0x1
#define UIELEMENT_FLAG_USE_CUSTOM_TEXT 0x2
#define UIELEMENT_FLAG_TEXT_CHANGED 0x400000

struct UIElement {
        void *vtable;
        struct UIElement *nextSibling;
        int field_8;
        int field_C;
        unsigned int hash;
        int field_14;
        int type;
        int someFlags;
        int field_20;
        int field_24;
        int field_28;
        int ptrField_2C; /*maybe something size related*/
        int field_30;
        int field_34;
        int field_38;
        int field_3C;
        int field_40;
        int field_44;
        int field_48;
        int field_4C;
        int field_50;
        int field_54;
        void *FERenderObject;
        /*TODO how big is this base struct??*/
};

struct UIElementType1 {
        struct UIElement __parent; /*__parent.type = 1*/
        /*TODO how big is the base struct?*/
};

struct UILabel {
        struct UIElement __parent; /*__parent.type = 2*/
        /*TODO how big is the base struct?*/
        /*+60*/
        unsigned int textLanguageString;
        struct WideCharString string; /*ignored when textLanguageString is set*/
};

struct UIElementType3 {
        struct UIElement __parent; /*__parent.type = 3*/
        /*TODO how big is the base struct?*/
};

struct UIElementType4 {
        struct UIElement __parent; /*__parent.type = 4*/
        /*TODO how big is the base struct?*/
};

struct UIContainer {
        struct UIElement __parent; /*__parent.type = 5*/
        /*TODO how big is the base struct?*/

        /*incomplete*/

        /*+0x60*/
        int numChildren;
        struct UIElement *children;
};

struct UIElementType6 {
        struct UIElement __parent; /*__parent.type = 6*/
        /*TODO how big is the base struct?*/
};

struct UIElementType7 {
        struct UIElement __parent; /*__parent.type = 7*/
        /*TODO how big is the base struct?*/
};

struct UIElementType8 {
        struct UIElement __parent; /*__parent.type = 8*/
        /*TODO how big is the base struct?*/
};

struct UIElementType9 {
        struct UIElement __parent; /*__parent.type = 9*/
        /*TODO how big is the base struct?*/
};

struct UIElementVisitor {
        struct UIElementVisitor$vtable *vtable;
};

struct UIElementVisitor$vtable {
        void *func_0;
        int (*acceptElement)(struct UIElementVisitor*,struct UIElement*); /*return 0 to stop visiting*/
};

struct UIElementVisitor_FindByHash {
        struct UIElementVisitor __parent;
        unsigned int searchingHash;
        struct UIElement *foundUIelement;
};

Symbols:

Index

Mouse input #

While the canvas is 640x480, mouse position seems to be handled mostly as ([-320,+320],[-240,+240]).

struct MouseData {
	void /*DInputDevice8*/ *dinputdevice;
	int cursorX; // on 640x480 canvas
	int cursorY; // on 640x480 canvas
	int previousCursorX; // on 640x480 canvas
	int previousCursorY; // on 640x480 canvas
	int deltaCursorX; // on 640x480 canvas
	int deltaCursorY; // on 640x480 canvas
	int mousestate_lZ; // scrollwheel data
	char areMouseButtonsSwapped; // result of GetSystemMetrics(SM_SWAPBUTTON);
	char button0State; // left mouse button
	char button0JustPressed;
	char button0JustReleased;
	char button1State; // right mouse button
	char button1JustPressed;
	char button1JustReleased;
	char button2State; // middle mouse button
	char button2JustPressed;
	char button2JustReleased;
};

struct MouseData *_mouseData = (struct MouseData*) 0x8763D0;

Symbols:

Index

Cheats #

struct CheatScreenData {
	void **ppFunc;
	char doCheatCheck; // only 1 when in the 'press enter key' screen
	char field5;
	char field6;
	char field7;
	int field2CofLastCheatActivated;
	struct CheatData *cheats; /*0x7FB960*/
	int numCheats; // 20
	char cheatString[32]; // typed string
	int typedCheatLength; // characters typed
	int field38;
};

#define CHEAT_TYPE_VISUALS 1
#define CHEAT_TYPE_PERFORMANCE 2
#define CHEAT_TYPE_CAREER 5
#define CHEAT_TYPE_SPONSOR_CAR 6 // shows up when doing quick race
#define CHEAT_TYPE_VINYL 7

struct CheatData {
	char cheat[32];
	int cheatType;
	int cheatData; // hash when cheatType is sponsor car or vinyl
	int field28;
	int field2C;
	char hasBeenTriggered;
	char field31;
	char cheatEnabled;
	char field33;
};
EXPECT_SIZE(struct CheatData, 0x34);

struct CheatScreenData 865930:cheatScreenData; // partially initialized by 575770:InitCheats_()
struct CheatData 7FB960:cheatData[20] = {
	{ "needperformance1",	2, 1, 0, 0, 0, 1, 1, 0 }, // 0
	{ "needperformance2",	2, 2, 0, 0, 0, 1, 1, 0 }, // 1
	{ "gimmevisual1",	1, 2, 0, 0, 0, 1, 1, 0 }, // 2
	{ "gimmevisual2"	1, 2, 0, 0, 0, 1, 1, 0 }, // 3
	{ "gimmechingy"		6, 0, 0, 0, 0, 0, 0, 0 }, // 4
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 5
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 6
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 7
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 8
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 9
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 10
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 11
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 12
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 13
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 14
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 15
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 16
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 17
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 18
	{ ""			0, 0, 0, 0, 0, 0, 0, 0 }, // 19
};
// after 575770:InitCheats_() was called, it looks like this:
// hashes are done with case sensitive hash
struct CheatData 7FB960:cheatData[20] = {
	{ "needperformance1",	2, 0x00000001, 0, 0, 0, 1, 1, 0 }, //
	{ "needperformance2",	2, 0x00000002, 0, 0, 0, 1, 1, 0 }, //
	{ "gimmevisual1",	1, 0x00000002, 0, 0, 0, 1, 1, 0 }, //
	{ "gimmevisual2"	1, 0x00000002, 0, 0, 0, 1, 1, 0 }, //
	{ "gimmechingy"		6, 0x3EE7D094, 0, 0, 0, 1, 1, 0 }, // "SPONSOR_CHINGY"
	{ "wannacapone"		6, 0x3E6D00C8, 0, 0, 0, 1, 1, 0 }, // "SPONSOR_CAPONE"
	{ "wantmyd3"		6, 0x54E32D29, 0, 0, 0, 1, 1, 0 }, // "SPONSOR_D3"
	{ "shinestreetbright"	6, 0xE57B6400, 0, 0, 0, 1, 1, 0 }, // "SPONSOR_SHINESTREET"
	{ "davidchoeart"	6, 0x7760C479, 0, 0, 0, 1, 1, 0 }, // "SPONSOR_DAVIDCHOE"
	{ "tunejapantuning"	6, 0x002F98F1, 0, 0, 0, 1, 1, 0 }, // "SPONSOR_JAPANTUNING"
	{ "yodogg"		6, 0x007090A1, 0, 0, 0, 1, 1, 0 }, // "SPONSOR_SNOOP_DOGG"
	{ "opendoors"		6, 0x4BAECE79, 0, 0, 0, 1, 1, 0 }, // "SPONSOR_THE_DOORS"
	{ "needmybestbuy"	7, 0x9E52EAC1, 0, 0, 0, 1, 1, 0 }, // "AD_BESTBUY"
	{ "goforoldspice"	7, 0x5FD7B956, 0, 0, 0, 1, 1, 0 }, // "AD_OLDSPICE"
	{ "gottaedge"		7, 0x44C47B98, 0, 0, 0, 1, 1, 0 }, // "AD_EDGE"
	{ "gottahavebk"		7, 0xCF5F6873, 0, 0, 0, 1, 1, 0 }, // "AD_BURGERKING"
	{ "gotmycingular"	7, 0x7C504338, 0, 0, 0, 1, 1, 0 }, // "AD_CINGULAR"
	{ "regmybank"		5, 0x00000002, 0, 0, 0, 1, 1, 0 }, //
	{ "regmebaby"		5, 0x00000003, 0, 0, 0, 1, 1, 0 }, //
	{ "ordermebaby"		5, 0x00000004, 0, 0, 0, 1, 1, 0 }, //
};

void __thiscall 575770:InitCheats_()(struct CheatScreenData *data)
{
	int i;

	cheatScreenData.ppFunc = (void*) 0x7A0604;
	cheatScreenData.doCheatCheck = 0;
	cheatScreenData.cheats = cheatData;
	cheatScreenData.numCheats = 20;
	cheatScreenData.typedCheatLength = 0;
	cheatScreenData.field38 = 0;

	for (i = 0; i < 20; i++) {
		if (cheatData[i].cheatType == CHEAT_TYPE_VINYL &&
			cheatData[i].someHash == hashCS43DB50("AD_BESTBUY") &&
			_gameRegion != 0 /*REGION_US*/)
		{
			cheatData[i].cheatEnabled = 0;
		}
	}
}

Cheats can only be inserted at the 'press enter key' screen at boot. (I remember this from looking up cheats in my childhood), but toggling sponsor car cheats when ingame has immediate effect.

Symbols:

Index

Car model data #

When jumping around XREFs I stumbled upon a function that I named 511E60:GetLogoForCarModel(). It uses a lot of strings that are manufacturers and stuff like CARSELECT_MANUFACTURER_%s. There are two parameters, the first seems to be the index of the car and the second is if the caller wants the manufacturer logo or the brand logo. The function gave some nice information:

After hooking and printing addresses, some more obvious data could be seen:

Symbols:

Index

Sound #

Symbols:

Index

Career #

83648C:autoLoadLastProfileIntoCareer? is a weird variable. It changes the boot process so when UI_Main.fng is loaded, the game immediately goes in career freeroam after loading a profile. Except, you're in Rachel's car. When entering a shop, you're back in your own car, but when exiting the GPS is set to the crib. When arriving at the crib though, you can't enter it. However, you can enter the crib if that first shop you entered was the car lot.

Doing this on a finished career profile will put you in Rachel's car at the airport parking, just like when starting a new career. The end of game sms will be open. When driving, the phonecalls from the start of the game come in (friends telling Rachel where the races are going on), but they don't appear on the map.

My best guess is that this 'load into career' option is a cut/unfinished feature.

Index

Career > Markers

Markers #

The money pickup/info pickup(engage sms messages)/race/shop/garage markers. They are in bin section 0x3414A, which is in bin section 0x80034147, in TRACKS\ROUTESL4R%c\Paths%d.bin. Bin section 0x80034147 is loaded in 5D9A90:Markers::InitFromBinData().

Not loading bin section 0x3414A results in no career markers at all, and no traffic (but other racers do still spawn).

struct Markers {

};

#define MARKER_TYPE_BODYSHOP_AREA 0x4 /*marks the entrance, ie where the road goes into private property, not the entrace aura itself*/
#define MARKER_TYPE_NEIGHBOURHOOD 0x10
#define MARKER_TYPE_ENGAGE_TIP 0x12
#define MARKER_TYPE_MONEY_PICKUP 0x13

struct Marker {
        char type;
        char field_1;
        char field_2;
        char field_3;
        int field_4;
        int field_8;
        int field_C;
        int field_10;
        int field_14;
        int field_18;
        int field_1C;
        int field_20;
        int field_24;
        int field_28;
        int field_2C;
        unsigned int hash; /*for neighbourhood: hash of name, for engage: hash of sms name*/
        int field_34;
        int field_38;
        int field_3C;
        short radius?;
        short markerStructSize;
        float pos_x;
        float pos_y;
};

struct NeighbourhoodName {
        char *name;
        unsigned int hash;
};

Symbols:

Index

Career > SMS stuff

SMS stuff #

Dump of all sms message data: smsdump.txt. This data is in bin section 0x34A17 (which is inside bin section 0x80034A10, which is in file GLOBAL/GLOBALB.BUN).

#define SMS_TYPE_END_OF_GAME 1
/*      ^ 1 msg:
        SMS_END_OF_GAME_MESSAGE
*/
#define SMS_TYPE_CAREER_START 2
/*      ^ 3 msgs:
        SMS_LOC_GO_TO_GARAGE
        SMS_LOC_EVENTS_OUT_THERE
        SMS_GPS_INTRO
*/
#define SMS_TYPE_INSTRUCTIONS 3
/*      ^ 4 msgs:
        SMS_INSTRUCTION
        SMS_MINIMAP_INTRUCTION
        SMS_MINIMAP_LEGEND_BLURB
        SMS_STEERING_CONTROLS
*/
#define SMS_TYPE_OUTRUN_INFO 4
/*      ^ 1 msg:
        SMS_OUTRUN_INFO
*/
#define SMS_TYPE_OUTRUN_VICTORY 5
/*      ^ 1 msg:
        SMS_OUTRUN_VICTORY
*/
#define SMS_TYPE_OUTRUN_DEFEAT 6
/*      ^ 1 msg:
        SMS_OUTRUN_DEFEAT
*/
#define SMS_TYPE_UNLOCK_9 9
/*      ^ 5 part unlock msgs*/
#define SMS_TYPE_UNLOCK_10 10
/*      ^ part unlock/region unlock/car unlock msgs
        SMS_LOC_TEXT_JACKSON_HEIGHTS
        SMS_LOC_TEXT_COAL_HARBOR
        SMS_LOC_TEXT_COAL_HARBOR_WEST
*/
#define SMS_TYPE_UNLOCK_12 12
/*      ^ part unlock/region unlock/angry Rachel/sponsor msgs
        SMS_LOC_TEXT_BEACON_HILL
        SMS_LOC_GO_TO_CAR_LOT_NOW
        SMS_LOC_X_SPONSOR
*/
#define SMS_TYPE_MAGS_AND_UNIQUES 13
/*      ^ magazine/unqiue parts msgs*/
#define SMS_TYPE_DVD_COVER 14
/*      ^ 10 dvd cover msgs*/
#define SMS_TYPE_ENGAGE_TIP 15
/*      ^ 42 tip msgs, from the ingame markers you have to drive through*/
struct Mailbox {
        int field_0;
        int field_4;
        int field_8;
        int field_C;
        int field_10;
        int field_14;
        int mailboxId; /*1 inbox, 2 game tips, 3 special events, 4 Rachel, 5 unlocks*/
};

struct MailboxFNGObject {
        /*TODO*/

        /*+0x120*/
        struct Mailbox *currentBrowsingMailbox;
};

struct SmsData {
	/*offset in language table ptr838428*/
	short careerTextLanguageTableOffset;
	char type;
	char mailboxId; /*1 inbox, 2 game tips, 3 special events, 4 Rachel, 5 unlocks*/
	int field_4;
	int field_8;
	int moneyReward;
	unsigned int senderLanguageLabel;
};

struct SmsMessage {
	struct SmsData *data;
	char read;
	char field_5;
	char deleted;
	char movedToCorrectInbox;
	unsigned int bodyFormatLanguageLabel;
	unsigned int subjectAndBodyParameterLanguageLabel;
	unsigned int subjectFormatLanguageLabel;
};

struct SmsMessageList {
        struct SmsMessage messages[256];
        int numMessages;
        int numUnreadMessages;
        int field_1408;
        char field_140C;
        char field_140D;
        char field_140E;
        char field_140F;
        int field_1410;
};

struct SmsListEntry {
        void *vtable;
        int field_4;
        int field_8;
        struct ObjectLink *link;
        char field_14;
        char field_15;
        char field_16;
        char field_17;
        struct SmsMessage *sms;
};

Symbols:

Index

Career (continuation)

Need text here or my parser breaks.

struct CareerMoney {
	int money;
};

struct CareerData {
        /*5D20*/
        struct SmsMessageList smsMessages;
	/*7134*/
	struct CareerMoney money;
};

The career money cheat is mildly interesting, as it seems to work in two different ways. It adds the amount to a variable that is used when a new career is started and it adds the amount to the current career data if the loaded profile didn't start career yet.

/*from 5760A0:DoCareerCheat()*/
/*ordermebaby ($1000 career start bonus)*/
cheatExtraCareerMoney1000 = 1000;
// ^ used when starting a new career from a profile that
// was loaded from the profile menu
if (!isProfileWithStartedCareerLoaded)  {
	if (CareerGetMoney() == 0)  {
		CareerGiveMoney(1000);
		// ^ This only has an effect when the profile was
		// loaded from the loading screen
	}
}

Symbols:

Index

LAN/OL #

struct SocketWrapper {
        struct SocketWrapper *previousSocketWrapper
        int field_4;
        int socket_af;
        int socket_type;
        int socket_protocol;
        int field_14;
        SOCKET socket;
        int field_1C;
        int field_20;
        int field_24;
        int field_28;
        int field_2C;
        int field_30;
        int field_34;
        int field_38;
        int field_3C;
        int field_40;
        int field_44;
        struct SocketWrapperWrapper *socketWrapperWrapper;
        int (*func)(void*,void*,void*); /*actual number of parameters is unknown*/
};

struct CriticalSectionWrapper_3 {
        int lastAccessedThreadId;
        int numActiveLocks;
        int isInCriticalSection;
        CRITICAL_SECTION criticalSection
};
EXPECT_SIZE(struct CriticalSectionWrapper_3, 0x24);

struct SocketWrapperWrapper {
        struct SocketWrapper *socketWrapper;
        int field_4;
        int field_8;
        struct CriticalSectionWrapper_3 criticalSectionWrapper;
        int ptrField_30;
        int field_34;
        int field_38;
};

Symbols:

Index

D3D9 Stuff #

Symbols:

Index

Other symbols #

.rdata:0079B9F0 aDDecodercaptur db 'd:\decodercapture%04d.fss',0

.rdata:007A1344 a__IndepSrcOnli db '..\indep\src\online/LobbyCore.cpp',0

.rdata:007A5BF8 aHotpositionSHo db '//',0Ah
.rdata:007A5BF8                 db '// HotPosition - %s',0Ah
.rdata:007A5BF8                 db '//',0Ah
.rdata:007A5BF8                 db 'HOTPOSITION: %8.2f,%8.2f,%8.2f  Angle=0x%04x',0Ah,0

.rdata:00787568 aSeeulator      db 'Seeulator',0
.rdata:00787574 aJr2server      db 'JR2Server',0
struct ObjectLink {
        struct ObjectLink *next;
        struct ObjectLink *prev;
};

struct WideCharString {
        wchar_t *ptrString;
        int allocatedWcharLength; /*amount of wchars that can fit, excluding zero term*/
};

struct WorldAnimStuff {
        struct Pool *worldAnimEntityPool;
        struct Pool *worldAnimEntityTreePool;
        struct Pool *worldAnimEntityTreeInfoPool;
        struct Pool *worldAnimCtrlPool;
        struct Pool *worldAnimInstanceEntryPool;
        char poolsCreated?;
        char field_15;
        char field_16;
        char field_17;
        int field_18;
        int field_1C;
        int field_20;
        int field_24;
        int field_28;
        int field_2C;
        int field_30;
        int field_34;
        int field_38;
        int field_3C;
        int field_40;
        int field_44;
        int field_48;
};
EXPECT_SIZE(struct WorldAnimStuff, 0x4C);

Symbols:

Index