diff --git a/docs/re/structures/savegame-format.md b/docs/re/structures/savegame-format.md index 8a0382d..9b40dbc 100644 --- a/docs/re/structures/savegame-format.md +++ b/docs/re/structures/savegame-format.md @@ -298,35 +298,38 @@ What the title does with an edited save is itself a finding: on load it can now develop …"). So the blob's `4`s are authoritative state and its `2`s are not — see the economy note. -### What the probes then refuted: +36 / +52 / +56 are **not** stage or difficulty +### What the probes then refuted: the whole GHAD block is **not** stage or difficulty The three fields that all hold `2` were read as "difficulty or stage, undecidable from one save" from the day the format was parsed. Writing saves makes it -decidable, and the answer is **neither**. +decidable, and the answer is **neither** — and the sweep did not stop there. Method — [`boot_menu.sh`](../../../tools/re-capture/boot_menu.sh) boots to the title menu **without loading anything**, and LOAD GAME's slot list renders each slot's Details panel (`STAGE`, `Game Status`, `Points`, `Times Cleared`) plus a -row (date, `Difficulty`, flight time, clear ratio) straight out of that slot's -payload. Extra slots can be fabricated — copy the save directory and its -`Headers/…/gameNN.header`, patching the UTF-16BE display string and the ASCII -`gameNN` inside it — so **four probes fit in one boot**, read-only, nothing -loaded. +row (date, `Difficulty`, flight time, clear ratio). Extra slots can be fabricated +— copy the save directory and its `Headers/…/gameNN.header`, patching the +UTF-16BE display string and the ASCII `gameNN` inside it — so **five probes fit +in one boot**, read-only, nothing loaded. -Eleven candidate fields were written and read back: `+36` at 1, 3 and 9; `+52` -and `+56` at 1 and 9; `+0`, `+16`, `+32`, `+48`, `+28`, and `SHAB[0].a` — every -one of them left the panel at `STAGE 02 / Declaration of War`, `Difficulty EASY`, -`At Standby`, `Times Cleared 0`. +**Probed, all with no effect on `STAGE 02` or `Difficulty EASY`:** every scalar in +the GHAD block — `+0`, `+12`, `+16`, `+20`, `+28`, `+32`, `+36` (at 1, 3 and 9), +`+40` (u64), `+48`, `+52`, `+56`, `+60`, `+64` (raw) — plus `SHAB[0].a`, plus the +`SHAB` **fill count** in both directions (record 1 filled with a copy of record 0; +record 0 cleared). Sixteen elements. -The negative is meaningful because the panel demonstrably *does* read each -payload: slot 02 shows `Clear Ratio 5 %` against the others' `6 %`, and `Points` -tracked `+24` exactly. Two further controls: patching a slot's **header** string -to `STAGE09 HARD` changed nothing, so the display is payload-driven, not header -text; and the row's date follows the **container FILETIME**, which is why every -fabricated slot showed 18:04. +The panel genuinely re-reads each slot — the control is slot 02, which holds +`Points 4101` and `Clear Ratio 5 %` and displays exactly that while its +neighbours show `101` and `6 %`. A further control: patching a slot's **header** +string to `STAGE09 HARD` changed nothing, and the row's date follows the +**container FILETIME**, so the row is payload-formatted, not header text. -So stage and difficulty live in one of the remaining unprobed fields — `+12`, -`+20`, `+40` (u64), `+60`, `+64` (raw 4) — or in the phase string. That is the -next probe round, and it is now cheap. +**What is left**, and it is a much narrower set than when this started: the +**phase string** (`GP_BUNK`), the trailer (`"BUNK"` + `0x09150000`), or the blob. +One honest caveat on the negative: **every save on disc is genuinely Stage 02 / +EASY**, so "the field was not found" and "the panel does not vary those two +labels per slot" are not yet separated. The next move is not another probe — it +is to find the code that formats `STAGE %02d` and read which offset it loads, +which the static DB can answer directly. Editing beyond a throwaway slot is still the user's call. diff --git a/tools/re-capture/savegame_edit.py b/tools/re-capture/savegame_edit.py index 0e77236..caf458b 100755 --- a/tools/re-capture/savegame_edit.py +++ b/tools/re-capture/savegame_edit.py @@ -77,7 +77,11 @@ def main(): blob[int(k)] = int(v) elif a == "--set": k, v = args[i + 1].split("=") - parsed["ghad"][k] = int(v, 0) + # `raw_*` fields are stored as bytes; accept an int and pack it BE. + old = parsed["ghad"][k] + parsed["ghad"][k] = ( + int(v, 0).to_bytes(len(old), "big") if isinstance(old, bytes) else int(v, 0) + ) elif a == "--slot": # --slot ,=, e.g. --slot 0,0=3 where, v = args[i + 1].split("=")