From f490fecefb18eb9d8a03156ee4b2a7f7486d979f Mon Sep 17 00:00:00 2001 From: "Claude (auto-RE)" Date: Thu, 13 Aug 2026 20:06:11 +0000 Subject: [PATCH] re(challenge): both gate words are one ~1880-byte record, and it is not the save Two results, one of which kills an operational hope I had been carrying. THE RECORD IS MUCH BIGGER THAN I MEASURED. Reading 0x82175110 to the end: after the two words, the 8-byte pair, the 184-byte memcpy and the 8 words at +200, it copies 816 bytes at +232, 816 more at +1048, a sub-object at +1864 and a final word at +1876. So the record spans +0..~+1880 = singleton +80..+1960, which means word A is record +0 AND WORD B IS RECORD +1876. That retires last commit's "word B has no known writer": there is no separate store because the whole record is copied out, modified and assigned back as a unit (0x8216FF70 -> compare 0x822C3708, assign 0x82170650, then a worker 0x821700A8 -- note 168 decimal, not hex, which I misread first time -- retrying a commit 0x822C33B8 up to five times). IT IS NOT THE SAVEGAME, by two independent checks: - size: every real save on disk is a 276-byte container deflating to 545 bytes; the record is ~1880. It does not fit. - files: after many sessions the content tree holds only game0N/savedata and game0N/__thumbnail.png per slot plus three Headers/*.header. No second data file exists anywhere under the title id. Also negative: the savegame object is *(*(this+4)) + 304, and the singleton's holder address 0x828F48B0 is referenced NOWHERE outside the accessor, so this+4 is a different holder -- the save block is not a window into this record. => Hand-editing a save cannot unlock the challenge missions. The earlier savegame-editing win does not extend here. WHAT WOULD WORK. The singleton is a static object at 0x828F4070 (0x8216F650: addis 0x828F + addi 16496), so the gate words sit at fixed guest addresses with no scanning: word A = 0x828F40C0, word B = 0x828F4814. Canary maps guest RAM into /dev/shm and the project already reads it live, so writing 0xFFFF / 0x3F there while the title sits on a menu should open all six challenge missions without playing the campaign -- the route to the last 42 EX units. Untested; needs a run. --- docs/re/challenge-mission-gate.md | 67 +++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/docs/re/challenge-mission-gate.md b/docs/re/challenge-mission-gate.md index cfa6f7d..d20c3b3 100644 --- a/docs/re/challenge-mission-gate.md +++ b/docs/re/challenge-mission-gate.md @@ -2,8 +2,9 @@ **Status:** โœ… for the static structure (stage set, GamePart ids, the config-section switch) and for the **unlock mechanism** (a bit test against a *cleared-stage* mask, -whose sole writer is `GamePart_StageClear`); ๐ŸŸก for the crash mechanism, for word B's -writer, and for which mission consumes which bit. +whose sole writer is `GamePart_StageClear`, in a ~1 880-byte progress record that is +**not** the savegame); ๐ŸŸก for the crash mechanism, for word B's writer, and for which +mission consumes which bit. **Method:** static only โ€” `.pe` string/pointer analysis + DuckDB disassembly + disc records. No emulator run, no gamepad input. **Evidence:** [`captures/challenge-map.txt`](captures/challenge-map.txt), @@ -232,18 +233,58 @@ via `hash::TOC_NAME_SCHEMES`. ### 5.4 Where the mask lives, and one lead refuted -Word A is **word 0 of a 232-byte progress struct at singleton `+80`** (`0x82175110` -copies it out: two words, an 8-byte pair, a 184-byte `memcpy`, then 8 words from -`+200`). `0x8216FF70` compares, assigns, and then spawns a worker (`0x82170168`) under -a lock โ€” i.e. the struct is **persisted asynchronously**. `GamePart_Debriefing` uses -the same get/modify/set on it to accumulate saturating career counters -(`+200`โ€ฆ`+224`, the last a 64-bit total), which are exactly the quantities the -achievement requirements test. +### 5.5 Both gate words are one record โ€” and it is **not** the savegame โœ… -So a **hand-written progress record with the stage bits set would unlock the challenge -missions**, and the last 42 units of the Route-B harvest become one run instead of an -unreachable menu โ€” *if* this struct is what the savegame carries. That is the open -question. +`0x82175110` copies the record at singleton `+80` in full: two words, an 8-byte pair, a +184-byte `memcpy`, 8 words from `+200`, then **816 bytes at `+232`, 816 more at +`+1048`**, a sub-object at `+1864`, and a final word at **`+1876`**. So the record runs +`+0 โ€ฆ ~+1880`, i.e. singleton `+80 โ€ฆ +1960` โ€” which means + +- **word A** is record `+0` (singleton `+80`), and +- **word B is record `+1876`** (singleton `+1956`). + +That answers ยง5.3's "word B has no known writer": there is no separate `stw` because +the whole record is copied out, modified and assigned back as a unit +(`0x8216FF70` โ†’ compare `0x822C3708`, assign `0x82170650`, then spawn a worker +`0x821700A8` under a lock which retries a commit `0x822C33B8` up to five times). +`GamePart_Debriefing` uses the same get/modify/set to accumulate saturating career +counters at `+200`โ€ฆ`+224` (the last 64-bit) โ€” the quantities the achievement +requirements test. + +**And the record is not what `savedata` holds.** Two independent checks: + +- **Size.** Every real save on disk is a **276-byte** container deflating to a + **545-byte** payload. The progress record is **~1 880 bytes**. It does not fit. +- **Files.** After many sessions the game's content tree holds only + `game0N/savedata` + `game0N/__thumbnail.png` per slot and the three + `Headers/*.header` โ€” no second data file anywhere. + +Also checked and negative: the savegame object is `*(*(this+4)) + 304`, and the +singleton's holder address (`0x828F48B0`) is referenced **nowhere but inside the +accessor itself**, so `this+4` is a different holder โ€” the save block is not a window +into this record. + +โš ๏ธ **So hand-editing a save cannot unlock the challenge missions.** That kills the +operational hope this section previously carried; the earlier savegame-editing win +does not extend here. + +### 5.6 What *would* work โ€” static addresses for a live write + +The singleton is a **static object at `0x828F4070`** (`0x8216F650`: +`addis 0x828F` + `addi โ€ฆ, 16496`), with the holder at `0x828F48B0` pointing at it. So +the two gate words are at fixed guest addresses, no scanning required: + +| word | guest VA | meaning | +|---|---|---| +| A | **`0x828F40C0`** | cleared stages, bit = stage id (`< 24`) | +| B | **`0x828F4814`** | cleared stages, bit = stage id โˆ’ 24 (challenge 24โ€“29) | + +Canary maps guest RAM into `/dev/shm`, which the project already reads live +([`tools/re-capture/gmem.py`](../../tools/re-capture/gmem.py)). Writing +`0xFFFF` into word A and `0x3F` into word B while the title sits on a menu should +open all six challenge missions **without playing the campaign** โ€” and that is the +route to the last 42 `*_EX4`/`*_EX5` units. Untested: it needs a run, and the run +needs gamepad input to reach the menu. **The obvious lead is dead.** The three `stw`s to `+1956` in `0x822AF278` / `sub_822C8748` looked promising because they sit in the save serializer's code region