Text and fonts

Technical appendix · Home · Français

The text is decoded. See game-text.txt.

1. Encoding layers

G3.CON rec 0x29 → 5-bit codes → glyph bytes → font atlas → pixels
                  Txt_Decode5Bit             Vid_DrawGlyph

Text was invisible to earlier scans because it is neither ASCII nor byte-aligned glyph data.

2. Glyph bytes (verified)

byte = glyph index + 1.

Index Characters
0-25 a-z
26-51 A-Z
52-61 0-9
62-74 â ä à ç é ê ë è î ï ô ö ò (read from shapes)
75, 76, 97, 98 blank
77-79 û ù ü (read from shapes)
80-96 % " ' . , ; : ! ? / ( ) + - = ° ß (read from shapes)

Special bytes: 0x00 ends a string, 0x65 breaks a line, 0x4C (index 75) is the word space, which Txt_DrawIndexed tests at 0x15913. Letter and digit order, and all decoded French, confirm the map; accents and punctuation come from their shapes.

3. 5-bit strings: Txt_Decode5Bit EA 0x11402 (verified)

Far entry 0x84:0xBC2. Arguments: bits far pointer, sizes far pointer, destination far pointer, index.

  • Sizes are big-endian u16 byte lengths. Start = sum of the sizes before the index.
  • Codes are 5 bits, MSB-first (Txt_Read5Bits EA 0x114A2).
  • Code n selects page[n-1]. Page 0 is DS:429. Code 0x1E moves to page 1 (DS:446) and 0x1F to page 2 (DS:463), for the next code only.
  • Code 0 would read the byte before the table; correct text doesn't use it.
  • At the last byte (0x11478), one more code is read if at least 5 bits remain in that byte (mask >= 0x10); it is stored only if non-zero.

Page 0 decodes to easrniutol.dc pmhvb\nfgé-'!,L, clearly tuned for French.

4. Where strings live

Bits Sizes Strings Used by
DS:1B06 DS:1BFD 20 intro credits Intro_OpShowCredit
G3.CON rec 0x29 sub 0 rec 0x28 sub 0 1,589 game strings in 7 banks MAUPITI1, through services 2 and 9

Credits include LANKHOR présente and Copyright (c) 1990.

Game text banks (verified)

MAUPITI1.EXE adds these constants to the two pointers to select a storage bank. Txt_DrawFromBank EA 0x1FD70 takes a text class and maps it through a local array (5,3,1,2,7,4,6), so class numbers in code differ from bank numbers. Each bank has its own string numbers, and calls use them 1-based: Game_AddEnergy draws class 3, string 37 + condition/20, which is bank 1 entry 36, "Lessivé". Txt_DrawLinesBank12 EA 0x1F230 picks bank 1 or 2 directly.

Bank Class Strings Size-table bytes Text bytes Contents, from sampling them
1 3 235 0x1D6 0x6CE verbs, menus, short messages
2 4 80 0xA0 0x240 object names
3 2 483 0x3C6 0x239E examine and object text
4 6 102 0xCC 0x270C long documents: chess games, grotto text, and some cipher-like strings
5 1 103 0xCE 0x104A story events
6 7 135 0x10E 0x6A0 questions and answers
7 5 451 0x386 0x46C0 character dialogue

The sizes add up exactly to records 0x28 (3,178 B) and 0x29 (45,410 B). Banks are word-aligned: bank 1's strings total 1,741 B and bank 7's 18,111 B, each with one padding byte. No string uses the invalid code 0.

5. Fonts

Two aMpu-tagged descriptors, both 99 glyphs. Vid_DrawGlyph uses whichever wFontDescSeg:wFontDescOfs points to.

Field Meaning DS:1522 (EA 0x1B942) DS:1F11 (EA 0x1C331)
+0x4E u8 rows 8 7
+0x5C u16 atlas 110 110
+0x60 u16 row stride 68 56
+0x62 u16 records {u16 bit offset, u16 ink width} 654 502
+0x66 u16 advance table 1050 898
+0x6A u16 nudge table 1248 1096
ink total / row capacity (bits) 515 / 544 438 / 448
swapped atlas bytes 544 392

advance == ink + 1 for 95 of 99 glyphs; the exceptions are the blanks, with advances 6, 6, 6, 4.

Installers: Font_LoadCharset installs the 8-row font at 0x16F65; Font_SelectDescriptor EA 0x1734A installs the 7-row one.

Reading the atlas

Font_ByteswapBlock EA 0x1733C swaps every aligned 16-bit word of the atlas once. After that, each row is a plain MSB-first bitstream. To fetch a glyph, Vid_DrawGlyph takes two bytes big-endian at offset >> 3, shifts left by offset & 7, and samples the high byte, so at most 8 pixels per glyph. Some 8-row glyphs have ink widths up to 11; the code cuts them at 8 pixels, and the atlas shows that.

Swap the words first. Reading the file as little-endian at the same byte offset is only right for even offsets, and gets 47 of the 7-row font's 99 glyphs wrong.

6. Drawing

Vid_DrawGlyph EA 0x11590, per glyph:

  1. Glyph index = AL - 1.
  2. Adds nudge[index] to wCanvasX, and draws at that position in wTextColorCur.
  3. Removes the nudge, then adds advance[index] (0x1164D-0x11663).

So text is proportional. Vid_DrawString EA 0x11858 draws until 0x00 or 0x65, copies wTextColor (DS:D4) to wTextColorCur (DS:496), and adds a caller-supplied extra letter spacing. Txt_DrawIndexed also reads the advance table to measure text (0x158DF).

Callers:

service 9 → Txt_DrawIndexed EA 0x157C3    (also Intro_OpShowCredit x3)
service 2 → Vid_DrawTextLines EA 0x15A38 → Vid_DrawString → Vid_DrawGlyph
                                           Txt_Decode5Bit at 0x15AC4
Vid_DrawStringEx EA 0x1188C → Vid_DrawGlyph

Txt_DrawIndexed calls Txt_Decode5Bit at 0x15822 and 0x158A7. Its 28-word block holds the colour at +0xC, y at +0x18, x at +0x1C, string buffer at +0x28, sizes at +0x2C, bits at +0x30, and a 1-based string number at +0. Vid_DrawTextLines advances by 10 per line (0x15C01, 0x15CCA).

The 0x4C-based wrapping and any drawing by Txt_DrawIndexed beyond its decode and measuring steps have not been traced in full.

7. Intro script

Font_LoadCharset EA 0x16F04 is really the intro (Intro_Run in Binary Ninja). It installs the 8-row font, decodes rec 0x27 sub 2 to the page, copies it to wPage2Seg, then runs the script at DS:1C25:

Opcode Routine Action
0 0x17107 copy page
4 0x17126 (IDA Font_LoadStrings) decode rec 0x27 sub n at x, y
8 0x1716B service 0x12
0xC 0x171A4 show credit string
0x10 0x172D7 timed step (inferred)
0xFF end check

So record 0x27 holds intro pictures and music plus other assets that MAUPITI1 loads at start (The two programs). It holds neither fonts nor strings.

Previous: Video · Next: Speech · Technical appendix · Home