mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-10-09 01:59:26 -04:00
109ce6a2c3
- CRACKED at xHigh and VERIFIED INDEPENDENTLY: match_one MATCH (1061 ins); agent re-matched 3x from clean runs (100% register-masked AND register-kept, all 10 regions, frame 0x270 exact). §81 carve chain clean first try: jr_isolate_all --only -> byte-identical cacaf7c2 -> jtbl_carve (43-piece set) -> byte-identical -> bank -> R22 clean-fleet 140/140, tools-health OK. instr 80.6%; distinct-code 3,844,100 -> 3,845,161. - WHAT IT IS: the matched base func_8017CA80 + camera height-band cull + distance-driven CLUT fade. func_8004974C (TransposeMatrix) sits in a 36-ins prologue deriving a Y band; the part-level `lim >= g.otz` cull is GONE; flat arms gain an `sz < lim` near-plane cull. The base+one-extra-callee fingerprint predicted this exactly. - §82 ORACLE 1 -- A DUPLICATED `addiu $aN,$sp,K` ACROSS A `jal` MEANS THE BLOCK WAS INLINED. `&X` on any non-first local always creates a pseudo and CSE always merges two of them (expr.c:6260 ADDR_EXPR -> force_operand(..., NULL); exception: virtual-stack-vars offset 0). So the same stack address re-materialised at two sites separated by a jal means CSE was PREVENTED from merging => not the same function body. 17 non-inline spellings failed; a `static inline` helper reproduced the prologue BYTE-FOR-BYTE first try. Reusable probe: scan the ~1,200 built objects for that signature in NON-INCLUDE_ASM functions. - §82 ORACLE 2 -- SCALAR vs AGGREGATE DECIDES *WHEN* A STACK SLOT IS ALLOCATED: lazily at first `&` for a scalar, AT DECLARATION for an aggregate. Six GTE result words had to be six separate longs, not a struct, or they don't land after the inlined helper's temps and the frame isn't 0x270. Second-order: it also flips MEM_IN_STRUCT_P (§30's /s) -- with one word a fixed-address scalar, ((PolyF3*)pkt)->rgbc stops aliasing it, so a store needed respelling to keep the target's nop. A scalar-vs-struct choice is simultaneously a frame-layout AND an aliasing decision. - BANKING FOOTNOTE (§75a class A): first bank rejected `conflicting types for ApplyMatrixSV` -- draft (MATRIX2*, SVECTOR2*, SVECTOR2*) vs the TU/fleet canon (void*, void*, void*), 2,286 of 2,835 sites. Conforming the decl is byte-neutral and banked first try. On a jr function expect BOTH gates to speak: the carve chain answers the jump table, §75a answers the declarations. - Also reproduced: §78 (reuse a busy variable), §80(i) (a lever went -8 -> exactly neutral as the base moved), §72 (a register pin made it worse). - AGENT'S OWN CAVEAT, recorded not hidden: one zero-byte __asm__ keeps a vestigial `mnc = hmid` alive that flow.c would delete (costing 10 ins + the 0x130 spill slot). Emits nothing, compile is 1061 exact, but it is a documented stand-in -- 12 natural spellings measured, all DCE'd.
81 lines
4.9 KiB
Python
81 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Drop-one ablations against the MATCHing func_8017C730 draft.
|
|
usage: c730_abl.py <base_body.c> -- prints each ablation's match_one result."""
|
|
import sys, os, subprocess, concurrent.futures
|
|
|
|
BASE = sys.argv[1] if len(sys.argv) > 1 else '/home/musashi/bfm-decomp/.run/c730/base3.c'
|
|
R = '/home/musashi/bfm-decomp'
|
|
src = open(BASE).read()
|
|
|
|
INLINE_BLOCK = (
|
|
" { MATRIX2 rot; MATRIX2 inv; SVECTOR2 dv;\n"
|
|
" extern void func_8004974C(MATRIX2 *, MATRIX2 *);\n"
|
|
" extern void ReadRotMatrix(MATRIX2 *);\n"
|
|
" extern void PushMatrix(void);\n"
|
|
" extern void PopMatrix(void);\n"
|
|
" extern void ApplyMatrixSV(MATRIX2 *, SVECTOR2 *, SVECTOR2 *);\n"
|
|
" ReadRotMatrix(&rot);\n"
|
|
" PushMatrix();\n"
|
|
" dv.vx = org.vx - rot.t[0];\n"
|
|
" dv.vy = org.vy - rot.t[1];\n"
|
|
" dv.vz = org.vz - rot.t[2];\n"
|
|
" func_8004974C(&rot, &inv);\n"
|
|
" ApplyMatrixSV(&inv, &dv, &hv);\n"
|
|
" PopMatrix(); }")
|
|
|
|
ABL = {
|
|
'L1-no-inline-helper': [(" bandsetup(&org, &hv);", INLINE_BLOCK)],
|
|
'L2-g-as-struct': [(" long gflag, gopz, gsz0, gsz1, gsz2, gsz3;",
|
|
" struct { long flag, opz, sz0, sz1, sz2, sz3; } g;"),
|
|
("gflag","g.flag"),("gopz","g.opz"),("gsz0","g.sz0"),
|
|
("gsz1","g.sz1"),("gsz2","g.sz2"),("gsz3","g.sz3")],
|
|
'L3-rgbc-via-struct-A+C': [(" *(u32 *)(pkt + 4) = prim->w0;",
|
|
" ((PolyF3 *)pkt)->rgbc = prim->w0;"),
|
|
(" *(u32 *)(pkt + 4) = prim->w0;",
|
|
" ((PolyF4 *)pkt)->rgbc = prim->w0;")],
|
|
'L4-no-t32-launder': [(" t32 = mid;\n if (!(hhi < t32))",
|
|
" if (!(hhi < mid))")],
|
|
'L5-no-mnc-dial': [(' __asm__ volatile ("" : : "r" (mnc));', '')],
|
|
'L6-wv-computed-late': [(" wv = wz >> 16;\n mid", " mid"),
|
|
(" box[0].vx = mn; box[0].vy = mny; box[0].vz = wz;",
|
|
" wv = wz >> 16;\n box[0].vx = mn; box[0].vy = mny; box[0].vz = wz;")],
|
|
'L7-armB-otp-last': [(" otp = (u32 *)(((gopz >> 2) << 2) + ot);\n ((PolyFT3 *)pkt)->uvc0",
|
|
" ((PolyFT3 *)pkt)->uvc0"),
|
|
(" ((PolyFT3 *)pkt)->uv2 = tp[3];",
|
|
" ((PolyFT3 *)pkt)->uv2 = tp[3];\n otp = (u32 *)(((gopz >> 2) << 2) + ot);")],
|
|
'L8-armD-share-uvw': [(" uvm = tp[1] & 0x3FC0FFFF;",
|
|
" uvw = tp[1] & 0x3FC0FFFF;"),
|
|
(" ((PolyFT4 *)pkt)->uvc0 = uvm |",
|
|
" ((PolyFT4 *)pkt)->uvc0 = uvw |"),
|
|
(" u32 uvm;\n", "")],
|
|
'L9-armB-clut-inline': [(" uvw = tp[1] & 0x3FC0FFFF;\n", ""),
|
|
("((PolyFT3 *)pkt)->uvc0 = uvw |", "((PolyFT3 *)pkt)->uvc0 = (tp[1] & 0x3FC0FFFF) |")],
|
|
'L10-add-sec47-slider': [(" gte_ldv3c(&box[4]);", " gte_ldv3c(&box[4]);\n __asm__ volatile (\"\");")],
|
|
'L11-no-avsz3': [(" gte_avsz3();\n", "")],
|
|
'L12-nprim-after-nparts': [(" u32 nprim;\n s32 nparts;", " s32 nparts;\n u32 nprim;")],
|
|
}
|
|
|
|
def run(item):
|
|
name, edits = item
|
|
s = src
|
|
for old, new in edits:
|
|
if s.count(old) == 0:
|
|
return '%-26s :: PATTERN-NOT-FOUND %r' % (name, old[:50])
|
|
s = s.replace(old, new)
|
|
d = R + '/.run/c730/abl'
|
|
os.makedirs(d, exist_ok=True)
|
|
body = d + '/b_%s.c' % name
|
|
out = d + '/v_%s.c' % name
|
|
open(body, 'w').write(s)
|
|
subprocess.run(['python3', R + '/.run/giants/c730_mk.py', body, out],
|
|
capture_output=True, cwd=R)
|
|
r = subprocess.run(['python3', 'tools/match_one.py', 'func_8017C730', '--c', out,
|
|
'--asm-subdir', 'asm/ov_SC03_010/nonmatchings/ov_SC03_010_jr_8017AE2C'],
|
|
capture_output=True, text=True, cwd=R)
|
|
line = (r.stdout + r.stderr).strip().splitlines()
|
|
return '%-26s :: %s' % (name, ' | '.join(l.strip() for l in line[:2]))
|
|
|
|
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as ex:
|
|
for res in ex.map(run, sorted(ABL.items())):
|
|
print(res)
|