diff --git a/docs/re/structures/savegame-format.md b/docs/re/structures/savegame-format.md index 18c6a7c..1d99da8 100644 --- a/docs/re/structures/savegame-format.md +++ b/docs/re/structures/savegame-format.md @@ -332,6 +332,41 @@ labels per slot" are not yet separated. The next move is not another probe — i is to find the code that formats `STAGE %02d` and read which offset it loads, which the static DB can answer directly. +### The read side, and where the save state lives in memory + +The serializer at `0x822C00E8` has a mirror, found by disassembling around it — +and the call graph names the whole read path: + +| function | role | pairs with | +|---|---|---| +| `0x822C0380` | **deserializer** — reads `GDAA`, the phase string, then calls the two below | `0x822C00E8` | +| `0x822BF858` | **GHAD reader**, called with `save+8` | `0x822BF678` | +| `0x822BFD60` | **`SHAB` table reader**, called with `save+136`, count 16 | — | + +The GHAD reader is a plain byte-for-byte copy — unrolled per field, no clamping +or validation anywhere in it, which is why a hand-written value survives into the +object exactly as written (and why the develop-blob probes worked). + +**The save state lives at offset `+304` of a game object.** The deserializer's +only caller sets it up as `lwz r11,4(r30)` → `lwz r11,0(r11)` → `addi r4,r11,304` +(`0x82290594`–`0x8229059c`). Combined with the layout above, every field has a +fixed absolute offset in that object, which is what a live-RAM read needs: + +``` +save base = obj + 304 GHAD block = obj + 312 +flight time = obj + 316 clear ratio = obj + 320 +Points = obj + 336 develop blob = obj + 380 +SHAB table = obj + 440 (obj + 304 + 136) +``` + +**What this did not do, and why:** finding the panel's populate code by searching +for those absolute offsets does not work — the address space is dense enough that +`lwz rX, 336(rY)` has 193 matches, and the windows that contain several of the +right offsets at once are false positives: one is a **vtable thunk table** whose +slots happen to sit at 308–340, another is a float-parameter block. Naming the +stage and difficulty fields needs the save-screen's populate path reached from +the screen side (its config record and sprite-key lookup), not from the offsets. + ### What the screen itself is built from (static, `.rdata`) The LOAD/SAVE screen's config **key list is compiled into the executable**, as a