From c286bc435504d4877111ab3d65b4e7e841b00238 Mon Sep 17 00:00:00 2001 From: Christopher Williams Date: Thu, 24 Sep 2026 00:33:42 -0400 Subject: [PATCH] =?UTF-8?q?phase9:=20merge=20worker=20C=20handoff=20close?= =?UTF-8?q?=20=E2=80=94=20283=20regions=20/=20274=20distinct=20bodies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Worker C rotated out on budget with a clean handoff (28 claims total, all verified; 18 negatives with hypotheses). Its final 2 rows verified MATCH (0x80101838 reopening closed via do/while + sentinel: this closes B's cycle-2 negative on the same address!; 0x8007C4A8). Handoff inheritance: orig.py disassembly helper, F1-F14 findings confirmed, 2-byte free claim 0x800266A8 left with its exact hypothesis. Gate MATCH whole-binary SHA-1 e173426c157384ebf1b6caf8c6fea18a85a14af9. --- config/regions.tsv | 2 ++ src/func_800281A4.c | 48 ++++++++++++++++++++++++++++++++++++++++++ src/func_8002A9D4.c | 51 +++++++++++++++++++++++++++++++++++++++++++++ src/func_80057524.c | 41 ++++++++++++++++++++++++++++++++++++ src/func_8007C4A8.c | 50 ++++++++++++++++++++++++++++++++++++++++++++ src/func_8007ED4C.c | 44 ++++++++++++++++++++++++++++++++++++++ src/func_80096324.c | 44 ++++++++++++++++++++++++++++++++++++++ src/func_80101838.c | 50 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 330 insertions(+) create mode 100644 src/func_800281A4.c create mode 100644 src/func_8002A9D4.c create mode 100644 src/func_80057524.c create mode 100644 src/func_8007C4A8.c create mode 100644 src/func_8007ED4C.c create mode 100644 src/func_80096324.c create mode 100644 src/func_80101838.c diff --git a/config/regions.tsv b/config/regions.tsv index 5659e62..3e534bb 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -130,6 +130,7 @@ 0x80072BA8 0x80072BDC src/func_80072BA8.c 0x800734A4 0x800734DC src/func_800734A4.c 0x8007A404 0x8007A428 src/func_8007A404.c +0x8007C4A8 0x8007C4EC src/func_8007C4A8.c 0x8007C4EC 0x8007C524 src/func_8007C4EC.c 0x8007DC40 0x8007DC4C src/func_8007DC40.c 0x8007DF00 0x8007DF34 src/func_8007DF00.c @@ -245,6 +246,7 @@ 0x80100964 0x8010097C src/func_80100964.c 0x8010097C 0x80100998 src/func_8010097C.c 0x80101244 0x8010128C src/func_80101244.c +0x80101838 0x80101878 src/func_80101838.c 0x80101CAC 0x80101CDC src/func_80101CAC.c 0x801027CC 0x801027F8 src/func_801027CC.c 0x80102B10 0x80102B2C src/func_80102B10.c maspsx=off diff --git a/src/func_800281A4.c b/src/func_800281A4.c new file mode 100644 index 0000000..07a3d7e --- /dev/null +++ b/src/func_800281A4.c @@ -0,0 +1,48 @@ +/* + * func_800281A4 — 64 bytes at 0x800281A4..0x800281E4 + * + * Framed routine: passes two words of its first argument to a routine and stores the result + * through its second argument, returning 0. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * move v0,a0 00801021 v0 = a0 + * sw s0,0x10(sp) afb00010 save s0 + * move s0,a1 00a08021 s0 = a1 + * sw ra,0x14(sp) afbf0014 save ra + * lw a0,0x0(v0) 8c440000 a0 = *(int *)v0 + * lw a1,0x8(v0) 8c450008 a1 = *(int *)(v0 + 8) + * jal 0x800f2fc0 0c03cbf0 call func_800F2FC0 + * nop 00000000 (delay slot) + * sw v0,0x0(s0) ae020000 *(int *)s0 = v0 + * addu v0,zero,zero 00001021 v0 = 0 + * lw ra,0x14(sp) 8fbf0014 restore ra + * lw s0,0x10(sp) 8e100010 restore s0 + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The first argument is copied to **v0** and both of its words are read through that copy, so + * a0 is free to receive the callee's first argument — the pointer is preserved in a scratch + * register rather than on the stack. The second argument is copied to **s0**, a callee-saved + * register, because it is used as the store base *after* the call. The frame saves s0 at 0x10 + * and ra at 0x14. + * + * The wrapper's own result is the constant 0, set after the store, so it is not the callee's + * value. + * + * LIMITS: the function name, the callee, the two source words and the claim that the second + * argument is an output pointer are hypotheses; only the bytes are evidence. All accesses are + * 32-bit. + */ + +extern int func_800F2FC0(int a0, int a1); + +int func_800281A4(int a0, int *a1) +{ + int v0 = a0; + + *a1 = func_800F2FC0(*(int *)v0, *(int *)(v0 + 8)); + + return 0; +} diff --git a/src/func_8002A9D4.c b/src/func_8002A9D4.c new file mode 100644 index 0000000..109c809 --- /dev/null +++ b/src/func_8002A9D4.c @@ -0,0 +1,51 @@ +/* + * func_8002A9D4 — 68 bytes at 0x8002A9D4..0x8002AA18 + * + * Leaf routine that walks a 133-entry table of pointers and clears one field of every entry + * whose field holds 1. + * + * The observed instructions are: + * addu a1,zero,zero 00002821 i = 0 + * li a2,0x1 24060001 a2 = 1 + * lui a0,0x8011 3c048011 \ + * addiu a0,a0,-0xf10 2484f0f0 / a0 = 0x8010F0F0 (D_8010F0F0) + * 0x8002a9e4: + * lw v1,0x0(a0) 8c830000 v1 = *p + * nop 00000000 load-delay slot + * lw v0,0x14(v1) 8c620014 v0 = *(int *)(v1 + 0x14) + * nop 00000000 load-delay slot + * bne v0,a2,0x8002aa00 14460001 if (v0 != 1) goto the increment + * nop 00000000 (delay slot) + * sw zero,0x14(v1) ac600014 *(int *)(v1 + 0x14) = 0 + * 0x8002aa00: + * addiu a1,a1,0x1 24a50001 i++ + * slti v0,a1,0x85 28a20085 v0 = (i < 133) signed + * bne v0,zero,0x8002a9e4 1440fff8 if (v0) loop + * addiu a0,a0,0x4 24840004 p++ (delay slot) + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The table is a walked pointer of 4-byte elements built with `lui`+`addiu` (the linker-resolved + * symbol form, cookbook finding 4), each element being a **pointer** whose +0x14 field is + * compared with 1 and zeroed on a match. The comparison constant is materialised once outside + * the loop, the bound test is `slti` (signed) against 133, and the pointer is advanced in the + * branch delay slot. + * + * LIMITS: the function name, the table, the pointer elements, the field at +0x14 and the meaning + * of the value 1 are hypotheses; only the bytes are evidence. All accesses are 32-bit. Whether + * the elements are valid pointers for all 133 entries is not established. + */ + +extern int D_8010F0F0[]; + +void func_8002A9D4(void) +{ + int i; + + for (i = 0; i < 133; i++) { + int v1 = D_8010F0F0[i]; + + if (*(int *)(v1 + 0x14) == 1) + *(int *)(v1 + 0x14) = 0; + } +} diff --git a/src/func_80057524.c b/src/func_80057524.c new file mode 100644 index 0000000..005e3af --- /dev/null +++ b/src/func_80057524.c @@ -0,0 +1,41 @@ +/* + * func_80057524 — 64 bytes at 0x80057524..0x80057564 + * + * Framed routine: calls one routine with a field reached through two dereferences, then calls + * a second routine with its own argument. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw s0,0x10(sp) afb00010 save s0 + * move s0,a0 00808021 s0 = a0 + * sw ra,0x14(sp) afbf0014 save ra + * lw v0,0x20(s0) 8e020020 v0 = *(int *)(a0 + 0x20) + * nop 00000000 load-delay slot + * lw a0,0xf8(v0) 8c4400f8 a0 = *(int *)(v0 + 0xf8) + * jal 0x8003636c 0c00d8db call func_8003636C + * nop 00000000 (delay slot) + * jal 0x80057418 0c015d06 call func_80057418 + * move a0,s0 02002021 a0 = s0 (delay slot) + * lw ra,0x14(sp) 8fbf0014 restore ra + * lw s0,0x10(sp) 8e100010 restore s0 + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The argument is copied to **s0**, a callee-saved register, because it must survive the first + * call to become the second call's argument — the same tell as func_800128E4. The second call + * restores it in its own `jal` delay slot. The first callee, 0x8003636C, is the store-only + * function cookbook finding 13 is built on. + * + * LIMITS: the function name, both callees, the pointer chain and the field at +0xf8 are + * hypotheses; only the bytes are evidence. The routine sets no result, so it is `void`. + */ + +extern void func_8003636C(int a0); +extern void func_80057418(int a0); + +void func_80057524(int a0) +{ + func_8003636C(*(int *)(*(int *)(a0 + 0x20) + 0xf8)); + func_80057418(a0); +} diff --git a/src/func_8007C4A8.c b/src/func_8007C4A8.c new file mode 100644 index 0000000..3b7576b --- /dev/null +++ b/src/func_8007C4A8.c @@ -0,0 +1,50 @@ +/* + * func_8007C4A8 — 68 bytes at 0x8007C4A8..0x8007C4EC + * + * Copies three words out of a 16-byte-stride table entry into a caller buffer and + * RETURNS the fourth. The entry index is a byte field of the source structure, and + * the table base is materialised as `lui` + `addiu` with a sign-adjusted low half + * — the symbol form (cookbook finding 4) — so the base is written as an + * address-shaped array. + * + * The observed instructions are: + * lbu v0,37(a0) ; index = p->byte_25 + * lui v1,0x8014 ; %hi of the table base + * addiu v1,v1,-31224 ; v1 = D_80138608 (%lo, sign-adjusted) + * sll v0,v0,0x4 ; index * 16 + * addu v0,v0,v1 ; table + index*16 (index first) + * lw v1,0(v0) / nop / sw v1,0(a1) + * lw v1,4(v0) / nop / sw v1,4(a1) + * lw v1,8(v0) / nop / sw v1,8(a1) + * lw v0,12(v0) ; the returned word + * jr ra + * nop + * + * The element stride is 16 and the table is indexed by a struct, so the entry is + * modelled as a four-word record and the index scaling falls out of `sizeof`. + * The `nop`s after each load are maspsx's load-delay fills. + * + * LIMITS: the table base (0x80138608), the stride (16), the index field offset + * (0x25) and the record's four-word layout are hypotheses read from the + * instruction shape; what the table holds is unknown and is not guessed here. The + * `lbu` is what shows the index byte is unsigned. Only the compiled bytes are + * evidence. + */ + +typedef struct { + int word_00; + int word_04; + int word_08; + int word_0c; +} func_8007C4A8_entry; + +extern func_8007C4A8_entry D_80138608[]; + +int func_8007C4A8(char *p, int *dst) { + func_8007C4A8_entry *entry = &D_80138608[*(unsigned char *)(p + 37)]; + + dst[0] = entry->word_00; + dst[1] = entry->word_04; + dst[2] = entry->word_08; + return entry->word_0c; +} diff --git a/src/func_8007ED4C.c b/src/func_8007ED4C.c new file mode 100644 index 0000000..386ecb8 --- /dev/null +++ b/src/func_8007ED4C.c @@ -0,0 +1,44 @@ +/* + * func_8007ED4C — 64 bytes at 0x8007ED4C..0x8007ED8C + * + * Framed routine: calls a routine returning a pointer and, when it is non-null, stores -1 into + * an element of a halfword array selected by a byte field of the returned object. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * jal 0x8007eb8c 0c01fae3 call func_8007EB8C + * nop 00000000 (delay slot) + * beq v0,zero,0x8007ed7c 10400005 if (v0 == 0) goto epilogue + * li v1,-0x1 2403ffff v1 = -1 (delay slot) + * lbu v0,0x25(v0) 90420025 v0 = *(unsigned char *)(v0 + 0x25) + * nop 00000000 load-delay slot + * sll v0,v0,0x1 00021040 v0 *= 2 + * lui at,0x8012 3c018012 \ + * addu at,at,v0 00220821 / at = 0x80120000 + index * 2 + * sh v1,0x2618(at) a4232618 *(short *)(at + 0x2618) = -1 + * 0x8007ed7c: + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The constant -1 is materialised in the guard's branch delay slot, so the guard carries the + * stored value. The array access is the explicit indexed symbol form with a 2-byte stride + * (`sll ...,1`), so the element type is 16-bit, and the address arithmetic carries: + * `lui 0x8012` with the signed displacement 0x2618 gives **0x80122618**. + * + * LIMITS: the function name, the callee, the byte field at +0x25 and the halfword array are + * hypotheses; only the bytes are evidence. The routine sets no result, so it is `void`. + */ + +extern char *func_8007EB8C(void); +extern short D_80122618[]; + +void func_8007ED4C(void) +{ + char *v0 = func_8007EB8C(); + + if (v0 != 0) + D_80122618[*(unsigned char *)(v0 + 0x25)] = -1; +} diff --git a/src/func_80096324.c b/src/func_80096324.c new file mode 100644 index 0000000..f219f2f --- /dev/null +++ b/src/func_80096324.c @@ -0,0 +1,44 @@ +/* + * func_80096324 — 64 bytes at 0x80096324..0x80096364 + * + * Framed routine that dispatches to one of two routines on a byte field. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * lbu v1,0x26(a0) 90830026 v1 = *(unsigned char *)(a0 + 0x26) + * li v0,0x9 24020009 v0 = 9 + * bne v1,v0,0x8009634c 14620003 if (v1 != 9) goto the else arm + * nop 00000000 (delay slot) + * jal 0x800964f8 0c02593e call func_800964F8 + * nop 00000000 (delay slot) + * j 0x80096354 081002d5 goto epilogue + * nop 00000000 (delay slot) + * 0x8009634c: + * jal 0x80095d74 0c02575d call func_80095D74 + * nop 00000000 (delay slot) + * 0x80096354: + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The comparison constant is materialised into v0 by `li` (SGIs have no branch-immediate form), + * the taken arm is the first callee, and the else arm is the branch target. The first arm ends + * with a `j` over the second, so the source is a plain `if`/`else` and not a `switch` — a + * two-case switch would produce a different layout. + * + * LIMITS: the function name, both callees, the field at +0x26 and the meaning of the value 9 + * are hypotheses; only the bytes are evidence. The routine sets no result, so it is `void`. + */ + +extern void func_800964F8(void); +extern void func_80095D74(void); + +void func_80096324(int a0) +{ + if (*(unsigned char *)(a0 + 0x26) == 9) + func_800964F8(); + else + func_80095D74(); +} diff --git a/src/func_80101838.c b/src/func_80101838.c new file mode 100644 index 0000000..c4bb106 --- /dev/null +++ b/src/func_80101838.c @@ -0,0 +1,50 @@ +/* + * func_80101838 — 64 bytes at 0x80101838..0x80101878 + * + * Initialises sixteen parallel fields of a structure: two byte arrays indexed by + * the loop counter and two 16-bit arrays indexed by twice the counter, with two + * single 16-bit fields cleared outside the loop (one before, one after). + * + * The observed instructions are: + * sh zero,64(a0) ; *(short *)(base + 0x40) = 0 + * move a1,zero ; i = 0 + * li a3,127 ; the byte constant + * li a2,8192 ; the 16-bit constant (0x2000) + * move v1,a0 ; v1 walks base + i*2 + * 4C: addu v0,a0,a1 ; base + i + * sb zero,68(v0) ; *(char *)(base + i + 0x44) = 0 + * sb a3,84(v0) ; *(char *)(base + i + 0x54) = 127 + * sh zero,100(v1) ; *(short *)(base + i*2 + 0x64) = 0 + * sh a2,132(v1) ; *(short *)(base + i*2 + 0x84) = 0x2000 + * addiu a1,a1,1 ; i++ + * slti v0,a1,16 ; i < 16 + * bnez v0,0x8010184C ; loop + * addiu v1,v1,2 ; v1 += 2 (delay slot) + * jr ra + * sh zero,66(a0) ; *(short *)(base + 0x42) = 0 (delay slot) + * + * The two indexings differ on purpose and must be kept distinct: the byte fields + * use `base + i` (an `addu` of base and the counter) while the 16-bit fields use + * a separate pointer walked by two. Writing all four as `base + i * 2` or all + * four as `base + i` will not reproduce the `addu v0,a0,a1` plus walking-pointer + * pair. + * + * LIMITS: every offset, the count (16), the two constants (127 and 8192) and the + * field widths are hypotheses read from the instruction shape; what the structure + * holds is unknown and is not guessed here. The `sh` at the very end lands in the + * `jr ra` delay slot, which is why it appears out of source order. Only the + * compiled bytes are evidence. + */ + +void func_80101838(char *base) { + int i; + + *(short *)(base + 64) = 0; + for (i = 0; i < 16; i++) { + *(char *)(base + i + 68) = 0; + *(char *)(base + i + 84) = 127; + *(short *)(base + i * 2 + 100) = 0; + *(short *)(base + i * 2 + 132) = 8192; + } + *(short *)(base + 66) = 0; +}