PC port: Phase 1 boot-to-window (ROM load + map + SDL2 clear loop)

- scripts/gen_romassets.py: emit absolute cart-address asset symbols
  (0x10000000 + rom_offset) from filelist.<r>.csv + the asset .s files,
  in file_resource_table.inc.c order. US region (romassets_u.s).
- romdata.c: load/validate the GE-specific header (magic, name @0x20,
  cart ID @0x3C, country @0x3E) and map the .z64 at cart base 0x10000000
  so every &asset-symbol is a live host pointer.
- libultra.c: osPiStartDma/osPiRawStartDma now service reads as
  bounds-checked memcpys from the mapped ROM (synchronous completion).
- system.c: implement sysResolvePath ($S data dir, $E exe dir).
- video.c: real SDL2 window + GL context + clear/swap frame loop with
  FPS accounting; main.c runs a Phase 1 demo loop (ESC quits) until the
  software RSP/scheduler can drive mainproc().
- CMakeLists: project(... ASM), wire romassets_<region>.s, drop
  assetstubs.c (replaced by generated symbols); n64stubs.c keeps only
  pure-RAM segment markers.
- docs: findings D18-D23 (header offsets, BE music table, .rz tracks,
  scanner-prefixed manifest names, CMake ASM gotcha, absolute-symbol
  approach).

Verified: baserom.u.z64 (12MB) passes validation and maps at 0x10000000;
window opens and renders until killed; asset offsets spot-checked against
ROM bytes (music bank seqCount=63 BE, first track offset 508).
This commit is contained in:
James Dansereau committed 2026-08-20 22:19:45 -04:00
1 parent 5ea46fabc1
commit 2aad171344
14 files changed
+4462 -971

No files matched your search

+1 -1
View File
@@ -45,7 +45,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):** remaining — ROM loading in `romdata.c` (currently a TODO), run the binary, wire gfx stubs to a real window.
- **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 3 (audio + input):** libaudio → SDL; decide the ASP strategy (C2: `aspMain*` dummies in `ucode.c`).
+21 -5
View File
@@ -44,7 +44,9 @@ if(APPLE)
endif()
endif()
project(ge007 C CXX)
# ASM is needed for the generated absolute ROM asset symbols
# (port/src/romassets_<region>.s, see scripts/gen_romassets.py).
project(ge007 C CXX ASM)
include(TargetArch)
include(FindPkgConfig)
@@ -99,18 +101,21 @@ message(STATUS "Target platform: ${TARGET_PLATFORM}")
if(ROMID STREQUAL "ntsc-final")
set(VERSION_IS_PAL 0)
set(BIN_REGION "")
set(ASSET_REGION "u")
set(REGION_DEFS
-DVERSION_US -DLANG_US -DREFRESH_NTSC
-DLEFTOVERDEBUG -DLEFTOVERSPECTRUM -DBUGFIX_R0 -DBYTEMATCH)
elseif(ROMID STREQUAL "pal-final")
set(VERSION_IS_PAL 1)
set(BIN_REGION ".pal")
set(ASSET_REGION "e")
set(REGION_DEFS
-DVERSION_EU -DLANG_EU -DREFRESH_PAL
-DBUGFIX_R1 -DBUGFIX_R2 -DBYTEMATCH)
elseif(ROMID STREQUAL "jpn-final")
set(VERSION_IS_PAL 0)
set(BIN_REGION ".jpn")
set(ASSET_REGION "j")
set(REGION_DEFS
-DVERSION_JP -DLANG_JP -DREFRESH_NTSC
-DBUGFIX_R1 -DLEFTOVERDEBUG -DLEFTOVERSPECTRUM -DBYTEMATCH)
@@ -255,6 +260,15 @@ file(GLOB_RECURSE SRC_PORT "${CMAKE_SOURCE_DIR}/port/src/*.c")
file(GLOB_RECURSE SRC_PORT_CXX "${CMAKE_SOURCE_DIR}/port/src/*.cpp")
file(GLOB_RECURSE SRC_FAST3D "${CMAKE_SOURCE_DIR}/port/fast3d/*.cpp")
# Generated absolute ROM asset symbols for this region (see
# scripts/gen_romassets.py). Defines every obseg/ramrom/music label and the
# ge007.ld segment markers as 0x10000000 + rom_offset, so `&symbol` / romCopy()
# work once romdata.c maps the .z64 at 0x10000000.
set(SRC_ROMASSETS "${CMAKE_SOURCE_DIR}/port/src/romassets_${ASSET_REGION}.s")
if(NOT EXISTS ${SRC_ROMASSETS})
message(FATAL_ERROR "Missing ${SRC_ROMASSETS} — run: python3 scripts/gen_romassets.py ${ASSET_REGION}")
endif()
# Game code — compiled unmodified.
file(GLOB_RECURSE SRC_GAME "${CMAKE_SOURCE_DIR}/src/game/*.c")
@@ -296,10 +310,11 @@ list(PREPEND SRC_LIBULTRARE_LIBC "${CMAKE_SOURCE_DIR}")
# `intro`, ...) that the N64 build keeps apart by linking every file into its
# own ELF (see assets/obseg/Makefile.*). In a single host link they collide.
# The game loads those blobs from ROM at runtime anyway; their top-level
# symbols (C*Z, G*Z, P*Z, Tbg_*Z, U*Z, L*E/L*J, bg_*_all_p_seg, ...) are
# stubbed in port/src/assetstubs.c until Phase 2 ROM loading. The per-model
# data itself (chr/*/Model.c etc.) is gitignored ROM-derived and absent from
# this checkout. See docs/PCPortResearch.md §11.
# symbols (C*Z, G*Z, P*Z, Tbg_*Z, U*Z, L*E/L*J, bg_*_all_p_seg, ...) are now
# absolute cart addresses in the generated romassets_<region>.s (see
# scripts/gen_romassets.py), backed by the ROM image that romdata.c maps at
# 0x10000000. The per-model data itself (chr/*/Model.c etc.) is gitignored
# ROM-derived and absent from this checkout. See docs/PCPortResearch.md §11.
set(SRC_ASSETS
assets/animationtable_data.c
assets/animationtable_entries.c
@@ -361,6 +376,7 @@ list(PREPEND SRC_ENGINE "${CMAKE_SOURCE_DIR}")
set(SRC
${SRC_GAME}
${SRC_PORT}
${SRC_ROMASSETS}
${SRC_PORT_CXX}
${SRC_FAST3D}
${SRC_LIBAUDIO}
+63 -5
View File
@@ -665,13 +665,71 @@ in the same change. Where a fix could not be made purely in `port/`, the minimal
implementations of every entry point (plus `gfx_current_dimensions`) so
video.c links. **Delete it when the real fast3d sources are added** (same
symbols → duplicate-definition error if both remain).
* **D18 — GE ROM header offsets.** The N64 header is GE-specific
(`src/rom_header.s`): magic `0x80371240` @0x00, ROM name "GOLDENEYE" @0x20,
cartridge ID "GE" **@0x3C**, country byte **@0x3E** ('E' US / 'P' EU /
'J' JP), version @0x3F. The standard-N64 offsets (ID @0x38, country @0x3A)
are wrong for GE — verified against `baserom.u.z64` (hexdump: `... 47 45
45 00` at 0x3C–0x3F). romdata.c validates all four fields.
* **D19 — music table is big-endian in the ROM.** The `.music` section of
`assets/music/music.s` (the `RareALSeqBankFile` header + 63 × 8-byte
`music_table_entry` records) is stored in the ROM in **big-endian** word
order: at 0x419790 the bytes are `00 3f 00 00 | 00 00 01 fc` = BE u16
seqCount=63 (= NUM_MUSIC_TRACKS), BE u16 unk=0, then BE u32 offset 508 for
the first track. The N64 build evidently assembles that section with
big-endian output; the PC port never reassembles it — the CSV offsets are
unaffected and `musicSeqPlayerInit()`'s romCopy + `musicSeqFileNew()`
patching work on the raw bytes unchanged.
* **D20 — US music tracks are RLE-compressed in ROM.** The `music_file`
macro incbins `build/u/assets/music/<name>.rz` under `.ifdef VERSION_US`, so
every M* track lives in the ROM 1172/RLE-compressed; the filelist sizes are
the compressed lengths and per-track `end_` symbols = start + csv_size are
correct boundaries. Decompression happens at playback (Phase 3 audio), not
at load — no Phase-1/2 action needed.
* **D21 — scanner-prefixed manifest names.** `filelist.<r>.csv` contains
entries whose file column is prefixed with the scan origin, e.g.
`assets/ge007.u.117880.jfont_dl.bin` (real name `jfont_dl.bin`).
gen_romassets.py aliases such names to their suffix after the
`ge007.<r>.<hexoffset>.` prefix, so font/image/oddtexture segments resolve.
* **D22 — CMake silently skips `.s` files without ASM enabled.** The first
assembly source in the PC build (`romassets_u.s`) was added to the target
but never compiled: `project(ge007 C CXX)` had no ASM language, and CMake
does not error on unknown extensions — it just drops them, surfacing only as
a wall of undefined asset symbols at link. Fixed with
`project(ge007 C CXX ASM)`. Lesson: when adding a new source *extension*,
verify the object file actually appears in the build graph.
* **D23 — absolute cart-address asset symbols (Phase 1 approach).** Instead of
zeroed stubs (D16), every ROM asset symbol is now an ABSOLUTE cart address:
`scripts/gen_romassets.py` emits `port/src/romassets_<r>.s` defining all
obseg labels (in `file_resource_table.inc.c` order — ob.c computes file
sizes as `table[i+1].hw_address - table[i].hw_address`, so order matters),
ramrom files, music tracks/markers, and the ge007.ld segment markers as
`0x10000000 + rom_offset` from `filelist.<r>.csv`. romdata.c maps the .z64
at exactly 0x10000000 (VirtualAlloc preferred address), so `&symbol` is a
live host pointer and the PI shims (`osPiStartDma`/`osPiRawStartDma` in
libultra.c) service reads as bounds-checked memcpys. This makes both DMA'd
assets (models, banks) and direct-read assets (fonts, image display lists)
work with unmodified game code. `assetstubs.c` is deleted; n64stubs.c keeps
only the pure-RAM segment symbols (`_bssSegmentEnd`, `_csegment*`,
`_inflate/_gameSegmentVaddr*`). US-only for now: EU/JPN manifests have
naming inconsistencies (trailing-Z add/drop, region subdirs) that need the
same treatment before those regions build.
### E. Compile + link milestone (status)
All ~235 translation units now compile and the target **links**: clean build
from scratch is `236/236` steps, zero errors (`ninja ge007 -k 0`), producing
All ~235 translation units compile and the target **links**: clean build from
scratch is `236/236` steps, zero errors (`ninja ge007 -k 0`), producing
`ge007.x86_64.exe`. Remaining warnings (~4.5k) are expected IDO-leniency noise
(int-conversion, implicit declarations, etc.), demoted via the CMake warning
flags. The binary is not yet runnable end-to-end: gfx is stubbed (D17), asset
model/level data is zeroed (D16), and ROM loading is still a Phase-2 TODO in
romdata.c.
flags.
**Phase 1 (boot to window) is done:** `romdata.c` loads and validates the ROM
(`baserom.u.z64` at the repo root, or a final .z64 in `data/` / next to the
exe), maps it at cart base 0x10000000 (D23); `video.c` opens an SDL2 640×480
window with a GL context and clears/presents every frame; `main.c` runs a
demo loop (ESC quits) in place of `mainproc()`, which is deferred until the
software RSP + scheduler can service real frames. Verified: ROM header passes
validation, 12 MB mapped at 0x10000000, window renders for the full test
duration. Asset symbol spot-checks against the ROM confirm correct offsets
(e.g. music bank BE seqCount=63 @0x419790, first track offset 508 → Mno_music
@0x41998C; sfx/instrument banks start with GE's `B1` header).
+8 -1
View File
@@ -24,11 +24,18 @@ extern "C" {
int romdataInit(void);
void romdataDestroy(void);
/* Pointer to the loaded ROM image (start of the .z64). */
/* Pointer to the loaded ROM image (start of the .z64). When the image is
* mapped at the N64 cart base this is 0x10000000 itself. */
const u8 *romdataGetRom(void);
/* Size of the loaded ROM in bytes. */
u32 romdataGetRomSize(void);
/*
* True if [addr, addr+size) is a valid cart address (0x10000000+) inside the
* mapped image. The PI shims call this before servicing DMA.
*/
int romdataCartAddrValid(u32 addr, u32 size);
/*
* Map an N64 virtual address (0xA0000000 RDRAM space) to the corresponding
* pointer in the loaded image. Used to resolve asset/segment references.
+4
View File
@@ -26,6 +26,10 @@ void videoStartFrame(void);
void videoSubmitCommands(Gfx *cmds); /* runs the software RSP on the list */
void videoEndFrame(void);
/* Pump window events; returns 1 when the app should quit. Used by the
* Phase 1 demo loop until the scheduler owns the frame boundary. */
int videoHandleEvents(void);
/* VSync / framerate control. */
void videoSetVsync(int enabled);
void videoSetFramerateLimit(int fps); /* 0 = unlimited */
-816
View File
@@ -1,816 +0,0 @@
/*
* Link stubs for ROM asset data that is NOT present in this checkout.
*
* The N64 build gets these symbols from assets/obseg/ob_seg.s and the per-
* category .s files: each model/level file is compressed to a .rz blob and
* .incbin'ed into the ROM at link time (the blobs are gitignored, ROM-
* derived). The decompiled C sources for them (chr/<name>/Model.c etc.)
* are also absent. ob.c's file_resource_table references every one of these symbols
* by address, so they must exist to link.
*
* Each is a single zero word: just enough to satisfy the linker and any
* &symbol reference. The game never meaningfully reads them in Phase 1;
* real data arrives with ROM loading in Phase 2 (romdata.c), which will also
* replace file_resource_table's address arithmetic. See
* docs/PCPortResearch.md section 11.
*/
#include <PR/ultratypes.h>
/* --- Character models (C*Z) --------------------------------------------- */
u32 CarmourguardZ[1] = {0};
u32 CbaronsamediZ[1] = {0};
u32 CbluecamguardZ[1] = {0};
u32 CbluemanZ[1] = {0};
u32 CbluewomanZ[1] = {0};
u32 CboilerbondZ[1] = {0};
u32 CboilertrevZ[1] = {0};
u32 CborisZ[1] = {0};
u32 CcamguardZ[1] = {0};
u32 CcardimanZ[1] = {0};
u32 CcheckmanZ[1] = {0};
u32 CcommguardZ[1] = {0};
u32 CdjbondZ[1] = {0};
u32 CfattechwomanZ[1] = {0};
u32 Cgreatguard2Z[1] = {0};
u32 CgreatguardZ[1] = {0};
u32 CgreyguardZ[1] = {0};
u32 CgreymanZ[1] = {0};
u32 CheadalanZ[1] = {0};
u32 CheadbZ[1] = {0};
u32 CheadbalaclavaZ[1] = {0};
u32 CheadbikeZ[1] = {0};
u32 CheadbrosnanZ[1] = {0};
u32 CheadbrosnanboilerZ[1] = {0};
u32 CheadbrosnansnowZ[1] = {0};
u32 CheadbrosnansuitZ[1] = {0};
u32 CheadbrosnantimberZ[1] = {0};
u32 CheadchrisZ[1] = {0};
u32 CheaddaveZ[1] = {0};
u32 CheaddesZ[1] = {0};
u32 CheadduncanZ[1] = {0};
u32 CheaddwayneZ[1] = {0};
u32 CheadgrahamZ[1] = {0};
u32 CheadgrantZ[1] = {0};
u32 CheadjimZ[1] = {0};
u32 Cheadjoe2Z[1] = {0};
u32 CheadjoeZ[1] = {0};
u32 CheadjoelZ[1] = {0};
u32 CheadkarlZ[1] = {0};
u32 CheadkenZ[1] = {0};
u32 CheadleeZ[1] = {0};
u32 CheadmandyZ[1] = {0};
u32 CheadmarionZ[1] = {0};
u32 CheadmarkZ[1] = {0};
u32 CheadmartinZ[1] = {0};
u32 CheadmishkinZ[1] = {0};
u32 CheadneilZ[1] = {0};
u32 CheadpeteZ[1] = {0};
u32 CheadrobinZ[1] = {0};
u32 CheadsallyZ[1] = {0};
u32 CheadscottZ[1] = {0};
u32 CheadshaunZ[1] = {0};
u32 CheadsteveeZ[1] = {0};
u32 CheadstevehZ[1] = {0};
u32 CheadvivienZ[1] = {0};
u32 CjawsZ[1] = {0};
u32 CjeanwomanZ[1] = {0};
u32 CmaydayZ[1] = {0};
u32 CmoonfemaleZ[1] = {0};
u32 CmoonguardZ[1] = {0};
u32 CnatalyaZ[1] = {0};
u32 CnavyguardZ[1] = {0};
u32 CoddjobZ[1] = {0};
u32 ColiveguardZ[1] = {0};
u32 CorumovZ[1] = {0};
u32 CpilotZ[1] = {0};
u32 CredmanZ[1] = {0};
u32 CrusguardZ[1] = {0};
u32 CsnowbondZ[1] = {0};
u32 CsnowguardZ[1] = {0};
u32 CspicebondZ[1] = {0};
u32 Csuit_lf_handZ[1] = {0};
u32 CsuitbondZ[1] = {0};
u32 CtechmanZ[1] = {0};
u32 CtechwomanZ[1] = {0};
u32 CtimberbondZ[1] = {0};
u32 CtrevelyanZ[1] = {0};
u32 CtrevguardZ[1] = {0};
u32 CvalentinZ[1] = {0};
u32 CxeniaZ[1] = {0};
/* --- Gun / prop-object models (G*Z) ---------------------------------- */
u32 Gak47Z[1] = {0};
u32 GaudiotapeZ[1] = {0};
u32 GautoshotZ[1] = {0};
u32 GblackboxZ[1] = {0};
u32 GblueprintsZ[1] = {0};
u32 GbombcaseZ[1] = {0};
u32 GbombdefuserZ[1] = {0};
u32 GbriefcaseZ[1] = {0};
u32 GbugZ[1] = {0};
u32 GbugdetectorZ[1] = {0};
u32 GbungeeZ[1] = {0};
u32 GcameraZ[1] = {0};
u32 GcartblueZ[1] = {0};
u32 GcartridgeZ[1] = {0};
u32 GcartrifleZ[1] = {0};
u32 GcartshellZ[1] = {0};
u32 GcircuitboardZ[1] = {0};
u32 GclipboardZ[1] = {0};
u32 GcreditcardZ[1] = {0};
u32 GdarkglassesZ[1] = {0};
u32 GdatathiefZ[1] = {0};
u32 GdattapeZ[1] = {0};
u32 GdoordecoderZ[1] = {0};
u32 GdoorexploderZ[1] = {0};
u32 GdossierredZ[1] = {0};
u32 GdynamiteZ[1] = {0};
u32 GexplosivefloppyZ[1] = {0};
u32 GexplosivepenZ[1] = {0};
u32 GextinguisherZ[1] = {0};
u32 GfingergunZ[1] = {0};
u32 GfistZ[1] = {0};
u32 GflarepistolZ[1] = {0};
u32 Gfnp90Z[1] = {0};
u32 GgaskeyringZ[1] = {0};
u32 GgoldbarZ[1] = {0};
u32 GgoldeneyekeyZ[1] = {0};
u32 GgoldengunZ[1] = {0};
u32 GgoldwppkZ[1] = {0};
u32 GgrenadeZ[1] = {0};
u32 GgrenadelaunchZ[1] = {0};
u32 GheroinZ[1] = {0};
u32 GjoypadZ[1] = {0};
u32 GkeyanalysercaseZ[1] = {0};
u32 GkeyboltZ[1] = {0};
u32 GkeycardZ[1] = {0};
u32 GkeyyaleZ[1] = {0};
u32 GknifeZ[1] = {0};
u32 GlaserZ[1] = {0};
u32 GlectreZ[1] = {0};
u32 GlockexploderZ[1] = {0};
u32 Gm16Z[1] = {0};
u32 GmapZ[1] = {0};
u32 GmicrocameraZ[1] = {0};
u32 GmicrocodeZ[1] = {0};
u32 GmicrofilmZ[1] = {0};
u32 GmoneyZ[1] = {0};
u32 Gmp5kZ[1] = {0};
u32 Gmp5ksilZ[1] = {0};
u32 GpitongunZ[1] = {0};
u32 GplansZ[1] = {0};
u32 GplastiqueZ[1] = {0};
u32 GpolarizedglassesZ[1] = {0};
u32 GproximitymineZ[1] = {0};
u32 GremotemineZ[1] = {0};
u32 GrocketlaunchZ[1] = {0};
u32 GrugerZ[1] = {0};
u32 GsafecrackercaseZ[1] = {0};
u32 GshotgunZ[1] = {0};
u32 GsilverwppkZ[1] = {0};
u32 GskorpionZ[1] = {0};
u32 GsniperrifleZ[1] = {0};
u32 GspectreZ[1] = {0};
u32 GspooltapeZ[1] = {0};
u32 GspyfileZ[1] = {0};
u32 GstafflistZ[1] = {0};
u32 GtaserZ[1] = {0};
u32 GthrowknifeZ[1] = {0};
u32 GtimedmineZ[1] = {0};
u32 GtriggerZ[1] = {0};
u32 Gtt33Z[1] = {0};
u32 GuziZ[1] = {0};
u32 GvideotapeZ[1] = {0};
u32 GwatchcommunicatorZ[1] = {0};
u32 GwatchgeigercounterZ[1] = {0};
u32 GwatchidentifierZ[1] = {0};
u32 GwatchlaserZ[1] = {0};
u32 GwatchmagnetattractZ[1] = {0};
u32 GwatchmagnetrepelZ[1] = {0};
u32 GweaponcaseZ[1] = {0};
u32 GwppkZ[1] = {0};
u32 GwppksilZ[1] = {0};
u32 GwristdartZ[1] = {0};
/* --- Prop models (P*Z) ------------------------------------------------- */
u32 PICBMZ[1] = {0};
u32 PICBM_noseZ[1] = {0};
u32 Pak47magZ[1] = {0};
u32 Palarm1Z[1] = {0};
u32 Palarm2Z[1] = {0};
u32 Pammo_crate1Z[1] = {0};
u32 Pammo_crate2Z[1] = {0};
u32 Pammo_crate3Z[1] = {0};
u32 Pammo_crate4Z[1] = {0};
u32 Pammo_crate5Z[1] = {0};
u32 PapcZ[1] = {0};
u32 Parchsecdoor1Z[1] = {0};
u32 Parchsecdoor2Z[1] = {0};
u32 ParticZ[1] = {0};
u32 PartictrailerZ[1] = {0};
u32 PbarricadeZ[1] = {0};
u32 Pbin1Z[1] = {0};
u32 Pblotter1Z[1] = {0};
u32 PbodyarmourZ[1] = {0};
u32 PbodyarmourvestZ[1] = {0};
u32 PbollardZ[1] = {0};
u32 PbombZ[1] = {0};
u32 Pbook1Z[1] = {0};
u32 Pbookshelf1Z[1] = {0};
u32 Pborg_crateZ[1] = {0};
u32 PboxcartridgesZ[1] = {0};
u32 Pboxes2x4Z[1] = {0};
u32 Pboxes3x4Z[1] = {0};
u32 Pboxes4x4Z[1] = {0};
u32 PbrakeunitZ[1] = {0};
u32 Pbridge_console1aZ[1] = {0};
u32 Pbridge_console1bZ[1] = {0};
u32 Pbridge_console2aZ[1] = {0};
u32 Pbridge_console2bZ[1] = {0};
u32 Pbridge_console3aZ[1] = {0};
u32 Pbridge_console3bZ[1] = {0};
u32 PcarbmwZ[1] = {0};
u32 Pcard_box1Z[1] = {0};
u32 Pcard_box2Z[1] = {0};
u32 Pcard_box3Z[1] = {0};
u32 Pcard_box4_lgZ[1] = {0};
u32 Pcard_box5_lgZ[1] = {0};
u32 Pcard_box6_lgZ[1] = {0};
u32 PcarescortZ[1] = {0};
u32 PcargolfZ[1] = {0};
u32 PcarweirdZ[1] = {0};
u32 PcarzilZ[1] = {0};
u32 PcctvZ[1] = {0};
u32 PchraudiotapeZ[1] = {0};
u32 PchrautoshotZ[1] = {0};
u32 PchrblackboxZ[1] = {0};
u32 PchrblueprintsZ[1] = {0};
u32 PchrbombcaseZ[1] = {0};
u32 PchrbombdefuserZ[1] = {0};
u32 PchrbriefcaseZ[1] = {0};
u32 PchrbugZ[1] = {0};
u32 PchrbugdetectorZ[1] = {0};
u32 PchrbungeeZ[1] = {0};
u32 PchrcameraZ[1] = {0};
u32 PchrcircuitboardZ[1] = {0};
u32 PchrclipboardZ[1] = {0};
u32 PchrcreditcardZ[1] = {0};
u32 PchrdarkglassesZ[1] = {0};
u32 PchrdatathiefZ[1] = {0};
u32 PchrdattapeZ[1] = {0};
u32 PchrdoordecoderZ[1] = {0};
u32 PchrdoorexploderZ[1] = {0};
u32 PchrdossierredZ[1] = {0};
u32 PchrdynamiteZ[1] = {0};
u32 PchrexplosivepenZ[1] = {0};
u32 PchrextinguisherZ[1] = {0};
u32 PchrfingergunZ[1] = {0};
u32 PchrflarepistolZ[1] = {0};
u32 Pchrfnp90Z[1] = {0};
u32 PchrgaskeyringZ[1] = {0};
u32 PchrgoldbarZ[1] = {0};
u32 PchrgoldenZ[1] = {0};
u32 PchrgoldeneyekeyZ[1] = {0};
u32 PchrgoldwppkZ[1] = {0};
u32 PchrgrenadeZ[1] = {0};
u32 PchrgrenadelaunchZ[1] = {0};
u32 PchrgrenaderoundZ[1] = {0};
u32 PchrheroinZ[1] = {0};
u32 PchrkalashZ[1] = {0};
u32 PchrkeyanalysercaseZ[1] = {0};
u32 PchrkeyboltZ[1] = {0};
u32 PchrkeyyaleZ[1] = {0};
u32 PchrknifeZ[1] = {0};
u32 PchrlaserZ[1] = {0};
u32 PchrlectreZ[1] = {0};
u32 PchrlockexploderZ[1] = {0};
u32 Pchrm16Z[1] = {0};
u32 PchrmapZ[1] = {0};
u32 PchrmicrocameraZ[1] = {0};
u32 PchrmicrocodeZ[1] = {0};
u32 PchrmicrofilmZ[1] = {0};
u32 PchrmoneyZ[1] = {0};
u32 Pchrmp5kZ[1] = {0};
u32 Pchrmp5ksilZ[1] = {0};
u32 PchrpitongunZ[1] = {0};
u32 PchrplansZ[1] = {0};
u32 PchrplastiqueZ[1] = {0};
u32 PchrpolarizedglassesZ[1] = {0};
u32 PchrproximitymineZ[1] = {0};
u32 PchrremotemineZ[1] = {0};
u32 PchrrocketZ[1] = {0};
u32 PchrrocketlaunchZ[1] = {0};
u32 PchrrugerZ[1] = {0};
u32 PchrsafecrackercaseZ[1] = {0};
u32 PchrshotgunZ[1] = {0};
u32 PchrsilverwppkZ[1] = {0};
u32 PchrskorpionZ[1] = {0};
u32 PchrsniperrifleZ[1] = {0};
u32 PchrspectreZ[1] = {0};
u32 PchrspooltapeZ[1] = {0};
u32 PchrspyfileZ[1] = {0};
u32 PchrstafflistZ[1] = {0};
u32 PchrtesttubeZ[1] = {0};
u32 PchrthrowknifeZ[1] = {0};
u32 PchrtimedmineZ[1] = {0};
u32 Pchrtt33Z[1] = {0};
u32 PchruziZ[1] = {0};
u32 PchrvideotapeZ[1] = {0};
u32 PchrweaponcaseZ[1] = {0};
u32 PchrwppkZ[1] = {0};
u32 PchrwppksilZ[1] = {0};
u32 PchrwristdartZ[1] = {0};
u32 Pconsole1Z[1] = {0};
u32 Pconsole2Z[1] = {0};
u32 Pconsole3Z[1] = {0};
u32 Pconsole_sev2aZ[1] = {0};
u32 Pconsole_sev2bZ[1] = {0};
u32 Pconsole_sev2cZ[1] = {0};
u32 Pconsole_sev2dZ[1] = {0};
u32 Pconsole_sev_GEaZ[1] = {0};
u32 Pconsole_sev_GEbZ[1] = {0};
u32 Pconsole_sevaZ[1] = {0};
u32 Pconsole_sevbZ[1] = {0};
u32 Pconsole_sevcZ[1] = {0};
u32 Pconsole_sevdZ[1] = {0};
u32 Pcryptdoor1aZ[1] = {0};
u32 Pcryptdoor1bZ[1] = {0};
u32 Pcryptdoor2aZ[1] = {0};
u32 Pcryptdoor2bZ[1] = {0};
u32 Pcryptdoor3Z[1] = {0};
u32 Pcryptdoor4Z[1] = {0};
u32 PdamchaindoorZ[1] = {0};
u32 PdamgatedoorZ[1] = {0};
u32 PdamtundoorZ[1] = {0};
u32 Pdepot_door_steelZ[1] = {0};
u32 Pdepot_gate_entryZ[1] = {0};
u32 Pdesk1Z[1] = {0};
u32 Pdesk2Z[1] = {0};
u32 Pdesk_arecibo1Z[1] = {0};
u32 Pdesk_lamp2Z[1] = {0};
u32 Pdest_engineZ[1] = {0};
u32 Pdest_exocetZ[1] = {0};
u32 Pdest_gunZ[1] = {0};
u32 Pdest_harpoonZ[1] = {0};
u32 Pdest_seawolfZ[1] = {0};
u32 Pdisc_readerZ[1] = {0};
u32 Pdisk_drive1Z[1] = {0};
u32 Pdoor_azt_chairZ[1] = {0};
u32 Pdoor_azt_deskZ[1] = {0};
u32 Pdoor_azt_desk_topZ[1] = {0};
u32 Pdoor_aztecZ[1] = {0};
u32 Pdoor_dest1Z[1] = {0};
u32 Pdoor_dest2Z[1] = {0};
u32 Pdoor_eyelidZ[1] = {0};
u32 Pdoor_irisZ[1] = {0};
u32 Pdoor_mfZ[1] = {0};
u32 Pdoor_roller1Z[1] = {0};
u32 Pdoor_roller2Z[1] = {0};
u32 Pdoor_roller3Z[1] = {0};
u32 Pdoor_roller4Z[1] = {0};
u32 Pdoor_rollertrainZ[1] = {0};
u32 Pdoor_st_arec1Z[1] = {0};
u32 Pdoor_st_arec2Z[1] = {0};
u32 Pdoor_winZ[1] = {0};
u32 PdoorconsoleZ[1] = {0};
u32 PdoorpanelZ[1] = {0};
u32 Pdoorprison1Z[1] = {0};
u32 PdoorstatgateZ[1] = {0};
u32 PexplosionbitZ[1] = {0};
u32 Pfiling_cabinet1Z[1] = {0};
u32 PflagZ[1] = {0};
u32 PfloppyZ[1] = {0};
u32 Pfnp90magZ[1] = {0};
u32 Pgas_plant_met1_do1Z[1] = {0};
u32 Pgas_plant_sw2_do1Z[1] = {0};
u32 Pgas_plant_sw3_do1Z[1] = {0};
u32 Pgas_plant_sw4_do1Z[1] = {0};
u32 Pgas_plant_sw_do1Z[1] = {0};
u32 Pgas_plant_wc_cub1Z[1] = {0};
u32 PgasbarrelZ[1] = {0};
u32 PgasbarrelsZ[1] = {0};
u32 Pgasplant_clear_doorZ[1] = {0};
u32 PgastankZ[1] = {0};
u32 Pglassware1Z[1] = {0};
u32 Pglassware2Z[1] = {0};
u32 Pglassware3Z[1] = {0};
u32 Pglassware4Z[1] = {0};
u32 PgoldeneyelogoZ[1] = {0};
u32 PgoldenshellsZ[1] = {0};
u32 PgroundgunZ[1] = {0};
u32 Pgun_runway1Z[1] = {0};
u32 PhatberetZ[1] = {0};
u32 PhatberetblueZ[1] = {0};
u32 PhatberetredZ[1] = {0};
u32 PhatchboltZ[1] = {0};
u32 PhatchdoorZ[1] = {0};
u32 PhatchsevxZ[1] = {0};
u32 PhatfurryZ[1] = {0};
u32 PhatfurryblackZ[1] = {0};
u32 PhatfurrybrownZ[1] = {0};
u32 PhathelmetZ[1] = {0};
u32 PhathelmetgreyZ[1] = {0};
u32 PhatmoonZ[1] = {0};
u32 PhatpeakedZ[1] = {0};
u32 PhattbirdZ[1] = {0};
u32 PhattbirdbrownZ[1] = {0};
u32 PhelicopterZ[1] = {0};
u32 PhindZ[1] = {0};
u32 PjeepZ[1] = {0};
u32 Pjerry_can1Z[1] = {0};
u32 Pjungle3_treeZ[1] = {0};
u32 Pjungle5_treeZ[1] = {0};
u32 Pkey_holderZ[1] = {0};
u32 Pkeyboard1Z[1] = {0};
u32 Pkit_units1Z[1] = {0};
u32 PlabbenchZ[1] = {0};
u32 PlandmineZ[1] = {0};
u32 PlegalpageZ[1] = {0};
u32 Pletter_tray1Z[1] = {0};
u32 Plocker3Z[1] = {0};
u32 Plocker4Z[1] = {0};
u32 Pm16magZ[1] = {0};
u32 PmagnumshellsZ[1] = {0};
u32 Pmainframe1Z[1] = {0};
u32 Pmainframe2Z[1] = {0};
u32 Pmetal_chair1Z[1] = {0};
u32 Pmetal_crate1Z[1] = {0};
u32 Pmetal_crate2Z[1] = {0};
u32 Pmetal_crate3Z[1] = {0};
u32 Pmetal_crate4Z[1] = {0};
u32 PmilcopterZ[1] = {0};
u32 PmiltruckZ[1] = {0};
u32 Pmissile_rack2Z[1] = {0};
u32 Pmissile_rackZ[1] = {0};
u32 PmodemboxZ[1] = {0};
u32 PmotorbikeZ[1] = {0};
u32 Pmp5kmagZ[1] = {0};
u32 PnintendologoZ[1] = {0};
u32 Poil_drum1Z[1] = {0};
u32 Poil_drum2Z[1] = {0};
u32 Poil_drum3Z[1] = {0};
u32 Poil_drum5Z[1] = {0};
u32 Poil_drum6Z[1] = {0};
u32 Poil_drum7Z[1] = {0};
u32 PpadlockZ[1] = {0};
u32 PpalmZ[1] = {0};
u32 PpalmtreeZ[1] = {0};
u32 Pphone1Z[1] = {0};
u32 PplaneZ[1] = {0};
u32 Pplant11Z[1] = {0};
u32 Pplant1Z[1] = {0};
u32 Pplant2Z[1] = {0};
u32 Pplant2bZ[1] = {0};
u32 Pplant3Z[1] = {0};
u32 Pradio_unit1Z[1] = {0};
u32 Pradio_unit2Z[1] = {0};
u32 Pradio_unit3Z[1] = {0};
u32 Pradio_unit4Z[1] = {0};
u32 ProofgunZ[1] = {0};
u32 PsafeZ[1] = {0};
u32 PsafedoorZ[1] = {0};
u32 Psat1_reflectZ[1] = {0};
u32 PsatboxZ[1] = {0};
u32 PsatdishZ[1] = {0};
u32 Psec_panelZ[1] = {0};
u32 Psev_door3Z[1] = {0};
u32 Psev_door3_windZ[1] = {0};
u32 Psev_door4_windZ[1] = {0};
u32 Psev_doorZ[1] = {0};
u32 Psev_door_v1Z[1] = {0};
u32 Psev_trislideZ[1] = {0};
u32 PsevdishZ[1] = {0};
u32 PsevdoormetslideZ[1] = {0};
u32 PsevdoornowindZ[1] = {0};
u32 PsevdoorwindZ[1] = {0};
u32 PsevdoorwoodZ[1] = {0};
u32 PshuttleZ[1] = {0};
u32 Pshuttle_door_lZ[1] = {0};
u32 Pshuttle_door_rZ[1] = {0};
u32 PsilencerZ[1] = {0};
u32 Psilo_lift_doorZ[1] = {0};
u32 PsilotopdoorZ[1] = {0};
u32 PskorpionmagZ[1] = {0};
u32 PspectremagZ[1] = {0};
u32 PspeedboatZ[1] = {0};
u32 Pst_pete_room_1iZ[1] = {0};
u32 Pst_pete_room_2iZ[1] = {0};
u32 Pst_pete_room_3tZ[1] = {0};
u32 Pst_pete_room_5cZ[1] = {0};
u32 Pst_pete_room_6cZ[1] = {0};
u32 Psteel_door1Z[1] = {0};
u32 Psteel_door2Z[1] = {0};
u32 Psteel_door2bZ[1] = {0};
u32 Psteel_door3Z[1] = {0};
u32 Pstool1Z[1] = {0};
u32 Pswipe_card2Z[1] = {0};
u32 Pswivel_chair1Z[1] = {0};
u32 PtankZ[1] = {0};
u32 PtigerZ[1] = {0};
u32 Ptorpedo_rackZ[1] = {0};
u32 Ptrain_door2Z[1] = {0};
u32 Ptrain_door3Z[1] = {0};
u32 Ptrain_doorZ[1] = {0};
u32 PtrainextdoorZ[1] = {0};
u32 Ptt33magZ[1] = {0};
u32 Ptuning_console1Z[1] = {0};
u32 Ptv1Z[1] = {0};
u32 Ptv4screenZ[1] = {0};
u32 Ptv_holderZ[1] = {0};
u32 PtvscreenZ[1] = {0};
u32 PuzimagZ[1] = {0};
u32 PvertdoorZ[1] = {0};
u32 PwalletbondZ[1] = {0};
u32 PwindowZ[1] = {0};
u32 Pwindow_cor11Z[1] = {0};
u32 Pwindow_lib_lg1Z[1] = {0};
u32 Pwindow_lib_sm1Z[1] = {0};
u32 Pwood_lg_crate1Z[1] = {0};
u32 Pwood_lg_crate2Z[1] = {0};
u32 Pwood_md_crate3Z[1] = {0};
u32 Pwood_sm_crate4Z[1] = {0};
u32 Pwood_sm_crate5Z[1] = {0};
u32 Pwood_sm_crate6Z[1] = {0};
u32 Pwooden_table1Z[1] = {0};
u32 PwppkmagZ[1] = {0};
/* --- Background texture sets (Tbg_*Z) -------------------------------- */
u32 Tbg_ame_all_p_stanZ[1] = {0};
u32 Tbg_arch_all_p_stanZ[1] = {0};
u32 Tbg_arec_all_p_stanZ[1] = {0};
u32 Tbg_ark_all_p_stanZ[1] = {0};
u32 Tbg_ash_all_p_stanZ[1] = {0};
u32 Tbg_azt_all_p_stanZ[1] = {0};
u32 Tbg_cat_all_p_stanZ[1] = {0};
u32 Tbg_cave_all_p_stanZ[1] = {0};
u32 Tbg_crad_all_p_stanZ[1] = {0};
u32 Tbg_cryp_all_p_stanZ[1] = {0};
u32 Tbg_dam_all_p_stanZ[1] = {0};
u32 Tbg_depo_all_p_stanZ[1] = {0};
u32 Tbg_dest_all_p_stanZ[1] = {0};
u32 Tbg_dish_all_p_stanZ[1] = {0};
u32 Tbg_imp_all_p_stanZ[1] = {0};
u32 Tbg_jun_all_p_stanZ[1] = {0};
u32 Tbg_len_all_p_stanZ[1] = {0};
u32 Tbg_oat_all_p_stanZ[1] = {0};
u32 Tbg_pete_all_p_stanZ[1] = {0};
u32 Tbg_ref_all_p_stanZ[1] = {0};
u32 Tbg_run_all_p_stanZ[1] = {0};
u32 Tbg_sev_all_p_stanZ[1] = {0};
u32 Tbg_sevb_all_p_stanZ[1] = {0};
u32 Tbg_sevx_all_p_stanZ[1] = {0};
u32 Tbg_silo_all_p_stanZ[1] = {0};
u32 Tbg_stat_all_p_stanZ[1] = {0};
u32 Tbg_tra_all_p_stanZ[1] = {0};
/* --- Stage / MP setup data (U*Z, Ump_*Z, Ubrief*Z) ------------------ */
u32 UbriefarchZ[1] = {0};
u32 UbriefarkZ[1] = {0};
u32 UbriefaztZ[1] = {0};
u32 UbriefcaveZ[1] = {0};
u32 UbriefcontrolZ[1] = {0};
u32 UbriefcradZ[1] = {0};
u32 UbriefcrypZ[1] = {0};
u32 UbriefdamZ[1] = {0};
u32 UbriefdepoZ[1] = {0};
u32 UbriefdestZ[1] = {0};
u32 UbriefjunZ[1] = {0};
u32 UbriefpeteZ[1] = {0};
u32 UbriefrunZ[1] = {0};
u32 UbriefsevbZ[1] = {0};
u32 UbriefsevbunkerZ[1] = {0};
u32 UbriefsevxZ[1] = {0};
u32 UbriefsevxbZ[1] = {0};
u32 UbriefsiloZ[1] = {0};
u32 UbriefstatueZ[1] = {0};
u32 UbrieftraZ[1] = {0};
u32 Ump_setupameZ[1] = {0};
u32 Ump_setuparchZ[1] = {0};
u32 Ump_setuparkZ[1] = {0};
u32 Ump_setupashZ[1] = {0};
u32 Ump_setupcaveZ[1] = {0};
u32 Ump_setupcradZ[1] = {0};
u32 Ump_setupcrypZ[1] = {0};
u32 Ump_setupdishZ[1] = {0};
u32 Ump_setupimpZ[1] = {0};
u32 Ump_setupoatZ[1] = {0};
u32 Ump_setuprefZ[1] = {0};
u32 Ump_setupsevbZ[1] = {0};
u32 Ump_setupstatueZ[1] = {0};
u32 UsetuparchZ[1] = {0};
u32 UsetuparkZ[1] = {0};
u32 UsetupaztZ[1] = {0};
u32 UsetupcaveZ[1] = {0};
u32 UsetupcontrolZ[1] = {0};
u32 UsetupcradZ[1] = {0};
u32 UsetupcrypZ[1] = {0};
u32 UsetupdamZ[1] = {0};
u32 UsetupdepoZ[1] = {0};
u32 UsetupdestZ[1] = {0};
u32 UsetupjunZ[1] = {0};
u32 UsetuplenZ[1] = {0};
u32 UsetuppeteZ[1] = {0};
u32 UsetuprunZ[1] = {0};
u32 UsetupsevbZ[1] = {0};
u32 UsetupsevbunkerZ[1] = {0};
u32 UsetupsevxZ[1] = {0};
u32 UsetupsevxbZ[1] = {0};
u32 UsetupsiloZ[1] = {0};
u32 UsetupstatueZ[1] = {0};
u32 UsetuptraZ[1] = {0};
/* --- Text tables (L*E / L*J) -------------------------------------------- */
u32 LameE[1] = {0};
u32 LameJ[1] = {0};
u32 LameP[1] = {0};
u32 LarchE[1] = {0};
u32 LarchJ[1] = {0};
u32 LarchP[1] = {0};
u32 LarecE[1] = {0};
u32 LarecJ[1] = {0};
u32 LarecP[1] = {0};
u32 LarkE[1] = {0};
u32 LarkJ[1] = {0};
u32 LarkP[1] = {0};
u32 LashE[1] = {0};
u32 LashJ[1] = {0};
u32 LashP[1] = {0};
u32 LaztE[1] = {0};
u32 LaztJ[1] = {0};
u32 LaztP[1] = {0};
u32 LcatE[1] = {0};
u32 LcatJ[1] = {0};
u32 LcatP[1] = {0};
u32 LcaveE[1] = {0};
u32 LcaveJ[1] = {0};
u32 LcaveP[1] = {0};
u32 LcradE[1] = {0};
u32 LcradJ[1] = {0};
u32 LcradP[1] = {0};
u32 LcrypE[1] = {0};
u32 LcrypJ[1] = {0};
u32 LcrypP[1] = {0};
u32 LdamE[1] = {0};
u32 LdamJ[1] = {0};
u32 LdamP[1] = {0};
u32 LdepoE[1] = {0};
u32 LdepoJ[1] = {0};
u32 LdepoP[1] = {0};
u32 LdestE[1] = {0};
u32 LdestJ[1] = {0};
u32 LdestP[1] = {0};
u32 LdishE[1] = {0};
u32 LdishJ[1] = {0};
u32 LdishP[1] = {0};
u32 LearE[1] = {0};
u32 LearJ[1] = {0};
u32 LearP[1] = {0};
u32 LeldE[1] = {0};
u32 LeldJ[1] = {0};
u32 LeldP[1] = {0};
u32 LgunE[1] = {0};
u32 LgunJ[1] = {0};
u32 LgunP[1] = {0};
u32 LimpE[1] = {0};
u32 LimpJ[1] = {0};
u32 LimpP[1] = {0};
u32 LjunE[1] = {0};
u32 LjunJ[1] = {0};
u32 LjunP[1] = {0};
u32 LleeE[1] = {0};
u32 LleeJ[1] = {0};
u32 LleeP[1] = {0};
u32 LlenE[1] = {0};
u32 LlenJ[1] = {0};
u32 LlenP[1] = {0};
u32 LlipE[1] = {0};
u32 LlipJ[1] = {0};
u32 LlipP[1] = {0};
u32 LlueE[1] = {0};
u32 LlueJ[1] = {0};
u32 LlueP[1] = {0};
u32 LmiscE[1] = {0};
u32 LmiscJ[1] = {0};
u32 LmiscP[1] = {0};
u32 LmpmenuE[1] = {0};
u32 LmpmenuJ[1] = {0};
u32 LmpmenuP[1] = {0};
u32 LmpweaponsE[1] = {0};
u32 LmpweaponsJ[1] = {0};
u32 LmpweaponsP[1] = {0};
u32 LoatE[1] = {0};
u32 LoatJ[1] = {0};
u32 LoatP[1] = {0};
u32 LoptionsE[1] = {0};
u32 LoptionsJ[1] = {0};
u32 LoptionsP[1] = {0};
u32 LpamE[1] = {0};
u32 LpamJ[1] = {0};
u32 LpamP[1] = {0};
u32 LpeteE[1] = {0};
u32 LpeteJ[1] = {0};
u32 LpeteP[1] = {0};
u32 LpropobjE[1] = {0};
u32 LpropobjJ[1] = {0};
u32 LpropobjP[1] = {0};
u32 LrefE[1] = {0};
u32 LrefJ[1] = {0};
u32 LrefP[1] = {0};
u32 LritE[1] = {0};
u32 LritJ[1] = {0};
u32 LritP[1] = {0};
u32 LrunE[1] = {0};
u32 LrunJ[1] = {0};
u32 LrunP[1] = {0};
u32 LsevE[1] = {0};
u32 LsevJ[1] = {0};
u32 LsevP[1] = {0};
u32 LsevbE[1] = {0};
u32 LsevbJ[1] = {0};
u32 LsevbP[1] = {0};
u32 LsevxE[1] = {0};
u32 LsevxJ[1] = {0};
u32 LsevxP[1] = {0};
u32 LsevxbE[1] = {0};
u32 LsevxbJ[1] = {0};
u32 LsevxbP[1] = {0};
u32 LshoE[1] = {0};
u32 LshoJ[1] = {0};
u32 LshoP[1] = {0};
u32 LsiloE[1] = {0};
u32 LsiloJ[1] = {0};
u32 LsiloP[1] = {0};
u32 LstatE[1] = {0};
u32 LstatJ[1] = {0};
u32 LstatP[1] = {0};
u32 LtitleE[1] = {0};
u32 LtitleJ[1] = {0};
u32 LtitleP[1] = {0};
u32 LtraE[1] = {0};
u32 LtraJ[1] = {0};
u32 LtraP[1] = {0};
u32 LwaxE[1] = {0};
u32 LwaxJ[1] = {0};
u32 LwaxP[1] = {0};
/* --- Background level data segment bases (bg_*_all_p_seg) ------------ */
u32 bg_arch_all_p_seg[1] = {0};
u32 bg_arec_all_p_seg[1] = {0};
u32 bg_ark_all_p_seg[1] = {0};
u32 bg_azt_all_p_seg[1] = {0};
u32 bg_cat_all_p_seg[1] = {0};
u32 bg_cave_all_p_seg[1] = {0};
u32 bg_crad_all_p_seg[1] = {0};
u32 bg_cryp_all_p_seg[1] = {0};
u32 bg_dam_all_p_seg[1] = {0};
u32 bg_depo_all_p_seg[1] = {0};
u32 bg_dest_all_p_seg[1] = {0};
u32 bg_dish_all_p_seg[1] = {0};
u32 bg_ear_all_p_seg[1] = {0};
u32 bg_imp_all_p_seg[1] = {0};
u32 bg_jun_all_p_seg[1] = {0};
u32 bg_lee_all_p_seg[1] = {0};
u32 bg_len_all_p_seg[1] = {0};
u32 bg_lip_all_p_seg[1] = {0};
u32 bg_oat_all_p_seg[1] = {0};
u32 bg_pam_all_p_seg[1] = {0};
u32 bg_pete_all_p_seg[1] = {0};
u32 bg_ref_all_p_seg[1] = {0};
u32 bg_rit_all_p_seg[1] = {0};
u32 bg_run_all_p_seg[1] = {0};
u32 bg_sev_all_p_seg[1] = {0};
u32 bg_sevb_all_p_seg[1] = {0};
u32 bg_sevx_all_p_seg[1] = {0};
u32 bg_sho_all_p_seg[1] = {0};
u32 bg_silo_all_p_seg[1] = {0};
u32 bg_stat_all_p_seg[1] = {0};
u32 bg_tra_all_p_seg[1] = {0};
u32 bg_wax_all_p_seg[1] = {0};
/* --- Other --------------------------------------------------------------- */
u32 ob__ob_end_seg[1] = {0};
/* --- RAM-rom demo replays (assets/ramrom/ramrom.s, .incbin) -------------- */
/* ramromreplay.c declares these `extern u32 *` and stores &symbol in its */
/* table; NULL pointers link and keep the entries inert. */
u32 *ramrom_BunkerI_1 = NULL;
u32 *ramrom_BunkerI_2 = NULL;
u32 *ramrom_Dam_1 = NULL;
u32 *ramrom_Dam_2 = NULL;
u32 *ramrom_Facility_1 = NULL;
u32 *ramrom_Facility_2 = NULL;
u32 *ramrom_Facility_3 = NULL;
u32 *ramrom_Frigate_1 = NULL;
u32 *ramrom_Frigate_2 = NULL;
u32 *ramrom_Runway_1 = NULL;
u32 *ramrom_Runway_2 = NULL;
u32 *ramrom_Silo_1 = NULL;
u32 *ramrom_Silo_2 = NULL;
u32 *ramrom_Train = NULL;
/* --- assets/romfiles2.s (.incbin blob) ----------------------------------- */
/* title.c romCopies (unknown2_end - unknown2) bytes; as a pointer pair that */
/* is zero, so the copy is a no-op. */
void *unknown2 = NULL;
void *unknown2_end = NULL;
+34 -7
View File
@@ -202,20 +202,47 @@ void osAiSetConvert(u32 convert) { (void)convert; }
/* PI (peripheral) — ROM/cart reads come from the loaded ROM image. */
/* ------------------------------------------------------------------------ */
/*
* PI DMA is synchronous on the PC: romdata.c maps the .z64 at the N64 cart
* base (0x10000000), so an OS_READ from a cart address is a plain memcpy and
* the "completion" is already delivered by the time we return. The game's
* romReceiveMesg()/osRecvMesg() after every romCopy() is therefore satisfied
* trivially (see the message-queue shims below).
*/
static void piServiceDma(s32 direction, u32 srcPA, void *dstVA, u32 size)
{
if (size == 0)
return;
if (direction == OS_READ) {
if (!romdataCartAddrValid(srcPA, size)) {
sysLogPrintf(LOG_WARNING,
"osPiStartDma: ROM read out of range "
"(src=0x%08X size=0x%X); skipped",
srcPA, size);
return;
}
memcpy(dstVA, (const void *)(uintptr_t)srcPA, size);
} else {
/* OS_WRITE: the game never writes the cart (saves go to EEPROM via
* osEeprom*, shimmed separately). Log once-style and drop. */
sysLogPrintf(LOG_WARNING,
"osPiStartDma: cart write dropped (src=0x%08X size=0x%X)",
srcPA, size);
}
}
void osPiCreateManager(OSMesgQueue *mq, int prio) { (void)mq; (void)prio; }
s32 osPiStartDma(OSIoMesg *mesg, s32 prio, s32 direction, u32 addr,
void *buf, u32 size, OSMesgQueue *mq)
{
/* TODO(Phase 1): service a read from romdata (cart -> host memory). */
(void)mesg; (void)prio; (void)direction; (void)addr; (void)buf;
(void)size; (void)mq;
return 1;
piServiceDma(direction, addr, buf, size);
(void)mesg; (void)prio; (void)mq;
return 1; /* done */
}
s32 osPiRawStartDma(s32 direction, u32 srcPA, void *dstVA, u32 size)
{
/* TODO(Phase 1): copy between ROM image and host memory. */
(void)direction; (void)srcPA; (void)dstVA; (void)size;
return 1;
piServiceDma(direction, srcPA, dstVA, size);
return 1; /* done */
}
u32 osPiGetStatus(void) { return 0; } /* not busy */
+23 -11
View File
@@ -4,13 +4,16 @@
* Replaces the N64 boot path (boot.s -> init() -> mainproc() -> bossEntry()).
* On the PC we:
* 1. set up system / config / fs / rom
* 2. load the ROM and map its segments
* 2. load the ROM and map it at the cart base (0x10000000)
* 3. init video (SDL2 + GL), audio, input
* 4. hand control to the game's mainproc() / bossEntry()
*
* Modelled on the PD port's port/src/main.c.
*
* STATUS: scaffolding stub — fill in during Phase 1.
* STATUS: Phase 1 — ROM loads and maps, a window opens and clears each frame.
* Step 4 (mainproc) is deferred until the software RSP (fast3d) and the
* scheduler can service the game's rendering/audio threads; see
* docs/PCPortResearch.md.
*/
#include <stdlib.h>
@@ -58,18 +61,27 @@ int main(int argc, char **argv)
mixerInit();
inputInit();
/* 4. Hand control to the game.
/* 4. Phase 1 demo loop: clear colour until the RSP/scheduler can drive
* real frames (Phase 2). Press ESC or close the window to quit.
*
* mainproc() is defined in src/init.c, which IS compiled for the PC (see
* A3 in docs/PCPortResearch.md). init.c's N64-only init() is compiled but
* NOT called — its hardware deps (segment starts, decompress, TLB, FPU
* CSR) are stubbed in port/src/n64stubs.c, and the libultra OS calls are
* shimmed in port/src/libultra.c. mainproc() itself just sets up the
* threads and calls schedulerInitThread() + bossEntry().
* When fast3d lands, replace this with mainproc(NULL) — defined in
* src/init.c (compiled for the PC; see A3 in docs/PCPortResearch.md).
* init.c's N64-only init() is never called; its hardware deps are
* stubbed in port/src/n64stubs.c and the libultra OS calls are shimmed
* in port/src/libultra.c.
*/
mainproc(NULL);
sysLogPrintf(LOG_INFO, "Phase 1: ROM mapped at 0x%08X (%u bytes); "
"demo loop running — ESC to quit",
(unsigned)0x10000000, romdataGetRomSize());
for (;;) {
if (videoHandleEvents())
break;
videoStartFrame();
videoSubmitCommands(NULL); /* no-op until fast3d lands */
videoEndFrame();
}
/* mainproc() normally does not return; clean up if it does. */
/* Clean up. */
inputDestroy();
mixerDestroy();
audioDestroy();
+11 -55
View File
@@ -33,69 +33,25 @@ u32 get_inflateSegmentRomStart(void) { return 0; }
u32 get_inflateSegmentRomEnd(void) { return 0; }
/*
* Linker segment boundary pointers (normally linker-script symbols, declared
* `extern u32 *` in bondgame.h and assorted game headers). Game code takes
* their ADDRESSES for size calcs + PI DMA / romCopy; on the PC the values are
* meaningless until Phase 2 ROM loading remaps them (romdata.c). All defined
* NULL: address-of gives a valid host pointer, and pairs of NULLs compute to
* zero-length copies. Complete set = every `*Segment*` symbol referenced by
* the compiled game sources.
* Linker segment boundary symbols.
*
* The ROM-backed ones (every _XSegment{Start,RomStart,RomEnd,End} from
* ge007.ld, plus all obseg/ramrom/music asset labels) are now ABSOLUTE cart
* addresses in the generated port/src/romassets_u.s — see
* scripts/gen_romassets.py. romdata.c maps the .z64 at 0x10000000 so `&sym`
* yields a live address and romCopy() works.
*
* What remains here are pure-RAM segment symbols that have no ROM presence:
* the host's own .bss/.csegment ends and the N64 vaddr markers (only used by
* the never-run N64 boot path on the PC).
*/
u32 *_bssSegmentEnd = NULL;
u32 *_codeSegmentStart = NULL;
u32 *_codeSegmentEnd = NULL;
u32 *_codeSegmentRomStart = NULL;
u32 *_codeSegmentRomEnd = NULL;
u32 *_csegmentSegmentStart = NULL;
u32 *_csegmentSegmentEnd = NULL;
u32 *_cdataSegmentRomStart = NULL;
u32 *_cdataSegmentRomEnd = NULL;
u32 *_inflateSegmentVaddrStart = NULL;
u32 *_inflateSegmentVaddrEnd = NULL;
u32 *_inflateSegmentRomStart = NULL;
u32 *_inflateSegmentRomEnd = NULL;
u32 *_gameSegmentVaddrStart = NULL;
u32 *_gameSegmentVaddrEnd = NULL;
u32 *_gameSegmentRomStart = NULL;
u32 *_gameSegmentRomEnd = NULL;
u32 *_animation_dataSegmentRomStart = NULL;
u32 *_animation_dataSegmentStart = NULL;
u32 *_animation_dataSegmentEnd = NULL;
u32 *_animation_entriesSegmentRomStart = NULL;
u32 *_alt_startSegmentRomStart = NULL;
u32 *_alt_startSegmentStart = NULL;
/* Font / image / music segment bases (referenced by textrelated.c,
* image_bank.c, music.c — data for the font/image ones is compiled from
* assets/; the ROM-base markers themselves come from ge007.ld on N64). */
u32 *_efontchardataSegmentRomStart = NULL;
u32 *_jfontchardataSegmentRomStart = NULL;
u32 *_fontbankgothicSegmentStart = NULL;
u32 *_fontbankgothicSegmentEnd = NULL;
u32 *_fontbankgothicSegmentRomStart = NULL;
u32 *_fontzurichboldSegmentStart = NULL;
u32 *_fontzurichboldSegmentEnd = NULL;
u32 *_fontzurichboldSegmentRomStart = NULL;
u32 *_fontdlSegmentRomStart = NULL;
u32 *_fontdlSegmentRomEnd = NULL;
u32 *_imagesSegmentRomStart = NULL;
u32 *_GlobalimagetableSegmentStart = NULL;
u32 *_GlobalimagetableSegmentEnd = NULL;
u32 *_GlobalimagetableSegmentRomStart = NULL;
u32 *_instrumentsctlSegmentRomStart = NULL;
u32 *_instrumentstblSegmentRomStart = NULL;
u32 *_musicsampletblSegmentRomStart = NULL;
u32 *_sfxctlSegmentRomStart = NULL;
u32 *_sfxtblSegmentRomStart = NULL;
u32 *_rarewarelogoSegmentStart = NULL;
u32 *_rarewarelogoSegmentEnd = NULL;
u32 *_rarewarelogoSegmentRomStart = NULL;
/* --- Decompressor (src/inflate, not built for PC) ----------------------- */
/* init() calls this to unpack the data segment. Unused on the PC. */
File diff suppressed because it is too large. Load diff
+152 -44
View File
@@ -1,89 +1,197 @@
/*
* ROM loading: reads the .z64 ROM from the data dir and exposes its segments.
* ROM loading: reads the .z64 and maps it at the N64 cart address 0x10000000.
*
* The N64 build links assets in from the ROM at fixed addresses. The PC port
* loads the ROM file into a heap image and maps the game's data segments into
* it, then patches the segment table so asset references resolve.
* Why the fixed address: every ROM asset symbol in the PC build is an ABSOLUTE
* cart address (port/src/romassets_<region>.s, generated by
* scripts/gen_romassets.py from ge007.ld + scripts/filelist.<r>.csv). Game
* code takes `&symbol` and DMAs via romCopy()/osPiStartDma() exactly as on
* hardware. If the ROM image is mapped at 0x10000000, those addresses are
* live host pointers: PI reads become plain memcpys (see libultra.c) and
* direct-read assets (fonts, image display lists) work unmodified.
*
* Modelled on the PD port's port/src/romdata.c (~605 lines).
* The N64 ROM header is GE-specific (src/rom_header.s): magic 0x80371240 at
* 0x00, ROM name "GOLDENEYE" at 0x20, cartridge ID "GE" at 0x3C, country byte
* at 0x3E ('E' US / 'P' EU / 'J' JP).
*
* STATUS: scaffolding stub — implement during Phase 1.
*
* NOTE: GE's ROM layout (segments, asset offsets) differs from PD's. The
* segment table and asset map must be derived from this decomp's
* ge007.ld / imagelist.u.csv / assets/ layout. See docs/PCPortResearch.md.
* STATUS: Phase 1 — load + validate + map. No segment patching needed: the
* generated symbols already carry the layout.
*/
#include <stdlib.h>
#include <string.h>
#include "platform.h"
#if defined(PLATFORM_WINDOWS)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#endif
#include "system.h"
#include "fs.h"
#include "romdata.h"
static u8 *rom = NULL;
static u32 romSize = 0;
#define CART_BASE 0x10000000u
/* Expected ROM file names per region (place one in the data/ dir). */
static const char *romFileName(void)
static u8 *rom = NULL; /* heap copy (fallback path) */
static u32 romSize = 0;
static int mappedAtCartBase = 0;
/* Candidate ROM file names, in priority order. The decomp repo ships the
* base ROM at the repo root (baserom.<r>.z64); a user-supplied final ROM in
* data/ takes precedence. */
static const char *romFileCandidates(void)
{
#if GE007_IS_PAL
return "ge007.pal-final.z64";
return "ge007.pal-final.z64|baserom.e.z64";
#else
return "ge007.ntsc-final.z64";
return "ge007.ntsc-final.z64|baserom.u.z64";
#endif
}
static int romHeaderValid(const u8 *h, char *err, size_t errsz)
{
static const u8 magic[4] = { 0x80, 0x37, 0x12, 0x40 };
#if GE007_IS_PAL
const char country = 'P';
#else
const char country = 'E';
#endif
if (memcmp(h, magic, 4) != 0) {
snprintf(err, errsz, "bad N64 magic at 0x00 (got %02x%02x%02x%02x)",
h[0], h[1], h[2], h[3]);
return 0;
}
if (memcmp(h + 0x20, "GOLDENEYE", 9) != 0) {
snprintf(err, errsz, "ROM name at 0x20 is not GOLDENEYE");
return 0;
}
if (h[0x3C] != 'G' || h[0x3D] != 'E') {
snprintf(err, errsz, "cartridge ID at 0x3C is not GE");
return 0;
}
if (h[0x3E] != country) {
snprintf(err, errsz, "country byte at 0x3E is %c, expected %c",
h[0x3E], country);
return 0;
}
return 1;
}
int romdataInit(void)
{
static char pathBuf[1024 + 64];
snprintf(pathBuf, sizeof(pathBuf), "$S/%s", romFileName());
const char *path = sysResolvePath(pathBuf);
char err[128] = "";
const char *list = romFileCandidates();
char listBuf[512];
strcpy(listBuf, list);
/* TODO(Phase 1):
* - open + read the whole ROM into `rom`
* - validate the N64 magic ("Nintendo" @ 0x100) + region
* - build the segment map (code/data/assets) from the decomp's layout
* - patch the game's segment table so IMAGESEG / asset refs resolve
*/
sysLogPrintf(LOG_INFO, "romdataInit: loading %s (TODO Phase 1)", path);
/* Try each candidate in the data dir, next to the exe, and in the CWD
* (the decomp repo ships baserom.<r>.z64 at the repo root). */
for (char *tok = strtok(listBuf, "|"); tok;
tok = strtok(NULL, "|")) {
static const char *locs[3] = { "$S/", "$E/", "./" };
int li;
FSFile *f = NULL;
FSFile *f = fsOpen(path, "rb");
if (!f) {
sysLogPrintf(LOG_ERROR, "romdataInit: cannot open %s", path);
return -1;
}
romSize = (u32)fsSize(f);
rom = (u8 *)malloc(romSize);
if (!rom || fsRead(f, rom, romSize) != (int32_t)romSize) {
sysLogPrintf(LOG_ERROR, "romdataInit: failed to read ROM");
for (li = 0; li < 3 && !f; li++) {
char pathbuf[1024];
snprintf(pathbuf, sizeof(pathbuf), "%s%s", locs[li], tok);
f = fsOpen(sysResolvePath(pathbuf), "rb");
}
if (!f)
continue;
romSize = (u32)fsSize(f);
u8 *img = (u8 *)malloc(romSize ? romSize : 1);
int ok = img && fsRead(f, img, romSize) == (int32_t)romSize;
fsClose(f);
return -1;
}
fsClose(f);
if (!ok) {
free(img);
continue;
}
if (romSize < 0x104 || memcmp(rom + 0x100, "Nintendo", 8) != 0) {
sysLogPrintf(LOG_ERROR, "romdataInit: bad N64 ROM magic");
return -1;
if (romSize < 0x40 || !romHeaderValid(img, err, sizeof(err))) {
sysLogPrintf(LOG_WARNING, "romdataInit: %s rejected: %s",
tok, err);
free(img);
continue;
}
/* Map at the cart address so absolute asset symbols are live. */
#if defined(PLATFORM_WINDOWS)
void *at = VirtualAlloc((LPVOID)CART_BASE, romSize,
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (at == (void *)(uintptr_t)CART_BASE) {
memcpy(at, img, romSize);
free(img);
mappedAtCartBase = 1;
sysLogPrintf(LOG_INFO, "romdataInit: %s (%u bytes) mapped at "
"0x%08X (cart base)", tok, romSize, CART_BASE);
return 0;
}
if (at)
VirtualFree(at, 0, MEM_RELEASE);
sysLogPrintf(LOG_WARNING, "romdataInit: could not reserve 0x%08X; "
"using heap copy — direct ROM reads will fail",
CART_BASE);
#else
/* Best effort: fixed-hint mmap. If the hint is refused we fall back
* to a heap copy (same limitation as above). */
sysLogPrintf(LOG_WARNING, "romdataInit: fixed-address mapping not "
"implemented on this platform; using heap copy");
#endif
rom = img;
return 0;
}
return 0;
sysLogPrintf(LOG_ERROR, "romdataInit: no ROM found (tried %s in data/ "
"and exe dir)", list);
return -1;
}
void romdataDestroy(void)
{
#if defined(PLATFORM_WINDOWS)
if (mappedAtCartBase) {
VirtualFree((LPVOID)CART_BASE, 0, MEM_RELEASE);
mappedAtCartBase = 0;
}
#endif
free(rom);
rom = NULL;
romSize = 0;
}
const u8 *romdataGetRom(void) { return rom; }
const u8 *romdataGetRom(void)
{
if (mappedAtCartBase)
return (const u8 *)(uintptr_t)CART_BASE;
return rom;
}
u32 romdataGetRomSize(void) { return romSize; }
/*
* Is this cart address inside the mapped ROM? Used by the PI shims to bounds-
* check DMA before touching host memory.
*/
int romdataCartAddrValid(u32 addr, u32 size)
{
if (!mappedAtCartBase || !romSize)
return 0;
if (addr < CART_BASE)
return 0;
u32 off = addr - CART_BASE;
return off + size <= romSize;
}
const void *romdataMapVa(u32 va)
{
/* TODO(Phase 1): map an N64 RDRAM VA (0xA0000000+) into the image. */
/* N64 RDRAM VAs (0x80000000+/0xA0000000+) have no PC meaning; only cart
* addresses are mapped. Kept for API parity with the PD port. */
(void)va;
return NULL;
}
+57 -1
View File
@@ -12,6 +12,17 @@
#include <stdarg.h>
#include "platform.h"
#if defined(PLATFORM_WINDOWS)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <io.h>
#else
#include <unistd.h>
#endif
#include "system.h"
/* --- Time --------------------------------------------------------------- */
@@ -64,10 +75,55 @@ const char *sysGetExeDir(void)
return exeDir;
}
/*
* Expand the "$S/" (data dir) and "$E/" (exe dir) prefixes used throughout
* the port. Anything else is returned unchanged (relative to the CWD).
*
* $E: directory containing the executable.
* $S: "data/" next to the CWD if it exists there, else "data/" next to the
* executable (so running build-pc/ge007.*.exe from anywhere finds
* ./data/ when run from the repo root).
*/
const char *sysResolvePath(const char *path)
{
static char out[1024];
/* TODO(Phase 1): expand "$S/" (data dir) and "$E/" (exe dir) prefixes. */
#if defined(PLATFORM_WINDOWS)
static char exedir[1024] = "";
if (!exedir[0]) {
DWORD n = GetModuleFileNameA(NULL, exedir, sizeof(exedir) - 1);
if (n) {
char *slash = strrchr(exedir, '\\');
if (slash)
*slash = 0;
} else {
exedir[0] = 0;
}
}
#endif
if (!strncmp(path, "$E/", 3)) {
#if defined(PLATFORM_WINDOWS)
snprintf(out, sizeof(out), "%s\\%s", exedir, path + 3);
#else
snprintf(out, sizeof(out), "./%s", path + 3);
#endif
return out;
}
if (!strncmp(path, "$S/", 3)) {
#if defined(PLATFORM_WINDOWS)
if (_access("data", 0) == 0)
snprintf(out, sizeof(out), "data\\%s", path + 3);
else
snprintf(out, sizeof(out), "%s\\data\\%s", exedir, path + 3);
#else
if (access("data", F_OK) == 0)
snprintf(out, sizeof(out), "data/%s", path + 3);
else
snprintf(out, sizeof(out), "./data/%s", path + 3);
#endif
return out;
}
strncpy(out, path, sizeof(out) - 1);
out[sizeof(out) - 1] = 0;
return out;
+136 -25
View File
@@ -4,13 +4,14 @@
* Owns the GL context that the software RSP (fast3d) renders into. Drives the
* frame boundary hooks called by the port scheduler (gesched.c).
*
* Modelled on the PD port's port/src/video.c (~590 lines).
*
* STATUS: scaffolding stub — implement during Phase 1 (window + clear) and
* Phase 2 (frame pacing, options).
* Phase 1: real window + GL context + clear colour, driven by the demo loop
* in main.c. gfx_* entry points are still the no-ops in gfxstub.c until the
* PD-derived fast3d lands (Phase 2); videoStartFrame/EndFrame do the GL work
* directly so a visible frame exists before that.
*/
#include <SDL.h>
#include <GL/gl.h>
#include "platform.h"
#include "system.h"
@@ -30,53 +31,163 @@ static int vidFpsLimit = 0;
static int vidFullscreen = 0;
static int vidMaximize = 0;
static SDL_Window *vidWindow = NULL;
static SDL_GLContext vidGL = NULL;
/* FPS accounting. */
static uint64_t fpsLastTick = 0;
static int fpsFrames = 0;
static float fpsValue = 0.0f;
int videoInit(void)
{
/* TODO(Phase 1):
* - SDL_Init(VIDEO)
* - create window (title "GoldenEye 007", default 640x480 or config)
* - create GL 3.0+ context
* - build the GfxInitSettings (window-manager API = SDL2, rendering API
* = OpenGL) and call gfx_init()
* - apply config: vsync, fps limit, fullscreen, maximize
*/
sysLogPrintf(LOG_INFO, "videoInit: TODO (Phase 1)");
if (initDone)
return 0;
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
sysLogPrintf(LOG_ERROR, "videoInit: SDL_Init failed: %s",
SDL_GetError());
return -1;
}
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
/* fast3d will want a modern context once it lands; 3.0 core is the floor
* the PD port targets. */
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
int w = 640, h = 480; /* GE's internal resolution (NTSC) */
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
if (vidFullscreen)
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
vidWindow = SDL_CreateWindow("GoldenEye 007 (PC port)",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, w, h, flags);
if (!vidWindow) {
sysLogPrintf(LOG_ERROR, "videoInit: SDL_CreateWindow failed: %s",
SDL_GetError());
return -1;
}
if (vidMaximize)
SDL_MaximizeWindow(vidWindow);
vidGL = SDL_GL_CreateContext(vidWindow);
if (!vidGL) {
sysLogPrintf(LOG_ERROR, "videoInit: SDL_GL_CreateContext failed: %s",
SDL_GetError());
return -1;
}
SDL_GL_MakeCurrent(vidWindow, vidGL);
SDL_GL_SetSwapInterval(vidVsync ? 1 : 0);
/* Phase 1 clear colour: dark blue-grey so the window is visibly alive. */
glClearColor(0.10f, 0.12f, 0.16f, 1.0f);
glViewport(0, 0, w, h);
/* fast3d hooks (no-ops until Phase 2). */
gfx_init(NULL);
fpsLastTick = sysGetMicroseconds();
initDone = 1;
sysLogPrintf(LOG_INFO, "videoInit: %dx%d window + GL context up", w, h);
return 0;
}
void videoDestroy(void)
{
if (initDone) {
gfx_destroy();
initDone = 0;
if (!initDone)
return;
gfx_destroy();
if (vidGL) {
SDL_GL_DeleteContext(vidGL);
vidGL = NULL;
}
if (vidWindow) {
SDL_DestroyWindow(vidWindow);
vidWindow = NULL;
}
SDL_Quit();
initDone = 0;
}
/*
* Pump window events. Returns 1 when the app should quit (window close or
* ESC). Called by the demo loop until the port scheduler owns the frame
* boundary (Phase 2).
*/
int videoHandleEvents(void)
{
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT)
return 1;
if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE)
return 1;
}
return 0;
}
void videoStartFrame(void)
{
if (initDone) gfx_start_frame();
if (!initDone)
return;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
gfx_start_frame();
}
void videoSubmitCommands(Gfx *cmds)
{
if (initDone) gfx_run(cmds);
if (initDone)
gfx_run(cmds);
}
void videoEndFrame(void)
{
if (!initDone) return;
if (!initDone)
return;
gfx_end_frame();
/* TODO(Phase 2): FPS accounting + framerate limiting. */
if (vidWindow)
SDL_GL_SwapWindow(vidWindow);
/* FPS accounting. */
fpsFrames++;
uint64_t now = sysGetMicroseconds();
if (now - fpsLastTick >= 1000000) {
fpsValue = (float)fpsFrames * 1000000.0f / (float)(now - fpsLastTick);
fpsFrames = 0;
fpsLastTick = now;
}
}
void videoSetVsync(int enabled) { vidVsync = enabled; /* TODO: SDL_GL_SetSwapInterval */ }
void videoSetFramerateLimit(int fps) { vidFpsLimit = fps; /* TODO */ }
void videoSetVsync(int enabled)
{
vidVsync = enabled;
if (initDone && vidWindow)
SDL_GL_SetSwapInterval(vidVsync ? 1 : 0);
}
void videoSetFramerateLimit(int fps) { vidFpsLimit = fps; /* TODO Phase 2 */ }
int videoGetFullscreen(void) { return vidFullscreen; }
void videoSetFullscreen(int enabled) { vidFullscreen = enabled; /* TODO */ }
void videoSetFullscreen(int enabled)
{
vidFullscreen = enabled;
if (initDone && vidWindow)
SDL_SetWindowFullscreen(vidWindow,
enabled ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
}
int videoGetMaximize(void) { return vidMaximize; }
void videoSetMaximize(int enabled) { vidMaximize = enabled; /* TODO */ }
float videoGetFPS(void) { return 0.0f; /* TODO */ }
void videoSetMaximize(int enabled)
{
vidMaximize = enabled;
if (initDone && vidWindow) {
if (enabled)
SDL_MaximizeWindow(vidWindow);
else
SDL_RestoreWindow(vidWindow);
}
}
float videoGetFPS(void) { return fpsValue; }
PD_CONSTRUCTOR static void videoConfigInit(void)
{
+559
View File
@@ -0,0 +1,559 @@
#!/usr/bin/env python3
"""
gen_romassets.py — generate absolute ROM asset symbols for the PC port.
For a given region this emits a GAS file (port/src/romassets_<r>.s) defining
every ROM asset symbol as an ABSOLUTE cart address (0x10000000 + rom_offset),
matching the N64 linker layout (ge007.ld). The PC port maps the loaded .z64
into host memory at exactly 0x10000000 (see port/src/romdata.c), so:
* `&symbol` in game code yields the cart address, and romCopy()/
osPiStartDma(OS_READ, ...) shims memcpy from that address (Phase 1).
* file_resource_table.inc.c entries stay in ROM order, so ob.c's
`table[i+1].hw_address - table[i].hw_address` size math is correct.
Sources of truth (all in-repo, N64 build untouched):
scripts/filelist.<r>.csv rom offset + size per asset file
assets/obseg/file_resource_table.inc.c ordered obseg symbol list
assets/ramrom/ramrom.s region-aware (.ifdef/.else/.endif)
assets/music/{music,sfx.ctl,sfx.tbl,instruments.ctl,instruments.tbl}.s
ge007.ld segment markers (BEGIN_SEG/END_SEG)
Code segments (header/boot/start/code/cdata/inflate/game) have no CSV entry;
their offsets are best-effort values derived from the disassembly comments in
ge007.ld and are marked as such. They are only consumed by the N64 boot path,
which the PC port does not run.
Usage:
python3 scripts/gen_romassets.py u # -> port/src/romassets_u.s
python3 scripts/gen_romassets.py e # (EU manifest quirks: see docs)
"""
import os
import re
import sys
CART_BASE = 0x10000000
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def die(msg):
print(f"gen_romassets: ERROR: {msg}", file=sys.stderr)
sys.exit(1)
# --------------------------------------------------------------------------
# CSV manifest
# --------------------------------------------------------------------------
class Csv:
def __init__(self, region):
path = os.path.join(ROOT, "scripts", f"filelist.{region}.csv")
self.by_name = {} # basename -> (offset, size, fullpath)
self.by_path = {} # fullpath -> (offset, size)
with open(path, encoding="utf-8", errors="replace") as f:
for line in f:
parts = line.strip().split(",")
if len(parts) < 3:
continue
try:
off, size = int(parts[0]), int(parts[1])
except ValueError:
continue
full = parts[2]
name = full.rsplit("/", 1)[-1]
self.by_name[name] = (off, size, full)
self.by_path[full] = (off, size)
# Scanner-prefixed names: ge007.<r>.<hexoffset>.<realname>
m = re.match(r"^ge007\.[uje]\.[0-9a-fA-F]+\.(.+)$", name)
if m:
self.by_name.setdefault(m.group(1), (off, size, full))
def lookup(self, candidates):
"""Try candidate basenames in order; return (offset, size, matched)."""
for cand in candidates:
if cand in self.by_name:
off, size, full = self.by_name[cand]
return off, size, cand
# also allow an exact full-path hit (dir-qualified lookups)
if cand.startswith("assets/") and cand in self.by_path:
off, size = self.by_path[cand]
return off, size, cand
return None
@staticmethod
def name_candidates(basename):
"""Manifest names are inconsistent across regions: with/without .bin,
trailing Z added/dropped. Try the plain name first, then variants."""
cands = [basename]
stem = re.sub(r"\.(bin|seg)$", "", basename)
if stem != basename:
cands.append(stem)
else:
cands.append(basename + ".bin") # CSV names carry .bin
for s in (stem, stem[:-1] if stem.endswith("Z") else stem + "Z"):
cands.append(s) # trailing Z added/dropped
cands.append(s + ".bin")
cands.append(s + ".seg") # ob__ob_end.seg style
return cands
# --------------------------------------------------------------------------
# Region-aware .s parsing (ob_seg.s / ramrom.s)
# --------------------------------------------------------------------------
class RegionParser:
"""Parses asset .s files, honouring .ifdef VERSION_XX / .else / .endif.
Yields, in file order, either:
("file", symbol, incbin_path) — a .global label + .incbin pair
("label", symbol) — a bare label (segment marker etc.)
for the requested target region.
"""
COND_RE = re.compile(r"^\s*\.ifdef\s+(VERSION_\w+)")
GLOBAL_RE = re.compile(r"^\s*\.global\s+(\w+)")
LABEL_RE = re.compile(r"^(\w+):")
INCBIN_RE = re.compile(r'^\s*\.incbin\s+"([^"]+)"')
def __init__(self, target_region): # "u" | "e" | "j"
self.target = {"u": "VERSION_US", "e": "VERSION_EU",
"j": "VERSION_JP"}[target_region]
self.stack = [] # entries: (cond, in_else)
def _active(self):
for cond, in_else in self.stack:
if (cond == self.target) != (not in_else):
return False
return True
def parse(self, path):
"""State: a `.global X` arms `pending`; the following `X:` label moves
it to `expecting` (its own label line); an `.incbin` then makes it a
file entry, otherwise the next `.global` flushes it as a bare marker."""
out = []
pending = None # symbol from .global, label line not yet seen
expecting = None # label seen; waiting for .incbin (file) or next
# .global (bare marker)
with open(path, encoding="utf-8", errors="replace") as f:
for raw in f:
line = raw.rstrip("\n")
m = re.match(r"^\s*\.ifdef\s+(\w+)", line)
if m:
self.stack.append((m.group(1), False))
continue
if re.match(r"^\s*\.else\b", line):
if self.stack:
cond, _ = self.stack[-1]
self.stack[-1] = (cond, True)
continue
if re.match(r"^\s*\.endif\b", line):
if self.stack:
self.stack.pop()
continue
if not self._active():
continue
m = self.GLOBAL_RE.match(line)
if m:
if expecting is not None:
out.append(("label", expecting))
expecting = None
pending = m.group(1)
continue
m = self.INCBIN_RE.match(line)
if m and expecting is not None:
out.append(("file", expecting, m.group(1)))
expecting = None
continue
m = self.LABEL_RE.match(line)
if m:
sym = m.group(1)
if sym == pending:
expecting = sym
pending = None
else:
out.append(("label", sym))
if expecting is not None:
out.append(("label", expecting))
return out
# --------------------------------------------------------------------------
# Per-source extraction
# --------------------------------------------------------------------------
def parse_resource_table(region):
"""assets/obseg/file_resource_table.inc.c -> ordered [(symbol, pathstr)].
The table is C: entries for other regions are wrapped in
`#ifdef VERSION_XX` (e.g. all L*P text rows under VERSION_EU). Honour
those conditionals for the target region."""
path = os.path.join(ROOT, "assets", "obseg", "file_resource_table.inc.c")
cond_region = {"VERSION_US": "u", "VERSION_EU": "e", "VERSION_JP": "j"}
entries = []
seen = set()
stack = [] # condition names (only VERSION_* occur in this file)
rx = re.compile(r'\{\s*(\w+)\s*,\s*"([^"]*)"\s*,\s*&(\w+)\s*\}')
with open(path, encoding="utf-8") as f:
for line in f:
m = re.match(r"^\s*#ifdef\s+(\w+)", line)
if m:
stack.append(m.group(1))
continue
if re.match(r"^\s*#endif\b", line):
if stack:
stack.pop()
continue
active = all(cond_region.get(c, "?") == region for c in stack)
if not active:
continue
m = rx.search(line)
if not m:
continue
sym = m.group(3)
if sym in seen:
continue
seen.add(sym)
entries.append((sym, m.group(2)))
return entries
def resolve_obseg(entries, csv):
"""Map each resource-table symbol to (offset, size)."""
resolved = [] # (symbol, offset, size, matched_name)
missing = []
for sym, pathstr in entries:
base = pathstr.rsplit("/", 1)[-1]
hit = csv.lookup(Csv.name_candidates(base))
if hit is None:
# fall back to the symbol name itself (pathstr may be stale,
# e.g. BG_ELD -> bg_imp_all_p_seg; strip a trailing _seg)
cands = Csv.name_candidates(sym)
if sym.endswith("_seg"):
cands += Csv.name_candidates(sym[:-4])
hit = csv.lookup(cands)
if hit is None:
missing.append((sym, pathstr))
continue
off, size, matched = hit
resolved.append((sym, off, size, matched))
return resolved, missing
def parse_ramrom_region(region, csv):
rp = RegionParser(region)
path = os.path.join(ROOT, "assets", "ramrom", "ramrom.s")
items = rp.parse(path)
files = [] # (symbol, offset, size)
markers = {} # bare label -> index of next file symbol (resolved later)
pending_marker = None
for kind, a, *rest in items:
if kind == "file":
sym, incbin = a, rest[0]
base = incbin.rsplit("/", 1)[-1]
hit = csv.lookup(Csv.name_candidates(base))
if hit is None:
die(f"ramrom: no CSV entry for {sym} ({incbin})")
off, size, _ = hit
files.append((sym, off, size))
pending_marker = None
else: # bare label
if a.endswith("_end"):
continue # handled via start+size below
markers[a] = len(files) # value = offset of next file
return files, markers
def parse_music_tracks(region, csv):
"""music.s: music_file <name> lines (region blocks inside the macro)."""
path = os.path.join(ROOT, "assets", "music", "music.s")
tracks = []
with open(path, encoding="utf-8") as f:
for line in f:
m = re.match(r"^\s*music_file\s+(\w+)", line)
if m:
tracks.append(m.group(1))
resolved = []
for name in tracks:
hit = csv.lookup(Csv.name_candidates(f"{name}.bin"))
if hit is None:
die(f"music: no CSV entry for track {name}")
off, size, _ = hit
resolved.append((name, off, size))
return resolved
def parse_music_sections(csv):
"""sfx.ctl.s / sfx.tbl.s / instruments.ctl.s / instruments.tbl.s:
each defines _<x>SegmentRomStart + incbin + _<x>SegmentRomEnd."""
out = []
for stem in ("sfx.ctl", "sfx.tbl", "instruments.ctl", "instruments.tbl"):
path = os.path.join(ROOT, "assets", "music", f"{stem}.s")
start_sym = end_sym = incbin = None
with open(path, encoding="utf-8") as f:
for line in f:
m = re.match(r"^\s*\.global\s+(\w+)", line)
if m:
sym = m.group(1)
if sym.endswith("SegmentRomStart"):
start_sym = sym
elif sym.endswith("SegmentRomEnd"):
end_sym = sym
continue
m = re.match(r'^\s*\.incbin\s+"([^"]+)"', line)
if m:
incbin = m.group(1)
if not (start_sym and end_sym and incbin):
die(f"music section {stem}: expected start/end/incbin, got "
f"{start_sym}/{end_sym}/{incbin}")
base = incbin.rsplit("/", 1)[-1]
hit = csv.lookup(Csv.name_candidates(base))
if hit is None:
die(f"music section {stem}: no CSV entry for {incbin}")
off, size, _ = hit
out.append((start_sym, end_sym, off, size))
return out
# --------------------------------------------------------------------------
# Linker-script segments (ge007.ld)
# --------------------------------------------------------------------------
def build_segments(region, csv, obseg_resolved, ramrom_files, music_tracks,
music_sections, rom_size):
"""Assign (offset, size) to every BEGIN_SEG segment in ge007.ld.
Asset-backed segments come from the CSV (exact). Code segments use
best-effort values from the disassembly comments in ge007.ld.
"""
seg = {} # name -> (offset, size, exact?)
def csv1(basename):
hit = csv.lookup(Csv.name_candidates(basename))
if hit is None:
die(f"segment lookup failed for {basename}")
return hit[0], hit[1]
# --- code segments (approximate; N64 boot path only) -----------------
seg["header"] = (0x0, 0x40, False)
seg["boot"] = (0x40, 0x1000 - 0x40, False) # start @0x1000 assumed
seg["start"] = (0x1000, 0x50, False) # RAM size 0x50 (ld cmt)
seg["code"] = (0x1050, 0x21990 - 0x1050, False) # ld comment 001050-021990
# inflate size 0x29BD (ld comment [29BD]); game ends at fontdl start.
fontdl_off, _ = csv1("jfont_dl.bin")
game_end = fontdl_off # ld: fontdl @ game end
game_size = 0xE2D51 # ld comment [E2D51]
seg["game"] = (game_end - game_size, game_size, False)
inflate_size = 0x29BD
seg["inflate"] = (seg["game"][0] - inflate_size, inflate_size, False)
seg["cdata"] = (seg["code"][0] + seg["code"][1],
seg["inflate"][0] - (seg["code"][0] + seg["code"][1]),
False)
# --- asset segments (exact, from CSV) --------------------------------
off, size = csv1("jfont_dl.bin")
seg["fontdl"] = (off, size, True)
off, size = csv1("jfont_chardata.bin")
seg["jfontchardata"] = (off, size, True)
off, size = csv1("efont_chardata.bin")
seg["efontchardata"] = (off, size, True)
off, size = csv1("animationtable_entries.bin")
seg["animation_entries"] = (off, size, True)
off, size = csv1("animationtable_data.bin")
seg["animation_data"] = (off, size, True)
off, size = csv1("Globalimagetable.bin")
seg["Globalimagetable"] = (off, size, True)
off, size = csv1("rarewarelogo.bin")
seg["rarewarelogo"] = (off, size, True)
off, size = csv1("usedby7F008DE4.bin") # assets/romfiles2.s blob
seg["romfiles2"] = (off, size, True)
if ramrom_files:
first = min(o for _, o, _ in ramrom_files)
last_end = max(o + s for _, o, s in ramrom_files)
seg["ramromfiles"] = (first, last_end - first, True)
# font objects = kerning + chartable, contiguous in ROM (verified).
k_off, k_size = csv1("fontBankGothic_kerning.bin")
c_off, c_size = csv1("fontBankGothic_fontchartable.bin")
if k_off + k_size != c_off:
die("fontBankGothic kerning/chartable not contiguous in CSV")
seg["fontbankgothic"] = (k_off, k_size + c_size, True)
k_off, k_size = csv1("fontZurichBold_kerning.bin")
c_off, c_size = csv1("fontZurichBold_fontchartable.bin")
if k_off + k_size != c_off:
die("fontZurichBold kerning/chartable not contiguous in CSV")
seg["fontzurichbold"] = (k_off, k_size + c_size, True)
# musicfiles = sfx.ctl .. last M* track end.
m_off = min(o for _, _, o, _ in music_sections)
m_end = max(o + s for _, o, s in music_tracks)
seg["musicfiles"] = (m_off, m_end - m_off, True)
# obseg = first resource-table symbol .. end of ob__ob_end_seg.
if obseg_resolved:
first = min(o for _, o, _, _ in obseg_resolved)
last_end = max(o + s for _, o, s, _ in obseg_resolved)
seg["obseg"] = (first, last_end - first, True)
# images = everything after obseg to the end of the ROM.
if "obseg" in seg:
img_off = seg["obseg"][0] + seg["obseg"][1]
seg["images"] = (img_off, rom_size - img_off, True)
return seg
# --------------------------------------------------------------------------
# Emission
# --------------------------------------------------------------------------
def emit(region, csv, rom_size):
lines = []
ap = lines.append
ap("/*")
ap(f" * AUTO-GENERATED by scripts/gen_romassets.py ({region.upper()}) -- do not edit.")
ap(" *")
ap(" * Absolute ROM asset symbols for the PC port. Each symbol is the N64")
ap(" * cart address (0x10000000 + rom_offset) of the corresponding asset in")
ap(f" * scripts/filelist.{region}.csv, so `&symbol` / romCopy() work exactly as")
ap(" * on hardware once romdata.c has mapped the .z64 at 0x10000000.")
ap(" * Regenerate: python3 scripts/gen_romassets.py " + region)
ap(" */")
ap(".section .data")
ap("")
def sym(name, value):
ap(f".global {name}")
ap(f".set {name}, 0x{value:X}")
# --- obseg (file_resource_table order = ROM order) -------------------
entries = parse_resource_table(region)
resolved, missing = resolve_obseg(entries, csv)
if missing:
for s, p in missing:
print(f"gen_romassets: MISSING {s} ({p})", file=sys.stderr)
die(f"{len(missing)} obseg symbols unresolved")
# Invariants the game relies on (ob.c size math = table[i+1]-table[i]).
# Offsets must be non-decreasing; zero-size placeholder files may share
# an offset (bg_ash/bg_imp/bg_sho all sit at one address in the US ROM).
for i in range(1, len(resolved)):
prev_sym, prev_off, prev_size, _ = resolved[i - 1]
sym2, off2, _, _ = resolved[i]
if off2 < prev_off:
die(f"resource table not in ROM order: {prev_sym} ({prev_off:#x}) "
f"> {sym2} ({off2:#x})")
if prev_off + prev_size > off2:
print(f"gen_romassets: WARNING overlap {prev_sym}+{prev_size} "
f"> {sym2}", file=sys.stderr)
ap("/* --- assets/obseg (file_resource_table.inc.c order) ------------- */")
for symname, off, size, matched in resolved:
sym(symname, CART_BASE + off)
sym(f"end_{symname}", CART_BASE + off + size)
ap("")
# --- ramrom -----------------------------------------------------------
ramrom_files, ramrom_markers = parse_ramrom_region(region, csv)
if ramrom_files:
ap("/* --- assets/ramrom (demo replays) ---------------------------- */")
for i, (symname, off, size) in enumerate(ramrom_files):
sym(symname, CART_BASE + off)
sym(f"{symname}_end", CART_BASE + off + size)
for marker, idx in ramrom_markers.items():
if idx < len(ramrom_files):
sym(marker, CART_BASE + ramrom_files[idx][1])
ap("")
# --- music ------------------------------------------------------------
tracks = parse_music_tracks(region, csv)
sections = parse_music_sections(csv)
hit = csv.lookup(Csv.name_candidates("number_music_samples"))
if hit is None:
die("music: no CSV entry for number_music_samples")
sample_tbl_off = hit[0]
first_track_off = min(o for _, o, _ in tracks)
ap("/* --- assets/music ------------------------------------------------ */")
sym("_musicsampletblSegmentRomStart", CART_BASE + sample_tbl_off)
sym("number_music_samples", CART_BASE + sample_tbl_off)
sym("number_music_samples_end", CART_BASE + sample_tbl_off + 4)
sym("table_music_data", CART_BASE + sample_tbl_off + 4)
sym("_musicsampletblSegmentRomEnd", CART_BASE + first_track_off)
sym("table_music_data_end", CART_BASE + first_track_off)
for start_sym, end_sym, off, size in sections:
sym(start_sym, CART_BASE + off)
sym(end_sym, CART_BASE + off + size)
for name, off, size in tracks:
sym(name, CART_BASE + off)
sym(f"end_{name}", CART_BASE + off + size)
ap("")
# --- romfiles2 ---------------------------------------------------------
hit = csv.lookup(Csv.name_candidates("usedby7F008DE4.bin"))
if hit is not None:
unknown2_off, unknown2_size = hit[0], hit[1]
ap("/* --- assets/romfiles2.s -------------------------------------- */")
sym("unknown2", CART_BASE + unknown2_off)
sym("unknown2_end", CART_BASE + unknown2_off + unknown2_size)
ap("")
# --- linker-script segment markers -------------------------------------
segs = build_segments(region, csv, resolved, ramrom_files, tracks,
sections, rom_size)
ap("/* --- ge007.ld segment markers ------------------------------------ */")
ap("/* RomStart/RomEnd are exact for asset segments; Start/End mirror them")
ap("/* (game code only ever uses End-Start size differences for these).")
ap("/* Code segments are approximate (see generator header comment). */")
for name in ("header", "boot", "start", "code", "cdata", "inflate",
"game", "fontdl", "jfontchardata", "efontchardata",
"animation_entries", "animation_data", "Globalimagetable",
"rarewarelogo", "romfiles2", "ramromfiles", "fontbankgothic",
"fontzurichbold", "musicfiles", "obseg", "images"):
if name not in segs:
continue
off, size, exact = segs[name]
note = "" if exact else " /* approximate */"
sym(f"_{name}SegmentRomStart", CART_BASE + off)
sym(f"_{name}SegmentRomEnd", CART_BASE + off + size)
sym(f"_{name}SegmentStart", CART_BASE + off)
sym(f"_{name}SegmentEnd", CART_BASE + off + size)
if note:
lines[-1] += note
# alt-start (ld: _startSegmentRomStart + 0x100000)
s_off = segs["start"][0]
sym("_alt_startSegmentRomStart", CART_BASE + s_off + 0x100000)
sym("_alt_startSegmentStart", CART_BASE + s_off + 0x100000)
out = os.path.join(ROOT, "port", "src", f"romassets_{region}.s")
with open(out, "w", encoding="utf-8", newline="\n") as f:
f.write("\n".join(lines) + "\n")
n_syms = sum(1 for l in lines if l.startswith(".set "))
print(f"gen_romassets: wrote {os.path.relpath(out, ROOT)} "
f"({n_syms} symbols, obseg={len(resolved)}, ramrom={len(ramrom_files)}, "
f"tracks={len(tracks)}, segments={len(segs)})")
def main():
if len(sys.argv) != 2 or sys.argv[1] not in ("u", "e", "j"):
die("usage: gen_romassets.py <u|e|j>")
region = sys.argv[1]
csv = Csv(region)
rom_size = None
rom_name = {"u": "baserom.u.z64", "e": "baserom.e.z64",
"j": "baserom.j.z64"}[region]
rom_path = os.path.join(ROOT, rom_name)
if os.path.exists(rom_path):
rom_size = os.path.getsize(rom_path)
else:
print(f"gen_romassets: note: {rom_name} not found; "
f"images segment sized from CSV max", file=sys.stderr)
rom_size = max(o + s for o, s in csv.by_path.values())
emit(region, csv, rom_size)
if __name__ == "__main__":
main()