Repository navigation
feat: slots and gate driver for functional C - #342
Conversation
Add nonmatching/functional/ and an empty functional.tsv for C that behaves like the retail function without matching it, for the native port only. check_repository.py requires each listed function to still be INCLUDE_ASM in src/ with its file present; progress.py reads src/ only, so nothing listed is ever counted.
Compiles a function's unit with its own flags (a candidate in place of its INCLUDE_ASM line or C, else on its own), places its section at 0xA0000 with every relocation resolved against retail: the boot build's derived values, the unit's writable data where retail has it, read-only data and jump tables copied with the code, gp-relative references kept inside $gp's window. Captures call states headless from the US disc (menu pages and New Game up to the first level's load, phased and spread) and runs openrac-fcheck.
ByteMatch progress reportSCUS_972.68
No function changes. SCES_516.07
No function changes. SCUS_972.68_v2
No function changes. Commit 32e8cd7 · Ratchet & Clank: Going Commando on ByteMatch |
llesieur99
left a comment
There was a problem hiding this comment.
Reviewed draft head 0d133e4802954709188758e6a98b395e47f6e934. The isolated functional-source layout is supported by current RAC1 source and its active build graph; matching credit remains separate. I am not rejecting that layout or the use of the RAC1 method. The driver needs these fail-closed fixes before acceptance-gate readiness:
- Return contract:
tools/fcheck.py:445-472,550,633infers the comparison bank from candidate spelling. A typedef-float is classified as int; aggregate/missing definitions can become void, and a candidate void declaration can disable the retail return comparison. Resolve or reject unsupported contracts and use an independently established retail ABI. - GP address construction: lines 433-434 treat address-taking as a four-byte scalar load. An eight-byte constant object
[1,2]can be mapped to an earlier[1,9]object sharing its first word. Map the complete referenced span or reject that relocation shape. - Containment:
tests/check_repository.py:156acceptsnonmatching/functional/../game/helper.c. Canonicalize and enforce the functional-root boundary, including symlink escapes. - Capture status: lines 596/690 discard a failed capture subprocess status and return CLI success. Propagate failure distinctly and do not reuse stale snapshots as a completed fresh capture.
The 11 supplied tests pass; five independent synthetic negative controls reproduce these defects (two return-policy controls). No real game/interpreter execution or independent calibration of 502 SAME/11 unchecked was claimed. The return heuristic is inherited from current RAC1, which makes it important to fix rather than proving it correct. Keep this PR draft until these controls pass.
C that does what retail does, for the native port; never counted. Each is SAME under tools/fcheck.py on every call state captured in the menu and New Game runs (USA v1.01). The gate's verdicts are behaviour on those calls, not proof of every path.
A variable the candidate defines under a retail name is at that address even when its reference names the section (a static, an initialised small-data global), and a $gp-relative addend to a local symbol counts from the object's gp0. D_XXXXXXXX_sda and SDATA's __gp aliases are the variable they name. A name nothing else places, a near miss's ref_LNN_XXXXXXXX_kk, takes the value retail's bytes give it under the candidate's relocations, aligned by instruction kind.
…karounds removed func_00132498, func_001325E0, func_002B85F8 and func_00300AA8 are SAME at the gate now that it compares only a call's arguments; func_00300AA8's gp-relative globals are at their real addresses. The committed drafts drop what worked around the old gate: hardcoded addresses become named globals (func_001334B8, func_00133580, func_00132C48, func_002B88F0), constants held in locals and leftover-register arguments go (func_002B68C8, func_00132498), the frame-size helper goes (func_002B85F8), the program's end is its ref_ name again (func_00290B48), and func_00128FD0 passes the 32-bit tag its callee stores instead of a 64-bit word built on an unset $s4.
There was a problem hiding this comment.
Reviewed exact head b9b2ca9, including the two new tooling commits. Please keep this PR draft until these corrections are qualified.
- The inline matrix finding remains: func_002E76C0 declares a 16-byte m2 object but its helpers use 48 bytes. Correct the actual object extent/alignment and recalibrate.
- All five independent negative controls still fail on this updated driver: typedef-float return becomes int; an unsupported aggregate return does not fail closed; GP address-taking selects a four-byte prefix instead of the complete readonly object; the functional path validator accepts a ../ escape; mocked capture exit 7 becomes successful CLI exit 0. These are fresh synthetic controls, not live-game false-SAME claims.
- The new retail_references call handling at tools/fcheck.py:315-319 clears only RA. A synthetic callee overwriting caller-clobbered v0 leaves the caller's earlier high-half provenance live in the enumerator, which then reports a false data address. Clear caller-clobbered provenance at calls unless a separately established contract proves preservation, and handle indirect calls conservatively. Add a regression control before using these references as alignment evidence.
- The actual TextureBits candidate (func_0027C878) still resolves to int return mode although it declares the EE 64-bit unsigned-long type. Resolve verified return contracts or reject unsupported aliases. Only this driver mode mismatch is independently demonstrated: the exact gate backend is unavailable in this review environment, so I do not claim that its comparison necessarily truncates the upper half or produces a false SAME.
The 13 supplied fcheck tests, full 299-test suite, 99-row contained index, repository and progress checks pass. Matching credit remains isolated. No independent live capture/calibration or exhaustive semantic audit of the other function bodies is claimed; those passing checks do not close the concrete source and negative-control findings above.
| { | ||
| int m0[4]; | ||
| int m1[4]; | ||
| int m2[4]; |
There was a problem hiding this comment.
[P1] Allocate the complete matrix object. m2[4] contains 16 bytes, but func_00283958(f, m2) at line 58 writes three 16-byte rows at destination offsets 0, 0x10 and 0x20. The following func_00283738 and func_00283098 also use that 48-byte matrix. The retail caller reserves sp+0x20 through sp+0x4F for it. This overruns the declared object by 32 bytes; adjacent arrays or a captured matching stack layout do not make that valid. Use a truthful, suitably aligned 48-byte matrix and recalibrate.
A level00 capture script plays New Game into level 00 (Aranos) to frame 7000: through the intro, then running with the left stick, jumping, the wrench, the pause menu, weapons and the map, in four phases. A function with a place in level 00's program is captured there, the CRC guard keeping the boot's code at the address before the load out; a resident boot function (below 0x1A7C80) at its boot address. check groups a function's states by the program they are of and builds it once for each: in a level its symbols resolve at the level's addresses (its catalogue places, the level build's placed.json, the boot's resident values, boot addresses the level moved read from the level's copies of boot code, a boot function's own copy first), against the level's bytes. build takes --level, capture lists may give states per phase, and check --retail runs retail's own bytes as the candidate, the harness's self-test.
This sets rac2 up for a C backfill: functional C for the native port, kept apart from matching C and never counted.
nonmatching/functional/andnonmatching/functional.tsv(empty) use the same layout as rac1-decomp.tests/check_repository.pynow checks that every listed function is stillINCLUDE_ASMinsrc/and that its file exists.tools/progress.pyreads onlysrc/, so nothing listed here can be counted.tools/fcheck.pyis the gate's driver for boot functions. It compiles a candidate with its unit's own flags and places the function at a scratch address, along with:$gpreferences, range-checked;OpenRAC's test-only interpreter then compares it with retail on captured calls. It has 11 tests in
tests/test_fcheck.py.nonmatching/README.mdexplains how to run the gate.The OpenRAC side is OpenRAC/OpenRAC#85. Calibration: the 513 matched boot functions reached by the menu and New Game runs give 502 SAME, 0 DIFFERS and 11 unchecked. The progress report is not touched here.