Skip to content
PORTSNATIVE.
HomeCatalog
Web GamesEmulatorsToolsNews
PORTSNATIVE.

A catalog for discovering ports and related projects. We link to official sources and do not host original game files.

ExploreCatalogMy list
InformationAbout usSuggest a portSupportDonationsLegal notice
© 2026 PortsNative
Back to catalog
Oracle of Ages & Oracle of Seasons - Static Recompilation
PORTSNATIVE

Oracle of Ages & Oracle of Seasons - Static Recompilation

NATIVE PORTACTION / ADVENTUREWINDOWSLINUXMACOSALPHA

Oracle of Ages & Oracle of Seasons - Static Recompilation

The Legend of Zelda: Oracle of Ages / Oracle of Seasons

OriginGame Boy / Game Boy Color

TechnologyStatic recompilation (native C/C++)

Available forWindows, Linux, macOS

View official project

About the project

The Legend of Zelda es una serie de videojuegos de acción-aventura creada por los diseñadores japoneses Shigeru Miyamoto y Takashi Tezuka, y desarrollada por Nintendo, empresa que también se encarga de su distribución internacional. Su trama por lo general describe las heroicas aventuras del joven guerrero Link, que debe enfrentarse a peligros y resolver acertijos para ayudar a la Princesa Zelda a derrotar a Ganondorf y salvar su hogar, el reino de Hyrule.

2 versions of The Legend of Zelda: Oracle of Ages / Oracle of Seasons

You're viewing: Oracle of Ages & Oracle of Seasons - Static Recompilation

Oracle of Ages & Oracle of Seasons - Static Recompilationgithub.com/sp00nznet/oracle-recompiled
Windows
Linux
macOS
Current
The Legend of Zelda: Oracle of Ages / Oracle of Seasonsgithub.com/ZakyPew/epoch-equinox
Windows
›

Official project information

Author's original READMEOpen on GitHub ↗

Oracle of Ages & Oracle of Seasons — Static Recompilation

Two sister games. One shared runtime. Zero emulators.

Static recompilations of The Legend of Zelda: Oracle of Ages and The Legend of Zelda: Oracle of Seasons (Game Boy Color, 2001) — the entire ROM lifted into native C code that runs directly on your CPU. No interpreter loop, no JIT, no per-instruction fetch/decode at runtime. Just the game, recompiled, as if Capcom had shipped it for Windows in 2001 instead of for a 4 MHz handheld.

The Pitch

Released the same day, the Oracle games are arguably Capcom Flagship's most ambitious Game Boy work — two parallel adventures (Ages = time-travel puzzles, Seasons = season-shifting action) connected by a passwords system that lets a finished cartridge unlock a linked sequel on the other. Same engine, different worlds. So they get the same treatment here: one monorepo, two binaries, one shared runtime.

Screenshots

Oracle of AgesOracle of Seasons
Ages gameplaySeasons gameplay
overworld — the forest of Labrynnaoverworld — Holodrum, near Horon Village
Ages introSeasons intro
intro cinematic — the castle at sunsetintro cinematic — Link rides out
Ages boot logoSeasons boot logo
boot — Capcom Flagship logoboot — Capcom Flagship logo

All 160×144 framebuffer dumps from the recompiled binaries (--dump-frames), converted to PNG. Both games now reach interactive overworld gameplay — boot, intro cinematic, title, file select, name entry, opening cutscene, and Link walking around with a live HUD (hearts, rupees, item slots).

These are taken straight from the PPU framebuffer. In the live SDL window the ImGui menu bar now sits in its own strip above an unobstructed game — the window is grown by the bar's height and the game is blitted below it, so the top HUD row (hearts / rupees / items) is fully visible. (Earlier the bar overlaid the stretched game and clipped that row; fixed upstream in gb-recompiled — the game image fills its strip, the bar gets its own.)

Status

StageAgesSeasons
ROM acquired (your own copy, gitignored)✅✅
Project scaffolded✅✅
Recompiler bootstrapped (gbrecomp)✅✅
Recompiled to C (~3M lines each)✅✅
First successful native build✅✅
SDL window opens, framebuffer renders✅✅
Cartridge header recognized✅✅
Boots past publisher logos✅✅
Plays opening cinematic✅✅
Title → file select → name entry✅✅
Reaches overworld gameplay✅✅
Link responds to the controller✅✅
Writes save data to SRAM✅✅
Reaches first dungeon⏳⏳
Load existing save (Continue)⏳⏳
Linked-game passwords verified⏳⏳
Audio sounds correct (4ch, 44.1 kHz)🛠️🛠️
Full playthrough⏳⏳

⚙️ Update — both games are playable into the overworld! gbrecomp translated both ROMs to native C on the first try (~86–89 MB of generated .c each, ~13.5k functions, ~92k basic blocks). After three CMakeLists tweaks (path, missing runtime sources, LA_HAS_IMGUI) and one real runtime bug fix (serial-port emulation — see "Solved" below), oracle_of_ages.exe (21.7 MB) and oracle_of_seasons.exe (22.0 MB) now go all the way from the Nintendo/Capcom logos through the opening cinematic, title screen, file select, name entry, and the new-game intro into live overworld gameplay — Link walking around Labrynna / Holodrum with a working HUD (hearts, rupees, item slots), responding to the D-pad, and the game writing its save to SRAM. Driven headlessly via the --input script flag (frame:buttons:duration) and captured with --dump-frames.

Solved: the boot-logo stall

The first real bug, and a fun one to track down. Both games booted, rendered the Capcom Flagship logo from a genuine framebuffer, ran every interrupt handler correctly… and then froze on the logo forever. Here's the trail:

  1. The logo→title transition is gated by a countdown. A 16-bit timer in WRAM ($cbb3) is decremented once per frame; when it hits zero the boot state machine advances ($c2e7: 1→2) and sets flag $ffb9, kicking off the intro. In a reference emulator (PyBoy) the timer initializes to 0x00D0 and cleanly counts to zero around frame 300.

  2. Our timer never reached zero. Instrumenting the runtime showed the timer's high byte was 0x09 instead of 0x00, so it would have taken ~2500 frames — and it got reloaded before then anyway. The init code at 3:4d56 correctly wrote 0x00D0, but a single instruction at 3:4cd3 (LD ($cbb4),$09) kept clobbering the high byte right back.

  3. That clobber is gated by $ffba. The game only runs the clobber when $ffba != 0. In PyBoy $ffba stays 0; in our build it was non-zero.

  4. $ffba is the serial link-cable handshake byte. The boot code probes the Game Link port: it sends 0xe1/0xe0 and, if it reads the same bytes back, concludes a second Game Boy is connected and enters link mode — which is what corrupted the logo timer.

  5. The runtime's serial emulation was the culprit. On a write to FF02 with the transfer-start bit set, the fallback handler raised the serial interrupt but never updated FF01 — so the game read back the very byte it had just sent and mistook the open bus for a real peer. On actual hardware with no link cable, an internal-clock transfer clocks in 0xFF from the floating data line.

The fix (gbrecomp/runtime/src/gbrt.c, upstreamed to gb-recompiled). The runtime's serial fallback now mirrors a disconnected link cable: an internal-clock (master) transfer clocks in 0xFF, raises the IRQ, and clears the start bit; an external-clock (slave) transfer is left pending — with no peer to supply the clock it never completes on hardware. That second half matters: an interim version auto-completed slave transfers too, which (against the runtime's newer serial_exchange path) flooded the games with spurious serial interrupts and drove their stack down into VRAM — trading the clean logo freeze for an outright crash. Leaving slave transfers pending fixes both failure modes, and both Oracles sail past the logo into their intros.

🔎 How it was found: --trace-entries first pointed at the boot wait-loop; targeted fprintf probes in the runtime exposed the $cbb3 counter's bad high byte; a PyBoy reference script (pyboy.memory[...] per frame) pinned the exact divergence — $cbb3, then $ffba, then the serial register.

Known Issues / What's next

  • Audio not yet validated by ear. The 4-channel APU is wired up and the 44.1 kHz stream runs, but nobody's confirmed the music/SFX actually sound correct (right notes, no glitches). Needs a listening pass.
  • Load-existing-save (Continue) untested. New-game works and saves to SRAM; loading a save back through the file-select "Continue" path hasn't been exercised yet.
  • Deep gameplay unproven. Verified up to walking the opening overworld. Dungeons, items, screen transitions, and a longer playthrough are next.
  • Linked-game password system. The headline feature of the pair — still to be verified end-to-end across the two builds.
  • RAM-resident code runs via the interpreter fallback. Both games copy a small bank-switch trampoline into WRAM and execute it there; the recompiler can't see RAM at build time, so those few instructions correctly fall back to the interpreter. Working as intended, noted here so the [INTERP] WRAM exec log lines aren't mistaken for a bug.

Updated as the work progresses — this section is the source of truth.

How It Works

