mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 13:33:34 -04:00
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:
@@ -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.
|
||||
|
||||
@@ -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 $@"
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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
@@ -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 |
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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).
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "common.h"
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/gsgap1", ApplyMatrix);
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "common.h"
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/gsgap2", func_80052430);
|
||||
@@ -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);
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "common.h"
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/gsgap4", func_800538BC);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "common.h"
|
||||
|
||||
INCLUDE_ASM("asm/nonmatchings/libgs3", func_80052460);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -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
@@ -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__":
|
||||
|
||||
Reference in New Issue
Block a user