phase9: merge worker C batch 2 — 320 regions / 311 distinct bodies

4 new (0x80082750, 0x8007EB8C, 0x800B34FC, 0x800C1EA8), 2 new gp symbols
(g_80122610, g_80122618) for the gp-relative table-address rows. Recorded
findings F17 (sra-vs-srl = signedness of the shifted value), F18
(inline mask strength-reduce -> bind to a local), F19 (address-shaped name
resolves implicitly but loses its gp marker -> use the registry's exact
name). Gate MATCH whole-binary SHA-1
e173426c157384ebf1b6caf8c6fea18a85a14af9.
This commit is contained in:
Christopher Williams
2026-09-24 01:21:40 -04:00
parent 130a043d91
commit 69eba852c8
6 changed files with 278 additions and 0 deletions
+4
View File
@@ -157,7 +157,9 @@
0x8007DC40 0x8007DC4C src/func_8007DC40.c
0x8007DF00 0x8007DF34 src/func_8007DF00.c
0x8007E8B8 0x8007E904 src/func_8007E8B8.c
0x8007EB8C 0x8007EBEC src/func_8007EB8C.c
0x8007ED4C 0x8007ED8C src/func_8007ED4C.c
0x80082750 0x800827A8 src/func_80082750.c
0x800827A8 0x800827C4 src/func_800827A8.c
0x80082914 0x80082944 src/func_80082914.c
0x80083440 0x80083470 src/func_80083440.c
@@ -219,6 +221,7 @@
0x800B24EC 0x800B2534 src/func_800B24EC.c
0x800B2534 0x800B255C src/func_800B2534.c
0x800B3474 0x800B34A4 src/func_800B3474.c
0x800B34FC 0x800B3554 src/func_800B34FC.c
0x800B5AF0 0x800B5AF8 src/func_80042088.c
0x800B5CB4 0x800B5CF8 src/func_800B5CB4.c
0x800B6BDC 0x800B6C14 src/func_800B6BDC.c
@@ -227,6 +230,7 @@
0x800BFE80 0x800BFEA0 src/func_800BFE80.c
0x800BFEC0 0x800BFEE0 src/func_800BFEC0.c
0x800BFEE0 0x800BFF00 src/func_800BFEE0.c
0x800C1EA8 0x800C1F04 src/func_800C1EA8.c
0x800C5C84 0x800C5CBC src/func_800C5C84.c
0x800F2F6C 0x800F2FB8 src/func_800F2F6C.c maspsx=off
0x800F3140 0x800F3160 src/func_800F3140.c
1 # Code-region registry: one C region per matched function.
157 0x8007DC40
158 0x8007DF00
159 0x8007E8B8
160 0x8007EB8C
161 0x8007ED4C
162 0x80082750
163 0x800827A8
164 0x80082914
165 0x80083440
221 0x800B24EC
222 0x800B2534
223 0x800B3474
224 0x800B34FC
225 0x800B5AF0
226 0x800B5CB4
227 0x800B6BDC
230 0x800BFE80
231 0x800BFEC0
232 0x800BFEE0
233 0x800C1EA8
234 0x800C5C84
235 0x800F2F6C
236 0x800F3140
+2
View File
@@ -382,6 +382,8 @@ func_800F4098 0x800F4098
g_80122068 0x80122068 gp
g_80122158 0x80122158 gp
g_80122354 0x80122354
g_80122610 0x80122610 gp
g_80122618 0x80122618 gp
g_8012263D 0x8012263D gp
g_80122738 0x80122738 gp
g_8012277C 0x8012277C gp
1 # Symbol registry: absolute addresses for cross-references used by C regions.
382 g_80122068
383 g_80122158
384 g_80122354
385 g_80122610
386 g_80122618
387 g_8012263D
388 g_80122738
389 g_8012277C
+72
View File
@@ -0,0 +1,72 @@
/*
* func_8007EB8C — 96 bytes at 0x8007EB8C..0x8007EBEC
*
* Searches three table entries for one whose byte field selects a 16-bit table
* entry equal to a halfword read from the argument, returning the matching entry
* or null. The 16-bit table sits at a gp-relative address computed once outside
* the loop, and the search is a `for` over three entries.
*
* The observed instructions are:
* move a2,zero ; result = 0
* move a1,zero ; i = 0
* addiu t0,gp,3296 ; t0 = &D_80122618 (the 16-bit table)
* lh a3,2(a0) ; want = *(short *)(p + 2) <- SIGNED 16-bit
* lui a0,0x8013
* addiu a0,a0,-7472 ; a0 = D_8012E2D0 (the entry-pointer table)
* A4: lw v1,0(a0) ; entry = table[i]
* nop
* lbu v0,37(v1) ; entry->byte_25
* nop
* sll v0,v0,0x1 ; * 2
* addu v0,v0,t0 ; &table[byte] (index first)
* lh v0,0(v0) ; candidate <- SIGNED 16-bit
* nop
* bne a3,v0,0x8007EBD4 ; if (want != candidate) continue
* nop
* j 0x8007EBE4
* move a2,v1 ; result = entry (delay slot)
* D4: addiu a1,a1,1 ; i++
* slti v0,a1,3 ; i < 3
* bnez v0,0x8007EBA4
* addiu a0,a0,4 ; table++ (delay slot)
* E4: jr ra
* move v0,a2 ; return result (delay slot)
*
* The loop keeps `result` in a register and breaks to a single shared epilogue,
* rather than returning from inside the loop. The gp-relative table address is
* materialised ONCE with `addiu t0,gp,3296`, so the table pointer is hoisted out
* of the loop while the entry-pointer table is walked.
*
* The 16-bit table must be a GP-MARKED symbol in the registry: without the marker
* the address materialises absolutely (`lui t0,0x8012` / `addiu t0,t0,0x2618`)
* where the original computes it from `gp` (`addiu t0,gp,3296`), which is 4 bytes
* long. The compared field is at offset 2, not 0 - the original loads `lh a3,2(a0)`.
*
* LIMITS: the entry-pointer table base (0x8012E2D0), the 16-bit table address
* (0x80122618 = gp + 3296), the entry count (3), the entry field offset (0x25) and
* the compared field offset (2) are hypotheses read from the instruction shape;
* what the tables mean is unknown and is not guessed here. Both compared values
* are loaded with `lh`, so both are signed 16-bit. Only the compiled bytes are
* evidence.
*/
extern int D_8012E2D0[];
extern short g_80122618[];
int func_8007EB8C(short *p) {
int result = 0;
int i = 0;
short *table = g_80122618;
short want = *(short *)((char *)p + 2);
for (; i < 3; i++) {
char *entry = (char *)D_8012E2D0[i];
if (want == table[*(unsigned char *)(entry + 37)]) {
result = (int)entry;
break;
}
}
return result;
}
+57
View File
@@ -0,0 +1,57 @@
/*
* func_80082750 — 88 bytes at 0x80082750..0x800827A8
*
* Indexes a table of objects, follows two pointer hops to a flags word, and when
* one flag bit is set returns a byte out of a strided byte table indexed by the
* difference between the argument and a gp-relative base. When the flag is clear
* it returns -1.
*
* The observed instructions are:
* lui v1,0x8012
* lw v1,7168(v1) ; v1 = D_80121C00 (a table base)
* sll v0,a0,0x2 ; index * 4
* addu v0,v0,v1 ; table + index*4 (index first)
* lw v0,0(v0) ; entry = table[index]
* nop
* lw v0,28(v0) ; hop 2
* nop
* lw v0,0(v0) ; flags
* nop
* andi v0,v0,0x4 ; flags & 4
* beqz v0,0x800827A0 ; if clear, return -1
* li v1,-1 ; result = -1 (delay slot)
* lh v0,3288(gp) ; base = D_80122610 <- SIGNED 16-bit
* nop
* subu v0,a0,v0 ; index - base
* sll v0,v0,0x2 ; * 4
* lui at,0x8014 ; %hi of the byte table
* addu at,at,v0 ; table + offset (base first)
* lbu v1,-31684(at) ; result = table[index-base] (%lo as displacement)
* A0: jr ra
* move v0,v1 ; return result (delay slot)
*
* The gp-relative read is a SIGNED halfword (`lh`), so the base is a `short`.
* The byte table base is 0x8013843C (`lui 0x8014` plus the sign-extended -31684).
*
* LIMITS: the table base (0x80121C00), the byte table base (0x8013843C), the gp
* offset (3288 = 0xCD8), the pointer hop offset (0x1c), the flag bit (4) and the
* sentinel (-1) are hypotheses read from the instruction shape; what the tables
* and the flag mean is unknown and is not guessed here. Only the compiled bytes
* are evidence.
*/
extern int D_80121C00;
extern int D_8013843C[];
extern short g_80122610;
int func_80082750(int index) {
int result = -1;
char *entry = *(char **)(D_80121C00 + index * 4);
int flags = *(int *)*(char **)(entry + 28);
if (flags & 4)
result = *(unsigned char *)((char *)D_8013843C +
(index - g_80122610) * 4);
return result;
}
+72
View File
@@ -0,0 +1,72 @@
/*
* func_800B34FC — 88 bytes at 0x800B34FC..0x800B3554
*
* Tests one bit of a bitset whose base word and bit offset are both derived from
* a single packed field. The field's low two bits select a sub-word of eight bits
* each; clearing those two bits gives the word the bitset lives in.
*
* The observed instructions are:
* lui v0,0x8013
* lw v0,-10340(v0) ; v0 = D_8012D79C (a structure pointer)
* nop
* lw a1,16(v0) ; a1 = ->word_10
* nop
* andi v0,a1,0x3 ; sub = word & 3
* beqz v0,0x800B352C ; if (sub == 0) keep the default offset
* li a0,20 ; offset = 20 (delay slot)
* sll v0,v0,0x3 ; sub * 8
* addiu a0,v0,20 ; offset = sub*8 + 20
* li v0,-4
* and a1,a1,v0 ; word &= ~3
* 2C: sra v0,a0,0x5 ; offset >> 5 (word index)
* andi a0,a0,0x1f ; offset & 0x1f (bit in word)
* li v1,1
* sll v0,v0,0x2 ; index * 4
* addu v0,v0,a1 ; word + index*4 (index first)
* lw v0,0(v0)
* sllv v1,v1,a0 ; 1 << bit <- variable shift
* and v0,v0,v1
* jr ra
* sltu v0,zero,v0 ; return result != 0 (delay slot)
*
* The result is produced by `sltu` against zero, so the comparison is UNSIGNED
* and the return is a boolean. The `sllv` shows the bit index is a runtime value.
*
* The OFFSET is a SIGNED int, and that is observable: the original shifts it with
* `sra` (arithmetic), so `offset >> 5` must be a signed shift. Declaring the
* offset `unsigned` emits `srl` and is 1 byte off — the register field of that one
* instruction. The `& 0x1f` and the `1 << bit` are unaffected.
*
* The mask must be bound to its own local before the AND. Written inline as
* `(word & (1u << bit)) != 0`, cc1 strength-reduces the whole test to
* `sllv` of the LOADED WORD followed by `andi ...,1` instead of materialising
* `1 << bit` in a register and ANDing - a different, shorter lowering (76 vs 80
* bytes). Naming the mask keeps the original's `li v1,1` / `sllv` / `and` shape.
*
* LIMITS: the symbol name D_8012D79C, the field offset (0x10), the two constants
* (20 and 8) and the sub-word width (3 bits / 8 bits per sub-word) are hypotheses
* read from the instruction shape; what the bitset means is unknown and is not
* guessed here. The `-10340` displacement is `lui 0x8013` plus a sign-extended
* low half, i.e. 0x8012D79C — an easy place to be off by 0x10 by hand. Only the
* compiled bytes are evidence.
*/
extern int D_8012D79C;
int func_800B34FC(void) {
unsigned int word = *(unsigned int *)(D_8012D79C + 16);
int offset = 20;
if ((word & 3) != 0) {
offset = (word & 3) * 8 + 20;
word &= ~3u;
}
{
unsigned int index = offset >> 5;
unsigned int bit = offset & 0x1F;
unsigned int mask = 1u << bit;
return (*(unsigned int *)(word + index * 4) & mask) != 0;
}
}
+71
View File
@@ -0,0 +1,71 @@
/*
* func_800C1EA8 — 92 bytes at 0x800C1EA8..0x800C1F04
*
* The parameter-taking sibling of func_800B34FC: same packed-field bit test with
* the same sub-word arithmetic, but the structure is the argument rather than a
* global, and the result is one of two small codes (4 or 5) instead of a boolean.
*
* The observed instructions are:
* li a2,4 ; result = 4
* lw a1,16(a0) ; a1 = p->word_10
* nop
* andi v0,a1,0x3 ; sub = word & 3
* beqz v0,0x800C1ED0
* li a0,30 ; offset = 30 (delay slot)
* sll v0,v0,0x3 ; sub * 8
* addiu a0,v0,30 ; offset = sub*8 + 30
* li v0,-4
* and a1,a1,v0 ; word &= ~3
* D0: sra v0,a0,0x5
* andi a0,a0,0x1f
* li v1,1
* sll v0,v0,0x2
* addu v0,v0,a1 ; word + index*4 (index first)
* lw v0,0(v0)
* sllv v1,v1,a0 ; 1 << bit
* and v0,v0,v1
* beqz v0,0x800C1EFC ; if bit clear keep 4
* nop
* li a2,5 ; result = 5
* FC: jr ra
* move v0,a2 ; return result (delay slot)
*
* The base is the parameter here, so the same arithmetic produces the same bytes
* with a different operand source; the two constants differ (30 vs 20) and the
* result codes differ (4/5 vs a boolean).
*
* Three source-shape requirements, all measured: the mask must be bound to its own
* local (an inline `1u << bit` inside the AND makes cc1 strength-reduce the test
* to a shift of the loaded word plus `andi`, 84 vs 92 bytes); the result local
* must be initialised BEFORE the field load so `li a2,4` is the first
* instruction; and the OFFSET must be a SIGNED int, because the original shifts
* it with `sra` — an `unsigned` offset emits `srl` and is 1 byte off.
*
* LIMITS: the field offset (0x10), the two constants (30 and 8), the result codes
* (4 and 5) and the parameter type are hypotheses read from the instruction
* shape; what the bitset and the codes mean is unknown and is not guessed here.
* Only the compiled bytes are evidence.
*/
int func_800C1EA8(char *p) {
int result = 4;
unsigned int word;
int offset = 30;
unsigned int index;
unsigned int bit;
unsigned int mask;
word = *(unsigned int *)(p + 16);
if ((word & 3) != 0) {
offset = (word & 3) * 8 + 30;
word &= ~3u;
}
index = offset >> 5;
bit = offset & 0x1F;
mask = 1u << bit;
if (*(unsigned int *)(word + index * 4) & mask)
result = 5;
return result;
}