How the Lighthouse Decompilation Project Is Structured: A Deep Dive into the Banjo-Kazooie Source Code
The Lighthouse repository follows a layered architecture with CMake-driven builds, global headers in include/, low-level N64 emulation via libultralib, game logic in src/, and YAML-based asset pipelines.
The Lighthouse decompilation project is a community effort to recreate Banjo-Kazooie as clean, buildable C code that runs on modern platforms. Understanding how the Lighthouse decompilation project structure works is essential for contributors aiming to extend the engine or debug gameplay systems. This guide walks through each architectural layer, from build scripts to game logic, with concrete examples from the source tree.
Root Directory and CMake Build System
The repository root contains the orchestration layer. The top-level CMakeLists.txt defines the entire build process: it pulls in the libultraship submodule, sets platform-specific compiler flags, and registers custom CMake targets.
Two targets are critical for first-time setup:
ExtractAssets— runs the Torch-based extractor to producebk.o2rfrom a legal ROMGeneratePortO2R— packages port-specific textures and shaders intolighthouse.o2r
These targets are invoked automatically via the commands documented in docs/BUILDING.md. The root also houses CI configuration (.github/workflows/), documentation (docs/), and the README.md with quick-start instructions.
Global Headers in include/
The include/ directory is the single source of truth for data structures that mirror the original N64 binary layout. Every source file includes these headers, making them the backbone of the decompilation's memory model.
Key files:
structs.h— definesObject,Level,Player, and other core structs passed throughout the enginevariables.h— holds global state such asgCurrentLevelandgPlayerHealth
Because these headers are universally included, changes here propagate across the entire codebase. This design matches the original game's reliance on fixed-memory layouts for its actors and world state.
Low-Level N64 Emulation via lib/ultralib/
Rather than shipping Nintendo's original proprietary SDK, the project depends on libultraship, a community-maintained submodule hosted in lib/ultralib/. This library provides:
- RSP/CPU primitives — vector mathematics, DMA transfers, and inter-processor messaging
- Audio synthesis — sample playback, reverb, and sequencer logic
- Utility macros — assertions via
ultra_assert.hand platform-agnostic wrappers
Higher-level game code calls into this layer exactly as the original binary did, preserving timing-sensitive behavior. The ultra_assert.h header, for example, is referenced across the codebase for runtime consistency checks.
Game Logic in src/
The src/ directory contains the decompiled game implementation, organized to reflect the original module boundaries.
The SM/ Subdirectory
Despite its name, SM/ holds the core Banjo-Kazooie gameplay systems. Key files include:
quarrie.c— level initialization and player spawn logicanim_callbacks.c— animation state machine callbacksfurniture.c— interactive object handlers
These files implement character movement, collision response, and object interaction. They rely on the global structs from include/ and call into libultralib for hardware-abstraction needs.
Cut-Scene System in cutscenes/
Each scripted sequence lives in its own module:
cutscene_trigger.c— detects conditions and launches sequencessparkle.c,glow_sparkle.c— individual cut-scene implementationscutscene_animsequence.c— drives camera and animation from extracted assets
The cut-scene manager registers callbacks that pause normal gameplay, play the sequence, then restore control.
Development and Reference Subdirectories
emptyLvl/— a minimal test level for rapid iteration without loading the full game worldunused/— legacy code retained for reference; excluded from the final binary
Example: Level Initialization from quarrie.c
This pattern appears throughout src/SM/ for every playable area:
#include "include/structs.h"
#include "include/variables.h"
/* Called once ROM assets are unpacked */
void init_quarrie(void) {
/* Set player start position */
gPlayer->position = (Vec3){ .x = 100.0f, .y = 0.0f, .z = -250.0f };
/* Stream level geometry via DMA-style loader */
load_level_geometry("quarrie_lvl.bin");
/* Register per-frame update hook */
register_update_callback(quarrie_update);
}
The function composes global state manipulation (gPlayer), asset loading (load_level_geometry), and engine registration (register_update_callback) — the three pillars of level initialization in this architecture.
Example: Cut-Scene Trigger from cutscene_trigger.c
#include "include/structs.h"
/* Invoked on player-object collision */
void trigger_cutscene(Object *obj) {
if (obj->state == OBJ_STATE_ACTIVE) {
/* Queue cut-scene by ID; manager handles the rest */
start_cutscene(CUTSCENE_SPARKLE);
}
}
The trigger inspects object state, then delegates to the centralized cut-scene system. This separation keeps gameplay code focused on conditions while the cutscenes/ modules handle presentation.
Asset Pipelines in assets/ and docs/
Runtime content is defined declaratively in YAML files under assets/yaml/us/rev1/. The build tooling (documented as "retro" in the README) consumes these definitions to generate .o2r and .otr archives.
soundfont.yaml— describes instrument banks and sample mappings- Texture and model definitions — specify source files and compression settings
The docs/BUILDING.md file contains the exact command sequences for generating these archives, which the engine loads at startup. This pipeline decouples asset authoring from engine compilation, enabling iterative content work without full rebuilds.
Continuous Integration and Distribution
GitHub Actions workflows in .github/workflows/ automate three stages on every commit:
- Asset extraction — runs
ExtractAssetson a runner configured with the extractor (ROM not included in repository) - Multi-platform builds — compiles for Windows (MSVC), macOS (Xcode), and Linux (GCC/Clang) via CMake generators
- Packaging — produces zip archives, AppImages, and DMG bundles for end-user testing
This pipeline ensures reproducible binaries and catches build regressions before they reach contributors.
Summary
- CMake-driven builds —
CMakeLists.txtorchestrates extraction, compilation, and packaging through documented targets - Centralized headers —
include/structs.handinclude/variables.hprovide the memory model shared across all modules - Hardware abstraction —
lib/ultralib/(libultraship) implements N64-specific primitives without proprietary code - Modular game logic —
src/SM/for gameplay,src/cutscenes/for scripted sequences, with clear separation of concerns - Declarative assets — YAML definitions in
assets/feed the build pipeline for runtime archives - Automated quality assurance — CI workflows validate every commit across platforms and package for distribution
Frequently Asked Questions
What programming language is the Lighthouse decompilation written in?
The project is written in C, matching the original Banjo-Kazooie development approach. Headers use standard C syntax with preprocessor macros for platform adaptability. The build system is CMake, and tooling scripts use Python for asset extraction.
How does Lighthouse handle original Nintendo 64 hardware calls?
All low-level hardware access routes through libultraship, a community replacement for Nintendo's proprietary ultralib. This submodule implements RSP vector operations, audio synthesis, and memory management without including copyrighted code. Game logic in src/ calls these APIs exactly as the original binary did.
Where is the actual game code located in the repository?
The decompiled gameplay implementation lives in src/, with subdirectories organized by function: src/SM/ for core mechanics, src/cutscenes/ for scripted sequences, src/emptyLvl/ for debugging, and src/unused/ for reference material. Each .c file typically corresponds to a specific system or level from the original game.
What files do I need to edit to add a new level?
Start with three locations: create level initialization code in src/SM/ (following the quarrie.c pattern), add any required struct definitions to include/structs.h, and define assets in assets/yaml/us/rev1/ if the level needs new textures or audio. Update CMakeLists.txt if you introduce new source files that require separate compilation units.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →