forked from Ports/goldeneye-pc-port
PC port: Phase 1.5 — root-cause D32 Part B (D33): ROM big-endian
structured fields + x86-64 loop-stride bug; fix designed, handoff updated - Finding D33 in docs/PCPortResearch.md: the .z64 stores multi-byte structured fields in big-endian byte order (bit-packed streams raw); per-field load-time transform rule (u32 bswap32 / u16 bswap16 / u8 identity) validated over all 173 animation entries; full blob layout mapped; exact reproduction of the gdb-observed garbage values. - Second root cause: expand_ani_table_entries iterates s32** (+8 bytes/iter on x86-64 vs +4 on N64), rebasing only even-indexed entries (verified in compiled binary). Fix design: romdataFixupAnimationData() in port/, one call between romCopy and expand, s32* loop stride fix. - docs/HANDOFF.md rewritten with the implementation task; AGENTS.md phase status updated. tools_pc/disasm.py (BE ROM research tool) added.
This commit is contained in:
1 parent
ab61e4be4a
commit
8482ba3ed1
4 files changed
+317
-69
No files matched your search
@@ -4,7 +4,7 @@
|
||||
|
||||
- `n64decomp/007`: WIP decompilation of GoldenEye 007 (N64), byte-matches US/EU/JP ROMs.
|
||||
- Active work: **PC port** modelled on the Perfect Dark PC port (same Rare "Indy" engine family).
|
||||
- **Read `docs/PCPortResearch.md` first** — architecture, GE-specific RSP deltas, phased plan, and review findings (A1–D32 in §11; current handoff + plan in §H).
|
||||
- **Read `docs/PCPortResearch.md` first** — architecture, GE-specific RSP deltas, phased plan, and review findings (A1–D33 in §11; current handoff + plan in §H).
|
||||
- **Starting a fresh session?** Read `docs/HANDOFF.md` — the paste-ready brief with the immediate task, standing procedure, and environment gotchas.
|
||||
|
||||
## Non-negotiables
|
||||
@@ -47,7 +47,7 @@ Run `/linkcheck` for this sweep. Record new findings in `docs/PCPortResearch.md`
|
||||
- **Phase 0 (scaffolding):** done, committed.
|
||||
- **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):** IN PROGRESS — mainThread runs through `bossInitMainthreadData()`; D31 (langInit file-load SIGSEGV) fixed via real-zlib decompression (`port/src/rzdecomp.c`, replaces the excluded `src/game/decompress.c`+`zlib.c`). Current blocker is **D32** (ROM-struct pointer-width): Part A (u32 layout in `ModelAnimation`) done, Part B (load-time rebase in `expand_ani_table_entries`) open. Recurring sub-task: ABI/layout fixes per the D32 procedure (§H) as more asset loading is reached.
|
||||
- **Phase 1.5 (boot to first frame):** IN PROGRESS — mainThread runs through `bossInitMainthreadData()`; D31 (langInit file-load SIGSEGV) fixed via real-zlib decompression (`port/src/rzdecomp.c`, replaces the excluded `src/game/decompress.c`+`zlib.c`). Current blocker is **D32** (ROM-struct pointer-width): Part A (u32 layout in `ModelAnimation`) done; Part B **root-caused (D33)** — the ROM stores structured fields big-endian (per-field load-time fixup needed) + an x86-64 loop-stride bug in `expand_ani_table_entries`; fix designed, implementation next. Recurring sub-task: ABI/layout fixes per the D32 procedure (§H) as more asset loading is reached.
|
||||
- **Phase 2 (rendering):** bring in PD fast3d (replaces `port/src/gfxstub.c`), verify custom CC/RM modes against `gmain.s`, port the scheduler; replace asset stubs with ROM-backed data (D16). Assumes ABI-clean data loading from Phase 1.5.
|
||||
- **Phase 3 (audio + input):** libaudio → SDL; decide the ASP strategy (C2: `aspMain*` dummies in `ucode.c`).
|
||||
- **Phase 4 (saves + polish):** file-backed EEPROM; PFS/motor already no-op stubs.
|
||||
+71
-33
@@ -1,7 +1,7 @@
|
||||
# Handoff brief — GoldenEye 007 PC port (Phase 1.5: boot to first frame)
|
||||
|
||||
_Paste-ready briefing for the next agent session. The authoritative detail lives in
|
||||
`AGENTS.md` and `docs/PCPortResearch.md` (§H handoff, §F/D31–D32 findings); this is
|
||||
`AGENTS.md` and `docs/PCPortResearch.md` (§H handoff, §F/D31–D33 findings); this is
|
||||
the summary + the immediate task._
|
||||
|
||||
## Your job
|
||||
@@ -13,61 +13,99 @@ when the user is back: a booting game.
|
||||
|
||||
## Read first (authoritative, in order)
|
||||
1. `AGENTS.md` — non-negotiables, critical files, build, verification ritual, phase status.
|
||||
2. `docs/PCPortResearch.md` §H (handoff + plan), then §F/D31–D32 (findings).
|
||||
2. `docs/PCPortResearch.md` §H (handoff + plan), then **§F/D33** (root cause of the
|
||||
current blocker + complete fix design), D31–D32 for background.
|
||||
|
||||
## Non-negotiables (the ones that bite)
|
||||
- N64 build files (`Makefile`, `tools/`, `rsp/`, `ld/`) — never touch.
|
||||
- **Game logic is unmodified.** Hardware deps go in `port/`. Narrow exception: mechanical,
|
||||
semantics-preserving ABI/layout fixes forced by the 32→64-bit transition (embedded
|
||||
pointers → `u32` + cast at use), each documented as a D3x finding. No behavior changes.
|
||||
semantics-preserving ABI/layout fixes forced by the 32→64-bit transition, each documented
|
||||
as a D3x finding. No behavior changes.
|
||||
- Region macros in `CMakeLists.txt` must mirror the N64 Makefile exactly.
|
||||
|
||||
## Current state (verified)
|
||||
## Current state (verified this session)
|
||||
- **D31 FIXED:** langInit file-load crash gone (real-zlib `decompressdata` in
|
||||
`port/src/rzdecomp.c`; excluded `src/game/decompress.c`+`zlib.c`). mainThread boots past
|
||||
all 7 language files into `bossInitMainthreadData()`.
|
||||
- **D32 Part A DONE:** `ModelAnimation` pointer fields → `u32` (N64 layout, sizeof 64),
|
||||
casts in `model.c`.
|
||||
- **D32 Part B = the current blocker.** Boot dies at `boss.c:233`
|
||||
`init_weapon_animation_groups_maybe()` → … → `modelAnimReadRootMotionValue`
|
||||
(`model.c:914`) reading `desc->bitCount`, because animation entries' `bitDescriptors` /
|
||||
`bitStream` are not valid DRAM pointers after load.
|
||||
`port/src/rzdecomp.c`). mainThread boots into `bossInitMainthreadData()`.
|
||||
- **D32 Part A DONE:** `ModelAnimation` pointer fields → `u32` (N64 layout, sizeof 64).
|
||||
- **D32 Part B = ROOT-CAUSED (finding D33). Two independent bugs, both confirmed:**
|
||||
1. **ROM endianness.** The `.z64` file stores structured multi-byte fields in
|
||||
**big-endian byte order** (bit-packed streams are raw). The animation blob's
|
||||
20-byte record headers and descriptor arrays must be converted per-field at load
|
||||
time: u32 → bswap32, u16 → bswap16, u8 → identity. No uniform word transform exists.
|
||||
Validated in Python over all 173 ROM entries: 0 bad ranges, 0 non-sequential
|
||||
descriptor arrays; simulated root-motion frames show smooth per-frame deltas.
|
||||
The exact garbage seen under gdb (0xC82BD8C0) is reproduced by LE-reading the BE
|
||||
file bytes + rebase. Evidence: GE-specific ROM header, `tools/utils.h` "v64→z64"
|
||||
convention, build pipeline with no byte-swap step, BE toolchain defaults.
|
||||
2. **x86-64 stride bug.** `expand_ani_table_entries()` iterates with `s32** var_v0;
|
||||
var_v0++` — +4 bytes/iter on N64, but +8 on x86-64 (verified: `add $0x8,%rdx` in the
|
||||
compiled binary). Only even-indexed entries get rebased; odd ones keep raw small
|
||||
offsets (fire_standing is index 1 → would stay 0x144 → SIGSEGV on use). The earlier
|
||||
"zeros at odd indices" gdb observation was a display artifact — the compiled `.data`
|
||||
array is dense (objdump-verified). This pattern occurs nowhere else in the set.
|
||||
|
||||
## Immediate task — D32 Part B
|
||||
Make `expand_ani_table_entries()` (`src/game/initanitable.c:233`) rebase **every** entry's
|
||||
`bitDescriptors` (u32 @0x08) and `bitStream` (u32 @0x10) to a valid V1 address (< 0x80000000).
|
||||
## Immediate task — implement the D33 fix (design complete, ~3 small edits)
|
||||
1. **`port/src/romdata.c` + `port/include/romdata.h`:** add
|
||||
`void romdataFixupAnimationData(u8 *blob, u32 blobSize, const s32 *tableA, const s32 *tableB)`.
|
||||
For each non-null (≠0 and ≠1 — sentinel is **1**) offset in both tables:
|
||||
- record at `blob+off` (20 bytes): bswap32 fields at +0x00 (address), +0x08
|
||||
(bitDescriptors), +0x10 (bitStream); bswap16 fields at +0x04 (frame count),
|
||||
+0x0C (bitsPerFrame root motion), +0x0E (frame size bits); bytes +0x06/+0x07
|
||||
(u8 angle width / loop flag) untouched.
|
||||
- descriptor array at `[blob+bd, blob+bs)` step 6 (`ModelAnimBitField`): bswap16
|
||||
words at +0 and +4 (bitOffset, valueOffset); bytes +2/+3 (bitCount, pad) untouched.
|
||||
Guards: skip if bd==0 && bs==0; require `bd < bs <= blobSize` and `(bs-bd)%6==0`.
|
||||
Full field semantics + layout rules in D33 (blob interleave: entry i's payload sits
|
||||
at [PTR_ANIM_{i-1}+0x14, PTR_ANIM_i); all regions disjoint).
|
||||
2. **`src/game/initanitable.c`, `alloc_load_expand_ani_table()` (~line 265):** call the
|
||||
fixup **between** `romCopy(...)` and `expand_ani_table_entries(...)` — it must run
|
||||
*before* expand (expand reads/writes those fields as LE after fixup). Add
|
||||
`#include "romdata.h"` (`port/include` is already on the include path). One-line
|
||||
mechanical ABI edit — document with a D32/D33 comment.
|
||||
3. **`src/game/initanitable.c`, `expand_ani_table_entries()` (line 233):** change the loop
|
||||
pointer from `s32** var_v0` to `s32 *var_v0` (cast at assignment: `var_v0 = (s32 *)arg0;`)
|
||||
so every entry is visited on x86-64. Semantics-preserving on N64. Keep the rest of the
|
||||
function body identical (`*var_v0 += base`, `((struct anim_entry *)(s32)*var_v0)->unk08/unk10`,
|
||||
and loop 2's `*(s32 *)(s32)*var_v0 += entries_base`).
|
||||
|
||||
Gdb method (launch mode only — attach fails with error 87):
|
||||
**Do NOT transform the entries segment** (per-frame joint angles, file 0x124AC0,
|
||||
0x169EC0 bytes) — it is identity-encoded bit-packed data, not consumed at boot; verify
|
||||
visually in Phase 2 when Bond first animates.
|
||||
|
||||
## Verify (gdb, launch mode only — attach fails with error 87)
|
||||
- Break at `boss.c:233` (right after `alloc_load_expand_ani_table` returns). base =
|
||||
`&ptr_animation_table->data`.
|
||||
- For each `animation_table_ptrs1[i]` / `animation_table_ptrs2[i]`, check
|
||||
`*(u32*)(entry+8)` and `*(u32*)(entry+16)`.
|
||||
- Two anomalies to resolve first: (a) in-memory `animation_table_ptrs1[]` reads
|
||||
`[ptr, 0, ptr, 0, …]` though the source is a dense `PTR_ANIM_*` list — reconcile that
|
||||
against the `while (*var_v0 != 0)` fixup loop; (b) entry[0]'s `bitStream` high byte is
|
||||
0x88, not 0x70. Likely a 32/64-bit or write-overlap issue in the two fixup loops.
|
||||
- Prefer understanding *why it works on N64* and matching that. If a game-code edit is
|
||||
needed it's an ABI/layout change (allowed per non-neg #2) — document it as D3x.
|
||||
- For a sample of `animation_table_ptrs1/2[i]` entries (include odd indices!):
|
||||
`*(u32*)(entry+8)` and `*(u32*)(entry+16)` must be in [0x70xxxxxx] (base + blob offset),
|
||||
and the record's address field (+0x00) must be a cart address 0x10xxxxxx.
|
||||
- Confirm boot advances past `init_weapon_animation_groups_maybe()` into the next init
|
||||
step of `bossInitMainthreadData`. Expect more D32-class faults further along (models,
|
||||
textures, other tables) — apply the standing procedure below each time.
|
||||
|
||||
## Standing procedure (you will hit more of these)
|
||||
Every ROM-serialized struct with a pointer field faults the same way once you reach more
|
||||
asset loading (models, textures, other tables). The D32 fix procedure (doc §H): at the
|
||||
fault run `ptype /o <Struct>`; if a pointer field's offset/size diverges from its N64
|
||||
offset comment, change it to `u32` + cast at the use sites; verify the load-time rebase
|
||||
yields valid V1 addresses; rebuild; confirm the boot advances past this struct. Log each as
|
||||
D3x in §F and note it in AGENTS.md phase status.
|
||||
asset loading. The D32 fix procedure (doc §H): at the fault run `ptype /o <Struct>`; if a
|
||||
pointer field's offset/size diverges from its N64 offset comment, change it to `u32` + cast
|
||||
at the use sites; verify the load-time rebase yields valid V1 addresses; **also check the
|
||||
ROM bytes' endianness per-field (D33 rule: u32 bswap32 / u16 bswap16 / u8 identity) and any
|
||||
fixup loop's pointer stride on x86-64**; rebuild; confirm boot advances. Log each as D3x in
|
||||
§F and note it in AGENTS.md phase status.
|
||||
|
||||
## Environment (do not rediscover these)
|
||||
- MSYS2/MinGW tools are in `/c/msys64/mingw64/bin/` — **not** on PATH. Prefix every shell:
|
||||
`export PATH="/c/msys64/mingw64/bin:$PATH"`.
|
||||
- Build: `./build-pc.sh ntsc-final` (incremental; full rebuild only when `port/shim/`
|
||||
changes). ROM is at `data/ge007.ntsc-final.z64`.
|
||||
changes). ROM is at `data/ge007.ntsc-final.z64` (= `baserom.u.z64`, byte-identical).
|
||||
- gdb: **launch** mode only (`gdb -batch -ex "handle SIGSEGV stop" -ex run …`). Symbolicate
|
||||
offline with `addr2line -e build-pc/ge007.x86_64.exe -f -C <0x140000000+rel>`. Image base
|
||||
is `0x140000000` (re-verify with `info address` after a rebuild).
|
||||
- `objdump -d --disassemble=<fn>` / `objdump -s -j .data` on build-pc/ge007.x86_64.exe is a
|
||||
fast way to check what the compiler actually emitted (used this session to confirm the
|
||||
stride bug and the dense table array).
|
||||
- Many init functions use a fake RBP — compute stack offsets from the **entry RSP**, not RBP.
|
||||
- The D30 crash handler did **not** write `ge007.crash.log` for the game-thread fault this
|
||||
session; attribute crashes via gdb. (Worth a separate look later.)
|
||||
- The D30 crash handler did **not** write `ge007.crash.log` for the game-thread fault;
|
||||
attribute crashes via gdb. (Worth a separate look later.)
|
||||
- Python on this box: no f-strings with nested quotes (< 3.12) — use `%` formatting.
|
||||
|
||||
## Definition of done (this milestone)
|
||||
mainThread reaches `bossMainloop()` and a frame is actually drawn on the GL surface (not just
|
||||
|
||||
+114
-34
@@ -962,26 +962,96 @@ gameInit — and currently dies inside `langInit()` (D31).
|
||||
matches. `expand_ani_table_entries()` writes s32 to 0x08/0x10 via its own
|
||||
`struct anim_entry`, which now aligns exactly (no change needed there).
|
||||
|
||||
**Part B — pointer-rebase correctness: OPEN (next target).** After Part A the
|
||||
*values* are still invalid for the crashing weapon-anim entry (gdb, break at
|
||||
boss.c:233 after `alloc_load_expand_ani_table`): anim = 0x702adad4 = base
|
||||
0x702ad8c0 + 0x214; `bitDescriptors` = 0xf714b067, `bitStream` = 0x000c9100 —
|
||||
neither a V1 address. Two concrete anomalies to debug in
|
||||
`expand_ani_table_entries()` (initanitable.c:233):
|
||||
* `animation_table_ptrs1[]` is a **dense** array of `PTR_ANIM_*` offsets in
|
||||
source, but in memory reads `[0x702ad8dc, 0, 0x702adad4, 0, …]` (zeros at
|
||||
odd indices). The fixup loop `while (*var_v0 != 0)` would stop at the first
|
||||
zero — yet even-index entries show rebased values. Reconcile the array's true
|
||||
post-fixup shape and confirm every entry's unk08/unk10 is actually rebased
|
||||
(the crashing anim is `animation_table_ptrs1[2]`).
|
||||
* Even entry[0]'s `bitStream` comes out wrong (0x882ad8c0 — low 24 bits match
|
||||
base, high byte 0x88≠0x70) while its `bitDescriptors` is correct
|
||||
(0x702ad8c0). Inspect the second fixup loop (`**var_v0 +=
|
||||
&_animation_entriesSegmentRomStart`) for a 32/64-bit or overlap bug.
|
||||
**Part B — pointer-rebase correctness: ROOT-CAUSED (see D33), fix designed,
|
||||
implementation pending.** The garbage values are *not* a bug in
|
||||
`expand_ani_table_entries`'s arithmetic — the rebase is correct N64 code fed
|
||||
wrong bytes, plus one genuine x86-64 ABI stride bug. Both root causes and the
|
||||
full fix design are in **D33** below.
|
||||
|
||||
The crash site is unchanged (still model.c:914) — Part A is a necessary
|
||||
prerequisite, not a regression. Next session: gdb `expand_ani_table_entries`
|
||||
step-by-step until every entry's bitDescriptors/bitStream is a valid V1 address.
|
||||
* **D33 — ROM file stores structured fields big-endian; animation load needs a per-field endianness fixup + an x86-64 loop-stride fix (root cause of D32 Part B).**
|
||||
|
||||
**Discovery.** The `.z64` ROM file is not a plain image: multi-byte
|
||||
*structured* fields (record headers, tables) are stored in **big-endian byte
|
||||
order**, while bit-packed data streams are raw. Evidence chain:
|
||||
1. `baserom.u.z64` is byte-for-byte identical to `data/ge007.ntsc-final.z64`;
|
||||
the ROM header is GE-specific (magic `80 37 12 40` from `src/rom_header.s`,
|
||||
BE u32 CRC at 0x10/0x14 written by `tools/n64cksum.c`) — not the standard
|
||||
N64 header layout.
|
||||
2. Build pipeline: `$(LD)` → ELF → `objcopy -O binary --gap-fill=0xff` →
|
||||
RareZip cdata compression (`data_compress.sh`) → `n64cksum`. No byte-swap
|
||||
step; the mips64-elf-gcc toolchain (SGI-style BE default) emits BE bytes.
|
||||
3. `tools/utils.h` documents the convention: `swap_bytes()` = "convert from
|
||||
v64 to z64 ordering", `reverse_endian()` = "convert from n64 to z64
|
||||
ordering" — the project's ".z64" is a word-swapped cart image (community
|
||||
".n64" format). Real cart = bswap32(file).
|
||||
4. Animation-blob analysis: reading headers as BE u32/u16 yields self-
|
||||
consistent values (frame counts, bit widths 0–31, offsets that tile the
|
||||
blob exactly); LE reads yield garbage.
|
||||
|
||||
**Per-field transform rule** (applied at load time to the `animation_data`
|
||||
segment): u32 fields → bswap32; u16 fields → bswap16; u8 fields → identity;
|
||||
bit-packed streams → identity. No single uniform word transform exists (mixed
|
||||
field widths).
|
||||
|
||||
**Blob layout** (fully mapped; all 173 entries verified): each C array
|
||||
(`animation_table_ptrs1/2` in `src/game/initanitable.c`) is a sequence of
|
||||
20-byte records at the `PTR_ANIM_*` offsets. Record fields: +0x00 address
|
||||
(entries-segment offset, u32), +0x04 frame count (u16), +0x06 angle bit width
|
||||
(u8, used by `modelAnimReadBitsAsU16Angle`), +0x07 loop flag (u8), +0x08
|
||||
bitDescriptors blob offset (u32), +0x0C bitsPerFrame root motion (u16, used by
|
||||
`sub_GAME_7F06D2E4`: `scaled = unk0C * frame`), +0x0E frame size in bits
|
||||
(u16, used by `loadAnimationFrame`: `frameSize = unk0E >> 3`), +0x10
|
||||
bitStream blob offset (u32). Tail (+0x14..+0x3C) is unused by any code — the
|
||||
effective record size is 20 bytes (= `struct anim_entry`, 5×s32). Interleave
|
||||
rule: entry *i*'s payload [descriptors][stream] sits at
|
||||
[PTR_ANIM_{i-1}+0x14, PTR_ANIM_i); all regions are disjoint; stream size =
|
||||
ceil(frameSizeBits × frames / 8) — exact fit on every entry (fire_standing:
|
||||
544 bits × 106 frames → 252 bytes). Descriptor array = `ModelAnimBitField`
|
||||
{u16 bitOffset, u8 bitCount, u8 pad, u16 valueOffset} ×4 (6 bytes each); after
|
||||
the transform all 692 descriptor arrays pack bitOffsets sequentially from 0.
|
||||
Null entries use sentinel **1** (not 0) — `expand_ani_table_entries` skips
|
||||
`*var_v0 == 1`.
|
||||
|
||||
**Validation.** Simulated the full transform in Python over all 173 entries:
|
||||
0 bad descriptor ranges, 0 non-sequential arrays. Root-motion simulation for
|
||||
fire_standing frames 0–3 (widths 6/7/6/0, bitsPerFrame=19) gives smooth
|
||||
per-frame deltas (x = 7, 10, 12, 11; y ≈ 1086–1088) — proving the stream
|
||||
bytes are identity-correct (no transform). The previously observed garbage is
|
||||
reproduced exactly: file bytes `[00 00 01 58]` at fire_standing_fast's bd →
|
||||
LE read 0x58010000 + base 0x702ad8c0 = **0xC82BD8C0**, the exact value seen
|
||||
under gdb. The "zeros at odd indices" observation was a gdb display artifact —
|
||||
the compiled `.data` array is dense (verified by objdump of
|
||||
build-pc/ge007.x86_64.exe).
|
||||
|
||||
**Second root cause: x86-64 stride bug in `expand_ani_table_entries`.** The
|
||||
loops iterate with `s32** var_v0; var_v0++` — +4 bytes/iter on N64, but
|
||||
`s32*` is 8 bytes on x86-64. Verified in the compiled binary (`add $0x8,%rdx`)
|
||||
: only even-indexed entries are rebased; odd entries keep raw small offsets
|
||||
(e.g. fire_standing, index 1, would stay 0x144 → instant SIGSEGV on use).
|
||||
This pattern occurs nowhere else in the compiled set.
|
||||
|
||||
**Fix design (implementation pending — this is the immediate task):**
|
||||
1. New port function `romdataFixupAnimationData(u8 *blob, u32 blobSize,
|
||||
const s32 *tableA, const s32 *tableB)` in `port/src/romdata.c` (+ decl in
|
||||
`port/include/romdata.h`): for each non-null (≠0, ≠1) offset in both
|
||||
tables — bswap the 20-byte record header per-field (u32: +0x00/+0x08/
|
||||
+0x10; u16: +0x04/+0x0C/+0x0E; u8: +0x06/+0x07 untouched); then transform
|
||||
the descriptor array at [bd, bs) step 6 (bswap16 on words 0 and 4 only).
|
||||
Guards: skip if bd==0 && bs==0; require bd < bs ≤ blobSize and
|
||||
(bs−bd) % 6 == 0.
|
||||
2. Call it in `alloc_load_expand_ani_table()` (initanitable.c ~line 265)
|
||||
**between** the `romCopy` and `expand_ani_table_entries` — one line +
|
||||
`#include "romdata.h"` (`port/include` is already on the include path).
|
||||
Mechanical ABI edit per non-negotiable #2; must run *before* expand
|
||||
(expand reads/writes the fields as LE after fixup).
|
||||
3. Fix the stride in `expand_ani_table_entries()` (initanitable.c:233):
|
||||
iterate with `s32 *` instead of `s32**` — mechanical ABI fix, semantics-
|
||||
preserving on N64 where it was already correct.
|
||||
|
||||
**Open item.** The entries segment (per-frame joint angles; file offset
|
||||
0x124AC0, 0x169EC0 bytes) is left as identity for Phase 1.5 — not consumed at
|
||||
boot (only during rendering). Verify visually in Phase 2 when Bond first
|
||||
animates.
|
||||
|
||||
### G. Phase 2 status (current)
|
||||
|
||||
@@ -1028,9 +1098,14 @@ shim expansion in compiled code.
|
||||
|
||||
**State.** D31 (langInit file-load SIGSEGV) is fixed & verified — mainThread
|
||||
boots past all language-file loading into `bossInitMainthreadData()`. The boot
|
||||
now dies in `init_weapon_animation_groups_maybe()` on the **D32** ROM-struct
|
||||
pointer-width issue. D32 Part A (struct layout) is done; Part B (load-time
|
||||
rebase correctness) is the next target. No frames rendered yet.
|
||||
dies in `init_weapon_animation_groups_maybe()` on the **D32** ROM-struct issue.
|
||||
D32 Part A (struct layout) is done. **D32 Part B is now fully root-caused
|
||||
(D33):** (1) the ROM file stores the animation segment's structured fields in
|
||||
big-endian byte order — a per-field endianness fixup is required at load time;
|
||||
(2) `expand_ani_table_entries` has an x86-64 loop-stride bug (`s32**` advances
|
||||
8 bytes/iter, rebasing only even-indexed entries). The fix design is complete
|
||||
and validated against all 173 ROM entries; **implementation is the immediate
|
||||
task** (three small edits — see D33 "Fix design"). No frames rendered yet.
|
||||
|
||||
**D32 repeatable fix procedure** (apply to any ROM-serialized struct that faults
|
||||
on a pointer-field read):
|
||||
@@ -1043,18 +1118,23 @@ on a pointer-field read):
|
||||
into those u32 fields; if not, debug the fixup (see Part B below).
|
||||
4. Rebuild and confirm the boot advances past this struct to the next init step.
|
||||
|
||||
**Next concrete target — D32 Part B.** Make `expand_ani_table_entries()`
|
||||
(initanitable.c:233) rebase *every* entry's `bitDescriptors`/`bitStream` to a
|
||||
valid V1 address. Gdb plan: break at boss.c:233 (after `alloc_load_expand_ani_table`),
|
||||
step through the two fixup loops, and for each `animation_table_ptrs1[i]` /
|
||||
`animation_table_ptrs2[i]` entry verify `*(u32*)(entry+8)` and `*(u32*)(entry+16)`
|
||||
are in [0x70xxxxxx]. Resolve these two anomalies first:
|
||||
* In-memory `animation_table_ptrs1[]` reads `[ptr, 0, ptr, 0, …]` (zeros at odd
|
||||
indices) though the source is a dense `PTR_ANIM_*` list — reconcile the array's
|
||||
true shape vs the `while (*var_v0 != 0)` loop.
|
||||
* entry[0]'s `bitStream` = 0x882ad8c0 (high byte 0x88≠0x70) while its
|
||||
`bitDescriptors` = 0x702ad8c0 is correct — inspect the second fixup loop
|
||||
(`**var_v0 += &_animation_entriesSegmentRomStart`) for a 32/64-bit or overlap bug.
|
||||
**Next concrete target — implement the D33 fix.** Three edits (full design in
|
||||
D33):
|
||||
1. `port/src/romdata.c` + `port/include/romdata.h`: add
|
||||
`romdataFixupAnimationData(u8 *blob, u32 blobSize, const s32 *tableA,
|
||||
const s32 *tableB)` — per-field BE→LE transform of the 20-byte records and
|
||||
the [bd, bs) descriptor arrays (u32 → bswap32, u16 → bswap16, u8 identity).
|
||||
2. `src/game/initanitable.c` `alloc_load_expand_ani_table()`: call the fixup
|
||||
between `romCopy` and `expand_ani_table_entries` (+ `#include "romdata.h"`).
|
||||
3. `src/game/initanitable.c` `expand_ani_table_entries()`: change the loop
|
||||
pointer from `s32**` to `s32 *` so every entry is visited on x86-64.
|
||||
|
||||
Then: build (`./build-pc.sh ntsc-final`), verify under gdb (launch mode):
|
||||
break at boss.c:233 after `alloc_load_expand_ani_table`; for a sample of
|
||||
`animation_table_ptrs1/2[i]` entries confirm `*(u32*)(entry+8)` and
|
||||
`*(u32*)(entry+16)` are in [0x70xxxxxx] (blob base + offset) and the record's
|
||||
address field is a cart address (0x10xxxxxx); confirm boot advances past
|
||||
`init_weapon_animation_groups_maybe`. Commit at the milestone.
|
||||
|
||||
**Non-negotiable #2 refinement (applied to AGENTS.md).** The original "game code
|
||||
compiles unmodified / fix belongs in port/" is too absolute: pointer-width layout
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Minimal MIPS disassembler for the BE-encoded GE ROM.
|
||||
|
||||
Usage: disasm.py <ram_addr_hex> [n_instructions]
|
||||
Maps game-segment RAM (base 0x7F000000) to file offset 0x34B28.
|
||||
Also handles lib segment (RAM base = _startSegmentEnd - 0x10000000, ROM 0x1050).
|
||||
"""
|
||||
import struct, sys
|
||||
|
||||
ROM = open('data/ge007.ntsc-final.z64', 'rb').read()
|
||||
GAME_RAM_BASE = 0x7F000000
|
||||
GAME_ROM_OFF = 0x34B28
|
||||
LIB_ROM_OFF = 0x1050
|
||||
LIB_RAM_BASE = 0x70000450 # from boot.s comment: code at ROM 0x1050 -> RAM 0x70000450
|
||||
|
||||
REGS = ["$zero","$at","$v0","$v1","$a0","$a1","$a2","$a3",
|
||||
"$t0","$t1","$t2","$t3","$t4","$t5","$t6","$t7",
|
||||
"$s0","$s1","$s2","$s3","$s4","$s5","$s6","$s7",
|
||||
"$t8","$t9","$t10","$t11","$t12","$t13","$gp","$sp",
|
||||
"$fp","$ra"]
|
||||
|
||||
RTYPE = {0x00:"sll",0x02:"srl",0x04:"sra",0x06:"sllv",0x08:"srav",0x0A:"sralv",
|
||||
0x0C:"and",0x0E:"or",0x0F:"xor",0x10:"nor",0x12:"slt",0x14:"sltu",
|
||||
0x18:"add",0x1A:"addu",0x1C:"sub",0x1E:"subu",
|
||||
0x20:"mult",0x21:"multu",0x22:"div",0x23:"divu",
|
||||
0x24:"madd",0x25:"maddu",0x26:"msub",0x27:"msubu"}
|
||||
|
||||
COP0 = {0x00:"mfc0",0x01:"mfc1",0x02:"mfc2",0x10:"mtc0",0x11:"mtc1",0x12:"mtc2",
|
||||
0x40:"bc0t",0x44:"bc0f"}
|
||||
|
||||
def disasm_word(w):
|
||||
op = w >> 26
|
||||
rs = (w >> 21) & 31
|
||||
rt = (w >> 16) & 31
|
||||
rd = (w >> 11) & 31
|
||||
sa = (w >> 6) & 31
|
||||
fn = w & 63
|
||||
imm = w & 0xFFFF
|
||||
def s16(v): return v - 0x10000 if v >= 0x8000 else v
|
||||
|
||||
if op == 0x00:
|
||||
if fn in RTYPE:
|
||||
if fn in (0x00,0x02,0x04,0x06,0x08,0x0A):
|
||||
return "%s %s,%s,%d" % (RTYPE[fn], REGS[rd], REGS[rt], sa)
|
||||
if fn in (0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27):
|
||||
return "%s %s,%s" % (RTYPE[fn], REGS[rs], REGS[rt])
|
||||
return "%s %s,%s,%s" % (RTYPE[fn], REGS[rd], REGS[rs], REGS[rt])
|
||||
if fn == 0x19: # tgei etc rare
|
||||
return "tgeiu? %s,%s,0x%04x" % (REGS[rd], REGS[rs], imm)
|
||||
return ".word 0x%08x" % w
|
||||
if op == 0x01:
|
||||
c = (w >> 21) & 3
|
||||
if c == 0:
|
||||
if fn in COP0:
|
||||
if fn in (0x00,0x10):
|
||||
return "%s %s,0x%02x" % (COP0[fn], REGS[rt], rs)
|
||||
return "%s %s,0x%02x" % (COP0[fn], REGS[rs], rt)
|
||||
if fn == 0x00: return "mfc0? %s" % REGS[rt]
|
||||
return ".word 0x%08x" % w
|
||||
if c == 1: # cop1
|
||||
if fn in (0x00,0x01,0x02,0x03):
|
||||
names = ["mfc1","cfc1","mtc1","ctc1"]
|
||||
return "%s %s,%s" % (names[fn], REGS[rt], REGS[rs])
|
||||
if fn == 0x46: return "bc1t +0x%04x" % s16(imm)
|
||||
if fn == 0x47: return "bc1f +0x%04x" % s16(imm)
|
||||
if fn == 0x4E: return "bc1t? (cond)"
|
||||
if fn == 0x4F: return "bc1f? (cond)"
|
||||
# fp ops
|
||||
fops = {0x10:"abs.s",0x11:"add.s",0x12:"div.s",0x13:"cmp.s",0x14:"cvt.s.w",
|
||||
0x15:"cvt.w.s",0x16:"max.s",0x17:"min.s",0x18:"mov.s",0x19:"neg.s",
|
||||
0x1A:"recip.s",0x1B:"rsq.s",0x1C:"sqrt.s",0x20:"abs.d",0x21:"add.d",
|
||||
0x22:"div.d",0x23:"cmp.d",0x26:"max.d",0x27:"min.d",0x28:"mov.d",
|
||||
0x29:"neg.d",0x41:"and.b",0x42:"or.b",0x45:"cvt.s.w"}
|
||||
if fn in fops:
|
||||
return "%s %f%d,%f%d,%f%d" % (fops[fn], rd, rs, rt, 0)
|
||||
return ".word 0x%08x" % w
|
||||
if c == 2:
|
||||
return "cop2? .word 0x%08x" % w
|
||||
return ".word 0x%08x" % w
|
||||
if op == 0x02: return "jalr %s%s" % (REGS[rs], ", "+REGS[rt] if rt else "")
|
||||
if op == 0x03: return "syscall"
|
||||
if op == 0x04: return "beq %s,%s,+0x%04x" % (REGS[rs], REGS[rt], s16(imm))
|
||||
if op == 0x05: return "bne %s,%s,+0x%04x" % (REGS[rs], REGS[rt], s16(imm))
|
||||
if op == 0x06: return "blez %s,+0x%04x" % (REGS[rs], s16(imm))
|
||||
if op == 0x07: return "bgtz %s,+0x%04x" % (REGS[rs], s16(imm))
|
||||
if op == 0x08: return "j 0x%08x" % ((w & 0x3FFFFFF) << 2)
|
||||
if op == 0x09: return "jal 0x%08x" % ((w & 0x3FFFFFF) << 2)
|
||||
if op == 0x0A: return "addi %s,%s,0x%04x" % (REGS[rt], REGS[rs], s16(imm))
|
||||
if op == 0x0B: return "addiu %s,%s,0x%04x" % (REGS[rt], REGS[rs], s16(imm))
|
||||
if op == 0x0C: return "andi %s,%s,0x%04x" % (REGS[rt], REGS[rs], imm)
|
||||
if op == 0x0D: return "slti %s,%s,0x%04x" % (REGS[rt], REGS[rs], s16(imm))
|
||||
if op == 0x0E: return "sltiu %s,%s,0x%04x" % (REGS[rt], REGS[rs], imm)
|
||||
if op == 0x0F: return "ori %s,%s,0x%04x" % (REGS[rt], REGS[rs], imm)
|
||||
if op == 0x10: return "xori %s,%s,0x%04x" % (REGS[rt], REGS[rs], imm)
|
||||
if op == 0x11: return "lui %s,0x%04x" % (REGS[rt], imm)
|
||||
if op in range(0x20, 0x3C):
|
||||
name = {0x20:"lb",0x21:"lhu",0x22:"lw",0x23:"lbu",0x24:"lwl",0x25:"lwr",
|
||||
0x28:"sb",0x29:"sh",0x2A:"sw",0x2B:"swl",0x2D:"swr",0x2F:"ld"}.get(op, ".word")
|
||||
if op in (0x28,0x29,0x2A):
|
||||
return "%s 0x%04x(%s)" % (name, s16(imm), REGS[rs])
|
||||
if op == 0x2F:
|
||||
return "ld %s,0x%04x(%s)" % (REGS[rt], s16(imm), REGS[rs])
|
||||
return "%s %s,0x%04x(%s)" % (name, REGS[rt], s16(imm), REGS[rs])
|
||||
if op == 0x3C: return "lui %s,0x%04x" % (REGS[rt], imm)
|
||||
if op == 0x3D: return "ld %s,0x%04x(%s)" % (REGS[rt], s16(imm), REGS[rs])
|
||||
return ".word 0x%08x" % w
|
||||
|
||||
def ram_to_file(ram):
|
||||
if GAME_RAM_BASE <= ram < GAME_RAM_BASE + 0x200000:
|
||||
return GAME_ROM_OFF + (ram - GAME_RAM_BASE)
|
||||
if LIB_RAM_BASE <= ram < LIB_RAM_BASE + 0x40000:
|
||||
return LIB_ROM_OFF + (ram - LIB_RAM_BASE)
|
||||
# bss/data in lib segment: RAM 0x8002xxxx etc — not in ROM; skip
|
||||
return None
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__); sys.exit(1)
|
||||
ram = int(sys.argv[1], 16)
|
||||
n = int(sys.argv[2]) if len(sys.argv) > 2 else 40
|
||||
off = ram_to_file(ram)
|
||||
if off is None or off < 0 or off + 4*n > len(ROM):
|
||||
print("cannot map RAM %x" % ram); sys.exit(1)
|
||||
for i in range(n):
|
||||
w = struct.unpack_from('>I', ROM, off + i*4)[0]
|
||||
addr = ram + i*4
|
||||
print(" %08x: %08x %s" % (addr, w, disasm_word(w)))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user