From 1c21a00f7cfcc68e48d08a68181c1ba4619dd140 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Thu, 27 Aug 2026 07:38:24 +0000 Subject: [PATCH] re: two trigger appenders -- built-in 19's node carries the handler routine Resolves the puzzle the previous commit left open: payload+28 is a 1-12 index, yet the drain hands it to a spawner that wants a code offset. The cause is that built-in 25 is not the only thing that appends a trigger node. Searching for functions that write a double at +16 AND a word at +28 of the same register finds exactly two: sub_8226A0D8 (built-in 25, vtable slot 28) which fills the payload inline, and sub_8226E160, a free-list recycling variant whose sole caller sub_82269ED0 is vtable slot 68 = BUILT-IN 19. Built-in 19 passes local[4] -> node+0 (the unit), local[12] -> +4, local[24] -> +16 as a double, and local[32] -> +28. That last one is the field the drain spawns from, and it is a genuine code offset: over all 79 call sites disc-wide land on the instruction stream 79 / 79 = 100.0% control, random 4-aligned offsets 27.8% values 12164 .. 111080, 73 distinct Large, distinct, and every one resolves against a control that resolves barely a quarter of the time. So built-in 19 registers a trigger whose handler is a routine, and the drain's `base + payload+28` spawn is that handler. The two builders simply put different things in the same slot. Observed alongside: built-in 25 writes +24 = 1 and built-in 19 writes +24 = 0, which is kind-tag shaped -- but the drain was not shown branching on it, so that is recorded as an observation and not a reading. All artefacts regenerate byte-identical; documentation only. Still open: whether +24 selects between the node kinds, and this STILL does not explain the unreached code -- 0 of the 79 handler offsets are unreached run-starts. --- docs/re/BACKLOG.md | 16 ++++++++ docs/re/structures/isl-trigger-node.md | 52 ++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index d9e7144c..36a33c52 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -119,6 +119,22 @@ unknown, what evidence exists, and what the first step would be. Move an item in earlier. The drain does operate on the container — what `sub_8226E458` does to it stays unread. 🟡 What starts the ~15 % of unreached code: still open, not this. +* ✅ **(2026-08-27) RESOLVED — TWO trigger appenders, and built-in 19's carries the + handler routine. [structures/isl-trigger-node](structures/isl-trigger-node.md).** + Last iteration's puzzle (`payload+28` is a 1–12 index, yet the drain spawns + `base + payload+28`) had a simple cause: **built-in 25 is not the only appender**. + Searching for functions writing a double at `+16` AND a word at `+28` of the same + register finds exactly two — `sub_8226A0D8` (built-in 25, slot 28) and + `sub_8226E160`, whose sole caller `sub_82269ED0` is **vtable slot 68 = + built-in 19**. Built-in 19 passes `local[4]`→+0, `local[12]`→+4, `local[24]`→+16 + (double), **`local[32]`→+28**. 🔑 **Those ARE code offsets: 79/79 land on the + instruction stream vs a 27.8 % control**, values 12 164–111 080, 73 distinct. So + **built-in 19 registers a trigger whose handler is a routine**, and the drain's + spawn is that handler. Observed: built-in 25 writes `+24 = 1`, built-in 19 writes + `+24 = 0` — kind-tag shaped, 🟡 but the drain was not shown branching on it. + 🟡 **Still does not explain the unreached code** — 0 of the 79 handler offsets are + unreached run-starts. + ## ✅✅ SOLVED — the mission freeze was a modal sign-in dialog (2026-08-26) `XamShowSigninUI` opens a modal dialog and `xeXamDispatchDialog` blocks the diff --git a/docs/re/structures/isl-trigger-node.md b/docs/re/structures/isl-trigger-node.md index 9bcd5230..bda4ffb1 100644 --- a/docs/re/structures/isl-trigger-node.md +++ b/docs/re/structures/isl-trigger-node.md @@ -85,13 +85,59 @@ argument. `sub_8226D740` does operate on the trigger container; what `sub_8226E458` does *to* it is still unread, so only the argument is corrected here, not the conclusion. +## ✅ RESOLVED: there are TWO appenders, and only one carries a routine + +The puzzle above — `payload+28` holding a 1–12 index while the drain hands it to +a spawner that wants a code offset — had a simple cause: **built-in 25 is not the +only thing that appends a trigger node.** + +Searching for functions that write a **double at +16** and a **word at +28** of +the same register finds exactly two: + +| builder | how | +|---|---| +| `sub_8226A0D8` — **built-in 25**, vtable slot 28 | fills the payload inline | +| `sub_8226E160` | a free-list recycling variant, called from `sub_82269ED0` | + +`sub_82269ED0` is **vtable slot 68 = built-in 19**, and it calls the builder as + +``` +8226A034 addi r3, r28, 272 ; the trigger container +8226A040 lwz r4, 4(r30) ; local[4] -> node+0 (the unit) +8226A03C lwz r5, 12(r30) ; local[12] -> node+4 +8226A038 lfd f1, 24(r30) ; local[24] -> node+16 (double) +8226A030 lwz r8, 32(r30) ; local[32] -> node+28 +``` + +### ✅ Built-in 19's `local[32]` IS a code offset + +| over all 79 call sites disc-wide | | +|---|---| +| land on the instruction stream | **79 / 79 = 100.0 %** | +| control, random 4-aligned offsets | 27.8 % | +| value range | 12 164 … 111 080, **73 distinct** | + +Large, distinct, and every one resolves — against a control that resolves barely +a quarter of the time. So **built-in 19 registers a trigger whose handler is a +routine**, and the drain's `base + payload+28` spawn is exactly that handler. + +Built-in 25's `payload+28` really is a small index; the two builders simply put +different things in the same slot. Observed alongside it: built-in 25 writes +**`+24 = 1`** and built-in 19 writes **`+24 = 0`**, which is the shape of a kind +tag — 🟡 but the drain was not shown branching on it, so that is an observation, +not a reading. + ## 🟡 Not settled * **What `local[12]` indexes.** Values 1–12, and `SYM1_SLOTS` lists slot 12 for built-in 25 (symbol table 1 — routes, messages, objectives), which is consistent with a small index but was not verified here. -* **What the drain actually spawns.** `[stack+112]` is filled from `payload+28`, - yet the spawner takes a code offset — so some step in that chain is not what I - read. Left open rather than guessed. +* ~~What the drain actually spawns.~~ ✅ Resolved above: built-in 19's nodes carry + a real routine offset at `+28`. +* **Whether `+24` selects between the two node kinds** — 1 from built-in 25, 0 + from built-in 19 — is unshown; the drain reads it into a stack slot that the + spawn path does not visibly use. +* **This still does not explain the unreached code.** All 79 of built-in 19's + handler offsets are already reachable — **0 are unreached run-starts**. * **`sub_8226E458`'s effect** on the container. * **What starts the ~15 % of unreached code** — still open, and this was not it.