diff --git a/AGENTS.md b/AGENTS.md index d06474ca..03a85a6c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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_.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. diff --git a/CMakeLists.txt b/CMakeLists.txt index eaca134e..6963b837 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,6 +266,23 @@ if(ZLIB_INCLUDE_DIR) endif() list(APPEND LIBS "${ZLIB_LIBRARY}") +# Real 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 ` 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 diff --git a/docs/PCPortResearch.md b/docs/PCPortResearch.md index 2f5661df..17d96b26 100644 --- a/docs/PCPortResearch.md +++ b/docs/PCPortResearch.md @@ -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 ` (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 `` path, because the game's `src/game/zlib.h` + shadows `` 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 ` 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). diff --git a/port/include/realzlib.h.in b/port/include/realzlib.h.in new file mode 100644 index 00000000..f204f948 --- /dev/null +++ b/port/include/realzlib.h.in @@ -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 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 ` in a port TU. Same absolute-path trick as hoststring.h. + * See docs/PCPortResearch.md §F (D32). */ +#include "@PORT_REAL_ZLIB_H@" +#endif diff --git a/port/src/rzdecomp.c b/port/src/rzdecomp.c new file mode 100644 index 00000000..840ad55d --- /dev/null +++ b/port/src/rzdecomp.c @@ -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 +#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; +} diff --git a/src/bondtypes.h b/src/bondtypes.h index 80397242..b6300422 100644 --- a/src/bondtypes.h +++ b/src/bondtypes.h @@ -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; diff --git a/src/game/model.c b/src/game/model.c index 955b6a1f..6cff5829 100644 --- a/src/game/model.c +++ b/src/game/model.c @@ -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)