From e1482abd755b54a223e7806f3225f5ba294a53a9 Mon Sep 17 00:00:00 2001 From: ReaperOfVeriod Date: Wed, 14 Oct 2026 05:47:29 +0200 Subject: [PATCH] Correct my own decode bug: there is a caller, and r2 is a return value 349b498 reported that 0x3a7c30 has no jal callers. That was wrong and the bug was mine: the scan built J-type targets as ((instr & 0x03ffffff) << 2) | 0x0c000000 without masking to the upper nibble, adding 0x0c000000 to every target so nothing matched. Corrected: exactly one jal caller, at 0x3a3d84. Still no j tail-calls, still not a vtbl handler, still not a pointer in any level-0 data file. Tracing back, r2 is never written between the call and the use: 0x3a3ce4 lw r3, 0x1500(r2) 0x3a3d70 jal 0x2ee178 0x3a3d84 jal 0x3a7c30 so r2 is the return value of 0x2ee178, passed through as the first argument. The chain is now 0x2ee178 returns a pointer -> 0x3a7c30 -> 0x3a8060 -> addiu r17, r18, 0xc0. Next question is one function: does 0x2ee178 return core_data + moby_classes.offset? If so the reading is confirmed and the loader is located; if not, the 0xc0 in 0x3a8060 is an unrelated struct member. Also recorded in docs: a negative from a hand-rolled decoder is worth nothing until checked against a known case. jal 0x3a8060 decoded correctly under a different constant in the same probe and I did not notice. 284 passing with the disc present, clippy clean. --- docs/plan/level-assets.md | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/plan/level-assets.md b/docs/plan/level-assets.md index 4cc4ce6..8b45738 100644 --- a/docs/plan/level-assets.md +++ b/docs/plan/level-assets.md @@ -2268,3 +2268,49 @@ Not distinguished. The immediate question for anyone continuing is whether `? -> 0x3a7c30 (no static caller) -> 0x3a8060 -> addiu r17, r18, 0xc0`, with `r2` supplied by whatever reaches `0x3a7c30`. Every remaining question is about control flow and register provenance, not about counts. + +### Correction to 349b498: there is a caller, and I had a decode bug + +`349b498` reported "0x3a7c30 has no `jal` callers anywhere in level-0 `.text`". +**That was wrong, and the bug was mine.** The scan built the branch target as + + ((instr & 0x03ff_ffff) << 2) | 0x0c00_0000 + +without masking to the upper four bits. J-type targets take their top nibble from +the opcode field, which is zero here, so the correct form is just +`(instr & 0x03ffffff) << 2`. The un-masked constant added `0x0c000000` to every +target, so the comparison never matched anything. + +Corrected scan: **`0x3a7c30` has exactly one `jal` caller, at `0x3a3d84`.** No +`j` tail-calls, and the function is still not a `lvl.vtbl` handler and still not a +pointer in any level-0 data file. + +### r2 is a return value, not a caller argument + +Tracing backwards from `0x3a3d84` inside the calling function: + + 0x3a3ce4 lw r3, 0x1500(r2) r2 already live, used as a base + 0x3a3d70 jal 0x2ee178 + 0x3a3d84 jal 0x3a7c30 r2 is v0, i.e. the *result* of the call above + +No instruction between the two writes `r2`. It is simply the return value of the +function at `0x2ee178`, passed straight through as the first argument to +`0x3a7c30`. + +So the chain is + + 0x2ee178 --returns a pointer--> 0x3a7c30 --passes it in r2--> + 0x3a8060 --addiu r17, r18, 0xc0--> 16-byte-stride walk + +**The next question is now one function:** does `0x2ee178` return +`core_data + moby_classes.offset`? It is a small, self-contained function and it is +the only thing between the program and the constant. If it returns a pointer built +with `addiu r2, base, 0xc0`, the reading is confirmed and the moby class loader +has been located. If it returns something else, the `0xc0` in `0x3a8060` is an +unrelated struct member. + +**Recorded as the lesson it is:** a negative result from a hand-rolled decoder is +worth nothing until the decoder is checked against a case where the answer is +already known. `jal 0x3a8060` decoded correctly under a different constant in the +same probe and I did not notice, because one probe reporting a plausible number is +as convincing as one reporting zero.