From c160fa9326bc7351334fbd99668fbb978c7758aa Mon Sep 17 00:00:00 2001 From: Christopher Williams Date: Thu, 24 Sep 2026 00:32:22 -0400 Subject: [PATCH] =?UTF-8?q?phase9:=20merge=206=20more=20=E2=80=94=20281=20?= =?UTF-8?q?regions=20/=20272=20distinct=20bodies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Worker A batch 9: if-conversion demo, pre-increment append, negate-magnitude, guarded call pairs, epilogue-order-variant NEGATIVE 0x80100334 (finding 11's '13 CRT sites' scoping corrected — this site is outside the CRT; recorded as a third epilogue class data point), sched-tiebreak 0x8002515C. New symbol D_80122160 (gp). Gate MATCH whole-binary SHA-1 e173426c157384ebf1b6caf8c6fea18a85a14af9. --- config/regions.tsv | 6 ++++++ config/symbols.tsv | 1 + src/func_8002C6EC.c | 46 +++++++++++++++++++++++++++++++++++++++++ src/func_80045388.c | 50 +++++++++++++++++++++++++++++++++++++++++++++ src/func_800734A4.c | 46 +++++++++++++++++++++++++++++++++++++++++ src/func_8009B464.c | 44 +++++++++++++++++++++++++++++++++++++++ src/func_800A9D58.c | 49 ++++++++++++++++++++++++++++++++++++++++++++ src/func_800F9344.c | 47 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 289 insertions(+) create mode 100644 src/func_8002C6EC.c create mode 100644 src/func_80045388.c create mode 100644 src/func_800734A4.c create mode 100644 src/func_8009B464.c create mode 100644 src/func_800A9D58.c create mode 100644 src/func_800F9344.c diff --git a/config/regions.tsv b/config/regions.tsv index 42420fa..5659e62 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -64,6 +64,7 @@ 0x800276B0 0x800276D4 src/func_800276B0.c 0x800290D0 0x800290F0 src/func_800290D0.c 0x800290F0 0x80029118 src/func_800290F0.c +0x8002C6EC 0x8002C728 src/func_8002C6EC.c 0x8002C7BC 0x8002C7EC src/func_8002C7BC.c 0x8002C888 0x8002C894 src/func_8002C888.c 0x8002C894 0x8002C8A4 src/func_8002C894.c @@ -95,6 +96,7 @@ 0x80042D64 0x80042D88 src/func_80042D64.c 0x80043D8C 0x80043DC4 src/func_80043D8C.c 0x80044F58 0x80044FA4 src/func_80044F58.c gp=-D_80121BFC +0x80045388 0x800453C0 src/func_80045388.c 0x800453C0 0x800453F8 src/func_800453C0.c 0x8004C060 0x8004C090 src/func_8004C060.c 0x8004C090 0x8004C0AC src/func_8004C090.c @@ -126,6 +128,7 @@ 0x8006FA78 0x8006FAA0 src/func_8006FA78.c 0x8007049C 0x800704BC src/func_8007049C.c 0x80072BA8 0x80072BDC src/func_80072BA8.c +0x800734A4 0x800734DC src/func_800734A4.c 0x8007A404 0x8007A428 src/func_8007A404.c 0x8007C4EC 0x8007C524 src/func_8007C4EC.c 0x8007DC40 0x8007DC4C src/func_8007DC40.c @@ -164,6 +167,7 @@ 0x80099A94 0x80099AE4 src/func_80099A94.c 0x80099E14 0x80099E34 src/func_80099E14.c 0x8009AC08 0x8009AC28 src/func_8009AC08.c +0x8009B464 0x8009B4A0 src/func_8009B464.c 0x8009D8A0 0x8009D8E0 src/func_8009D8A0.c 0x8009E8D0 0x8009E95C src/func_8009E8D0.c 0x8009F0E8 0x8009F120 src/func_8009F0E8.c @@ -172,6 +176,7 @@ 0x800A6268 0x800A6294 src/func_800A6268.c 0x800A74BC 0x800A74D0 src/func_800A74BC.c 0x800A8B48 0x800A8B8C src/func_800A8B48.c +0x800A9D58 0x800A9D90 src/func_800A9D58.c 0x800AA56C 0x800AA59C src/func_800AA56C.c 0x800AC818 0x800AC85C src/func_800AC818.c 0x800AC85C 0x800AC884 src/func_800AC85C.c @@ -222,6 +227,7 @@ 0x800F8F9C 0x800F8FC0 src/func_800F8F9C.c 0x800F8FE4 0x800F8FF8 src/func_800F8FE4.c maspsx=off 0x800F8FF8 0x800F9008 src/func_800F8FF8.c +0x800F9344 0x800F9380 src/func_800F9344.c 0x800FA960 0x800FA980 src/func_800FA960.c 0x800FB13C 0x800FB1BC src/func_800FB13C.c 0x800FB5D4 0x800FB5DC src/func_800FB5D4.c diff --git a/config/symbols.tsv b/config/symbols.tsv index 974d716..a936a7d 100644 --- a/config/symbols.tsv +++ b/config/symbols.tsv @@ -179,6 +179,7 @@ D_80122150 0x80122150 gp D_80122154 0x80122154 gp D_80122158 0x80122158 gp D_8012215C 0x8012215C gp +D_80122160 0x80122160 gp D_80122164 0x80122164 gp D_80122170 0x80122170 gp D_80122174 0x80122174 gp diff --git a/src/func_8002C6EC.c b/src/func_8002C6EC.c new file mode 100644 index 0000000..aad1eb4 --- /dev/null +++ b/src/func_8002C6EC.c @@ -0,0 +1,46 @@ +/* + * func_8002C6EC — 60 bytes at 0x8002C6EC..0x8002C728 + * + * Framed routine: appends its argument to a global array indexed by an incrementing + * `gp`-relative counter, then calls a routine. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * lw v0,0x24c(gp) 8f82024c v0 = *(int *)(gp + 0x24c) + * sw ra,0x10(sp) afbf0010 save ra + * addiu v0,v0,0x1 24420001 v0 += 1 + * sw v0,0x24c(gp) af82024c *(int *)(gp + 0x24c) = v0 + * sll v0,v0,0x2 00021080 v0 *= 4 + * lui at,0x8011 3c018011 \ + * addu at,at,v0 00220821 / at = 0x80110000 + index * 4 + * sw a0,-0xcfc(at) ac24f304 *(int *)(at - 0xcfc) = a0 + * jal 0x8002c3f8 0c00b0fe call func_8002C3F8 + * li a1,0x1 24050001 a1 = 1 (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * `0x24c(gp)` is a `gp`-relative access off gp 0x80121938, i.e. 0x80121B84, which the registry + * already carries as `D_80121B84` with the `gp` marker. The counter is **incremented first** + * and the array is then indexed by the incremented value, so this is a pre-increment append. + * + * The array store is the explicit indexed symbol form (`lui at,%hi` / `addu at,at,index` / + * `sw rt,%lo(at)`), and the address arithmetic carries: `lui 0x8011` with the signed + * displacement -0xcfc gives **0x8010F304**, not 0x8011F304. + * + * LIMITS: the function name, the callee, the array and the counter's meaning are hypotheses; + * only the bytes are evidence. The array elements and the counter are 32-bit. The second + * argument to the callee is the literal 1, materialised in the `jal` delay slot. + */ + +extern int D_80121B84; +extern int D_8010F304[]; +extern void func_8002C3F8(int a0, int a1); + +void func_8002C6EC(int a0) +{ + D_80121B84++; + D_8010F304[D_80121B84] = a0; + func_8002C3F8(a0, 1); +} diff --git a/src/func_80045388.c b/src/func_80045388.c new file mode 100644 index 0000000..f86d7e9 --- /dev/null +++ b/src/func_80045388.c @@ -0,0 +1,50 @@ +/* + * func_80045388 — 56 bytes at 0x80045388..0x800453C0 + * + * Framed routine guarded by a byte flag: when the flag is clear it calls one routine with a + * global pointer and its own argument. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * lui v0,0x8012 3c028012 \ + * lbu v0,0x2374(v0) 90422374 / v0 = *(unsigned char *)0x80122374 + * move a1,a0 00802821 a1 = a0 + * bne v0,zero,0x800453b0 14400004 if (v0 != 0) goto epilogue + * sw ra,0x10(sp) afbf0010 save ra (delay slot) + * lui a0,0x8013 3c048013 \ + * lw a0,-0x2774(a0) 8c84d88c / a0 = *(int *)0x8012D88C + * jal 0x8005a78c 0c0169e3 call func_8005A78C + * nop 00000000 (delay slot) + * 0x800453b0: + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * Byte-for-byte the same shape as func_800453C0 except for the callee (func_8005A78C here, + * func_8005A81C there) — the pair is one guard reached through two different routines. The + * difference between the two is only scheduling: here the frame is established **first** and + * the flag load second, in func_800453C0 the flag load is hoisted above the frame. Both come + * from the same C and are cc1's scheduling choice. + * + * The guard's `sw ra` sits in the branch delay slot, so `ra` is only saved on the path that + * reaches the call. The argument moves to a1 before a0 is overwritten with the global, so the + * call is `f(global, arg)`. The address arithmetic carries: `lui 0x8013` with the signed + * displacement -0x2774 gives **0x8012D88C**. + * + * LIMITS: the function name, the flag's meaning, the global's type and the callee are + * hypotheses; only the bytes are evidence. The flag is a byte (`lbu`) and the global is read + * as a 32-bit pointer. The routine sets no result, so it is `void`. + */ + +extern unsigned char D_80122374; +extern int D_8012D88C; +extern void func_8005A78C(int a0, int a1); + +void func_80045388(int a0) +{ + unsigned char v0 = D_80122374; + + if (v0 == 0) + func_8005A78C(D_8012D88C, a0); +} diff --git a/src/func_800734A4.c b/src/func_800734A4.c new file mode 100644 index 0000000..5fafce1 --- /dev/null +++ b/src/func_800734A4.c @@ -0,0 +1,46 @@ +/* + * func_800734A4 — 56 bytes at 0x800734A4..0x800734DC + * + * Framed routine: sets one bit of a 16-bit value, passes it on, and if the result is + * non-null passes that on too. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * ori a0,a0,0x4000 34844000 a0 |= 0x4000 + * sll a0,a0,0x10 00042400 a0 <<= 16 + * jal 0x80072d0c 0c01cb43 call func_80072D0C + * sra a0,a0,0x10 00042403 a0 = (short)a0 (delay slot) + * beq v0,zero,0x800734cc 10400003 if (v0 == 0) goto epilogue + * nop 00000000 (delay slot) + * jal 0x8006f28c 0c01bcA3 call func_8006F28C + * move a0,v0 00402021 a0 = v0 (delay slot) + * 0x800734cc: + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The `ori`/`sll`/`sra` triple is the mask-and-sign-extend this ABI applies to a `short` + * argument: bit 14 is set on the incoming value and the result is narrowed back to 16 bits + * before the call, so both the parameter and the callee's parameter are `short`. The + * narrowing is scheduled into the `jal` delay slot. + * + * The second call is guarded and receives the first call's result; nothing touches v0 after + * it, so the wrapper sets no result of its own and is `void`. + * + * LIMITS: the function names, the callees and the meaning of bit 14 are hypotheses; only the + * bytes are evidence. The `nop` in the first branch's delay slot means nothing was available + * to fill it. + */ + +extern int func_80072D0C(short a0); +extern void func_8006F28C(int a0); + +void func_800734A4(short a0) +{ + int v0 = func_80072D0C(a0 | 0x4000); + + if (v0 != 0) + func_8006F28C(v0); +} diff --git a/src/func_8009B464.c b/src/func_8009B464.c new file mode 100644 index 0000000..1f21c7a --- /dev/null +++ b/src/func_8009B464.c @@ -0,0 +1,44 @@ +/* + * func_8009B464 — 60 bytes at 0x8009B464..0x8009B4A0 + * + * Framed routine: calls one routine with an advanced pointer, its own argument, a value + * selected by one bit of a field, and a zero. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * move a1,a0 00802821 a1 = a0 + * addiu a0,a1,0x30 24a40030 a0 = a1 + 0x30 + * sw ra,0x10(sp) afbf0010 save ra + * lw a2,0x28(a1) 8ca20028 a2 = *(int *)(a1 + 0x28) + * addu a3,zero,zero 00003821 a3 = 0 + * andi a2,a2,0x8000 30428000 a2 &= 0x8000 + * sltu a2,zero,a2 0002102b a2 = (0 < a2) + * subu a2,zero,a2 00021023 a2 = -a2 (0 or -1) + * jal 0x8009b074 0c026c1d call func_8009B074 + * andi a2,a2,0x29a 3042029a a2 &= 0x29a (delay slot) (0 or 0x29a) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The four-instruction `andi`/`sltu`/`subu`/`andi` sequence is cc1's **if-conversion** of a + * two-constant conditional: `(field & 0x8000) ? 0x29a : 0` becomes + * `-((field & 0x8000) != 0) & 0x29a`, with the final `andi` scheduled into the `jal` delay + * slot. That is the same mechanism cookbook finding 26's neighbours show, and it is why the + * source is written as a ternary: an `if`/`else` assigning the same variable produces the same + * code. + * + * The first argument is the incoming pointer advanced by 0x30 (computed in a register copy, so + * the original value survives as the second argument), and a3 is zeroed with + * `addu rd,zero,zero`. + * + * LIMITS: the function name, the callee, the field at +0x28, the bit 15 test and the constant + * 0x29a are hypotheses; only the bytes are evidence. The field load is 32-bit. + */ + +extern void func_8009B074(int a0, int a1, int a2, int a3); + +void func_8009B464(int a0) +{ + func_8009B074(a0 + 0x30, a0, (*(int *)(a0 + 0x28) & 0x8000) ? 0x29a : 0, 0); +} diff --git a/src/func_800A9D58.c b/src/func_800A9D58.c new file mode 100644 index 0000000..f916c10 --- /dev/null +++ b/src/func_800A9D58.c @@ -0,0 +1,49 @@ +/* + * func_800A9D58 — 56 bytes at 0x800A9D58..0x800A9D90 + * + * Framed routine: calls one routine, and if it returns non-null passes the result on and + * then calls a second routine. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * jal 0x800a82d0 0c02a0b4 call func_800A82D0 + * nop 00000000 (delay slot) + * beq v0,zero,0x800a9d80 10400005 if (v0 == 0) goto epilogue + * nop 00000000 (delay slot) + * jal 0x800a9d90 0c02a764 call func_800A9D90 + * move a0,v0 00402021 a0 = v0 (delay slot) + * jal 0x800a8224 0c02a089 call func_800A8224 + * nop 00000000 (delay slot) + * 0x800a9d80: + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * Both calls are inside the guard: the first takes the tested result as its argument (moved + * into a0 in the `jal` delay slot), the second takes none. Nothing touches v0 after the + * second call, so the wrapper sets no result and is `void`. + * + * The first callee, 0x800A82D0, is one of the project's recorded open negatives + * (`near_match_negatives.tsv`, class `-`); it is referenced here as an unresolved symbol, not + * as a matched region. The second callee, 0x800A9D90, is the address immediately after this + * function's end. + * + * LIMITS: the function names and the callees' purposes are hypotheses; only the bytes are + * evidence. The guard tests v0 for zero (`beq v0,zero`), so the source condition is `!= 0`. + */ + +extern int func_800A82D0(void); +extern void func_800A9D90(int a0); +extern void func_800A8224(void); + +void func_800A9D58(void) +{ + int v0 = func_800A82D0(); + + if (v0 != 0) { + func_800A9D90(v0); + func_800A8224(); + } +} diff --git a/src/func_800F9344.c b/src/func_800F9344.c new file mode 100644 index 0000000..5a10ce3 --- /dev/null +++ b/src/func_800F9344.c @@ -0,0 +1,47 @@ +/* + * func_800F9344 — 60 bytes at 0x800F9344..0x800F9380 + * + * Framed routine: applies a routine to a 12-bit field of its argument, negating both the + * argument and the result when the argument is negative. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * bltz a0,0x800f9360 04800006 if (a0 < 0) goto the negative path + * sw ra,0x10(sp) afbf0010 save ra (delay slot) + * jal 0x800f9380 0c03e4e0 call func_800F9380 + * andi a0,a0,0xfff 30840fff a0 &= 0xfff (delay slot) + * j 0x800f9370 081003dc goto epilogue + * nop 00000000 (delay slot) + * 0x800f9360: + * subu a0,zero,a0 00042023 a0 = -a0 + * jal 0x800f9380 0c03e4e0 call func_800F9380 + * andi a0,a0,0xfff 30840fff a0 &= 0xfff (delay slot) + * subu v0,zero,v0 00021023 v0 = -v0 + * 0x800f9370: + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The two arms each call the same routine with the argument masked to 12 bits; the negative + * arm negates the argument first and the result afterwards. The positive arm ends with a `j` + * over the negative block, so the source is the early `return` for the negative case followed + * by the positive one — the layout is the branch target being the later block, which is the + * shape the `if (a0 < 0) return ...;` spelling produces here. + * + * Both masks are scheduled into their `jal` delay slots, and the guard's `sw ra` is in the + * `bltz` delay slot. + * + * LIMITS: the function name, the callee and the claim that the mask is a 12-bit field are + * hypotheses; only the bytes are evidence. The negation implies a signed result. + */ + +extern int func_800F9380(int a0); + +int func_800F9344(int a0) +{ + if (a0 < 0) + return -func_800F9380((-a0) & 0xfff); + + return func_800F9380(a0 & 0xfff); +}