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:
James Dansereau committed 2026-08-21 16:01:49 -04:00
1 parent 13ca03a6db
commit dbeee7353f
7 files changed
+292 -21

No files matched your search

+4 -4
View File
@@ -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.
+34
View File
@@ -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
View File
@@ -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).
+10
View File
@@ -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
+66
View File
@@ -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
View File
@@ -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
View File
@@ -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)