From a106afe5ab0b9c5154a4e94439b21fdbb406cd4a Mon Sep 17 00:00:00 2001 From: Christopher Williams Date: Thu, 24 Sep 2026 00:42:33 -0400 Subject: [PATCH] =?UTF-8?q?phase9:=20merge=20worker=20A=20handoff=20?= =?UTF-8?q?=E2=80=94=20306=20regions=20/=20297=20distinct=20bodies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Worker A rotated out at 108 claims (+79 from its session) with a clean handoff; final 4 rows verified MATCH by the coordinator. 18 new regions gated MATCH whole-binary (c_regions=306, SHA-1 e173426c157384ebf1b6caf8c6fea18a85a14af9), make check green. P9-T4 checkpoint (>=290 total) CROSSED at 297 bodies. All three workers have now rotated out; the coordinator absorbs the remaining partitions. Negatives census now 34 report rows from A alone (25 match-class + 9 bad-extent triage), with the bad-extent class verified independently by the coordinator (disjoint from 288 registered regions; 7 worklist rows flagged incl. the runaway 0x801800C4/0x80180808 that were never excluded because they are fallthrough-graded). Handoff notes recorded: two high-value unattempted GTE rows (0x80101C2C, 0x80102FA4 — need raw lwc2/swc2 asm with memory operands), 8 lever findings for the next charter (srl=sra-unsigned, slti-sltiu 16-bit tell, single-exit result shape, struct-assignment for a0/a1 loads, maspsx=off scope x2, side- effect statement order, lui/lw address arithmetic, CSE-preventing symbols). The bad-extent detector is implemented in sf3_triage with synthetic tests; the regenerated worklist will exclude the class. --- config/regions.tsv | 18 +++++++++++ src/func_8001289C.c | 51 +++++++++++++++++++++++++++++ src/func_800170C0.c | 47 +++++++++++++++++++++++++++ src/func_80017D88.c | 49 ++++++++++++++++++++++++++++ src/func_8002AC84.c | 48 ++++++++++++++++++++++++++++ src/func_8002E028.c | 45 ++++++++++++++++++++++++++ src/func_8002F160.c | 53 ++++++++++++++++++++++++++++++ src/func_80031F78.c | 64 +++++++++++++++++++++++++++++++++++++ src/func_80036A0C.c | 43 +++++++++++++++++++++++++ src/func_80036A9C.c | 47 +++++++++++++++++++++++++++ src/func_80042964.c | 53 ++++++++++++++++++++++++++++++ src/func_8004E3FC.c | 54 +++++++++++++++++++++++++++++++ src/func_800668A8.c | 48 ++++++++++++++++++++++++++++ src/func_8006B778.c | 55 ++++++++++++++++++++++++++++++++ src/func_8007E8B8.c | 51 +++++++++++++++++++++++++++++ src/func_80083470.c | 50 +++++++++++++++++++++++++++++ src/func_8009107C.c | 43 +++++++++++++++++++++++++ src/func_800A9FD4.c | 51 +++++++++++++++++++++++++++++ src/func_800F7A20.c | 45 ++++++++++++++++++++++++++ src/func_8010A748.c | 78 ++++++++++++++++++++++++--------------------- 20 files changed, 956 insertions(+), 37 deletions(-) create mode 100644 src/func_8001289C.c create mode 100644 src/func_800170C0.c create mode 100644 src/func_80017D88.c create mode 100644 src/func_8002AC84.c create mode 100644 src/func_8002E028.c create mode 100644 src/func_8002F160.c create mode 100644 src/func_80031F78.c create mode 100644 src/func_80036A0C.c create mode 100644 src/func_80036A9C.c create mode 100644 src/func_80042964.c create mode 100644 src/func_8004E3FC.c create mode 100644 src/func_800668A8.c create mode 100644 src/func_8006B778.c create mode 100644 src/func_8007E8B8.c create mode 100644 src/func_80083470.c create mode 100644 src/func_8009107C.c create mode 100644 src/func_800A9FD4.c create mode 100644 src/func_800F7A20.c diff --git a/config/regions.tsv b/config/regions.tsv index 566ef5b..5e316ea 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -13,6 +13,7 @@ 0x80012780 0x8001278C src/func_80012780.c 0x800127F0 0x8001281C src/func_800127F0.c 0x8001281C 0x80012834 src/func_8001281C.c +0x8001289C 0x800128E4 src/func_8001289C.c 0x800128E4 0x80012918 src/func_800128E4.c 0x80012D8C 0x80012DBC src/func_80012D8C.c 0x80012DBC 0x80012DE8 src/func_80012DBC.c @@ -24,6 +25,7 @@ 0x80016174 0x80016198 src/func_80016174.c 0x80016198 0x800161E0 src/func_80016198.c 0x80016E50 0x80016E68 src/func_80016E50.c +0x800170C0 0x80017108 src/func_800170C0.c 0x800171D8 0x80017200 src/func_800171D8.c 0x8001761C 0x80017660 src/func_8001761C.c 0x800179B8 0x800179CC src/func_800179B8.c @@ -35,6 +37,7 @@ 0x80017C60 0x80017C6C src/func_80017C60.c 0x80017D1C 0x80017D48 src/func_80017D1C.c 0x80017D48 0x80017D88 src/func_80017D48.c +0x80017D88 0x80017DD0 src/func_80017D88.c 0x80017DD0 0x80017DF0 src/func_80017DD0.c 0x800182D4 0x800182F4 src/func_800182D4.c 0x800183B8 0x800183EC src/func_800183B8.c @@ -66,6 +69,7 @@ 0x800290D0 0x800290F0 src/func_800290D0.c 0x800290F0 0x80029118 src/func_800290F0.c 0x8002A9D4 0x8002AA18 src/func_8002A9D4.c +0x8002AC84 0x8002ACBC src/func_8002AC84.c 0x8002C6EC 0x8002C728 src/func_8002C6EC.c 0x8002C7BC 0x8002C7EC src/func_8002C7BC.c 0x8002C888 0x8002C894 src/func_8002C888.c @@ -76,17 +80,22 @@ 0x8002D2BC 0x8002D2D4 src/func_8002D2BC.c 0x8002D5D0 0x8002D608 src/func_8002D5D0.c 0x8002DEB4 0x8002DF1C src/func_8002DEB4.c +0x8002E028 0x8002E070 src/func_8002E028.c 0x8002E7C4 0x8002E7E4 src/func_8002E7C4.c +0x8002F160 0x8002F1A4 src/func_8002F160.c 0x8002F1A4 0x8002F1D8 src/func_8002F1A4.c 0x8002F2F8 0x8002F300 src/func_8002F2F8.c 0x8002F404 0x8002F450 src/func_8002F404.c 0x800301FC 0x8003022C src/func_800301FC.c 0x80030358 0x80030390 src/func_80030358.c +0x80031F78 0x80031FC4 src/func_80031F78.c 0x800321EC 0x800321F8 src/func_800321EC.c 0x80036308 0x80036328 src/func_80036308.c 0x8003636C 0x80036378 src/func_8003636C.c 0x80036378 0x80036380 src/func_80036378.c 0x80036380 0x80036390 src/func_80036380.c +0x80036A0C 0x80036A54 src/func_80036A0C.c +0x80036A9C 0x80036AD8 src/func_80036A9C.c 0x80036AD8 0x80036B14 src/func_80036AD8.c 0x8003768C 0x800376CC src/func_8003768C.c 0x80038788 0x80038790 src/func_80038788.c @@ -95,6 +104,7 @@ 0x8003B320 0x8003B34C src/func_8003B320.c 0x80041A24 0x80041A58 src/func_80041A24.c 0x80042088 0x80042090 src/func_80042088.c +0x80042964 0x800429B0 src/func_80042964.c 0x80042D64 0x80042D88 src/func_80042D64.c 0x80043D8C 0x80043DC4 src/func_80043D8C.c 0x80044F58 0x80044FA4 src/func_80044F58.c gp=-D_80121BFC @@ -105,6 +115,7 @@ 0x8004C0AC 0x8004C0F0 src/func_8004C0AC.c 0x8004C0F0 0x8004C110 src/func_8004C0F0.c 0x8004CEEC 0x8004CF0C src/func_8004CEEC.c +0x8004E3FC 0x8004E440 src/func_8004E3FC.c 0x800516E0 0x800516FC src/func_800516E0.c 0x8005182C 0x80051864 src/func_8005182C.c 0x80052C98 0x80052CAC src/func_80052C98.c @@ -114,6 +125,7 @@ 0x8005E3D0 0x8005E3F4 src/func_8005E3D0.c 0x8005ED6C 0x8005EDBC src/func_8005ED6C.c 0x80065B6C 0x80065B8C src/func_80065B6C.c +0x800668A8 0x800668F0 src/func_800668A8.c 0x800681A4 0x800681E0 src/func_800681A4.c 0x800681E0 0x8006821C src/func_800681E0.c 0x800683B0 0x800683E4 src/func_800683B0.c @@ -123,6 +135,7 @@ 0x80068F6C 0x80068F98 src/func_80068F6C.c 0x80068F98 0x80068FA8 src/func_80068F98.c 0x800697A4 0x800697C4 src/func_800697A4.c +0x8006B778 0x8006B7C0 src/func_8006B778.c 0x8006BC08 0x8006BC34 src/func_8006BC08.c 0x8006EC94 0x8006ECD4 src/func_8006EC94.c 0x8006F6BC 0x8006F6F4 src/func_8006F6BC.c @@ -137,10 +150,12 @@ 0x8007C4EC 0x8007C524 src/func_8007C4EC.c 0x8007DC40 0x8007DC4C src/func_8007DC40.c 0x8007DF00 0x8007DF34 src/func_8007DF00.c +0x8007E8B8 0x8007E904 src/func_8007E8B8.c 0x8007ED4C 0x8007ED8C src/func_8007ED4C.c 0x800827A8 0x800827C4 src/func_800827A8.c 0x80082914 0x80082944 src/func_80082914.c 0x80083440 0x80083470 src/func_80083440.c +0x80083470 0x800834B8 src/func_80083470.c 0x80083504 0x8008352C src/func_80083504.c 0x8008352C 0x8008355C src/func_8008352C.c 0x80085B80 0x80085B90 src/func_80085B80.c @@ -161,6 +176,7 @@ 0x80090A44 0x80090A70 src/func_80090A44.c 0x80090B64 0x80090B7C src/func_80090B64.c 0x80090C8C 0x80090CAC src/func_80090C8C.c +0x8009107C 0x800910B0 src/func_8009107C.c 0x800912D4 0x800912FC src/func_800912D4.c 0x800912FC 0x8009132C src/func_800912FC.c 0x80092068 0x80092088 src/func_80092068.c @@ -183,6 +199,7 @@ 0x800A74BC 0x800A74D0 src/func_800A74BC.c 0x800A8B48 0x800A8B8C src/func_800A8B48.c 0x800A9D58 0x800A9D90 src/func_800A9D58.c +0x800A9FD4 0x800AA01C src/func_800A9FD4.c 0x800AA56C 0x800AA59C src/func_800AA56C.c 0x800AC818 0x800AC85C src/func_800AC818.c 0x800AC85C 0x800AC884 src/func_800AC85C.c @@ -215,6 +232,7 @@ 0x800F75D0 0x800F760C src/func_800F75D0.c 0x800F7990 0x800F79C0 src/func_800F7990.c 0x800F79F0 0x800F7A20 src/func_800F79F0.c +0x800F7A20 0x800F7A54 src/func_800F7A20.c 0x800F7A54 0x800F7A84 src/func_800F7A54.c 0x800F7A84 0x800F7A94 src/func_800F7A84.c 0x800F7A94 0x800F7AAC src/func_800F7A94.c diff --git a/src/func_8001289C.c b/src/func_8001289C.c new file mode 100644 index 0000000..2cd3115 --- /dev/null +++ b/src/func_8001289C.c @@ -0,0 +1,51 @@ +/* + * func_8001289C — 72 bytes at 0x8001289C..0x800128E4 + * + * Framed routine: when its second argument is a non-null pointer to a non-zero word, calls a + * routine with a field of its first argument and that word, then clears the word. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw s0,0x10(sp) afb00010 save s0 + * move s0,a1 00a08021 s0 = a1 + * beq s0,zero,0x800128cc 12000007 if (a1 == 0) goto the shared zero return + * sw ra,0x14(sp) afbf0014 save ra (delay slot) + * lw a1,0x0(s0) 8e050000 a1 = *a1 + * nop 00000000 load-delay slot + * beq a1,zero,0x800128d0 10a00002 if (a1 == 0) goto epilogue + * addu v0,zero,zero 00001021 v0 = 0 (delay slot) + * jal 0x80026560 0c009958 call func_80026560 + * addiu a0,a0,0x94 24840094 a0 = a0 + 0x94 (delay slot) + * sw zero,0x0(s0) ae000000 *s0 = 0 + * 0x800128cc: + * addu v0,zero,zero 00001021 v0 = 0 + * 0x800128d0: + * 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 second argument is parked in **s0** because it is dereferenced before the call and written + * after it. The first argument is advanced by 0x94 in the `jal` delay slot, and the loaded word + * is passed as the call's second argument. The result is a constant 0 set on both exits, and the + * `beq a1,zero` exit materialises it in its own branch delay slot. + * + * LIMITS: the function name, the callee, the offset 0x94 and the claim that the second argument + * is a pointer to a single word are hypotheses; only the bytes are evidence. All accesses are + * 32-bit. Whether the pointer and the word are a handle or a slot is not recoverable. + */ + +extern void func_80026560(int a0, int a1); + +int func_8001289C(int a0, int *a1) +{ + int v0 = 0; + + if (a1 != 0 && *a1 != 0) { + func_80026560(a0 + 0x94, *a1); + *a1 = 0; + } + + return v0; +} diff --git a/src/func_800170C0.c b/src/func_800170C0.c new file mode 100644 index 0000000..0e746ba --- /dev/null +++ b/src/func_800170C0.c @@ -0,0 +1,47 @@ +/* + * func_800170C0 — 72 bytes at 0x800170C0..0x80017108 + * + * Framed routine: forwards all three arguments to a routine, then stores a 24-bit-masked copy of + * the third into a field reached through the first. + * + * The observed instructions are: + * addiu sp,sp,-0x20 27bdffe0 frame, 32 bytes + * sw s1,0x14(sp) afb10014 save s1 + * move s1,a0 00808821 s1 = a0 + * sw s0,0x10(sp) afb00010 save s0 + * sw ra,0x18(sp) afbf0018 save ra + * jal 0x8001703c 0c005c0f call func_8001703C + * move s0,a2 00c08021 s0 = a2 (delay slot) + * lui v1,0xff 3c0300ff \ + * ori v1,v1,0xffff 3463ffff / v1 = 0x00ffffff + * lw v0,0x10(s1) 8e220010 v0 = *(int *)(a0 + 0x10) + * and s0,s0,v1 02038024 s0 &= 0xffffff + * sw s0,0x1c(v0) ac50001c *(int *)(v0 + 0x1c) = s0 + * lw ra,0x18(sp) 8fbf0018 restore ra + * lw s1,0x14(sp) 8e310014 restore s1 + * lw s0,0x10(sp) 8e100010 restore s0 + * addiu sp,sp,0x20 27bd0020 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * Both the first and third arguments are parked in callee-saved registers (s1, s0) **before** the + * call because both are needed after it: a0 is the base of the pointer chain and a2 is the stored + * value. The second argument is not touched at all, so the call is `f(a0, a1, a2)` with all three + * forwarded. + * + * The mask 0x00ffffff is built as `lui 0xff` + `ori 0xffff`, so the stored value is a 24-bit + * truncation — this is the `lui`+`ori` **literal** form (cookbook finding 5), not a symbol. + * + * LIMITS: the function name, the callee, the pointer chain, the field at +0x1c and the meaning of + * the 24-bit mask are hypotheses; only the bytes are evidence. All accesses are 32-bit. Whether + * the mask is a colour, an address or an index is not recoverable. + */ + +extern void func_8001703C(int a0, int a1, int a2); + +void func_800170C0(int a0, int a1, int a2) +{ + func_8001703C(a0, a1, a2); + + *(int *)(*(int *)(a0 + 0x10) + 0x1c) = a2 & 0xffffff; +} diff --git a/src/func_80017D88.c b/src/func_80017D88.c new file mode 100644 index 0000000..1499297 --- /dev/null +++ b/src/func_80017D88.c @@ -0,0 +1,49 @@ +/* + * func_80017D88 — 72 bytes at 0x80017D88..0x80017DD0 + * + * Framed routine: calls one routine four times with four consecutive fields of its argument, then + * clears a fifth field. + * + * 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 + * jal 0x80026650 0c009994 call func_80026650 + * addiu a0,s0,0x8c 2604008c a0 = a0 + 0x8c (delay slot) + * jal 0x80026650 0c009994 call func_80026650 + * addiu a0,s0,0x90 26040090 a0 = a0 + 0x90 (delay slot) + * jal 0x80026650 0c009994 call func_80026650 + * addiu a0,s0,0x94 26040094 a0 = a0 + 0x94 (delay slot) + * jal 0x80026650 0c009994 call func_80026650 + * addiu a0,s0,0x98 26040098 a0 = a0 + 0x98 (delay slot) + * sw zero,0x9c(s0) ae00009c *(int *)(a0 + 0x9c) = 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 argument is parked in **s0** because it is needed for all five accesses. Each call's + * argument is computed in that call's `jal` delay slot as a fixed offset off the saved pointer, + * so the four fields are 0x8c, 0x90, 0x94 and 0x98 — a 4-byte-strided run — and the cleared word + * at 0x9c is the next one after them. No return value is used, so the callee's result (if any) is + * discarded and the routine is `void`. + * + * LIMITS: the function name, the callee, the four field offsets and the claim that the cleared word + * belongs to the same object are hypotheses; only the bytes are evidence. All five accesses are + * 32-bit. Whether the four fields are an array is not recoverable from these instructions — the + * four calls are written out because that is what the bytes show, not because an unrolled loop was + * ruled out. + */ + +extern void func_80026650(int a0); + +void func_80017D88(int a0) +{ + func_80026650(a0 + 0x8c); + func_80026650(a0 + 0x90); + func_80026650(a0 + 0x94); + func_80026650(a0 + 0x98); + *(int *)(a0 + 0x9c) = 0; +} diff --git a/src/func_8002AC84.c b/src/func_8002AC84.c new file mode 100644 index 0000000..7c77cd2 --- /dev/null +++ b/src/func_8002AC84.c @@ -0,0 +1,48 @@ +/* + * func_8002AC84 — 56 bytes at 0x8002AC84..0x8002ACBC + * + * Framed routine: when a `gp`-relative global is not -1, passes it to a routine and then sets it + * to -1. + * + * The observed instructions are: + * lw a0,0x268(gp) 8f840268 a0 = *(int *)(gp + 0x268) + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw s0,0x10(sp) afb00010 save s0 + * li s0,-0x1 2410ffff s0 = -1 + * beq a0,s0,0x8002aca8 10800002 if (a0 == -1) goto epilogue + * sw ra,0x14(sp) afbf0014 save ra (delay slot) + * jal 0x800a8920 0c02a248 call func_800A8920 + * nop 00000000 (delay slot) + * sw s0,0x268(gp) af900268 *(int *)(gp + 0x268) = -1 + * 0x8002aca8: + * 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) + * + * `0x268(gp)` is a `gp`-relative access off gp 0x80121938, i.e. 0x80121BA0, which the registry + * already carries with the `gp` marker. The sentinel -1 is materialised **once** into s0 (a + * callee-saved register) and used for both the comparison and the post-call store, which is the + * tell that the same constant appears twice in the source: it must survive the call, so cc1 had + * to put it in a saved register. The `sw ra` is in the guard's branch delay slot. + * + * The global's load is hoisted above the frame setup. + * + * LIMITS: the function name, the callee, the global's meaning and the sentinel's meaning are + * hypotheses; only the bytes are evidence. The global is written as a 32-bit `int` because both + * its load and its store are 32-bit. + */ + +extern int D_80121BA0; +extern void func_800A8920(int a0); + +void func_8002AC84(void) +{ + int a0 = D_80121BA0; + + if (a0 != -1) { + func_800A8920(a0); + D_80121BA0 = -1; + } +} diff --git a/src/func_8002E028.c b/src/func_8002E028.c new file mode 100644 index 0000000..54221f3 --- /dev/null +++ b/src/func_8002E028.c @@ -0,0 +1,45 @@ +/* + * func_8002E028 — 72 bytes at 0x8002E028..0x8002E070 + * + * Framed routine: calls one routine with a global, its own argument and its own pointer, and + * clears the pointed-to word when the call reports success. + * + * 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 + * move a1,v0 00402821 a1 = a0 + * lui a0,0x8012 3c048012 \ + * lw a0,0x235c(a0) 8c84235c / a0 = *(int *)0x8012235C (D_8012235C) + * sw ra,0x14(sp) afbf0014 save ra + * jal 0x800277ac 0c009deb call func_800277AC + * move a2,s0 02003021 a2 = a1 (delay slot) + * beq v0,zero,0x8002e05c 10400002 if (v0 == 0) goto epilogue + * nop 00000000 (delay slot) + * sw zero,0x0(s0) ae000000 *a1 = 0 + * 0x8002e05c: + * 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 call's three arguments are set up as: a0 = the global 0x8012235C (a same-register + * `lui`+`lw` symbol load, cookbook finding 2), a1 = the incoming first argument (via a v0 + * round-trip, because a0 was needed for the global), a2 = the incoming second argument (parked + * in s0). The second argument is saved in **s0** because it is also the store base after the + * call. The result is tested for zero and the pointed-to word is cleared on success. + * + * LIMITS: the function name, the callee, the global 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 D_8012235C; +extern int func_800277AC(int a0, int a1, int a2); + +void func_8002E028(int a0, int *a1) +{ + if (func_800277AC(D_8012235C, a0, a1)) + *a1 = 0; +} diff --git a/src/func_8002F160.c b/src/func_8002F160.c new file mode 100644 index 0000000..adc0292 --- /dev/null +++ b/src/func_8002F160.c @@ -0,0 +1,53 @@ +/* + * func_8002F160 — 68 bytes at 0x8002F160..0x8002F1A4 + * + * Framed routine: calls one routine with a zero, then if a `gp`-relative byte flag is set it + * clears the flag and calls a second routine with an address, a literal and a zero. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * jal 0x800acac8 0c02b2b2 call func_800ACAC8 + * addu a0,zero,zero 00002021 a0 = 0 (delay slot) + * lbu v0,0x9dc(gp) 93a209dc v0 = *(unsigned char *)(gp + 0x9dc) + * nop 00000000 load-delay slot + * beq v0,zero,0x8002f194 10400005 if (v0 == 0) goto epilogue + * li a1,0x28 24050028 a1 = 0x28 (delay slot) + * sb zero,0x9dc(gp) a3a009dc *(unsigned char *)(gp + 0x9dc) = 0 + * lui a0,0x8003 3c048003 \ + * addiu a0,a0,-0xee8 2484f118 / a0 = 0x8002F118 (D_8002F118) + * jal 0x8002d0a8 0c00b42a call func_8002D0A8 + * addu a2,zero,zero 00003021 a2 = 0 (delay slot) + * 0x8002f194: + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * `0x9dc(gp)` is a `gp`-relative access off gp 0x80121938, i.e. 0x80122314, which the registry + * already carries with the `gp` marker. It is read and cleared as a **byte** (`lbu`/`sb`) and the + * clear happens before the second call. The `0x28` argument is materialised in the guard's + * branch delay slot, so it is set up on the path that reaches the call. + * + * The address carries: `lui 0x8003` with the signed displacement -0xee8 gives **0x8002F118**, + * not 0x8003F118. + * + * LIMITS: the function name, both callees, the flag's meaning, the pointed-to object and the + * literal 0x28 are hypotheses; only the bytes are evidence. The routine sets no result, so it is + * `void`. + */ + +extern unsigned char D_80122314; +extern char D_8002F118[]; +extern void func_800ACAC8(int a0); +extern void func_8002D0A8(char *a0, int a1, int a2); + +void func_8002F160(void) +{ + func_800ACAC8(0); + + if (D_80122314 != 0) { + D_80122314 = 0; + func_8002D0A8(D_8002F118, 0x28, 0); + } +} diff --git a/src/func_80031F78.c b/src/func_80031F78.c new file mode 100644 index 0000000..cf8b668 --- /dev/null +++ b/src/func_80031F78.c @@ -0,0 +1,64 @@ +/* + * func_80031F78 — 76 bytes at 0x80031F78..0x80031FC4 + * + * Leaf routine that copies either one or four words out of a structure into the caller's buffer, + * depending on a flag word. + * + * The observed instructions are: + * lw a2,0xa4(a0) 8c8600a4 a2 = *(int *)(a0 + 0xa4) + * nop 00000000 load-delay slot + * lw v0,0x514(a2) 8cc20514 v0 = *(int *)(a2 + 0x514) + * nop 00000000 load-delay slot + * bne v0,zero,0x80031f9c 14400005 if (v0 != 0) goto the four-word path + * move a3,a1 00a03821 a3 = a1 (delay slot) + * lw v0,0x54c(a2) 8cc2054c v0 = *(int *)(a2 + 0x54c) + * j 0x80031fbc 0800c7ef goto epilogue + * sw v0,0x0(a3) ace20000 *a1 = v0 (delay slot) + * 0x80031f9c: + * lw v0,0x54c(a2) 8cc2054c v0 = *(int *)(a2 + 0x54c) + * lw v1,0x550(a2) 8cc30550 v1 = *(int *)(a2 + 0x550) + * lw a0,0x554(a2) 8cc40554 a0 = *(int *)(a2 + 0x554) + * lw a1,0x558(a2) 8cc50558 a1 = *(int *)(a2 + 0x558) + * sw v0,0x0(a3) ace20000 a1[0] = v0 + * sw v1,0x4(a3) ace30004 a1[1] = v1 + * sw a0,0x8(a3) ace40008 a1[2] = a0 + * sw a1,0xc(a3) ace5000c a1[3] = a1 + * 0x80031fbc: + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The four-word path loads **all four words before storing any**, which is what puts the loads in + * v0/v1/a0/a1 and requires the destination pointer to be parked in a3 first — so the source reads + * four consecutive fields and writes four consecutive elements, and cc1 batched the loads because + * the registers were free. The one-word path re-reads only the first field rather than reusing the + * four-word path's first load, which is why the two arms each carry their own `lw ... 0x54c`. + * + * The four loads landing in **a1** (the argument register) is the tell that the else arm is a + * four-word STRUCT ASSIGNMENT rather than four element stores: element stores keep the destination + * pointer in a1 and pick other registers for the loaded values, which costs 16 extra bytes + * (measured: 92 vs 76). Writing `*(struct Q4 *)a1 = *(struct Q4 *)(a2 + 0x54c);` reproduces the + * original exactly, and the pointer copy into a3 is what the compiler emits to free a1. + * + * The pointer hop is `a0 + 0xa4` then the fields at +0x514 (the flag) and +0x54c..+0x558 (the + * data), so the four data words are consecutive and the flag is 0x38 bytes before them. + * + * LIMITS: the function name, the pointer chain, the flag field, the four data fields and the claim + * that the second argument is an output buffer are hypotheses; only the bytes are evidence. All + * accesses are 32-bit. Whether the one-word path is a degenerate case or a different record type is + * not recoverable. + */ + +struct func_80031F78_quad { + int w[4]; +}; + +void func_80031F78(int a0, int *a1) +{ + int a2 = *(int *)(a0 + 0xa4); + + if (*(int *)(a2 + 0x514) == 0) { + a1[0] = *(int *)(a2 + 0x54c); + } else { + *(struct func_80031F78_quad *)a1 = *(struct func_80031F78_quad *)(a2 + 0x54c); + } +} diff --git a/src/func_80036A0C.c b/src/func_80036A0C.c new file mode 100644 index 0000000..acd032b --- /dev/null +++ b/src/func_80036A0C.c @@ -0,0 +1,43 @@ +/* + * func_80036A0C — 72 bytes at 0x80036A0C..0x80036A54 + * + * Framed routine: calls one routine, then passes its result into a second call along with the + * original first and third arguments. + * + * The observed instructions are: + * addiu sp,sp,-0x20 27bdffe0 frame, 32 bytes + * sw s0,0x10(sp) afb00010 save s0 + * move s0,a0 00808021 s0 = a0 + * sw s1,0x14(sp) afb10014 save s1 + * move s1,a2 00c08821 s1 = a2 + * sw ra,0x18(sp) afbf0018 save ra + * jal 0x80032258 0c00c896 call func_80032258 + * addu a1,zero,zero 00002821 a1 = 0 (delay slot) + * move a0,s0 02002021 a0 = s0 + * move a1,v0 00402821 a1 = result + * jal 0x80032300 0c00c8c0 call func_80032300 + * move a2,s1 02203021 a2 = s1 (delay slot) + * lw ra,0x18(sp) 8fbf0018 restore ra + * lw s1,0x14(sp) 8e310014 restore s1 + * lw s0,0x10(sp) 8e100010 restore s0 + * addiu sp,sp,0x20 27bd0020 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The first and third arguments are parked in callee-saved registers (s0, s1) before the first + * call because both are needed after it; the second argument is not used at all, so the first + * call is `f(a0, 0)` and the second is `g(a0, result, a2)`. Both `jal` delay slots carry the + * next argument rather than a nop. + * + * LIMITS: the function names, both callees and the claim that the first call's result becomes the + * second call's second argument are hypotheses; only the bytes are evidence. Whether the first + * callee's zero second argument is a flag or a count is not recoverable. + */ + +extern int func_80032258(int a0, int a1); +extern void func_80032300(int a0, int a1, int a2); + +void func_80036A0C(int a0, int a1, int a2) +{ + func_80032300(a0, func_80032258(a0, 0), a2); +} diff --git a/src/func_80036A9C.c b/src/func_80036A9C.c new file mode 100644 index 0000000..a3bdb4e --- /dev/null +++ b/src/func_80036A9C.c @@ -0,0 +1,47 @@ +/* + * func_80036A9C — 60 bytes at 0x80036A9C..0x80036AD8 + * + * Leaf routine that indexes two nested tables and copies out one word. + * + * The observed instructions are: + * lw v1,0x134(a0) 8c830134 v1 = *(int *)(a0 + 0x134) + * nop 00000000 load-delay slot + * sll v0,v1,0x4 00031080 v0 = v1 * 16 + * subu v0,v0,v1 00431023 v0 = 16a - a (= 15a) + * sll v0,v0,0x2 00021080 v0 = 60a + * addu v0,a0,v0 00821021 v0 = a0 + 60a + * lw v1,0xe60(v0) 8c430e60 v1 = *(int *)(v0 + 0xe60) + * nop 00000000 load-delay slot + * sll v0,v1,0x3 000310c0 v0 = v1 * 8 + * subu v0,v0,v1 00431023 v0 = 8b - b (= 7b) + * sll v0,v0,0x5 000210c0 v0 = 224b + * addu a0,a0,v0 00822021 a0 += 224b + * lw v0,0x164(a0) 8c820164 v0 = *(int *)(a0 + 0x164) + * jr ra 03e00008 + * sw v0,0x0(a1) aca20000 *a1 = v0 (delay slot) + * + * Two nested index computations, both strength-reduced from a non-power-of-two multiply: + * `* 60` becomes `(16a - a) << 2` and `* 224` becomes `(8b - b) << 5`, so the strides are **60** + * and **224** bytes and the first index lives at +0x134 of the base while the second lives at + * +0xe60 of the first-level element. That is the tell that both multiplies were by literals + * (cookbook finding 12 is the opposite case: a real `mult` means the operand was not a literal). + * + * The address arithmetic is **base first** (`addu v0,a0,v0` and `addu a0,a0,v0`), which finding + * 22 identifies as the `base + index * stride` spelling rather than `index * stride + base`. + * + * LIMITS: the function name, the two strides, the two index fields and the copied field at + * +0x164 are hypotheses; only the bytes are evidence. All accesses are 32-bit. Whether the + * second-level base is `a0 + 60*idx` or a nested structure is not recoverable — the code folds + * both levels onto the original pointer. + */ + +void func_80036A9C(int a0, int *a1) +{ + int v1 = *(int *)(a0 + 0x134); + int v0 = a0 + v1 * 60; + + v1 = *(int *)(v0 + 0xe60); + a0 += v1 * 224; + + *a1 = *(int *)(a0 + 0x164); +} diff --git a/src/func_80042964.c b/src/func_80042964.c new file mode 100644 index 0000000..e50fe71 --- /dev/null +++ b/src/func_80042964.c @@ -0,0 +1,53 @@ +/* + * func_80042964 — 76 bytes at 0x80042964..0x800429B0 + * + * Framed routine that calls two routines with values scaled out of a global structure and a global + * halfword. + * + * The observed instructions are: + * lui v0,0x8012 3c028012 \ + * lw v0,0x2430(v0) 8c422430 / v0 = *(int *)0x80122430 (D_80122430) + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * lh a0,0x4e(v0) 8444004e a0 = *(short *)(v0 + 0x4e) + * lh a1,0x50(v0) 84450050 a1 = *(short *)(v0 + 0x50) + * sll a0,a0,0x1 00042040 a0 *= 2 + * jal 0x80017c50 0c005f14 call func_80017C50 + * sll a1,a1,0x1 00052840 a1 *= 2 (delay slot) + * lui a0,0x8012 3c048012 \ + * lh a0,0x242c(a0) 8484242c / a0 = *(short *)0x8012242C (D_8012242C) + * nop 00000000 load-delay slot + * sll a0,a0,0x11 000427c0 a0 <<= 17 + * jal 0x80017c60 0c005f18 call func_80017C60 + * sra a0,a0,0x10 00042703 a0 = (short)a0 (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * Both callees take **16-bit** parameters and the three values come from three signed halfword + * loads. The first two are simply doubled (`sll` by 1) with no mask, which means those callees' + * parameters are wide enough that cc1 did not need to narrow them; the third is doubled AND + * narrowed with `sll 17` + `sra 16`, which is this compiler's mask-and-sign-extend for a `short` + * argument — `(x << 17) >> 16` keeps the low 16 bits of `x * 2` and sign-extends them. + * + * The pointer load is hoisted above the frame setup. The first two arguments are computed into a0 + * and a1 and the second is scheduled into the `jal` delay slot. + * + * LIMITS: the function name, both callees, the two globals and the meaning of the doubling are + * hypotheses; only the bytes are evidence. The three loads are 16-bit signed; the parameter widths + * are inferred from which values cc1 narrowed, not from a declaration. + */ + +extern int D_80122430; +extern short D_8012242C; +extern void func_80017C50(int a0, int a1); +extern void func_80017C60(short a0); + +void func_80042964(void) +{ + int v0 = D_80122430; + + func_80017C50(*(short *)(v0 + 0x4e) * 2, *(short *)(v0 + 0x50) * 2); + func_80017C60(D_8012242C * 2); +} diff --git a/src/func_8004E3FC.c b/src/func_8004E3FC.c new file mode 100644 index 0000000..55f347c --- /dev/null +++ b/src/func_8004E3FC.c @@ -0,0 +1,54 @@ +/* + * func_8004E3FC — 68 bytes at 0x8004E3FC..0x8004E440 + * + * Framed routine: calls one routine and, when its result is the sentinel -1, falls back to a + * halfword reached through a field of its own argument. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw s0,0x10(sp) afb00010 save s0 + * sw ra,0x14(sp) afbf0014 save ra + * jal 0x8004dd74 0c01375d call func_8004DD74 + * move s0,a0 00808021 s0 = a0 (delay slot) + * move v1,v0 00401821 v1 = result + * li v0,-0x1 2402ffff v0 = -1 + * bne v1,v0,0x8004e42c 14620005 if (v1 != -1) goto epilogue + * move v0,v1 00601021 v0 = v1 (delay slot) + * lw v0,0x14(s0) 8e020014 v0 = *(int *)(a0 + 0x14) + * nop 00000000 load-delay slot + * lh v0,0x0(v0) 84420000 v0 = *(short *)v0 + * 0x8004e42c: + * 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 is needed *after* the + * call as the base of the fallback load. The callee's result is copied to v1 and compared + * against a `li`-materialised -1, and the result is moved into v0 **in the branch delay slot** — + * so the non-fallback path returns v1 and the fallback path overwrites v0 with the halfword. One + * shared epilogue. + * + * The fallback is a sign-extended 16-bit load (`lh`) through a pointer at +0x14 of the argument, + * so the function's return type is at least 16-bit and the value is signed. + * + * LIMITS: the function name, the callee, the sentinel -1, the pointer field at +0x14 and the + * loaded halfword are hypotheses; only the bytes are evidence. Whether the callee's `int` result + * and the `short` fallback share a meaningful range is not established. + */ + +extern int func_8004DD74(void); + +int func_8004E3FC(int a0) +{ + int v1 = func_8004DD74(); + int v0; + + if (v1 == -1) + v0 = *(short *)(*(int *)(a0 + 0x14)); + else + v0 = v1; + + return v0; +} diff --git a/src/func_800668A8.c b/src/func_800668A8.c new file mode 100644 index 0000000..4875d09 --- /dev/null +++ b/src/func_800668A8.c @@ -0,0 +1,48 @@ +/* + * func_800668A8 — 72 bytes at 0x800668A8..0x800668F0 + * + * Framed routine: when a global halfword is not the sentinel -1, calls a routine with eight + * arguments, four of them constants. + * + * The observed instructions are: + * addiu sp,sp,-0x28 27bdffd8 frame, 40 bytes + * move a2,a0 00803021 a2 = a0 + * lui a3,0x8012 3c078012 \ + * lh a3,0x237c(a3) 84e7237c / a3 = *(short *)0x8012237C (D_8012237C) + * li v0,-0x1 2402ffff v0 = -1 + * beq a3,v0,0x800668e0 10e2000b if (a3 == -1) goto epilogue + * sw ra,0x20(sp) afbf0020 save ra (delay slot) + * li a0,0x6 24040006 a0 = 6 + * sw a1,0x10(sp) afa50010 arg5 = a1 + * li a1,0x5 24050005 a1 = 5 + * sw zero,0x14(sp) afa00014 arg6 = 0 + * sw zero,0x18(sp) afa00018 arg7 = 0 + * jal 0x8002b608 0c00ad82 call func_8002B608 + * sw zero,0x1c(sp) afa0001c arg8 = 0 (delay slot) + * 0x800668e0: + * lw ra,0x20(sp) 8fbf0020 restore ra + * addiu sp,sp,0x28 27bd0028 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The global is read as a signed **halfword** (`lh`) and compared against a `li`-materialised -1; + * the same-register `lui`+`lh` form makes it a named symbol (cookbook finding 2). The incoming + * first argument is moved to a2 before a0 is overwritten with the literal 6, so the call is + * `f(6, 5, arg0, global, arg1, 0, 0, 0)` — eight arguments, with arguments five through eight + * stored at 16(sp), 20(sp), 24(sp) and 28(sp) per the o32 convention. The frame is 40 bytes: + * 16 for the register arguments' home slots, 16 for the four stack arguments, 8 for the saved + * `ra` at 0x20. + * + * LIMITS: the function name, the callee, the global's meaning and the four constants are + * hypotheses; only the bytes are evidence. The global is 16-bit and signed; the stack arguments + * are 32-bit. + */ + +extern short D_8012237C; +extern void func_8002B608(int a0, int a1, int a2, int a3, int a4, int a5, int a6, int a7); + +void func_800668A8(int a0, int a1) +{ + if (D_8012237C != -1) + func_8002B608(6, 5, a0, D_8012237C, a1, 0, 0, 0); +} diff --git a/src/func_8006B778.c b/src/func_8006B778.c new file mode 100644 index 0000000..86f4707 --- /dev/null +++ b/src/func_8006B778.c @@ -0,0 +1,55 @@ +/* + * func_8006B778 — 72 bytes at 0x8006B778..0x8006B7C0 + * + * Framed routine that walks a `gp`-relative list of objects and calls a routine once per node. + * + * The observed instructions are: + * lw a0,0x550(gp) 8f840550 a0 = *(int *)(gp + 0x550) + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x14(sp) afbf0014 save ra + * beq a0,zero,0x8006b7ac 10800009 if (a0 == 0) goto epilogue + * sw s0,0x10(sp) afb00010 save s0 (delay slot) + * 0x8006b78c: + * lw v0,0xc(a0) 8c82000c v0 = *(int *)(a0 + 0xc) + * nop 00000000 load-delay slot + * lw s0,0x18c(v0) 8c50018c s0 = *(int *)(v0 + 0x18c) + * jal 0x800c2ce4 0c030b39 call func_800C2CE4 + * nop 00000000 (delay slot) + * move a0,s0 02002021 a0 = s0 + * bne a0,zero,0x8006b78c 1480fffb if (a0 != 0) loop + * nop 00000000 (delay slot) + * 0x8006b7ac: + * 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) + * + * `0x550(gp)` is a `gp`-relative access off gp 0x80121938, i.e. 0x80121E88, which the registry + * already carries with the `gp` marker. The next node is computed **before** the call (through + * +0xc then +0x18c) and parked in s0, because it must survive the call — so the loop advances + * after the call returns even though the load was hoisted. The call receives the *current* node + * in a0, which is why the `jal` delay slot is a `nop` and a0 is reloaded from s0 afterwards. + * + * The head load is hoisted above the frame setup, and the guard's `sw s0` is in the branch delay + * slot. + * + * LIMITS: the function name, the callee, the list layout and the field offsets are hypotheses; + * only the bytes are evidence. All accesses are 32-bit. The list is assumed acyclic — nothing in + * these bytes proves it. + */ + +extern int D_80121E88; +extern void func_800C2CE4(int a0); + +void func_8006B778(void) +{ + int a0 = D_80121E88; + + while (a0 != 0) { + int s0 = *(int *)(*(int *)(a0 + 0xc) + 0x18c); + + func_800C2CE4(a0); + a0 = s0; + } +} diff --git a/src/func_8007E8B8.c b/src/func_8007E8B8.c new file mode 100644 index 0000000..1b56228 --- /dev/null +++ b/src/func_8007E8B8.c @@ -0,0 +1,51 @@ +/* + * func_8007E8B8 — 76 bytes at 0x8007E8B8..0x8007E904 + * + * Framed routine that builds a three-word difference record on the stack and passes its address on + * along with a fourth argument. + * + * The observed instructions are: + * addiu sp,sp,-0x28 27bdffd8 frame, 40 bytes + * sw ra,0x20(sp) afbf0020 save ra + * lw a0,0xc(a1) 8ca4000c a0 = *(int *)(a1 + 0xc) + * lw v0,0x0(a2) 8cc20000 v0 = a2[0] + * lw v1,0x0(a0) 8c830000 v1 = *(int *)a0 + * move a1,a3 00e02821 a1 = a3 + * sw zero,0x14(sp) afa00014 local[1] = 0 + * subu v0,v0,v1 00431023 v0 -= v1 + * sw v0,0x10(sp) afa20010 local[0] = v0 + * lw v0,0x8(a2) 8cc20008 v0 = a2[2] + * lw v1,0x8(a0) 8c830008 v1 = *(int *)(a0 + 8) + * addiu a0,sp,0x10 27a40010 a0 = &local + * subu v0,v0,v1 00431023 v0 -= v1 + * jal 0x80010b14 0c0042c5 call func_80010B14 + * sw v0,0x18(sp) afa20018 local[2] = v0 (delay slot) + * + * The record is 12 bytes at sp+0x10..sp+0x1c and holds `a2[0] - p[0]`, 0 and `a2[2] - p[2]` where + * `p` is the pointer at +0xc of the second argument. The **first argument is never read**: a0 is + * overwritten by the load of `a1 + 0xc` before any use, so the source's first parameter is unused — + * the shape of a function whose signature must match a table slot. The fourth argument is moved to + * a1 before a0 is taken for the record address, so the call is `f(&local, arg3)`. + * + * The 40-byte frame is 12 bytes of record plus padding, 16 bytes of argument home slots and 8 bytes + * for the saved `ra` at 0x20. + * + * LIMITS: the function name, the callee, the record's meaning and the claim that a2 is an array of + * at least three words are hypotheses; only the bytes are evidence. All accesses are 32-bit. The + * first parameter's absence of use is evidence from the bytes; whether the original declared it at + * all is not recoverable. + */ + +extern void func_80010B14(int *a0, int a1); + +void func_8007E8B8(int a0, int a1, int *a2, int a3) +{ + int *p = *(int **)(a1 + 0xc); + int local[3]; + + local[0] = a2[0] - p[0]; + local[1] = 0; + local[2] = a2[2] - p[2]; + + func_80010B14(local, a3); +} diff --git a/src/func_80083470.c b/src/func_80083470.c new file mode 100644 index 0000000..461a1bb --- /dev/null +++ b/src/func_80083470.c @@ -0,0 +1,50 @@ +/* + * func_80083470 — 72 bytes at 0x80083470..0x800834B8 + * + * Framed routine: calls one routine with its argument, then sets bit 15 of a 16-byte record + * selected by that argument. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw s0,0x10(sp) afb00010 save s0 + * sw ra,0x14(sp) afbf0014 save ra + * jal 0x80083440 0c020d10 call func_80083440 + * move s0,a0 00808021 s0 = a0 (delay slot) + * lui v0,0x8012 3c028012 \ + * lw v0,0x2308(v0) 8c422308 / v0 = *(int *)0x80122308 (D_80122308) + * sll s0,s0,0x4 00108080 s0 = a0 * 16 + * addu s0,s0,v0 02028021 s0 = (a0 * 16) + base + * lw v0,0x0(s0) 8e020000 v0 = *(int *)s0 + * nop 00000000 load-delay slot + * ori v0,v0,0x8000 34428000 v0 |= 0x8000 + * sw v0,0x0(s0) ae020000 *(int *)s0 = v0 + * 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 parked in **s0** because it is used after the call as the record index, and it + * is scaled by 16 (`sll`), so each record is 16 bytes. The address arithmetic is **stride first, + * base second** (`addu s0,s0,v0`), the spelling cookbook finding 22 identifies as + * `(index * 16) + symbol` rather than `symbol + index * 16`; the rule's stated scope (a symbol or + * `gp` base) applies here, and this is the same global that finding 22's own evidence + * (0x80083504) uses. + * + * The field is read, OR-ed with 0x8000 and written back, so bit 15 of the record's first word is + * set. The call's argument is the incoming a0 unchanged (the `move s0,a0` is a copy for later, + * not an argument setup). + * + * LIMITS: the function name, the callee, the global's type, the record size of 16 bytes and the + * meaning of bit 15 are hypotheses; only the bytes are evidence. All accesses are 32-bit. + */ + +extern int D_80122308; +extern void func_80083440(int a0); + +void func_80083470(int a0) +{ + func_80083440(a0); + + *(int *)(D_80122308 + a0 * 16) |= 0x8000; +} diff --git a/src/func_8009107C.c b/src/func_8009107C.c new file mode 100644 index 0000000..b466f4e --- /dev/null +++ b/src/func_8009107C.c @@ -0,0 +1,43 @@ +/* + * func_8009107C — 52 bytes at 0x8009107C..0x800910B0 + * + * Framed routine: sets a `gp`-relative byte to 1 and calls one routine with an address, a + * literal and a zero. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * li v0,0x1 24020001 v0 = 1 + * lui a0,0x8009 3c048009 \ + * addiu a0,a0,0x1070 24841070 / a0 = 0x80091070 (D_80091070) + * li a1,0xa 2405000a a1 = 0xa + * sw ra,0x10(sp) afbf0010 save ra + * sb v0,0x5a3(gp) a3a205a3 *(char *)(gp + 0x5a3) = 1 + * jal 0x800137c8 0c004df2 call func_800137C8 + * addu a2,zero,zero 00003021 a2 = 0 (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * `0x5a3(gp)` is a `gp`-relative access off gp 0x80121938, i.e. 0x80121EDB, which the registry + * already carries with the `gp` marker, so no request row is needed. The store is **byte-wide** + * (`sb`) and happens before the call, with the constant 1 materialised into v0 at the top of + * the function. + * + * The address argument is `lui`+`addiu` (the linker-resolved symbol form, cookbook finding 4), + * the literal 0xa uses `li`, and the zero uses `addu rd,zero,zero` scheduled into the `jal` + * delay slot. + * + * LIMITS: the function name, the callee, the pointed-to object and the meaning of 0xa are + * hypotheses; only the bytes are evidence. The routine sets no result, so it is `void`. + */ + +extern char D_80121EDB; +extern char D_80091070[]; +extern void func_800137C8(char *a0, int a1, int a2); + +void func_8009107C(void) +{ + D_80121EDB = 1; + func_800137C8(D_80091070, 0xa, 0); +} diff --git a/src/func_800A9FD4.c b/src/func_800A9FD4.c new file mode 100644 index 0000000..60f5270 --- /dev/null +++ b/src/func_800A9FD4.c @@ -0,0 +1,51 @@ +/* + * func_800A9FD4 — 72 bytes at 0x800A9FD4..0x800AA01C + * + * Framed routine: calls one routine, and when it returns non-null passes the result plus its own + * two arguments to a second routine. + * + * The observed instructions are: + * addiu sp,sp,-0x20 27bdffe0 frame, 32 bytes + * sw s0,0x10(sp) afb00010 save s0 + * move s0,a1 00a08021 s0 = a1 + * sw s1,0x14(sp) afb10014 save s1 + * sw ra,0x18(sp) afbf0018 save ra + * jal 0x800a82d0 0c02a0b4 call func_800A82D0 + * move s1,a2 00c08821 s1 = a2 (delay slot) + * beq v0,zero,0x800aa004 10400004 if (v0 == 0) goto epilogue + * move a0,v0 00402021 a0 = result (delay slot) + * move a1,s0 02002821 a1 = s0 + * jal 0x800a84e8 0c02a13a call func_800A84E8 + * move a2,s1 02203021 a2 = s1 (delay slot) + * 0x800aa004: + * lw ra,0x18(sp) 8fbf0018 restore ra + * lw s1,0x14(sp) 8e310014 restore s1 + * lw s0,0x10(sp) 8e100010 restore s0 + * addiu sp,sp,0x20 27bd0020 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * Both of the wrapper's arguments are parked in callee-saved registers (s0, s1) before the first + * call because both are needed after it, and the first call's result moves into a0 in its own + * branch delay slot — so the second call is `g(result, arg1, arg2)` and the guard carries the + * argument setup. + * + * 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. + * + * LIMITS: the function names, both callees and the claim that the first call's result becomes the + * second call's first argument are hypotheses; only the bytes are evidence. The routine sets no + * result, so it is `void`. + */ + +extern int func_800A82D0(void); +extern void func_800A84E8(int a0, int a1, int a2); + +void func_800A9FD4(int a0, int a1, int a2) +{ + int v0 = func_800A82D0(); + + if (v0 != 0) + func_800A84E8(v0, a1, a2); +} diff --git a/src/func_800F7A20.c b/src/func_800F7A20.c new file mode 100644 index 0000000..dbce5cc --- /dev/null +++ b/src/func_800F7A20.c @@ -0,0 +1,45 @@ +/* + * func_800F7A20 — 52 bytes at 0x800F7A20..0x800F7A54 + * + * Framed routine that makes an **indirect** call through a function pointer taken from a global + * structure, with a literal first argument and its own argument second. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * lui v0,0x8012 3c028012 \ + * lw v0,-0x4b4(v0) 8c42fb4c / v0 = *(int *)0x8011FB4C (D_8011FB4C) + * move a1,a0 00802821 a1 = a0 + * sw ra,0x10(sp) afbf0010 save ra + * lw v0,0x14(v0) 8c420014 v0 = *(int *)(v0 + 0x14) + * nop 00000000 load-delay slot + * jalr v0 0040f809 call v0 + * li a0,0x4 24040004 a0 = 4 (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The **fourth** indirect-call wrapper through the same global (the others are func_800F7990 at + * +0xc, func_800F79F0 at +4 and func_800F7A54 at +0x14), so the global holds a table of + * callable entries and each wrapper invokes a different slot. This one differs from its + * siblings in that it also **passes arguments**: the wrapper's own argument moves to a1 and the + * literal 4 is materialised into a0 in the `jal` delay slot, so the callee takes at least two + * arguments. + * + * The address arithmetic carries: `lui 0x8012` with the signed displacement -0x4b4 gives + * **0x8011FB4C**, not 0x8012FB4C. + * + * LIMITS: the function name, the claim that +0x14 holds a function pointer, the callee's + * identity and the meaning of 4 are hypotheses; only the bytes are evidence. No result is set, + * so the wrapper is `void`; whether the callee returns a value that is discarded is not + * recoverable. + */ + +extern int D_8011FB4C; + +void func_800F7A20(int a0) +{ + void (*fn)(int, int) = *(void (**)(int, int))(D_8011FB4C + 0x14); + + fn(4, a0); +} diff --git a/src/func_8010A748.c b/src/func_8010A748.c index f188a38..d104ec0 100644 --- a/src/func_8010A748.c +++ b/src/func_8010A748.c @@ -1,50 +1,54 @@ /* * func_8010A748 — 68 bytes at 0x8010A748..0x8010A78C * - * Writes a 16-bit value into a table indexed by the first argument, shifting the - * value down by a runtime amount from a global when a flag is set. The index - * scaling is computed once and lands in the first branch's delay slot. + * Leaf routine that stores a halfword into an array element, optionally shifted right by a + * global amount. * * The observed instructions are: - * bnez a2,0x8010A768 ; if (flag != 0) take the shifted arm - * sll v0,a0,0x1 ; offset = index * 2 (delay slot) - * lui v1,0x8012 - * lw v1,4168(v1) ; v1 = D_80121048 (table base) - * nop - * addu v0,v0,v1 ; table + offset (offset first) - * j 0x8010A784 - * sh a1,0(v0) ; *(short *)... = value (delay slot) - * 68: lui a0,0x8012 ; RELOAD the table base - * lw a0,4168(a0) - * lui v1,0x8012 - * lw v1,0x1070(v1) ; v1 = D_80121070 (shift amount) - * addu v0,v0,a0 ; table + offset (offset first) - * srlv v1,a1,v1 ; value >> shift <- UNSIGNED, variable - * sh v1,0(v0) - * 84: jr ra - * nop + * bne a2,zero,0x8010a768 14c00007 if (a2 != 0) goto the shifted path + * sll v0,a0,0x1 00041040 v0 = a0 * 2 (delay slot) + * lui v1,0x8012 3c038012 \ + * lw v1,0x1048(v1) 8c631048 / v1 = *(int *)0x80121048 (D_80121048) + * nop 00000000 load-delay slot + * addu v0,v0,v1 00431021 v0 = index * 2 + base + * j 0x8010a784 080429e1 goto epilogue + * sh a1,0x0(v0) a4450000 *(short *)v0 = a1 (delay slot) + * 0x8010a768: + * lui a0,0x8012 3c048012 \ + * lw a0,0x1048(a0) 8c841048 / a0 = *(int *)0x80121048 + * lui v1,0x8012 3c038012 \ + * lw v1,0x1070(v1) 8c631070 / v1 = *(int *)0x80121070 (D_80121070) + * addu v0,v0,a0 00441021 v0 = index * 2 + base + * srlv v1,a1,v1 00650806 v1 = a1 >> v1 + * sh v1,0x0(v0) a4410000 *(short *)v0 = v1 + * 0x8010a784: + * jr ra 03e00008 + * nop 00000000 (delay slot) * - * The table base is loaded in EACH arm, so the source references the global in - * both — caching it in one local would emit a single load and change the bytes. - * The index scaling must ALSO stay inline: binding `index * 2` to a local makes - * cc1 compute it into the argument register before the branch and then copy it, - * which costs an extra instruction (72 vs 68). Written inline, cc1 CSEs it into - * the branch delay slot exactly as the original does. The shift is `srlv`, so the - * shifted operand is unsigned. + * The `sll` that scales the index by 2 is computed in the branch delay slot and is shared by + * **both** arms, so the element type is 16-bit and the stride is 2 bytes. The unshifted arm + * stores the argument directly; the shifted arm shifts it by the value in the global + * 0x80121070 with `srlv` — a **logical** shift by a register, so the shifted value is unsigned. * - * LIMITS: the two symbol names, the element stride (2), the field width and the - * parameter types are hypotheses read from the instruction shape; the offsets - * 4168/0x1070 are facts about this executable's globals. What the table holds is - * unknown and is not guessed here. Only the compiled bytes are evidence. + * Both globals are read with the same-register `lui`+`lw` symbol form (cookbook finding 2), so + * they are written as named symbols and not as literal addresses (finding 5). The two arms each + * reload the array base rather than sharing one load, which is what the duplicated `lui`/`lw` + * pairs show. + * + * LIMITS: the function name, the array, the shift-amount global and the meaning of the third + * argument are hypotheses; only the bytes are evidence. The array elements are 16-bit. Whether + * the third argument is a flag or a count is not recoverable — only that zero selects the + * unshifted path. */ extern int D_80121048; extern int D_80121070; -void func_8010A748(int index, int value, int flag) { - if (flag == 0) - *(short *)((char *)D_80121048 + index * 2) = value; - else - *(short *)((char *)D_80121048 + index * 2) = - (short)((unsigned int)value >> D_80121070); +void func_8010A748(int a0, unsigned int a1, int a2) +{ + if (a2 == 0) { + *(short *)(D_80121048 + a0 * 2) = a1; + } else { + *(short *)(D_80121048 + a0 * 2) = a1 >> D_80121070; + } }