Files
llamamod/notes/testlevel-chapters.md
T

7.9 KiB
Raw Blame History

TestLevel chapters: which ones are safe (2026-11-07)

World 9 ("TestLevel") has 49 chapter slots (table 0x572420), but the game's per-chapter arrays only have rows for worlds 0–8. Indexing them with world 9 runs into other globals. Code: save/TestLevelChapters (usable = chapter 1 and 32–49).

The arrays (index world·6 + chapter)

Array Where it is used
u32 coins 0x584F1C (coins << 12) levelRecord (strat var 0xBE) get/set, HUD (FUN_00426bf0), tally (FUN_0040dd60), endLevel (FUN_0054d620: "all coins" check), zeroed on every level start (FUN_0041cba0), KSV keeps 54 entries as u16
u8 flags 0x584EC8 (bit 0 collectible, bit 1 gold coin) collectible picked up (HUD, strat var 0xFA rising) → |=1; endLevel → |=2 when coins == static total 0x5722F4[world·5 + chapter] (garbage for world 9, often 0) or cheat 0x584710&8 → |=3; gold count FUN_0041cd70 (worlds 1–8 only)

Coin total table 0x5722F4 and the selection map only know worlds 1–8 (map shows garbage targets for world 9 – cosmetic).

What world-9 chapter c hits

Coin slot 0x584FF4 + 4c, flag byte 0x584EFE + c (Ghidra references 0x584EF0–0x585240):

c coin slot flag byte
0 0x584FF4 counter var 0xC0 (HUD) free
1 0x584FF8 free (only cleared by new game) free
2 0x584FFC counter var 0xBF (HUD icon 0x150) free
3 0x585000 HUD flag (FUN_00427c40) free
4 0x585004 var 0x80 free
5–8 0x585008–14 last played tribe/world/chapter/map (strat vars 0xAB–0xAE, KSV +0x3E–41) free
6–9 0x584F04 language index (u32; |=2 → text table out of range)
9 0x585018 furthest progress (world, chapter bytes; KSV +0x42/43)
10–13 0x58501C/20 free 0x584F08 option (KSV +0xD6)
12 0x585024 start of byte table (var 0x93, tribe 0)
14–21 0x585028–48 free 0x584F0C / 0x584F10 (var 0x5B)
22–25 0x58504C–58 table 0x585024, tribe 1 (read by var 0x93 in original levels) free
26–29 table, tribe 1 0x584F18 option (KSV +0xD8)
30–31 table, tribe 1 (world 8/9) coin slots of world 0 (never read)
32–49 table, tribes 2/3 (never used: tribe is 1, 4 only in the attract demo) coin slots of world 0

Verified in game (apptest :24, 2026-11-07)

  • Chapter 9 (arena test mod, 1 alligator, 2 coins collected): pause → "Beenden und zur Auswahlkarte" → the selection map shows world "°p", "Kapitel 0", coin target 14384: furthest progress 0x585018 was replaced by the coin counter (2 << 12 → world 0, chapter 32). chapter 9: broken map
  • Chapter 49 (same run): tally "Münzen gesammelt 2 / 12, Geheimnisse 0 / 0" (LNFO recounted), save written with progress (9, 50) – the map caps the cursor at chapter 49, no crash; map fine. chapter 49 tally
  • Before (arena on chapter 8, arena agent): tally "0 / 55": the collected value is the coin slot after endLevel copied the level ids into 0x585008–14 (chapter 8 = last played map = 0), the total is LNFO of the Village base.

Tally totals (LNFO)

Chunk LNFO = u32 coins, u32 secrets → 0x5FF070 / 0x5FF060 (FUN_0042AB10; the tally shows the values of the level just finished, kept in 0x5FF064 / 0x5FF06C). Originals: e.g. t1l3m004 40 = 20 Coin + 4 Coin5·5; levels with CoinLine/CoinCircle/enemies count those too. mods/levels/LevelTally recounts Coin (1) + Coin5 (5) and Secret strats for new level projects and the Test World (20 coins, 0 secrets); the arena adds its dynamic coins (alligator hit/death coins + 5 exit bonus).

