mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
feat(phase-7): -O0 split + 4 matches (42 real) + libcd.h — session B checkpoint
- per-file -O0 mechanism: split text c-subseg into boot@-O0 (src/boot.c) + 800@-O2 via splat resegmentation + Makefile target-specific CC1FLAGS; the boot/main/ game-mode-dispatch module (0x80010000-0x800123F0) is -O0, not the pinned -O2 (per-module compiler mixing, SETUP §5.5). Regression-gated byte-identical. - 4 byte-matches (38->42 real, make check BYTE-IDENTICAL throughout): GameModeDispatch (-O0 register-ptr far member), DebugMenuHandler (-O0 reserved-slot local), CdQueueBusy (-O2 if/else order + branch polarity), CdReadRequest (-O2 early-return fall-through) - PsyQ infra: include/psyq/libcd.h (CdlLOC/CdlFILE from the .gdt) + 4 named symbols (CdSearchFile/CdPosToInt/CdIntToPos/VSync) in symbols.us.txt; func_* stubs renamed - loader cluster non-jtbl COMPLETE (6 matched + 2 drafted): NON_MATCHING drafts of LoaderInitFileTable + ResourceLoadStateMachine (logically faithful, residuals in-source) - tooling: progress.py/difficulty.py fixed for the multi-file split (glob src/*.c, all asm dirs, skip forward-decls); deterministic; 42 real / 4 NM - flywheel: matching-cookbook §6 (-O0 detection + idioms), §7 (PsyQ types/symbols), T4 - build byte-identical 143dbb89f34491258bbc27810d0a12ec8b43a8dd from a full clean cycle
This commit is contained in:
@@ -208,6 +208,14 @@ build/src/%.o: src/%.c
|
||||
@echo " CC $@"
|
||||
@set -o pipefail; $(CPP) $(CPPFLAGS) $< | $(CC1_PSX) $(CC1FLAGS) | $(VENV_PY) $(MASPSX) --aspsx-version=$(ASPSX_VERSION) $(MASPSX_FLAGS) | $(AS) $(ASFLAGS) -o $@
|
||||
|
||||
# Per-module optimization override (SETUP §5.5 — per-module compiler mixing). The boot/
|
||||
# main/game-mode-dispatch module (src/boot.c, vram 0x80010000-0x800123F0) was compiled at
|
||||
# -O0, NOT the -O2 game-code default: frame-pointer setup + unfolded large-offset loads
|
||||
# are the evidence (GameModeDispatch byte-matches only at -O0). gcc 2.7.2 has no
|
||||
# per-function optimize pragma, so opt level is per-file. Target-specific CC1FLAGS (the
|
||||
# pattern recipe reads $(CC1FLAGS), so this overrides it for just build/src/boot.o):
|
||||
build/src/boot.o: CC1FLAGS := -quiet -O0 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker
|
||||
|
||||
# link (the .ld pulls in the .o by path) + objcopy to the raw PS-X EXE image.
|
||||
$(OUT): $(OBJS) $(LD_SCRIPT)
|
||||
@set -e
|
||||
|
||||
@@ -66,6 +66,13 @@ segments:
|
||||
# no-op at every real boundary here.
|
||||
align: 4
|
||||
subsegments:
|
||||
- [0x800, c, 800] # text — Phase 6: c-scaffold (src/800.c INCLUDE_ASM stubs); matched fns replace stubs
|
||||
# Per-module optimization mixing (SETUP §5.5): the boot/main/game-mode-dispatch
|
||||
# module (vram 0x80010000-0x800123F0) was compiled at -O0; the rest of the text at
|
||||
# -O2. Split into two c-subsegments so the Makefile applies per-file flags
|
||||
# (build/src/boot.o is overridden to -O0). The split is byte-identical at 100%
|
||||
# INCLUDE_ASM (Phase-7 regression gate) — opt level only affects matched C, not stubs.
|
||||
# Boundary = func_800123F0 (vram 0x800123F0 -> file vram-0x8000F800 = 0x2BF0).
|
||||
- [0x800, c, boot] # -O0 boot module -> src/boot.c (vram 0x80010000-0x800123F0)
|
||||
- [0x2BF0, c, 800] # -O2 remainder -> src/800.c (vram 0x800123F0-0x800629DC); name "800" kept (legacy) so the 38 matched fns + their asm/nonmatchings/800 paths don't migrate; NB start is now 0x2BF0, not 0x800
|
||||
- [0x531DC, data, 531DC] # data — psxexeinfo boundary; round-trips byte-identical
|
||||
- [0x65000]
|
||||
|
||||
@@ -977,3 +977,7 @@ resLoad_result = 0x800C6D10; // data
|
||||
resLoad_curId = 0x800C6D34; // data
|
||||
lzss_state = 0x800C7D24; // data
|
||||
listCdBuffer = 0x80180000; // data
|
||||
CdIntToPos = 0x80043A18; // func PsyQ libcd (Ghidra-confirmed, Phase 7)
|
||||
CdPosToInt = 0x80043B1C; // func PsyQ libcd (Ghidra-confirmed, Phase 7)
|
||||
CdSearchFile = 0x80045374; // func PsyQ libcd (Ghidra-confirmed, Phase 7)
|
||||
VSync = 0x8004239C; // func PsyQ libetc (Ghidra-confirmed, Phase 7)
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
# Unmatched difficulty inventory (generated by tools/difficulty.py — harvest queue)
|
||||
|
||||
unmatched functions : 1968
|
||||
unmatched functions : 1962
|
||||
trivial (<=5 ins) : 291
|
||||
non-jtbl leaves : 1095 (best harvest targets)
|
||||
non-jtbl leaves : 1093 (best harvest targets)
|
||||
jump-table funcs : 84 (deferred — need the rodata-island workflow, Task 2')
|
||||
|
||||
## Easiest 120 unmatched (score asc) — the work queue
|
||||
|
||||
@@ -73,6 +73,21 @@ Open example: `func_80015A74` residual (counter init vs hoisted magic constant).
|
||||
`lb` (sign-extend). Pick the load width/sign that matches the asm, then layer `& 0xff` (I2) /
|
||||
casts as needed.
|
||||
|
||||
### T4 — Branch polarity: invert the source condition to flip gcc's chosen branch
|
||||
Two source forms can be logically identical but emit **opposite** branches:
|
||||
`if (x & m) return A; return B;` vs `if ((x & m) == 0) return B; return A;`. gcc -O2 picks one
|
||||
polarity (beqz vs bnez); it may be the opposite of the target. Symptom in asm-differ: the right
|
||||
structure but a lone **beqz↔bnez flip with the two return constants swapped** between the branch
|
||||
and its `j`/delay slot. Fix: rewrite the condition with the other polarity. Also: which arm of an
|
||||
`if/else` becomes the fall-through follows source order — put the target's fall-through block in the
|
||||
`if`, the branched-to block in the `else` (e.g. `if (a != b){…} else {…}` if the `==` block sits
|
||||
last). Example: `CdQueueBusy` (1405 → 210 via if/else order, 210 → 0 via the 0x20 polarity flip).
|
||||
Likewise multi-exit functions: write the **success/main return LAST** (it becomes the fall-through
|
||||
into the shared epilogue) and error cases as **early `return`s** (they branch in). Reversing this —
|
||||
`if (ok) { … return good; } return 0;` — makes `return 0` the fall-through and duplicates the
|
||||
`j epilogue`/`move v0,zero` tail. Example: `CdReadRequest` (305 → 0 by flipping to `if (busy) return 0;
|
||||
… return cdReq_result;`).
|
||||
|
||||
---
|
||||
|
||||
## §3 When a diff is pure scheduling → decomp-permuter (harness built, Phase 6)
|
||||
@@ -118,3 +133,77 @@ as decomp-permuter candidates rather than hand-grinding.
|
||||
- **Hoisted-invariant vs IV-init ordering** — a loop-invariant load scheduled before/after the
|
||||
counter init; not reachable by C-source changes (permuter stuck at base). Needs `PERM_*` or
|
||||
insight. Example: `func_80015A74` (uint→BCD). See §3.
|
||||
|
||||
---
|
||||
|
||||
## §6 Per-module optimization mixing — the -O0 boot module (Phase 7)
|
||||
**Finding (2026-06-14):** the EXE mixes optimization levels per original translation unit (the
|
||||
§5.5 Xenogears-style mixing, now concrete). The **boot/main/game-mode-dispatch module** — a clean
|
||||
contiguous block at **vram 0x80010000–0x800123F0** (~50 funcs: `start`, `main`, `GameModeDispatch`,
|
||||
`DebugMenuHandler`, the game-mode handlers) — was compiled at **-O0**. Everything from 0x800123F0
|
||||
onward (every match so far + the file-loader cluster) is **-O2**. **Always opt-fingerprint a new
|
||||
function before writing C** — the pinned `-O2` is NOT global.
|
||||
|
||||
### Detecting the opt level (do this first)
|
||||
gcc 2.7.2 **-O0 keeps a frame pointer**: `addu $fp,$sp,$zero` (`21F0A003`) in the prologue +
|
||||
`addu $sp,$fp,$zero` in the epilogue; **-O2 omits it**. Grep the target `.s`:
|
||||
`grep -l 21F0A003 asm/nonmatchings/<seg>/<fn>.s` → hit = **-O0**, miss = -O2. Module scan: classify
|
||||
every `.s` by that signature, sort by address; the contiguous -O0 run is the module (the boot block
|
||||
is the one early -O0 run). Other -O0 tells: redundant `move`/`addu rd,rs,$zero` copies; a `nop` after
|
||||
every load (no load-delay scheduling); single-use values parked in callee-saved `s0..`; large
|
||||
constant member offsets left **unfolded** (`la $reg,sym` + `lhu off($reg)`), where -O2 folds
|
||||
`sym+off` into one load.
|
||||
|
||||
### -O0 idiom — far struct member via a `register` base pointer
|
||||
Target: `lui s0,%hi(BASE); addiu s0,s0,%lo(BASE); lui at,1; addu at,s0,at; lhu v0,-0x5c52(at)` =
|
||||
load a u16 at `BASE + 0xA3AE`. The `lui 1 / addu / -0x5c52` is just `as` expanding a register-relative
|
||||
load whose offset (0xA3AE) exceeds 0x7FFF (%hi=1, %lo=-0x5C52). C — a **`register`-qualified pointer**
|
||||
to the base, then offset-deref:
|
||||
```c
|
||||
extern u8 BASE[];
|
||||
register u8 *p = BASE;
|
||||
... *(u16 *)(p + 0xA3AE) ... /* base stays in a callee-saved reg; offset left unfolded */
|
||||
```
|
||||
`register` is **load-bearing**: drop it and -O0 spills the pointer (extra sw/lw, bigger frame); and
|
||||
-O1/-O2 fold it all back to `lhu sym+off` (no base reg, no frame pointer). Plain `BASE[idx]` or
|
||||
`((struct*)BASE)->m` also **fold** at -O0 → wrong. Example: `GameModeDispatch` (0x80010B40) =
|
||||
`gameModeHandlerTable[*(u16*)(p+0xA3AE)]()` — byte-exact (asm-differ 0).
|
||||
|
||||
### -O0 idiom — a reserved (unstored) local sets the frame size
|
||||
A named local the original declares but our toolchain wouldn't store (e.g. a call result the
|
||||
original checks directly) still **reserves its 8-byte stack slot** at -O0, enlarging the frame.
|
||||
If a near-match differs **only** by frame size + a uniform save-offset shift (every instruction
|
||||
identical), add the missing local as a **declaration-only** `int x;` + `(void)x;` — no store, no
|
||||
load, no `-Wall` noise, no code, just the slot. (Assigning the result to the local instead emits a
|
||||
`sw`/`lw` pair the target lacks.) Example: `DebugMenuHandler` (0x80011144) — `if (CdReadRequest(...)
|
||||
!= 0)` with a reserved `int iVar1;` → frame 0x20 (score 42 → 0).
|
||||
|
||||
### Build mechanism — per-file opt override (splat resegmentation)
|
||||
One original .c = one opt level; you can't mix within a compile unit, and gcc 2.7.2 has no
|
||||
per-function optimize pragma. **Split the module into its own splat c-subsegment** and give that
|
||||
object its flags:
|
||||
- `config/splat.us.exe.yaml`: split the text subseg at the module boundary (a function start; file
|
||||
off = vram − 0x8000F800). Boot module = `[0x800, c, boot] → src/boot.c`; the rest stays
|
||||
`[0x2BF0, c, 800] → src/800.c` (name kept to avoid migrating matched C).
|
||||
- `Makefile`: target-specific override — `build/src/boot.o: CC1FLAGS := …-O0…` (the pattern recipe
|
||||
reads `$(CC1FLAGS)`, so this overrides just that object).
|
||||
- **Regression gate:** the split must rebuild **byte-identical at 100% INCLUDE_ASM** before any -O0 C
|
||||
is added (opt level only affects matched C, not stubs). Verified for the boot split.
|
||||
Reuse this hook for any future module whose flags differ (another opt level, bare `divu`, etc.).
|
||||
|
||||
---
|
||||
|
||||
## §7 PsyQ SDK types & symbols (the library-call prerequisite)
|
||||
A function that calls PsyQ library routines needs both the SDK **types** and the library **symbols**:
|
||||
- **Types:** pull the EXACT layout from Ghidra's imported `.gdt` (`mcp__ghidra__types get <Name>`,
|
||||
category e.g. `/LIBCD.H`) — never guess offsets (G1). Declare in `include/psyq/<lib>.h` (it
|
||||
`#include "common.h"` for u8/u32, guard-safe). Verify sizes with a compile-time assert:
|
||||
`typedef char a[sizeof(T)==N ? 1 : -1];`.
|
||||
- **Symbols:** PsyQ fns are already named in the Ghidra DB but absent from our exported
|
||||
`config/symbols.us.txt`, so the build shows them as `func_<addr>`. Add `Name = 0xADDR; // func`
|
||||
to `symbols.us.txt` (R15) AND rename the matching `INCLUDE_ASM("…", func_<addr>)` stub(s) in
|
||||
`src/` to the canonical name (splat won't rewrite a committed `.c`). Re-extract →
|
||||
**byte-identical** (label-only change). No matched C may already reference the old name.
|
||||
- Done Phase 7: `include/psyq/libcd.h` (CdlLOC 4B, CdlFILE 24B + CdSearchFile/CdPosToInt/CdIntToPos
|
||||
protos) + the 4 libcd/libetc symbols — unlocks the file-loader cluster. Same pattern for
|
||||
libgpu/libgte/libspu as they come up.
|
||||
|
||||
+7
-7
@@ -1,17 +1,17 @@
|
||||
# BFM matching progress (generated by tools/progress.py — authoritative)
|
||||
|
||||
REAL substantive matches : 38 <- the Gen1-exit >=25 bar counts THIS
|
||||
NON_MATCHING (near-miss) : 2
|
||||
REAL substantive matches : 42 <- the Gen1-exit >=25 bar counts THIS
|
||||
NON_MATCHING (near-miss) : 4
|
||||
splat-auto empty no-ops : 42
|
||||
INCLUDE_ASM stubs : 1968
|
||||
INCLUDE_ASM stubs : 1962
|
||||
data blobs (excluded) : 4
|
||||
----------------------------------------
|
||||
matchable functions : 2050
|
||||
REAL / matchable : 38 / 2050 = 1.85%
|
||||
incl. empties : 80 / 2050 = 3.90%
|
||||
REAL / matchable : 42 / 2050 = 2.05%
|
||||
incl. empties : 84 / 2050 = 4.10%
|
||||
|
||||
REAL matches: LoaderResetReadState ResourceGetCdLoc func_80012AB0 func_800168B4 func_80018F20 func_80019018 func_80019198 func_80019378 func_80019388 func_80019398 func_800193A8 func_8001AA78 func_8001AA88 func_8001B22C func_8001B374 func_8001B384 func_8001B85C func_8001BE20 func_8001BFA0 func_8001BFE8 func_8001D0E8 func_80029254 func_80029264 func_80029504 func_80029514 func_8002953C func_8002954C func_8002957C func_8002958C func_80029FD4 func_8002A26C func_8002A27C func_8002A4B8 func_8002A4C8 func_8002A728 func_8002A738 func_8002A998 func_8002A9A8
|
||||
NON_MATCHING: func_80015A74 func_80016714
|
||||
REAL matches: CdQueueBusy CdReadRequest DebugMenuHandler GameModeDispatch LoaderResetReadState ResourceGetCdLoc func_80012AB0 func_800168B4 func_80018F20 func_80019018 func_80019198 func_80019378 func_80019388 func_80019398 func_800193A8 func_8001AA78 func_8001AA88 func_8001B22C func_8001B374 func_8001B384 func_8001B85C func_8001BE20 func_8001BFA0 func_8001BFE8 func_8001D0E8 func_80029254 func_80029264 func_80029504 func_80029514 func_8002953C func_8002954C func_8002957C func_8002958C func_80029FD4 func_8002A26C func_8002A27C func_8002A4B8 func_8002A4C8 func_8002A728 func_8002A738 func_8002A998 func_8002A9A8
|
||||
NON_MATCHING: LoaderInitFileTable ResourceLoadStateMachine func_80015A74 func_80016714
|
||||
|
||||
build SHA1: 143dbb89f34491258bbc27810d0a12ec8b43a8dd (byte-identical)
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/* psyq/libcd.h — PSY-Q libcd (CD-ROM) types & prototypes used by the file loader.
|
||||
*
|
||||
* Struct layouts are the authoritative ones from the imported PsyQ 4.0 .gdt
|
||||
* (Ghidra category /LIBCD.H) — verified offsets/sizes, not guessed (G1). Function
|
||||
* prototypes use the canonical PsyQ signatures; their symbols are pinned in
|
||||
* config/symbols.us.txt (and already named in the Ghidra DB). Grow this as more
|
||||
* libcd surface is matched. Companion to common.h (provides the u8/u32 typedefs).
|
||||
*/
|
||||
#ifndef PSYQ_LIBCD_H
|
||||
#define PSYQ_LIBCD_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
/* CD logical location — minute/second/sector are BCD. 4 bytes. (/LIBCD.H) */
|
||||
typedef struct {
|
||||
u8 minute; /* +0x00 */
|
||||
u8 second; /* +0x01 */
|
||||
u8 sector; /* +0x02 */
|
||||
u8 track; /* +0x03 */
|
||||
} CdlLOC;
|
||||
|
||||
/* CD file descriptor (CdSearchFile result / directory entry). 24 bytes. (/LIBCD.H) */
|
||||
typedef struct {
|
||||
CdlLOC pos; /* +0x00 file start location */
|
||||
u32 size; /* +0x04 file size in bytes */
|
||||
char name[16]; /* +0x08 file name */
|
||||
} CdlFILE;
|
||||
|
||||
/* libcd prototypes (symbols in config/symbols.us.txt). */
|
||||
extern CdlFILE *CdSearchFile(CdlFILE *fp, char *name); /* 0x80045374 */
|
||||
extern int CdPosToInt(CdlLOC *p); /* 0x80043B1C */
|
||||
extern CdlLOC *CdIntToPos(int i, CdlLOC *p); /* 0x80043A18 */
|
||||
|
||||
#endif /* PSYQ_LIBCD_H */
|
||||
@@ -23,9 +23,12 @@ rodata-island foundation + LZSS match are DEFERRED to a focused sub-project afte
|
||||
## Task checklist (REORDERED — execution order top to bottom)
|
||||
- [x] **Task 3 — Report machinery** ✓ DONE. `tools/{progress,difficulty,dup_report}.py` + `make report` (deterministic) + `make sig-refresh`. Authoritative baseline **14 real / 42 empty / 2 NM / 1992 stubs / 2050 matchable** (REAL/matchable=0.68%); empties audit 42/42 clean; dup leverage 8 h_exact + 44 h_norm redundant; harvest queue 1119 non-jtbl leaves + 313 trivial. Digests → `docs/{progress,difficulty,duplicates}.md`.
|
||||
- [x] **Task 4 — Harvest to ≥25 real** ✓ DONE. **36 real matches** (14 + 22 trivial accessor leaves: getters/setters of globals, one mask, one two-store), build BYTE-IDENTICAL → all 22 byte-perfect. **≥25 bar PASS** (margin 36/25). `.run/harvest.py` = the applier. Dedup-collapse skipped (the sig dup groups are mostly PsyQ library fragments/epilogues, not real funcs). LZSS still required (Task 2′).
|
||||
- [~] **Task 5 — Loader cluster** PARTIAL. Matched: **ResourceGetCdLoc** (15, table lookup), **LoaderResetReadState** (32, store seq) → byte-identical. Triage of the rest:
|
||||
- [x] **Task 5.5 — Per-file -O0 split mechanism** ✓ DONE (session B, Drew-approved). MAJOR FINDING: per-module opt mixing is real (§5.5). The **boot/main/game-mode-dispatch module @ vram 0x80010000–0x800123F0** (~50 funcs incl. start/main/GameModeDispatch/DebugMenuHandler) is **-O0**, not the pinned -O2; everything ≥0x800123F0 is -O2. Mechanism: split text c-subseg → `[0x800,c,boot]→src/boot.c` (-O0 via Makefile target-specific CC1FLAGS) + `[0x2BF0,c,800]→src/800.c` (-O2, matched fns untouched). Regression gate PASS (byte-identical at 100% INCLUDE_ASM). Cookbook §6 added (detect via `21F0A003` frame-ptr sig; `register`-pointer far-member idiom). Reusable for every future flag-divergent module.
|
||||
- [x] **Task 5.6 — PsyQ CD struct header** ✓ DONE (session B). `include/psyq/libcd.h` (CdlLOC 4B + CdlFILE 24B, offsets from Ghidra `/LIBCD.H`, G1) + 3 libcd protos; added 4 PsyQ symbols to `config/symbols.us.txt` (CdSearchFile/CdPosToInt/CdIntToPos/VSync — Ghidra-confirmed, R15) and renamed their `func_*` stubs in src/800.c. Build BYTE-IDENTICAL (label-only); header compiles, sizeof asserts hold. Unlocks the file-loader cluster (LoaderInitFileTable, ResourceLoadStateMachine, the jtbl loaders). Cookbook §7 added (PsyQ types/symbols pattern). **NB symbols.us.txt now has 983 lines (4 appended, un-sorted — re-merge note for R15).**
|
||||
- [~] **Task 5 — Loader cluster** PARTIAL (42 real total; non-jtbl 5/7 matched). Matched (score 0, byte-identical): **ResourceGetCdLoc** (15), **LoaderResetReadState** (32), **GameModeDispatch** (29, -O0 `register`-ptr far member), **DebugMenuHandler** (30, -O0, reserved-slot local), **CdQueueBusy** (35, -O2 — if/else order + branch polarity), **CdReadRequest** (53, -O2 — early-return fall-through). Triage of the rest:
|
||||
- **jtbl → Task 2′ (rodata-gated):** CdReadStateMachine (385), CdReadSectorReadyCB (424, sole LZSS caller), SaveLoadRoutine (1139), StreamLoadStateMachine (459).
|
||||
- **non-jtbl, need Ghidra+iteration:** GameModeDispatch (29, base+offset addr + callee-saved across jalr), DebugMenuHandler (30), CdReadRequest (53), LoaderInitFileTable (133), ResourceLoadStateMachine (211), CdQueueBusy (35). Match or NON_MATCHING-draft in continuation.
|
||||
- **non-jtbl COMPLETE (6 matched + 2 drafted):** **LoaderInitFileTable** (133) + **ResourceLoadStateMachine** (211) → **NON_MATCHING-drafted** (logically faithful, libcd.h-typed; per-function residual notes in src/800.c — LoaderInit: regalloc/loop-invariant hoisting + name=base-0x14; ResourceLoad: block placement (state 0→1→2→3 layout vs nested-if) + reserved-local frame 0x38 vs 0x28 + func_8002D4C8 arg & 0xFFFF). **NON_MATCHING count now 4.** Byte-match both in a later structural/permuter pass.
|
||||
- **Report tooling fixed for the multi-file split:** `tools/{progress,difficulty}.py` now glob all `src/*.c` + search all `asm/nonmatchings/*` subdirs, and `progress.py` skips `extern …(…);` forward-declarations (they were swallowing the next fn + double-counting). Deterministic; 42 real / 1964 stubs consistent across both. (Note for PhaseEnd.)
|
||||
- [ ] **Task 2′ — Focused LZSS + rodata-island sub-project** (DEFERRED hard task; Gen1-exit LZSS gate). **Max.** See investigation findings below.
|
||||
- [ ] **Task 6 — Gen1-exit close-out** (README, checklist, ≥3-session zero-regression evidence). **xHigh.**
|
||||
- [ ] **Task 7 — PhaseEnd_Phase7** (Gen1 synthesis, milestone gate). **Max · Tier 1.**
|
||||
@@ -36,6 +39,7 @@ NOTE: Gen1 exit needs ≥3 SESSIONS of green `make check` — cannot complete th
|
||||
|
||||
## Per-session `make check` green log (≥3 sessions needed for the milestone)
|
||||
- 2026-06-14 (session A): `make check` → `143dbb89… BYTE-IDENTICAL` ✓ — baseline restored + reproducibility fix, reports built, **38 real matches** (22 accessor leaves + ResourceGetCdLoc + LoaderResetReadState), build byte-identical throughout. [need ≥2 more sessions]
|
||||
- 2026-06-14 (session B): `make check` → `143dbb89… BYTE-IDENTICAL` ✓ — **per-file -O0 split mechanism** (src/boot.c + Makefile per-file flags); **4 real matches** (GameModeDispatch, DebugMenuHandler, CdQueueBusy, CdReadRequest) → **42 real**; **PsyQ libcd.h infra** (CdlLOC/CdlFILE + 4 named symbols, unlocks the loader cluster); **LoaderInitFileTable + ResourceLoadStateMachine NON_MATCHING-drafted** (→ 4 NM) — **Task 5 non-jtbl loaders COMPLETE** (6 matched + 2 drafted); report tooling fixed (multi-file); cookbook §6/§7/T4. Build byte-identical throughout. [need ≥1 more session]
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,114 +1,5 @@
|
||||
#include "common.h"
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", start);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_800100A0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", __do_global_dtors);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", main);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_8001096C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_8001099C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010A08);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010A98);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010AE0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010B10);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", GameModeDispatch);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010BB4);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010C7C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010CEC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010D60);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010DA0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010DE0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010E14);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010E48);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010E7C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010ED4);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80010F80);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_800110CC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", DebugMenuHandler);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_800111BC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011220);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_8001125C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_800112A8);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_800112C8);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011320);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011350);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011380);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011680);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_800116E0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011778);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011818);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_800118AC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011928);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011998);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_800119F0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011A3C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011ADC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011B7C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011C10);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011C8C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011CFC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011D54);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011DA0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011DCC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011DF4);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011E24);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011E84);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80011EB4);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_800120DC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_8001212C);
|
||||
#include "psyq/libcd.h"
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_800123F0);
|
||||
|
||||
@@ -631,7 +522,113 @@ void func_800193A8(s32 arg0) {
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_800193B8);
|
||||
|
||||
#ifdef NON_MATCHING
|
||||
extern void LoaderResetReadState(void);
|
||||
extern int VSync(int mode);
|
||||
extern CdlFILE D_80063058;
|
||||
extern char cdpath_DEBUG_BIN[];
|
||||
extern u8 D_80062C38; /* path table entry[0].file (CdlFILE), entries stride 0x30; names at -0x14 */
|
||||
extern u8 D_80062C68; /* path entry[1].file.pos (CdlLOC), the 8 .CD files, stride 0x30 */
|
||||
extern s32 debugBinPresent;
|
||||
extern s32 D_800747D4;
|
||||
extern s32 D_80063074;
|
||||
extern s32 D_800747D8;
|
||||
extern u8 cdFileLocTable[]; /* out: {CdlLOC pos; u32 size} per sub-file, 8B stride */
|
||||
extern u8 D_800AE834; /* cdFileLocTable + 4 (the size field) */
|
||||
extern s32 listCdBuffer; /* LIST.CD content: 8B records {value; size} */
|
||||
extern u8 D_80180004; /* listCdBuffer + 4 (the size field) */
|
||||
/* file-loader directory resolver (boot @0x800101fc): probe \DEBUG.BIN;1, resolve the 21
|
||||
* CdPathTable entries via CdSearchFile, read LIST.CD (0xE40 B) into listCdBuffer, then build
|
||||
* cdFileLocTable (CdlLOC+size per sub-file) over the 8 .CD files. Phase 3 T2.
|
||||
* NON_MATCHING: logically faithful, structurally close (138 vs 133 ins) but not byte-exact.
|
||||
* Residual is register allocation / loop-invariant hoisting — the target keeps &D_80063058,
|
||||
* &D_80062C38, &D_80062C68 in callee-saved regs and derives the name arg as (base - 0x14)
|
||||
* rather than a separate symbol; a multi-iteration / decomp-permuter target for a later pass. */
|
||||
void LoaderInitFileTable(void) {
|
||||
CdlFILE *res;
|
||||
int tries;
|
||||
int pathOff;
|
||||
int pathN;
|
||||
int base;
|
||||
int n;
|
||||
int fileIdx;
|
||||
int bufIdx;
|
||||
int j;
|
||||
int outer;
|
||||
int locOff;
|
||||
int dstOff;
|
||||
int srcOff;
|
||||
int *pCount;
|
||||
int *pOff;
|
||||
CdlLOC *p;
|
||||
|
||||
LoaderResetReadState();
|
||||
tries = 0;
|
||||
do {
|
||||
res = CdSearchFile(&D_80063058, cdpath_DEBUG_BIN);
|
||||
tries++;
|
||||
if (res != (CdlFILE *)-1) {
|
||||
break;
|
||||
}
|
||||
} while (tries < 0x10);
|
||||
pathN = 0;
|
||||
pathOff = 0;
|
||||
debugBinPresent = (res != (CdlFILE *)0);
|
||||
D_800747D4 = 1;
|
||||
D_80063074 = 0;
|
||||
D_800747D8 = 0;
|
||||
do {
|
||||
do {
|
||||
res = CdSearchFile((CdlFILE *)(&D_80062C38 + pathOff), (char *)(&D_80062C38 - 0x14 + pathOff));
|
||||
} while ((u32)((int)res + 1) < 2);
|
||||
pathN++;
|
||||
pathOff += 0x30;
|
||||
} while (pathN < 0x15);
|
||||
do {
|
||||
fileIdx = 0;
|
||||
n = CdReadRequest(&D_80062C38, &listCdBuffer, 0xE40, 0);
|
||||
if (n != 0) {
|
||||
break;
|
||||
}
|
||||
VSync(0);
|
||||
} while (1);
|
||||
bufIdx = 0;
|
||||
outer = 0;
|
||||
locOff = 0;
|
||||
pCount = &listCdBuffer;
|
||||
do {
|
||||
base = CdPosToInt((CdlLOC *)(&D_80062C68 + locOff));
|
||||
n = *pCount;
|
||||
pCount += 2;
|
||||
bufIdx++;
|
||||
j = 0;
|
||||
if (n > 0) {
|
||||
dstOff = fileIdx * 8;
|
||||
p = (CdlLOC *)(cdFileLocTable + dstOff);
|
||||
srcOff = bufIdx * 8;
|
||||
pOff = &listCdBuffer + bufIdx * 2;
|
||||
do {
|
||||
if (*pOff != 0) {
|
||||
CdIntToPos(*pOff + base, p);
|
||||
*(s32 *)(&D_800AE834 + dstOff) = *(s32 *)(&D_80180004 + srcOff);
|
||||
}
|
||||
pOff += 2;
|
||||
srcOff += 8;
|
||||
pCount += 2;
|
||||
bufIdx++;
|
||||
p += 2;
|
||||
dstOff += 8;
|
||||
j++;
|
||||
fileIdx++;
|
||||
} while (j < n);
|
||||
}
|
||||
outer++;
|
||||
locOff += 0x30;
|
||||
} while (outer < 8);
|
||||
}
|
||||
#else
|
||||
INCLUDE_ASM("asm/nonmatchings/800", LoaderInitFileTable);
|
||||
#endif
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80019930);
|
||||
|
||||
@@ -668,7 +665,44 @@ void LoaderResetReadState(void) {
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80019A10);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", CdReadRequest);
|
||||
extern s32 CdQueueBusy(void);
|
||||
extern void CdReadStateMachine(int);
|
||||
extern s32 cdReq_curSector;
|
||||
extern void *cdReq_dest;
|
||||
extern s32 cdReq_size;
|
||||
extern void *cdReq_cdlFile;
|
||||
extern s32 cdReq_result;
|
||||
extern s32 D_800AE720;
|
||||
extern s32 D_800AE724;
|
||||
/* Read-request dispatcher (Phase 3 T2). Refuse while CdQueueBusy(); dedup on the request's
|
||||
* start sector (*cdlFile) vs the in-flight cdReq_curSector; stash dest/size/cdlFile/mode into
|
||||
* the control block, set the "first read" flag D_800AE720 = (mode == 0), drive the state
|
||||
* machine, return cdReq_result. */
|
||||
s32 CdReadRequest(int *cdlFile, void *dest, s32 size, s32 mode) {
|
||||
s32 sector;
|
||||
if (CdQueueBusy() != 0) {
|
||||
return 0;
|
||||
}
|
||||
if (cdReq_curSector == 0) {
|
||||
sector = *cdlFile;
|
||||
} else {
|
||||
sector = *cdlFile;
|
||||
if (sector != cdReq_curSector) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
cdReq_dest = dest;
|
||||
cdReq_size = size;
|
||||
cdReq_cdlFile = cdlFile;
|
||||
D_800AE724 = mode;
|
||||
D_800AE720 = 0;
|
||||
cdReq_curSector = sector;
|
||||
if (mode == 0) {
|
||||
D_800AE720 = 1;
|
||||
}
|
||||
CdReadStateMachine(0);
|
||||
return cdReq_result;
|
||||
}
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", CdReadStateMachine);
|
||||
|
||||
@@ -739,7 +773,107 @@ void func_8001B384(void) {
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_8001B394);
|
||||
|
||||
#ifdef NON_MATCHING
|
||||
extern int func_8001A114(void);
|
||||
extern void func_8001B710(void);
|
||||
extern void func_8002D4C8(int arg0, int arg1);
|
||||
extern void func_80036D58(int arg0);
|
||||
extern int StreamLoadStateMachine(int arg0, void *loc, int n);
|
||||
extern s32 CdQueueBusy(void); /* defined later in this file */
|
||||
extern s32 ResourceGetCdLoc(s16 arg0); /* defined later in this file */
|
||||
extern s32 resLoad_state;
|
||||
extern s32 resLoad_curId;
|
||||
extern s32 resLoad_lastId;
|
||||
extern s32 resLoad_loadedFileIdx;
|
||||
extern s32 resLoad_result;
|
||||
extern s32 cdReq_curSector;
|
||||
extern u8 resourceIdMap[]; /* 6B entries {s16 fileIdx; s16 D_8006313A; s16 D_8006313C} */
|
||||
extern u8 D_8006313A;
|
||||
extern u8 D_8006313C;
|
||||
extern u8 cdFileLocTable[];
|
||||
extern s32 D_800AE6F4;
|
||||
extern s32 D_800AE70C;
|
||||
/* loads a resource by resLoad_curId via resourceIdMap; load-once cache
|
||||
* (resLoad_lastId/resLoad_loadedFileIdx). field0<0 -> non-CD path func_80036D58; else
|
||||
* ResourceGetCdLoc -> StreamLoadStateMachine -> func_8002D4C8 post-process. Polled on
|
||||
* resLoad_state; done flag resLoad_result. Phase 3 T3.
|
||||
* NON_MATCHING: logically faithful (Ghidra-derived), body close but not byte-exact. Residuals:
|
||||
* (1) block placement — the target lays state blocks 0->1->2->3 in order under a top beq-chain
|
||||
* dispatch (order 1,0,2,3); this nested-if emits the state-2/3 block inline. (2) frame 0x38 vs
|
||||
* 0x28 (~16B reserved locals the original keeps). (3) func_8002D4C8 arg is field2 & 0xFFFF.
|
||||
* A switch() risks a rodata jump table (target has none). A later structural / permuter pass. */
|
||||
void ResourceLoadStateMachine(void) {
|
||||
int result;
|
||||
|
||||
result = 0;
|
||||
if (resLoad_state != 1) {
|
||||
if (resLoad_state != 0) {
|
||||
if (resLoad_state == 2) {
|
||||
D_800AE6F4 = 0;
|
||||
D_800AE70C = 0;
|
||||
resLoad_state = 3;
|
||||
} else if (resLoad_state != 3) {
|
||||
goto done;
|
||||
}
|
||||
if (func_8001A114() != 0) {
|
||||
resLoad_state = 0;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
if (resLoad_curId == resLoad_lastId) {
|
||||
resLoad_result = 1;
|
||||
return;
|
||||
}
|
||||
if (*(s16 *)(resourceIdMap + resLoad_curId * 6) == resLoad_loadedFileIdx &&
|
||||
*(s16 *)(&D_8006313C + resLoad_curId * 6) != 0) {
|
||||
func_8002D4C8(*(s16 *)(&D_8006313C + resLoad_curId * 6), 0);
|
||||
resLoad_lastId = resLoad_curId;
|
||||
resLoad_result = 1;
|
||||
return;
|
||||
}
|
||||
if (CdQueueBusy() != 0) {
|
||||
goto done;
|
||||
}
|
||||
if (*(s16 *)(resourceIdMap + resLoad_curId * 6) < 0) {
|
||||
func_80036D58(*(s16 *)(&D_8006313A + resLoad_curId * 6));
|
||||
resLoad_result = 1;
|
||||
return;
|
||||
}
|
||||
if (ResourceGetCdLoc((s16)resLoad_curId) == 0) {
|
||||
resLoad_result = 1;
|
||||
return;
|
||||
}
|
||||
if (cdReq_curSector != 0 &&
|
||||
*(s32 *)(cdFileLocTable + *(s16 *)(resourceIdMap + resLoad_curId * 6) * 8) != cdReq_curSector) {
|
||||
goto done;
|
||||
}
|
||||
cdReq_curSector = *(s32 *)(cdFileLocTable + *(s16 *)(resourceIdMap + resLoad_curId * 6) * 8);
|
||||
resLoad_state++;
|
||||
}
|
||||
result = StreamLoadStateMachine(*(s16 *)(&D_8006313A + resLoad_curId * 6),
|
||||
cdFileLocTable + *(s16 *)(resourceIdMap + resLoad_curId * 6) * 8, 0x10);
|
||||
if (result == 2) {
|
||||
resLoad_state++;
|
||||
result = 0;
|
||||
}
|
||||
done:
|
||||
if (result != 0) {
|
||||
resLoad_loadedFileIdx = *(s16 *)(resourceIdMap + resLoad_curId * 6);
|
||||
if (*(s16 *)(&D_8006313C + resLoad_curId * 6) > 0) {
|
||||
func_8002D4C8(*(s16 *)(&D_8006313C + resLoad_curId * 6), 0);
|
||||
resLoad_lastId = resLoad_curId;
|
||||
if (resLoad_curId == 0x3D) {
|
||||
func_8001B710();
|
||||
}
|
||||
}
|
||||
resLoad_state = 0;
|
||||
cdReq_curSector = 0;
|
||||
}
|
||||
resLoad_result = result;
|
||||
}
|
||||
#else
|
||||
INCLUDE_ASM("asm/nonmatchings/800", ResourceLoadStateMachine);
|
||||
#endif
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_8001B710);
|
||||
|
||||
@@ -1652,7 +1786,33 @@ INCLUDE_ASM("asm/nonmatchings/800", func_80034B0C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80034B3C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", CdQueueBusy);
|
||||
extern s32 D_8006AEF8;
|
||||
extern s32 D_8006AEFC;
|
||||
extern u8 D_80076214;
|
||||
extern u8 D_8007620C;
|
||||
/* CD read-queue status: head==tail (D_8006AEF8/FC equal) => idle; flags D_80076214 (a
|
||||
* pending/error byte) and D_8007620C (bit7/bit5) classify the busy/result state.
|
||||
* Returns 0=idle done, 8=had pending, 2/4/1=busy variants. */
|
||||
s32 CdQueueBusy(void) {
|
||||
if (D_8006AEF8 != D_8006AEFC) {
|
||||
if (D_80076214 == 0) {
|
||||
if (D_8007620C & 0x80) {
|
||||
return 2;
|
||||
}
|
||||
if (D_8007620C & 0x20) {
|
||||
return 4;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
D_8007620C = 0;
|
||||
if (D_80076214 == 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
D_80076214 = 0;
|
||||
return 8;
|
||||
}
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80034C24);
|
||||
|
||||
@@ -2350,7 +2510,7 @@ INCLUDE_ASM("asm/nonmatchings/800", func_800422E8);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80042374);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_8004239C);
|
||||
INCLUDE_ASM("asm/nonmatchings/800", VSync);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", VSYNC_OBJ_84);
|
||||
|
||||
@@ -2490,9 +2650,9 @@ INCLUDE_ASM("asm/nonmatchings/800", func_800439D4);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_800439F8);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80043A18);
|
||||
INCLUDE_ASM("asm/nonmatchings/800", CdIntToPos);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80043B1C);
|
||||
INCLUDE_ASM("asm/nonmatchings/800", CdPosToInt);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80043B9C);
|
||||
|
||||
@@ -2558,7 +2718,7 @@ INCLUDE_ASM("asm/nonmatchings/800", callback);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", BIOS_OBJ_1728);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", func_80045374);
|
||||
INCLUDE_ASM("asm/nonmatchings/800", CdSearchFile);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/800", ISO9660_OBJ_F8);
|
||||
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
#include "common.h"
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", start);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_800100A0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", __do_global_dtors);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", main);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_8001096C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_8001099C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010A08);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010A98);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010AE0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010B10);
|
||||
|
||||
/* (*gameModeHandlerTable[gameMode])() — 18 handlers @0x800629F4 (entry [7] =
|
||||
* DebugMenuHandler). gameMode is a u16 at D_800AF630 + 0xA3AE (= 0x800B99DE) in the
|
||||
* main state block. NOTE: `register` is REQUIRED to match — at -O0 (this module) it
|
||||
* keeps the struct base in a callee-saved reg with no stack spill, and the 0xA3AE
|
||||
* (>0x7FFF) member offset then assembles to the +0x10000/-0x5C52 split. */
|
||||
extern void (*gameModeHandlerTable[])(void);
|
||||
extern u8 D_800AF630[];
|
||||
void GameModeDispatch(void) {
|
||||
register u8 *p = D_800AF630;
|
||||
gameModeHandlerTable[*(u16 *)(p + 0xA3AE)]();
|
||||
}
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010BB4);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010C7C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010CEC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010D60);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010DA0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010DE0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010E14);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010E48);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010E7C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010ED4);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80010F80);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_800110CC);
|
||||
|
||||
extern void func_8001A9F8(int);
|
||||
extern int CdReadRequest(void *dst, void *src, int arg2, int arg3);
|
||||
extern void func_80010AE0(int);
|
||||
extern void func_80011778(void);
|
||||
extern void func_80015310(void);
|
||||
extern u8 D_800AE888;
|
||||
extern void *loadDestPtrTable;
|
||||
/* gameMode 7 handler (gameModeHandlerTable[7]) — the TCRF L3 debug menu. Streams the
|
||||
* room-select overlay (cdFileLocTable[11] -> 0x800CEDF8) via CdReadRequest, then inits it
|
||||
* (func_80011778 / func_80015310). Retail: only room-select works. Phase 3 T8. */
|
||||
void DebugMenuHandler(void) {
|
||||
int iVar1; /* -O0 reserves this local's 8-byte slot (frame 0x20); the original
|
||||
* checks CdReadRequest's result directly (no store) — assigning iVar1
|
||||
* here would emit a store the target lacks, so it stays declaration-only. */
|
||||
func_8001A9F8(0);
|
||||
if (CdReadRequest(&D_800AE888, loadDestPtrTable, 0, 0) != 0) {
|
||||
func_80010AE0(0x3E0);
|
||||
func_80011778();
|
||||
func_80015310();
|
||||
}
|
||||
(void)iVar1;
|
||||
}
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_800111BC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011220);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_8001125C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_800112A8);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_800112C8);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011320);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011350);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011380);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011680);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_800116E0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011778);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011818);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_800118AC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011928);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011998);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_800119F0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011A3C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011ADC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011B7C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011C10);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011C8C);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011CFC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011D54);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011DA0);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011DCC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011DF4);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011E24);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011E84);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_80011EB4);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_800120DC);
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/boot", func_8001212C);
|
||||
+23
-15
@@ -11,11 +11,17 @@ Usage: tools/difficulty.py [TOP] (TOP = how many easy rows in the md digest, d
|
||||
import re, sys, pathlib
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
SRC = ROOT / "src" / "800.c"
|
||||
ASM = ROOT / "asm" / "nonmatchings" / "800"
|
||||
SRCS = sorted((ROOT / "src").glob("*.c")) # every c-segment (src/boot.c, src/800.c, ...)
|
||||
ASM_ROOT = ROOT / "asm" / "nonmatchings" # per-segment subdirs (boot/, 800/, ...)
|
||||
MD = ROOT / "docs" / "difficulty.md"
|
||||
CSV = ROOT / ".run" / "difficulty.csv"
|
||||
|
||||
def find_s(name):
|
||||
"""Locate <name>.s in any asm/nonmatchings/<seg>/ subdir."""
|
||||
for p in sorted(ASM_ROOT.glob(f"*/{name}.s")):
|
||||
return p
|
||||
return None
|
||||
|
||||
INSTR = re.compile(r'^\s*/\*\s*[0-9A-Fa-f]+\s+([0-9A-Fa-f]+)\s+[0-9A-Fa-f]+\s*\*/\s+([a-z][a-z0-9.]*)')
|
||||
BRANCH = re.compile(r'^b(eq|ne|gez|gtz|lez|ltz|nez|eqz|c1t|c1f|gezal|ltzal)?z?$')
|
||||
|
||||
@@ -24,22 +30,24 @@ def is_data_blob(txt):
|
||||
|
||||
def unmatched_stubs():
|
||||
"""INCLUDE_ASM names that are real functions (exclude data-blobs) and not inside NON_MATCHING."""
|
||||
lines = SRC.read_text().split('\n'); n = len(lines); i = 0; out = []
|
||||
while i < n:
|
||||
s = lines[i].strip()
|
||||
if s.startswith('#ifdef NON_MATCHING'):
|
||||
while i < n and not lines[i].strip().startswith('#endif'):
|
||||
i += 1
|
||||
i += 1; continue
|
||||
m = re.match(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', s)
|
||||
if m:
|
||||
out.append(m.group(1))
|
||||
i += 1
|
||||
out = []
|
||||
for src in SRCS:
|
||||
lines = src.read_text().split('\n'); n = len(lines); i = 0
|
||||
while i < n:
|
||||
s = lines[i].strip()
|
||||
if s.startswith('#ifdef NON_MATCHING'):
|
||||
while i < n and not lines[i].strip().startswith('#endif'):
|
||||
i += 1
|
||||
i += 1; continue
|
||||
m = re.match(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', s)
|
||||
if m:
|
||||
out.append(m.group(1))
|
||||
i += 1
|
||||
return out
|
||||
|
||||
def analyze(name):
|
||||
p = ASM / f"{name}.s"
|
||||
if not p.exists(): return None
|
||||
p = find_s(name)
|
||||
if p is None: return None
|
||||
txt = p.read_text()
|
||||
if is_data_blob(txt): return None # not a function
|
||||
nins = branches = ncalls = 0; last_vaddr = None; jtbl = False
|
||||
|
||||
+53
-35
@@ -13,12 +13,18 @@ Usage:
|
||||
import re, sys, hashlib, pathlib
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parent.parent
|
||||
SRC = ROOT / "src" / "800.c"
|
||||
ASM = ROOT / "asm" / "nonmatchings" / "800"
|
||||
SRCS = sorted((ROOT / "src").glob("*.c")) # every c-segment (src/boot.c, src/800.c, ...)
|
||||
ASM_ROOT = ROOT / "asm" / "nonmatchings" # per-segment subdirs (boot/, 800/, ...)
|
||||
OUT = ROOT / "docs" / "progress.md"
|
||||
BUILD = ROOT / "build" / "us" / "SLUS_007.26"
|
||||
CHECK = ROOT / "config" / "check.us.sha"
|
||||
|
||||
def find_s(name):
|
||||
"""Locate <name>.s in any asm/nonmatchings/<seg>/ subdir (segments: boot, 800, ...)."""
|
||||
for p in sorted(ASM_ROOT.glob(f"*/{name}.s")):
|
||||
return p
|
||||
return None
|
||||
|
||||
INSTR = re.compile(r'^\s*/\*\s*[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s*\*/\s+[a-z]')
|
||||
|
||||
def strip_comments(s):
|
||||
@@ -27,16 +33,16 @@ def strip_comments(s):
|
||||
|
||||
def is_data_blob(name):
|
||||
"""A .s with a code label (glabel/jlabel) is a function; data-only (dlabel, no code) is a blob."""
|
||||
p = ASM / f"{name}.s"
|
||||
if not p.exists():
|
||||
p = find_s(name)
|
||||
if p is None:
|
||||
return False
|
||||
txt = p.read_text()
|
||||
return ('glabel' not in txt and 'jlabel' not in txt and 'dlabel' in txt)
|
||||
|
||||
def asm_is_trivial(name):
|
||||
"""True iff the function's asm is exactly {jr, nop} (the empty-no-op shape splat emits void{} for)."""
|
||||
p = ASM / f"{name}.s"
|
||||
if not p.exists():
|
||||
p = find_s(name)
|
||||
if p is None:
|
||||
return None
|
||||
mnem = []
|
||||
for ln in p.read_text().splitlines():
|
||||
@@ -48,37 +54,49 @@ def asm_is_trivial(name):
|
||||
SIG = re.compile(r'^\s*[A-Za-z_][\w \t\*]*\b([A-Za-z_]\w*)\s*\(')
|
||||
|
||||
def classify():
|
||||
lines = SRC.read_text().split('\n')
|
||||
n = len(lines); i = 0
|
||||
real, empty, nonmatching, stubs, blobs = [], [], [], [], []
|
||||
while i < n:
|
||||
s = lines[i].strip()
|
||||
if s.startswith('#ifdef NON_MATCHING'):
|
||||
blk = []
|
||||
while i < n and not lines[i].strip().startswith('#endif'):
|
||||
blk.append(lines[i]); i += 1
|
||||
i += 1
|
||||
m = re.search(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', '\n'.join(blk))
|
||||
if m: nonmatching.append(m.group(1))
|
||||
continue
|
||||
m = re.match(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', s)
|
||||
if m:
|
||||
(blobs if is_data_blob(m.group(1)) else stubs).append(m.group(1)); i += 1; continue
|
||||
if s.startswith('INCLUDE_RODATA'):
|
||||
i += 1; continue
|
||||
fm = SIG.match(lines[i])
|
||||
if fm and '(' in lines[i]:
|
||||
start = i; depth = 0; opened = False
|
||||
while i < n:
|
||||
c = strip_comments(lines[i]); depth += c.count('{') - c.count('}')
|
||||
if '{' in c: opened = True
|
||||
for src in SRCS:
|
||||
lines = src.read_text().split('\n')
|
||||
n = len(lines); i = 0
|
||||
while i < n:
|
||||
s = lines[i].strip()
|
||||
if s.startswith('#ifdef NON_MATCHING'):
|
||||
blk = []
|
||||
while i < n and not lines[i].strip().startswith('#endif'):
|
||||
blk.append(lines[i]); i += 1
|
||||
i += 1
|
||||
if opened and depth <= 0: break
|
||||
body = '\n'.join(lines[start:i])
|
||||
a, b = body.index('{'), body.rindex('}')
|
||||
(real if strip_comments(body[a+1:b]).strip() else empty).append(fm.group(1))
|
||||
continue
|
||||
i += 1
|
||||
m = re.search(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', '\n'.join(blk))
|
||||
if m: nonmatching.append(m.group(1))
|
||||
continue
|
||||
m = re.match(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', s)
|
||||
if m:
|
||||
(blobs if is_data_blob(m.group(1)) else stubs).append(m.group(1)); i += 1; continue
|
||||
if s.startswith('INCLUDE_RODATA'):
|
||||
i += 1; continue
|
||||
fm = SIG.match(lines[i])
|
||||
if fm and '(' in lines[i]:
|
||||
# Definition ({ ... }) vs forward declaration (ends ;)? Scan to the first { or ;.
|
||||
# extern/prototype lines (e.g. `extern s32 CdQueueBusy(void);`) are NOT functions.
|
||||
j = i; kind = None
|
||||
while j < n:
|
||||
c = strip_comments(lines[j])
|
||||
br = c.find('{'); sm = c.find(';')
|
||||
if br != -1 and (sm == -1 or br < sm): kind = 'def'; break
|
||||
if sm != -1: kind = 'decl'; break
|
||||
j += 1
|
||||
if kind != 'def':
|
||||
i = j + 1; continue # skip the declaration
|
||||
start = i; depth = 0; opened = False
|
||||
while i < n:
|
||||
c = strip_comments(lines[i]); depth += c.count('{') - c.count('}')
|
||||
if '{' in c: opened = True
|
||||
i += 1
|
||||
if opened and depth <= 0: break
|
||||
body = '\n'.join(lines[start:i])
|
||||
a, b = body.index('{'), body.rindex('}')
|
||||
(real if strip_comments(body[a+1:b]).strip() else empty).append(fm.group(1))
|
||||
continue
|
||||
i += 1
|
||||
return real, empty, nonmatching, stubs, blobs
|
||||
|
||||
def main():
|
||||
|
||||
Reference in New Issue
Block a user