txte: localized text tables
- Source
docs/formats/txte.md- SHA-256
33c1e1b7…c5120a72
Header · Row · String pool · Padding · Text encoding · How the game reads it · Rebuilding · Leads
The game's text format. Each txte text table is a 16-byte header, then 32-byte rows, then a pool of strings. A row pairs a text key, such as ARMSTEXT_LC_ARMS_001_NAME, with its text in one language. The keys are the strings that other files point to, for example xlce string pools (xlce.md). In localize/LocalizeText_<lang>.dat, the payload is a pakd (containers.md) of txte tables.
| Based on | All eight localize/LocalizeText_<lang>.dat (de-de, en-us, es-es, fr-fr, ja-jp, ko-kr, zh-cn, zh-tw), 72 tables each, 576 in all (the English one: LocalizeText_en-us.dat); Font/FontAllFilePackage.dat for the code sizes (font.md); Gibbed's EventTextFile.cs (reference_tools/Gibbed.LetUsClingTogether/projects/Gibbed.Reborn.FileFormats/), and RebornDecoder.cs, BaseDecoder.cs, MacroCode.cs and MacroCodeHelpers.cs (reference_tools/Gibbed.LetUsClingTogether/projects/Gibbed.TacticsOgre.TextFormats/), each the same in reference_tools/gibbed10/projects/; Gibbed's docs/text_encoding/text_formatting.md (reference_tools/Gibbed.LetUsClingTogether/docs/), which describes the PSP game's codes; the unpacked executable, SHA-256 7b0230a11498f40396c9048345e7cfe0ef442cb7ca8faffc6f197920d2669599. |
| Checked | 2026-10-12 |
| Method | Layout and code sizes copied from these sources and checked against them. How the game reads it and the control codes' names: static analysis of the unpacked executable with Ghidra 12.1.4, through the decompiled code in local/exe/decomp/, and DuckDB over the decoded text (R31 review, 2026-10-11). Checked against the game data, 2026-10-11: the round trip, tor round-trip local/game/Data/localize/LocalizeText_en-us.dat local/game/Data/event/event_data.dat --docs docs, rebuilt all 72 English text tables (31,053 rows) from their rows in the text form, with no byte taken from the original, and the whole file byte for byte, with no decode problem reported; tor read-back over the same file read the decoded text back to the same file. 2026-10-12 (R19): tor round-trip local/game/Data/localize --docs docs rebuilt all 576 tables of the eight languages (1,205,348 strings) and all 8 files byte for byte, with no decode problem reported, and tor read-back read each back to the same file; the decode walks every escape and offset, and the encoder recomputes every zombie_condition length. 2026-10-13 (R36): DuckDB over the decoded text of the eight languages (data/game/localize/*/*.txte.jsonl, from tor export) tallied the codes, icons, extra NULs and zombie_condition length bytes below from their text forms, by language. The code sizes were found by searching the game files for Gibbed's sizes (r19/findbytes.cs). No in-game tests yet. |
Header
| Offset | Size | Type | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|---|
| 0x0 | 4 | raw[4] | magic |
txte. |
confirmed | The round trip (576/576). Gibbed EventTextFile agrees (Signature 0x65747874, read little-endian). |
| 0x4 | 4 | s32 | row_count |
Number of rows. | confirmed | The round trip. 0x1402ba410. Gibbed EventTextFile agrees (count). |
| 0x8 | 4 | u32 | row_offset |
Where the rows start; always 16. Gibbed rejects any other value. | confirmed | The round trip (the decode refuses any other value). 0x1402ba410: row n is at entry + row_offset + n × row_size. Gibbed EventTextFile agrees (headerSize). |
| 0xC | 4 | u32 | row_size |
Size of one row; always 32. Gibbed rejects any other value. | confirmed | The round trip (as above). 0x1402ba410 (the stride). Gibbed EventTextFile agrees (entrySize). |
| 0x10 | row_count × 32 |
— | rows | The rows, packed with no gaps. See Row. | confirmed | The round trip. Gibbed EventTextFile agrees. |
Row
Every field is a str_ofs64: an offset from the start of the table (the txte magic) to a NUL-terminated string in the pool. Gibbed reads each as a u32 offset followed by a u32 that must be zero, which is the same bytes.
| Offset | Size | Type | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|---|
| 0x00 | 8 | str_ofs64 | key |
The text key, plain UTF-8. | likely | The round trip (layout). 0x1402ba410 indexes the rows by it; 0x1402b95d0 looks keys up. Gibbed EventTextFile agrees (IdOffset, Id). |
| 0x08 | 8 | str_ofs64 | text |
The text, in the text encoding. | likely | The round trip (layout). 0x1402b95d0 returns it. Gibbed EventTextFile agrees (ValueOffset, Value, decoded with RebornDecoder). |
| 0x10 | 8 | str_ofs64 | unk_0x10 |
Unknown; no reader found. Empty in every row but the unit names of German, Spanish and French: there it is M or F on the random-name tables #19–#23 (UNITNAME1–5; #20 and #22 all F, the others mostly M) and on some rows of #18 (UNITNAME0), which looks like the grammatical gender of the name. Gibbed reads it as a plain UTF-8 string. |
— | The round trip, all 8 languages; DuckDB over localize/*/*.txte.jsonl. Gibbed EventTextFile (Unknown10Offset). |
| 0x18 | 8 | str_ofs64 | unk_0x18 |
Unknown; no reader found. X in every row of table #0 in every language, empty in every other row. Gibbed reads it as a plain UTF-8 string. |
— | The round trip; DuckDB as above. Gibbed EventTextFile (Unknown18Offset). |
- confirmed: the upper 4 bytes of every offset are zero. Source: the round trip (the encoder writes each offset as a u64 and gives back every table). Gibbed
EventTextFileagrees (fails ifUnknown04,Unknown0C,Unknown14orUnknown1Cisn't zero).
String pool
- confirmed: the pool starts right after the last row, at 16 +
row_count× 32. Source: the round trip (the encoder starts it there, 576/576). - confirmed: strings are stored in row order, and within a row in field order (
key,text,unk_0x10,unk_0x18), each followed by one NUL. No string is shared, empty ones included: every offset points just past the previous string's NUL, except after sometextstrings that are followed by two NULs (German#67rows 620 and 680; Spanish#65row 568,#66rows 1732, 1734 and 1736,#67rows 620, 680, 735 and 789,#68row 500; allSCENARIO_dialogue). The text form shows such an extra NUL as{nul}at the end of the string (Text encoding). Source: the round trip, all 8 languages (the encoder writes them so, 576/576 tables); DuckDB over the text forms for the{nul}rows (R36, see Method).
Padding
- confirmed: after the pool, the table is zero-padded to
align16(data_size + 1), the same rule asxlcepadding. Source: the round trip (576/576).
Text encoding
text is UTF-8 with escapes mixed in. Each escape starts with a Unicode private-use code point. All rows are likely: the eight languages' text uses every escape and icon below and every code whose row doesn't say no text uses it, and decodes with them with no problem reported (no unknown code, no unlisted private-use code point, no invalid UTF-8) and rebuilds byte for byte, but a round trip would also pass with some wrong code sizes, since the text form keeps every byte either way. No text uses U+F8FC or U+F8FE.
| Bytes | Code point | Followed by | Meaning | Confidence | Source |
|---|---|---|---|---|---|
EF A3 BF |
U+F8FF | A code byte, then its arguments. | A control code. | likely | Gibbed RebornDecoder (Macro), BaseDecoder. |
EF A3 BD |
U+F8FD | 1 byte. | Inserts stored string number xx − 1 (Escapes and control codes). Shown as {f8fd xx}; used in CONFIRM_ rows. |
likely | The game's drawing routines (0x1402a4a00 …) pass xx − 1 to 0x14029b720. Gibbed RebornDecoder (UnknownFD), BaseDecoder (reads 1 byte). |
F4 80 80 80–F4 80 80 9E |
U+100000–U+10001E | Nothing. | An icon; the ones listed there are all the text uses. | likely | DuckDB over the text forms of all eight languages (R36, see Method); Gibbed BaseDecoder (gaiji labels). |
- likely: Gibbed reads U+F8FC as a variable-length span, with its own nested text. This doc reads it as a header the game skips (Escapes and control codes) and doesn't decode it; the decoder shows it as
{U+F8FC}and reports it. Source: GibbedRebornDecoder(Span),BaseDecoder. - likely: Gibbed treats the private-use ranges U+E000–U+F8FB, U+F0000–U+FFFFD and U+100000–U+10FFFD as icon glyphs (its word is "gaiji"). Source: Gibbed
RebornDecoder.
In the file pages and the decoded data (../../toolchain/README.md#decoded-data), control codes and icons are shown as {name}, or {name args} with the argument bytes in hex: How {wrap 01 01}could (an illustration). This text form is what the encoder reads back to bytes, so every byte string has exactly one text form:
| Text form | Bytes | When |
|---|---|---|
{{ and }} |
{ and } |
A literal brace in the game text. A lone { or } is an error when encoding. Gibbed's BaseDecoder escapes braces the same way. |
{name}, {name args} |
U+F8FF, the code byte, then the argument bytes | A control code; the number of argument bytes must be its Size − 1. |
{f8fd xx} |
U+F8FD, then byte xx |
The U+F8FD escape. |
{nul} |
One extra NUL after the string's own | Only at the end, once per extra NUL (String pool). The game stops reading at the first NUL, so it never shows. |
{icon_…} |
The icon's code point, in UTF-8 | An icon. |
{U+XXXX} |
That code point, in UTF-8 | A private-use code point or a control character (below U+0020) not in this doc. Reported as a problem. |
{?? hex} |
U+F8FF, then the bytes as given | A control code not in this doc. The decoder can't know its size, so the hex runs to the end of the string. Reported as a problem. |
{byte xx} |
Byte xx |
A byte that isn't valid UTF-8. Reported as a problem. |
Everything else is the character itself, in UTF-8. A string can't contain NUL.
Control codes
Size counts the code byte and its arguments, not the U+F8FF before it. The sizes are the 54 bytes of control_code_sizes in Font/FontAllFilePackage.dat (font.md), one per code from 0x80 to 0xB5, which is the shape of the size table the drawing routines skip codes by (Escapes and control codes); Gibbed's MacroCodeHelpers.GetSize gives the same sizes for 0x80–0xB3. Only the codes below are decoded; an unlisted code stops the decoder, which reports it and keeps the rest of the string as {?? hex}. Names and meanings are from the drawing routines (0x1402a4a00 and the others in Escapes and control codes), the game's keys and the decoded text; codes the drawing routines hand to a virtual function (0x93–0x96, 0xA9 and others) rest on keys and data only. text_formatting.md describes the PSP game, where codes start with byte FF instead of U+F8FF. Which languages use a code is from DuckDB over the text forms of the eight LocalizeText_<lang>.dat (R36, see Method); a code with no use says so.
| Code | Size | Name | Meaning | Confidence | Source |
|---|---|---|---|---|---|
| 0x80 | 1 | newline |
Line break: the pen goes back to the line start and down one line height. | likely | 0x1402a4a00 case 0x80. Gibbed MacroCode (InsertNewline), MacroCodeHelpers. |
| 0x81 | 3 | wrap |
A wrap area: where a line break may go if what follows doesn't fit. Arguments: the width (× 2 is the game's width; Gibbed: pixels), then a byte; 01 01 is the usual form, and a second argument of 0xDE acts as 0x86. |
likely | Case 0x81: breaks the line when the text's wrap bit (0x200) is set and the pen would pass the width. Gibbed MacroCode (IndicateWrapArea), MacroCodeHelpers; text_formatting.md (FF 81). |
| 0x82 | 5 | color |
Sets the text color: three bytes, each minus 1, then an alpha minus 1 (scaled by the renderer's alpha). No text uses it. | likely | Case 0x82. Gibbed MacroCode (SetTextColor), MacroCodeHelpers. |
| 0x83 | 6 | string_param |
Inserts a string parameter of the text object. Arguments: the parameter slot (from 1), unknown, a number slot (from 1; FF for none), unknown, unknown; when that number is above 1 the parameter's second string is used. |
likely | Case 0x83: slot arg1 − 1 (0x14029b7e0, +0x50 + 8n), number slot arg3 − 1 (0x14029b510). Gibbed MacroCodeHelpers, BaseDecoder (var str); text_formatting.md (FF 83). |
| 0x84 | 5 | number_param |
Inserts a number parameter of the text object. Arguments: the parameter slot (from 1), a string slot for 1 (from 1), unknown, then a format (from 1; Reborn's fourth byte). | likely | Case 0x84: 0x14029b510; 1 → string slot arg2 − 1; otherwise formats the number (0x14029db90, arg4 − 1). Gibbed MacroCodeHelpers (5 in Reborn, 4 on PSP), BaseDecoder (var num); text_formatting.md (FF 84). |
| 0x85 | 3 | unk_85 |
Unknown; the drawing routine computes arg2 + arg1 × 255 − 256 (the length formula of 0x95) and skips. |
— | Case 0x85. Gibbed MacroCodeHelpers. |
| 0x86 | 1 | page_break |
Page break: drawing stops there; in MODAL_ rows the text after it is a separate message. |
likely | Case 0x86 sets the done flags; data. Gibbed MacroCode (InsertPageBreak), MacroCodeHelpers. |
| 0x87 | 2 | unk_87 |
Unknown; switches between two styles by its argument (1 or other). No text uses it. | — | Case 0x87. Gibbed MacroCodeHelpers. |
| 0x88 | 2 | spacing |
Horizontal spacing: moves the pen by the signed argument, in pixels. English only. | likely | Case 0x88. Gibbed MacroCode (InsertPixelSpacing), MacroCodeHelpers. |
| 0x89 | 2 | wait |
A pause in text that is shown a character at a time: takes (argument − 1) × 10 from the countdown each drawn character also takes from, and stops drawing there, as 0x86 does, once that countdown is used up. None in English or Spanish. | likely | Case 0x89. Gibbed MacroCodeHelpers (Unknown89). |
| 0x8A | 2 | unk_8a |
Unknown; no text uses it. | — | Size only. Gibbed MacroCode (InsertIcon), MacroCodeHelpers. |
| 0x8B | 3 | unk_8b |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0x8C | 2 | choice |
Starts a choice; the argument is the default option, from 1. {choice 01} starts every numbered choice in SCENARIO_ texts, whose options are CHOICE_ rows. The drawing routine only sets a scale flag. |
likely | Data (DuckDB); case 0x8C. Gibbed MacroCode (InsertSelect), MacroCodeHelpers; text_formatting.md (FF 8C). |
| 0x8D | 3 | unk_8d |
Unknown; moves the pen to half the line width minus arg1 unless arg2 is 1. |
— | Case 0x8D. Gibbed MacroCodeHelpers. |
| 0x8E | 3 | unk_8e |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0x8F | 3 | unk_8f |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0x90 | 3 | unk_90 |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0x91 | 1 | unk_91 |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0x92 | 1 | unk_92 |
Unknown. | — | Gibbed MacroCodeHelpers. |
| 0x93 | 1 | hero_name |
Inserts the hero's name: it stands where Denam's name goes (CHARA_TEXT_, BATTLE_TEXT_). |
likely | Key MODAL_LC_SYSTEM_MESNAME_HERO_TEXT; data. Gibbed MacroCode (InsertHeroName), MacroCodeHelpers. |
| 0x94 | 1 | order_name |
Inserts the order's name: "leads the Order of {order_name}". Not the order titles of the ORDER_ table. |
likely | Data (CHARA_TEXT_LC_WR_CHARA_0003_CHR_TEXT). Gibbed MacroCode (InsertOrderName), MacroCodeHelpers. |
| 0x95 | 4 | zombie_condition |
Starts a span, which 0x96 ends, of text about undeath ("…As true in undeath…", in CLASS_TEXT_DATA_ rows). Arguments: a value (Gibbed shows it minus 1), then two length bytes a b: the span length L = a × 255 + b − 256 is the number of bytes from just after b to the end of the 0x96 code, its U+F8FF included; Gibbed checks that the last 4 of them are EF A3 BF 96. An encoder recomputes the length (Rebuilding). |
likely | Data; the game's string "ZombieFadeIn" (0x1406958c0). Gibbed MacroCode (ZombieCondition), MacroCodeHelpers, BaseDecoder. |
| 0x96 | 1 | zombie_condition_end |
Ends the span 0x95 starts. | likely | Data. Gibbed MacroCode (ZombieConditionEnd), MacroCodeHelpers. |
| 0x97 | 1 | unk_97 |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0x98 | 3 | unk_98 |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0x99 | 3 | unk_99 |
Unknown. | — | Gibbed MacroCodeHelpers. |
| 0x9A | 2 | unk_9a |
Unknown. | — | Gibbed MacroCodeHelpers. |
| 0x9B | 1 | unk_9b |
Unknown; the drawing routine adds the same render command it adds after a callback's style change. No text uses it. | — | Case 0x9B. Gibbed MacroCode (ResetTextColor), MacroCodeHelpers. |
| 0x9C | 3 | unk_9c |
Unknown. | — | Gibbed MacroCodeHelpers. |
| 0x9D | 2 | unk_9d |
Unknown; sets a value of the renderer to the argument minus 1, which 0x9E resets. No text uses it. | — | Case 0x9D. Gibbed MacroCode (SetFontPalette), MacroCodeHelpers. |
| 0x9E | 1 | unk_9e |
Unknown; resets the value 0x9D sets. No text uses it. | — | Case 0x9E. Gibbed MacroCode (ResetFontPalette), MacroCodeHelpers. |
| 0x9F | 1 | uppercase_next |
Uppercases the next character (toupper, or − 0x20 for U+00E0–U+00FF). |
likely | Case 0x9F sets a flag that the next character clears (0x1402a45e0). Gibbed MacroCode (UppercaseFollowingCharacter), MacroCodeHelpers. |
| 0xA0 | 1 | unk_a0 |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xA1 | 1 | unk_a1 |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xA2 | 1 | unk_a2 |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xA3 | 1 | unk_a3 |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xA4 | 1 | unk_a4 |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xA5 | 1 | unk_a5 |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xA6 | 1 | unk_a6 |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xA7 | 1 | unk_a7 |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xA8 | 1 | unk_a8 |
Unknown; no text uses it. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xA9 | 1 | new_page |
Starts most dialogue texts (the SCENARIO_C4_ rows, for one); not handled by the drawing routines. |
likely (size); guess (name) | Data. Gibbed MacroCode (MaybeNewPage), MacroCodeHelpers; text_formatting.md ("New page?/reset state?"). |
| 0xAA | 2 | unk_aa |
Unknown; Reborn only. Switches between two styles by its argument; German, Spanish and French mark terms with it ({unk_aa 02}Attaque physique{unk_aa 01}). |
— | Case 0xAA; data. Gibbed MacroCodeHelpers. |
| 0xAB | 3 | unk_ab |
Unknown; French only. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xAC | 3 | unk_ac |
Unknown; French only. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xAD | 1 | unk_ad |
Unknown; French only. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xAE | 3 | unk_ae |
Unknown; French only. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xAF | 3 | unk_af |
Unknown; French only. | — | Size only. Gibbed MacroCodeHelpers. |
| 0xB0 | 1 | unk_b0 |
Unknown; Reborn only. Depends on the voice language (0x140286e90). |
— | Case 0xB0. Gibbed MacroCodeHelpers. |
| 0xB1 | 1 | unk_b1 |
Unknown; Reborn only. As 0xB0, for the other language. | — | Case 0xB1. Gibbed MacroCodeHelpers. |
| 0xB2 | 4 | unk_b2 |
Draws an image from the renderer's table at +0x600; Reborn only. Gibbed shows each argument minus 1. |
— | Case 0xB2 → 0x1402a32f0. Gibbed MacroCodeHelpers, BaseDecoder (symbol). |
| 0xB3 | 4 | button |
Draws a button image; Reborn only. Some of its uses are in keys with BTN (GUIDE_LC_GUIDE_BTN_SELECT_NAME), none of 0xB2's. Gibbed shows each argument minus 1. |
likely | 0x1402a32f0(…, code == 0xB3, …); DuckDB. Gibbed MacroCodeHelpers, BaseDecoder (button). |
| 0xB4 | 3 | unk_b4 |
Picks one of two text variants by whether a character is in the font package's char_set_150 (in French, the vowels): probably French elision (le/l'). French only, in BATTLE_TEXT_ and CHARA_TEXT_ rows. |
— | font.md (char_set_150, tested by 0xB4 and 0xB5 through 0x14029be20). Gibbed lists no 0xB4. |
| 0xB5 | 3 | unk_b5 |
As 0xB4; French only. | — | As 0xB4. |
Icons
| Code point | Name | Meaning | Confidence | Source |
|---|---|---|---|---|
| U+10000B | icon_ai |
Icon; the game's word AI is in the keys it's used with (COMMAND_LC_COMMAND_NAME_AI02_NAME …). |
likely | DuckDB. Gibbed BaseDecoder (ai). |
| U+10000C | icon_skillrankup |
Icon: the whole text of TAG_LC_TAG_BTLBYHIT_SKILLRANKUP_NAME. |
likely | DuckDB. Gibbed BaseDecoder (skillrankup). |
| U+10000D | icon_enemy |
Icon before the enemy side's line in BATTLE_TEXT_ briefings ("The Dark Knights"). |
likely | DuckDB. Gibbed BaseDecoder (enemy). |
| U+10000E | icon_leader |
Icon before the enemy leaders' line in briefings ("The Dark Knights Volaq and Ozma"). | likely | DuckDB. Gibbed BaseDecoder (leader). |
| U+10000F | icon_guest |
Icon before a guest unit's line in briefings ("Azelstan"). | likely | DuckDB; the game's word GUESTMENU_. Gibbed BaseDecoder (guest). |
| U+100000 | icon_heart |
Icon at the end of Deneb's playful lines ("W-I-C-C-E! {icon_heart}" in German, the title "Mit Herz und Kürbis" of CURRENT_AFFAIRS_TEXT_LC_CURRENT_0246_TITLE); not in English or Spanish, which use none. |
likely | DuckDB (CLASS_TEXT_DATA_CLASS0061_HELP in German, French, Japanese, Korean and Chinese). Gibbed BaseDecoder (heart). |
| U+100001 | icon_unk_100001 |
Icon after the same kind of line as icon_heart, in Deneb's skill helps (EFFECTTEXT_LC_EFFECT_640_HELP: "…poison them──{icon_unk_100001}"). What it shows is unknown. |
guess | DuckDB. |
| U+100010 | icon_letter_w |
One letter of a word drawn as icons: "World Tarot" is {icon_letter_w}{icon_letter_o}{icon_letter_r}{icon_letter_l}{icon_letter_d} after 運命の輪 in Japanese, Korean and Chinese (COMMAND_LC_COMMAND_NAME_WRMAIN09_NAME), where English writes "World Tarot". |
likely | DuckDB; the letters line up across "WORLD" and "CHARIOT" (BATTLETASK_LC_BTL_TSK0560_NAME, "Chariot Tarot"), which share O and R. |
| U+100011 | icon_letter_o |
The letter O, as U+100010. | likely | As U+100010. |
| U+100012 | icon_letter_r |
The letter R, as U+100010. | likely | As U+100010. |
| U+100013 | icon_letter_l |
The letter L, as U+100010. | likely | As U+100010. |
| U+100014 | icon_letter_d |
The letter D, as U+100010. | likely | As U+100010. |
| U+100015 | icon_letter_c |
The letter C: "CHARIOT" is U+100015, U+100016, U+100017, U+100012, U+100018, U+100011, U+100019. | likely | As U+100010. |
| U+100016 | icon_letter_h |
The letter H, as U+100015. | likely | As U+100010. |
| U+100017 | icon_letter_a |
The letter A, as U+100015. | likely | As U+100010. |
| U+100018 | icon_letter_i |
The letter I, as U+100015. | likely | As U+100010. |
| U+100019 | icon_letter_t |
The letter T, as U+100015. | likely | As U+100010. |
| U+10001B | icon_arrow_up |
Up arrow: German, Spanish and French write it where English writes "Up", "+" or "Ascending" (CHOICE_LC_CHOICE_NAME_TCS_KEY0100_NAME "Cursor/Dirección {icon_arrow_up}", TAG_LC_TAG_BTLBYHIT_AGL_PLUS_NAME "AGI {icon_arrow_up}"). |
likely | DuckDB, against the English rows of the same keys. |
| U+10001C | icon_arrow_down |
Down arrow: where English writes "Down", "−" or "Descending" (…TCS_KEY0110_NAME, …AGL_MINUS_NAME). |
likely | As U+10001B. |
| U+10001D | icon_arrow_left |
Left arrow: where English writes "Left" (…TCS_KEY0120_NAME). |
likely | As U+10001B. |
| U+10001E | icon_arrow_right |
Right arrow: where English writes "Right" (…TCS_KEY0130_NAME) or ">" (TAG_LC_TAG_TCS_NOTICE_UNI_LV_VALUE_NAME, "Lv. 2 > Lv. 3"). |
likely | As U+10001B. |
How the game reads it
From text/textmanager.cpp and the font renderer (BaseFontRenderer, graphics/font/basefontrenderer.cpp), by static analysis with Ghidra 12.1.4 of the unpacked executable (SHA-256 7b0230a1…), so at most likely.
Loading and lookup
- likely: the game loads one language's text: group 4000, file ID = the game's language (0–7; 1 when the language is 8 or more), unwrapped (
RotXorobfuscation, then ZIP; containers.md). Group 4000 is probablylocalize/, whose eightLocalizeText_<lang>.datfiles match the eight languages. Source:0x1402b9bf0,0x1402ba8d0→0x1402b94a0(FileTable_OpenById(4000, language)); language from0x14026ca60. - likely: it takes every
pakdentry whose first 3 bytes aretxt, without checking the 4th, which fits the magic beingtxtplus a version byte. For each row it turns all four fields (key,text,unk_0x10,unk_0x18) into pointers by adding the table's address, and indexes the rows bykey(FNV-1a 64-bit hashes), and bytexttoo for entries 0, 1 and 18–23 (the list at0x14067DE28). Source:0x1402ba410(strncmp(entry, "txt", 3); headerrow_count,row_offset,row_sizeas inxlce);0x1402b9cd0undoes it on unload. - likely: looking a key up returns that row's
text. When the key isn't found, it returns the key itself (or nothing, when the caller asks). Nothing read so far usesunk_0x10orunk_0x18. Source:0x1402b95d0. - likely: before the lookup, a key can be swapped for another:
system/system_data.datentry#9is a table of rows of four strings (a key and three replacements), and when a game setting (at0x140CDE96C, read by0x14026ca70) is 1, 2 or 3, a key found in that table is replaced by its replacement 1, 2 or 3. What the setting is: a lead. Source:0x1402b95d0;0x1402bd9a0(system/systemdata.cpp, which sets0x140CE4DB8from header offset0x2C, entry#9).
How a table's ID becomes text
- confirmed: an ID like
arms_data'sname_idis a row index intobattle_data_release.dat#35(text_keys), often plus a first row for its kind of record; the game takes that row's string at0x08as the key for a name, or at0x10for its help, and looks it up as above. Each field that holds such an ID linkstext_keyswithText:and shows its English text in the decoded data. Source: the decode (R24): every value of the 11 linked fields picks a key of its own table's prefix, or an empty key on a row with no text (DuckDB, 2026-10-12, in each field's Source);0x1402d36d0(arms_dataname_idat0x86→text_keysfield0x08),0x1402d3630(help_idat0x62→ field0x10); other accessors read fixedtext_keysrows from 3,672 (0xE58) on (0x1402d3800,0x1402d3830) (How the game loads it).
Escapes and control codes
- likely: a string that starts with U+F8FC begins with a header the game skips, both before a lookup and before drawing: after the code point, a byte with bit 0 clear means 5 bytes in all; with bit 0 set, 5 bytes, then 6-byte groups while the next group's first byte has bit 0 set, then one more 6-byte group. Source:
0x1402b95d0,0x1402b99d0, and the drawing routines0x1402a4a00,0x1402a5fe0,0x1402a7610,0x1402a8c30,0x1402aa8d0. - likely: U+F8FD and U+F8FE are each followed by one byte
b, and insert a stored string: numberb − 1for U+F8FD,b + 253for U+F8FE. Source: the same drawing routines (0x14029b720returns the string, through an indirect call atDAT_140ce3870). - likely: the drawing routines handle control codes 0x80–0x89, 0x8C, 0x8D, 0x9B, 0x9D–0x9F, 0xAA, 0xB0–0xB3 themselves, and hand every other code to a virtual function; 0x93
hero_name, 0x94order_nameand the 0x95/0x96 span are among those. They skip a code by a size looked up in a table, atcode − 0x80, that the renderer holds (offset0xB0of the object at0x140CE3958), not by sizes written in the code; where that table comes from is a lead. Source: the drawing routines above (theswitchon the code byte,default:→(*renderer->vtable[1])). - likely: what some codes do: 0x80 breaks the line; 0x81 is a wrap point, its first argument × 2 being the width; 0x83 inserts string parameter
arg1 − 1; 0x84 inserts number parameterarg1 − 1; 0x86 stops drawing there, as a page break would, and 0x89 does too once its countdown runs out; 0x88 moves the pen by a signed pixel count; 0x82 sets a color from three bytes (each minus 1) and an alpha; 0x87 and 0xAA switch between two styles by their argument; 0x9D sets and 0x9E resets a value; 0x9F uppercases the next character; 0xB2 and 0xB3 draw an image (0xB3 a button). Source: the same drawing routines.
Rebuilding
How a table is rebuilt from its rows in the text form. All steps are confirmed for the eight languages: the encoder follows them with no byte taken from the original, and gives back all 576 tables byte for byte (the round trip, see Method). Step 5 recomputes every zombie_condition length to the original bytes, and step 7 writes back every extra NUL.
- Header:
magictxte,row_count= the number of rows,row_offset16,row_size32 (Header). - Rows: in their original order, each field an offset (upper 4 bytes zero) to its string (Row).
- String pool: right after the last row; each row's strings in field order (
key,text,unk_0x10,unk_0x18), each encoded from the text form and followed by one NUL. No string is shared, so every offset is recomputed from the lengths (String pool). - Padding: zeros up to
align16(data_size + 1), wheredata_sizeis the end of the pool (Padding). zombie_condition's length bytes are recomputed for each 0x95 from its matching 0x96 (paired like brackets), so edits inside the span stay valid: with L as in Control codes,a= L div 255 + 1 andb= L mod 255 + 1, so neither byte is ever 0 (a NUL would end the string); L must be below 255 × 255. A 0x95 with no 0x96 keeps the bytes given. Every span in the eight languages is written so (one French span hasa= 2). Source for the length: GibbedBaseDecoder; for the bytes, the round trip and DuckDB over the text forms (R36, see Method).unk_0x10andunk_0x18are kept as given; their meaning is unknown.- A
{nul}at the end of a string writes one more NUL after it (String pool).
Leads
- What
unk_0x10andunk_0x18hold (unk_0x10isMorFon German, Spanish and French unit names;unk_0x18isXin every row of table#0only): the lookup never returns them (How the game reads it), so look for other readers of the row, such as the code that builds random unit names. - How the renderer gets its code-size table (offset
0xB0of the object at0x140CE3958): its values areFontAllFilePackage.dat's chunk 10, but the code read so far frees that file unread (font.md), and no write to the field but the constructor's zero was found. - Why some
SCENARIO_texts in German and Spanish end in an extra NUL (an editing tool's leftover, probably); the game never sees it. - What the French-only codes 0xAB–0xAF, 0xB4 and 0xB5 do, and what
icon_unk_100001shows. - Where the control-code size table the renderer uses comes from (offset
0xB0of the renderer at0x140CE3958), and what the key-swap setting at0x140CDE96Cis. - What the arguments of each control code mean, and what the
unk_codes do. An in-game test settles most of them. - Codes the virtual function handles (0x93–0x96, 0xA9 and others;
default:→renderer->vtable[1]) weren't read, sohero_name,order_nameandzombie_conditionrest on game keys, strings and data;new_pagehas only Gibbed's guess. The iconsenemy,leaderandguestrest on the briefings' layout; no code maps their code points. - Gibbed reads U+F8FC as a span; no text uses it.
Txte.csdoesn't decode U+F8FE (it would show as{U+F8FE}and its byte as text, still lossless); no text uses it.- Whether a string can carry an extra NUL, which step 3 of Rebuilding doesn't allow for.
- The magic may be
txtplus a version bytee, asxlceisxlcplus 101: the game checks onlytxt(How the game reads it). txtetables in other files.- legacy/formats/txte.md (
../../legacy/formats/txte.md) has earlier observations to recheck (reference only).