This repo holds two thin game-specific wrappers (one CMakeLists.txt per game) that share a single recompiler runtime via the gbrecomp/ submodule. The recompiler emits oracle_of_<game>.{c,h}, _rom.c (ROM bytes as a C array), and _main.c (entrypoint) into each game's directory.

                        your own ROMs (never committed)
                   roms/oracle-of-ages.gbc      roms/oracle-of-seasons.gbc
                              │                              │
                              ▼                              ▼
              ┌──────────────────────────────────────────────────────┐
              │   gbrecomp/recompiler/    (sp00nznet/gb-recompiled)  │
              │   SM83 → C translation, ROM-as-array, headers        │
              └──────────────────────────────────────────────────────┘
                              │                              │
         ages/oracle_of_ages.{c,h,_main.c,_rom.c}   seasons/oracle_of_seasons.{c,h,_main.c,_rom.c}
                              │                              │
                              ▼                              ▼
              ┌────────────────────────┐    ┌────────────────────────┐
              │   ages/CMakeLists.txt  │    │ seasons/CMakeLists.txt │
              │   + shared gbrt        │    │   + shared gbrt        │
              └────────────────────────┘    └────────────────────────┘
                              │                              │
                              ▼                              ▼
                       oracle_of_ages.exe              oracle_of_seasons.exe

The heavy lifting (SM83 CPU model, scanline PPU with full CGB color, MBC5 mapper, APU, SDL2 platform layer, ImGui debug overlay) all lives in sp00nznet/gb-recompiled — the same runtime that powers the Pokémon Gold / Crystal and Link's Awakening DX recomps.

Optional — nicer function names. By default the recompiler names functions func_<bank>_<addr>. If you analyse a ROM in Ghidra (with the GhidraBoy SM83 extension), gbrecomp/tools/ghidra/ will run a headless analysis, export the function/symbol names, and feed them back so the generated C reads e.g. AdvanceLogoState instead of func_03_4d23:

export GHIDRA_INSTALL_DIR=/path/to/ghidra
python gbrecomp/tools/ghidra/run_ghidra.py roms/oracle-of-ages.gbc -o ghidra_out
python gbrecomp/tools/ghidra/build_names.py ghidra_out -o ages/names.sym
./gbrecomp/build/bin/gbrecomp.exe roms/oracle-of-ages.gbc -o ages/ --names ages/names.sym

See gbrecomp/tools/ghidra/README.md for details.

Building

Prerequisites

  • Windows 10/11
  • MSYS2 with MinGW64: pacman -S mingw-w64-x86_64-toolchain mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-SDL2
  • Legally-obtained Oracle of Ages and/or Oracle of Seasons ROMs (.gbc)

One-time setup

git clone --recurse-submodules https://github.com/sp00nznet/oracle-recompiled.git
cd oracle-recompiled

If you forgot --recurse-submodules:

git submodule update --init --recursive

Generate the C output for each game

See the gb-recompiled README for the exact recompiler invocation. Drop your ROMs into roms/ and point the tool at them:

# First, build the recompiler tool itself (only needed once)
cd gbrecomp && mkdir -p build && cd build && cmake -G Ninja .. && ninja && cd ../..

# Oracle of Ages
./gbrecomp/build/bin/gbrecomp.exe roms/oracle-of-ages.gbc -o ages/

# Oracle of Seasons
./gbrecomp/build/bin/gbrecomp.exe roms/oracle-of-seasons.gbc -o seasons/

This produces, per game, oracle_of_<game>.c (~85 MB), oracle_of_<game>_rom.c (~6 MB ROM bytes as a C array), oracle_of_<game>.h, oracle_of_<game>_main.c, and a fresh CMakeLists.txt. All five are gitignored — only the hand-curated CMakeLists.txt patch (one line, fixes the runtime path for our monorepo layout) is committed via a tracked override in this README's "Layout caveat" below.

Layout caveat. The recompiler auto-generates a CMakeLists.txt per game that's correct in shape but needs two tweaks for this layout, both already applied in the committed CMakeLists files (so a regeneration from a clean ROM checkout will overwrite them — re-apply if you do):

  1. Runtime path — auto-gen assumes the output sits at gbrecomp/<game>/, so GBRT_DIR points to ../../runtime. We need ../gbrecomp/runtime:
    sed -i 's|../../runtime|../gbrecomp/runtime|' \
      ages/CMakeLists.txt seasons/CMakeLists.txt
    
  2. Missing runtime sources — platform_sdl.cpp in the runtime calls into menu_gui_*, asset_viewer_*, and hwtrace_*, but auto-gen only adds gbrt.c, ppu.c, audio.c, interpreter.c, and platform_sdl.cpp. Without the other three you get undefined-symbol link errors. Add to the add_library(gbrt STATIC ...) block:
    ${GBRT_DIR}/src/hwtrace.c
    ${GBRT_DIR}/src/menu_gui.cpp
    ${GBRT_DIR}/src/asset_viewer.cpp
    
    and add ${GBRT_DIR}/src to target_include_directories.

