From 0bcf5f1777377a35353d8ac1e9b18f2f6ccf3146 Mon Sep 17 00:00:00 2001 From: James Dansereau <106631954+jkdansereau@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:47:04 -0400 Subject: [PATCH] D86+D87: player gait-model pointer truncation + attract-mode demo endian fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D86 (resolved): modelInitRwData crashed on a bad ModelNode pointer during room-triggered gameplay init. Root cause: initplayergaitobject.c did `player_gait_object_header.RootNode = (int)&player_gait_hdr;` -- a same-width pointer<->int round trip that's a no-op on N64 but truncates a real 64-bit pointer on PC, dropping the executable's load-base high bits. Fixed under #ifdef PORT by assigning the real pointer directly. Only exercised once real gameplay starts (past the intro), which is why it was never hit before D69 let the game reach stage load. D87 (resolved): once D86 stopped blocking progress, an idle (no-input) run reaches the front end's genuine attract-mode demo playback and crashes in ramrom_replay_handler. Root cause: ramromfilestructure is a real ROM-compiled asset (big-endian) loaded via romCopyAligned's raw byte copy (D66) with no byteswap, so every multi-byte field reads scrambled and drives wild pointer arithmetic in the block-buffer consumers. Fixed with a #ifdef PORT ramromFixupEndian() called once after the load (D54 cseq-fixup pattern). Both root-caused with new env-gated TEMP probes (GE_D86, GE_D87), left in place per the existing convention. D88 (per-level "Usetup*Z" stage-setup file: zero PC porting done, struct-width + endian bugs stacked) is the new next blocker, root-caused but intentionally not patched inline -- it's D69-scale format-conversion work, logged as an open finding instead. Also documents a fast, deterministic BUNKER1 repro (-level_09 command-line arg, bypasses the front end/attract-mode entirely) and a correction to the gdb guidance: attach mode (vs. launch-only) works well for watching a global's write on an already-running process. Docs: docs/PCPortResearch.md §F (D86/D87 resolved, D88 new), docs/HANDOFF.md (session K), AGENTS.md summary line. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01C3xib6jjMV8o2iE4JfixAo --- AGENTS.md | 2 +- docs/HANDOFF.md | 229 ++++++++++++++++++++------------ docs/PCPortResearch.md | 149 +++++++++++++++++---- src/game/initplayergaitobject.c | 12 ++ src/game/model.c | 7 + src/game/objecthandler_2.c | 12 +- src/game/ramromreplay.c | 75 +++++++++++ 7 files changed, 370 insertions(+), 116 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4afa98e5..88ccba16 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,6 +48,6 @@ Run `/linkcheck` for this sweep. Record new findings in `docs/PCPortResearch.md` - **Compile+link milestone:** all ~235 TUs compile and `ge007.x86_64.exe` links clean (`ninja ge007 -k 0`, 236/236). Findings D7–D17 in `docs/PCPortResearch.md` §11; asset stubs in `port/src/assetstubs.c`, fast3d no-ops in `port/src/gfxstub.c` (delete when real fast3d lands). - **Phase 1 (boot to window):** done — ROM loads/validates/maps at cart base 0x10000000 (`romdata.c`), SDL2 window + GL clear loop (`video.c`), absolute asset symbols from `scripts/gen_romassets.py` → `port/src/romassets_.s` (findings D18–D23). - **Phase 1.5 (boot to first frame):** DONE — boot chain complete AND real frames render (`[NOTE] frame N rendered in … us`). D34–D42 closed the blockers on top of D31–D33: ANIM_DATA lvalues, music seq-table ABI+endianness, music-heap sizing, libaudio bank-tree re-layout, implicit-declaration prototype shim (D38), Globalimagetable rebasing + true segment size (D39), ModelHitEntry pool sizing (D40), cross-thread GL context release (D41), rsp task-settings toggle (D42). ROM dump verified against the No-Intro good dump (byte-identical, header CRCs re-checked — `tools_pc/romverify.c`). -- **Phase 2 (rendering):** IN PROGRESS — fast3d integrated + real frames render. **Plan B EXECUTED (D50, committed):** all 512 NTSC model files converted offline by `tools_pc/d43_emit.py` → `data/pcmodels-ntsc-final/pcmodels.bin` + manifest.csv (single concatenated RZ image; regenerate per region with the script — sidecars are gitignored), served through the existing load path via a port-layer table patch (`port/src/pcmodels.c`, romdata cart-extension, one-shot `pcmodelsPatchTable()` + poolRemaining reset hook in `load_object_fill_header`); runtime C fixups for language-bank BE offset tables + font re-layout (D50.3/D50.4); legal-screen UB seed (D50.5); **D50.6 fixed the first model-render crash** — `texCopyGdls` copied only w0 of each 16-byte Gfx slot on PC (the union's trailing `long long` is 8 bytes), leaving stale mempool w1s in the compacted GDLs; byte-proven against PlegalpageZ, fixed with a full-slot copy under #ifdef PORT. Model GDLs now execute to completion (PlegalpageZ sub-DLs). **D51 RESOLVED** (font `pixeldata` fixup wrote the pointer at the wrong offsets — frame-5 `import_texture_i8` crash gone; GE opcode space decoded along the way, G_TEXRECT=0xE4/0xE5 non-F3DEX numbering), **D52 RESOLVED** (`osGetCount` now advances at the N64 RSP counter rate ≈ 46.5525 ticks/µs — fixed the non-deterministic post-frame-2 hang), **D53 RESOLVED** (model RW-data pool addressing: D53.1 `Model.datas` word-stride → `u32 *` under PORT + casts; D53.2 ModelSlot/AnimModelSlot re-laid on top of the PC struct Model — in-use@0x10 / pool@0x20, slot size = sizeof(Model) = 0xE8 — plus PORT-guarded heap-fallback bumps in model.c), **D54 RESOLVED** (cseq ALCMidiHdr BE header: port-layer `romdataFixupCseq` bswap32 of the first 17 u32s, called from music.c after each decompress), **D54b RESOLVED** (synth param slots sized for ALStartParamAlt = 0x28 in synthesizer.c alSynInit under PORT), **D55 RESOLVED** (RLE folder-menu background BE w/h header: bswap32 of the first word at `(u32 *)&unknown2` at cart-map time in romdataInit, guarded). Build GREEN; game boots, plays intro music, renders through logo/gun-barrel, instantiates Brosnan, and executes the gun-barrel hole DL clean to ENDDL. **D56 RESOLVED** (watch Model/RW-pool raw offsets: field-name Model access + static 0xC8-byte pool under PORT in bondview2.c), **D57 RESOLVED** (pointer-grown rwdata records overflowed N64-sized spare pools — Brosnan PC numRecords 153 > 140: capacities grown in initunk_005520.c + dynamic slot+pool fallback in model.c), **D58 RESOLVED** (gun-barrel DL: vertex-pointer K0 idiom rebuilt via `OS_K0_TO_PHYSICAL(…) | 0x80000000` in title.c + DL reserve 0x100→0x200 because `sizeof(Gfx)` is 16 on PC). **D59–D68 RESOLVED (committed):** the "GL DLL" crash was D64's unbounded blood-RLE write (sentinel section-adjacency lost on PC) clobbering the gun-barrel sub-DL; then D60–D62 (DMA target validation, OSMesgQueue layout), D65/D65b (HEADS/BODIES enum signed sentinels under PORT), D66 (romCopyAligned + ramrom replay 64-bit pointer width), D67 (struct image_entry N64 layout), **D68 (Globalimagetable BE→LE fixup** — `port/src/gimgfixup.c` bswaps IMAGESEG Gfx w1 words + sImageTableEntry.index after texReset's romCopy; compiled globalDL shadows synced for explosion.c). The game now renders the **entire intro** (logo → gun barrel w/ Brosnan → cast; ~frame 2100, ≈2 min wall-clock at the current ~20 fps clean-run rate). **D69 RESOLVED (committed):** `load_bg_file` no longer faults on stage load. BG-file headers/records are N64 big-endian segment-0x0F offsets with internal self-relative offsets; fixed via offline sidecar conversion (`tools_pc/d69_emit.py` → `data/pccg-/pccg.bin` + manifest, same Plan-B pattern as D43/pcmodels, port-layer wiring in `port/src/pccg.c`) plus four ABI layout fixes surfaced along the way: D78 (`StandTile` id/room bitfield packing), D79 (`bg_room_data` pointer-width), D83 (`StandTileHeaderMid`/`Tail` bitfield packing — same class as D78, caused an infinite loop in `stanBuildRoomData` until fixed), D84 (hand-inlined `BG_SEG_TO_PTR`-style folds in room-DL loaders needed the same `(u32)`-cast 32-bit wrap the macro already has). **D85/D86 OPEN (found during D69 verification, next up):** D85 — room primary/secondary DL binaries decompress correctly but `texLoadFromGdl` (the already-working model-GDL runtime converter) produces garbage from their content, not yet root-caused; safety-netted (bounds-checked in `bgBuildRoomVtxBounds` so it can't segfault, but geometry is wrong/empty). D86 — a **new** crash in `modelInitRwData` during room-triggered model instantiation, likely unrelated to bg/stan, not yet root-caused. See `docs/HANDOFF.md` and `docs/PCPortResearch.md` §F/D85-D86 for the recommended next steps (D86 first, then D85). **D70–D74 (intro-logo pixels, RESOLVED):** D70 env-gated PPM frame capture (`GE_PCDUMP` → `./ppm/`, gitignored — kept as permanent dev tooling); **D71** C-array texture sources (the four rarewarelogo.c RGBA16 images) byte-swapped on LE PC → port-layer per-source bswap in `import_texture`; **D72** the UV path (GE always uses authored `tc[]` UVs; `lookat_enabled` defaults false); **D73 = root cause of old D72.3** — sinf/cosf `du` double constants are big-endian word pairs, garbage on LE PC → guMtxF2L emitted −32768 for every sin/cos entry → logo triangles projected off-screen; fixed with the PORT-only `DVAL()` macro in `src/libultra/gu/guint.h` (sinf.c/cosf.c `.d` reads replaced; N64 build untouched); **D74** texture import fallback no longer truncates valid gDPLoadBlock data (was dropping letter mip chains + reducing D_02005FF0 to 32×3), `TextureCacheKey` gained `size_bytes`, and the VBO path wraps UVs by tile size for WRAP sub-tiles (N64 semantics). The Rareware logo now renders its four gold letters on the dark-blue plate (PPM-verified ~frame 555; final pixel-perfect check vs N64 footage optional). D69 is resolved; D85/D86 are the current blockers to a rendered stage frame (see above). Committed through D86. Recurring sub-task: ABI/layout fixes per the D32 procedure (§H) as more asset types are first touched (PD's `preprocess/` module already solves most of them; prefer the offline pattern for format work — D48; full-struct Gfx copies on PC — D50.6; audit struct type-puns after pointer-width changes — D53.2; raw byte offsets into pointer-containing structs shift on PC — D56; N64-sized reservations must be re-checked against 16-byte Gfx/Vtx widths — D58). **Open this session (tracked in §F as D75–D77, lower priority than D69):** D75 3D rendering still wrong (Nintendo logo misplacement + intro player models absent); D76 2D disclaimer screen only partially drawn; D77 no audible music on PC. Prioritization: rendering over audio unless a shared root cause is found (Q1). +- **Phase 2 (rendering):** IN PROGRESS — fast3d integrated + real frames render. **Plan B EXECUTED (D50, committed):** all 512 NTSC model files converted offline by `tools_pc/d43_emit.py` → `data/pcmodels-ntsc-final/pcmodels.bin` + manifest.csv (single concatenated RZ image; regenerate per region with the script — sidecars are gitignored), served through the existing load path via a port-layer table patch (`port/src/pcmodels.c`, romdata cart-extension, one-shot `pcmodelsPatchTable()` + poolRemaining reset hook in `load_object_fill_header`); runtime C fixups for language-bank BE offset tables + font re-layout (D50.3/D50.4); legal-screen UB seed (D50.5); **D50.6 fixed the first model-render crash** — `texCopyGdls` copied only w0 of each 16-byte Gfx slot on PC (the union's trailing `long long` is 8 bytes), leaving stale mempool w1s in the compacted GDLs; byte-proven against PlegalpageZ, fixed with a full-slot copy under #ifdef PORT. Model GDLs now execute to completion (PlegalpageZ sub-DLs). **D51 RESOLVED** (font `pixeldata` fixup wrote the pointer at the wrong offsets — frame-5 `import_texture_i8` crash gone; GE opcode space decoded along the way, G_TEXRECT=0xE4/0xE5 non-F3DEX numbering), **D52 RESOLVED** (`osGetCount` now advances at the N64 RSP counter rate ≈ 46.5525 ticks/µs — fixed the non-deterministic post-frame-2 hang), **D53 RESOLVED** (model RW-data pool addressing: D53.1 `Model.datas` word-stride → `u32 *` under PORT + casts; D53.2 ModelSlot/AnimModelSlot re-laid on top of the PC struct Model — in-use@0x10 / pool@0x20, slot size = sizeof(Model) = 0xE8 — plus PORT-guarded heap-fallback bumps in model.c), **D54 RESOLVED** (cseq ALCMidiHdr BE header: port-layer `romdataFixupCseq` bswap32 of the first 17 u32s, called from music.c after each decompress), **D54b RESOLVED** (synth param slots sized for ALStartParamAlt = 0x28 in synthesizer.c alSynInit under PORT), **D55 RESOLVED** (RLE folder-menu background BE w/h header: bswap32 of the first word at `(u32 *)&unknown2` at cart-map time in romdataInit, guarded). Build GREEN; game boots, plays intro music, renders through logo/gun-barrel, instantiates Brosnan, and executes the gun-barrel hole DL clean to ENDDL. **D56 RESOLVED** (watch Model/RW-pool raw offsets: field-name Model access + static 0xC8-byte pool under PORT in bondview2.c), **D57 RESOLVED** (pointer-grown rwdata records overflowed N64-sized spare pools — Brosnan PC numRecords 153 > 140: capacities grown in initunk_005520.c + dynamic slot+pool fallback in model.c), **D58 RESOLVED** (gun-barrel DL: vertex-pointer K0 idiom rebuilt via `OS_K0_TO_PHYSICAL(…) | 0x80000000` in title.c + DL reserve 0x100→0x200 because `sizeof(Gfx)` is 16 on PC). **D59–D68 RESOLVED (committed):** the "GL DLL" crash was D64's unbounded blood-RLE write (sentinel section-adjacency lost on PC) clobbering the gun-barrel sub-DL; then D60–D62 (DMA target validation, OSMesgQueue layout), D65/D65b (HEADS/BODIES enum signed sentinels under PORT), D66 (romCopyAligned + ramrom replay 64-bit pointer width), D67 (struct image_entry N64 layout), **D68 (Globalimagetable BE→LE fixup** — `port/src/gimgfixup.c` bswaps IMAGESEG Gfx w1 words + sImageTableEntry.index after texReset's romCopy; compiled globalDL shadows synced for explosion.c). The game now renders the **entire intro** (logo → gun barrel w/ Brosnan → cast; ~frame 2100, ≈2 min wall-clock at the current ~20 fps clean-run rate). **D69 RESOLVED (committed):** `load_bg_file` no longer faults on stage load. BG-file headers/records are N64 big-endian segment-0x0F offsets with internal self-relative offsets; fixed via offline sidecar conversion (`tools_pc/d69_emit.py` → `data/pccg-/pccg.bin` + manifest, same Plan-B pattern as D43/pcmodels, port-layer wiring in `port/src/pccg.c`) plus four ABI layout fixes surfaced along the way: D78 (`StandTile` id/room bitfield packing), D79 (`bg_room_data` pointer-width), D83 (`StandTileHeaderMid`/`Tail` bitfield packing — same class as D78, caused an infinite loop in `stanBuildRoomData` until fixed), D84 (hand-inlined `BG_SEG_TO_PTR`-style folds in room-DL loaders needed the same `(u32)`-cast 32-bit wrap the macro already has). **D85 OPEN, D86/D87 RESOLVED, D88 OPEN (current blocker) — found during/after D69 verification:** D85 — room primary/secondary DL binaries decompress correctly but `texLoadFromGdl` (the already-working model-GDL runtime converter) produces garbage from their content, not yet root-caused; safety-netted (bounds-checked in `bgBuildRoomVtxBounds` so it can't segfault, but geometry is wrong/empty). D86 (RESOLVED) — `modelInitRwData` crash was a single `(int)`-truncated pointer in `initplayergaitobject.c`'s embedded player gait-model init; fixed under `#ifdef PORT`. D87 (RESOLVED) — the front end's genuine attract-mode demo playback crashed on an unswapped big-endian `ramromfilestructure`; fixed with a `#ifdef PORT` byteswap fixup in `ramromreplay.c` (D54-cseq pattern). D88 (OPEN, current blocker) — the per-level `Usetup*Z` "stage setup" file (`prop.c`/`struct stagesetup`) has **zero** PC porting work: it's raw ROM bytes read directly through a pointer-widened, still-big-endian C struct (D79-class width bug + D87-class endian bug, stacked); crashes in `proplvreset2` (`prop.c:1306`), reproducible in seconds via `-level_09` (bypasses the front end, no attract-mode wait). Needs D69-scale format-conversion work (byte-accurate spec + offline converter or careful runtime fixup), not a quick inline patch — logged as an open finding per AGENTS.md's "stop and write up" guidance. See `docs/HANDOFF.md` and `docs/PCPortResearch.md` §F/D86-D88 for full detail and next steps. **D70–D74 (intro-logo pixels, RESOLVED):** D70 env-gated PPM frame capture (`GE_PCDUMP` → `./ppm/`, gitignored — kept as permanent dev tooling); **D71** C-array texture sources (the four rarewarelogo.c RGBA16 images) byte-swapped on LE PC → port-layer per-source bswap in `import_texture`; **D72** the UV path (GE always uses authored `tc[]` UVs; `lookat_enabled` defaults false); **D73 = root cause of old D72.3** — sinf/cosf `du` double constants are big-endian word pairs, garbage on LE PC → guMtxF2L emitted −32768 for every sin/cos entry → logo triangles projected off-screen; fixed with the PORT-only `DVAL()` macro in `src/libultra/gu/guint.h` (sinf.c/cosf.c `.d` reads replaced; N64 build untouched); **D74** texture import fallback no longer truncates valid gDPLoadBlock data (was dropping letter mip chains + reducing D_02005FF0 to 32×3), `TextureCacheKey` gained `size_bytes`, and the VBO path wraps UVs by tile size for WRAP sub-tiles (N64 semantics). The Rareware logo now renders its four gold letters on the dark-blue plate (PPM-verified ~frame 555; final pixel-perfect check vs N64 footage optional). D69 is resolved; D88 is the current blocker to a rendered stage frame, with D85 next after it (see above). Committed through D87. Recurring sub-task: ABI/layout fixes per the D32 procedure (§H) as more asset types are first touched (PD's `preprocess/` module already solves most of them; prefer the offline pattern for format work — D48; full-struct Gfx copies on PC — D50.6; audit struct type-puns after pointer-width changes — D53.2; raw byte offsets into pointer-containing structs shift on PC — D56; N64-sized reservations must be re-checked against 16-byte Gfx/Vtx widths — D58). **Open this session (tracked in §F as D75–D77, lower priority than D69):** D75 3D rendering still wrong (Nintendo logo misplacement + intro player models absent); D76 2D disclaimer screen only partially drawn; D77 no audible music on PC. Prioritization: rendering over audio unless a shared root cause is found (Q1). - **Phase 3 (audio + input):** libaudio → SDL — largely copy-and-adapt from PD (`mixer.c` 722 lines vs our 31-line stub; `input.c` SDL_GameController backend; §2.4); decide the ASP strategy (C2: `aspMain*` dummies in `ucode.c`). - **Phase 4 (saves + polish):** file-backed EEPROM — build on PD's `fs.c` (file-backed save dir); PFS/motor already no-op stubs. diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 33b115ac..3f69903d 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -1,115 +1,168 @@ -# Handoff brief — GoldenEye 007 PC port (Phase 2: Session J — D69 resolved, D85/D86 open) +# Handoff brief — GoldenEye 007 PC port (Phase 2: Session K — D86/D87 +# resolved, D88 is the new hard blocker) _Paste-ready brief. Authoritative context: `AGENTS.md`, -`docs/PCPortResearch.md` §F (D69, D78-D86), `docs/BRIEF-D69-stage-load.md`._ +`docs/PCPortResearch.md` §F (D69, D78-D88), `docs/BRIEF-D69-stage-load.md`._ ## Where things stand -**D69 (the "stage load faults" milestone blocker) is RESOLVED and -verified.** `load_bg_file` (bg.c) no longer faults on BUNKER1 (or any of -the 20+ other levels the offline converter covers). This took five -findings, all committed: +**D69 (the original "stage load faults" milestone blocker) remains +RESOLVED.** `load_bg_file` (bg.c) doesn't fault on BUNKER1. Since then, +two more crashes further down the load chain were found and fixed this +session (D86, D87), and a third — the current blocker — was root-caused +but not fixed (D88). -- **D78** `StandTile.id`/`.room` bitfield packing (MIPS-BE vs x86-LE) — - `#ifdef PORT` layout fix in `src/bondtypes.h`. -- **D79** `bg_room_data`'s 3 offset fields declared `void *` (never - dereferenced, but grows the record on x86-64 and breaks array indexing) - — `#ifdef PORT` `u32` fix in `src/game/bg.h`. -- **D80/D81/D82** the bg `.seg` + `Tbg_*_stanZ` format spec, the offline - converter (`tools_pc/d69_emit.py`), and the port-layer sidecar wiring - (`port/src/pccg.c`, `port/include/pccg.h`, wired into `romdata.c` / - `libultra.c` / `objecthandler_2.c`, same Plan-B pattern as - `pcmodels.c`/D50). -- **D83** `StandTileHeaderMid`/`StandTileHeaderTail` bitfield packing - (same MIPS-BE/x86-LE class as D78, found because it caused an actual - infinite loop in `stanBuildRoomData` once D78-D82 got that far) — - `#ifdef PORT` reversed-field-order fix in `src/bondtypes.h`. -- **D84** three hand-inlined `BG_SEG_TO_PTR`-style segment folds in - `bgLoadRoomVtxData`/`bgLoadRoomPrimaryGdl`/`bgLoadRoomSecondaryGdl` did - the `+0xf1000000` fold as real 64-bit pointer arithmetic (doesn't wrap - at 32 bits the way the macro's `(u32)` cast does) — `#ifdef PORT` fix, - same fold as `BG_SEG_TO_PTR`, in `src/game/bg.c`. +**D86 RESOLVED.** `modelInitRwData` crash (`model.c:6174`) was a single +truncating pointer cast in the player's embedded gait/arm model: +`src/game/initplayergaitobject.c:5` did +`player_gait_object_header.RootNode = (int)&player_gait_hdr;` — a +same-width no-op on N64 that truncates+zero-extends a real 64-bit pointer +on PC. Fixed with a narrow `#ifdef PORT` branch assigning the pointer +directly. Root-caused via a new node-walk trace (`GE_D86=1`, left in +place, gated in `model.c`/`objecthandler_2.c`). -**Regenerate the sidecar** with `python tools_pc/d69_emit.py ntsc-final` -(writes `data/pccg-ntsc-final/pccg.bin` + `manifest.csv`, gitignored like -`pcmodels-*`) before running — it is not checked into git. PAL/JPN are -implemented in the converter but **not regenerated or verified** (those -ROMs aren't present in this dev environment). +**D87 RESOLVED.** Once D86 stopped blocking progress, an idle (no-input) +run eventually triggers the front-end's genuine attract-mode demo +playback (`select_ramrom_to_play()` picks a random compiled-in demo — +this is shipped retail behavior, not a debug feature) and crashed in +`ramrom_replay_handler` (`ramromreplay.c`). Root cause: `ramromfilestructure` +is a real ROM-compiled asset (big-endian, like everything else) loaded via +`romCopyAligned()` — a raw byte copy by design (D66) — with **no +byteswap**, so every multi-byte field read back scrambled (e.g. `size_cmds` +2 → 33554432) and drove wild pointer arithmetic. Fixed with a `#ifdef PORT` +`ramromFixupEndian()` called once after the load (same pattern as the D54 +cseq fixup). Not BUNKER1-specific — attract mode picks any of 7 demo +locations at random, so don't rely on it for BUNKER1-specific testing (see +`-level_09` below). -## What's still open (found *during* D69 verification, not the original bug) +**D88 OPEN — root-caused, this is the current hard blocker.** Launch with +`-level_09` (NTSC `LEVELID_BUNKER1 = 9`; `boss.c:199-339` decodes +`-level_XX` into `g_StageNum`, bypassing the front end/attract-mode +entirely — fast, deterministic BUNKER1 repro, crashes in well under a +minute instead of waiting ~2 min for attract mode to maybe pick Bunker). +Crash: `proplvreset2` (`prop.c:1306`) segfaults reading +`g_CurrentSetup.pathwaypoints[i1].padID`. Root cause: the per-level +`"UsetupZ"` file (`prop.c:1267`, `struct stagesetup` in +`bondtypes.h:4091`) is loaded as raw ROM bytes and has **zero PC porting +work done on it** — unlike bg/stan (D69/D80-82) and models (D43/D50). +Two compounding problems, not just one: +1. The 10 top-level fields (`pathwaypoints`/`waypointgroups`/`intro`/ + `propDefs`/`patrolpaths`/`ailists`/`pads`/`boundpads`/`padnames`/ + `boundpadnames`) are declared as real pointers in the live C struct, + so on PC they're 8 bytes each (an 80-byte header) instead of the + file's real 4-byte-each (40-byte) N64 layout — same class as D79 + (`bg_room_data` pointer growth). Field 0 reads fine; everything after + it is reading the wrong bytes entirely. +2. The 4 meaningful bytes each field *does* store are big-endian (the + code's own comment: "stores every internal reference as a byte offset + from the start of the file") and nothing byte-swaps them — same class + as D87. +No PORT/byteswap handling exists anywhere in `prop.c` (confirmed by grep). +**Not fixed this session** — this is D69-scale format-conversion work: a +byte-accurate spec of the whole `Usetup*Z` format (top-level header + +every nested sub-table: `waypoint`/`waygroup`/`PropDefHeaderRecord`/ +`PathRecord`/`AIListRecord`/`PadRecord`/`BoundPadRecord`/`pname`, each +likely with its own internal offsets not yet audited) plus either an +offline converter sidecar (preferred pattern per AGENTS.md, same shape as +`tools_pc/d69_emit.py`) or a careful runtime fixup pass that parses the +raw 40-byte N64-packed header by explicit byte offset, byte-swaps each +field, and writes results into the PC-widened struct. -**D85 (room DL binaries decode to garbage; safety-netted, not fixed).** -`bgLoadRoomPrimaryGdl`/`bgLoadRoomSecondaryGdl` decompress correctly (the -`11 72` RZ header and offset are right — D84 fixed that), but the content -`texLoadFromGdl` (the *same* runtime converter already used for model -GDLs) produces from the raw N64 bytes is not valid GBI opcodes. Not -root-caused. A `#ifdef PORT` bounds check was added in -`bgBuildRoomVtxBounds` so a garbage command stream can't segfault (it just -produces an empty/degenerate bounding box for that batch) — this is a -crash-safety net, not a geometry fix. **Next step:** figure out what -`texLoadFromGdl` actually does with room-specific GBI markers — room GDLs -reference `bgApplyDynamicCCRMLUT`/`ptrDynamic_CC_RM_LUT`/ -`DL_LUT_PRIMARY_ADDFOG`, suggesting a CC/RM-LUT-selection marker convention -models don't use — and verify `csize_primary_DL_binary`/ -`csize_secondary_DL_binary` sizing end-to-end (these come from subtracting -adjacent `bg_room_data` offsets; D79 argued the +delta cancels in the -subtraction, but that argument hasn't been independently re-verified with -a probe). +**Net effect vs. last session:** the game now runs substantially further +— all the way through room-streaming setup, past the intro's model +pipeline, and into per-level "Usetup" data — before hitting D88. The +"loads without fault" acceptance bar is **still not met**, but the +remaining blocker is now narrowly scoped and has a fast, deterministic +repro (`-level_09`, no attract-mode wait, no depending on which random +demo attract-mode picks). -**D86 (new crash, likely unrelated to bg/stan).** With D85's safety net -in, the game reaches a **new** segfault in `modelInitRwData` (model.c:6174, -called from `modelInit`/`animInit`), dereferencing a bad `ModelNode` -pointer. This is the existing D50-D58 model pipeline (stable for every -intro/cast model) hitting a case that's never been exercised before — -most likely a room-instantiated object (light fixture / prop / character) -whose load path differs, or a pcmodels-manifest edge case. **Not -root-caused.** Next step: identify which model name is being loaded at -the crash site (add a probe in `load_object_fill_header` or -`modelInit`/`animInit` printing the header/model name) and check whether -it's in `data/pcmodels-ntsc-final/manifest.csv`. +## Recommended next steps, in order -**Net effect:** the game now runs substantially further (through BUNKER1's -full room-streaming setup) before crashing, vs. the original instant fault -in `load_bg_file`. The "loads without fault" acceptance bar is **not yet -fully met** — D86 is the current hard blocker to reaching a rendered -frame. Recommended order: D86 first (probably a small, isolated fix, and -unblocks seeing whether D85's degenerate room geometry is otherwise -"plausible pixels" per the D69 bar), then D85, and only then worry about -D75 (pre-existing intro-3D findings, still open, still lower priority). +1. **D88.** Get a byte-level spec of `Usetup*Z` (start from BUNKER1's + file; `strResource` is built as `"U" + "sev" + "Z"`-style name in + `prop.c:1253-1265` — check `setup_text_pointers[LEVELID_BUNKER1]` for + the exact literal). Decide offline-converter vs. runtime-fixup (D69's + bg/stan work is the template for the former; D54's cseq fixup is the + template for the latter — given the struct-width mismatch on top of + the byteswap, a runtime fixup that manually walks the *raw* 40-byte + N64 header by hand (not through the live `stagesetup` struct) into a + freshly-populated `g_CurrentSetup` is probably simpler than a full + sidecar here, but verify against a raw ROM hex dump either way). +2. Once BUNKER1 loads past `proplvreset2`, re-check for further crashes + in the same vein (this session found 3 in a row — D86, D87, D88 — each + only reachable after the previous one was fixed; expect more). +3. Once BUNKER1 reaches a rendered frame with no fault, revisit **D85** + (room primary/secondary GDL binaries decode to garbage via + `texLoadFromGdl`) — use `GE_PCDUMP=":10"` + `tools_pc/pixcount.py` + to confirm non-black, non-degenerate content per the original D69 + acceptance bar. (This session's `GE_PCDUMP` captures around frame + 2100-2400 during attract-mode-driven "loading" were still on a HUD/menu + screen, not real 3D geometry — don't read too much into pixel counts + from before D88 is fixed.) +4. Do NOT touch D75/D76/D77 (parked, lower priority, unrelated). ## Debug tooling added this session (kept, env-gated, zero cost when unset) -- `GE_D69STAN=1` — `stanBuildRoomData` (stan.c): logs the tile walk - (address/room/mid/tail/pointCount) for the first 20 tiles and any tile - whose derived tile-size would be 0 (the D83 symptom). -- `GE_D69BB=1` — `bgBuildRoomVtxBounds`/`bgLoadRoomPrimaryGdl` (bg.c): logs - the room's expanded-GDL pointer, vertex-buffer pointer, first 8 decoded - Gfx slots, and the compressed/decompressed primary-DL bytes around the - fold (D84/D85 diagnostic). -- Pre-existing `GE_D69=1` (ob.c, `obLoadBGFileBytesAtOffset`) — **left in - place** (HANDOFF previously said to strip it once D69 is solved; D69's - *original* bug is solved, but D85/D86 are still open follow-ups in the - same overall load path, so it stays useful for now). +- `GE_D86=1` — node-walk trace in `modelInitRwData` (model.c) + a + load-identity probe in `load_object_fill_header` (objecthandler_2.c). + Resolved the D86 crash; left in place since the same + load_object_fill_header/modelInitRwData pipeline could surface new + edge cases as more of the game becomes reachable. +- `GE_D87=1` — block-setup trace in `iterate_ramrom_entries_handle_camera_out` + and consumer trace in `ramrom_replay_handler` (ramromreplay.c). Resolved + the D87 crash; left in place as it's a rarely-exercised path (attract + mode) worth having visibility into if it acts up again. +- Pre-existing `GE_D69STAN=1`, `GE_D69BB=1`, `GE_D69=1` — unchanged, still + useful for the bg/stan/D85 load path (see prior session's HANDOFF + entries, preserved in git history, for exactly what each logs). + +## New: fast, deterministic BUNKER1 repro (no attract-mode wait) + +Launch with `-level_09` as a program argument +(`./build-pc/ge007.x86_64.exe -level_09`) to skip the front end/attract +mode and load BUNKER1 directly — `boss.c:199-339` decodes `-level_XX` +(the two digit-chars are consumed as raw ASCII bytes: +`g_StageNum = tokenFindLevel[0]*10 + tokenFindLevel[1] - 0x210`; NTSC +`LEVELID_BUNKER1 = 9` → `"09"` since `'0'*10 + '9' - 0x210 = 9`). This is +now the preferred way to test BUNKER1-specific load/render work — it's +faster (crashes/completes in well under a minute vs. ~2+ min waiting on +attract mode) and deterministic (not dependent on which of 7 random demo +locations attract mode happens to pick). ## Environment / build - `export PATH="/c/msys64/mingw64/bin:$PATH" && ./build-pc.sh ntsc-final` - (~5 s). Build is GREEN with all D69 work included. + (~5 s). Build is GREEN with all D86/D87 work included. - Run from the **repo root**, not `build-pc/`. -- `GE_PCDUMP="2100-2700:10"` + `tools_pc/pixcount.py` for frame captures - once D86 is fixed and a frame actually renders past stage load. +- Regenerate sidecars if missing: `python tools_pc/d69_emit.py ntsc-final` + (bg/stan) and the D43/D50 model sidecar generator (pcmodels) — both + gitignored, not checked in. (Both were already present in this dev + environment this session.) +- `GE_PCDUMP="-:"` + `tools_pc/pixcount.py` for frame + captures once D88 is fixed and a frame actually renders real BUNKER1 + geometry. - Crash log: `ge007.crash.log` (repo root); symbolicate with `addr2line -e build-pc/ge007.x86_64.exe -f -C ` (image base `0x140000000`). -- gdb is launch-mode only and far too slow for timing-dependent bugs; - prefer env-gated probes + the crash log (see the D85/D86 probes above - for the pattern: `#if defined(PORT)` / `getenv("GE_...")`). +- gdb **launch** mode is too slow for timing-dependent bugs (unchanged + guidance). **New this session:** gdb **attach** mode + (`gdb -batch -x cmds.txt -p `, `` = 4th column of + `ps -p `) works well and is fast for "watch a global for a + legitimate vs. corrupted write" questions on an already-running, + not-yet-crashed process — see `docs/PCPortResearch.md` §F environment + reminders for the exact recipe used to root-cause D87. ## Non-negotiables (unchanged, see AGENTS.md) 1. N64 build files untouched. 2. Game logic unmodified except narrow, documented `#ifdef PORT` - ABI/layout exceptions (D78/D79/D83/D84 this session, all logged in - `PCPortResearch.md` §F). -3. Offline sidecar conversion preferred over runtime fixup — D80-D82 - follow the D43/pcmodels pattern exactly. + ABI/layout exceptions (D86/D87 this session, both logged in + `PCPortResearch.md` §F). D88 is explicitly **not** patched with a + quick inline hack — it needs the same disciplined + spec-then-convert/fixup treatment as D69, logged as an open finding + instead per AGENTS.md's "stop and write it up" guidance for anything + beyond a narrow, obviously-correct exception. +3. Offline sidecar conversion preferred over runtime fixup for whole + ROM-asset formats (D69/D80-82 pattern) — likely the right call for + D88 too, though a careful runtime fixup is also plausible; decide + after the byte-level spec work. diff --git a/docs/PCPortResearch.md b/docs/PCPortResearch.md index ed82e40f..f89ac587 100644 --- a/docs/PCPortResearch.md +++ b/docs/PCPortResearch.md @@ -2709,34 +2709,121 @@ room-specific opcodes (`bgApplyDynamicCCRMLUT`/`ptrDynamic_CC_RM_LUT`/ `DL_LUT_PRIMARY_ADDFOG` suggest room GDLs carry CC/RM-LUT-selection markers models don't use) and verify `csize_*_DL_binary` sizing end-to-end. -**D86 (OPEN — new downstream crash in `modelInitRwData` during -room-triggered model instantiation, found during D69 verification, likely -unrelated to the bg/stan conversion).** With D85's safety net in, -`bgBuildRoomVtxBounds` no longer segfaults, but the game reaches a **new** -crash a little further into room streaming: `modelInitRwData` -(`model.c:6174`) dereferences a bad `node` pointer, called from -`modelInit` ← `animInit` (`model.c:6323`/`6331`). This is the existing -model-loading pipeline (D50-D58, already working for every intro/cast -model) hitting an edge case that was never exercised before because -gameplay never progressed this far — most likely a room-instantiated -object (light fixture / prop / character) whose model load path differs -from the intro's, or a `RootNode`/pcmodels-table edge case not covered by -the existing 512-file conversion. **Not yet root-caused** — needs its own -investigation (probably starts with identifying which model name is being -loaded at the crash site and whether it's present in -`data/pcmodels-ntsc-final/manifest.csv`). Filed separately from D75 -(pre-existing 3D-render findings) since it's a hard crash, not a visual -defect; likely fixable independent of D75/D85. +**D86 (RESOLVED — `modelInitRwData` crash was a single truncating pointer +cast in the player's embedded gait/arm model init, unrelated to bg/stan).** +Root-caused with a new env-gated trace (`GE_D86=1`: node-walk trace in +`modelInitRwData` + a load-identity probe in `load_object_fill_header`, +both TEMP, left in place). The trace showed the crash node's low 32 bits +were `(header_ptr & 0xFFFFFFFF) + 0x1E0` with the high 32 bits zeroed, and +that this header was **never** loaded via `load_object_fill_header` (no +matching probe line) — pointing at a statically-embedded model, not a +dynamically-loaded one. `src/game/initplayergaitobject.c:5` does +`player_gait_object_header.RootNode = (int)&player_gait_hdr;` — a +same-width (32-bit) pointer→int→pointer round trip that's a no-op on N64, +but on PC `(int)` truncates the real 64-bit `&player_gait_hdr` to its low +32 bits, and the implicit int→pointer conversion back into `RootNode` +zero-extends it, dropping the executable's load-base high bits (module +maps at `0x140000000`, so the truncated pointer silently loses the +`0x1`). `init_player_gait_object()` runs once from `boss.c:236`, and +`player_gait_object_header` is only used once real gameplay starts +(`initBondDATAdefaults.c:99` `animInit`s the player's gait model) — never +exercised while the game only ever got as far as the intro/cast screens. +Fixed with a `#ifdef PORT` branch in `initplayergaitobject.c` that assigns +the real pointer directly (behavior-identical to the N64 assignment, +ABI-width fix only). Verified: BUNKER1 now loads past this point with a +clean, deterministic repro via `-level_09` (see D88). -**D69 status after D78-D86: the ORIGINAL blocker (`load_bg_file` faulting +**D87 (RESOLVED — attract-mode demo playback (`ramrom_replay_handler`) +crashed on a big-endian `ramromfilestructure` read with no byteswap).** +Found while re-verifying D86: an idle front-end run (no player input) +eventually calls `select_ramrom_to_play()` (`ramromreplay.c`), which picks +a random compiled-in demo blob from `ramrom_table[]` (`ramrom_Dam_1`, +`ramrom_BunkerI_1`, etc. — genuine shipped attract-mode assets, not a +debug-only feature; the debug-menu replay path, `DEB_REPLAYRAMROM`, is +structurally unreachable in this `ntsc-final`-equivalent build since +`DEBUGMENU` isn't defined — confirmed with a `gdb -p ` **attach** +hardware watchpoint on `is_ramrom_flag`, which resolved cleanly and +quickly this session; attach mode works fine for a non-timing-dependent +write, unlike the launch-mode-only guidance logged after the D56 session — +worth a retry next time attach seems useful). `replay_recorded_ramrom_at_address` +loads `ramromfilestructure` via `romCopyAligned()`, a raw byte copy (by +design, D66) from a real ROM-compiled asset — so, like every other +N64-compiled ROM asset, its multi-byte fields are big-endian, and nothing +byte-swaps them on read. A real `size_cmds` of 2 (BE bytes `00 00 00 02`) +read as native LE prints as `33554432` (`0x02000000`); that garbage then +drives the loop bound and pointer arithmetic in +`iterate_ramrom_entries_handle_camera_out`/`ramrom_replay_handler`, which +walks far outside the small `ramrom_blkbuf_2`/`ramrom_blkbuf_3` scratch +buffers and segfaults reading `temp_v0->stick_x` +(`ramromreplay.c:301`/`ramrom.c` callers). Root-caused with a new +env-gated probe (`GE_D87=1`, left in place). Fixed with a `#ifdef PORT` +`ramromFixupEndian()` in `ramromreplay.c`, called once right after the +`romCopyAligned()` in `replay_recorded_ramrom_at_address` (same pattern as +the D54 cseq-header fixup): byte-swaps every multi-byte field +(`u64`/`u32`/enum fields via `__builtin_bswap64`/`32`, `save_data.options` +via `bswap16`); `save_data`'s single-byte fields and the `times[]` byte +array are left alone. The **downstream** per-frame chunks +(`ramrom_seed`/`ramrom_blockbuf`, read via the same `romCopyAligned` +pattern in `iterate_ramrom_entries_handle_camera_out`) are all-`u8` +structs and need no swap. Not BUNKER1-specific — this is a front-end/ +attract-mode path that can select any of the 7 demo locations at random; +use `-level_09` (see D88) to skip the front end entirely for deterministic +BUNKER1 testing instead of waiting on/fixing attract mode. + +**D88 (OPEN — root-caused, next blocker: per-level `Usetup*Z` "stage +setup" file is raw N64-endian/width ROM bytes read directly through a +PC-widened struct, with no conversion at all).** Found immediately after +D86/D87 while re-verifying BUNKER1 specifically — launch with `-level_09` +(`boss.c:199-339` decodes `-level_XX` into `g_StageNum`, bypassing the +front end/attract-mode entirely for a fast, deterministic repro; NTSC +`LEVELID_BUNKER1 = 9`, and the token's two digit-chars are consumed as raw +ASCII bytes, so `"09"` → `'0'*10 + '9' - 0x210 = 9`) reaches the exact same +crash as the random attract-mode run, immediately and reproducibly: +`proplvreset2` (`prop.c:1306`) segfaults reading +`g_CurrentSetup.pathwaypoints[i1].padID`. `prop.c:1267-1282` loads the +level's `"UsetupZ"` file with `_fileNameLoadToBank` (raw ROM bytes, +**not** run through any PC-layout converter — unlike bg/stan (D69/D80-82) +and models (D43/D50), this asset type has zero PC porting work done on +it) into `local_stage`, then rebases 10 top-level fields +(`pathwaypoints`/`waypointgroups`/`intro`/`propDefs`/`patrolpaths`/ +`ailists`/`pads`/`boundpads`/`padnames`/`boundpadnames`, plus nested +`neighbours`/`waypoints`/`ailist` pointers inside the sub-tables) with +`(void *)(((u32) local_stage) + ((u32) local_stage->pathwaypoints))` — +i.e. by reading the *raw file bytes* directly through the live +`struct stagesetup` (`bondtypes.h:4091`), whose 10 fields are declared as +real pointers. This is worse than a plain missing-byteswap bug (cf. D87): +on N64 those 10 fields are 4 bytes each (40-byte header, correctly +self-describing "byte offset from file start" per the code's own +comment), but the PC struct widens every pointer field to 8 bytes (an +80-byte header) — the same class as D79 (`bg_room_data` pointer growth) +— so field N's read doesn't even land on the right *bytes* of the file +past field 0, before even considering that the 4 meaningful bytes it does +read are big-endian. Confirmed no PORT/byteswap handling exists anywhere +in `prop.c` (`grep` for `bswap`/`#ifdef PORT` in the file: zero hits). +**Not fixed this session** — this is format-conversion work at the same +scale as D69 (a whole ROM asset type needs a byte-accurate spec + either +an offline converter sidecar, the established preferred pattern per +AGENTS.md, or a careful runtime fixup pass that parses the raw 40-byte +N64-packed header by explicit byte offset, byte-swaps each field, and +writes the results into the PC-widened `stagesetup` struct — plus the +same treatment for every nested sub-table referenced from it +(`waypoint`/`waygroup`/`PropDefHeaderRecord`/`PathRecord`/`AIListRecord`/ +`PadRecord`/`BoundPadRecord`/`pname`, each of which likely has its own +internal offsets/BE fields not yet audited). **This is the actual next +blocker to a rendered BUNKER1 frame** — reachable deterministically via +`-level_09` in well under a minute, no attract-mode wait required. + +**D69 status after D78-D88: the ORIGINAL blocker (`load_bg_file` faulting on first stage load) is fully resolved and verified** — a clean run loads BUNKER1's header/room/portal/envdata tables and its full ~1066-tile stan file correctly (spot-checked byte-for-byte against the N64 source via the `GE_D69STAN` probe: tile room/mid/tail/point values match). The game now progresses substantially further than before (through room-streaming -setup) before hitting D86's separate, newly-exposed crash. D85 (room -geometry renders wrong, not yet crash-free at the *visual* level) and D86 -(new model-load crash) remain open follow-ups — **the "loads without +setup, past the old D86 model-init crash and the D87 attract-mode crash) +before hitting D88's separate, newly-exposed "stage setup" file format +gap. D85 (room geometry renders wrong, not yet crash-free at the *visual* +level) and D88 (stage-setup file format, unconverted) remain open +follow-ups — **the "loads without fault" acceptance bar is not yet fully met** (the process still exits via crash, just much later in the load sequence), but the converter, port wiring, and every ABI fix identified so far are format-verified correct @@ -2744,10 +2831,20 @@ and committed. **Environment reminders.** MSYS2 tools in `/c/msys64/mingw64/bin/` (not on PATH — prefix `export PATH=…`). Build: `./build-pc.sh ntsc-final`. gdb **launch** mode -only (attach fails, error 87) and it is **far too slow for timing-dependent -crashes** (a D56-class crash ~10 s in took >300 s under gdb to reach 2 frames — -DBGHELP symbol loading + the D51 stall-heartbeat thread dumps; don't wait on -it): prefer env-gated TEMP probes + the built-in crash log, and symbolicate +is far too slow for timing-dependent crashes (a D56-class crash ~10 s in took +>300 s under gdb to reach 2 frames — DBGHELP symbol loading + the D51 +stall-heartbeat thread dumps; don't wait on it): prefer env-gated TEMP probes + +the built-in crash log for reproducible faults. **Correction (D87 session):** +gdb **attach** mode (`gdb -batch -x cmds -p `, where `` is the +Windows PID from `ps`, 4th column — the game must already be running, e.g. +launched with `nohup ... &`) works fine and is fast, since the process is +already warmed up and running at full speed before you attach; a hardware +watchpoint (`watch *(int*)0xADDR`) caught a global's write in well under a +minute. Useful for "is this global legitimately written, or corrupted" +questions on a long-running, non-crashing process — attach once the process +has been running a while, `continue`, and it'll fire on the very next real +write. Still avoid gdb for the crash itself if the crash is reproducible via +the crash log; symbolicate offline with `addr2line -e build-pc/ge007.x86_64.exe -f -C <0x140000000+rel>`. Image base 0x140000000. `load_resource`/many init fns use a fake RBP — compute stack offsets from entry RSP. The D30 crash handler writes `ge007.crash.log` diff --git a/src/game/initplayergaitobject.c b/src/game/initplayergaitobject.c index abfbbd7c..a8c6e1c2 100644 --- a/src/game/initplayergaitobject.c +++ b/src/game/initplayergaitobject.c @@ -2,7 +2,19 @@ #include "chrobjdata.h" void init_player_gait_object(void) { +#ifdef PORT + /* PC port (D86, docs/PCPortResearch.md): the N64 source does `(int)&player_gait_hdr`, + * a same-width (32<-32) pointer->int->pointer round trip that's a no-op on N64. + * On PC the int cast truncates a real 64-bit pointer to its low 32 bits, and the + * implicit int->pointer conversion back into RootNode zero-extends it, producing a + * bad address whose upper 32 bits don't match the executable's load base (crashes + * in modelInitRwData the first time the player's gait/arm model is initialized, + * i.e. once gameplay starts past the intro). Assign the real pointer directly; + * behavior-identical to the N64 assignment, ABI-width fix only. */ + player_gait_object_header.RootNode = &player_gait_hdr; +#else player_gait_object_header.RootNode = (int)&player_gait_hdr; +#endif return; } diff --git a/src/game/model.c b/src/game/model.c index ce7284d0..111968be 100644 --- a/src/game/model.c +++ b/src/game/model.c @@ -6169,6 +6169,13 @@ void modelInitRwData(Model *model, ModelNode *startnode) while (node) { +#if defined(PORT) /* TEMP D86: trace node walk to catch the bad pointer before deref */ + if (getenv("GE_D86")) { + fprintf(stderr, "[D86] modelInitRwData model=%p header=%p node=%p\n", + (void *)model, (void *)(model ? model->obj : NULL), (void *)node); + fflush(stderr); + } +#endif u32 type = node->Opcode & 0xFF; switch (type) diff --git a/src/game/objecthandler_2.c b/src/game/objecthandler_2.c index cea7deb0..28a72acd 100644 --- a/src/game/objecthandler_2.c +++ b/src/game/objecthandler_2.c @@ -12,6 +12,8 @@ #include "tex.h" #ifdef PORT +#include +#include #include "pcmodels.h" /* D50: PC-layout model sidecars (Plan B, D48/D49) */ #include "pccg.h" /* D69: PC-layout bg/stan sidecars */ extern resource_lookup_data_entry resource_lookup_data_array[]; /* ob.c */ @@ -136,7 +138,15 @@ void load_object_fill_header(struct ModelFileHeader *objheader, u8 *name, u8* ds #endif objheader->RootNode = (struct ModelNode *)&objheader->Textures[objheader->numtextures]; - + +#if defined(PORT) /* TEMP D86: correlate header identity with the model name at load time */ + if (getenv("GE_D86")) { + fprintf(stderr, "[D86] load_object_fill_header name=%s objheader=%p filedata=%p RootNode=%p numSwitches=%d numtextures=%d\n", + (const char *)name, (void *)objheader, (void *)filedata, (void *)objheader->RootNode, + objheader->numSwitches, objheader->numtextures); + fflush(stderr); + } +#endif sub_GAME_7F075A90(objheader, 0x5000000, filedata); sub_GAME_7F0762E0(objheader, name, dst, buffer); } diff --git a/src/game/ramromreplay.c b/src/game/ramromreplay.c index a3ff7b58..564b48a6 100644 --- a/src/game/ramromreplay.c +++ b/src/game/ramromreplay.c @@ -1,5 +1,9 @@ #include +#ifdef PORT +#include +#include +#endif #include "debugmenu_handler.h" #include "lv.h" #include "initcheattext.h" @@ -288,6 +292,14 @@ s32 ramrom_replay_handler(struct contsample *arg0, s32 arg1) temp_a2 = (s32) ptr_active_demofile->size_cmds; temp_t2 = ramrom_blkbuf_2->count; +#if defined(PORT) /* TEMP D87: trace consumer before the crash-site deref */ + if (getenv("GE_D87")) { + fprintf(stderr, "[D87] replay_handler: ramrom_blkbuf_3=%p temp_a2=%d temp_t2=%d arg1=%d\n", + (void *)ramrom_blkbuf_3, temp_a2, temp_t2, arg1); + fflush(stderr); + } +#endif + for (var_a3 = 0; var_a3 < temp_t2; var_a3++) { arg1 = (s32) (arg1 + 1) % CONTSAMPLE_LEN; @@ -347,6 +359,17 @@ void iterate_ramrom_entries_handle_camera_out(void) ramrom_blkbuf_2 = romCopyAligned(ramrom_data_target + 0x1F8, address_demo_loaded, sizeof(struct ramrom_seed)); +#if defined(PORT) /* TEMP D87: trace ramrom block setup */ + if (getenv("GE_D87")) { + fprintf(stderr, "[D87] iterate: address_demo_loaded=%p ramrom_blkbuf_2=%p count=%d speedframes=%d size_cmds=%d\n", + (void *)address_demo_loaded, (void *)ramrom_blkbuf_2, + ramrom_blkbuf_2 ? ramrom_blkbuf_2->count : -1, + ramrom_blkbuf_2 ? ramrom_blkbuf_2->speedframes : -1, + ptr_active_demofile ? ptr_active_demofile->size_cmds : -1); + fflush(stderr); + } +#endif + var_a3 = ramrom_blkbuf_2->count; if (var_a3 > 0) { @@ -354,6 +377,13 @@ void iterate_ramrom_entries_handle_camera_out(void) ramrom_data_target + 0x21E, address_demo_loaded + 4, ptr_active_demofile->size_cmds * sizeof(struct ramrom_blockbuf) * ramrom_blkbuf_2->count); +#if defined(PORT) /* TEMP D87 */ + if (getenv("GE_D87")) { + fprintf(stderr, "[D87] iterate: ramrom_blkbuf_3=%p len=%d\n", (void *)ramrom_blkbuf_3, + (int)(ptr_active_demofile->size_cmds * sizeof(struct ramrom_blockbuf) * ramrom_blkbuf_2->count)); + fflush(stderr); + } +#endif } var_a3 = ramrom_blkbuf_2->count; @@ -502,10 +532,55 @@ void stop_recording_ramrom(void) } } +#ifdef PORT +/* PC port (D87, docs/PCPortResearch.md): ramromfilestructure is a compiled-in + * ROM asset (the attract-mode demo blobs in `ramrom_table[]` -- ramrom_Dam_1 + * etc.), so like every other N64-compiled ROM asset its multi-byte fields are + * big-endian. romCopyAligned() is a raw byte copy (no endian translation, by + * design -- see D66), so on PC every multi-byte field here reads corrupted + * (e.g. a real size_cmds of 2 stored as BE 00 00 00 02 reads back as LE + * 0x02000000). That garbage then drives loop counts/pointer arithmetic in + * iterate_ramrom_entries_handle_camera_out/ramrom_replay_handler, which walks + * far outside the small ramrom_blkbuf_2/3 scratch buffers and segfaults. + * Byte-swap every multi-byte field once, right after the raw copy -- same + * pattern as the D54 cseq-header fixup. save_data's single-byte fields + * (chksum1/chksum2/options aside) don't need swapping; `times[]` is a byte + * array. */ +static void ramromFixupEndian(ramromfilestructure *f) +{ + int i; + f->randomseed = __builtin_bswap64(f->randomseed); + f->randomizer = __builtin_bswap64(f->randomizer); + f->stagenum = (enum LEVELID)__builtin_bswap32((u32)f->stagenum); + f->difficulty = (enum DIFFICULTY)__builtin_bswap32((u32)f->difficulty); + f->size_cmds = __builtin_bswap32(f->size_cmds); + f->savefile.chksum1 = (s32)__builtin_bswap32((u32)f->savefile.chksum1); + f->savefile.chksum2 = (s32)__builtin_bswap32((u32)f->savefile.chksum2); + f->savefile.options = __builtin_bswap16(f->savefile.options); + f->totaltime_ms = (s32)__builtin_bswap32((u32)f->totaltime_ms); + f->filesize = (s32)__builtin_bswap32((u32)f->filesize); + f->mode = (enum GAMEMODE)__builtin_bswap32((u32)f->mode); + f->slotnum = __builtin_bswap32(f->slotnum); + f->numplayers = __builtin_bswap32(f->numplayers); + f->scenario = __builtin_bswap32(f->scenario); + f->mpstage_sel = __builtin_bswap32(f->mpstage_sel); + f->gamelength = __builtin_bswap32(f->gamelength); + f->mp_weapon_set = __builtin_bswap32(f->mp_weapon_set); + for (i = 0; i < 4; i++) f->mp_char[i] = __builtin_bswap32(f->mp_char[i]); + for (i = 0; i < 4; i++) f->mp_handi[i] = __builtin_bswap32(f->mp_handi[i]); + for (i = 0; i < 4; i++) f->mp_contstyle[i] = __builtin_bswap32(f->mp_contstyle[i]); + f->aim_option = __builtin_bswap32(f->aim_option); + for (i = 0; i < 4; i++) f->mp_flags[i] = __builtin_bswap32(f->mp_flags[i]); +} +#endif + void replay_recorded_ramrom_at_address(ramromfilestructure *demofile) { address_demo_loaded = demofile; ptr_active_demofile = romCopyAligned(&ramrom_data_target, address_demo_loaded, sizeof(struct ramromfilestructure)); +#ifdef PORT + ramromFixupEndian(ptr_active_demofile); +#endif address_demo_loaded += sizeof(ramromfilestructure); g_ramromPlayBackFlag = 1; set_solo_and_ptr_briefing(ptr_active_demofile->stagenum);