D86+D87: player gait-model pointer truncation + attract-mode demo endian fix

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C3xib6jjMV8o2iE4JfixAo
This commit is contained in:
James DansereauandClaude Sonnet 5 committed 2026-08-27 21:47:04 -04:00
1 parent 1d4e912e83
commit 0bcf5f1777
7 files changed
+370 -116

No files matched your search

+1 -1
View File
@@ -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_<r>.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-<region>/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 Line truncated
- **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-<region>/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).Line truncated
- **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.
+141 -88
View File
@@ -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
`"Usetup<name>Z"` 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="<range>: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="<start>-<end>:<stride>"` + `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 <pc>` (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 <winpid>`, `<winpid>` = 4th column of
`ps -p <bashpid>`) 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.
+123 -26
View File
@@ -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 <pid>` **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 `"Usetup<name>Z"` 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 <winpid>`, where `<winpid>` 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`
+12
View File
@@ -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;
}
+7
View File
@@ -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)
+11 -1
View File
@@ -12,6 +12,8 @@
#include "tex.h"
#ifdef PORT
#include <stdio.h>
#include <stdlib.h>
#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);
}
+75
View File
@@ -1,5 +1,9 @@
#include <ultra64.h>
#ifdef PORT
#include <stdio.h>
#include <stdlib.h>
#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);