From e317c8ed5ad4b982f1f67160b2e4eaabd5c865ef Mon Sep 17 00:00:00 2001 From: Christopher Williams Date: Thu, 24 Sep 2026 00:26:45 -0400 Subject: [PATCH] =?UTF-8?q?phase9:=20merge=20cycle=202=20close=20=E2=80=94?= =?UTF-8?q?=20260=20regions=20/=20251=20distinct=20bodies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 14 more regions merged and gated MATCH (c_regions=260, whole-binary SHA-1 e173426c157384ebf1b6caf8c6fea18a85a14af9); make check green (AGREE + MATCH). Full audit passed from clean state (CMP_OK, SHA-1, 223 tests, gate, extents). Worker A: 12 more (tier-2 wrappers incl. 7-arg o32 forwarder, GTE-forwarder adjacent to the registered maspsx=off 0x800F3160, and 0x80036380 — a byte-proven 48-byte empty-frame registered under an honest limits header). New: maspsx-conflict scope limit (finding 17 corrected: maspsx=off unsafe for bodies with move pseudo-instructions — addu-vs-or expansion), alloc- tiebreak negative 0x80019700. Worker C: 2 boundary-crossing matches accepted under the decided policy (verified work is claimable across regenerated partition edges); handoff 0x8010A748 accepted. F10 named-pointer base-first for register bases (closes the F5 gap), F11 duplicate stores need volatile, F12 if-conversion is a compiler class (0x80010418 exclusion recommended and accepted). Collision policy decided: verified work is claimable regardless of the regenerated partition boundary; overlapping staging is deduped by the merge. All boundary crossings reported by the workers. --- config/regions.tsv | 14 ++++++++++ src/func_80017D48.c | 49 +++++++++++++++++++++++++++++++++++ src/func_80021F24.c | 39 ++++++++++++++++++++++++++++ src/func_80026F14.c | 43 +++++++++++++++++++++++++++++++ src/func_8002D5D0.c | 51 ++++++++++++++++++++++++++++++++++++ src/func_80036380.c | 39 ++++++++++++++++++++++++++++ src/func_80041A24.c | 44 +++++++++++++++++++++++++++++++ src/func_800453C0.c | 50 +++++++++++++++++++++++++++++++++++ src/func_80068D54.c | 29 +++++++++++++++++++++ src/func_80068F6C.c | 38 +++++++++++++++++++++++++++ src/func_80072BA8.c | 42 ++++++++++++++++++++++++++++++ src/func_800900A0.c | 36 ++++++++++++++++++++++++++ src/func_80090C8C.c | 30 +++++++++++++++++++++ src/func_8009AC08.c | 29 +++++++++++++++++++++ src/func_800AC818.c | 63 +++++++++++++++++++++++++++++++++++++++++++++ src/func_800AC85C.c | 33 ++++++++++++++++++++++++ src/func_800BFEC0.c | 31 ++++++++++++++++++++++ src/func_800F3140.c | 35 +++++++++++++++++++++++++ src/func_800F3A00.c | 30 +++++++++++++++++++++ src/func_800F8B18.c | 29 +++++++++++++++++++++ src/func_800F8F5C.c | 30 +++++++++++++++++++++ src/func_800FA960.c | 31 ++++++++++++++++++++++ src/func_8010A748.c | 50 +++++++++++++++++++++++++++++++++++ 23 files changed, 865 insertions(+) create mode 100644 src/func_80017D48.c create mode 100644 src/func_80021F24.c create mode 100644 src/func_80026F14.c create mode 100644 src/func_8002D5D0.c create mode 100644 src/func_80036380.c create mode 100644 src/func_80041A24.c create mode 100644 src/func_800453C0.c create mode 100644 src/func_80068D54.c create mode 100644 src/func_80068F6C.c create mode 100644 src/func_80072BA8.c create mode 100644 src/func_800900A0.c create mode 100644 src/func_80090C8C.c create mode 100644 src/func_8009AC08.c create mode 100644 src/func_800AC818.c create mode 100644 src/func_800AC85C.c create mode 100644 src/func_800BFEC0.c create mode 100644 src/func_800F3140.c create mode 100644 src/func_800F3A00.c create mode 100644 src/func_800F8B18.c create mode 100644 src/func_800F8F5C.c create mode 100644 src/func_800FA960.c create mode 100644 src/func_8010A748.c diff --git a/config/regions.tsv b/config/regions.tsv index d67cb12..e31825c 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -34,6 +34,7 @@ 0x80017C50 0x80017C60 src/func_80017C50.c 0x80017C60 0x80017C6C src/func_80017C60.c 0x80017D1C 0x80017D48 src/func_80017D1C.c +0x80017D48 0x80017D88 src/func_80017D48.c 0x80017DD0 0x80017DF0 src/func_80017DD0.c 0x800182D4 0x800182F4 src/func_800182D4.c 0x800198C0 0x800198F4 src/func_800198C0.c @@ -67,6 +68,7 @@ 0x8002D288 0x8002D2A0 src/func_8002D288.c gp=-D_80121F84 0x8002D2A0 0x8002D2BC src/func_8002D2A0.c 0x8002D2BC 0x8002D2D4 src/func_8002D2BC.c +0x8002D5D0 0x8002D608 src/func_8002D5D0.c 0x8002DEB4 0x8002DF1C src/func_8002DEB4.c 0x8002E7C4 0x8002E7E4 src/func_8002E7C4.c 0x8002F2F8 0x8002F300 src/func_8002F2F8.c @@ -76,16 +78,19 @@ 0x80036308 0x80036328 src/func_80036308.c 0x8003636C 0x80036378 src/func_8003636C.c 0x80036378 0x80036380 src/func_80036378.c +0x80036380 0x80036390 src/func_80036380.c 0x80036AD8 0x80036B14 src/func_80036AD8.c 0x8003768C 0x800376CC src/func_8003768C.c 0x80038788 0x80038790 src/func_80038788.c 0x80038790 0x8003879C src/func_80038790.c 0x8003B2F0 0x8003B320 src/func_8003B2F0.c 0x8003B320 0x8003B34C src/func_8003B320.c +0x80041A24 0x80041A58 src/func_80041A24.c 0x80042088 0x80042090 src/func_80042088.c 0x80042D64 0x80042D88 src/func_80042D64.c 0x80043D8C 0x80043DC4 src/func_80043D8C.c 0x80044F58 0x80044FA4 src/func_80044F58.c gp=-D_80121BFC +0x800453C0 0x800453F8 src/func_800453C0.c 0x8004C060 0x8004C090 src/func_8004C060.c 0x8004C090 0x8004C0AC src/func_8004C090.c 0x8004C0AC 0x8004C0F0 src/func_8004C0AC.c @@ -112,6 +117,7 @@ 0x8006F944 0x8006F95C src/func_8006F944.c 0x8006FA78 0x8006FAA0 src/func_8006FA78.c 0x8007049C 0x800704BC src/func_8007049C.c +0x80072BA8 0x80072BDC src/func_80072BA8.c 0x8007A404 0x8007A428 src/func_8007A404.c 0x8007C4EC 0x8007C524 src/func_8007C4EC.c 0x8007DC40 0x8007DC4C src/func_8007DC40.c @@ -137,6 +143,7 @@ 0x80090048 0x80090058 src/func_80090048.c 0x80090A44 0x80090A70 src/func_80090A44.c 0x80090B64 0x80090B7C src/func_80090B64.c +0x80090C8C 0x80090CAC src/func_80090C8C.c 0x800912D4 0x800912FC src/func_800912D4.c 0x800912FC 0x8009132C src/func_800912FC.c 0x80092068 0x80092088 src/func_80092068.c @@ -147,6 +154,7 @@ 0x800943C4 0x800943E0 src/func_800943C4.c 0x80099A94 0x80099AE4 src/func_80099A94.c 0x80099E14 0x80099E34 src/func_80099E14.c +0x8009AC08 0x8009AC28 src/func_8009AC08.c 0x8009D8A0 0x8009D8E0 src/func_8009D8A0.c 0x8009E8D0 0x8009E95C src/func_8009E8D0.c 0x8009F0E8 0x8009F120 src/func_8009F0E8.c @@ -155,6 +163,7 @@ 0x800A74BC 0x800A74D0 src/func_800A74BC.c 0x800A8B48 0x800A8B8C src/func_800A8B48.c 0x800AA56C 0x800AA59C src/func_800AA56C.c +0x800AC818 0x800AC85C src/func_800AC818.c 0x800ACC00 0x800ACC20 src/func_800ACC00.c 0x800AE0F4 0x800AE10C src/func_800AE0F4.c 0x800AE548 0x800AE574 src/func_800AE548.c @@ -170,9 +179,11 @@ 0x800B7230 0x800B7264 src/func_800B7230.c 0x800BBDEC 0x800BBDF8 src/func_800BBDEC.c 0x800BFE80 0x800BFEA0 src/func_800BFE80.c +0x800BFEC0 0x800BFEE0 src/func_800BFEC0.c 0x800BFEE0 0x800BFF00 src/func_800BFEE0.c 0x800C5C84 0x800C5CBC src/func_800C5C84.c 0x800F2F6C 0x800F2FB8 src/func_800F2F6C.c maspsx=off +0x800F3140 0x800F3160 src/func_800F3140.c 0x800F3160 0x800F316C src/func_800F3160.c maspsx=off 0x800F3E70 0x800F3E88 src/func_800F3E70.c 0x800F5200 0x800F521C src/func_800F5200.c @@ -189,13 +200,16 @@ 0x800F8ACC 0x800F8ADC src/func_800F8ACC.c 0x800F8ADC 0x800F8AEC src/func_800F8ADC.c 0x800F8AEC 0x800F8AF8 src/func_800F8AEC.c maspsx=off +0x800F8B18 0x800F8B38 src/func_800F8B18.c 0x800F8B38 0x800F8B58 src/func_800F8B38.c 0x800F8B58 0x800F8B6C src/func_800F8B58.c maspsx=off 0x800F8B6C 0x800F8B80 src/func_800F8B6C.c maspsx=off +0x800F8F5C 0x800F8F7C src/func_800F8F5C.c 0x800F8F7C 0x800F8F9C src/func_800F8F7C.c 0x800F8F9C 0x800F8FC0 src/func_800F8F9C.c 0x800F8FE4 0x800F8FF8 src/func_800F8FE4.c maspsx=off 0x800F8FF8 0x800F9008 src/func_800F8FF8.c +0x800FA960 0x800FA980 src/func_800FA960.c 0x800FB13C 0x800FB1BC src/func_800FB13C.c 0x800FB5D4 0x800FB5DC src/func_800FB5D4.c 0x800FB5E4 0x800FB5FC src/func_800FB5E4.c diff --git a/src/func_80017D48.c b/src/func_80017D48.c new file mode 100644 index 0000000..3156fb3 --- /dev/null +++ b/src/func_80017D48.c @@ -0,0 +1,49 @@ +/* + * func_80017D48 — 64 bytes at 0x80017D48..0x80017D88 + * + * Reads a gp-relative packed value, splits it into two 16-bit halves, and derives + * a scale factor from them: the low half is the base, the high half minus one is + * a variable shift count, and the result is the base plus a shifted complement. + * A low half of 4096 or more is clamped to a fixed 15000. + * + * The observed instructions are: + * lw v0,32(gp) ; v0 = D_80121958 (gp = 0x80121938) + * li v1,4096 + * andi a0,v0,0xffff ; low = value & 0xffff + * srl v0,v0,0x10 ; value >> 16 + * addiu v0,v0,-1 ; shift = (value >> 16) - 1 + * subu v1,v1,a0 ; 4096 - low + * srlv v1,v1,v0 ; (4096 - low) >> shift <- UNSIGNED, variable + * addu v1,a0,v1 ; result = low + that + * sltiu a0,a0,4096 ; low < 4096 <- UNSIGNED compare + * beqz a0,0x80017D7C ; if (!(low < 4096)) goto 15000 + * srl v0,v1,0x2 ; result >> 2 (delay slot) + * j 0x80017D80 + * addu v0,v1,v0 ; return result + (result >> 2) (delay slot) + * 7C: li v0,15000 + * 80: jr ra + * nop + * + * Both the variable shift (`srlv`) and the range test (`sltiu`) are UNSIGNED, so + * the source operands are unsigned — a signed source would emit `srav`/`slti`. + * + * LIMITS: the symbol name D_80121958, the constants (4096, 15000), the shift + * amount (2) and the packing (two 16-bit halves) are hypotheses read from the + * instruction shape; the offset 32 is a fact about this executable's gp layout. + * What the packed value means is unknown and is not guessed here. Only the + * compiled bytes are evidence. + */ + +extern int D_80121958; + +int func_80017D48(void) { + unsigned int value = D_80121958; + unsigned int low = value & 0xFFFF; + unsigned int shift = (value >> 16) - 1; + unsigned int result = low + ((4096 - low) >> shift); + + if (low >= 4096) + return 15000; + + return result + (result >> 2); +} diff --git a/src/func_80021F24.c b/src/func_80021F24.c new file mode 100644 index 0000000..1f330a1 --- /dev/null +++ b/src/func_80021F24.c @@ -0,0 +1,39 @@ +/* + * func_80021F24 — 44 bytes at 0x80021F24..0x80021F50 + * + * Framed wrapper: calls one routine with two `gp`-relative halfwords and its own argument. + * + * The observed instructions are: + * move a2,a0 00803021 a2 = a0 + * lh a0,0x922(gp) 87a40922 a0 = *(short *)(gp + 0x922) + * lh a1,0x924(gp) 87a50924 a1 = *(short *)(gp + 0x924) + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * jal 0x80021d90 0c008764 call func_80021D90 + * nop 00000000 (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The incoming argument moves to a2 **before** a0 and a1 are overwritten, so the call is + * `f(global0, global1, arg)` — the argument is shifted two slots. Both globals are read as + * signed **halfwords** (`lh`), which fixes them as 16-bit signed objects, and both loads are + * hoisted above the frame setup. + * + * `0x922(gp)` and `0x924(gp)` are `gp`-relative accesses off gp 0x80121938, i.e. 0x8012225A + * and 0x8012225C, both already `gp`-marked in `config/symbols.tsv` as `D_8012225A` and + * `D_8012225C`, so no request row is needed. + * + * LIMITS: the function name, the callee and the two globals' meanings are hypotheses; only + * the bytes are evidence. The wrapper sets no result, so it is `void`. + */ + +extern short D_8012225A; +extern short D_8012225C; +extern void func_80021D90(int a0, int a1, int a2); + +void func_80021F24(int a0) +{ + func_80021D90(D_8012225A, D_8012225C, a0); +} diff --git a/src/func_80026F14.c b/src/func_80026F14.c new file mode 100644 index 0000000..58de0f7 --- /dev/null +++ b/src/func_80026F14.c @@ -0,0 +1,43 @@ +/* + * func_80026F14 — 40 bytes at 0x80026F14..0x80026F3C + * + * Framed routine that makes an **indirect** call through a `gp`-relative function pointer, + * but only when the pointer is non-null. + * + * The observed instructions are: + * lw v0,0x214(gp) 8f820214 v0 = *(int *)(gp + 0x214) + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * beq v0,zero,0x80026f2c 10400003 if (v0 == 0) goto epilogue + * sw ra,0x10(sp) afbf0010 save ra (delay slot) + * jalr v0 0040f809 call v0 + * nop 00000000 (delay slot) + * 0x80026f2c: + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * `0x214(gp)` is a `gp`-relative access off gp 0x80121938, i.e. 0x80121B4C. It is **not** + * in `config/symbols.tsv`, so this region needs the registry row `D_80121B4C 0x80121B4C gp` + * (staged in `symbols-request.tsv`); without the `gp` marker the harness emits an absolute + * access and the region cannot match. + * + * The pointer is loaded **once** into v0 and used for both the null test and the `jalr`, so + * the source binds it to a local rather than reading the global twice. The guard is checked + * before the frame is established: the `sw ra` that saves the return address is placed in + * the branch's delay slot, so `ra` is only saved on the path that reaches the call. + * + * LIMITS: the function name, the claim that the global holds a function pointer and the + * callee's identity are hypotheses; only the bytes are evidence — the value is data, so + * nothing here proves it is code. The routine sets no result, so it is `void`. + */ + +extern int D_80121B4C; + +void func_80026F14(void) +{ + void (*fn)(void) = (void (*)(void))D_80121B4C; + + if (fn != 0) + fn(); +} diff --git a/src/func_8002D5D0.c b/src/func_8002D5D0.c new file mode 100644 index 0000000..30e9942 --- /dev/null +++ b/src/func_8002D5D0.c @@ -0,0 +1,51 @@ +/* + * func_8002D5D0 — 56 bytes at 0x8002D5D0..0x8002D608 + * + * Leaf routine that appends a value to a bounded array, using a `gp`-relative counter. + * + * The observed instructions are: + * lh a1,0x2b8(gp) 87a102b8 a1 = *(short *)(gp + 0x2b8) + * addiu sp,sp,-0x8 27bdfff8 frame, 8 bytes + * slti v0,a1,0x1e 28a2001e v0 = (a1 < 30) signed + * beq v0,zero,0x8002d5fc 10400007 if (!(a1 < 30)) goto epilogue + * move v1,a1 00a01821 v1 = a1 (delay slot) + * addiu v0,v1,0x1 24620001 v0 = v1 + 1 + * sh v0,0x2b8(gp) a7a202b8 *(short *)(gp + 0x2b8) = v0 + * sll v0,a1,0x2 00051080 v0 = a1 * 4 + * lui at,0x8013 3c018013 \ + * addu at,at,v0 00220821 / at = 0x80130000 + a1 * 4 + * sw a0,-0x5390(at) ac24ac70 *(int *)(at - 0x5390) = a0 + * 0x8002d5fc: + * addiu sp,sp,0x8 27bd0008 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * `0x2b8(gp)` is a `gp`-relative access off gp 0x80121938, i.e. 0x80121BF0, which the + * registry already carries as `D_80121BF0` with the `gp` marker. It is read as a signed + * **halfword** (`lh`) and incremented as one (`sh`), and the bound test is signed + * (`slti`), so the counter is a 16-bit signed object and 30 is the limit. + * + * The array store is the explicit indexed symbol form: `lui at,%hi` / `addu at,at,index` / + * `sw rt,%lo(at)`. The displacement is **-0x5390 with high half 0x8013**, i.e. the base is + * 0x80130000 - 0x5390 = **0x8012AC70**; reading the pair as 0x8013AC70 would be wrong. + * + * The 8-byte frame is allocated and never written — cc1 reserves it for the local and then + * keeps everything in registers. It is reproduced by declaring the counter as a local + * rather than by any flag. + * + * LIMITS: the function name, the array, the counter's purpose and the bound 30 are + * hypotheses; only the bytes are evidence. The array elements are 32-bit. + */ + +extern short D_80121BF0; +extern int D_8012AC70[]; + +void func_8002D5D0(int a0) +{ + short i = D_80121BF0; + + if (i < 30) { + D_80121BF0 = i + 1; + D_8012AC70[i] = a0; + } +} diff --git a/src/func_80036380.c b/src/func_80036380.c new file mode 100644 index 0000000..43819e7 --- /dev/null +++ b/src/func_80036380.c @@ -0,0 +1,39 @@ +/* + * func_80036380 — 16 bytes at 0x80036380..0x80036390 + * + * An empty function that still allocates and releases a 48-byte stack frame. + * + * The observed instructions are: + * addiu sp,sp,-0x30 27bdffd0 allocate 48 bytes + * addiu sp,sp,0x30 27bd0030 release 48 bytes + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * There is no body, no saved register and **no `sw ra`** — so this is not a call frame; + * `ra` is untouched and the function is a leaf. The frame exists only because a local was + * allocated, and the exact pair `addiu sp,sp,-0x30` / `addiu sp,sp,0x30` with nothing in + * between is what this compiler emits when a local object is *allocated* but its uses are + * all gone: the size survives, the accesses do not. + * + * Three spellings were measured to find what keeps the frame: + * - `volatile int buf[12];` (unused) -> 16 bytes, MATCH + * - `int buf[12]; buf[11] = 0;` (dead store) -> 20 bytes (the store survives) + * - `int buf[12]; (void)buf;` (address taken, no use) -> 16 bytes, MATCH + * The last is the spelling used here, because taking the local's address is the ordinary + * reason a compiler allocates stack it then never touches, and it does not depend on a + * qualifier that would have no other effect. + * + * LIMITS: the function name is a hypothesis and there is no evidence at all about what the + * local was — only that 48 bytes of stack were reserved for something. A body that was + * conditionally compiled out, a removed call, or an unused scratch buffer are all consistent + * with these four instructions; the C below is a reconstruction of the *codegen*, not a + * recovery of the original source. This is the one region in this batch whose C cannot be + * argued from the bytes beyond the frame size. + */ + +void func_80036380(void) +{ + int buf[12]; + + (void)buf; +} diff --git a/src/func_80041A24.c b/src/func_80041A24.c new file mode 100644 index 0000000..e200751 --- /dev/null +++ b/src/func_80041A24.c @@ -0,0 +1,44 @@ +/* + * func_80041A24 — 52 bytes at 0x80041A24..0x80041A58 + * + * Framed routine: calls a routine that returns a pointer, and if the pointer is + * non-null initialises two bytes at its start. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * jal 0x800419d0 0c010674 call func_800419D0 + * nop 00000000 (delay slot) + * move v1,v0 00401821 v1 = result + * beq v1,zero,0x80041a48 10200004 if (v1 == 0) goto epilogue + * li v0,0xff 240200ff v0 = 0xff (delay slot) + * sb zero,0x0(v1) a0200000 *(char *)v1 = 0 + * sb v0,0x1(v1) a0220001 *(char *)(v1 + 1) = 0xff + * 0x80041a48: + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The callee's result is copied to v1 rather than tested in place, and the constant + * 0xff is materialised in the guard's branch delay slot — so the byte value is written + * by the guard, not inside the taken block. Both stores are byte-wide (`sb`), which is + * what fixes the pointed-to type as `char`. + * + * LIMITS: the function name, the callee, the two initialised bytes and their meaning are + * hypotheses; only the bytes are evidence. The routine sets no result of its own, so it + * is `void`; whether the callee's pointer is to a 2-byte object or a longer structure is + * not recoverable from these bytes. + */ + +extern char *func_800419D0(void); + +void func_80041A24(void) +{ + char *v1 = func_800419D0(); + + if (v1 != 0) { + v1[0] = 0; + v1[1] = 0xff; + } +} diff --git a/src/func_800453C0.c b/src/func_800453C0.c new file mode 100644 index 0000000..e855a11 --- /dev/null +++ b/src/func_800453C0.c @@ -0,0 +1,50 @@ +/* + * func_800453C0 — 56 bytes at 0x800453C0..0x800453F8 + * + * 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,0x800453e8 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 0x8005a81c 0c016a07 call func_8005A81C + * nop 00000000 (delay slot) + * 0x800453e8: + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * Two things are scheduled unusually and both follow from the C. The flag load is hoisted + * **above** the frame setup, and the `sw ra` that establishes the frame is placed in the + * guard's branch delay slot — so `ra` is only saved on the path that reaches the call, and + * the epilogue's `lw ra` is likewise only correct on that path. The incoming argument is + * moved to a1 before a0 is overwritten with the global, so the call is + * `f(global, arg)` with the two operands in the opposite order from the parameters. + * + * Both globals use the same-register `lui`+load form (cookbook finding 2), so they are + * written as named symbols. The address arithmetic is exact and 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_8005A81C(int a0, int a1); + +void func_800453C0(int a0) +{ + unsigned char v0 = D_80122374; + + if (v0 == 0) + func_8005A81C(D_8012D88C, a0); +} diff --git a/src/func_80068D54.c b/src/func_80068D54.c new file mode 100644 index 0000000..6bccd96 --- /dev/null +++ b/src/func_80068D54.c @@ -0,0 +1,29 @@ +/* + * func_80068D54 — 36 bytes at 0x80068D54..0x80068D78 + * + * Framed wrapper: calls one routine with a zero second argument and a literal third. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * addu a1,zero,zero 00002821 a1 = 0 + * jal 0x800f6f00 0c03dbc0 call func_800F6F00 + * li a2,0x190 24060190 a2 = 0x190 (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * a0 is forwarded untouched; the zero uses `addu rd,zero,zero` and the literal uses `li`. + * The zero is issued before the `jal` while the literal is scheduled into its delay slot. + * + * LIMITS: the function name, the callee and the meaning of 0x190 are hypotheses; only the + * bytes are evidence. The wrapper sets no result, so it is `void`. + */ + +extern void func_800F6F00(int a0, int a1, int a2); + +void func_80068D54(int a0) +{ + func_800F6F00(a0, 0, 0x190); +} diff --git a/src/func_80068F6C.c b/src/func_80068F6C.c new file mode 100644 index 0000000..7dc70d1 --- /dev/null +++ b/src/func_80068F6C.c @@ -0,0 +1,38 @@ +/* + * func_80068F6C — 44 bytes at 0x80068F6C..0x80068F98 + * + * Framed wrapper: calls one routine with four zero arguments and masks the result to a byte. + * + * The observed instructions are: + * addiu sp,sp,-0x20 27bdffe0 frame, 32 bytes + * addu a1,zero,zero 00002821 a1 = 0 + * addu a2,zero,zero 00003021 a2 = 0 + * addu a3,zero,zero 00003821 a3 = 0 + * sw ra,0x18(sp) afbf0018 save ra + * jal 0x80068d78 0c01a35e call func_80068D78 + * sw zero,0x10(sp) afa00010 arg5 = 0 (delay slot) + * lw ra,0x18(sp) 8fbf0018 restore ra + * andi v0,v0,0xff 304200ff v0 &= 0xff + * jr ra 03e00008 + * addiu sp,sp,0x20 27bd0020 frame release (delay slot) + * + * The frame is 32 bytes: 16 for the four register arguments' home slots, 8 for the fifth + * argument at 0x10, and 8 for the saved `ra` at 0x18 — so the call takes **five** arguments, + * with a0 forwarded and the rest zero. All four register zeroes use `addu rd,zero,zero` (this + * compiler's zeroing form, not `li rd,0`), and the stack argument is scheduled into the `jal` + * delay slot. + * + * The `andi ...,0xff` after the call is the mask this ABI applies when an `unsigned char` + * return value is widened to `int`, so the callee returns `unsigned char` and the wrapper + * widens it. That is why the return type below is `int` with a `unsigned char` callee. + * + * LIMITS: the function name, the callee and the meaning of the zeros are hypotheses; only + * the bytes are evidence. + */ + +extern unsigned char func_80068D78(int a0, int a1, int a2, int a3, int a4); + +int func_80068F6C(int a0) +{ + return func_80068D78(a0, 0, 0, 0, 0); +} diff --git a/src/func_80072BA8.c b/src/func_80072BA8.c new file mode 100644 index 0000000..4af2ea0 --- /dev/null +++ b/src/func_80072BA8.c @@ -0,0 +1,42 @@ +/* + * func_80072BA8 — 52 bytes at 0x80072BA8..0x80072BDC + * + * Framed routine: calls the routine that immediately follows it with seven arguments, + * three of them constants passed on the stack. + * + * The observed instructions are: + * addiu sp,sp,-0x28 27bdffd8 frame, 40 bytes + * li v0,0x8 24020008 v0 = 8 + * move a2,a3 00e03021 a2 = a3 + * li a3,-0x1 2407ffff a3 = -1 + * sw ra,0x20(sp) afbf0020 save ra + * sw zero,0x10(sp) afa00010 arg5 = 0 + * sw zero,0x14(sp) afa00014 arg6 = 0 + * jal 0x80072bdc 0c01caf7 call func_80072BDC + * sw v0,0x18(sp) afa20018 arg7 = 8 (delay slot) + * lw ra,0x20(sp) 8fbf0020 restore ra + * addiu sp,sp,0x28 27bd0028 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The callee is the **next address** (0x80072BDC = 0x80072BA8 + 0x34), so this is a + * forwarder into the following body rather than a call into unrelated code. + * + * The frame is 40 bytes: 16 for the four register arguments' home slots, 16 for the + * three extra arguments at 0x10/0x14/0x18, and 8 for the saved `ra` at 0x20. The extra + * arguments are stored at 16(sp), 20(sp) and 24(sp), which is the o32 convention for + * arguments five, six and seven — so the call takes seven arguments, with a0 and a1 + * forwarded untouched, a3 moved into a2's slot, and -1 in a3. + * + * LIMITS: the function name, the callee and the meanings of the constants (8, 0, 0) are + * hypotheses; only the bytes are evidence. The stack argument slots are written as a + * seven-parameter call; whether the original declared them individually or as a struct + * is not recoverable from these instructions. + */ + +extern void func_80072BDC(int a0, int a1, int a2, int a3, int a4, int a5, int a6); + +void func_80072BA8(int a0, int a1, int a2, int a3) +{ + func_80072BDC(a0, a1, a3, -1, 0, 0, 8); +} diff --git a/src/func_800900A0.c b/src/func_800900A0.c new file mode 100644 index 0000000..5d45f39 --- /dev/null +++ b/src/func_800900A0.c @@ -0,0 +1,36 @@ +/* + * func_800900A0 — 44 bytes at 0x800900A0..0x800900CC + * + * Framed wrapper: calls one routine with a fixed address, its own argument, and a zero. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * move a1,a0 00802821 a1 = a0 + * lui a0,0x8009 3c048009 \ + * addiu a0,a0,0x58 24840058 / a0 = 0x80090058 (D_80090058) + * jal 0x800fb6c4 0c03edb1 call func_800FB6C4 + * 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) + * + * The argument is moved to a1 before a0 is overwritten, so the call is `f(address, arg, 0)` + * with the address in the first slot. The address is materialised as `lui`+`addiu` (the + * linker-resolved symbol form, cookbook finding 4), so it is written as a named symbol and + * not a literal (finding 5's `lui`+`ori`). + * + * LIMITS: the function name, the callee, the pointed-to object and the meaning of the zero + * argument are hypotheses; only the bytes are evidence. Whether the address names data or code + * is not recoverable; it is written as a `char[]` so it stays an address. The wrapper sets no + * result, so it is `void`. + */ + +extern char D_80090058[]; +extern void func_800FB6C4(char *a0, int a1, int a2); + +void func_800900A0(int a0) +{ + func_800FB6C4(D_80090058, a0, 0); +} diff --git a/src/func_80090C8C.c b/src/func_80090C8C.c new file mode 100644 index 0000000..61b116c --- /dev/null +++ b/src/func_80090C8C.c @@ -0,0 +1,30 @@ +/* + * func_80090C8C — 32 bytes at 0x80090C8C..0x80090CAC + * + * Framed wrapper: calls one routine with its argument masked to eight bits. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * jal 0x800fe844 0c03fa11 call func_800FE844 + * andi a0,a0,0xff 308400ff a0 &= 0xff (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The `andi ...,0xff` is the promotion mask this ABI applies to an unsigned `char` + * parameter (cookbook finding 7), so the parameter is declared `unsigned char` and the + * mask is not written in the C. cc1 schedules it into the `jal` delay slot, so the + * callee receives the masked value. + * + * LIMITS: the function name and the callee's purpose are hypotheses; only the bytes are + * evidence. The wrapper sets no result, so it is `void`. + */ + +extern void func_800FE844(unsigned char a0); + +void func_80090C8C(unsigned char a0) +{ + func_800FE844(a0); +} diff --git a/src/func_8009AC08.c b/src/func_8009AC08.c new file mode 100644 index 0000000..bfcd12b --- /dev/null +++ b/src/func_8009AC08.c @@ -0,0 +1,29 @@ +/* + * func_8009AC08 — 32 bytes at 0x8009AC08..0x8009AC28 + * + * Framed wrapper: calls one routine with a zero second argument. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * jal 0x8009a904 0c026a41 call func_8009A904 + * addu a1,zero,zero 00002821 a1 = 0 (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * a0 is never touched, so the wrapper forwards its first argument; the zero uses + * `addu rd,zero,zero` (this compiler's zeroing form, not `li rd,0`) and is scheduled + * into the `jal` delay slot. + * + * LIMITS: the function name, the callee and the meaning of the zero argument are + * hypotheses; only the bytes are evidence. The wrapper sets no result, so it is `void`. + */ + +extern void func_8009A904(int a0, int a1); + +void func_8009AC08(int a0) +{ + func_8009A904(a0, 0); +} diff --git a/src/func_800AC818.c b/src/func_800AC818.c new file mode 100644 index 0000000..38649cc --- /dev/null +++ b/src/func_800AC818.c @@ -0,0 +1,63 @@ +/* + * func_800AC818 — 68 bytes at 0x800AC818..0x800AC85C + * + * Selects one of three globals from two byte-wide flags and stores it through the + * output pointer, returning it as well. All three arms converge on ONE epilogue, + * so the source keeps a single `value` local. + * + * The observed instructions are: + * andi a2,a2,0xff ; a2 &= 0xff + * beqz a2,0x800AC84C ; if (a2 == 0) use the third global + * andi v0,a3,0xff ; a3 &= 0xff (delay slot) + * beqz v0,0x800AC83C ; if (a3 == 0) use the second global + * nop + * lui v0,0x8014 + * lw v0,-15556(v0) ; value = D_8013C33C + * j 0x800AC854 + * nop + * 3C: lui v0,0x8014 + * lw v0,-15552(v0) ; value = D_8013C340 + * j 0x800AC854 + * nop + * 4C: lui v0,0x8014 + * lw v0,-15548(v0) ; value = D_8013C344 + * 54: jr ra + * sw v0,0(a0) ; *out = value (delay slot) + * + * The return value is the LOADED global, not the output pointer: `v0` holds the + * global at the `jr ra` and the store in the delay slot consumes it. The first + * parameter is a pointer while the other three are used only as masked bytes. + * + * The blocks are MIRRORED (cookbook finding 28): the `first_flag == 0` arm is + * emitted LAST and reached by a forward branch, so the natural `== 0` chain + * spelling emits an inverted `bne` and the wrong block order. The nested + * `!= 0` guards below reproduce the original's order. The symbol addresses are + * 0x8013C33C / 0x8013C340 / 0x8013C344 — reachable as `lui 0x8014` plus the + * sign-extended displacements -15556 / -15552 / -15548. + * + * LIMITS: the three symbol addresses (0x8013C34C/350/354), the byte masks and the + * parameter types are hypotheses read from the instruction shape; the second + * parameter is not referenced by any instruction in the body and its purpose is + * NOT guessed. What the globals mean is unknown. Only the compiled bytes are + * evidence. + */ + +extern int D_8013C33C; +extern int D_8013C340; +extern int D_8013C344; + +int func_800AC818(int *out, int unused, int first_flag, int second_flag) { + int value; + + if ((first_flag & 0xFF) != 0) { + if ((second_flag & 0xFF) != 0) + value = D_8013C33C; + else + value = D_8013C340; + } else { + value = D_8013C344; + } + + *out = value; + return value; +} diff --git a/src/func_800AC85C.c b/src/func_800AC85C.c new file mode 100644 index 0000000..c7c9a12 --- /dev/null +++ b/src/func_800AC85C.c @@ -0,0 +1,33 @@ +/* + * func_800AC85C — 40 bytes at 0x800AC85C..0x800AC884 + * + * Framed wrapper: calls one routine with a global loaded from a fixed address. + * + * The observed instructions are: + * lui a0,0x8014 3c048014 \ + * lw a0,-0x3cb8(a0) 8c84c348 / a0 = *(int *)0x8013C348 + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * jal 0x8002e870 0c00ba1c call func_8002E870 + * nop 00000000 (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The global load is hoisted **above** the frame setup, and the address arithmetic carries: + * `lui 0x8014` with the signed displacement -0x3cb8 gives **0x8013C348**, not 0x8014C348 — + * the same trap as func_800F4B54 and func_800F7990. + * + * LIMITS: the function name, the callee and the global's type are hypotheses; only the bytes + * are evidence. The global is read as a 32-bit value and passed as the only argument. The + * wrapper sets no result, so it is `void`. + */ + +extern int D_8013C348; +extern void func_8002E870(int a0); + +void func_800AC85C(void) +{ + func_8002E870(D_8013C348); +} diff --git a/src/func_800BFEC0.c b/src/func_800BFEC0.c new file mode 100644 index 0000000..b24cbd9 --- /dev/null +++ b/src/func_800BFEC0.c @@ -0,0 +1,31 @@ +/* + * func_800BFEC0 — 32 bytes at 0x800BFEC0..0x800BFEE0 + * + * Framed wrapper: calls one routine, deriving the third argument from the second. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * jal 0x800bf734 0c02fdcd call func_800BF734 + * addiu a2,a1,0x378 24a60378 a2 = a1 + 0x378 (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The third callee is func_800BF734, reached here with offset 0x378 — the same callee + * and the same shape as func_800BFE80 (0x128) and func_800BFEE0 (0x4a0), so the three + * are one callee reached through three different record offsets. The offset is again + * computed in the `jal` delay slot. + * + * LIMITS: the function name, the callee and the meaning of the 0x378 offset are + * hypotheses; only the bytes are evidence. The wrapper is `void` because no return value + * is set. + */ + +extern void func_800BF734(int a0, int a1, int a2); + +void func_800BFEC0(int a0, int a1) +{ + func_800BF734(a0, a1, a1 + 0x378); +} diff --git a/src/func_800F3140.c b/src/func_800F3140.c new file mode 100644 index 0000000..186bda6 --- /dev/null +++ b/src/func_800F3140.c @@ -0,0 +1,35 @@ +/* + * func_800F3140 — 32 bytes at 0x800F3140..0x800F3160 + * + * Framed wrapper: calls the GTE control-register write that this project has already + * matched as func_80102FD4. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * jal 0x80102fd4 0c040bf5 call func_80102FD4 + * nop 00000000 (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * Nothing is set up before the `jal`, so the argument is **forwarded**: the callee is + * `void func_80102FD4(int h)` (registered at 0x80102FD4..0x80102FE0, a single `ctc2` to + * the GTE H register), and a forwarding wrapper emits no instructions for a0. Writing the + * wrapper with no parameter and a literal argument would instead emit a `clear a0`, so the + * absence of any setup is the evidence for the parameter. + * + * This region also sits immediately before the registered 0x800F3160..0x800F316C, which + * needs `maspsx=off` — the two are adjacent, not overlapping. + * + * LIMITS: the function name and the claim that the value is a projection-plane distance + * are hypotheses; only the bytes are evidence. The wrapper sets no result, so it is `void`. + */ + +extern void func_80102FD4(int h); + +void func_800F3140(int h) +{ + func_80102FD4(h); +} diff --git a/src/func_800F3A00.c b/src/func_800F3A00.c new file mode 100644 index 0000000..e5b1736 --- /dev/null +++ b/src/func_800F3A00.c @@ -0,0 +1,30 @@ +/* + * func_800F3A00 — 36 bytes at 0x800F3A00..0x800F3A24 + * + * Framed wrapper: loads a field from its argument and passes it on. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * lw a0,0x10(a0) 8c840010 a0 = *(int *)(a0 + 0x10) + * jal 0x800f452c 0c03d14b call func_800F452C + * nop 00000000 (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * The field load targets **a0 itself**, overwriting the incoming pointer, so the wrapper + * dereferences its argument once and forwards the loaded word. Nothing else is set up, so + * the callee takes exactly one argument. + * + * LIMITS: the function name, the callee and the field at +0x10 are hypotheses; only the + * bytes are evidence. The load is 32-bit. The wrapper sets no result, so it is `void`. + */ + +extern void func_800F452C(int a0); + +void func_800F3A00(int a0) +{ + func_800F452C(*(int *)(a0 + 0x10)); +} diff --git a/src/func_800F8B18.c b/src/func_800F8B18.c new file mode 100644 index 0000000..dcca99b --- /dev/null +++ b/src/func_800F8B18.c @@ -0,0 +1,29 @@ +/* + * func_800F8B18 — 32 bytes at 0x800F8B18..0x800F8B38 + * + * Framed wrapper: calls one routine and returns its result untouched. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * jal 0x80106154 0c041855 call func_80106154 + * nop 00000000 (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * addiu sp,sp,0x18 27bd0018 frame release + * jr ra 03e00008 + * nop 00000000 (delay slot) + * + * Nothing touches v0 after the call, so the callee's result is the return value. Same + * shape as func_800F8638 and func_800F8B38 with a different callee. + * + * LIMITS: the function name and the callee's purpose are hypotheses; only the bytes are + * evidence. A parameter-forwarding wrapper would produce the same instructions, so the + * parameter list is not recoverable. + */ + +extern int func_80106154(void); + +int func_800F8B18(void) +{ + return func_80106154(); +} diff --git a/src/func_800F8F5C.c b/src/func_800F8F5C.c new file mode 100644 index 0000000..043e73e --- /dev/null +++ b/src/func_800F8F5C.c @@ -0,0 +1,30 @@ +/* + * func_800F8F5C — 32 bytes at 0x800F8F5C..0x800F8F7C + * + * Framed wrapper: calls one routine and reports whether the result is zero. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * jal 0x8010703c 0c041c0f call func_8010703C + * nop 00000000 (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * sltiu v0,v0,0x1 2c420001 v0 = (v0 < 1) unsigned + * jr ra 03e00008 + * addiu sp,sp,0x18 27bd0018 frame release (delay slot) + * + * The `sltiu` against 1 is this compiler's idiom for `x == 0`; writing the comparison as + * `== 0` (rather than `< 1`) keeps the immediate form. Same shape as func_800F8F7C with a + * different callee. + * + * LIMITS: the function name and the callee's purpose are hypotheses; only the bytes are + * evidence. The callee's return type is written `int`; an `unsigned` or pointer result + * would produce the same unsigned comparison, so the type is not pinned. + */ + +extern int func_8010703C(void); + +int func_800F8F5C(void) +{ + return func_8010703C() == 0; +} diff --git a/src/func_800FA960.c b/src/func_800FA960.c new file mode 100644 index 0000000..fd60f65 --- /dev/null +++ b/src/func_800FA960.c @@ -0,0 +1,31 @@ +/* + * func_800FA960 — 32 bytes at 0x800FA960..0x800FA980 + * + * Framed wrapper: calls one routine with a literal third argument and reports whether the + * result is zero. + * + * The observed instructions are: + * addiu sp,sp,-0x18 27bdffe8 frame, 24 bytes + * sw ra,0x10(sp) afbf0010 save ra + * jal 0x80102e10 0c040b84 call func_80102E10 + * li a2,0xc 2406000c a2 = 0xc (delay slot) + * lw ra,0x10(sp) 8fbf0010 restore ra + * sltiu v0,v0,0x1 2c420001 v0 = (v0 < 1) unsigned + * jr ra 03e00008 + * addiu sp,sp,0x18 27bd0018 frame release (delay slot) + * + * a0 and a1 are never touched, so the first two arguments are forwarded; the third is the + * literal 0xc, materialised in the `jal` delay slot. The `sltiu` against 1 is this + * compiler's idiom for `x == 0`, so the wrapper returns a boolean. + * + * LIMITS: the function name, the callee and the meaning of 0xc are hypotheses; only the + * bytes are evidence. The callee's return type is written `int`; the unsigned comparison + * does not pin it. + */ + +extern int func_80102E10(int a0, int a1, int a2); + +int func_800FA960(int a0, int a1) +{ + return func_80102E10(a0, a1, 0xc) == 0; +} diff --git a/src/func_8010A748.c b/src/func_8010A748.c new file mode 100644 index 0000000..f188a38 --- /dev/null +++ b/src/func_8010A748.c @@ -0,0 +1,50 @@ +/* + * 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. + * + * 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 + * + * 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. + * + * 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. + */ + +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); +}