Migration

  • Level projects: stored chapter 1 or 32–49 kept (first file wins), others get the first free of 32–49; more than 18 projects share chapters (only one of them can be on – apply error otherwise). Play-tests/exports of an old project: migrate (2..19 → 32..49, 20..31 → 32..43).
  • The chapter options are choices (level: 1, 32–49; arena: 32–49, default 49); a stored old value is invalid and falls back to the default (ModOptions).
  • Editor: New level / Level settings spinners only offer usable chapters. The empty template (Templates.makeEmpty) defaults to 32; New level and CLI --editor-new take the first chapter from 32 no project in the levels folder uses (LevelProjects.nextFreeChapter).

chapter option

Fix: base exe patch relocates both arrays (ExePatcher.REVISION 2, 2026-11-07)

ExePatcher.relocateLevelRecords (part of the always-applied base patch: ExePatcher.patch for the installer, ExePatcher.changes replayed as owner "base" in ModManager.baseExe) moves both arrays to a 128-entry buffer (index ≤ 9·6 + 49 = 103) in the slack after .data's VirtualSize: .data ends at 0x6DB935, the page at 0x6DC000 (.idata follows), so coins u32[128] at 0x6DB940, flags u8[128] at 0x6DBB40 (zero-filled and RW, mapped with .data's last page; no new section, no code). Every absolute address of the arrays in .text is rewritten (whole-file scan of 0x584EC8–0x584EFE / 0x584F1C–0x584FF4: no other references, none in .data):

Old New Sites (operand VA)
0x584F1C coins 0x6DB940 tally 0x40DE67, KSV load 0x41C72A / save 0x41C8FB, new game 0x41CA6B, level start 0x41CBCC, HUD 0x426AC2 / 0x426D43 / 0x426E08, endLevel 0x54D6BA, levelRecord ptr 0x550BC5, 0x55223E
0x584FF4 coin loop end (cmp eax, imm; jl) 0x6DBA18 0x41C74B, 0x41C91A (0x584FF4 itself is also counter var 0xC0 – those uses stay)
0x584EC8 flags 0x6DBB40 tally 0x40DEA1 / 0x40DEC9, KSV 0x41C65C / 0x41C832, new game 0x41CA64 (rep stosd 54 bytes), collectible 0x42706B, endLevel 0x54D6E3 / 0x54D708 / 0x54D70F
0x584ECE flags from world 1 0x6DBB46 map icons 0x41B724 / 0x41B79C / 0x41B7EE / 0x54A75E, gold count 0x41CD7B
0x584EFE flag loop end 0x6DBB76 KSV 0x41C6D6 / 0x41C8AF, gold count 0x41CD9B

Worlds 0–8 behave as before (KSV keeps the first 54 entries; the rest is never saved). World-9 flags are not cleared by "new game" (only the 54 saved bytes are) – they only affect the map's collectible/gold icons of TestLevel chapters within one game session. If the counts of these references differ (other exe build) the relocation is skipped as a whole. Test: ExePatcherTest.

Effect: every world-9 chapter is harmless in LlamaMod's groove.exe, so the remaining holes are closed without restricting the map: picking an empty (linked) chapter like 9 on the world map, chapters 2–31 that unlock together with 32+, and Boot to level on chapters 2–31 (its option keeps 1–49). Not linking empty chapters is no option: a missing WAD crashes the map ("Couldnt load wad", ModManager.world9Fixup). Mod levels still only use 1 and 32–49 (no migration churn; also safe with an exe that has not been rebuilt yet after the update).

Verified in game (apptest :24, 2026-11-07):

  • A/B, Test World on, map cursor world 9 chapter 9 (the linked empty chapter), start, pause → quit to the map: old jar (base REVISION 1) → map "°p / Kapitel 0 / 14384" (progress zeroed by the level start alone); new → "TestLevel / Kapitel 9 / 237" (237 = garbage static coin total, cosmetic). chapter 9 before/after
  • Arena (test mod) on chapter 9, 2 coins collected (HUD 2 / 12), quit to the map → map fine.
  • Save with world-1 chapter 1 flags = 3 and gold count 2: slot menu "Goldmünzen 2", the map shows the collectible and gold icons of Village chapter 1 (KSV load loop + map reads relocated).
  • Not verified in game: the KSV save loop after finishing a level (same patch shape as the load loop, covered by the unit test).

Before the fix

  • The map let the player pick any unlocked world-9 chapter; empty ones are linked to an installed world-9 WAD (ModManager), so playing e.g. chapter 9 from the map still clobbered progress. Unlocking chapter 32+ unlocks 2–31 on the map too. Fixed by the relocation above.