Modding: how the game finds its files, and how to load a changed one
A mod for the original WINPOD.EXE needs no patch to the game and no new disc. The game looks for every data file in the folder named by Ubi.ini before it looks on the CD, so a changed file placed there is loaded instead of the disc's copy. Four mods were loaded this way on 2026-10-23 (measured, in the emulated PC, run/vm): a string, a menu image, a track texture and a car's grip. The writers that make them are in crates/pod-formats (tests/writers.rs), and examples/make_mod.rs makes each one.
Where the game looks
At start-up FUN_00479954 reads C:\WINDOWS\UbiSoft\Ubi.ini (observed: GetPrivateProfileStringA):
Key in [POD2_0] | Kept at | Use |
|---|---|---|
Directory | 0x578c5c | The installation folder, C:\POD\ in run/vm/inside.sh |
Language | 0x57905c | The language folder for text and localised images (French); from GetLocaleInfoA when missing |
The CD drive's root is kept at 0x57884c. open_data_file (FUN_0047ba70) finds the drive by trying every letter from A: for a drive of type 5 (CD-ROM) labelled POD2_0 (observed).
Every data file is opened through open_data_file with a folder number. folder_path (FUN_0047aeb4) builds two names from it (observed: one switch on the folder number, two string copies per case): the first under Directory, the second under the CD's root.
| Folder | Path under the root | Files | On the CD too |
|---|---|---|---|
| 1 | Data\Binary\Voitures\ | Cars .BV3/.BV4, CHROME.CH3/.CH4 | yes |
| 2 | Data\Binary\Circuits\ | Tracks .BL3/.BL4 | yes |
| 3 | Data\Binary\Polices\ | Fonts .BP3, effects .BI2 | yes |
| 4 | Data\Binary\Dicos\<Language>\ | Text .BMD | yes |
| 5 | Data\Binary\MIMG\ | Menu images .IMG | yes |
| 6 | Data\Binary\MIMG\<Language>\ | Localised menu images | yes |
| 7 | Data\Binary\Scripts\ | Lists .BIN | yes |
| 8 | Data\Records\ | Best times .BRT | no |
| 9 | Data\ChShip\ | Championship saves POD<n>.NS and others (formats/chship.md) | no |
| 10 | Data\Params\ | Car setups .prb | no |
| 11 | Scope\ | (not seen in use) | no |
| 12 | Data\Com\ | modem.ann | no |
| 13 | Data\Binary\Video\ | Stills .BBM, CREDITS.BVB, videos | yes |
| 14 | Data\Binary\resson\ | Sounds .MEG, music lists .TXT | yes |
Then open_data_file does this (observed):
CreateFileAon the name underDirectory. If it opens, that file is used.- If it fails and the file is opened for writing, the game reports "impossible to write file".
- Folders 8 to 12 (the game's own saves) stop here: no CD copy is looked for.
- Otherwise, when the CD has been found (
0x578858set), the name under the CD's root (inferred:folder_pathwrites that name into the buffer passed inEBX, which Ghidra's pseudocode drops; that the CD copy is read is measured, since unchanged files still load). - If that fails too, the game searches the drives for the
POD2_0disc again and asks for it (message0x436to its window); Cancel quits.
So a mod is a file at the same path under Directory: for example C:\POD\Data\Binary\Circuits\BELTANE.BL3. Wine finds the folder names in any case. Only the files that change need to be there; the rest still come from the CD.
The four mods (2026-10-23)
Each was made with cargo run --release -p pod-formats --example make_mod, copied into the VM's C:\POD\ with run/vm/mods.sh install, and seen in the running game (screenshots in the diary, 2026-10-23, "The first mods"). The 8-bit build reads the .BL3 tracks and .BV3 cars (docs/hardware/3d-cards.md).
| Mod | File | Command | Seen in the game |
|---|---|---|---|
| A string | Dicos\French\DIC_GAM.BMD | text … LAPCNT LAPESP "MOD " (and CHRONO LAP, AFFICH LAPX, AFFICH LASTLAP) | The lap counter reads "MOD 1/3" instead of "Tour 1/3" |
| A menu image | MIMG\BELTANE.IMG | image … 0 | The track screen's map of Beltane carries a frame and "MOD" |
| A track texture | Circuits\BELTANE.BL3 | texture … 0 | Texture page 0 (route2l2, murco4, Cellule, stone2) is a yellow and grey chequerboard on the tunnel walls and ceiling |
| A car value | Voitures\GAMMA.BV3 | setting … 2 5 | The car screen shows "Adhérence 5" instead of 95; the race car's grip falls from 3.67 to 1.33 (read from memory); lap times below |
Notes from the mods (measured):
- Colours come from the palette. A changed 8-bit picture keeps its palette, so the writer picks the nearest palette colour. Beltane's palette has no magenta: the chequerboard's magenta squares came out grey, and the menu image's yellow came out green.
- A longer string is fine. The text writer appends the new string to the pool and points the key at it; the pool and the file grow (
text.rs,Table::set). - The game checks the container. Every changed container file is written through
pod_container::encode, which recomputes each block's checksum; a wrong sum stops the game (docs/formats/container.md).
Grip: the garage setting, not the parameters
A first grip mod changed the parameters' side_grip and traction_grip (carparams.rs) on all four wheels to 1.2. The changed values were in the car's slot in memory (0x61757c + slot × 0x63c, parameters +0x94), so the file loaded; but the race car (0x61aad0, +0x94) held 3.67 on every wheel, as it does with the disc's file (measured). The race uses the garage setup: setup_defaults (FUN_004436c8) computes one grip for all four wheels, side and traction alike, from the car's third garage setting, and car_from_setup (FUN_00442f04) writes it over the parameters (observed). The setup lives at 0x50769c + car × 0x1a8, the grip at +0x13c.
The five garage settings at the end of the car file are the car screen's five figures, in order: acceleration, brakes, grip ("adhérence"), steering, speed (measured: GAMMA's 49, 25, 95, 82, 49 on screen; a changed third value shows there). make_mod setting FILE OUT 2 N changes the grip.
GAMMA driven by the line follower (run/vm/follow.py, the same recorded line, Beltane, 3 laps, EASY); progress from its log (--log), lap ends counted along the line:
| Grip setting | Race car's grip (memory) | Laps completed at (s after the follower starts) |
|---|---|---|
| 95 (the disc) | 3.67 (240517) | 159, 317, 472 (the game's best lap 2:33.38); a second run 154, 323, 482 (best 2:37.39) |
| 50 | 2.50 (163840) | 202, 506 (the game's lap times 3:28.38 and 5:00.79); wedged in a dark corner in lap 3 |
| 5 | 1.33 (87162) | none: 0.69 of a lap in 625 s, 418 s of them standing against walls |
The three values fit grip = 1.2 + 0.026 × setting exactly in 16.16 (measured at three points; the code computes it in x87 floating point, which Ghidra's pseudocode hides). The computer's cars race with 3.15 (206438) whatever the player's car file says.
Track edits (2026-10-24)
The track editor (apps/viewer, feature editor; pod-viewer edit) writes over the fields the reader found, so an edited track keeps its size and every other byte. Three edits were loaded in the 8-bit build in the emulated PC and compared with a stock race run the same way (screenshots in the diary, 2026-10-24, "The track editor"):
| Edit | File | Command | Seen in the game (measured) |
|---|---|---|---|
| A changed texture | Circuits\BELTANE.BL3 | --sector-page 35 1 --sector-page 36 1 | The road after the grid shows page 1's red and orange pictures; the stock race shows the brown road there |
| A raised road | Circuits\BELTANE.BL3 | --raise 601.5 48.2 35 6 | The cars drive over it: at x ≈ 596 the player's car is at z 55.2 against 52.3 in the stock race, at x ≈ 613 at 54.7 against 49.4, and the seven computer cars rise the same way (racerec.py); past the crest they stay above the old road to x ≈ 650, beyond the bump's end at 636, so they jump; the camera tilts over the crest at 222 km/h |
| Moved props | Circuits\ALDERON.BL3 | --move-prop on two signs and three cones, onto the road 40 and 60 units after the grid | The signs and cones stand on the road in the race; the stock race shows the bend empty |
Beltane has no environment props (its .env name is NEANT), so the props were moved on Alderon. The game takes the new heights for its physics as well as its drawing: the raised road needs no change outside the sector geometry, given the face normals and the sector bounds that the editor writes again (docs/formats/tracks.md, "After the sectors").
Edited tracks in Un-POD (POD, 2026-10-24)
The editor installs an edited track into a game folder (pod-viewer edit ... --install GAME_DIR, apps/viewer/README.md), and Un-POD reads it from there with unpod --live --content GAME_DIR/Data, as the original reads its install folder before the CD. --as NEW installs a copy under a new name with its own CIRCUITS.BIN row (pod_extras::install::add_circuit). Compared with the same race on POD's own files (measured with the probe, car 0 every 10 passes, holding accelerate only):
| Edit | Track | Seen in Un-POD |
|---|---|---|
--sector-page 35 1 --sector-page 36 1 | BELTANE.BL3 | The road after the grid shows page 1, as in the original |
--raise 601.5 48.2 35 6 | BELTANE.BL3 | The player's car is higher from x ≈ 570: +3.5 at x 600, +4.8 at x 620; it stays above the old road to x ≈ 650, past the bump's end, and lands (Un-POD at d4fa3b4; repeatable byte for byte). The original (first edition, racerec.py) gave +2.9 at x 596 and +5.3 at x 613 |
--sector-page 72 6 --as MAGVOLT | MAGNETIC.BL3 (new in POD), installed as a new track | Un-POD races MAGVOLT; the grid's sector shows page 6 |
--move-beacon 35 0 0 50 | BELTANE.BL3: the lap line (beacon 35, its chain runs "P-Chrono Tour") lifted 50 units | The cars pass under it: after 3:20 the computer's racer 1 has crossed it 0 times and has no lap, against 2 crossings and a lap of 111.2 s on POD's track; every car waits for checkpoint 0 |
--move-path all 600 48 80 0 25 0 | BELTANE.BL3: the racing line moved 25 units across the downhill straight | The seven computer cars follow it: their mean y on their first pass is +24.2 at x 595 and +23.8 at x 610, falling off on both sides as the move does (+7.7 at x 550 and 655) |
--add-prop 4 -380 -21.5 6.8 and three --add-prop 7 across the lane at x −360 | ALDERON.BL3 | The sign and three cones stand on the road; the player's car, holding accelerate, passes between the cones with its path and speed unchanged (whether the cone in its way moved is not measured) |
Make your own
cargo run --release -p pod-formats --example make_mod -- text DIC_GAM.BMD OUT.BMD SECTION KEY "TEXT"
cargo run --release -p pod-formats --example make_mod -- image BELTANE.IMG OUT.IMG ENTRY
cargo run --release -p pod-formats --example make_mod -- pages BELTANE.BL3
cargo run --release -p pod-formats --example make_mod -- texture BELTANE.BL3 OUT.BL3 PAGE
cargo run --release -p pod-formats --example make_mod -- car GAMMA.BV3 OUT.BV3 FIELD VALUEPut the result under Directory at the path in the table above, with the original file's name. To undo a mod, delete the file: the game goes back to the CD's copy.
Open
- Whether the 3D-card builds use the same two roots (they share
open_data_file's strings; not checked). - What folder 11,
Scope\, holds. - A changed track's race paths and beacons in the game: geometry, textures and props load (above); the race data is still kept as bytes (
track/layout.rs).