Lists, menus and the smaller files
The files that hold lists rather than pictures or sound: the circuit list, the menu tree, the race display layout and the music tables, plus the files the game writes into DATA\Params\ and DATA\Com\, and the two kinds of file in the data folders that are not POD formats (the AVI videos and LOADER.EXE). The car tables VOITURES.BIN, VOITURE2.BIN and VOIRESET.BIN are in cars.md.
| File | Reader (Python, Rust) | State |
|---|---|---|
SCRIPTS/CIRCUITS.BIN | decoders/circuits.py, circuits.rs | decoded, round-trip |
SCRIPTS/BETAMNU.BIN | decoders/betamnu.py, menus.rs | decoded, round-trip; many item fields unknown |
SCRIPTS/HABI.BIN | decoders/habi.py, dressing.rs | decoded, round-trip; fields traced (docs/systems/hud.md) |
RESSON/CDAREF.TXT, MIDIREF.TXT, SCRIPT1.TXT, SCRIPT2.TXT | decoders/restxt.py, music.rs | decoded, round-trip |
VIDEO/*.AVI | decoders/riff.py | chunk tree walked (standard RIFF AVI) |
LOADER.EXE | decoders/pe.py | layout walked (a Windows program) |
Params\<car>.prb, Com\modem.ann | none | traced in the code; not on the disc |
Measured for every file on the disc, 2026-10-23: every byte read, and writing the decoded form gives the original bytes (the AVI and EXE walkers only read).
CIRCUITS.BIN: the circuit list
Observed in FUN_0047737c: it opens the file in folder 7 (Data\Binary\Scripts\), reads a u32 count into 0x6174C4, then 0x1180 bytes into 0x61D5EC, and XORs each of those bytes with 0x50. The 0x1180 bytes are 32 rows of 0x8C bytes; the first count (17) are circuits, the other 15 rows are zero (measured). POD's file has 35 rows: 32 circuits, then three rows that each hold a circuit named "Night" (measured). POD's reader (FUN_00471f24) reads the count, then that many rows of 0x8c (up to 96, each in its own allocation), so the three "Night" rows are never read (observed, 2026-10-24). The readers keep them so that the file writes back the same.
| Offset | Size | Content | Evidence |
|---|---|---|---|
0x00 | 20 | File name stem: CIRCUITS/<stem>.BL3, RECORDS/<stem>.BRT | observed: copied to 0x620048 by FUN_0043572c |
0x14 | 64 | The name shown in the menus | observed: read at 0x61D600 by FUN_004a8b80 and FUN_004cbcec |
0x54 | 13 | Small map picture, sm_*.tga | inferred from the names |
0x61 | 13 | Sky picture, sk_*.tga; may fill all 13 bytes with no zero | inferred |
0x6E | 14 | Second sky picture, sk_*_2.tga; may be cut short (sk_Beltane_2.t) | inferred |
0x7C | 1 | Flags: bit 1 = may be raced mirrored; bit 0 set on every row, meaning unknown | observed: FUN_0043db04 draws a mirror bit only for rows with bit 1; FUN_004a8fd0 |
0x7D | 1 | Group: 2 for the 16 circuits, 1 for Special | observed: FUN_0044cb68 finds a row by (group, number) |
0x7E | 1 | Number, 0 to 16 | as above |
0x7F | 1 | Zero | measured |
0x80 | 4 | Length, metres (Beltane 7,717) | inferred: FUN_0044d98c prints it with %f divided by a number read from the text tables after INFOS/KM |
0x84 | 1 | Laps of a race (3 to 8) | observed: FUN_0043572c copies it to 0x61D5C8 outside time attack |
0x85 | 1 | Timing sectors per lap (2 to 4) | observed: FUN_0043572c copies it to 0x61D5D4, the sector count of records.md |
0x86 | 2 | Zero | measured |
0x88 | 4 | Zero in the file; the game stores the slot of the track's sound bank RESSON/<stem>.MEG here | observed: FUN_0045ede0 calls the bank loader FUN_004c1e48 with the stem |
Text fields are zero after their first zero byte (measured); the readers refuse other bytes there so that a written file matches the game's.
BETAMNU.BIN: the menu tree
Observed in FUN_00435d74 (start-up): three u32 words (0x502ED0 = 32, 0x502ED4 = 1, then the size of the rest), then the rest read whole into one allocation and walked as menus.
A menu is 0x1BC bytes, followed by its items of 0xFC bytes each. In POD a menu is 0x374 bytes: 200 item slots instead of 90 (observed in POD's load_menus 0x428af8, which steps 0x374 a menu at 0x428e09 and 0xFC an item; measured: the whole file walks with it). The walk ends at the end of the buffer: 33 menus, 503 items (measured).
| Menu offset | Size | Content | Evidence |
|---|---|---|---|
0x00 | 0x44 | The editor's name for the menu, in French ("Menu racine", "Ecran des Records") | measured |
0x44 | 2 | Menu number: the game stores the menu's address at 0x502DCC + 4 × number | observed |
0x46 | 2 | Handler: index into the function table FUN_0043be00 returns; -1 none. The game puts the function's address in the whole word +0x44 | observed |
0x48, 0x4C | 4, 4 | Unknown | |
0x50 | 4 | Item count | observed |
0x54 | 90 × 4 | Item addresses as the menu editor held them in its memory (0x6D902C …); the game overwrites the first count with its own pointers; the rest are zero | observed, measured |
| Item offset | Size | Content | Evidence |
|---|---|---|---|
0x00 | 4 × 4 | Top, bottom, left, right on a 640 × 480 screen | inferred from the values ("One Player": 153, 185, 195, 304) |
0x10 | 0x40 | The editor's label ("One Player"); empty for most items: the shown text comes from the DICOS tables | measured |
0x68 | 4 | Action: index into the same function table | observed |
0x74 | 4 | Type; types 6 and 10 carry a list | observed |
0x78 | 4 | List entries (types 6 and 10) | observed |
0x7C | 4 | Index into the table at 0x506EDC (types 6 and 10) | observed |
0xD0 / 0xD4 | 4 × entries | One action per list entry (type 6 / type 10) | observed |
The other item words are kept as they are; the words at 0x5C look like the indices of the neighbouring items (inferred).
HABI.BIN: the race display layout
Observed in FUN_0047ce34: it reads up to 0x6400 bytes into 0x5393F0 and XORs every byte with 0x80. The text is a run of zero-terminated lines; empty lines are extra zero bytes; the last line is Fin Habillage ("end of dressing"). An element starts with a line that FUN_0047ce34 reads with sscanf("%ld %s %ld %s %ld %ld %ld %ld %ld %ld"): ten fields, the fourth a font or picture name (typo021 … typo040, nulle). The lines after it, up to the next such line, are the element's options: characters to draw (! ! ! ! ! !), lists after /l, /p, /s, /t, /c, and a last line of /N KEY pairs naming the view keys (F3, F4, PgUp). 219 elements (measured). The ten fields (slot, kind, the element it hangs off, font, a word, the start state, display mode, views, x, y) and what each kind draws are in docs/systems/hud.md.
The music tables (RESSON/*.TXT)
Plain text, CR LF lines. The game reads a line at a time with FUN_004c21e8 (at most 0x50 characters) and skips lines that start with ; or a space, and empty lines (observed in FUN_004c49e0).
| File | Reader | Keys (sscanf) |
|---|---|---|
CDAREF.TXT | FUN_004c4170 | NB_CD_TRACKS:%hd; per track %hd:, TRACK:%hd (the CD audio track), START:%hd, TIME:%hd; at most 20 tracks of 6 bytes at 0x697D18 |
MIDIREF.TXT | FUN_004c45d8 | NB_MIDI_TRACKS:%d; per track %hd:, MIDIFILE:%s, TIME:%hd |
SCRIPT1.TXT, SCRIPT2.TXT | FUN_004c49e0 | NB_LOOPS:%hd, NB_ELEMENTS:%hd; per element TYPE:%s (CD → 3, else 2: MIDI), ID:%hd, VOLUME:%hd, FADEIN:%s, FADEOUT:%s, CROSSFADE:%s (OUI = yes) |
CDAREF.TXT lists CD tracks 2 (30) and 3 (20): the disc's music is audio tracks 2 and 3. MIDIREF.TXT lists no MIDI tracks; its commented-out entries name resson\gmarag.mid and resson\canon.mid. SCRIPT1.TXT plays CD element 0 at volume 50 for 9,999 loops; SCRIPT2.TXT is a MIDI test sequence ("sequence scriptee de test des pistes CD"). The units of START, TIME and VOLUME are open. The callers of FUN_004c49e0 (FUN_004c0f3c) choose the script file; how is open.
Files the game writes: Data\Params\ and Data\Com\
Both folders are empty on the disc. The folder numbers are observed in FUN_0047aeb4, which turns a folder number into a path: 10 = Data\Params\, 12 = Data\Com\ (11 = Scope\).
Params\<car>.prb: car set-ups
The garage's five saved set-ups for one car (docs/systems/front-end.md, "The garage"). Observed in POD: load_car_setups (0x472138) reads them, 0x472668 writes them; the reader and writer are crates/pod-formats/src/prb.rs. The name is the car's own name with .prb. Five rows of 0x28 bytes in memory (name at +0, flag at +0x10, the five values at +0x14); in the file, per row r (0 to 4):
| Size | Content |
|---|---|
| 4 | Name length n, the zero included; at most 15 |
n | The name, encoded: the first byte XOR 0xad + 0x2d × r (a byte), each next byte XOR the encoded byte before it plus 0xfd |
| 1 | Flag: 1 for an unused row |
| 4 | Value 1 XOR 0x1b37efe3 |
| 4 × 4 | Values 2 to 5: ((previous ^ K(i-1)) − value) ^ K(i), with K = 0x1b37efe3, 0x345cbe08, 0xfc405a6c, 0xadffddb3, 0xda27fe18 |
then a u32 check word: the sum of all 25 values XOR 0xda27fe18.
What the reader keeps: without the file, five rows named ... with flag 1. A row whose name length is over 15 becomes ... unused, and the reading goes on just after the length. Each value is added to the sum before a negative one is set to 0. A wrong check word makes every row ... unused (the values stay). In time attack (0x63331c & 4) a used row with a value outside 0 to 100, or five values adding up to more than 300, becomes unused. The values are acceleration, brakes, grip, steering and speed, sharing 300 points.
The first edition's reader (FUN_0047751c) and writer (FUN_00477894) store the values as differences without the keys and the names without encoding (history, docs/versions.md). No game-written file has been compared yet: tests in prb.rs check the writer against the reader.
Com\modem.ann: the modem phone book
Observed in FUN_00454bb4: three pairs of (u32 length, text) — a name and a phone number for each entry — read into the multi-player menu's text fields. Without the file the game shows ... in each. FUN_00454e24 writes it (opened for writing in folder 12).
Other files
VIDEO/*.AVI(6): standard RIFF AVI (Microsoft Video for Windows).riff.pywalks the chunk tree to the last byte: each has one Indeo 4.1 (IV41) video stream (CREDITS.AVI: 280 × 180, 131 frames at 33,367 µs). The pictures, the sound and how the game plays them:video.md.LOADER.EXE: a 32-bit Windows program linked 1997-02-11, identical to the installedLOADER.EXE(docs/FINDINGS.md).pe.pyaccounts for its bytes by headers and sections.