diff --git a/config/regions.tsv b/config/regions.tsv index 30bfc52..04f86b3 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -271,6 +271,7 @@ 0x800A9D58 0x800A9D90 src/func_800A9D58.c 0x800A9F7C 0x800A9FD4 src/func_800A9F7C.c 0x800A9FD4 0x800AA01C src/func_800A9FD4.c +0x800AA01C 0x800AA0A0 src/func_800AA01C.c 0x800AA2B4 0x800AA2F8 src/func_800AA2B4.c 0x800AA56C 0x800AA59C src/func_800AA56C.c 0x800AC818 0x800AC85C src/func_800AC818.c diff --git a/src/func_800AA01C.c b/src/func_800AA01C.c new file mode 100644 index 0000000..f3fd262 --- /dev/null +++ b/src/func_800AA01C.c @@ -0,0 +1,74 @@ +/* func_800AA01C — 0x800AA01C..0x800AA0A0 (132 bytes). + * + * Original words: + * 27BDFFE8 addiu sp,sp,-24 + * AFB00010 sw s0,16(sp) + * AFBF0014 sw ra,20(sp) + * 0C02A0B4 jal 0x800A82D0 the shared lookup + * 00A08021 _move s0,a1 (delay slot) keep the mode + * 00402021 move a0,v0 the looked-up object becomes the working pointer + * 10800015 beqz a0,0x800AA08C + * 00000000 _nop (delay slot) + * 16000006 bnez s0,0x800AA058 <- mode != 0 + * 00000000 _nop + * 90820014 lbu v0,20(a0) + * 34420008 ori v0,v0,0x8 + * 0802A821 j 0x800AA084 + * 304200BF _andi v0,v0,0xBF (delay slot) clear bit 6 + * 24020001 li v0,1 <- 0x800AA058, mode == 1 test + * 16020006 bne s0,v0,0x800AA078 + * 00000000 _nop (delay slot) + * 90820014 lbu v0,20(a0) <- mode 1 arm + * 34420040 ori v0,v0,0x40 set bit 6 + * 0802A821 j 0x800AA084 + * 304200F7 _andi v0,v0,0xF7 (delay slot) clear bit 3 + * 90820014 lbu v0,20(a0) <- 0x800AA078, the default arm + * 304200B7 andi v0,v0,0xB7 clear bits 6 and 3 + * 0C02A0C4 jal 0x800A8310 <- 0x800AA084 + * A0820014 _sb v0,20(a0) (delay slot) write the byte back + * 8FBF0014 lw ra,20(sp) <- 0x800AA08C + * 8FB00010 lw s0,16(sp) + * 27BD0018 addiu sp,sp,24 + * 03E00008 jr ra + * 00000000 nop + * + * A three-way mode switch that edits two bits of a byte field in a looked-up object, then + * flushes. All three arms converge on one store and one flush, so this is a `switch`-like + * shape with a single tail rather than three returns. + * + * The masks are read-modify-write pairs, and the pairing is what identifies each arm: + * - mode 0 → `| 0x8` then `& ~0x40` — **sets** bit 3, **clears** bit 6; + * - mode 1 → `| 0x40` then `& ~0x8` — **sets** bit 6, **clears** bit 3; + * - anything else → `& 0xB7` — clears both. + * So the field encodes a two-state selection with a third "neither" case, and the two + * non-default arms are exact complements of each other. That is a strong structural signal the + * source was an `if`/`else if`/`else` chain rather than a switch with distinct bodies. + * + * The object pointer is moved into `a0` immediately after the lookup, so the flush receives + * the **looked-up object** — the opposite of the sibling row `0x800AAC44`, where the flush + * receives the routine's original `a0`. Two rows in one family that differ in exactly that + * respect, which is why the `move a0,v0` is worth checking on every row rather than assumed. + * + * LIMITS: the field offset 0x14 and the masks 0x08/0x40/0xB7 are read from the bytes. The + * comparison `bnez s0` then `li v0,1` / `bne s0,v0` shows the mode is compared against 0 and + * then 1, so the parameter is an index; its type is not otherwise constrained. The lookup is + * already registered in this project, corroborating the target. + */ + +int func_800A82D0(int a0); +void func_800A8310(int p); + +void func_800AA01C(int a0, int mode) +{ + char *p = (char *)func_800A82D0(a0); + + if (p != 0) { + if (mode == 0) + p[0x14] = (p[0x14] | 0x08) & ~0x40; + else if (mode == 1) + p[0x14] = (p[0x14] | 0x40) & ~0x08; + else + p[0x14] = p[0x14] & ~0x48; + func_800A8310((int)p); + } +}