From 49db81d8ae4e3f7806840c2551900dcf9fa55c8b Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Mon, 15 Jun 2026 13:44:32 -0600 Subject: [PATCH] =?UTF-8?q?feat(phase-7):=20Gen1=20close-out=20=E2=80=94?= =?UTF-8?q?=20matching=20loop=20industrialized,=20PsyQ=20libs=20linked,=20?= =?UTF-8?q?LZSS=20matched=20(v1.7.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - reports (Task 3): tools/{progress,difficulty,dup_report}.py + make report/sig-refresh (deterministic digests); harvest to 43 REAL matches incl. LzssDecodeSector - per-module -O0 split (Task 5.5): src/boot.c + Makefile per-file CC1FLAGS (cookbook §6) - PsyQ SDK linking (sessions C-G): split/build/identify/link/integrate pipeline (tools/psyq_*.py, make_libgs.sh, ld_interleave.py, split_src_region.py) — libcd (18 obj) + FULL libgs (31 obj / 6 blocks) linked BYTE-IDENTICAL from real PsyQ 4.0 libs; byte- identical with or without the SDK objects (stub fallback); cookbook §7/§8/§9.1-9.5 - LZSS MATCHED byte-for-byte (session F): §5a cross-jump barrier + §10 regalloc/schedule idioms, ground-truthed against pinned gcc-2.7.2 source (the Gen1 LZSS gate) - file-loader cluster: 6 matched + 5 NON_MATCHING-drafted (CdReadStateMachine, CdReadSectorReadyCB, StreamLoadStateMachine new this session, valid under -DNON_MATCHING); SaveLoadRoutine DEFERRED to Q#5 (save/memcard, documented stub) - README Gen1-complete; worklog preserved -> phase-ends/logs/Phase7.md - rules R17 (web-research compiler quirks), R18 (plain-English session summaries), R19 (preserve phase worklog out of load order) - MILESTONE: Gen1 EXIT confirmed — byte-identical build, 43 matches incl LZSS, 7 zero- regression sessions, loader cluster matched/drafted, overlay map emulator-proven - bumps project version 1.6.0 -> 1.7.0 --- CLAUDE.md | 2 +- Makefile | 23 +- README.md | 9 +- config/splat.us.exe.yaml | 25 +- docs/difficulty.md | 20 +- docs/matching-cookbook.md | 28 +- docs/progress.md | 18 +- phase-ends/PhaseEnd_Phase7.md | 151 ++++ .../{CURRENT_PHASE.md => logs/Phase7.md} | 22 +- src/800.c | 688 ++++++++++++++++++ src/800b.c | 136 ---- src/gsgap1.c | 3 + src/gsgap2.c | 3 + src/gsgap3.c | 13 + src/gsgap4.c | 3 + src/gsgap5.c | 33 + src/libgs1.c | 17 + src/libgs2.c | 15 + src/libgs3.c | 3 + src/libgs4.c | 47 ++ src/libgs5.c | 9 + tools/make_libgs.sh | 45 ++ tools/make_libgs_block6.sh | 30 - tools/psyq_integrate.py | 18 +- 24 files changed, 1147 insertions(+), 214 deletions(-) create mode 100644 phase-ends/PhaseEnd_Phase7.md rename phase-ends/{CURRENT_PHASE.md => logs/Phase7.md} (86%) create mode 100644 src/gsgap1.c create mode 100644 src/gsgap2.c create mode 100644 src/gsgap3.c create mode 100644 src/gsgap4.c create mode 100644 src/gsgap5.c create mode 100644 src/libgs1.c create mode 100644 src/libgs2.c create mode 100644 src/libgs3.c create mode 100644 src/libgs4.c create mode 100644 src/libgs5.c create mode 100644 tools/make_libgs.sh delete mode 100644 tools/make_libgs_block6.sh diff --git a/CLAUDE.md b/CLAUDE.md index f0861b816..e7890d664 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,7 +7,7 @@ Matching decompilation of **Brave Fencer Musashi** (PS1, SLUS-00726 USA). Goal: Before doing any work, read these files in this order: 1. Read `PROJECT_CONTEXT.md` in full (the permanent constitution: rules, protocols, roadmap). -2. Read every `phase-ends/PhaseEnd_*.md` in numeric order (the build history). +2. Read every `phase-ends/PhaseEnd_*.md` in numeric order (the build history). **Do NOT read `phase-ends/logs/`** — those are on-demand worklog archives (R19), deliberately out of the load order; consult one only when researching a past mechanism (see the `phase-worklogs-reference-only` memory). 3. Read `phase-ends/CURRENT_PHASE.md` if it exists (the in-phase state). 4. Then follow the **Session Start Protocol** in `PROJECT_CONTEXT.md` — state phase, completed tasks, the single next task, and acknowledge the rules before working. 5. **For matching phases (Phase 6+):** before any function matching, also read `docs/matching-cookbook.md` (reusable compiler idioms asm↔C + techniques) and `docs/SETUP.md` §5.4 (the pinned triple). The matching knowledge base is a **compounding flywheel** — consult it before each match, and after each match (especially a hard-won near-miss) feed the generalizable lesson back into BOTH the cookbook AND the tooling (`tools/permuter/` PERM_ recipes/weights, the m2c `--context`) so future similar functions one-shot. diff --git a/Makefile b/Makefile index 5ff919c4e..e55c560dc 100644 --- a/Makefile +++ b/Makefile @@ -166,13 +166,14 @@ LIBCD_ELF := .run/obj40/libcd LIBCD_OBJDIR := build/psyq/libcd LIBCD_SYMS := build/psyq/libcd_externals.ld -# libgs block 6 (Phase 7 Task #9): 16 contiguous libgs objects (GS_110/119/120/121/122/123/127/131/ -# 133/134/135/137, PRESET, PRESET3, OBJT, OBJT3) linked in place of the single libgs6 stub. Same -# conditional/idempotent model as libcd. The curated block-6 object dir is SDK-derived (gitignored), -# regenerated by tools/make_libgs_block6.sh (needs the LIBGS ELF from psyq_build_libs.sh LIBGS). -LIBGS6_ELF := .run/obj40/libgs_block6 -LIBGS6_OBJDIR := build/psyq/libgs6 -LIBGS6_SYMS := build/psyq/libgs6_externals.ld +# libgs (Phase 7 Task #9, FULL integration): 31 libgs objects in 6 contiguous blocks linked in place +# of the libgs1..libgs6 block stubs (the 5 non-libgs gaps stay gsgapN asm stubs). Same conditional/ +# idempotent model as libcd. The curated object dir is SDK-derived (gitignored), regenerated by +# tools/make_libgs.sh (needs the LIBGS ELF from psyq_build_libs.sh LIBGS). GS_106 (block 4) anchors +# uniquely only within the libgs window, so the integrate call passes 0x80051804 0x80057928. +LIBGS_ELF := .run/obj40/libgs_used +LIBGS_OBJDIR := build/psyq/libgs +LIBGS_SYMS := build/psyq/libgs_externals.ld # Assembler flags (docs/SETUP.md §6.2). -G0 is confirmed by the disassembly # (ledger #8: zero $gp-relative addressing). -no-pad-sections keeps section ends @@ -249,12 +250,12 @@ $(OUT): $(OBJS) $(LD_SCRIPT) else echo " (no $(LIBCD_ELF) — libcd region stays asm stubs; run tools/psyq_build_libs.sh LIBCD)" fi - if [ -d "$(LIBGS6_ELF)" ]; then - $(PYTHON) tools/psyq_integrate.py $(LIBGS6_ELF) $(LD_SCRIPT) $(LIBGS6_OBJDIR) $(LIBGS6_SYMS) libgs6 + if [ -d "$(LIBGS_ELF)" ]; then + $(PYTHON) tools/psyq_integrate.py $(LIBGS_ELF) $(LD_SCRIPT) $(LIBGS_OBJDIR) $(LIBGS_SYMS) libgs1,libgs2,libgs3,libgs4,libgs5,libgs6 0x80051804 0x80057928 else - echo " (no $(LIBGS6_ELF) — libgs6 region stays asm stubs; run tools/make_libgs_block6.sh)" + echo " (no $(LIBGS_ELF) — libgs region stays asm stubs; run tools/make_libgs.sh)" fi - SYMS=""; [ -f "$(LIBCD_SYMS)" ] && SYMS="-T $(LIBCD_SYMS)"; [ -f "$(LIBGS6_SYMS)" ] && SYMS="$$SYMS -T $(LIBGS6_SYMS)" + SYMS=""; [ -f "$(LIBCD_SYMS)" ] && SYMS="-T $(LIBCD_SYMS)"; [ -f "$(LIBGS_SYMS)" ] && SYMS="$$SYMS -T $(LIBGS_SYMS)" echo " LD $(ELF)" $(LD) -T $(LD_SCRIPT) -T $(UNDEF_SYMS) -T $(UNDEF_FUNCS) $$SYMS --no-check-sections -Map $(MAPFILE) -o $(ELF) echo " OBJCOPY $@" diff --git a/README.md b/README.md index 48980c26d..6980a4d24 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,14 @@ This repository contains **no game assets, no disassembly output, and no ROM-der ## Project status -Early foundation. Current phase and progress live in `phase-ends/` (newest `PhaseEnd_*.md` = current state). Project methodology, rules, and the full roadmap are in `PROJECT_CONTEXT.md`. Environment setup is in `docs/SETUP.md`. +**Gen1 (foundation) complete — the matching pipeline is proven end-to-end.** `make extract && make build && make check` rebuilds `SLUS_007.26` **byte-for-byte identical** (SHA1 `143dbb89…`) from C + assembly, reproducibly across many sessions. + +- **Compiler pinned by evidence:** `gcc-2.7.2-psx -O2 -G0 -mips1 -mcpu=3000` + `maspsx --aspsx-version=2.56 --expand-div`. +- **43 functions hand-matched** to byte-identical machine code — including the LZSS streaming decompressor — with a decomp-permuter + matching-cookbook "flywheel" to accelerate the next. +- **~50 PsyQ SDK objects (libcd + libgs) linked byte-identical** straight from the real PsyQ 4.0 libraries instead of re-decompiling them. +- **File-loader / overlay system reverse-engineered**, with the resident engine blob + location overlays' load addresses **proven byte-identical against a live PCSX-Redux RAM dump**. + +Most of the EXE is still `INCLUDE_ASM` stubs (correct bytes, not yet C), and the bulk of the game lives in compressed overlays inside the `.CD` archives — **Gen2** (overlays & engine at scale) is next. Current phase and detailed progress live in `phase-ends/` (newest `PhaseEnd_*.md` = current state); methodology, rules, and the full roadmap are in `PROJECT_CONTEXT.md`; environment setup in `docs/SETUP.md`. This project is developed primarily by Claude Code driving Ghidra through an MCP server; see `CLAUDE.md`. diff --git a/config/splat.us.exe.yaml b/config/splat.us.exe.yaml index 56ede12a1..51c320e95 100644 --- a/config/splat.us.exe.yaml +++ b/config/splat.us.exe.yaml @@ -80,13 +80,26 @@ segments: - [0x33888, c, libcd1] # libcd block 1 -> src/libcd1.c (vram 0x80043088-0x80046980, 11 objs) - [0x37180, c, gap] # non-libcd gap -> src/gap.c (vram 0x80046980-0x800469CC, stub) - [0x371CC, c, libcd2] # libcd block 2 -> src/libcd2.c (vram 0x800469CC-0x8004787C, 7 objs) - # PsyQ libgs block 6 (Phase 7 Task #9): 16 contiguous libgs objects (GS_110/119/120/121/122/ - # 123/127/131/133/134/135/137, PRESET, PRESET3, OBJT, OBJT3) linked in place of stubs — same - # mechanism as libcd (psyq_integrate swaps build/src/libgs6.o -> the real objects + NOLOAD data). - # The other 5 libgs blocks + the GS_001 gap stay stubs (remaining libgs integration is future). - - [0x3807C, c, 800b] # -O2 game code (pre-libgs6) -> src/800b.c (vram 0x8004787C-0x80053AF8) + # PsyQ libgs (Phase 7 Task #9, FULL integration): 31 libgs objects in 6 contiguous blocks + # across vram 0x80051804-0x80057928, separated by 5 non-libgs gaps. psyq_integrate swaps each + # block stub's build/src/libgsN.o(.text) for the real objects + NOLOAD data (same mechanism as + # libcd). The 5 gaps stay asm stubs (gsgapN): 80 / 48 / 1536[GS_001] / 48 / 304 B. GS_001 is + # EXCLUDED (scattered-.bss hard case, cookbook §9.1) and stays a stub (gsgap3). GS_106 (block 4) + # is an 8-ins object that only anchors uniquely within the libgs window, so make extract passes + # 0x80051804 0x80057928 to psyq_integrate. Object lists + disambiguation: tools/make_libgs.sh. + - [0x3807C, c, 800b] # -O2 game code (pre-libgs) -> src/800b.c (vram 0x8004787C-0x80051804) + - [0x42004, c, libgs1] # libgs block 1: 2D_COM0 -> src/libgs1.c (vram 0x80051804-0x80051D78, 1 obj) + - [0x42578, c, gsgap1] # gap 80 B (non-libgs stub) -> src/gsgap1.c (vram 0x80051D78-0x80051DC8) + - [0x425C8, c, libgs2] # libgs block 2: 2D_LIN0/COM1/SP0 -> src/libgs2.c (vram 0x80051DC8-0x80052430, 3 objs) + - [0x42C30, c, gsgap2] # gap 48 B (non-libgs stub) -> src/gsgap2.c (vram 0x80052430-0x80052460) + - [0x42C60, c, libgs3] # libgs block 3: 2D_SP1 -> src/libgs3.c (vram 0x80052460-0x800525DC, 1 obj) + - [0x42DDC, c, gsgap3] # gap 1536 B: GS_001 excluded -> src/gsgap3.c (vram 0x800525DC-0x80052BDC) + - [0x433DC, c, libgs4] # libgs block 4: GS_002/003/MATRIX/103/104/105/106/107 -> src/libgs4.c (vram 0x80052BDC-0x800538BC, 8 objs) + - [0x440BC, c, gsgap4] # gap 48 B (non-libgs stub) -> src/gsgap4.c (vram 0x800538BC-0x800538EC) + - [0x440EC, c, libgs5] # libgs block 5: GS_108/109 -> src/libgs5.c (vram 0x800538EC-0x800539C8, 2 objs) + - [0x441C8, c, gsgap5] # gap 304 B (non-libgs stub) -> src/gsgap5.c (vram 0x800539C8-0x80053AF8) - [0x442F8, c, libgs6] # libgs block 6 (16 objs) -> src/libgs6.c (vram 0x80053AF8-0x80057928) - - [0x48128, c, 800b2] # -O2 game code (post-libgs6) -> src/800b2.c (vram 0x80057928-0x800629DC) + - [0x48128, c, 800b2] # -O2 game code (post-libgs) -> src/800b2.c (vram 0x80057928-0x800629DC) # Phase 7 (Task 2' / LZSS) — SURGICAL rodata carve for the LZSS switch only. # The rodata island (0x80072A38-0x80074750) interleaves game jtbls, game data # (loadDestPtrTable @0x80072C70 etc.) and library jtbls (PRESET/OBJT/PRNT @0x800737CC+), diff --git a/docs/difficulty.md b/docs/difficulty.md index de92f8e4c..655666619 100644 --- a/docs/difficulty.md +++ b/docs/difficulty.md @@ -1,9 +1,9 @@ # Unmatched difficulty inventory (generated by tools/difficulty.py — harvest queue) -unmatched functions : 1962 -trivial (<=5 ins) : 291 -non-jtbl leaves : 1093 (best harvest targets) -jump-table funcs : 84 (deferred — need the rodata-island workflow, Task 2') +unmatched functions : 2001 +trivial (<=5 ins) : 297 +non-jtbl leaves : 1123 (best harvest targets) +jump-table funcs : 80 (deferred — need the rodata-island workflow, Task 2') ## Easiest 120 unmatched (score asc) — the work queue | score | name | nins | br | calls | jtbl | leaf | @@ -57,6 +57,8 @@ jump-table funcs : 84 (deferred — need the rodata-island workflow, Task | 3 | SetData32 | 3 | 0 | 0 | - | Y | | 3 | SetIR0 | 3 | 0 | 0 | - | Y | | 3 | UT_KEYV_OBJ_174 | 3 | 0 | 0 | - | Y | +| 3 | func_8005E188 | 3 | 0 | 0 | - | Y | +| 3 | func_80062388 | 3 | 0 | 0 | - | Y | | 4 | CD_set_test_parmnum | 4 | 0 | 0 | - | Y | | 4 | C_011_OBJ_960 | 4 | 0 | 0 | - | Y | | 4 | CdLastCom | 4 | 0 | 0 | - | Y | @@ -113,6 +115,10 @@ jump-table funcs : 84 (deferred — need the rodata-island workflow, Task | 4 | func_8005C4CC | 4 | 0 | 0 | - | Y | | 4 | func_8005CF08 | 4 | 0 | 0 | - | Y | | 4 | func_8005CF18 | 4 | 0 | 0 | - | Y | +| 4 | func_8005E194 | 4 | 0 | 0 | - | Y | +| 4 | func_8005F384 | 4 | 0 | 0 | - | Y | +| 4 | func_80062394 | 4 | 0 | 0 | - | Y | +| 4 | func_80062988 | 4 | 0 | 0 | - | Y | | 4 | gfx2D_COM0_OBJ_30 | 4 | 0 | 0 | - | Y | | 5 | C_004_OBJ_DC | 5 | 0 | 0 | - | Y | | 5 | DsReadyCallback | 5 | 0 | 0 | - | Y | @@ -122,9 +128,3 @@ jump-table funcs : 84 (deferred — need the rodata-island workflow, Task | 5 | INTR_OBJ_6D0 | 5 | 0 | 0 | - | Y | | 5 | ISO9660_OBJ_29C | 3 | 0 | 1 | - | - | | 5 | LIBMCRD_OBJ_5C0 | 5 | 0 | 0 | - | Y | -| 5 | LIBMCRD_OBJ_840 | 3 | 0 | 1 | - | - | -| 5 | LIBMCRD_OBJ_A50 | 3 | 0 | 1 | - | - | -| 5 | LIBMCRD_OBJ_C90 | 3 | 0 | 1 | - | - | -| 5 | LIBMCRD_OBJ_F4C | 3 | 0 | 1 | - | - | -| 5 | OBJT_OBJ_108 | 2 | 1 | 0 | - | Y | -| 5 | OBJT_OBJ_110 | 2 | 1 | 0 | - | Y | diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md index 2eedfa74d..6135c3c8f 100644 --- a/docs/matching-cookbook.md +++ b/docs/matching-cookbook.md @@ -409,7 +409,33 @@ single-library path never hit. All fixed in `tools/psyq_integrate.py`; reuse for `psyq_identify ` over ONE block's range reports the right objects PLUS byte- identical-`.text` aliases (GS_131≡RVWUNIT, PRESET2≡PRESET3, OBJT2≡OBJT3); keep the one that also matches `.data/.rdata` (`psyq_link_region --verify` confirms per-object). Hardcode the disambiguated - object list in a committed regen script (`tools/make_libgs_block6.sh`) — SDK-derived dir, gitignored. + object list in a committed regen script (`tools/make_libgs.sh`) — SDK-derived dir, gitignored. + +### §9.5 Integrating a WHOLE multi-block library in one call (full libgs — Phase 7 session G) +Block 6 alone (§9.4) proved the pattern; the full library is then ONE `psyq_integrate` call over all of +its used objects — no separate integration per block. integrate auto-splits the elf dir's vram-ordered +objects into contiguous runs (`contiguous_blocks`) and maps the i-th run to the i-th stub: pass one +block stub per contiguous run, and a plain asm stub per non-library gap (gaps keep their own subseg). +- **Derive the block/gap structure empirically (G1), don't trust the notes.** `psyq_identify ` + prints each object's vram; a new block starts wherever the next object's vram ≠ the previous object's + end (`vram + nins*4`). libgs = 6 blocks, gaps 80/48/1536/48/304 B (the 1536 is the excluded GS_001). + Resegment the splat code subseg into `[game-pre][block1][gap1]…[blockN][gapN]…` (vram→file = + −0x8000F800); block subsegs become the `libgsN` integrate stubs, gap subsegs stay `gsgapN` asm stubs + (incl. the GS_001 gap). One `split_src_region.py trim` drops the old single-subseg's stubs from the + pre-file — but verify FIRST that **no matched C lives in [lo,hi)** (trim drops that range + unconditionally; here all matched fns were ≤0x8002Axxx, far below the 0x80051804 libgs base). +- **Short objects need the placement WINDOW (new `psyq_integrate ` arg).** An object + whose `.text` is too short to anchor uniquely over the whole EXE — libgs **GS_106** (8 ins; its pattern + recurs in game code) — is `ambiguous` in the default 0x80010000..0x800629DC scan, so it drops from the + placement map, its block splits, and the block↔stub count breaks. Pass the library's text window + (wired in the Makefile integrate call: `… libgs1,…,libgs6 0x80051804 0x80057928`); the object anchors + uniquely there. `tools/make_libgs.sh --verify` (psyq_link_region over the same window) is the preflight. +- **Result + checks:** 31 libgs objects / 6 blocks linked byte-identical in one integrate (≈49 named SDK + functions — count OBJECTS, not stubs: splat over-segments library code into ~5× more INCLUDE_ASM stubs + than real functions). The finer resegmentation (10 new subsegs) was **split-deterministic** across two + clean extracts (no new §9.4 boundary re-detection — the GsMulCoord2/3 declarations + the 53198 data + carve from §9.4 already cover it) and **byte-identical WITH or WITHOUT** the SDK objects (stub + fallback, fresh-clone-safe). The unified call cleanly supersedes the §9.4 block-6-only integration. --- diff --git a/docs/progress.md b/docs/progress.md index 2a35387b3..aa6158d2b 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -1,17 +1,17 @@ # BFM matching progress (generated by tools/progress.py — authoritative) -REAL substantive matches : 42 <- the Gen1-exit >=25 bar counts THIS -NON_MATCHING (near-miss) : 4 +REAL substantive matches : 43 <- the Gen1-exit >=25 bar counts THIS +NON_MATCHING (near-miss) : 7 splat-auto empty no-ops : 42 -INCLUDE_ASM stubs : 1962 -data blobs (excluded) : 4 +INCLUDE_ASM stubs : 2001 +data blobs (excluded) : 2 ---------------------------------------- -matchable functions : 2050 -REAL / matchable : 42 / 2050 = 2.05% -incl. empties : 84 / 2050 = 4.10% +matchable functions : 2093 +REAL / matchable : 43 / 2093 = 2.05% +incl. empties : 85 / 2093 = 4.06% -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 +REAL matches: CdQueueBusy CdReadRequest DebugMenuHandler GameModeDispatch LoaderResetReadState LzssDecodeSector 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: CdReadSectorReadyCB CdReadStateMachine LoaderInitFileTable ResourceLoadStateMachine StreamLoadStateMachine func_80015A74 func_80016714 build SHA1: 143dbb89f34491258bbc27810d0a12ec8b43a8dd (byte-identical) diff --git a/phase-ends/PhaseEnd_Phase7.md b/phase-ends/PhaseEnd_Phase7.md new file mode 100644 index 000000000..da1c01d04 --- /dev/null +++ b/phase-ends/PhaseEnd_Phase7.md @@ -0,0 +1,151 @@ +# PhaseEnd — Phase 7: Matching loop at scale + Gen1 close-out +**Date:** 2026-06-15 · **Project Version:** 1.7.0 · **Phase Status:** Complete · **🎓 Gen1 EXIT (confirmed by Drew)** + +> Phase 7 ran across **7 sessions (A–G)**. This file is the synthesis; the granular session-by-session +> trail (dead-ends, exact addresses, the libgs/rodata/PsyQ-linking working notes) is preserved on-demand +> at **`phase-ends/logs/Phase7.md`** (NOT auto-loaded — see R19). Consult it only when researching a past +> mechanism (it is the richest source for Gen2's overlay/library work). + +## Build Log + +**Files created/changed and complete — do not recreate:** + +*Matching-loop industrialization (Task 3):* +- `tools/progress.py` / `tools/difficulty.py` / `tools/dup_report.py` + `make report` / `make sig-refresh` — + deterministic, committable digests → `docs/{progress,difficulty,duplicates}.md`. progress.py distinguishes + REAL (hand-matched score-0 C) from NON_MATCHING (guarded near-miss drafts); globs all `src/*.c` + + `asm/nonmatchings/*`. Authoritative baseline at Gen1 exit: **43 REAL / 7 NON_MATCHING / 2050 stubs**. + +*Per-module flag mechanism (Task 5.5, cookbook §6):* +- `src/boot.c` (split text subseg `[0x800,c,boot]`) + `Makefile` target-specific `build/src/boot.o: CC1FLAGS + := …-O0…`. The boot/main/game-mode module (vram 0x80010000–0x800123F0) is **-O0**, the rest -O2 (per-module + opt mixing is real). Detect via the `21F0A003` frame-pointer signature. Reusable for any flag-divergent module. + +*PsyQ SDK integration — the Phase-7 strategic breakthrough (sessions C–G, cookbook §7/§8/§9.1–§9.5):* +- `include/psyq/libcd.h` — CdlLOC/CdlFILE + CdSearchFile/CdPosToInt/CdIntToPos (offsets from Ghidra `.gdt`, G1). +- `tools/psyq_lib_split.py` (LIB\x01 → .OBJ), `tools/psyq_build_libs.sh` (.OBJ → ELF via psyq-obj-parser → ar), + `tools/psyq_identify.py` (relocation-masked placement; optional text-window arg for short objects like + libgs GS_106), `tools/psyq_link.py` + `tools/psyq_link_lib.py` (per-object byte-link engine; weaken + mislabelled `.bss` commons), `tools/psyq_link_region.py` (NOLOAD region link — no data carving), + `tools/psyq_integrate.py` (build wiring: splat resegment + .ld swap + external resolution; multi-library-safe + per-lib NOLOAD namespacing + sibling externals + optional window), `tools/split_src_region.py` (H5-safe src + trim), `tools/ld_interleave.py` (the `.data→.rodata→.data` sandwich for the rodata island), + `tools/make_libgs.sh` (curated 31-object libgs regen dir). **Result: libcd (18 objects) + libgs (31 objects + / 6 blocks, ≈49 named SDK funcs) linked BYTE-IDENTICAL from the real PsyQ 4.0 USA libraries** — byte-identical + WITH or WITHOUT the SDK objects (fresh-clone stub fallback). + +*Config / build (Task 2′ LZSS + full libgs):* +- `config/splat.us.exe.yaml` — text subseg flipped asm→c (Phase 6); resegmented into boot + 800 + libcd1/gap/ + libcd2 + 800b + **libgs1–5 / gsgap1–5** + libgs6 + 800b2, plus the surgical LZSS rodata carve + (`[0x63238,.rodata,800]` migrates `jtbl_80072A38`) + the front-data carve `[0x53198,data]`. +- `config/symbols.us.txt` — hand-curated build-valid symbols (R15); +PsyQ names (CdSearchFile/CdPosToInt/ + CdIntToPos/VSync/GsMulCoord2/3 …), +the `func_80047CAC` reproducibility declaration. +- `Makefile` — c-rule cpp→cc1→maspsx→as; libcd + unified libgs `psyq_integrate` calls (conditional on the SDK + ELF dirs); `report`/`sig-refresh`. +- `src/800.c` — **43 REAL matches** (incl. `LzssDecodeSector`) + **7 NON_MATCHING drafts** + (LoaderInitFileTable, ResourceLoadStateMachine, CdReadStateMachine, CdReadSectorReadyCB, + StreamLoadStateMachine, func_80015A74, func_80016714) + the **SaveLoadRoutine deferral** (structured Q#5 + comment). The 3 CD-loader drafts are valid C under `-DNON_MATCHING`. `src/800b.c`/`800b2.c` + the libcd/ + libgs/gsgap stub `.c` files (committed records). + +*Knowledge base / docs:* +- `docs/matching-cookbook.md` — §6 (-O0 module) · §7 (PsyQ types/symbols) · §8 (rodata island) · §9.1–§9.5 + (library linking, single→region→build→multi-library→whole-multi-block) · §10 (LZSS regalloc/schedule tail) · + §3a/§5a (web-research tier + cross-jump barrier). +- `docs/SETUP.md` (§5.4 pinned triple, §5.5 per-module mixing, §6.6 matching loop), `docs/memory-map.md`, + `README.md` (Gen1-complete status). +- `requirements-python.txt` — venv freeze (pycparser<3.0 pin). +- `phase-ends/logs/Phase7.md` — **the preserved Phase-7 worklog** (R19; `git mv` of CURRENT_PHASE.md). +- `phase-ends/PhaseEnd_Phase7.md` — this file. + +**Tools/packages installed:** venv pip (Phase 6 close): asm-differ runtime (watchdog, Levenshtein/RapidFuzz, +cxxfilt) + decomp-permuter (pycparser 2.23, toml, PyNaCl). SDK-derived (gitignored): `tools/psyq/` (psyq-obj-parser, +PsyQ 4.0 USA `.LIB`s from the DTL-S2002 redump). No new apt packages this phase. + +**Verification results (literal):** +- **`make clean && make extract && make build && make check` → `build/us/SLUS_007.26`, SHA1 + `143dbb89f34491258bbc27810d0a12ec8b43a8dd` == original (BYTE-IDENTICAL)** — reproducible across **7 sessions + (A–G)**, zero checksum regressions. +- **43 REAL matches** (asm-differ score 0), incl. **`LzssDecodeSector`** (the Gen1 LZSS gate — closed session F + via the §5a cross-jump barrier + §10 regalloc/schedule idioms, ground-truthed against pinned gcc-2.7.2 source). +- **49 PsyQ SDK objects linked byte-identical** (libcd 18 + libgs 31); split DETERMINISTIC across clean extracts; + byte-identical with OR without the SDK objects. +- **7 NON_MATCHING drafts**, the 3 new CD-loaders valid under `-DNON_MATCHING`. +- **Loader cluster:** 6 matched (CdReadRequest, CdQueueBusy, ResourceGetCdLoc, LoaderResetReadState, + GameModeDispatch, DebugMenuHandler) + 5 NON_MATCHING-drafted + **SaveLoadRoutine deferred** (Q#5 save/memcard). +- Reports auto-generate deterministically; `git status` shows zero ROM-derived/generated bulk staged. + +**Milestone achieved:** Gen1-exit criteria met and **confirmed by Drew** — the EXE rebuilds **byte-for-byte +identical** from source, **≥25 functions matched (43, incl. the LZSS decompressor)**, **zero checksum +regressions across ≥3 sessions (7)**, reports auto-generate, the **file-loader cluster is matched or +NON_MATCHING-drafted**, and the overlay map is emulator-proven (Phase 3). The matching loop is industrialized +and the PsyQ-library-linking machinery is proven — the launchpad for Gen2. + +**Next:** **Gen2 — Overlays & engine at scale.** Do NOT start until a fresh session runs the Gen2 planning +(itself a Tier-1 task). First Gen2 work: splat configs for the resident engine blob + location overlays (the +Phase-3 overlay map + the Phase-7 library-linking/rodata machinery feed directly into this); then the engine +systems, the dedup pipeline, and the public flip (AGPL-3.0, CI, frogress). + +## Deviations +| Item | Plan | Actual | Reason | +|---|---|---|---| +| Phase order | LZSS/rodata first (Phase-6 framing) | **Reordered** (Drew, session start): reports + harvest + non-switch loaders first, THEN the focused LZSS/rodata sub-project | bank lower-risk wins before the structural rodata wall | +| Library code | hand-decompile ~350 PsyQ SDK funcs | **Link the real PsyQ 4.0 objects byte-identical** (libcd+libgs) | they ARE byte-identical to the SDK; linking is faster, provably exact, and fixes the library-half of the rodata `.align 3` problem. Major strategic addition (Drew-approved, session C) | +| LZSS milestone | Phase 6 | closed Phase 7 (session F) | needed the rodata-island + cross-jump-barrier + regalloc tail; carried per Drew | +| Loader cluster | 4 jtbl loaders drafted | **3 drafted; SaveLoadRoutine DEFERRED to Q#5** (Drew, session G) | it is the save/memcard system (different subsystem), a 1139-ins multi-entry blob Ghidra can't cleanly decompile; a forced draft would be worse than an honest stub (P9) | +| Worklog at phase close | absorb + **delete** (P8) | **preserve** via `git mv` → `phase-ends/logs/Phase7.md` (out of load order) | Drew (session G): keep the granular trail as an on-demand archive for Gen2; → R19 | +| Commit cadence | one commit at phase end (R8) | **per-session checkpoint commits** (A–F) + this PhaseEnd | Drew-directed; long phase, crash-safety; consistent across sessions | +| `make report` REAL count | n/a | a nested `#ifndef` inside a NON_MATCHING block mis-flagged a draft as REAL → removed the (unneeded) guard | progress.py guard-tracking limitation; the byte-identical build was never affected | + +## Commit Message +``` +feat(phase-7): Gen1 close-out — matching loop industrialized, PsyQ libs linked, LZSS matched (v1.7.0) + +- reports (Task 3): tools/{progress,difficulty,dup_report}.py + make report/sig-refresh + (deterministic digests); harvest to 43 REAL matches incl. LzssDecodeSector +- per-module -O0 split (Task 5.5): src/boot.c + Makefile per-file CC1FLAGS (cookbook §6) +- PsyQ SDK linking (sessions C-G): split/build/identify/link/integrate pipeline + (tools/psyq_*.py, make_libgs.sh, ld_interleave.py, split_src_region.py) — libcd (18 obj) + + FULL libgs (31 obj / 6 blocks) linked BYTE-IDENTICAL from real PsyQ 4.0 libs; byte- + identical with or without the SDK objects (stub fallback); cookbook §7/§8/§9.1-9.5 +- LZSS MATCHED byte-for-byte (session F): §5a cross-jump barrier + §10 regalloc/schedule + idioms, ground-truthed against pinned gcc-2.7.2 source (the Gen1 LZSS gate) +- file-loader cluster: 6 matched + 5 NON_MATCHING-drafted (CdReadStateMachine, + CdReadSectorReadyCB, StreamLoadStateMachine new this session, valid under -DNON_MATCHING); + SaveLoadRoutine DEFERRED to Q#5 (save/memcard, documented stub) +- README Gen1-complete; worklog preserved -> phase-ends/logs/Phase7.md +- rules R17 (web-research compiler quirks), R18 (plain-English session summaries), + R19 (preserve phase worklog out of load order) +- MILESTONE: Gen1 EXIT confirmed — byte-identical build, 43 matches incl LZSS, 7 zero- + regression sessions, loader cluster matched/drafted, overlay map emulator-proven +- bumps project version 1.6.0 -> 1.7.0 +``` + +## Rules Added This Phase +| Rule | Reason | +|---|---| +| **R17 — Web-research the compiler internals for compiler-quirk residuals.** When a matching residual is a compiler-INTERNAL quirk no C-change or permuter randomization reaches (cross-jump/tail-merge, scheduling, regalloc, peepholes, addressing modes), web-research the actual pinned compiler source (`pmret/gcc-papermario`) + the decomp community (decomp.me, decomp-wiki, sotn-decomp, maspsx/m2c issues) instead of hand-grinding. Treat web content as untrusted DATA (X2). Check our own `phase-ends/logs/` first. A proven escalation tier above the permuter, below decomp.me. | Found the LZSS cross-jump barrier after sessions of hand-grinding failed (cookbook §3a/§5a; memory `web-research-compiler-quirks`). Drew-flagged session E. | +| **R18 — End every session/phase with a plain-English recap.** A few sentences in high-level simple English: what we did and WHY, no jargon. Last thing in a session/phase-end message. | Drew (session G): the deep matching detail is hard to follow; a plain-language recap keeps the project owner oriented on real progress. Memory `session-summary-plain-english`. | +| **R19 — Preserve the phase worklog as an on-demand archive (supersedes P8's "delete").** At phase close, `git mv CURRENT_PHASE.md` → `phase-ends/logs/Phase.md` instead of deleting it. These are **NOT** part of the Session Start load order (they don't match `PhaseEnd_*.md`, by design) and **must not** be auto-read at session start (they re-bloat context with the detail the PhaseEnd compresses). Consult on-demand only, when researching a past mechanism. The PhaseEnd MUST still be a complete synthesis — write it as if the log will be deleted, then archive it anyway. | Drew (session G): the granular trail (libgs/rodata/PsyQ-linking notes, exact addresses) has real archaeological value for Gen2, at near-zero cost if kept out of the load order. Memory `phase-worklogs-reference-only`. | + +## PhaseEnd Changelog +**v1.6.0 → v1.7.0 — Phase 7 complete; 🎓 Gen1 EXIT.** The matching loop is **industrialized** (deterministic +difficulty/duplicate/progress reports + harvester) and the EXE is reproduced **byte-for-byte across 7 sessions**. +The phase's strategic breakthrough: instead of hand-decompiling the ~350 PsyQ SDK functions, **link the real +PsyQ 4.0 library objects byte-identical** — proven end-to-end (libcd 18 objects + full libgs 31 objects/6 blocks), +with a complete, reusable tool pipeline (split → build → identify → per-object/region/whole-library link → +build integration), byte-identical with or without the SDK objects (fresh-clone-safe). The **LZSS decompressor +matched byte-for-byte** (the Gen1 gate) via a new web-research escalation tier (R17) that ground-truthed the +gcc-2.7.2 cross-jump/regalloc/schedule behavior against the pinned compiler source (cookbook §3a/§5a/§10). +**43 functions matched** (incl. LZSS) + **49 SDK objects linked**; the **file-loader cluster** is 6 matched + +5 NON_MATCHING-drafted, with **SaveLoadRoutine deferred to Q#5** (the save/memcard subsystem — a multi-entry +blob best left documented than wrongly guessed). Per-module -O0 mixing handled (boot module). Three rules +(R17 web-research, R18 plain-English recaps, R19 preserve-worklog). **Gen1 (Foundation & EXE) is complete:** +deterministic extraction, the overlay/loader map emulator-proven, a byte-identical EXE from source, the compiler +pinned, and the matching + library-linking loops proven — the foundation Gen2 builds on. + +## 🛑 Stop Here +PhaseEnd written; `CURRENT_PHASE.md` **preserved** as `phase-ends/logs/Phase7.md` (R19, not deleted). **Drew +commits AND pushes** this PhaseEnd + the whole uncommitted Phase-7 change set (R6/R8). **Gen1 is complete and +confirmed** — do NOT start Gen2 here. Start a **fresh session** to plan **Gen2 — Overlays & engine at scale** +(a Tier-1 planning task; needs its own phase plan + your approval gate). Keep this file forever. diff --git a/phase-ends/CURRENT_PHASE.md b/phase-ends/logs/Phase7.md similarity index 86% rename from phase-ends/CURRENT_PHASE.md rename to phase-ends/logs/Phase7.md index 9f0774f9c..f4a59efda 100644 --- a/phase-ends/CURRENT_PHASE.md +++ b/phase-ends/logs/Phase7.md @@ -60,10 +60,19 @@ entry above + the 5 LOAD-BEARING comments in `src/800.c` + cookbook §10. NOLOAD lines globally re-sorted by vram across libraries (kills "dot moved backwards"). - **reproducibility:** `tools/make_libgs_block6.sh` regenerates the curated 16-object dir (disambiguated: GS_131/137 not RVWUNIT/RVWLUNIT, PRESET3/OBJT3 not PRESET2/OBJT2). -- ▶ **NEXT — full libgs integration** (the other 5 blocks: ~37 more SDK funcs; **GS_001 excluded**, scattered - `.bss` commons §9.1). Drew pre-approved ("then the integration"). Same pattern, now de-risked (the splat- - instability + multi-lib fixes are in). Then Task 6 close-out, Task 7 PhaseEnd (PhaseEnd gated on libgs per Drew). -NOTE: Gen1 exit needs ≥3 SESSIONS of green `make check` — **satisfied** (A, B, C, D, E, +F); Tasks 6/7 pending. +- ✅ **DONE (session G) — full libgs integration.** All **31 libgs objects in 6 contiguous blocks** now linked + from real PsyQ SDK objects, byte-identical. Blocks 1-5 (15 objects / 29 named funcs) added to block 6's 16 + (20 named funcs) → **≈49 named SDK functions across 31 objects** (count OBJECTS — splat over-segments library + code into 217 INCLUDE_ASM stubs; session-F's per-block "26" was a looser estimate, superseded). GS_001 stays + excluded (gsgap3 stub, §9.1). Unified into ONE `psyq_integrate` call (supersedes the block-6-only path). + Changes: `tools/make_libgs.sh` (31-obj regen dir, **replaces** make_libgs_block6.sh); `psyq_integrate.py` + gained an optional placement-window arg (GS_106 = 8 ins, only anchors uniquely in 0x80051804-0x80057928); + `config/splat.us.exe.yaml` 800b resegmented → 800b(pre) + libgs1-5 + gsgap1-5 (+ unchanged libgs6/800b2); + `Makefile` LIBGS6_* → LIBGS_* (window + 6 stubs); `src/800b.c` trimmed to <0x80051804; **10 new stub src + files** (libgs1-5.c, gsgap1-5.c). Split DETERMINISTIC across 2 clean extracts; byte-identical WITH or WITHOUT + the SDK objects (fresh-clone stub fallback). Cookbook §9.5. **Remaining: Task 6 close-out, Task 7 PhaseEnd + (PhaseEnd gated on libgs per Drew — now UNBLOCKED).** +NOTE: Gen1 exit needs ≥3 SESSIONS of green `make check` — **satisfied** (A, B, C, D, E, F, +G); Tasks 6/7 pending. ### Island ownership map (session E — the decisive finding) Rodata island 0x80072A38–0x80074750, 102 jtbls total (~52 in-island). By owner segment: @@ -96,6 +105,8 @@ Patched `tools/psyq_identify.py` to skip data-only objects (no .text, e.g. GLOBA - 2026-06-14 (session D): `make clean && make extract && make build && make check` → `143dbb89… BYTE-IDENTICAL` ✓ — **libcd LINKED INTO THE BUILD** (Drew-approved push-through). The first real PsyQ library is now sourced from real SDK objects in the byte-identical build: **58 libcd SDK functions** linked (not stubs), replacing the libcd-region asm stubs. Idempotent; Makefile-automated; conditional (fresh clone w/o `tools/psyq/` builds via stubs). New committed tooling: `tools/psyq_link.py` (per-object byte-link engine — recovers externals from resolved relocs, weakens psyq-obj-parser's mislabelled `.bss` commons), `psyq_link_lib.py` (whole-lib verify, 18/18 libcd), `psyq_link_region.py` (region link via **NOLOAD** = no data carving), `psyq_integrate.py` (build wiring: splat resegment + .ld swap + external resolution), `split_src_region.py` (H5-safe src split). Cookbook §9.1/9.2/9.3 + R16. **No data carving** (NOLOAD data placement; flat data subseg unchanged). Build green throughout. - 2026-06-15 (session E): `make clean && make extract && make build && make check` → `143dbb89… BYTE-IDENTICAL` ✓ — start-of-session baseline + after the **LZSS surgical rodata carve** (jtbl_80072A38 migrated + sandwiched) + LZSS C **NON_MATCHING-guarded** (default build = stub). **≥3-session bar already MET (A/B/C/D); E is margin.** Findings: lean LZSS path proven (no libgs needed for it); LZSS C structurally matches (111/122) but blocked on a gcc cross-jump-merge hard-tail (see LZSS block above); libgs placement+disambiguation done (bonus, task #9, deferred per Drew until before PhaseEnd). - 2026-06-15 (session F): `make clean && make extract && make build && make check` → `143dbb89… BYTE-IDENTICAL` ✓ — TWO milestones. **(1) LZSS MATCHED byte-for-byte** (asm-differ score 0; C compiled in, NON_MATCHING guard dropped). Closed the session-E ~4 regalloc/scheduling residuals via §3a web-research (ground-truthed against pinned gcc-2.7.2 `reorg.c`/`jump.c`/`local-alloc.c`) + a floor-free `.text` object metric: decoupled high-byte var + OR operand order (residual A); `result`-in-predecessor + explicit-`$2`-local-with-early-pin (residual B); no-`default` sltiu-reuse (residual B3); §5a barrier retained. **43 real matches; Gen1-exit LZSS gate SATISFIED.** Cookbook §10. **(2) libgs BLOCK 6 integrated** (16 objects / 26 SDK funcs byte-identical; the libcd §9.3 pattern). Fixed a resegment-triggered spimdisasm boundary instability (declared GsMulCoord2/3; carved the §8 data table `[0x53198,data]`) + made psyq_integrate multi-library-safe (per-lib NOLOAD namespacing + global sort; sibling externals in the trial); `tools/make_libgs_block6.sh` for reproducibility; cookbook §9.4. Build byte-identical with OR without the SDK objects; fresh-clone stub fallback verified. Changed: `src/800.c`, `config/{splat.us.exe.yaml,symbols.us.txt}`, `Makefile`, `tools/{psyq_integrate.py,ld_interleave.py,make_libgs_block6.sh}`, `docs/matching-cookbook.md` (zero generated/ROM bulk staged). **Tasks: full libgs → 6 → 7 remain** (PhaseEnd gated on libgs per Drew). +- 2026-06-15 (session G): `make clean && make extract && make build && make check` → `143dbb89… BYTE-IDENTICAL` ✓ (start-of-session baseline + multiple full clean cycles after the change). **FULL libgs integration DONE** — the remaining 5 blocks unified with block 6 into ONE `psyq_integrate` call: **all 31 libgs objects / 6 contiguous blocks** linked from real SDK objects byte-identical (≈49 named SDK functions). `psyq_integrate.py` gained an optional placement-window arg (GS_106 = 8 ins anchors uniquely only in the libgs window); `tools/make_libgs.sh` replaces `make_libgs_block6.sh` (31-obj dir); config 800b resegmented into 800b(pre)+libgs1-5+gsgap1-5 (+ unchanged libgs6/800b2); `src/800b.c` trimmed (<0x80051804, 0 matched-C lost); 10 new stub src files. Split DETERMINISTIC across 2 clean extracts (no §9.4 boundary instability); byte-identical WITH or WITHOUT the SDK objects (fresh-clone stub fallback verified). Cookbook §9.5. Changed: `config/splat.us.exe.yaml`, `Makefile`, `src/800b.c` + 10 new `src/{libgs1-5,gsgap1-5}.c`, `tools/{psyq_integrate.py,make_libgs.sh}` (−make_libgs_block6.sh), `docs/matching-cookbook.md`, `docs/{progress,difficulty}.md` (digest refresh). **Tasks: 6 (close-out) → 7 (PhaseEnd) remain; libgs gate CLEARED.** + **(session G cont.)** Then the 4 jtbl loaders: **CdReadStateMachine / CdReadSectorReadyCB / StreamLoadStateMachine NON_MATCHING-drafted** (faithful translations of the Ghidra decompile; all valid C under `-DNON_MATCHING`, verified via a cpp→cc1 syntax check; shared-loader-global types reconciled to the file's s32/void* convention). **SaveLoadRoutine DEFERRED to Q#5** (Drew) — it is a 1139-ins multi-entry save/memcard blob (single `jr $ra`, 3 saveHeaderTemplate entry points, Ghidra returns only an unaff_-register fragment); left a full structured deferral comment in src/800.c. Task 6 close-out: README → Gen1-complete status; Gen1-exit checklist verified (43 REAL incl LZSS, 7 zero-regression sessions, reports auto-generate, loader cluster matched/drafted, overlay map proven). **Gen1 EXIT confirmed by Drew.** 3 rules formalized at PhaseEnd: R17 (web-research compiler quirks), R18 (plain-English session recaps), R19 (preserve worklog → phase-ends/logs/, out of load order). This worklog is itself preserved under R19. **PhaseEnd_Phase7 written; Gen1 COMPLETE.** --- @@ -179,6 +190,9 @@ migration+ld_interleave path above. source e.g. `pmret/gcc-papermario`) + decomp community for **compiler-quirk residuals** (cross-jump, scheduling, regalloc) — a proven escalation tier above the permuter, below decomp.me. Found the LZSS cross-jump barrier. Captured in cookbook §3a/§5a + memory `web-research-compiler-quirks`. +- **Rule candidate (PhaseEnd, Drew-flagged session G):** end every session/phase with a few-sentence + plain-English recap of what we did and WHY (no jargon) — keeps Drew oriented on real progress without + tracking the byte-level detail. Captured in memory `session-summary-plain-english`. Apply immediately. - **Session-E checkpoint commit:** Drew directed a checkpoint commit (deviates from R8's strict one-commit-at-phase-end; consistent with the session A–D checkpoint commits in the git log). Commit local in WSL (no push, no Co-Authored-By per R5); Drew pushes via GitHub Desktop (R6). diff --git a/src/800.c b/src/800.c index 1f6391b37..f882e9b8b 100644 --- a/src/800.c +++ b/src/800.c @@ -851,13 +851,453 @@ s32 CdReadRequest(int *cdlFile, void *dest, s32 size, s32 mode) { return cdReq_result; } +#ifdef NON_MATCHING +/* libcd low-level command API (the loader uses raw CdControl, NOT the PsyQ CdRead() wrapper; + * not in psyq/libcd.h yet). */ +extern int CdControl(u8 com, u8 *param, u8 *result); +extern int CdSync(int mode, u8 *result); +extern void CdFlush(void); +extern void *CdReadyCallback(void *func); +extern void CdReadSectorReadyCB(char); /* the CdlReadN data-ready callback @0x8001A338 */ +extern void start(void); /* EXE entry — used as the idle "sink" sentinel */ +extern int func_8002FD14(int buf, int len); /* per-sector consumer (drains the queued list) */ +extern void func_8001A0FC(void); +extern int func_8001A114(void); + +/* cdReq_* async-read control block (Phase 3 T2); the D_800AE* are its unnamed members. */ +extern int cdReq_state; +extern int cdReq_retry; +extern int cdReq_result; /* return-by-global: 0 = still busy, 1 = done */ +extern u8 cdReq_cdResult; /* CdControl status byte (bit 0x10 = error) */ +extern void *cdReq_cdlFile; /* seek target (a CdlLOC*) */ +extern int cdReq_posInt; +extern int cdReq_timeout; +extern int cdReq_size; /* bytes to read */ +extern void *cdReq_dest; /* read destination */ +extern s32 cdReq_sink; /* sector sink address (dest while reading, &start idle) */ +extern int cdReq_wordsRemaining; +extern int cdReq_drainPhase; /* 0 = reading, 1 = drained, 2 = finished */ +extern void *cdReq_savedReadyCB; /* prior CdReadyCallback, restored on stop */ +extern int cdReq_curSector; +extern u8 D_800AE740; /* CdlSetmode mode-byte buffer (0xA0) */ +extern int D_800AE6E8; /* CdSync result scratch */ +extern int D_800AE6F8; +extern s32 D_800AE70C; /* last/idle CdlLOC (held as a word) */ +extern int D_800AE71C; /* count of queued sectors to drain (state 7) */ +extern int D_800AE724; /* base of the queued {ptr,len} sector list */ +extern int D_800AE6FC; +extern int D_800AE700; +extern u8 D_800AE798; +extern u8 D_800AE799; + +/* Hand-rolled polled async CD reader: one state step per call, switch(cdReq_state). Sequence + * SetMode(0xA0) -> SeekL(+CdPosToInt) -> ReadN(+CdReadSectorReadyCB sector drain) -> Pause/Flush. + * Progress is returned via cdReq_result (0 busy / 1 done). The game does NOT use PsyQ CdRead()/ + * CdReadSync(). pauseAfterSeek != 0 stops after the seek (state 8) instead of reading. + * Provenance: static trace, Phase 3 T2 (verified). + * NON_MATCHING: logically faithful to the Ghidra decompile; not byte-exact — a large switch state + * machine whose register allocation + jump-table placement are a later byte-match pass. */ +void CdReadStateMachine(int pauseAfterSeek) { + int n; + int i; + int off; + + switch (cdReq_state) { + case 0: + cdReq_retry = 0; + D_800AE71C = 0; + D_800AE70C = 0; + D_800AE740 = 0xA0; + CdControl(0x0E, &D_800AE740, &cdReq_cdResult); /* CdlSetmode */ + if ((cdReq_cdResult & 0x10) == 0) { + cdReq_state++; + goto state1; + } + break; + case 1: + state1: + D_800AE6E8 = CdSync(1, &cdReq_cdResult); + if (D_800AE6E8 == 0) { cdReq_result = 0; return; } + if (D_800AE6E8 == 5) { cdReq_state = 0; cdReq_result = 0; return; } + if (D_800AE6E8 != 2) { cdReq_result = 0; return; } + cdReq_retry = 0; + cdReq_state++; + D_800AE6E8 = 2; + state2: + if (cdReq_retry + 1 < 3) { + cdReq_retry++; + cdReq_result = 0; + return; + } + cdReq_state++; + cdReq_retry = 0; + cdReq_result = 0; + return; + case 2: + goto state2; + case 3: + CdControl(0x15, (u8 *)cdReq_cdlFile, &cdReq_cdResult); /* CdlSeekL */ + if ((cdReq_cdResult & 0x10) == 0) { + cdReq_timeout = 0; + cdReq_posInt = CdPosToInt((CdlLOC *)cdReq_cdlFile); + cdReq_state++; + goto state4; + } + break; + case 4: + state4: + D_800AE6E8 = CdSync(1, &cdReq_cdResult); + if (D_800AE6E8 == 5) { + cdReq_retry++; + flushRetry: + CdFlush(); + cdReq_state = 3; + cdReq_result = 0; + return; + } + if (D_800AE6E8 == 0) { cdReq_result = 0; return; } + if (D_800AE6E8 != 2) { + cdReq_result = 0; + cdReq_timeout++; + if (cdReq_timeout < 0xB5) { cdReq_result = 0; return; } + goto flushRetry; + } + if (pauseAfterSeek == 0) { + cdReq_state++; + goto state5; + } + CdControl(0x09, (u8 *)0, &cdReq_cdResult); /* CdlPause */ + if ((cdReq_cdResult & 0x10) == 0) { + cdReq_state = 8; + D_800AE70C = *(s32 *)cdReq_cdlFile; + cdReq_result = 0; + return; + } + break; + case 5: + state5: + cdReq_drainPhase = 0; + if (cdReq_size < 1) { + D_800AE6F8 = 0; + } else { + int bytes = cdReq_size + 3; + D_800AE6F8 = 3; + if (bytes < 0) bytes = cdReq_size + 6; /* round-up word count, neg-safe */ + cdReq_wordsRemaining = bytes >> 2; + D_800AE798 = 1; + cdReq_sink = (s32)cdReq_dest; + } + cdReq_savedReadyCB = CdReadyCallback(CdReadSectorReadyCB); + CdControl(0x06, (u8 *)0, &cdReq_cdResult); /* CdlReadN */ + if ((cdReq_cdResult & 0x10) == 0) { + cdReq_retry = 0; + cdReq_state++; + goto state6; + } + break; + case 6: + state6: + if (cdReq_drainPhase != 1) { + if (cdReq_drainPhase == 2) { + CdReadyCallback(cdReq_savedReadyCB); + cdReq_state = 0; + cdReq_result = 0; + return; + } + cdReq_retry++; + if (cdReq_retry > 299) { + CdReadyCallback(cdReq_savedReadyCB); + cdReq_state = 0; + CdFlush(); + cdReq_result = 0; + return; + } + cdReq_result = 0; + return; + } + CdReadyCallback(cdReq_savedReadyCB); + CdControl(0x09, (u8 *)0, &cdReq_cdResult); /* CdlPause */ + if ((cdReq_cdResult & 0x10) == 0) { + cdReq_state += (D_800AE71C == 0) ? 2 : 1; /* -> 8 (no drain) or 7 (drain) */ + cdReq_result = 0; + return; + } + break; + case 7: + n = 0; + if (D_800AE71C > 0) { + off = 0; + do { + do { + i = func_8002FD14(*(int *)(D_800AE724 + off), + *(int *)(D_800AE724 + off + 4)); + } while (i == 0); + n++; + off = n * 8; + } while (n < D_800AE71C); + } + cdReq_state++; + cdReq_result = 0; + return; + case 8: + D_800AE6E8 = CdSync(1, &cdReq_cdResult); + if (D_800AE6E8 == 2) { + cdReq_sink = (s32)start; + cdReq_state = 0; + D_800AE6F8 = 0; + D_800AE6FC = 0; + D_800AE700 = 0; + cdReq_curSector = 0; + D_800AE70C = 0; + cdReq_wordsRemaining = 1; + cdReq_drainPhase = 0; + D_800AE798 = 0; + D_800AE799 = 0; + cdReq_result = 1; + return; + } + if (D_800AE6E8 != 5) { cdReq_result = 0; return; } + CdFlush(); + CdControl(0x09, (u8 *)0, &cdReq_cdResult); /* CdlPause */ + if ((cdReq_cdResult & 0x10) == 0) { cdReq_result = 0; return; } + break; + case 9: + func_8001A0FC(); + cdReq_state++; + /* fall through to state 10 */ + case 10: + D_800AE6E8 = func_8001A114(); + if (D_800AE6E8 != 0) { + cdReq_state = 0; + cdReq_result = 0; + return; + } + cdReq_result = 0; + return; + default: + goto setDefault; + } + cdReq_state = 9; +setDefault: + cdReq_result = 0; + return; +} +#else INCLUDE_ASM("asm/nonmatchings/800", CdReadStateMachine); +#endif INCLUDE_ASM("asm/nonmatchings/800", func_8001A0FC); INCLUDE_ASM("asm/nonmatchings/800", func_8001A114); +#ifdef NON_MATCHING +typedef struct { short x, y, w, h; } RECT; /* libgpu RECT (VRAM rectangle) */ +extern int CdGetSector(void *madr, int nsector); +extern void LoadImage(RECT *rect, u32 *data); /* libgpu VRAM upload */ +extern int func_8002FC64(int nbytes, u32 *src); /* stage/copy a payload run */ +extern int func_8002FB08(int entry); /* kick off a queued list entry */ +extern void func_8002FDC8(void); /* finalize the queued transfer */ +extern void func_80018714(void); /* reset LZSS sector state */ + +/* shared cdReq_ control block (see CdReadStateMachine) + this callback's members */ +extern int cdReq_posInt; +extern int cdReq_size; +extern int cdReq_wordsRemaining; +extern int cdReq_drainPhase; +extern void *cdReq_dest; +extern s32 cdReq_sink; +extern u32 lzss_sectorStagingBuf[]; /* 0x80079A70 — PAC header lands here ([0] = 'PAC' magic) */ +extern u8 cdReq_sectorHdrBuf[]; /* sub-header scratch (3 words -> CdPosToInt) */ +extern u8 D_80079A74; /* PAC type */ +extern u8 D_80079A75; /* PAC flags -> D_800AE798 */ +extern int D_80079A78; /* PAC sub-count (textures) */ +extern int D_80079A7C; /* PAC payload size (bytes) */ +extern int D_800AE6F8; /* load phase (PAC-type dispatcher state) */ +extern u8 D_800AE798; /* saved PAC flags (nonzero = more sectors follow) */ +extern int D_800AE704; /* texture sector index (0..3, round-robin) */ +extern short D_800AE710, D_800AE712; /* VRAM dst x,y of the current tile (a RECT @0xAE710) */ +extern short D_800AE714, D_800AE716; /* tile w,h (0x20 x 0x20) */ +extern u8 D_800AE758[]; /* 64-byte present bitmask (which tiles are sent) */ +extern u8 D_800AE79A, D_800AE79B; /* bit / byte cursor into the bitmask */ +extern RECT *D_800AE718; /* current texture-rect pointer (PAC type 6) */ +extern int D_800AE71C; /* queued-entry count */ +extern int D_800AE720; /* "transfer already in progress" guard */ +extern int D_800AE724; /* base of the queued {ptr,len} list */ +extern void *D_80072C80; /* a fixed destination pointer (PAC type 7) */ +extern u32 D_8007A280[]; /* texture RECT array (read from CD) */ +extern u32 D_8007A310[]; /* texture pixel data (read from CD) */ + +/* CdReadyCallback for CdReadStateMachine (registered in its state 5). Per CD data-ready interrupt, + * drain one sector and dispatch on the load phase D_800AE6F8: parse the PAC header, then per PAC + * type either upload tiles to VRAM (LoadImage), copy raw sectors to cdReq_sink, or decompress via + * LzssDecodeSector (this is its ONLY caller). reason: 1 = data-ready, 5 = end/error. + * Provenance: static trace, Phase 3 T2/T4 (verified). + * NON_MATCHING: faithful translation of the Ghidra decompile — logically faithful, not byte-verified + * (large interrupt callback; register allocation + jump-table placement are a later byte-match pass). */ +void CdReadSectorReadyCB(char reason) { + void *dst; + int pos; + int i, n; + + if (reason != 1) goto endReason; + CdGetSector(cdReq_sectorHdrBuf, 3); + pos = CdPosToInt((CdlLOC *)cdReq_sectorHdrBuf); + if (pos != cdReq_posInt) { /* sector out of order -> abort the drain */ + if (D_800AE6F8 == 4) func_8002FDC8(); + cdReq_drainPhase = 2; + return; + } + cdReq_posInt = pos + 1; + switch (D_800AE6F8) { + case 0: /* expect a PAC header */ + CdGetSector(lzss_sectorStagingBuf, 4); + if (lzss_sectorStagingBuf[0] != 0x434150) goto abortDrain; /* "PAC" magic? */ + D_800AE798 = D_80079A75; + switch (D_80079A74) { /* PAC type */ + case 0: + case 5: /* tiled texture: scan the present-bitmask */ + D_800AE704 = 0; + CdGetSector(D_800AE758, 0x10); + D_800AE716 = 0x20; D_800AE714 = 0x20; + D_800AE712 = 0; D_800AE710 = 0; + D_800AE79A = 1; D_800AE79B = 0; + while (D_800AE79B < 0x40) { + if ((D_800AE79A & D_800AE758[D_800AE79B]) != 0) goto tilePhase; + if (D_800AE79A == 0x80) { D_800AE79A = 1; D_800AE79B++; } + else D_800AE79A <<= 1; + D_800AE710 += 0x20; + if (D_800AE710 > 0x3FF) { D_800AE710 = 0; D_800AE712 += 0x20; } + } + D_800AE6F8 = (D_800AE798 == 0) ? 0 : 2; + tilePhase: + D_800AE6F8++; + break; + case 1: + dst = cdReq_dest; + goto setSink; + case 2: + if (D_800AE720 != 0) goto phaseDone; + dst = *(void **)(D_800AE71C * 8 + D_800AE724 + 4); + goto setSink; + case 3: + if (D_800AE720 != 0) goto phaseDone; + do { i = func_8002FB08(*(int *)(D_800AE71C * 8 + D_800AE724)); } while (i == 0); + D_800AE6F8 = 4; + cdReq_wordsRemaining = D_80079A7C - 0x800; + break; + case 4: + D_800AE6F8 = 5; + cdReq_wordsRemaining = (D_80079A7C - 0x7FD) >> 2; + cdReq_sink = (s32)cdReq_dest; + func_80018714(); + break; + case 6: /* multiple TIMs: LoadImage each */ + CdGetSector(D_8007A280, 0x1FC); + n = D_80079A78; + { + u32 *p = D_8007A310; + i = 0; + D_800AE718 = (RECT *)D_8007A280; + if (n > 0) { + do { + LoadImage(D_800AE718, p); + i++; + p += D_800AE718->w / 2; + D_800AE718++; + } while (i < n); + } + } + goto sectorDone; + case 7: + dst = D_80072C80; + if (cdReq_size < 0) goto phaseDone; + setSink: + D_800AE6F8 = 3; + cdReq_wordsRemaining = (D_80079A7C - 0x7FD) >> 2; + cdReq_sink = dst; + break; + case 8: + D_800AE6F8 = 6; + cdReq_wordsRemaining = (D_80079A7C - 0x7FD) >> 2; + break; + } + break; + case 1: /* stream tile pixels to VRAM */ + i = D_800AE704 * 0x200; + CdGetSector(&lzss_sectorStagingBuf[i], 0x200); + LoadImage((RECT *)&D_800AE710, &lzss_sectorStagingBuf[i]); + D_800AE704 = (D_800AE704 + 1) & 3; + do { + if (D_800AE79A == 0x80) { D_800AE79A = 1; D_800AE79B++; } + else D_800AE79A <<= 1; + if (D_800AE79B > 0x3F) goto sectorDone; + D_800AE710 += 0x20; + if (D_800AE710 > 0x3FF) { D_800AE710 = 0; D_800AE712 += 0x20; } + } while ((D_800AE79A & D_800AE758[D_800AE79B]) == 0); + break; + case 3: /* raw copy to cdReq_sink */ + if (cdReq_wordsRemaining < 0x201) { + CdGetSector((void *)cdReq_sink, cdReq_wordsRemaining); + sectorDone: + D_800AE6F8 = (D_800AE798 == 0) ? 0 : 2; + } else { + CdGetSector((void *)cdReq_sink, 0x200); + cdReq_wordsRemaining -= 0x200; + cdReq_sink += 0x800; + } + break; + case 4: /* staged copy via func_8002FC64 */ + if (cdReq_wordsRemaining < 0x801) { + n = cdReq_wordsRemaining + 3; + if (n < 0) n = cdReq_wordsRemaining + 6; + CdGetSector(lzss_sectorStagingBuf, n >> 2); + if (func_8002FC64(cdReq_wordsRemaining, lzss_sectorStagingBuf) == 0) { + stageFail: + func_8002FDC8(); + D_800AE6F8 = 7; + } else { + D_800AE71C++; + if (D_800AE798 != 0) goto phaseDone; + D_800AE6F8 = 0; + } + } else { + CdGetSector(lzss_sectorStagingBuf, 0x200); + i = func_8002FC64(0x800, lzss_sectorStagingBuf); + cdReq_sink += 0x800; + cdReq_wordsRemaining -= 0x800; + if (i == 0) goto stageFail; + } + break; + case 5: /* LZSS decompress one sector */ + CdGetSector(lzss_sectorStagingBuf, 0x200); + if (LzssDecodeSector(lzss_sectorStagingBuf) == 0) goto sectorDone; + break; + case 6: /* countdown the remaining words */ + cdReq_wordsRemaining -= 0x200; + if (cdReq_wordsRemaining < 1) { + if (D_800AE798 != 0) goto phaseDone; + D_800AE6F8 = 0; + } + break; + } + if (D_800AE6F8 == 2) { + phaseDone: /* (reached by the switch above or by goto) */ + D_800AE6F8 = 2; + cdReq_drainPhase = 1; + } else if (D_800AE6F8 == 7) { + cdReq_drainPhase = 2; + } +endReason: + if (reason == 5) { + if (D_800AE6F8 == 4) func_8002FDC8(); + abortDrain: + cdReq_drainPhase = 2; + } + return; +} +#else INCLUDE_ASM("asm/nonmatchings/800", CdReadSectorReadyCB); +#endif INCLUDE_ASM("asm/nonmatchings/800", func_8001A9D8); @@ -1678,6 +2118,25 @@ INCLUDE_ASM("asm/nonmatchings/800", func_8002B08C); INCLUDE_ASM("asm/nonmatchings/800", func_8002B0B4); +/* DEFERRED: SaveLoadRoutine (0x8002B154) — Phase 7 (session G), per Drew, to Q#5. + * Original intent: the save / PS1 memory-card handler. Referenced by saveHeaderTemplate + * (0x80072DF0) via THREE entry-point pointers: 0x8002B154 / 0x8002B1AC / 0x8002BEA4. + * Dispatch branches on (selector & 7) (case 0 -> +1 counter; 1 -> 7; 2 -> 0x1E; 3 -> 0x23). + * Why deferred (NOT a clean state machine like the 3 CD loaders drafted this session): + * - splat emits ONE 1139-instruction stub spanning 0x8002B154-0x8002C31C with a SINGLE `jr $ra` + * => it is effectively one large MULTI-ENTRY function (the 3 saveHeaderTemplate entries share a + * return; the caller passes args in $s0/$s3) — awkward to express in C at all. + * - Ghidra mis-analyses it: get_code(0x8002B154) returns only a tiny fragment using unaff_s0/ + * unaff_s3 (caller-set regs), so there is no faithful whole-function decompile to translate. + * - It is the save-data/memcard format, explicitly Phase-3 Q#5 "format still TBD" — a different + * subsystem from the file/overlay loader cluster (which IS drafted: CdReadStateMachine, + * CdReadSectorReadyCB, StreamLoadStateMachine here + the matched CdReadRequest/CdQueueBusy/…). + * - External helpers it calls (uncharacterised): func_800603BC (x20), func_80060614, func_8006023C, + * func_80060D9C, func_80060AE0, func_8005FFB4, func_8005FD58, func_80061114/524, func_80016714(bzero). + * Re-enable / revisit when Q#5 (save/memcard format) is studied: FIRST fix the Ghidra function + * boundary (make 0x8002B154 span the whole 1139 ins, or model the 3 entry points), re-decompile, + * characterise the func_80060xxx memcard helpers, THEN draft. A wrong faithful-looking draft here + * would be worse than this honest stub (P9/G3). The default build is byte-identical via this stub. */ INCLUDE_ASM("asm/nonmatchings/800", SaveLoadRoutine); INCLUDE_ASM("asm/nonmatchings/800", func_8002C320); @@ -1989,7 +2448,236 @@ INCLUDE_ASM("asm/nonmatchings/800", func_8003621C); INCLUDE_ASM("asm/nonmatchings/800", func_80036260); +#ifdef NON_MATCHING +extern int CdControl(u8 com, u8 *param, u8 *result); +extern int CdSync(int mode, u8 *result); +extern u32 CdMode(void); +extern void CdFlush(void); +extern void *CdReadyCallback(void *func); +extern void func_800377D8(void); /* this loader's CdlReadN ready-callback */ +extern void func_80036260(void); /* sub-handler passed to func_80037CD8 */ +extern void func_8002EC10(void); +extern void func_80037334(void); +extern void func_80037358(int posInt); +extern void func_80037144(int idx); +extern int func_80037CD8(void *arg); +extern int func_8003750C(void); +extern int func_800374CC(int *out); +extern int func_8003775C(void); +extern void func_80037D74(void); +extern void *func_80037368(int *out); + +extern int streamLoad_state; /* 0x8006AF00 */ +extern void *streamLoad_savedReadyCB; +extern u8 streamLoad_cbActive; +extern int D_800A4F28; /* tick / timeout counter */ +extern int D_800A4F2C; /* CdMode-derived retry budget */ +extern int D_800A4F30; /* remaining sub-stream repeat count (from param_3) */ +extern int D_800A4F34; /* last sector position (stall detection) */ +extern int D_800A4F38; /* end/abort flag */ +extern int D_800A6544; +extern u16 D_800A4E8E; /* 16-bit status flags */ +extern u8 D_8006AEF4; /* 8-bit status flags */ +extern u8 D_80068B60[]; /* per-resource descriptor table (0x10 stride) */ + +/* Second CD loader, DISTINCT from CdReadStateMachine: an 18-state machine (streamLoad_state) with + * its OWN ready-callback (func_800377D8). Runs SetMode(0xA0)/SeekL/ReadN with retry + CdMode + * handling, looping param_3 (D_800A4F30) times over sub-streams. Returns 0 = busy, 1 = done, + * 2 = error/abort. Driven by ResourceLoadStateMachine. Provenance: static trace, Phase 3 T4 (fn id + * verified; full semantics partial). + * NON_MATCHING: faithful translation of the Ghidra decompile — logically faithful, not byte-verified. */ +int StreamLoadStateMachine(int param_1, void *param_2, int param_3) { /* param_2 is a CdlLOC* */ + short sVar1; + int iVar4; + u32 uVar2; + char *pcVar3; + u8 local_28[8]; + u8 local_20[8]; + int local_18; + int local_14; + + sVar1 = *(short *)(D_80068B60 + (param_1 - 0x100) * 0x10); + D_800A4F28++; + switch (streamLoad_state) { + case 0: + D_800A4F38 = 0; + D_800A6544 = 0; + D_800A4F30 = param_3; + func_8002EC10(); + streamLoad_state++; + D_800A4E8E &= 0xFFDF; + return 0; + case 1: + func_80037334(); + iVar4 = CdPosToInt((CdlLOC *)param_2); + func_80037358(iVar4); + func_80037144(sVar1); + streamLoad_state++; + /* fall through */ + case 2: + local_14 = CdSync(1, local_20); + if (local_14 != 5 && local_14 != 2) return 0; + uVar2 = CdMode(); + D_800A4F2C = ((uVar2 & 0x80) == 0) ? 3 : 0; + streamLoad_state++; + /* fall through */ + case 3: + local_28[0] = 0xA0; + iVar4 = CdControl(0x0E, local_28, local_20); /* CdlSetmode */ + if (iVar4 == 0) { + if ((local_20[0] & 0x10) != 0) { func_80037334(); streamLoad_state = 0; return 2; } + return 0; + } + D_800A4F28 = 0; + streamLoad_state++; + /* fall through */ + case 4: + iVar4 = CdSync(1, local_20); + if (iVar4 != 5) { + if (iVar4 == 2) { + streamLoad_state++; + local_14 = 2; + modeWait: + if (D_800A4F2C != 0) { D_800A4F2C--; return 0; } + streamLoad_state++; + goto issueSeek; + } + if (D_800A4F28 < 0x3D) return 0; + } + streamLoad_state = 3; + return 0; + case 5: + goto modeWait; + case 6: + issueSeek: + iVar4 = func_80037CD8((void *)func_80036260); + if (iVar4 == 0) return 0; + streamLoad_state = 7; + /* fall through */ + case 7: + iVar4 = func_8003750C(); + if (iVar4 == 0) return 0; + streamLoad_state++; + /* fall through */ + case 8: + local_14 = CdSync(1, local_20); + if (local_14 != 5 && local_14 != 2) return 0; + streamLoad_state++; + /* fall through */ + case 9: + D_800A4F28 = 0; + iVar4 = CdControl(0x15, (u8 *)param_2, local_20); /* CdlSeekL */ + if (iVar4 == 0) return 0; + streamLoad_state++; + /* fall through */ + case 10: + local_14 = CdSync(1, local_20); + if (local_14 == 5) { + iVar4 = CdControl(0x01, (u8 *)0, local_20); /* CdlNop */ + if (iVar4 == 0) { streamLoad_state = 9; return 0; } + if ((local_20[0] & 0x10) != 0) { + func_80037334(); + D_8006AEF4 &= 0xFD; + streamLoad_state = 0; + return 2; + } + streamLoad_state = 9; + return 0; + } + if (local_14 != 2) { + if (D_800A4F28 < 0x12D) return 0; + CdFlush(); + streamLoad_state = 9; + return 2; + } + streamLoad_state++; + local_14 = 2; + /* fall through */ + case 0xB: + D_800A4F28 = 0; + iVar4 = CdControl(0x06, (u8 *)param_2, local_20); /* CdlReadN */ + if (iVar4 != 0) { + if (streamLoad_cbActive == 0) + streamLoad_savedReadyCB = CdReadyCallback((void *)func_800377D8); + else + CdReadyCallback((void *)func_800377D8); + streamLoad_state++; + streamLoad_cbActive = 1; + return 0; + } + if ((local_20[0] & 0x10) == 0) return 0; + D_8006AEF4 &= 0xFD; + func_80037334(); + streamLoad_state = 0; + return 2; + case 0xC: + iVar4 = func_800374CC(&local_18); + if (iVar4 == 0) { + if (local_18 != D_800A4F34) { D_800A4F34 = local_18; D_800A4F28 = 0; } + if (D_800A4F28 > 300) { + if (streamLoad_cbActive != 0) { + CdReadyCallback(streamLoad_savedReadyCB); + streamLoad_savedReadyCB = 0; + streamLoad_cbActive = 0; + } + streamLoad_state++; + } + return 0; + } + if (streamLoad_cbActive != 0) { + CdReadyCallback(streamLoad_savedReadyCB); + streamLoad_savedReadyCB = 0; + streamLoad_cbActive = 0; + } + streamLoad_state++; + /* fall through */ + case 0xD: + iVar4 = func_8003775C(); + if (iVar4 == 0) return 0; + func_80037D74(); + streamLoad_state++; + return 0; + case 0xE: + pcVar3 = (char *)func_80037368(&local_14); + if (local_14 != 1) { streamLoad_state = 0x11; return 0; } + if (*pcVar3 != 1) { + if (*pcVar3 == 2) { streamLoad_state = 0x11; D_800A4F38 = 1; return 0; } + if (D_800A4F30 != 0) { + if (D_800A4F30 - 1 == 0) { streamLoad_state = 0x11; D_800A4F30 = 0; return 0; } + streamLoad_state = 1; + D_800A4F30--; + return 0; + } + streamLoad_state = 1; + return 0; + } + streamLoad_state++; + break; /* -> CdlPause (post-switch) */ + case 0xF: + break; /* -> CdlPause (post-switch) */ + case 0x10: + iVar4 = CdSync(1, local_20); + if (iVar4 != 5 && iVar4 != 2) return 0; + if (D_800A4F38 == 0) { streamLoad_state = 0; return 1; } + streamLoad_state = 0; + D_800A4F38 = 0; + return 2; + case 0x11: + iVar4 = CdControl(0x09, (u8 *)0, local_20); /* CdlPause */ + if (iVar4 != 0) { streamLoad_state = 0x10; return 0; } + return 0; + default: + return 0; + } + /* shared tail for states 0xE (advance) and 0xF: pause, then wait for it */ + iVar4 = CdControl(0x09, (u8 *)0, local_20); /* CdlPause */ + if (iVar4 != 0) { streamLoad_state++; return 0; } + if ((local_20[0] & 0x10) != 0) { streamLoad_state = 0; return 1; } + return 0; +} +#else INCLUDE_ASM("asm/nonmatchings/800", StreamLoadStateMachine); +#endif INCLUDE_ASM("asm/nonmatchings/800", func_80036AF8); diff --git a/src/800b.c b/src/800b.c index 03002cb8c..9b359ef93 100644 --- a/src/800b.c +++ b/src/800b.c @@ -344,139 +344,3 @@ INCLUDE_ASM("asm/nonmatchings/800b", gfx2D_BG0_OBJ_698); INCLUDE_ASM("asm/nonmatchings/800b", GsSortFastBg); INCLUDE_ASM("asm/nonmatchings/800b", gfx2D_BG1_OBJ_648); - -INCLUDE_ASM("asm/nonmatchings/800b", _mk_spr_packet); - -INCLUDE_ASM("asm/nonmatchings/800b", gfx2D_COM0_OBJ_30); - -INCLUDE_ASM("asm/nonmatchings/800b", _mk_xpndsp); - -INCLUDE_ASM("asm/nonmatchings/800b", gfx2D_COM0_OBJ_EC); - -INCLUDE_ASM("asm/nonmatchings/800b", gfx2D_COM0_OBJ_154); - -INCLUDE_ASM("asm/nonmatchings/800b", _mk_normsp); - -INCLUDE_ASM("asm/nonmatchings/800b", gfx2D_COM0_OBJ_39C); - -INCLUDE_ASM("asm/nonmatchings/800b", gfx2D_COM0_OBJ_400); - -INCLUDE_ASM("asm/nonmatchings/800b", ApplyMatrix); - -INCLUDE_ASM("asm/nonmatchings/800b", GsSortLine); - -INCLUDE_ASM("asm/nonmatchings/800b", _make_packet); - -INCLUDE_ASM("asm/nonmatchings/800b", GsSortSprite); - -INCLUDE_ASM("asm/nonmatchings/800b", gfx2D_SP0_OBJ_240); - -INCLUDE_ASM("asm/nonmatchings/800b", gfx2D_SP0_OBJ_388); - -INCLUDE_ASM("asm/nonmatchings/800b", gfx2D_SP0_OBJ_3C8); - -INCLUDE_ASM("asm/nonmatchings/800b", gfx2D_SP0_OBJ_4C0); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80052430); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80052460); - -INCLUDE_ASM("asm/nonmatchings/800b", func_800525DC); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80052654); - -INCLUDE_ASM("asm/nonmatchings/800b", GsInitGraph2); - -INCLUDE_ASM("asm/nonmatchings/800b", func_8005283C); - -INCLUDE_ASM("asm/nonmatchings/800b", GsSortClear); - -INCLUDE_ASM("asm/nonmatchings/800b", GS_001_OBJ_5D0); - -INCLUDE_ASM("asm/nonmatchings/800b", GsGetActiveBuff); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80052BEC); - -INCLUDE_ASM("asm/nonmatchings/800b", GS_002_OBJ_10C); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80052D00); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80052D90); - -INCLUDE_ASM("asm/nonmatchings/800b", GsInitCoord2param); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80052E38); - -INCLUDE_ASM("asm/nonmatchings/800b", GsSetLightMatrix); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80052F04); - -INCLUDE_ASM("asm/nonmatchings/800b", GsMulCoord0); - -INCLUDE_ASM("asm/nonmatchings/800b", GsMulCoord2); - -INCLUDE_ASM("asm/nonmatchings/800b", GsMulCoord3); - -INCLUDE_ASM("asm/nonmatchings/800b", print_matrix); - -INCLUDE_ASM("asm/nonmatchings/800b", print_vector); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80053178); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80053218); - -INCLUDE_ASM("asm/nonmatchings/800b", GsMapModelingData); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80053308); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80053328); - -INCLUDE_ASM("asm/nonmatchings/800b", GS_107_OBJ_464); - -INCLUDE_ASM("asm/nonmatchings/800b", GS_107_OBJ_4B8); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80053804); - -INCLUDE_ASM("asm/nonmatchings/800b", func_8005386C); - -INCLUDE_ASM("asm/nonmatchings/800b", func_800538BC); - -INCLUDE_ASM("asm/nonmatchings/800b", func_800538EC); - -INCLUDE_ASM("asm/nonmatchings/800b", GS_108_OBJ_6C); - -INCLUDE_ASM("asm/nonmatchings/800b", GS_108_OBJ_7C); - -INCLUDE_ASM("asm/nonmatchings/800b", GsSetFogParam); - -INCLUDE_ASM("asm/nonmatchings/800b", SetVertex0); - -INCLUDE_ASM("asm/nonmatchings/800b", SetVertex1); - -INCLUDE_ASM("asm/nonmatchings/800b", SetVertex2); - -INCLUDE_ASM("asm/nonmatchings/800b", SetVertexTri); - -INCLUDE_ASM("asm/nonmatchings/800b", SetRGBfifo); - -INCLUDE_ASM("asm/nonmatchings/800b", SetIR123); - -INCLUDE_ASM("asm/nonmatchings/800b", SetIR0); - -INCLUDE_ASM("asm/nonmatchings/800b", SetSZfifo3); - -INCLUDE_ASM("asm/nonmatchings/800b", SetSZfifo4); - -INCLUDE_ASM("asm/nonmatchings/800b", SetSXSYfifo); - -INCLUDE_ASM("asm/nonmatchings/800b", SetRii); - -INCLUDE_ASM("asm/nonmatchings/800b", SetMAC123); - -INCLUDE_ASM("asm/nonmatchings/800b", SetData32); - -INCLUDE_ASM("asm/nonmatchings/800b", SetDQA); - -INCLUDE_ASM("asm/nonmatchings/800b", SetDQB); - -INCLUDE_ASM("asm/nonmatchings/800b", func_80053AD8); diff --git a/src/gsgap1.c b/src/gsgap1.c new file mode 100644 index 000000000..a2c8ece90 --- /dev/null +++ b/src/gsgap1.c @@ -0,0 +1,3 @@ +#include "common.h" + +INCLUDE_ASM("asm/nonmatchings/gsgap1", ApplyMatrix); diff --git a/src/gsgap2.c b/src/gsgap2.c new file mode 100644 index 000000000..bbea7f48c --- /dev/null +++ b/src/gsgap2.c @@ -0,0 +1,3 @@ +#include "common.h" + +INCLUDE_ASM("asm/nonmatchings/gsgap2", func_80052430); diff --git a/src/gsgap3.c b/src/gsgap3.c new file mode 100644 index 000000000..f86a78675 --- /dev/null +++ b/src/gsgap3.c @@ -0,0 +1,13 @@ +#include "common.h" + +INCLUDE_ASM("asm/nonmatchings/gsgap3", func_800525DC); + +INCLUDE_ASM("asm/nonmatchings/gsgap3", func_80052654); + +INCLUDE_ASM("asm/nonmatchings/gsgap3", GsInitGraph2); + +INCLUDE_ASM("asm/nonmatchings/gsgap3", func_8005283C); + +INCLUDE_ASM("asm/nonmatchings/gsgap3", GsSortClear); + +INCLUDE_ASM("asm/nonmatchings/gsgap3", GS_001_OBJ_5D0); diff --git a/src/gsgap4.c b/src/gsgap4.c new file mode 100644 index 000000000..e38e997d0 --- /dev/null +++ b/src/gsgap4.c @@ -0,0 +1,3 @@ +#include "common.h" + +INCLUDE_ASM("asm/nonmatchings/gsgap4", func_800538BC); diff --git a/src/gsgap5.c b/src/gsgap5.c new file mode 100644 index 000000000..76d3934df --- /dev/null +++ b/src/gsgap5.c @@ -0,0 +1,33 @@ +#include "common.h" + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetVertex0); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetVertex1); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetVertex2); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetVertexTri); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetRGBfifo); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetIR123); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetIR0); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetSZfifo3); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetSZfifo4); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetSXSYfifo); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetRii); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetMAC123); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetData32); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetDQA); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", SetDQB); + +INCLUDE_ASM("asm/nonmatchings/gsgap5", func_80053AD8); diff --git a/src/libgs1.c b/src/libgs1.c new file mode 100644 index 000000000..8ce1628fc --- /dev/null +++ b/src/libgs1.c @@ -0,0 +1,17 @@ +#include "common.h" + +INCLUDE_ASM("asm/nonmatchings/libgs1", _mk_spr_packet); + +INCLUDE_ASM("asm/nonmatchings/libgs1", gfx2D_COM0_OBJ_30); + +INCLUDE_ASM("asm/nonmatchings/libgs1", _mk_xpndsp); + +INCLUDE_ASM("asm/nonmatchings/libgs1", gfx2D_COM0_OBJ_EC); + +INCLUDE_ASM("asm/nonmatchings/libgs1", gfx2D_COM0_OBJ_154); + +INCLUDE_ASM("asm/nonmatchings/libgs1", _mk_normsp); + +INCLUDE_ASM("asm/nonmatchings/libgs1", gfx2D_COM0_OBJ_39C); + +INCLUDE_ASM("asm/nonmatchings/libgs1", gfx2D_COM0_OBJ_400); diff --git a/src/libgs2.c b/src/libgs2.c new file mode 100644 index 000000000..8d4bb3b66 --- /dev/null +++ b/src/libgs2.c @@ -0,0 +1,15 @@ +#include "common.h" + +INCLUDE_ASM("asm/nonmatchings/libgs2", GsSortLine); + +INCLUDE_ASM("asm/nonmatchings/libgs2", _make_packet); + +INCLUDE_ASM("asm/nonmatchings/libgs2", GsSortSprite); + +INCLUDE_ASM("asm/nonmatchings/libgs2", gfx2D_SP0_OBJ_240); + +INCLUDE_ASM("asm/nonmatchings/libgs2", gfx2D_SP0_OBJ_388); + +INCLUDE_ASM("asm/nonmatchings/libgs2", gfx2D_SP0_OBJ_3C8); + +INCLUDE_ASM("asm/nonmatchings/libgs2", gfx2D_SP0_OBJ_4C0); diff --git a/src/libgs3.c b/src/libgs3.c new file mode 100644 index 000000000..5e7bcaa67 --- /dev/null +++ b/src/libgs3.c @@ -0,0 +1,3 @@ +#include "common.h" + +INCLUDE_ASM("asm/nonmatchings/libgs3", func_80052460); diff --git a/src/libgs4.c b/src/libgs4.c new file mode 100644 index 000000000..1ec2834f3 --- /dev/null +++ b/src/libgs4.c @@ -0,0 +1,47 @@ +#include "common.h" + +INCLUDE_ASM("asm/nonmatchings/libgs4", GsGetActiveBuff); + +INCLUDE_ASM("asm/nonmatchings/libgs4", func_80052BEC); + +INCLUDE_ASM("asm/nonmatchings/libgs4", GS_002_OBJ_10C); + +INCLUDE_ASM("asm/nonmatchings/libgs4", func_80052D00); + +INCLUDE_ASM("asm/nonmatchings/libgs4", func_80052D90); + +INCLUDE_ASM("asm/nonmatchings/libgs4", GsInitCoord2param); + +INCLUDE_ASM("asm/nonmatchings/libgs4", func_80052E38); + +INCLUDE_ASM("asm/nonmatchings/libgs4", GsSetLightMatrix); + +INCLUDE_ASM("asm/nonmatchings/libgs4", func_80052F04); + +INCLUDE_ASM("asm/nonmatchings/libgs4", GsMulCoord0); + +INCLUDE_ASM("asm/nonmatchings/libgs4", GsMulCoord2); + +INCLUDE_ASM("asm/nonmatchings/libgs4", GsMulCoord3); + +INCLUDE_ASM("asm/nonmatchings/libgs4", print_matrix); + +INCLUDE_ASM("asm/nonmatchings/libgs4", print_vector); + +INCLUDE_ASM("asm/nonmatchings/libgs4", func_80053178); + +INCLUDE_ASM("asm/nonmatchings/libgs4", func_80053218); + +INCLUDE_ASM("asm/nonmatchings/libgs4", GsMapModelingData); + +INCLUDE_ASM("asm/nonmatchings/libgs4", func_80053308); + +INCLUDE_ASM("asm/nonmatchings/libgs4", func_80053328); + +INCLUDE_ASM("asm/nonmatchings/libgs4", GS_107_OBJ_464); + +INCLUDE_ASM("asm/nonmatchings/libgs4", GS_107_OBJ_4B8); + +INCLUDE_ASM("asm/nonmatchings/libgs4", func_80053804); + +INCLUDE_ASM("asm/nonmatchings/libgs4", func_8005386C); diff --git a/src/libgs5.c b/src/libgs5.c new file mode 100644 index 000000000..d48e0e4e4 --- /dev/null +++ b/src/libgs5.c @@ -0,0 +1,9 @@ +#include "common.h" + +INCLUDE_ASM("asm/nonmatchings/libgs5", func_800538EC); + +INCLUDE_ASM("asm/nonmatchings/libgs5", GS_108_OBJ_6C); + +INCLUDE_ASM("asm/nonmatchings/libgs5", GS_108_OBJ_7C); + +INCLUDE_ASM("asm/nonmatchings/libgs5", GsSetFogParam); diff --git a/tools/make_libgs.sh b/tools/make_libgs.sh new file mode 100644 index 000000000..cad3ec2b0 --- /dev/null +++ b/tools/make_libgs.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Regenerate the curated libgs object dir (.run/obj40/libgs_used) for the Phase-7 FULL-libgs +# integration (Makefile LIBGS_ELF). SDK-derived + gitignored, like .run/obj40/libcd; the build is +# byte-identical WITH or WITHOUT it (the stubs reproduce the same bytes), so this only matters to a +# developer who wants the real SDK objects linked. Needs the LIBGS ELF objects from +# tools/psyq_build_libs.sh LIBGS (which needs tools/psyq/lib40/LIBGS.LIB from the DTL-S2002 redump). +# +# These are the 31 libgs objects BFM links, forming 6 contiguous blocks across vram +# 0x80051804-0x80057928, separated by 5 non-libgs gaps (80 / 48 / 1536[GS_001] / 48 / 304 B): +# block 1: 2D_COM0 (0x80051804) +# block 2: 2D_LIN0 2D_COM1 2D_SP0 (0x80051DC8) +# block 3: 2D_SP1 (0x80052460) +# block 4: GS_002 GS_003 MATRIX GS_103 GS_104 GS_105 GS_106 GS_107 (0x80052BDC) +# block 5: GS_108 GS_109 (0x800538EC) +# block 6: GS_110 GS_119 GS_120 GS_121 GS_122 GS_123 GS_127 GS_131 +# GS_133 GS_134 GS_135 GS_137 PRESET PRESET3 OBJT OBJT3 (0x80053AF8) +# GS_001 (the 1536 B gap before block 4) is EXCLUDED — psyq-obj-parser packs its scattered PSD* +# commons into .bss referenced via .bss+offset and 35 words differ at the global-zeroing run +# (cookbook §9.1 hard case); it stays an asm stub (gsgap3) and reproduces its bytes. +# The list is DISAMBIGUATED (session E): psyq_identify reports byte-identical-.text aliases — keep +# GS_131 (not RVWUNIT), GS_137 (not RVWLUNIT), PRESET3 (not PRESET2), OBJT3 (not OBJT2); the losers +# have a spurious .data/.rdata mismatch and do NOT link byte-exact. GS_106 is an 8-instruction object +# whose .text pattern recurs in game code, so it anchors uniquely only within the libgs window +# (psyq_identify/psyq_integrate take the 0x80051804..0x80057928 range — see make extract). +# psyq_link_region --verify confirms the chosen 31 are per-object .text byte-identical. +set -euo pipefail +cd "$(dirname "$0")/.." +LIBGS=.run/obj40/libgs +DST=.run/obj40/libgs_used +OBJS=(2D_COM0 2D_LIN0 2D_COM1 2D_SP0 2D_SP1 \ + GS_002 GS_003 MATRIX GS_103 GS_104 GS_105 GS_106 GS_107 GS_108 GS_109 \ + GS_110 GS_119 GS_120 GS_121 GS_122 GS_123 GS_127 GS_131 GS_133 GS_134 GS_135 GS_137 \ + PRESET PRESET3 OBJT OBJT3) + +[ -d "$LIBGS" ] || tools/psyq_build_libs.sh LIBGS +rm -rf "$DST"; mkdir -p "$DST" +for o in "${OBJS[@]}"; do + [ -f "$LIBGS/$o.o" ] || { echo "ERROR: $LIBGS/$o.o missing (run tools/psyq_build_libs.sh LIBGS)"; exit 1; } + cp "$LIBGS/$o.o" "$DST/$o.o" +done +echo "libgs_used: ${#OBJS[@]} objects -> $DST" +# Optional self-check: verify the whole region links per-object .text byte-identical. +if [ "${1:-}" = "--verify" ]; then + .venv/bin/python tools/psyq_link_region.py "$DST" 0x80051804 0x80057928 +fi diff --git a/tools/make_libgs_block6.sh b/tools/make_libgs_block6.sh deleted file mode 100644 index 7cd1f529c..000000000 --- a/tools/make_libgs_block6.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -# Regenerate the curated libgs block-6 object dir (.run/obj40/libgs_block6) for the Phase-7 Task-#9 -# integration (Makefile LIBGS6_ELF). SDK-derived + gitignored, like .run/obj40/libcd; the build is -# byte-identical WITH or WITHOUT it (the stubs reproduce the same bytes), so this only matters to a -# developer who wants the real SDK objects linked. Needs the LIBGS ELF objects from -# tools/psyq_build_libs.sh LIBGS (which needs tools/psyq/lib40/LIBGS.LIB from the DTL-S2002 redump). -# -# Block 6 = the 6th of the 6 contiguous libgs blocks (vram 0x80053AF8-0x80057928), 16 objects. The -# list is DISAMBIGUATED (session E): psyq_identify reports 20 candidates because four objects have a -# byte-identical-.text alias — keep GS_131 (not RVWUNIT), GS_137 (not RVWLUNIT), PRESET3 (not -# PRESET2), OBJT3 (not OBJT2); the losers have a spurious .data/.rdata mismatch and do NOT link -# byte-exact. psyq_link_region verifies the chosen 16 are per-object .text byte-identical. -set -euo pipefail -cd "$(dirname "$0")/.." -LIBGS=.run/obj40/libgs -DST=.run/obj40/libgs_block6 -OBJS=(GS_110 GS_119 GS_120 GS_121 GS_122 GS_123 GS_127 GS_131 GS_133 GS_134 GS_135 GS_137 \ - PRESET PRESET3 OBJT OBJT3) - -[ -d "$LIBGS" ] || tools/psyq_build_libs.sh LIBGS -rm -rf "$DST"; mkdir -p "$DST" -for o in "${OBJS[@]}"; do - [ -f "$LIBGS/$o.o" ] || { echo "ERROR: $LIBGS/$o.o missing (run tools/psyq_build_libs.sh LIBGS)"; exit 1; } - cp "$LIBGS/$o.o" "$DST/$o.o" -done -echo "libgs_block6: ${#OBJS[@]} objects -> $DST" -# Optional self-check: verify the region links per-object .text byte-identical. -if [ "${1:-}" = "--verify" ]; then - .venv/bin/python tools/psyq_link_region.py "$DST" 0x80053AF8 0x80057928 -fi diff --git a/tools/psyq_integrate.py b/tools/psyq_integrate.py index 34c5a2ce8..26eb2d754 100644 --- a/tools/psyq_integrate.py +++ b/tools/psyq_integrate.py @@ -16,7 +16,14 @@ for the library's text region(s), this: Stub<->block mapping is by vram order: the i-th stub (in vram order) gets the i-th contiguous object block. Non-library gaps between blocks keep their own stub subsegment untouched. -Usage: psyq_integrate.py [,,...] +The optional [text_lo text_hi] window narrows the psyq_identify placement scan to the library's +text region. This is REQUIRED when an object's `.text` pattern is too short to anchor uniquely over +the whole EXE but is unique within the library region (e.g. libgs GS_106, an 8-instruction object +whose pattern recurs in game code — ambiguous in the default 0x80010000..0x800629DC window, unique +in 0x80051804..0x80057928). Without it that object drops out of the placement map and its block +splits, breaking the block<->stub count. + +Usage: psyq_integrate.py [,,...] [text_lo text_hi] """ import glob, os, re, subprocess, sys, tempfile sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) @@ -60,9 +67,9 @@ def trial_undefined(ld_path, extra_syms=None): | set(re.findall(r"[`']([^`']+)' referenced in section .*? defined in discarded section", err))) -def integrate(elf_dir, ld_path, objdir, syms_path, stubs): +def integrate(elf_dir, ld_path, objdir, syms_path, stubs, lo=None, hi=None): exe = open(EXE, "rb").read() - order = sorted(placement(elf_dir, None, None).items(), key=lambda kv: kv[1][0]) + order = sorted(placement(elf_dir, lo, hi).items(), key=lambda kv: kv[1][0]) recovered, weaken_by, bases_by = {}, {}, {} for name, (vram, _) in order: bases, weaken, sym_addr = classify(os.path.join(elf_dir, name), vram, exe) @@ -163,9 +170,10 @@ def integrate(elf_dir, ld_path, objdir, syms_path, stubs): def main(): - if len(sys.argv) != 6: + if len(sys.argv) not in (6, 8): sys.exit(__doc__) - integrate(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5].split(",")) + lo, hi = (sys.argv[6], sys.argv[7]) if len(sys.argv) == 8 else (None, None) + integrate(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5].split(","), lo, hi) if __name__ == "__main__":