From 74785840c7ace7e8eb69834e0ab9a63bc70c23ea Mon Sep 17 00:00:00 2001 From: Christopher Williams Date: Thu, 24 Sep 2026 01:34:54 -0400 Subject: [PATCH] =?UTF-8?q?phase9:=20merge=20B=204=20+=20C=204=20=E2=80=94?= =?UTF-8?q?=20346=20regions=20/=20337=20distinct=20bodies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Worker B: 0x8002E3A8 bare unused frame (16B of pure stack adjustment from a removed local — reproduced by declaring char unused[16] and nothing else), 0x8010AAC0 pass-through with arg reorder, 0x8008FF58 six-arg rebuild, 0x800F79C0 virtual dispatch. Worker C: 0x800268C4 (D_ names), 0x800697C4, 0x800658FC (F20 displacement-fold lever), 0x800A5CC8 (overlap with the coordinator's claim — C's registration stood). Gate MATCH whole-binary SHA-1 e173426c157384ebf1b6caf8c6fea18a85a14af9. --- config/regions.tsv | 7 +++++++ src/func_800268C4.c | 40 +++++++++++++++++++++++++++++++++++++ src/func_8002E3A8.c | 37 ++++++++++++++++++++++++++++++++++ src/func_800658FC.c | 48 +++++++++++++++++++++++++++++++++++++++++++++ src/func_800697C4.c | 37 ++++++++++++++++++++++++++++++++++ src/func_8008FF58.c | 41 ++++++++++++++++++++++++++++++++++++++ src/func_800F79C0.c | 46 +++++++++++++++++++++++++++++++++++++++++++ src/func_8010AAC0.c | 35 +++++++++++++++++++++++++++++++++ 8 files changed, 291 insertions(+) create mode 100644 src/func_800268C4.c create mode 100644 src/func_8002E3A8.c create mode 100644 src/func_800658FC.c create mode 100644 src/func_800697C4.c create mode 100644 src/func_8008FF58.c create mode 100644 src/func_800F79C0.c create mode 100644 src/func_8010AAC0.c diff --git a/config/regions.tsv b/config/regions.tsv index 54d09c1..e93f0e3 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -67,6 +67,7 @@ 0x800262EC 0x800262F8 src/func_800262E0.c 0x800262F8 0x80026304 src/func_800262F8.c 0x800267C0 0x800267CC src/func_800267C0.c +0x800268C4 0x800268F4 src/func_800268C4.c 0x80026C2C 0x80026C7C src/func_80026C2C.c 0x80026F14 0x80026F3C src/func_80026F14.c 0x800276B0 0x800276D4 src/func_800276B0.c @@ -86,6 +87,7 @@ 0x8002D5D0 0x8002D608 src/func_8002D5D0.c 0x8002DEB4 0x8002DF1C src/func_8002DEB4.c 0x8002E028 0x8002E070 src/func_8002E028.c +0x8002E3A8 0x8002E3B8 src/func_8002E3A8.c 0x8002E4C8 0x8002E4F0 src/func_8002E4C8.c 0x8002E7C4 0x8002E7E4 src/func_8002E7C4.c 0x8002F160 0x8002F1A4 src/func_8002F160.c @@ -134,6 +136,7 @@ 0x80058288 0x800582AC src/func_80058288.c 0x8005E3D0 0x8005E3F4 src/func_8005E3D0.c 0x8005ED6C 0x8005EDBC src/func_8005ED6C.c +0x800658FC 0x80065930 src/func_800658FC.c 0x80065930 0x80065980 src/func_80065930.c 0x80065B6C 0x80065B8C src/func_80065B6C.c 0x800668A8 0x800668F0 src/func_800668A8.c @@ -147,6 +150,7 @@ 0x80068F6C 0x80068F98 src/func_80068F6C.c 0x80068F98 0x80068FA8 src/func_80068F98.c 0x800697A4 0x800697C4 src/func_800697A4.c +0x800697C4 0x800697FC src/func_800697C4.c 0x8006B778 0x8006B7C0 src/func_8006B778.c 0x8006BC08 0x8006BC34 src/func_8006BC08.c 0x8006BC34 0x8006BC74 src/func_8006BC34.c @@ -187,6 +191,7 @@ 0x8008F4A0 0x8008F4AC src/func_8008F4A0.c 0x8008F4F4 0x8008F508 src/func_8008F4F4.c 0x8008F508 0x8008F530 src/func_8008F508.c +0x8008FF58 0x8008FF84 src/func_8008FF58.c 0x8008FF84 0x8008FFC4 src/func_8008FF84.c 0x80090028 0x80090048 src/func_80090028.c 0x80090048 0x80090058 src/func_80090048.c @@ -259,6 +264,7 @@ 0x800F6570 0x800F6594 src/func_800F6570.c 0x800F75D0 0x800F760C src/func_800F75D0.c 0x800F7990 0x800F79C0 src/func_800F7990.c +0x800F79C0 0x800F79F0 src/func_800F79C0.c 0x800F79F0 0x800F7A20 src/func_800F79F0.c 0x800F7A20 0x800F7A54 src/func_800F7A20.c 0x800F7A54 0x800F7A84 src/func_800F7A54.c @@ -349,3 +355,4 @@ 0x8010A888 0x8010A8B0 src/func_8010A888.c 0x8010A8B0 0x8010A8D8 src/func_8010A8B0.c 0x8010AAA0 0x8010AABC src/func_8010AAA0.c +0x8010AAC0 0x8010AAE4 src/func_8010AAC0.c diff --git a/src/func_800268C4.c b/src/func_800268C4.c new file mode 100644 index 0000000..0b52543 --- /dev/null +++ b/src/func_800268C4.c @@ -0,0 +1,40 @@ +/* + * func_800268C4 — 48 bytes at 0x800268C4..0x800268F4 + * + * Guarded teardown: if a gp-relative state word is set, passes the ADDRESS of an + * adjacent gp-relative object to a callee and clears the state word. + * + * The observed instructions are: + * lw v0,540(gp) ; v0 = D_80121B54 + * addiu sp,sp,-24 + * beqz v0,0x800268E4 ; if (state == 0) skip + * sw ra,16(sp) ; save ra (delay slot) + * addiu a0,gp,536 ; a0 = &D_80121B50 <- gp-relative ADDRESS + * jal 0x80026E94 + * nop + * sw zero,540(gp) ; D_80121B54 = 0 + * E4: lw ra,16(sp) + * addiu sp,sp,24 + * jr ra + * nop + * + * The callee receives the ADDRESS of a gp-relative object (`addiu a0,gp,536`), + * not a loaded value, so both symbols must be gp-marked in the registry: an + * address-shaped name that resolves implicitly loses the marker and the address + * materialises absolutely instead. See the F19 finding. + * + * LIMITS: the two gp offsets (536 = 0x218 and 540 = 0x21C) are hypotheses read + * from the instruction shape and are facts about this executable's gp layout. + * What the state word and the object mean is unknown and is not guessed here. + * Only the compiled bytes are evidence. + */ + +extern int D_80121B50; +extern int D_80121B54; + +void func_800268C4(void) { + if (D_80121B54 != 0) { + func_80026E94(&D_80121B50); + D_80121B54 = 0; + } +} diff --git a/src/func_8002E3A8.c b/src/func_8002E3A8.c new file mode 100644 index 0000000..540825d --- /dev/null +++ b/src/func_8002E3A8.c @@ -0,0 +1,37 @@ +/* func_8002E3A8 — 0x8002E3A8..0x8002E3B8 (16 bytes). + * + * Original words: + * 27BDFFF0 addiu sp,sp,-16 + * 27BD0010 addiu sp,sp,16 + * 03E00008 jr ra + * 00000000 nop + * + * An empty function that nonetheless adjusts the stack by 16 bytes and puts it + * back — the whole body is a frame that is never used and never addresses + * anything. + * + * This is a **compiler artefact, not a body**: cc1 allocates the frame for a + * local object that the optimiser then removes, leaving the `sp` adjustment + * behind. A function written as truly empty (`void f(void) {}`) emits only + * `jr ra` / `nop`, so the source must have declared something with automatic + * storage that GCC dropped after the frame had been assigned — which is why the + * C below declares an unused local array rather than being an empty body. There is + * deliberately nothing else in it: adding any use of the local would emit a store + * and change the bytes. + * + * The frame is 16 bytes, so the dropped object was 16 bytes — a `char[16]`, an + * `int[4]`, or any aggregate of that size. Which one cannot be told from the + * bytes, because a removed local leaves no trace beyond its size. + * + * LIMITS: the reconstruction reproduces a *compiler behaviour* (leave the frame, + * drop the object) rather than a program statement. The declared size is chosen to + * make the frame come out at 16 and is an inference from the frame size alone, not + * evidence about the original's declaration. If a later reader finds a compiler + * that does not emit the frame for this spelling, the row is a compiler-behaviour + * negative and not a source problem. + */ + +void func_8002E3A8(void) +{ + char unused[16]; +} diff --git a/src/func_800658FC.c b/src/func_800658FC.c new file mode 100644 index 0000000..b053c46 --- /dev/null +++ b/src/func_800658FC.c @@ -0,0 +1,48 @@ +/* + * func_800658FC — 52 bytes at 0x800658FC..0x80065930 + * + * Clears two 16-bit fields inside one element of a strided array reached through + * the argument, then calls a callee with the routine's own two arguments + * unchanged. The element offset is `index * 4 + 68`, computed before the frame is + * finished being set up. + * + * The observed instructions are: + * addiu sp,sp,-24 + * sll v0,a1,0x2 ; index * 4 + * sw ra,16(sp) + * lw v1,32(a0) ; v1 = p->ptr_20 (the array base) + * addiu v0,v0,68 ; index * 4 + 68 + * addu v1,v1,v0 ; element = base + that (base first) + * sh zero,2(v1) ; element->half_02 = 0 + * jal 0x80065794 + * sh zero,0(v1) ; element->half_00 = 0 (delay slot) + * lw ra,16(sp) + * addiu sp,sp,24 + * jr ra + * nop + * + * The callee is passed the routine's OWN arguments: `a0` and `a1` are never + * overwritten (only temporaries v0/v1 are used), so the call forwards `(p, index)` + * without any explicit argument setup. + * + * The element displacement must be folded into the POINTER, not into the two + * store displacements: the original emits `addiu v0,v0,68` (on the scaled index) + * then `addu` into the base, and both stores then use offset 0 and 2. Written as + * a flat `base + index * 4 + 68`, cc1 reassociates and emits the stores at + * displacements 68 and 70 instead, which is 16 bytes shorter (36 vs 52). Binding + * `index * 4 + 68` to its own local forces the original's shape. + * + * LIMITS: the array-base offset (0x20), the element stride (4), the element + * displacement (68) and the two cleared field offsets (0 and 2) are hypotheses + * read from the instruction shape; what the array holds is unknown and is not + * guessed here. Only the compiled bytes are evidence. + */ + +void func_800658FC(char *p, int index) { + int offset = index * 4 + 68; + char *element = (char *)(*(int *)(p + 32) + offset); + + *(short *)(element + 2) = 0; + *(short *)(element + 0) = 0; + func_80065794(p, index); +} diff --git a/src/func_800697C4.c b/src/func_800697C4.c new file mode 100644 index 0000000..9911d9f --- /dev/null +++ b/src/func_800697C4.c @@ -0,0 +1,37 @@ +/* + * func_800697C4 — 56 bytes at 0x800697C4..0x800697FC + * + * Two pointer hops and a guarded call: reads a pointer out of a structure, then a + * second pointer out of that, and calls a callee with it only when it is + * non-null. The callee receives the pointer that was already in `a0`, so no + * argument setup is emitted. + * + * The observed instructions are: + * addiu sp,sp,-24 + * sw ra,16(sp) + * lw v0,12(a0) ; v0 = p->ptr_0c + * nop + * lw a0,392(v0) ; a0 = v0->ptr_188 + * nop + * beqz a0,0x800697EC ; if (q == 0) skip the call + * nop + * jal 0x800697A4 + * nop + * EC: lw ra,16(sp) + * addiu sp,sp,24 + * jr ra + * nop + * + * The `nop` after each load is maspsx's load-delay fill. + * + * LIMITS: the two pointer offsets (0x0c and 0x188) are hypotheses read from the + * instruction shape; what the structures are is unknown and is not guessed here. + * Only the compiled bytes are evidence. + */ + +void func_800697C4(char *p) { + char *q = *(char **)(*(char **)(p + 12) + 392); + + if (q != 0) + func_800697A4(q); +} diff --git a/src/func_8008FF58.c b/src/func_8008FF58.c new file mode 100644 index 0000000..c829fb9 --- /dev/null +++ b/src/func_8008FF58.c @@ -0,0 +1,41 @@ +/* func_8008FF58 — 0x8008FF58..0x8008FF84 (44 bytes). + * + * Original words: + * 27BDFFE0 addiu sp,sp,-32 + * 8FA20030 lw v0,48(sp) v0 = the incoming FIFTH argument + * AFA70010 sw a3,16(sp) outgoing stack argument 1 = the fourth argument + * 00003821 move a3,zero fourth register argument = 0 + * AFBF0018 sw ra,24(sp) + * 0C023D83 jal 0x8008F60C + * AFA20014 _sw v0,20(sp) (delay slot) outgoing stack argument 2 = the fifth + * 8FBF0018 lw ra,24(sp) + * 27BD0020 addiu sp,sp,32 + * 03E00008 jr ra + * 00000000 nop + * + * A six-argument call rebuilt from five: the callee's first three arguments pass + * through untouched, its fourth is forced to zero, and its fifth and sixth are the + * routine's own fourth and fifth, placed on the stack. + * + * The `lw v0,48(sp)` is the tell that the routine itself takes five arguments: the + * frame is 32 bytes, so the fifth argument sits at 0x30 in the caller's frame and + * the load reads it before the outgoing stack slots are written. Both outgoing + * stack values land at 0x10 and 0x14(sp), which are the caller's slots 5 and 6. + * + * The two stack stores are what make this a *shuffling* adapter rather than a + * pass-through: every register argument except `a3` is already correct, so only + * `a3` is overwritten and the two stack slots are filled. + * + * LIMITS: the frame size 32 and the incoming displacement 0x30 are read from the + * bytes; the arithmetic that places the fifth argument at 0x30 (frame 32 minus the + * 16-byte register-save area) is a consequence of the o32 convention rather than a + * source fact. The callee is named for its address and nothing establishes what the + * forced zero or the forwarded arguments mean. + */ + +void func_8008F60C(int a0, int a1, int a2, int zero, int a4, int a5); + +void func_8008FF58(int a0, int a1, int a2, int a3, int a4) +{ + func_8008F60C(a0, a1, a2, 0, a3, a4); +} diff --git a/src/func_800F79C0.c b/src/func_800F79C0.c new file mode 100644 index 0000000..e6ac170 --- /dev/null +++ b/src/func_800F79C0.c @@ -0,0 +1,46 @@ +/* func_800F79C0 — 0x800F79C0..0x800F79F0 (48 bytes). + * + * Original words: + * 3C028012 lui v0,0x8012 + * 8C42FB4C lw v0,-1204(v0) v0 = D_8011FB4C (0x80120000 - 1204) + * 27BDFFE8 addiu sp,sp,-24 + * AFBF0010 sw ra,16(sp) + * 8C420008 lw v0,8(v0) v0 = *(int *)(v0 + 8) + * 0040F809 jalr v0 + * 00000000 _nop (delay slot) + * 8FBF0010 lw ra,16(sp) + * 27BD0018 addiu sp,sp,24 + * 03E00008 jr ra + * 00000000 nop + * + * A virtual dispatch thunk: it reads a global pointer, fetches the function + * pointer stored at offset 8 of whatever it points to, and calls it with the + * incoming arguments untouched. + * + * The pointer load is hoisted **above** the frame setup — `lui`/`lw` precede + * `addiu sp,sp,-24` — which is the same scheduling seen in `0x8002E4C8`: cc1 moves + * a load that does not depend on `sp` ahead of the prologue. The indirect call + * carries a `nop` in its delay slot, so the target was not known and no + * independent instruction was available to fill it. + * + * The call site passes nothing: `a0`-`a3` are untouched, so the callee receives + * the caller's arguments unchanged and the thunk is transparent apart from the + * target it selects. + * + * `lui`+`lw` into the **same** register is the macro expansion of a symbol load + * (cookbook finding 2), so the global at 0x8011FB4C holds a pointer; whether it is + * an object whose offset 8 is a function pointer, or a table whose third entry is + * one, cannot be told from these bytes. Written here as an indirect call through + * the loaded value with the offset folded in, which is what the two `lw`s express. + * + * LIMITS: the displacement 8 and the frame layout are read from the bytes. The + * signature of the called function is entirely unconstrained — the thunk passes + * whatever it received, so no argument type can be inferred from this body. + */ + +extern int D_8011FB4C; + +void func_800F79C0(void) +{ + (*(void (**)(void))(D_8011FB4C + 8))(); +} diff --git a/src/func_8010AAC0.c b/src/func_8010AAC0.c new file mode 100644 index 0000000..b808438 --- /dev/null +++ b/src/func_8010AAC0.c @@ -0,0 +1,35 @@ +/* func_8010AAC0 — 0x8010AAC0..0x8010AAE4 (36 bytes). + * + * Original words: + * 27BDFFE8 addiu sp,sp,-24 + * AFBF0010 sw ra,16(sp) + * 00802821 move a1,a0 second argument = the routine's first + * 0C03DE7C jal 0x800F79F0 + * 24040004 _li a0,4 (delay slot) first argument = 4 + * 8FBF0010 lw ra,16(sp) + * 27BD0018 addiu sp,sp,24 + * 03E00008 jr ra + * 00000000 nop + * + * A one-call adapter that swaps the argument positions and pins the first: the + * routine's single parameter becomes the callee's **second** argument, and the + * callee's first argument is the constant 4. Nothing is returned. + * + * The `move a1,a0` is the whole content of the routine — there is no second + * parameter to forward, so this is the minimal case of the pass-through idiom: + * the incoming value is moved one register along and the freed register is loaded + * with a constant in the call's delay slot. + * + * LIMITS: that the freed register's constant is 4 rather than a symbol or a + * variable is read directly from `li a0,4`. The callee is named for its address + * and its signature is inferred from the two registers that are set; in + * particular whether it returns a value is invisible, since the result is not + * used, so the signature here is `void`. + */ + +void func_800F79F0(int four, int arg); + +void func_8010AAC0(int arg) +{ + func_800F79F0(4, arg); +}