mirror of
https://github.com/jkdansereau/goldeneye-pc-port.git
synced 2026-10-23 15:52:42 +02:00
PC port: Phase 1.5 — fix langInit decompression (D31), start D32 ABI layout
- D31 (resolved): replace GE's gzip-1.2.4 inflate with real-zlib decompressdata() in port/src/rzdecomp.c; exclude src/game/decompress.c + zlib.c from the PC build. Root cause: struct huft is 8B on MIPS but 16B on x86-64, overflowing load_resource's fixed 0x2100 buffer and clobbering its frame/retaddr on the first file load. mainThread now boots past all language files into bossInitMainthreadData(). - D32 (in progress): ROM-serialized structs with 32-bit pointer fields misalign when read as 64-bit pointers. Part A done: ModelAnimation's bitDescriptors/bitStream are now u32 (N64 layout preserved) with casts at the two use sites in model.c. Part B (load-time rebase in expand_ani_table_entries) is open — see docs §F/D32 + §H. - Docs: D31 resolved, D32 recorded (A done / B open), Phase 1.5 added, new §H handoff section. AGENTS.md: refined non-negotiable #2 to "game logic is unmodified" with a narrow ABI/layout exception; refreshed phase status.
This commit is contained in:
1 parent
13ca03a6db
commit
dbeee7353f
7 files changed
+292
-21
No files matched your search
@@ -4,12 +4,12 @@
|
||||
|
||||
- `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–C2 in §11).
|
||||
- **Read `docs/PCPortResearch.md` first** — architecture, GE-specific RSP deltas, phased plan, and review findings (A1–D32 in §11; current handoff + plan in §H).
|
||||
|
||||
## Non-negotiables
|
||||
|
||||
1. **N64 build untouched.** `Makefile`, `tools/`, `rsp/`, `ld/` belong to the N64 build. Never modify them for the PC port.
|
||||
2. **Game code compiles unmodified.** `src/game/*.c` + the curated `src/*.c` list compile as-is for the host. All N64 hardware dependencies are satisfied by the `port/` layer. If a game file seems to need a change, stop — the fix belongs in `port/`.
|
||||
2. **Game logic is unmodified.** The decomp's control flow and behavior are ground truth for 1:1 fidelity — never change them. All N64 *hardware* dependencies are satisfied by the `port/` layer; if a game file seems to need a behavioral change, stop — the fix belongs in `port/`. **Narrow exception (ABI/layout only):** the 32→64-bit transition forces a small class of mechanical, semantics-preserving edits that cannot be isolated in `port/` — chiefly pointer-width reconciliation in ROM-serialized structs (a struct with a 32-bit-pointer field misaligns when read as 64-bit). These follow the PD ground-truth pattern (store the embedded address as `u32`, cast to a real pointer at the use site), change no logic or behavior, and are each documented in `docs/PCPortResearch.md` §F/D3x. No other game-code edits are permitted.
|
||||
3. **Region macros mirror the Makefile.** `CMakeLists.txt` `REGION_DEFS` must match the N64 Makefile's per-region macro set exactly (finding A1). Divergence = silent branch divergence + link failures.
|
||||
4. **`src/libultrare/Makefile.libultrare` is ground truth** for original-vs-Rare libultra files (finding B3). The PC build compiles: `libultra/audio`, `libultrare/audio` (drvrNew/env/reverb), `libultra/gu`, and `libultrare/io/vitbl.c` only. All other `io/` + `os/` files are excluded and shimmed in `port/src/libultra.c`.
|
||||
5. **`rsp/graphics/gmain.s` is the RSP ground truth** — the authoritative reference for which GBI commands GE emits (modified fast3d, 1545 lines). We do not run it on PC; `port/fast3d/` replaces it. Use it to validate the software RSP's command decoding and the custom CC/RM modes.
|
||||
@@ -46,7 +46,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 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).
|
||||
- **Phase 2 (rendering):** bring in PD fast3d, verify custom CC/RM modes against `gmain.s`, port the scheduler.
|
||||
- **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 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.
|
||||
@@ -266,6 +266,23 @@ if(ZLIB_INCLUDE_DIR)
|
||||
endif()
|
||||
list(APPEND LIBS "${ZLIB_LIBRARY}")
|
||||
|
||||
# Real <zlib.h> for port/src/rzdecomp.c (which backs decompressdata() with real
|
||||
# zlib). The game's own src/game/zlib.h sits on the -I path and shadows a plain
|
||||
# `#include <zlib.h>` in a port TU, so generate an absolute-path include (same
|
||||
# trick as hoststring.h). Prefer find_package(ZLIB)'s dir; fall back to the
|
||||
# toolchain include dir (MinGW layout: bin/../include), where
|
||||
# mingw-w64-x86_64-zlib installs zlib.h.
|
||||
set(PORT_REAL_ZLIB_H "${ZLIB_INCLUDE_DIR}/zlib.h")
|
||||
if(NOT EXISTS "${PORT_REAL_ZLIB_H}")
|
||||
set(PORT_REAL_ZLIB_H "${_PORT_GCC_DIR}/../include/zlib.h")
|
||||
endif()
|
||||
if(NOT EXISTS "${PORT_REAL_ZLIB_H}")
|
||||
message(FATAL_ERROR "Cannot locate the host zlib.h needed by port/src/rzdecomp.c")
|
||||
endif()
|
||||
configure_file(
|
||||
"${CMAKE_SOURCE_DIR}/port/include/realzlib.h.in"
|
||||
"${CMAKE_BINARY_DIR}/port/include/realzlib.h" @ONLY)
|
||||
|
||||
if(NOT GL_LIBRARY)
|
||||
if(WIN32)
|
||||
set(GL_LIBRARY opengl32)
|
||||
@@ -314,6 +331,23 @@ set(SRC_DRAM_SYMS "${CMAKE_SOURCE_DIR}/port/src/dram_syms.s")
|
||||
# Game code — compiled unmodified.
|
||||
file(GLOB_RECURSE SRC_GAME "${CMAKE_SOURCE_DIR}/src/game/*.c")
|
||||
|
||||
# EXCLUDED from src/game (replaced by port/src/rzdecomp.c, which backs
|
||||
# decompressdata()/rzipGetSomething() with real zlib):
|
||||
# decompress.c + zlib.c — GE's custom gzip-1.2.4 inflate builds its Huffman
|
||||
# tables contiguously into a caller-supplied fixed buffer of `struct huft`.
|
||||
# That struct is 8 bytes on MIPS (union holds a 32-bit pointer) but 16 bytes
|
||||
# on x86-64 (union holds a 64-bit pointer), so on a 64-bit host the tables
|
||||
# overflow load_resource's `u8 buffer[0x2100]` and clobber the caller's return
|
||||
# address -> SIGSEGV in langInit()'s first file load (finding D32). Real zlib
|
||||
# manages its own table memory, removing the fixed-buffer assumption. Mirrors
|
||||
# the PD port, which swaps its assembly rzip for a real-zlib-backed rzip_c.c.
|
||||
# The only externally-referenced symbols from these two files are
|
||||
# decompressdata() and rzipGetSomething(); both are provided by
|
||||
# port/src/rzdecomp.c (see the symbol sweep in docs/PCPortResearch.md §F).
|
||||
list(REMOVE_ITEM SRC_GAME
|
||||
"${CMAKE_SOURCE_DIR}/src/game/decompress.c"
|
||||
"${CMAKE_SOURCE_DIR}/src/game/zlib.c")
|
||||
|
||||
# libaudio (AL) — runs on the CPU, platform independent.
|
||||
file(GLOB SRC_LIBAUDIO "${CMAKE_SOURCE_DIR}/src/libultra/audio/*.c")
|
||||
# Rare's audio "New" driver (drvrNew/env/reverb) — synthesizer.c calls
|
||||
|
||||
+173
-13
@@ -317,6 +317,16 @@ sync/flush) is shared.
|
||||
* Port `video.c` + a minimal `fast3d` (clear color, present).
|
||||
* Goal: load `ge007.u.z64`, open a window, draw a clear color.
|
||||
|
||||
### Phase 1.5 — Boot to first frame (ABI reconciliation)
|
||||
* Get mainThread through all of `bossInitMainthreadData()` → `bossEntry()` →
|
||||
`bossMainloop()` and render frame 1. This is the current frontier (post-D31).
|
||||
* Recurring mechanism: **ROM-data struct ABI/layout fixes** (the D32 pattern) —
|
||||
a 32-bit-pointer struct read as a 64-bit-pointer struct misaligns; convert
|
||||
embedded pointer fields to `u32` + cast at use sites (PD ground truth), then
|
||||
verify the load-time rebase yields valid DRAM addresses. See §H for the
|
||||
step-by-step procedure and the standing "non-negotiable #2 ABI exception."
|
||||
* Goal: title screen / first level actually drawn on the GL surface.
|
||||
|
||||
### Phase 2 — RSP / rendering
|
||||
* Bring in the PD `fast3d` (gfx_pc / gfx_opengl / gfx_cc / gfx_sdl2).
|
||||
* Verify the custom CC/RM modes against `gmain.s`; add a `G_SETTEX` decode
|
||||
@@ -829,7 +839,7 @@ gameInit — and currently dies inside `langInit()` (D31).
|
||||
on MinGW, so multi-frame traces depend entirely on the EBP chain. Symbols
|
||||
are recovered offline: `addr2line -e build-pc/ge007.x86_64.exe -f -C
|
||||
<base+rel>` (the log's "rel" = address − actual load base).
|
||||
* **D31 — langInit SIGSEGV: stack frame clobbered by a truncated-pointer table (OPEN).**
|
||||
* **D31 — langInit SIGSEGV: `zlib_huft_build` overflows load_resource's frame via the x86-64-grown `struct huft` (RESOLVED).**
|
||||
After D24–D29, mainThread reaches `langInit()` and dies on the **first** file
|
||||
load (`_fileNameLoadToBank(LnameX_lookuptable[LGUN][…])` → index 670 →
|
||||
`fileIndexLoadToBank` → `mempAllocBytesInBank` → `load_resource`). Crash
|
||||
@@ -853,9 +863,16 @@ gameInit — and currently dies inside `langInit()` (D31).
|
||||
endianness is not an issue in the bitstream). At its entry rz_outbuf =
|
||||
ptrdata ✓, rz_inbuf = source+2 ✓, and the huft table base (tl) sits inside
|
||||
load_resource's 8 KB local `buffer` (rz_hlist).
|
||||
* **Not** a huft-table overflow: max observed `rz_hufts` = 797 entries ×
|
||||
4 B (`struct huft {u8 e; u8 b; union{u16 n; struct huft *t} v;}`) = 3188 B
|
||||
< 8448 B buffer.
|
||||
* **Root cause (confirmed):** `struct huft {u8 e; u8 b; union{u16 n;
|
||||
struct huft *t} v;}` is **8 B on MIPS** (4-byte pointer in the union) but
|
||||
**16 B on x86-64** (8-byte pointer, 8-aligned). GE's gzip-1.2.4 inflate
|
||||
builds its Huffman tables contiguously into load_resource's fixed local
|
||||
`u8 buffer[0x2100]` (8448 B), sized for the 32-bit layout (~1056 entries);
|
||||
on x86-64 only ~528 entries fit, so a stream needing more (observed
|
||||
`rz_hufts` ≈ 797) writes past the buffer and clobbers load_resource's frame
|
||||
— including the return-address slot. The earlier "truncated-pointer table"
|
||||
reading was a misattribution of this same overflow (the 8-byte `{u32,0x1}`
|
||||
slots are huft records spilling over the frame).
|
||||
* Ruled out by code inspection: `mempAddEntryOfSizeToBank` (only touches
|
||||
pool pos/prevpos), `fileGetIndex` (reads only), `decompressdata` epilogue
|
||||
(returns rz_wp).
|
||||
@@ -876,13 +893,95 @@ gameInit — and currently dies inside `langInit()` (D31).
|
||||
syscalls; gdb could not unwind their stacks further), so the writer is not
|
||||
yet identified.
|
||||
|
||||
Next steps: re-run the watchpoint with **`set scheduler-locking on`**
|
||||
(only mainThread runs → any hit is its own) to attribute the write to an
|
||||
exact instruction; if it is load_resource's own spill, that is a GCC
|
||||
miscompilation → compile ob.c at -O0/-Og or add a PORT-gated hook. Also try
|
||||
multiple watchpoints on consecutive slots to catch a sequential table fill
|
||||
mid-flight, and grep the disassembly of the whole chain for stores through
|
||||
s32-typed pointers (`movl …(%reg)` where %reg holds a truncated pointer).
|
||||
**Resolution (fix in `port/`, game code untouched).** Mirrors the Perfect
|
||||
Dark port, which replaces its assembly rzip with a real-zlib C impl
|
||||
(`pd_port/src/lib/rzip_c.c`: `inflateInit2` + `inflate`). We exclude
|
||||
`src/game/decompress.c` + `src/game/zlib.c` from the PC build (CMake
|
||||
`list(REMOVE_ITEM SRC_GAME …)`) and add `port/src/rzdecomp.c`, which backs the
|
||||
two externally-referenced entry points with host zlib:
|
||||
* `decompressdata(src,dst,hlist)` → `inflateInit2(-15)` (raw deflate),
|
||||
`next_in = src+2` (GE's RZ = 2-byte header `0x11 0x72` + raw deflate, no
|
||||
size field), loop on `Z_OK`, return `total_out`.
|
||||
* `rzipGetSomething()` → returns the stored `next_in` (consumed input).
|
||||
A generated `port/include/realzlib.h` (from `realzlib.h.in`) `#include`s the
|
||||
**absolute** host `<zlib.h>` path, because the game's `src/game/zlib.h`
|
||||
shadows `<zlib.h>` on the `-I` path.
|
||||
|
||||
**Verified:** rebuild links clean (238/238); the exe has the new
|
||||
`decompressdata`/`rzipGetSomething` and no `zlib_inflate`/`zlib_huft_build`.
|
||||
Under gdb, mainThread now boots **past langInit** (all language files load)
|
||||
all the way into `bossInitMainthreadData()` (boss.c:233) →
|
||||
`initWeaponAnimGroups` — i.e. the original first-file-load SIGSEGV is gone.
|
||||
The next blocker is D32.
|
||||
|
||||
* **D32 — ROM-serialized structs with embedded pointers have divergent N64/x86-64 layout (OPEN).**
|
||||
After the D31 fix, mainThread reaches `bossInitMainthreadData()` and dies in
|
||||
`init_weapon_animation_groups_maybe()` (boss.c:233) → `initWeaponAnimGroups`
|
||||
→ … → `modelAnimReadRootMotionValue` (model.c:914), faulting on
|
||||
`desc->bitCount` where `desc = anim->bitDescriptors`.
|
||||
|
||||
**Mechanism.** N64 pointers are 32-bit; x86-64 pointers are 64-bit with
|
||||
8-byte alignment. `struct ModelAnimation` (src/bondtypes.h:575) declares
|
||||
`ModelAnimBitField *bitDescriptors` and `u8 *bitStream`. In the N64 ROM
|
||||
layout these are 4-byte fields at 0x08 and 0x10; on x86-64 the compiler lays
|
||||
them out as 8-byte fields — `ptype /o` (gdb) shows bitDescriptors @0x08, a
|
||||
4-byte hole, then bitStream @**0x18**, sizeof = **80** (vs 64 on N64). The
|
||||
animation data is loaded by `alloc_load_expand_ani_table()`
|
||||
(initanitable.c:263) via `romCopy(ptr_animation_table,
|
||||
&_animation_dataSegmentRomStart, size)` — raw N64-layout bytes.
|
||||
`expand_ani_table_entries()` rebases the **low 32 bits** of
|
||||
bitDescriptors/bitStream but (a) leaves the high 32 bits as adjacent-field
|
||||
junk and (b) writes bitStream's rebase to offset 0x10, which on x86-64 is
|
||||
*not* where the C struct reads bitStream (0x18). Observed under gdb:
|
||||
anim = 0x702adad4 (base 0x702ad8c0 + 0x214), `*(u32*)(anim+8)` = 0xc82bd8c0 →
|
||||
as a 64-bit pointer the high word is junk → SIGSEGV.
|
||||
|
||||
**Scope.** Any ROM-serialized struct that (i) contains a pointer field and
|
||||
(ii) has other fields after it misreads the same way: each 64-bit pointer's
|
||||
high word holds adjacent-field bytes, and every post-pointer field shifts by
|
||||
the pointer-width delta. ModelAnimation is the first hit; bondtypes.h has ~13
|
||||
struct blocks with pointer fields (fewer are actually romCopy'd from ROM).
|
||||
|
||||
**PD ground truth.** PD keeps its ROM-data structs at N64 layout on both
|
||||
targets by storing embedded pointers as **u32** — e.g. `struct animtableentry
|
||||
{ … u32 data; }` (pd_port/src/include/types.h:5124) — and casting to a real
|
||||
pointer at the use site. GE's decomp instead uses real pointers in
|
||||
ModelAnimation, so it diverges on x86-64.
|
||||
|
||||
**Resolution — Option 1 chosen (user-approved), in progress.** The user
|
||||
approved the PD pattern after confirming PD does it (`animtableentry.u32 data`,
|
||||
raw-DMA'd then resolved at use sites). Note PD's *exact* mechanism (u32 ROM
|
||||
offset + on-demand DMA cache) differs from GE's rebased-DRAM-pointer
|
||||
ModelAnimation, so we adopt the **principle** (u32 embedded address, cast at
|
||||
use), not PD's code. This required refining non-negotiable #2 (see §H).
|
||||
|
||||
**Part A — struct layout: DONE & proven.** Changed `ModelAnimation`'s two
|
||||
pointer fields to u32 (src/bondtypes.h:575): `u32 bitDescriptors` @0x08,
|
||||
`u32 bitStream` @0x10; added casts at the only use sites (model.c:913/921).
|
||||
`ptype /o` now shows sizeof = **64** with fields at the N64 offsets — layout
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
### G. Phase 2 status (current)
|
||||
|
||||
@@ -892,8 +991,14 @@ kernel (D24); dual-mapped DRAM + address shims (D25/D26); ASLR off with startup
|
||||
sanity check (D27); PI completion messages (D29); SEH-free crash handler with
|
||||
thread dumps (D30). The process now boots, maps the ROM, opens the window,
|
||||
runs `mainproc()` → `bossInitMainthreadData` through controller init / timers /
|
||||
mempool / VI / rsp / stan / game init — and SIGSEGVs inside `langInit()`'s
|
||||
first file load (D31). No frames rendered yet.
|
||||
mempool / VI / rsp / stan / game init. The D31 langInit file-load SIGSEGV is
|
||||
**fixed** (real-zlib decompression, §F/D31): mainThread now loads all language
|
||||
files and proceeds into `bossInitMainthreadData()` → `alloc_load_expand_ani_table`
|
||||
→ `init_weapon_animation_groups_maybe` (boss.c:233), where it SIGSEGVs on the
|
||||
**D32** ROM-struct pointer-width issue. D32 is decomposed: **Part A** (struct
|
||||
layout — u32 pointer fields) is DONE & proven; **Part B** (the load-time pointer
|
||||
rebase in `expand_ani_table_entries`) is OPEN and is the current blocker. No
|
||||
frames rendered yet. See §H for the handoff + plan.
|
||||
|
||||
Reference — the file-load chain (for D31 debugging):
|
||||
`langInit` (language.c:230) loads 7 text banks via
|
||||
@@ -918,3 +1023,58 @@ function complete and inspect its return; hardware watchpoints on exact stack
|
||||
slots (compute offsets from entry RSP, not RBP — see D31); `addr2line` for
|
||||
offline symbolication; objdump + `gcc -E` with exact ninja flags to verify
|
||||
shim expansion in compiled code.
|
||||
|
||||
### H. Handoff & plan (current session)
|
||||
|
||||
**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.
|
||||
|
||||
**D32 repeatable fix procedure** (apply to any ROM-serialized struct that faults
|
||||
on a pointer-field read):
|
||||
1. At the fault, `ptype /o <Struct>` in gdb. If a pointer field's offset/size
|
||||
diverges from the N64 offset comment (e.g. an 8-byte pointer where N64 has 4),
|
||||
it is this bug class.
|
||||
2. Change the embedded pointer fields to `u32` in the struct (keeps N64 layout on
|
||||
x86-64). Add casts at every use site (`(T *)field`). Document as D3x.
|
||||
3. Verify the load-time rebase/fixup writes valid **V1** DRAM addresses (< 0x80000000)
|
||||
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.
|
||||
|
||||
**Non-negotiable #2 refinement (applied to AGENTS.md).** The original "game code
|
||||
compiles unmodified / fix belongs in port/" is too absolute: pointer-width layout
|
||||
cannot be isolated in `port/` (no hook between the romCopy and the first read).
|
||||
Refined to "game **logic** is unmodified" with a narrow, documented exception for
|
||||
mechanical, semantics-preserving ABI/layout changes forced by the 32→64-bit
|
||||
transition (embedded pointers → u32 + cast at use), following PD ground truth. No
|
||||
logic/behavior changes; each such edit is logged in §F/D3x.
|
||||
|
||||
**Reassessed plan.** The frontier is now **Phase 1.5 — boot to first frame**:
|
||||
drive mainThread through the rest of `bossInitMainthreadData` + `bossEntry` to
|
||||
`bossMainloop()` and render frame 1, using the D32 procedure each time a
|
||||
ROM-data struct faults. Phase 2 (fast3d/CC-RM/scheduler) then makes those frames
|
||||
correct. Expect several more D3x ABI fixes (models, textures, other tables) as
|
||||
more asset loading is reached — this is now a standing sub-task, not a one-off.
|
||||
|
||||
**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); 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 did **not** write `ge007.crash.log` for the game-thread
|
||||
SIGSEGV this session (attribute via gdb instead; worth a separate look).
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef PORT_REALZLIB_H
|
||||
#define PORT_REALZLIB_H
|
||||
/* Generated by CMake from realzlib.h.in — do not edit.
|
||||
* Includes the HOST <zlib.h> by absolute path so port/src/rzdecomp.c can use
|
||||
* the real zlib (inflateInit2/inflate) without picking up the game's own
|
||||
* src/game/zlib.h, which sits on the -I search path and shadows a plain
|
||||
* `#include <zlib.h>` in a port TU. Same absolute-path trick as hoststring.h.
|
||||
* See docs/PCPortResearch.md §F (D32). */
|
||||
#include "@PORT_REAL_ZLIB_H@"
|
||||
#endif
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* PC-port replacement for src/game/decompress.c + src/game/zlib.c, which are
|
||||
* EXCLUDED from the build (see CMakeLists.txt and docs/PCPortResearch.md §F D32).
|
||||
*
|
||||
* GE's RZ files are: a 2-byte header (0x11 0x72) followed by a raw deflate
|
||||
* stream that ends with an end-of-block marker. The original decompressdata()
|
||||
* skipped the 2-byte header and ran a hand-rolled gzip-1.2.4 inflate whose
|
||||
* Huffman tables were built contiguously into a caller-supplied fixed buffer of
|
||||
* `struct huft`. That struct is 8 bytes on MIPS (the union holds a 32-bit
|
||||
* pointer) but 16 bytes on x86-64 (the union holds a 64-bit pointer), so on a
|
||||
* 64-bit host the tables overflow load_resource's `u8 buffer[0x2100]` and clobber
|
||||
* the caller's return address -> SIGSEGV in langInit()'s first file load.
|
||||
*
|
||||
* Backing decompressdata() with real zlib removes the fixed-buffer assumption
|
||||
* entirely: zlib allocates and manages its own table memory. This mirrors the
|
||||
* Perfect Dark port, which swaps its assembly rzip for a real-zlib-backed
|
||||
* rzip_c.c. The only externally-referenced symbols from the two excluded files
|
||||
* are decompressdata() and rzipGetSomething(); both are defined here.
|
||||
*/
|
||||
|
||||
#include <ultra64.h>
|
||||
#include "realzlib.h" // host zlib by absolute path (CMake-generated)
|
||||
|
||||
struct huft; // opaque; only used as a pointer parameter
|
||||
|
||||
/* Input position just past the last consumed compressed byte, set by the most
|
||||
* recent decompressdata(). image.c uses it to locate the next texture section. */
|
||||
static u8 *s_rz_nextin = NULL;
|
||||
|
||||
u32 decompressdata(u8 *src, u8 *dst, struct huft *huffman_table)
|
||||
{
|
||||
(void)huffman_table; // real zlib allocates its own tables; arg ignored
|
||||
|
||||
z_stream strm = {0};
|
||||
|
||||
/* Raw deflate: windowBits=-15 tells zlib to expect no zlib/gzip wrapper. */
|
||||
if (inflateInit2(&strm, -15) != Z_OK) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Skip the 2-byte RZ header; the deflate stream starts at src+2. The
|
||||
* compressed size is not in the header, so bound avail_in generously —
|
||||
* inflate stops at the end-of-block marker regardless, and src points into
|
||||
* mapped DRAM (the mempool), so a short over-read is harmless and never
|
||||
* reached for valid data. */
|
||||
strm.next_in = src + 2;
|
||||
strm.avail_in = 0x400000; /* 4 MiB ceiling, far above any single RZ file */
|
||||
|
||||
u32 produced = 0;
|
||||
int ret;
|
||||
do {
|
||||
strm.next_out = dst + produced;
|
||||
strm.avail_out = 0x400000; /* caller sized dst for the full output (as on N64) */
|
||||
ret = inflate(&strm, Z_FINISH);
|
||||
produced = strm.total_out;
|
||||
} while (ret == Z_OK);
|
||||
|
||||
s_rz_nextin = strm.next_in; /* just past the compressed data */
|
||||
inflateEnd(&strm);
|
||||
return produced;
|
||||
}
|
||||
|
||||
s32 rzipGetSomething(void)
|
||||
{
|
||||
return (s32)(uintptr_t)s_rz_nextin;
|
||||
}
|
||||
+3
-2
@@ -578,10 +578,11 @@ typedef union
|
||||
u16 unk04; // number of frames in the animation
|
||||
u8 unk06;
|
||||
u8 unk07; // bit 0 is a loop flag: 0 means freeze anim at end, 1 means loop anim
|
||||
ModelAnimBitField *bitDescriptors; // 0x08
|
||||
u32 bitDescriptors; // 0x08 (PC port D32: was `ModelAnimBitField *`; stored as u32 so the
|
||||
// ROM-serialized layout matches N64 on x86-64 — cast at use sites)
|
||||
u16 unk0C;
|
||||
u16 unk0E;
|
||||
u8 *bitStream; // 0x10
|
||||
u32 bitStream; // 0x10 (PC port D32: was `u8 *`; see bitDescriptors note above)
|
||||
s32 unk14;
|
||||
s32 unk18;
|
||||
s32 unk1c;
|
||||
|
||||
+2
-2
@@ -910,7 +910,7 @@ u16 modelAnimReadRootMotionValue(ModelAnimation *anim, s32 fieldIndex, s32 extra
|
||||
u8 bitsThisRead;
|
||||
|
||||
result = 0;
|
||||
desc = anim->bitDescriptors + fieldIndex;
|
||||
desc = (ModelAnimBitField *)anim->bitDescriptors + fieldIndex; // D32: u32 -> ptr
|
||||
bitsRemaining = desc->bitCount;
|
||||
|
||||
if (bitsRemaining > 0)
|
||||
@@ -918,7 +918,7 @@ u16 modelAnimReadRootMotionValue(ModelAnimation *anim, s32 fieldIndex, s32 extra
|
||||
totalBitOffset = extraBitOffset + desc->bitOffset;
|
||||
byteIndex = totalBitOffset >> 3;
|
||||
totalBitOffset &= 7;
|
||||
byteptr = anim->bitStream + byteIndex;
|
||||
byteptr = (u8 *)anim->bitStream + byteIndex; // D32: u32 -> ptr
|
||||
bitsThisRead = 8 - totalBitOffset;
|
||||
|
||||
if (bitsRemaining >= bitsThisRead)
|
||||
|
||||
Reference in new issue
Block a user