phase11: merge 54 + cookbook 166 — 590 bodies / 599 regions
0x800F3DC0 (88 B) — a ONE-WORD sibling of the matched 0x800F3E18, found by worker E via sf3_family at ratio 1.000 and confirmed by raw-word diff: identical in all 22 words except the COP2 command field (0x4B70000C vs 0x4B78000C). The route was one copy, two renames and one field change; every __asm__ and register binding carried over untouched. 166 records it, and notes it is the MIRROR of finding 161: on 0x800F3E18 the field 0x178000c was the WRONG answer (one byte off, 0x170000c correct); on 0x800F3DC0 0x178000c IS correct. A count tells you a field is COMMON, not that it is right -- and a ratio-1.000 sibling is the cheapest place to learn which one a row wants. When the family tool reports one, diff the raw words FIRST.
This commit is contained in:
+1011
-1014
File diff suppressed because it is too large
Load Diff
@@ -482,6 +482,7 @@
|
||||
0x800F3140 0x800F3160 src/func_800F3140.c
|
||||
0x800F3160 0x800F316C src/func_800F3160.c maspsx=off
|
||||
0x800F3A00 0x800F3A24 src/func_800F3A00.c
|
||||
0x800F3DC0 0x800F3E18 src/func_800F3DC0.c
|
||||
0x800F3E18 0x800F3E70 src/func_800F3E18.c
|
||||
0x800F3E70 0x800F3E88 src/func_800F3E70.c
|
||||
0x800F4098 0x800F4100 src/func_800F4098.c maspsx=epilogue
|
||||
@@ -595,6 +596,7 @@
|
||||
0x801085B0 0x801085D0 src/func_801085B0.c
|
||||
0x80108690 0x801086AC src/func_80108690.c
|
||||
0x80108710 0x80108734 src/func_80108710.c
|
||||
0x80108740 0x801087C8 src/func_80108740.c maspsx=epilogue
|
||||
0x80108990 0x801089B4 src/func_80108990.c
|
||||
0x80109300 0x80109314 src/func_80109300.c
|
||||
0x80109314 0x80109338 src/func_800F8F9C.c
|
||||
|
||||
|
@@ -2696,3 +2696,23 @@ toolchain matches at 132 B and `--fill-epilogue` is a no-op (132/0 both ways).**
|
||||
|
||||
**Second independent confirmation of finding 147:** the epilogue list was selected on the **original's**
|
||||
tail, and the token must be decided per row from the **candidate's** tail.
|
||||
|
||||
### 166. A one-word sibling is a NEAR-FREE body — and the family tool finds them (worker E)
|
||||
|
||||
Worker E found `0x800F3DC0` (88 B) via `tools/sf3_family` at **ratio 1.000** against the already-matched
|
||||
`0x800F3E18`. A raw-word diff confirms it: **the two bodies are identical in all 22 words except ONE:**
|
||||
|
||||
func_800F3E18 word 13 = 0x4B70000C (gte_cmd(0x170000c))
|
||||
func_800F3DC0 word 13 = 0x4B78000C (gte_cmd(0x178000c))
|
||||
|
||||
**The route took one copy, two renames and one field change** — every `__asm__` and every
|
||||
`register int ... __asm__("$n")` binding carried over untouched. `0x800F3DC0` now reports
|
||||
**88 bytes / 0 differing / MATCH.**
|
||||
|
||||
**And it is the mirror image of finding 161:** on `0x800F3E18` the field `0x178000c` was the WRONG
|
||||
answer (the row came out one byte off, and `0x170000c` was right); on `0x800F3DC0` **`0x178000c` IS
|
||||
correct.** *A count tells you a field is COMMON, not that it is right* — and the sibling is the
|
||||
cheapest possible place to learn which one a row wants.
|
||||
|
||||
> **When `tools/sf3_family` reports a ratio-1.000 sibling, diff the raw words FIRST.** A one-word diff
|
||||
> means a copy and a field change, not a derivation.
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ int func_80026274(int a0) {
|
||||
|
||||
if (x < 0) {
|
||||
neg = 0;
|
||||
x = -x;
|
||||
x = -a0;
|
||||
}
|
||||
d = D_80121B18 - x;
|
||||
if (x == 0) {
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* func_800F3DC0 — 88 bytes at 0x800F3DC0..0x800F3E18
|
||||
*
|
||||
* Goal B, Phase 11. **A one-word sibling of func_800F3E18** — found by worker E via
|
||||
* `tools/sf3_family` at ratio 1.000 and confirmed by raw-word diff: the two bodies are identical
|
||||
* in all 22 words EXCEPT the COP2 command field.
|
||||
*
|
||||
* func_800F3E18 word 13 = 0x4B70000C (gte_cmd(0x170000c))
|
||||
* func_800F3DC0 word 13 = 0x4B78000C (gte_cmd(0x178000c))
|
||||
*
|
||||
* That single word is exactly the trap recorded below in item 3: two command fields differing
|
||||
* only in the low selector bits are different instructions. This file is a copy of
|
||||
* func_800F3E18.c with that one field, the name and the extent changed — every `__asm__` and
|
||||
* every `register int ... __asm__("$n")` binding carries over untouched.
|
||||
*
|
||||
* save the three control words (cfc2 $0/$2/$4 into $13/$14/$15)
|
||||
* load the 3x3 matrix from a0 into $0/$2/$4 (ctc2, i.e. RT1RT2 / RT22RT23 / RT33)
|
||||
* lwc2 $9/$10/$11 from a1 (the vector, as three WORDS from memory)
|
||||
* cop2 0x170000c (one command)
|
||||
* swc2 $25/$26/$27 to a2 (the MAC result, as three words)
|
||||
* restore the three control words
|
||||
*
|
||||
* WHAT THIS ROW ADDS TO THE TOOLKIT — three things, all of them needed to reach the bytes:
|
||||
*
|
||||
* 1. **`lwc2`/`swc2` are not `mtc2`/`mfc2`.** `include/gtemac.h`'s `gte_ldIR1/2/3` and
|
||||
* `gte_stMAC1/2/3` expand to `mtc2`/`mfc2` (register <-> COP2); this body moves the vector and the
|
||||
* result **straight between memory and COP2** with `lwc2`/`swc2`. Those had to be written as raw
|
||||
* `__asm__ volatile` with the address in a register operand. **A row can use the IR/MAC registers
|
||||
* without using the IR/MAC macros.**
|
||||
* 2. **Register variables pin the COP2 operand registers.** `register int r13 __asm__("$13");` plus
|
||||
* `"=r"(r13)` makes cc1 emit `cfc2 $13,...` rather than choosing its own destination; the same for
|
||||
* `$8/$9/$10` on the matrix loads. Without that the candidate loads a0[0..2] in a different order
|
||||
* into `v0/v1/a0` and is one instruction long. **An inline-asm row's residual is usually the
|
||||
* operand registers, and register variables are the lever.**
|
||||
* 3. **Read the command field off the ORIGINAL WORD, not off a table.** On func_800F3E18 worker D
|
||||
* wrote `0x178000c` (the value counted elsewhere in the binary) and the row came out **exactly
|
||||
* one byte wrong** — the correct field there is `0x170000c`. **This row is the mirror image:
|
||||
* here `0x178000c` IS correct.** A count tells you a field is common, not that it is right.
|
||||
*
|
||||
* The save/restore of $0/$2/$4 around the command is real: the caller's matrix must survive.
|
||||
*
|
||||
* LIMITS: the function name and the meaning of the three pointers are hypotheses reconstructed from
|
||||
* the disassembly; only the compiled bytes are evidence. The raw `__asm__` statements are inside the
|
||||
* project's documented GTE exception (cookbook 24, Option A: the integer logic stays in C and the
|
||||
* COP2 operations are inline asm, documented per file). The command field `0x178000c` is named by its
|
||||
* VALUE and not semantically, per the gtemac.h convention.
|
||||
*/
|
||||
#include "../include/gtemac.h"
|
||||
|
||||
void func_800F3DC0(int *a0, int *a1, int *a2)
|
||||
{
|
||||
register int r13 __asm__("$13");
|
||||
register int r14 __asm__("$14");
|
||||
register int r15 __asm__("$15");
|
||||
register int t0 __asm__("$8");
|
||||
register int t1 __asm__("$9");
|
||||
register int t2 __asm__("$10");
|
||||
|
||||
__asm__ volatile ("cfc2 %0,$0" : "=r"(r13));
|
||||
__asm__ volatile ("cfc2 %0,$2" : "=r"(r14));
|
||||
__asm__ volatile ("cfc2 %0,$4" : "=r"(r15));
|
||||
|
||||
t0 = a0[0];
|
||||
t1 = a0[1];
|
||||
t2 = a0[2];
|
||||
|
||||
gte_ldRT1RT2(t0);
|
||||
gte_ldRT22RT23(t1);
|
||||
gte_ldRT33(t2);
|
||||
|
||||
__asm__ volatile ("lwc2 $11, 8(%0)" : : "r"(a1));
|
||||
__asm__ volatile ("lwc2 $9, 0(%0)" : : "r"(a1));
|
||||
__asm__ volatile ("lwc2 $10, 4(%0)" : : "r"(a1));
|
||||
__asm__ volatile ("nop");
|
||||
gte_cmd(0x178000c);
|
||||
__asm__ volatile ("swc2 $25, 0(%0)" : : "r"(a2));
|
||||
__asm__ volatile ("swc2 $26, 4(%0)" : : "r"(a2));
|
||||
__asm__ volatile ("swc2 $27, 8(%0)" : : "r"(a2));
|
||||
|
||||
__asm__ volatile ("ctc2 %0,$0" : : "r"(r13));
|
||||
__asm__ volatile ("ctc2 %0,$2" : : "r"(r14));
|
||||
__asm__ volatile ("ctc2 %0,$4" : : "r"(r15));
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* func_80108740 — 136 bytes at 0x80108740..0x801087C8
|
||||
*
|
||||
* Clamp an unsigned argument, arm a delay, run two helpers against it, store the helper's result
|
||||
* into the word at 0x80121060, and return the clamped argument. First spelling.
|
||||
*
|
||||
* addiu sp,sp,-0x20 / sw s1,20 / move s1,a1
|
||||
* frame 32: s0 at 0x10, s1 at 0x14, ra at 0x18. a1 is kept
|
||||
* in s1 for the whole body (it is both the clamp input and
|
||||
* the return value), so s1 is the only value live across
|
||||
* the calls.
|
||||
* lui v0,0x7 / ori v0,v0,0xeff0 0x7EFF0 as a LITERAL -- `ori`, not `addiu`, is the tell
|
||||
* (cookbook 89)
|
||||
* sltu v0,v0,s1 UNSIGNED compare: `sltu`, not `slt`, so the clamped
|
||||
* parameter is `unsigned int` (cookbook 48)
|
||||
* beqz v0,SKIP / sw s0,16(sp) if (a1 > 0x7EFF0) a1 = 0x7EFF0; -- the s0 save lands in
|
||||
* the branch's delay slot
|
||||
* lui s1,0x7 / ori s1,s1,0xeff0
|
||||
* SKIP:
|
||||
* lhu s0,4192(s0) / lw v0,4208(v0)
|
||||
* v = D_80121060 << D_80121070 -- note `lhu`: the source
|
||||
* global is an `unsigned short` (cookbook 7), and the shift
|
||||
* is the `sllv` scheduled into the first call's delay slot
|
||||
* move a1,s1 / jal 0x8010A6C4 func_8010A6C4(a0, a1) -- a0 is the incoming first parameter,
|
||||
* passed through untouched
|
||||
* li a0,-1 / jal 0x8010A78C func_8010A78C(-1, v + a1), the sum in the delay slot
|
||||
* sh v0,4192(at) D_80121060 = <that result>, narrowed by the store width
|
||||
* lw v1,4224(v1)
|
||||
* bnez v1,END / move v0,s1 if (D_80121080 == 0) D_8012107C = 0;
|
||||
* sw zero,4220(at)
|
||||
* END: epilogue frame release in the jump slot -> maspsx=epilogue
|
||||
*
|
||||
* The whole-thing-match came on the first spelling, but it needs the harness token: with the
|
||||
* default toolchain the same source is 140 bytes (one instruction long) because the tail is
|
||||
* `lw ra / lw s1 / lw s0 / jr ra` with `addiu sp,sp,32` in the jump slot.
|
||||
*
|
||||
* The address forms are byte-load-bearing here and they are NOT the same for loads and stores:
|
||||
* all four global accesses are absolute. As LOADS they happen to fold into the displacement, but
|
||||
* as STORES the original emits `lui $at,%hi` + `sh`/`sw` -- the macro store form that goes through
|
||||
* `$at` (cookbook 3) -- so the two stores must be written as SYMBOL stores, not through a literal
|
||||
* address. The `$at` in `sh v0,4192(at)` / `sw zero,4220(at)` is the evidence.
|
||||
*
|
||||
* LIMITS: the two callees are cross-references by address only; the summary line is read off this
|
||||
* body's shape. The four globals are address symbols (name == address) so they need no registry
|
||||
* row, but their real types are only as strong as the access widths: `lhu`/`sh` make 0x80121060 a
|
||||
* 16-bit object, and the other three are read/written as words. Whether the source named them or
|
||||
* used literals is not recoverable for the two loads -- both emit the same bytes.
|
||||
*/
|
||||
|
||||
extern unsigned short D_80121060;
|
||||
extern int D_80121070;
|
||||
extern int D_80121080;
|
||||
extern int D_8012107C;
|
||||
extern void func_8010A6C4(int a0, int a1);
|
||||
extern int func_8010A78C(int a0, int a1);
|
||||
|
||||
int func_80108740(int a0, unsigned int a1)
|
||||
{
|
||||
int v;
|
||||
|
||||
if (a1 > 0x7EFF0)
|
||||
a1 = 0x7EFF0;
|
||||
|
||||
v = D_80121060 << D_80121070;
|
||||
|
||||
func_8010A6C4(a0, a1);
|
||||
|
||||
D_80121060 = func_8010A78C(-1, v + a1);
|
||||
|
||||
if (D_80121080 == 0)
|
||||
D_8012107C = 0;
|
||||
|
||||
return a1;
|
||||
}
|
||||
Reference in New Issue
Block a user