From 6fdcaf37407f4e4d9cd14784a4b8c13a7d4e99b4 Mon Sep 17 00:00:00 2001 From: Christopher Williams Date: Thu, 24 Sep 2026 09:40:40 -0400 Subject: [PATCH] =?UTF-8?q?phase11:=20merge=2016=20+=20cookbook=2083=20?= =?UTF-8?q?=E2=80=94=20525=20bodies=20/=20534=20regions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Worker C's 0x800320D8 (276 B), matched on the FIRST spelling where its sibling 0x80031FC4 took 5 -- the family lever measured, on one family, both ways. Finding 55's limit confirmed on the same family: a third row calling the same callee is NOT the same body and sits at +16 instructions. The family transfers the derivation method and the stable positions, never the body. Also recorded: an OR nested inside an && chain is observable from the branch DIRECTIONS -- bne to the call block on one test and bnez to the manual-copy block on the other is if (x == 0 && (a != 6 || b == 0)) call; else manual; --- config/regions.tsv | 1 + docs/MATCHING_COOKBOOK.md | 22 ++++++++++++++ src/func_8004857C.c | 63 +++++++++++++++++++++++++++++++++++++++ src/func_800B255C.c | 59 ++++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 src/func_8004857C.c create mode 100644 src/func_800B255C.c diff --git a/config/regions.tsv b/config/regions.tsv index 0799abc..6eeae88 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -141,6 +141,7 @@ 0x80031F2C 0x80031F78 src/func_80031F2C.c 0x80031F78 0x80031FC4 src/func_80031F78.c 0x80031FC4 0x800320D8 src/func_80031FC4.c +0x800320D8 0x800321EC src/func_800320D8.c 0x800321EC 0x800321F8 src/func_800321EC.c 0x80034A80 0x80034ABC src/func_80034A80.c 0x800354F8 0x80035548 src/func_800354F8.c diff --git a/docs/MATCHING_COOKBOOK.md b/docs/MATCHING_COOKBOOK.md index f1d92c5..b97d800 100644 --- a/docs/MATCHING_COOKBOOK.md +++ b/docs/MATCHING_COOKBOOK.md @@ -1331,3 +1331,25 @@ because it is used twice (`s1 = r + 6`), and there it lands in a0 in both compil **Rule: name a result only where the original REUSES it. Where the original consumes it immediately, leave the expression inline.** Do not apply the decision once per function — apply it once per value. + +### 83. The family lever, MEASURED — and its limit on the same family (worker C) + +Finding 55 said the family transfers the derivation method, never the body. Worker C has now +measured both halves on one family. + +**The lever working.** `0x800320D8` is the sibling of `0x80031FC4` (callee `0x80031F78` vs +`0x80031F2C`). Only the object offsets changed — the two flags stay at `+976`/`+2272` while the +two 4-word blocks move to `+1032`/`+2328` (48 bytes later). The body, the local 3x4 matrix, both +branch polarities and the epilogue are identical, and the match came on the **FIRST spelling**. +Contrast the original: **5 attempts**. + +**The limit, on the same family.** A third row calling `0x80031F78` — `0x80033DC8` (360 B) — is +**not the same body** at all: it is a matrix add (fill A from one block, add a global vector, fill +B from another block or a call, subtract), and it is a near-match at +16 instructions. +**The family transfers the derivation method and the stable positions — never the body.** + +**Bonus tell from the row that did not close: an OR nested inside an `&&` chain is observable +from the branch DIRECTIONS.** The original does `bne v1,v0` **to the call block** when +`f308 != 6` and `bnez` **to the manual-copy block** when `f312 != 0`, with the call inline — +which is `if (f4762 == 0 && (f308 != 6 || f312 == 0)) call; else manual;`. Branch direction +distinguishes `&&` from `||` without guessing at the operator. diff --git a/src/func_8004857C.c b/src/func_8004857C.c new file mode 100644 index 0000000..23d5468 --- /dev/null +++ b/src/func_8004857C.c @@ -0,0 +1,63 @@ +/* + * func_8004857C — 100 bytes at 0x8004857C..0x800485E0 + * + * Byte-identical reconstruction of a one-shot initialiser guarded by a + * byte-valued global: the first call sets up two static descriptors, publishes + * the guard byte, and raises a fixed event through a fourth callee. + * + * The observed instructions are: + * lui v0,0x8012 \ if (D_80122438) return; + * lbu v0,9272(v0) | + * addiu sp,sp,-24 | + * bnez v0,0x800485D0 | + * sw ra,16(sp) / (delay slot) + * lui a0,0x8011 \ func_80046348(&D_80116B90); + * addiu a0,a0,27536 | + * jal 0x80046348 | + * nop / + * lui a0,0x8011 \ func_80046348(&D_80116BD0); + * addiu a0,a0,27600 | + * jal 0x80046348 | + * nop / + * li v0,1 \ D_80122438 = 1; + * lui at,0x8012 | + * sb v0,9272(at) / + * move a0,zero \ func_8008FA58(0, 35, 0, 0); + * li a1,35 | + * move a2,zero | + * jal 0x8008FA58 | + * move a3,zero / (delay slot) + * lw ra,16(sp) + * addiu sp,sp,24 + * jr ra + * nop + * + * The guard is read **before** the frame is set up (`lbu` precedes + * `addiu sp,sp,-24`), which is why the early `return` costs nothing: the frame is + * only allocated when the body will run. The guard is written with `sb` through + * `$at` (a symbol store, cookbook finding 3) while the read folds the constant + * address into the displacement (cookbook finding 18) — the same global accessed + * both ways, per finding 16. + * + * LIMITS: names are address placeholders and every type is inferred from + * register usage alone; only the compiled bytes are evidence. The guard is + * modelled `char` because it is read with `lbu` and written with `sb`. The two + * descriptor symbols are passed only by address, so their contents are + * unobservable, and `func_8008FA58`'s four arguments are modelled from the + * registers the call site actually sets. + */ + +extern char D_80122438; +extern char D_80116B90; +extern char D_80116BD0; +extern void func_80046348(void *); +extern void func_8008FA58(int, int, int, int); + +void func_8004857C(void) { + if (D_80122438) + return; + func_80046348(&D_80116B90); + func_80046348(&D_80116BD0); + D_80122438 = 1; + func_8008FA58(0, 35, 0, 0); +} diff --git a/src/func_800B255C.c b/src/func_800B255C.c new file mode 100644 index 0000000..2cc3c38 --- /dev/null +++ b/src/func_800B255C.c @@ -0,0 +1,59 @@ +/* + * func_800B255C — 92 bytes at 0x800B255C..0x800B25B8 + * + * Byte-identical reconstruction of a framed two-conversion helper: it expands + * each of its two halfword arguments into its own 16-byte stack buffer, then + * combines the two buffers through a third callee and returns the word that + * callee wrote into a third stack slot. + * + * The observed instructions are: + * addiu sp,sp,-64 + * sw s0,56(sp) + * move s0,a1 the second argument survives the first call + * sll a0,a0,0x10 \ a0 = (short)a0 + * sra a0,a0,0x10 / + * sw ra,60(sp) + * jal 0x80044FA4 \ func_80044FA4((short)a0, b1); + * addiu a1,sp,16 / (delay slot) b1 is 16 bytes at sp+16 + * sll s0,s0,0x10 \ a0 = (short)a1 + * sra a0,s0,0x10 / + * addiu s0,sp,32 s0 = b2 (16 bytes at sp+32) + * jal 0x80044FA4 \ func_80044FA4((short)a1, b2); + * move a1,s0 / (delay slot) + * addiu a0,sp,16 \ func_80027D88(b1, b2, &out); + * move a1,s0 | + * jal 0x80027D88 | + * addiu a2,sp,48 / (delay slot) out is at sp+48 + * lw v0,48(sp) return out; + * lw ra,60(sp) + * lw s0,56(sp) + * addiu sp,sp,64 + * jr ra + * nop + * + * Both parameters are **narrowed explicitly** with `sll`/`sra`, so they are + * `short` (or narrower) rather than `int` — an `int` parameter would be passed + * through untouched. The frame is 64 bytes: two 16-byte buffers at sp+16 and + * sp+32, the result word at sp+48, and the saved `s0`/`ra` above them. + * + * LIMITS: names are address placeholders and every type is inferred from + * register usage alone; only the compiled bytes are evidence. The buffers are + * modelled as `char[16]` because they are only ever passed by pointer — their + * contents and element type are unobservable. The third callee's third parameter + * is modelled `int *` from the address the call site passes, and its result is + * only observed through the stack word at sp+48. + */ + +extern void func_80044FA4(short, char *); +extern void func_80027D88(char *, char *, int *); + +int func_800B255C(short a0, short a1) { + char b1[16]; + char b2[16]; + int out; + + func_80044FA4(a0, b1); + func_80044FA4(a1, b2); + func_80027D88(b1, b2, &out); + return out; +}