How Lighthouse Maintains Compatibility with Different N64 ROM Versions: US v1.0, v1.1, PAL, and JP

Lighthouse uses a compile-time VERSION flag combined with conditional-compilation helpers and runtime asset-remap tables to support all four original Banjo-Kazooie N64 ROM releases from a single codebase.

The HarbourMasters Lighthouse project is a reverse-engineered port of Banjo-Kazooie that maintains compatibility across multiple N64 ROM versions. Rather than maintaining separate forks for each regional release, Lighthouse employs a unified build system that adapts the same source tree to target US v1.0, US v1.1, PAL, or JP versions. This approach ensures that asset references, frame timing, and version-specific code paths align correctly with each ROM's unique characteristics.

Compile-Time Version Flag System

The foundation of Lighthouse N64 ROM version compatibility lies in the central header file include/version.h. This file defines four numeric constants that represent each supported release:

#define VERSION_USA_1_0 0   // US v1.0
#define VERSION_PAL     1   // PAL
#define VERSION_USA_1_1 2   // US v1.1
#define VERSION_JP      3   // JP

The build system passes -DVERSION=$(VERSION) to the compiler via the Makefile. This macro definition selects which code paths are active during compilation, allowing a single source tree to produce four distinct binaries—each targeting a specific N64 ROM version.

Conditional Compilation Macros

To manage version-specific code cleanly, include/version.h provides two categories of helper macros:

Exclusive macros that compile code for only one version:

  • USA10_EXCLUSIVE(body) – US v1.0 only
  • PAL_EXCLUSIVE(body) – PAL only
  • USA11_EXCLUSIVE(body) – US v1.1 only
  • JP_EXCLUSIVE(body) – JP only

Generic selector macro VER_SELECT(usa0, pal, usa1, jp) that resolves to the appropriate argument based on the current VERSION:

#if VERSION == VERSION_USA_1_0
#define VER_SELECT(usa0, pal, usa1, jp) usa0
#elif VERSION == VERSION_PAL
#define VER_SELECT(usa0, pal, usa1, jp) pal
#elif VERSION == VERSION_USA_1_1
#define VER_SELECT(usa0, pal, usa1, jp) usa1
#elif VERSION == VERSION_JP
#define VER_SELECT(usa0, pal, usa1, jp) jp
#endif

These macros eliminate scattered #if VERSION == … checks throughout the codebase and centralize version logic in a maintainable pattern.

Version-Specific Source Isolation

Functions that differ between ROM releases are concentrated in src/SM/version_compat.c. This file uses the same conditional-compilation guards to isolate variant implementations within one physical location. Only the block matching the current VERSION compiles, keeping the binary free of dead code while preserving all variants in source control.

Example structure from src/SM/version_compat.c:

#if VERSION == VERSION_USA_1_0
    // US v1.0 specific language selection UI
    void func_8038B490_pal(s32 arg0, s32 arg1) { … }
#endif

This pattern ensures that engineers can examine all version variants side-by-side without switching branches or repositories.

Runtime Asset ID Remapping

A significant challenge in N64 ROM version compatibility is that assets—textures, models, sounds, and other data—often have different numeric IDs across releases. Lighthouse solves this through three static unordered maps declared in src/port/GameVersion/AssetVersionRemap.h:

  • sV10toV11Remap – Translates US v1.0 asset IDs to US v1.1
  • sV10toPALRemap – Translates US v1.0 asset IDs to PAL
  • sV10toJPRemap – Translates US v1.0 asset IDs to JP

At runtime, the engine calls remapAssetId(originalId) to resolve generic asset references into ROM-specific IDs. The implementation follows this pattern:

uint32_t getAssetId(uint32_t genericId) {
    switch (VERSION) {
        case VERSION_USA_1_0: return genericId;                     // no remap needed
        case VERSION_USA_1_1: return sV10toV11Remap.at(genericId);
        case VERSION_PAL:    return sV10toPALRemap.at(genericId);
        case VERSION_JP:     return sV10toJPRemap.at(genericId);
    }
}

This design treats US v1.0 as the canonical reference format, minimizing the number of remap tables required while supporting all target versions.

Frame-Rate and Timing Compatibility

Regional ROM versions differ in video refresh rate: PAL runs at 50 Hz while NTSC versions (US v1.0, US v1.1, JP) run at 60 Hz. The include/version.h header defines FRAMERATE accordingly:

#if VERSION == VERSION_PAL
#define FRAMERATE 50
#else
#define FRAMERATE 60
#endif

The main game loop consumes this constant to drive timing logic:

while (running) {
    updateGameLogic();
    renderFrame();
    waitForVBlank(FRAMERATE);   // FRAMERATE defined in version.h
}

This ensures identical gameplay speed and physics behavior regardless of the target ROM's native refresh rate.

Build System Integration

The Makefile orchestrates the entire version selection mechanism. By defining VERSION at compile time, the build process can iterate through all four targets:


# Pseudocode representing the build pattern

make VERSION=0  # Produces US v1.0 compatible binary

make VERSION=1  # Produces PAL compatible binary

make VERSION=2  # Produces US v1.1 compatible binary

make VERSION=3  # Produces JP compatible binary

Each invocation preprocesses the same source files with different VERSION values, resulting in tailored binaries without source duplication.

Summary

Lighthouse achieves N64 ROM version compatibility through five coordinated mechanisms:

  • Compile-time VERSION flag (include/version.h) selects the target ROM at build time
  • Conditional-compilation macros (VER_SELECT, *_EXCLUSIVE) isolate version-specific code cleanly
  • Centralized variant implementations (src/SM/version_compat.c) group related changes in one location
  • Runtime asset remapping (AssetVersionRemap.h) translates generic IDs to ROM-specific values
  • Frame-rate constants ensure consistent timing across 50 Hz PAL and 60 Hz NTSC versions

Together, these components allow a single source tree to build correctly for all four original Banjo-Kazooie N64 releases without maintaining separate forks or manual patches.

Frequently Asked Questions

Does Lighthouse require separate downloads for each ROM version?

No. The same source repository builds all four variants. Users or distributors compile with the appropriate VERSION flag (0-3) to target their specific ROM. The resulting binary is paired with the matching original N64 dump.

Why does Lighthouse use US v1.0 as the canonical asset reference?

US v1.0 serves as the baseline because it reduces the number of remap tables from twelve (pairwise between all versions) to three (v1.0 → each other version). This minimizes maintenance overhead and potential translation errors when assets are added or modified.

What happens if I run a binary built for the wrong ROM version?

Asset ID mismatches would cause incorrect textures, missing models, or crashes. Lighthouse does not include runtime ROM detection; the VERSION constant is fixed at compile time. You must use the binary that matches your specific ROM dump.

Does the JP version require special handling beyond asset remapping?

Yes. The JP release contains unique code paths for certain UI elements and text rendering, implemented through JP_EXCLUSIVE blocks in src/SM/version_compat.c. These accommodate Japanese-specific features like additional font glyphs and modified menu layouts.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →