Skip to the content.

GoldenEye 007 — PC Port: architecture & plan

This began as the pre-implementation research note and is kept as the architecture reference. Sections 1–9 describe the design; section 10 lists external references. The phased plan in section 8 is largely done through Phase 2 — for current status see the README and dev/LEVEL-STATUS.md; for the blow-by-blow finding log see dev/findings.md.

This document captures the research done to plan a PC port of GoldenEye 007, based on the decompilation this repository is forked from (n64decomp/007). It is modeled on the approach used by the Perfect Dark PC port, which ports the same Rare “Indy” engine (a later revision) to modern platforms.


Contents

  1. Executive summary
  2. What the PD port actually does (the reference architecture)2.1 Emulate the RSP, bypass the RDP · 2.2 How the RSP is invoked · 2.3 Build system · 2.4 PD-port copy-candidate audit
  3. GoldenEye decomp: what we’re porting3.1 Entry / main loop · 3.2 The two libultra trees
  4. N64 hardware surfaces that must be shimmed
  5. GE-specific graphics differences vs PD (the RSP work)
  6. Audio
  7. Input & saves
  8. Recommended plan — phases 0–4
  9. Open questions / risks
  10. References

1. Executive summary


2. What the PD port actually does (the reference architecture)

The PD port is a fork of the PD decomp with one added top-level directory, port/. The original game code (src/game/*.c, src/lib/*.c) is compiled unmodified for the PC. All N64-hardware dependencies are satisfied by the port/ layer:

port/
├── fast3d/            # Software RSP: interprets GBI display lists -> OpenGL
│   ├── gfx_pc.cpp     #   the RSP interpreter (matrices, lights, tris, fog)
│   ├── gfx_opengl.cpp #   OpenGL rendering backend (shaders, textures, zbuf)
│   ├── gfx_sdl2.cpp   #   SDL2 window-manager backend
│   ├── gfx_cc.cpp     #   color-combiner -> GLSL shader compiler
│   └── ...
├── include/           # port-facing headers
│   ├── audio.h  config.h  fs.h  input.h  mixer.h  mod.h
│   ├── romdata.h  system.h  video.h  utils.h  platform.h
└── src/
    ├── main.c         # PC entry point (replaces boot.s / _start.s)
    ├── libultra.c     # shims for the libultra OS API (threads, time, VI, PI, SI, SP, AI)
    ├── video.c        # SDL2 window + GL context + frame pacing
    ├── audio.c        # SDL audio device, buffer queueing
    ├── mixer.c        # audio mixing / RSP-audio-buffer processing
    ├── mod.c          # MOD music playback
    ├── input.c        # SDL2 keyboard/mouse/gamepad -> N64 controller structs
    ├── fs.c           # filesystem abstraction over the ROM + data dir
    ├── romdata.c      # loads the .z64 ROM from disk, sets up segments
    ├── config.c       # INI config (pd.ini)
    ├── system.c       # platform primitives (time, logging, paths)
    ├── crash.c        # crash handler / stack traces
    ├── pdsched.c      # replacement RCP scheduler that drives the software RSP
    └── utils.c

2.1 The key architectural insight: emulate the RSP, bypass the RDP

On real hardware the render path is:

R4300 builds a Gfx display list
        |
        v
   RSP (coproc 0) runs "fast3d" ucode, turns GBI cmds into RDP cmds
        |
        v
   RDP (coproc 1) rasterizes into the framebuffer

The PD port emulates the RSP in software (fast3d/gfx_pc.cpp) and bypasses the RDP entirely: the software RSP translates GBI commands directly into OpenGL calls. The R4300 game code is run natively and is unaware of the difference — it just builds display lists and “starts the RSP” as usual.

The frame flow (from port/src/pdsched.c + port/src/video.c):

videoStartFrame()  -> gfx_start_frame()
   game builds display list (Gfx*)
videoSubmitCommands(Gfx* cmds) -> gfx_run(cmds)   # <- the software RSP runs here
videoEndFrame()    -> gfx_end_frame()             # swap buffers, FPS accounting

2.2 How the RSP is invoked

The game’s RCP scheduler (src/lib/sched.c in PD, src/sched.c in GE) calls osSpTaskLoad() + osSpTaskStartGo() to hand a task (gfx or audio) to the RSP. The port:

2.3 Build system

The PC build is CMake (the N64 build stays Make + IDO). Dependencies: SDL2, zlib, OpenGL (plus stdc++, m, dl). It compiles:

It deliberately excludes the N64 I/O drivers (src/lib/ultra/io/*.c), the boot/TLB assembly, and the RSP microcode (.s under rsp/).

2.4 PD-port copy-candidate audit (session 2026-08-22)

The Perfect Dark port is a standing reference for this project (see CONTRIBUTING.md): same Rare “Indy”/Bond engine family, and its port layer solves the same problem classes we are currently hitting. Audited against our remaining work items:

Direct copy candidates (Phase 3/4 — our stubs were scaffolded to be replaced by these):

PD file Lines Replaces our Notes
port/src/mixer.c 722 31-line stub libaudio→SDL final mixer; same RareAL family. Adapt to GE’s audi.c wiring (sample rate/buffering)
port/src/input.c 1551 48-line stub SDL_GameController backend: hotplug, rumble, keyboard fallback. Remap the button table to GE’s scheme (C-buttons/Z-trig differ from PD)
port/src/fs.c 294 81 lines File-backed save dir (--savedir, $S path expansion) — foundation for Phase 4 EEPROM/PFS file backing (ours are no-op stubs today)
port/src/audio.c 75 70 lines Near-identical; diff when Phase 3 starts

Reference implementations (Phase 2 — the D32/D37/D43 class). PD’s port layer has a port/src/preprocess/ module (~4,125 lines): a per-ROM-segment preprocessfunc table in their romdata.c that converts N64-layout asset data to PC-native layout at load time — a systematic solution to the problem class we have been fixing ad-hoc per asset type.

Not copyable: GE-specific ModelRoData record map; fast3d CC/RM correctness (GE’s custom GBI modes vs gmain.s do not exist in PD); PD extras (config.c, optionsmenu.c, mpsetups.c, mod.c) — options menu, MP setup, MOD player; not part of 1:1 fidelity.

Caveats: copy port-layer files only — their decomp was modified for PC (e.g. types.h keeps N64 offset comments while using real pointers); our non-negotiables are unchanged. Same family ≠ identical format: validate every copied conversion per-field against GE headers + ROM ground truth (the standing D32 procedure).


3. GoldenEye decomp: what we’re porting

3.1 Entry / main loop

boot.s -> init()  [src/init.c]
  -> decompress data segment
  -> osInitialize(), TLB setup
  -> osCreateThread(mainThread, mainproc)
mainproc()
  -> idleCreateThread(), piCreateManager(), rmonCreateThread()
  -> schedulerInitThread()   # osCreateScheduler + osScAddClient(gfxClient)
  -> bossEntry()             # [src/boss.c] real game start

The port’s main.c replaces boot.s/init() and drives mainproc() (or bossEntry()) directly after setting up video/audio/input/ROM.

3.2 The two libultra trees: src/libultra/ and src/libultrare/

GE ships two libultra trees. src/libultra/ is the standard Nintendo libultra; src/libultrare/ holds Rare’s modified/replacement files. The N64 build selects which files to compile via src/libultrare/Makefile.libultrare, which explicitly lists each file as original (from libultra/) or Rare (from libultrare/). The Rare files override the originals where they overlap.

What the PC build must do with each:

The exact original-vs-Rare file lists are in src/libultrare/Makefile.libultrare (LIBULTRA_*_C_FILES vs LIBULTRARE_*_C_FILES). Use it as the ground truth when curating the PC build’s source list.


4. N64 hardware surfaces that must be shimmed

Subsystem GE API used Port strategy (from PD)
RSP (coproc 0) osSpTaskLoad/StartGo/Yield, display lists Software RSP (fast3d) — main work
RDP (coproc 1) osDpSetStatus, osDpSetNextBuffer, render modes Bypassed; OpenGL backend
VI (video) osViSetMode, osViVSyncCallback, osViWaitVSync SDL2 window + GL context + frame pacing
AI (audio) osAiSetFrequency/SetNextBuffer/GetLength SDL audio device + queue
PI (peripheral) osPiRawStartDma, osPiCreateManager, cart reads Read from ROM file on disk
SI (controller) osContInit/StartReadData, osEeprom*, osPfs*/osMotor* SDL input + file-backed EEPROM; PFS/motor stubbed (accessory detection only)
OS core threads, msg queues, timers, osGetTime, cache Single-threaded shims + host clock
R4300 specifics TLB, cache, FPU csr, K0/K1 segments No-ops / identity (native x86-64)

5. GE-specific graphics differences vs PD (the RSP work)

GE’s include/gbi_extension.h defines the custom GBI surface. Compared to the PD port’s fast3d, the deltas are:

  1. G_TRI4 — draws up to 4 triangles in one command using 4-bit vertex indices (0–15). A memory-saving optimization (Rare packed 4 tris into the slot normally used for G_TRI2).
    • Status: already supported by the PD fast3d (gfx_sp_tri4, and the PD game itself uses it). ✅
  2. G_SETTEX (0xc0) — “use texture from bank”: selects a texture by bank/tile with detail/mipmap type, min-level, detail-id and texture-id. Emitted only by gsSPUseTexture (include/gbi_extension.h:193).
    • Status: appears UNUSED. gsSPUseTexture is defined but never called anywhere in the game code or the RSP ucode (rsp/graphics/gmain.s), and G_SETTEX appears nowhere outside its #define. The game uses the standard texture commands (gSPTexture, gSPTextureL, gSPTextureRectangle, gSPTextureRectangleFlip) — all already handled by the PD fast3d. If this holds (verify the decomp is complete), Phase 2 shrinks to verifying the custom CC/RM modes and the PD fast3d may work largely as-is. Keep a G_SETTEX decode path as a safety net, but it is no longer the headline task.
  3. Custom color-combiner modesG_CC_MODULATEIFADE, G_CC_FADE, G_CC_DECALFADE, G_CC_BLENDRGBFADEA, etc. (fade/blend variants).
    • Status: handled generically by the fast3d color-combiner -> GLSL shader compiler (gfx_cc.cpp), which compiles arbitrary CC equations. Needs verification that each GE mode produces correct GLSL.
  4. Custom render modeG_RM_CUSTOM_AA_ZB_XLU_SURF (AA_ZB_XLU with Z_UPD).
    • Status: handled by the render-mode -> GL state mapping; verify.
  5. gDPLoadTLUT06/07 — custom TLUT load variants.
    • Status: minor, map onto the existing G_LOADTLUT path.

Conclusion: the PD fast3d is a strong starting point. G_SETTEX appears unused (see item 2), so the concrete new RSP work is verification of the custom CC/RM modes against gmain.s, with G_SETTEX as a low-priority safety net. Everything else (matrices, lights, fog, tri1/tri4, texture load, sync/flush) is shared.

Note: GE’s on-hardware RSP ucode is rsp/graphics/gmain.s. We do not run it on PC, but it is the ground truth for the exact command encodings GE emits — use it to validate the software RSP’s command decoding.


6. Audio


7. Input & saves


Phase 0 — Scaffolding (this change)

Phase 1 — Boot to a window

Phase 1.5 — Boot to first frame (ABI reconciliation)

Phase 2 — RSP / rendering

Phase 3 — Audio + input

Phase 4 — Saves + polish


9. Open questions / risks

  1. G_SETTEX (low priority) — appears unused (gsSPUseTexture is never called). If a future decomp revision or a missed call site shows it is used, reverse the texture-bank selection (bank -> image, tile, detail/mipmap type) from gmain.s + tex.c / initmttex.c. Not currently the main unknown.
  2. Custom CC correctness — the GLSL compiler must handle every fade/blend mode GE uses; validate per-mode against the original. This is now the main rendering unknown.
  3. Scheduler fidelity — GE’s sched.c has speed-graph / profiling hooks (speedgraphMarkerHandler) and a slightly different task model than PD; the port scheduler must reproduce the gfx/audio interleaving and VSync pacing.
  4. Decomp completeness — the decomp is WIP; some functions may still be stubs/unk_. The PC port inherits whatever state the decomp is in. Track the upstream n64decomp/007 and rebase.
  5. Endianness / alignment — GE, like PD, is big-endian N64 code with unaligned accesses; the PD port handles this with careful casts and -fno-strict-aliasing. Reuse those flags.

10. References