Build

PATH="/c/msys64/mingw64/bin:$PATH"

# Ages
cmake -S ages -B ages/build -G Ninja
cmake --build ages/build           # ~3M lines of C → expect a long compile
./ages/build/oracle_of_ages.exe

# Seasons
cmake -S seasons -B seasons/build -G Ninja
cmake --build seasons/build
./seasons/build/oracle_of_seasons.exe

Expected ROM Hashes

These are the (U) [C][!] revisions — the ones the recompiler is being targeted against.

GameSHA-1MD5
Oracle of Ages880374fb978b18af4aa529e2e32f7ffb4d7dd2f4c4639cc61c049e5a085526bb6cac03bb
Oracle of Seasonsba1268290fb2b1b70505d2d7b5825fc8a4816a4bf2dc6c4e093e4f8c6cbea80e8dbd62cb

Both are 1,048,576 bytes (1 MB), MBC5+RAM+BATTERY, CGB-only.

Repository Layout

.
├── README.md                  # this file
├── .gitmodules                # gbrecomp submodule pointer
├── .gitignore                 # blocks ROMs and generated rom.c artifacts
├── gbrecomp/                  # shared submodule → sp00nznet/gb-recompiled
├── roms/                      # gitignored — drop your own ROMs here
├── ages/
│   ├── CMakeLists.txt              # regenerated by gbrecomp; one-line path fix needed
│   ├── oracle_of_ages.h            # generated, gitignored
│   ├── oracle_of_ages.c            # generated, gitignored (~86 MB)
│   ├── oracle_of_ages_main.c       # generated, gitignored — entrypoint
│   └── oracle_of_ages_rom.c        # generated, gitignored (~6 MB ROM-as-C-array)
├── seasons/                        # same shape as ages/, with oracle_of_seasons_*
│   └── …
└── screenshots/

A Note on the Linked-Game Passwords

The Oracles can talk to each other. A finished Seasons cart hands the player a password that, typed into a fresh Ages save, opens a continuation story — and vice versa. Two binaries, one save world. Verifying that handshake works end-to-end across the recompiled builds is a milestone in the status table above and possibly the most fun this project will produce.

Legal

This repository ships no ROM, no game code, and no game assets — nothing you could rebuild the game from:

  • No ROM bytes, no decompiled sources, no extracted audio, no ripped sprite or tile sheets.
  • The recompiler's output (rom.c, rom_rom.c, rom.h) is gitignored and must be regenerated locally from your own legally obtained ROM.
  • Everything checked in is original glue code (MIT) or a pointer to a public upstream (the gbrecomp submodule).

The only Nintendo/Capcom-owned content here is a handful of small screenshots in screenshots/ — captured framebuffer frames used to show progress. They remain © Nintendo / Capcom, are included for identification and commentary (fair use), and are not game assets you could rebuild the game from.

This is a personal preservation and education project. Don't redistribute ROMs.

Credits & Acknowledgements

  • Games — Nintendo / Capcom Flagship (Oracle of Ages & Oracle of Seasons, 2001)
  • Recompiler — arcanite24/gb-recompiled with CGB fixes, runtime extensions, and SDL2 frontend in sp00nznet/gb-recompiled
  • Reference emulators — two, with different jobs: PyBoy is the scriptable oracle used for debugging here (its tools/ ground-truth tracer feeds the recompiler, and pyboy.memory[addr] per-frame probing pinned the serial bug — see "Solved" above); SameBoy by LIJI32 is the accuracy reference that the runtime's --hw-trace mode diffs against scanline-by-scanline.
  • Frontend — Dear ImGui + SDL2
  • Disassembly references (for triage only, not build inputs) — oracles-disasm by stewmath et al.
  • Prior art in the sp00nznet recomp family — pokemon-gold · pokemon-crystal · LinksAwakening
View official project
CALIFICACIÓN
—
Loading votes…
Advertisement

PROJECT FILE

Project type
NATIVE PORT
Genre
Action / Adventure
Status
alpha
Original platform
Game Boy / Game Boy Color
Technology
Static recompilation (native C/C++)
Version
—
Updated
—

Target platforms 3

Windows
Linux
macOS
View on GitHub
Advertisement