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
This commit is contained in:
Drew T
2026-06-15 13:44:32 -06:00
parent f91a67a6fe
commit 49db81d8ae
24 changed files with 1147 additions and 214 deletions
+1 -1
View File
@@ -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.
+12 -11
View File
@@ -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 $@"
+8 -1
View File
@@ -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`.
+19 -6
View File
@@ -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+),
+10 -10
View File
@@ -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 |
+27 -1
View File
@@ -409,7 +409,33 @@ single-library path never hit. All fixed in `tools/psyq_integrate.py`; reuse for
`psyq_identify <full-lib-dir> <lo> <hi>` 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 <used-dir>`
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 <stubs> <lo> <hi>` 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.
---
+9 -9
View File
@@ -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)
+151
View File
@@ -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<N>.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.
@@ -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).
+688
View File
@@ -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);
-136
View File
@@ -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);
+3
View File
@@ -0,0 +1,3 @@
#include "common.h"
INCLUDE_ASM("asm/nonmatchings/gsgap1", ApplyMatrix);
+3
View File
@@ -0,0 +1,3 @@
#include "common.h"
INCLUDE_ASM("asm/nonmatchings/gsgap2", func_80052430);
+13
View File
@@ -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);
+3
View File
@@ -0,0 +1,3 @@
#include "common.h"
INCLUDE_ASM("asm/nonmatchings/gsgap4", func_800538BC);
+33
View File
@@ -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);
+17
View File
@@ -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);
+15
View File
@@ -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);
+3
View File
@@ -0,0 +1,3 @@
#include "common.h"
INCLUDE_ASM("asm/nonmatchings/libgs3", func_80052460);
+47
View File
@@ -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);
+9
View File
@@ -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);
+45
View File
@@ -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
-30
View File
@@ -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
+13 -5
View File
@@ -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 <elf_dir> <ld_path> <objdir> <syms_ld> <stub1>[,<stub2>,...]
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 <elf_dir> <ld_path> <objdir> <syms_ld> <stub1>[,<stub2>,...] [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__":