String tables
Nearly every piece of text in FH2 lives in a string table: menus, car names, loading messages, AI driver names. Changing them is the easiest way for a mod to put something on screen, and the platform does the file work for you.
Where they are
Each language has a zip in the game's Mount folder:
Mount\media\Stripped\StringTables\EN.zip English (US)
Mount\media\Stripped\StringTables\GB.zip English (UK)
... DE, FR, ES, IT, JP, KO, RU, PL, MX, br, CHT, nl, cz, HU
Mount\media\Stripped\StringTables\DEV.zip developer English
Mount\media\Stripped\StringTables\LOC.zip placeholders ("Main:IDS_Press")
Inside each zip are about 120 tables, one per area of the game: Main.str, PauseMenu.str, LoadingMessages.str, AIPlayers.str and so on. Don't edit the zips: media\zipmanifest.xml records where each zip's directory is, and the game relies on it. The platform writes edited copies to Extra Options' override folder instead, as Mount\mediaoverride\Stripped\StringTables\<language>\<table>.str, and Extra Options makes the game read those (or, when Extra Options isn't loaded, the platform from 2.7.3).
The game reads its string tables once, early in startup. Edits made in fh2mod_init are always in time; anything later shows from the next launch.
Keys
Every string has a 16-bit key, plus an IDS_... name used by the game's developers. Keys are the same in every language, so one set_string call covers them all. To find a key, list the table with the script in this repo:
python scripts/dump_strings.py "<Mount>/media/Stripped/StringTables/EN.zip" (lists the tables)
python scripts/dump_strings.py "<Mount>/media/Stripped/StringTables/EN.zip" Main.str start (searches one table)
0x18F0 IDS_To_Start TO START
0x1D35 IDS_To_Start_LowerCase to start
...
Some keys worth knowing:
| Table | Key | Name | Text |
|---|---|---|---|
Main.str | 0xB197 | IDS_Press | PRESS |
Main.str | 0x18F0 | IDS_To_Start | TO START (the platform adds title notices here) |
LoadingMessages.str | 0x6875 | IDS_FastTravel | Fast Traveling to Destination |
LoadingMessages.str | 0xBA63 | IDS_EnteringCarMeet | Entering {0} Car Meet |
AIPlayers.str | various | IDS_Name_1 ... | The 25 built-in Drivatar gamertags, plus "HRZN Ben". Car meets and road-trip traffic are named from here. |
Strings can contain {0}-style placeholders, filled in by the game. Keep them if you replace such a string. \n makes a new line where the game's text box allows it. On the title screen, for example, "TO START" is its own box, centred vertically on the button, which is why title notices are padded with blank lines above.
The file format
You don't need this to use the API, but it's here for tools. A table is an "LSB2" file:
0x00 "LSB2"
0x04 u32 0
0x08 u32 0x200
0x0C u32 header size (0x24)
0x10 u32 section count (4)
0x14 u32 section offsets[count] (0 = section absent)
section 0, strings:
u32 size (including this field)
u32 count
(count + 1) x { u16 key, u32 offset } offsets in UTF-16 units; the
last entry is a 0xFFFF sentinel
holding the total length
NUL-terminated UTF-16LE strings
section 1, names: u32 size, u32 count, count x { u16 key, u16 index, u32 offset },
then NUL-terminated ASCII "IDS_..." names
section 2, comment: "This file was auto-generated by the DBPipeline tool..."
section 3, key order
Only section 0 depends on the text. To rewrite a table, rebuild section 0 and shift the later section offsets in the header by the change in its size. That's what the platform does (src/strings.cpp), and scripts/dump_strings.py reads the format in Python.