diff --git a/config/regions.tsv b/config/regions.tsv index 1f63704..3bf6b48 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -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 diff --git a/config/symbols.tsv b/config/symbols.tsv index a936a7d..507d4e3 100644 --- a/config/symbols.tsv +++ b/config/symbols.tsv @@ -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 diff --git a/src/func_8007EB8C.c b/src/func_8007EB8C.c new file mode 100644 index 0000000..2d8b391 --- /dev/null +++ b/src/func_8007EB8C.c @@ -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; +} diff --git a/src/func_80082750.c b/src/func_80082750.c new file mode 100644 index 0000000..4654c82 --- /dev/null +++ b/src/func_80082750.c @@ -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; +} diff --git a/src/func_800B34FC.c b/src/func_800B34FC.c new file mode 100644 index 0000000..1cf1347 --- /dev/null +++ b/src/func_800B34FC.c @@ -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; + } +} diff --git a/src/func_800C1EA8.c b/src/func_800C1EA8.c new file mode 100644 index 0000000..820f631 --- /dev/null +++ b/src/func_800C1EA8.c @@ -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; +}