shantaerecomp
shantaerecomp is a community PC port of Shantae in the Quiver Launcher catalog.
- Type: PC port
- Game: Shantae
- Runs on Windows and Linux
- By vibecodekun
- Latest release v0.2.0, 2026-10-22
- Source: https://github.com/vibecodekun/shantaerecomp
README
Shantae (GBC) static recompilation
Shantae (USA) recompiled to native code with gbrecompiled. Boots as a Game Boy Advance by default, so the GBA Enhanced extras are on (title badge, the Bandit Town Tinkerbat secret), while the palette loader is kept on the original GBC colors. On top of that: an expanded world view (an aspect ratio of your choice, or Adaptive to fill the whole screen), no slowdown, reduced input lag, smoother movement (running, whipping and crawling without losing momentum), a button for each transformation with a HUD of the Dance Menu's icons, a map screen, a day and night clock, rewind, and RetroArch shader presets through librashader.
Download
Builds for Windows (x64, x86, ARM64) and Linux (x86_64, aarch64; a tarball or an AppImage) are on
the Releases page. Extract one
and run shantae.exe / shantae; the launcher asks for the ROM on the first start.
No ROM is included. You need your own Shantae (USA) Game Boy Color ROM, the No-Intro dump:
SHA-256 1b92e22d5510c51bab97d23074e4aad7464d93eb15f7596ef7da0a5efa27a19d (CRC32 E994B59B).
The launcher reads "ROM verified" under the box art and enables PLAY only for that file. Any other
file reads "ROM not recognized" and does not start: bad dumps, other games, and Shantae's other
releases (World, Switch, Limited Run Games), which are different ROMs and not supported yet. The
code was recompiled from that exact ROM. Translations and other patches for it are applied by the
launcher (ROM patches); a ROM file that already has one of the patches the
build was made with applied is verified too.
Layout
| Path | What |
|---|---|
gbrecompiled/ | Recompiler and runtime (submodule: fork of mstan/gbrecompiled, branch shantae) |
recomp-ui/ | Launcher and in-game menu (submodule: fork of RetroPortingToolKit/recomp-ui, branch shantae) |
shantae.toml | Recompiler config: far-call / inline-argument routines, scan banks, palette override site |
shantae.annotations | Generated candidate entry points (automatically refreshed by tools/build.sh) |
dispatch_misses.toml | Runtime-harvested entry points (Tier-0), auto-ingested by the recompiler |
extras.c | Shantae game hooks: hardware mode (GBA Enhanced on/off), palette override, shantae.ini settings |
expanded_view.c, expanded_background.inc | Expanded world compositor, live background objects, and wider object activation |
object_slots.c | With the expanded view, the object table grown from 32 slots to 157 (past DFFF and at A000 in bank 3) and the collision node pool from 12 to one per slot (towns keep the original 32 and 12) |
ram_native.c | Native translations of writable JP vectors and the copied DMA routine |
moveset.c | "Smoother movement": the whip slide and cancel, the run and air speed from B, the whip and the dance as she lands, and the faster crawl, as hooks in Shantae's movement routines (docs/moveset.md) |
forms.c | "Smoother movement" for the transformations: the monkey's and tinkerbat's attack slide, run and air speed, faster climbs, the harpy's speed and flaps through her talons, the charges, the elephant's turn, landing and stomp, the spider's run and shot, webs taken with A, the tinkerbat's box (docs/moveset.md) |
dance.c | "Easier dancing": the dance's steps entered like a code, each with its pose, and the blink once a transformation's silhouette ends and after turning back (docs/dance.md) |
transform.c | "Transformation buttons": a button for each form and the heal, switching her in the tick she is in control, where the form fits |
hud.c | The buttons' HUD (the Dance Menu's icons, decoded from the ROM) and the day and night clock (the status bar's numerals) |
map.c | The map screen: the world laid out as on vgmaps' World Map, each area's terrain from its collision, markers from the spawn records, explored cells kept with each save file, and the Zombie Caravan at its stop |
launcher_options.c | Built-in GBA, expanded-view and gameplay features on the launcher's Mods page |
extras_ui.cpp | In-game Shantae settings, including expanded-view size and the movement options |
game_build.cmake | Adds game hooks, UI, and regression targets to the generated project |
generated/ | Recompiler output (not committed) — never edit; regenerate |
roms/shantae.gbc | Stock ROM (CRC32 E994B59B): you supply it; never committed |
patches/ | ROM patches made for Shantae that the build compiles (translations, fixes): you supply them too; patches/README.md lists them, patches/patches.toml names them for players |
tools/build.sh | Rebuild everything (below) |
tools/gen_annotations.py, tools/inline_args.py | Derive shantae.annotations and the [[inline_call]] entries from the ROM |
tools/audit_whole_rom.py, tools/whole_rom_check.c | Exhaustive bank/address audit and native-instruction differential checks |
tools/audit_ram_coverage.py, tools/ram_native_check.c | ROM writer evidence, RAM disassembly, and direct RAM program checks |
tools/audit_native_coverage.py | Check discovered native script targets and banked inline returns against emitted metadata |
tools/test_annotations.py, tools/native_dispatch_check.c | Discovery regressions and compiled-dispatch differential checks |
tools/expanded_view_check.c, tools/object_slots_check.c | Expanded-view compositor checks; the grown object table through the game's own routines |
tools/check_towns.py | Every town with the expanded view against the original, frame for frame, from a debug-grid state |
tools/check_totem.py | The labyrinth's totem puzzle with the expanded view: the orb, then the key, from a saved state |
tools/check_budgets.py | Spawners that share a count of what they have made, with the expanded view against the original, from a cold boot |
tools/check_crow.py | Sky's crow at the desert labyrinth with the expanded view: its dialogue opens the door, and it is there however Shantae comes back, from a saved state |
tools/check_eyes.py | The third labyrinth's eye puzzle with the expanded view: the eye settles where its jar was, the socket takes it, the statue gives its key, and another puzzle in view is left alone, from a saved state |
tools/check_pictures.py | The fourth labyrinth's picture puzzles and key doors with the expanded view against the original, pixel for pixel: each picture and door shows its own state, the puzzle is solved and its key taken, from a saved state |
tools/make_states.py | The saved states the next three checks play, made from a cold boot with WayForward's debug mode (the title code, Start Debug Game with every dance learned, a scene of the debug grid, the debug flight) into logs/states/ |
tools/check_ui_fit.py | The world above the bottom UI in every scene of the debug grid, in a hidden window and three setups: the floor above the bottom UI, a dialogue box's rows of play kept above it |
tools/check_moveset.py | Shantae's moves with "Smoother movement" beside the original, frame for frame: the whip slide and cancel, air speed, a whip that lands or slides off a ledge, B and Select as she lands from a standing jump, the crawl, the crouch whip under a low ceiling; and the feature off against the previous release, from a saved state |
tools/check_forms.py | The transformations with "Smoother movement" beside the original: the monkey's and tinkerbat's slide (from standing and walking), cancel and air speed, the harpy's talons in her run and while flapping, the tinkerbat's box in the ice tower's hidden passage and the pipe maze, the pillar, the charges, the elephant's turn, landing and stomp, climbing with B, the spider's run and shot, webs, and the spider's shot in the expanded view; Transformations off against the previous release, from four saved states |
tools/check_dance.py | "Easier dancing": every dance of the ROM's table on its last step, skipped and restarted steps, the original rhythm, a slider against the new monkey (the blink once its silhouette ends) and against Shantae turned back, with and without the blink, and every transformation's blink; the feature off against the previous release, from a saved state |
tools/check_transform.py | The transformation buttons: every form by its button as by its dance, walking and jumping, mid-jump, a one-tile corridor, shops and scenes, forms not learned, the heal, beside quick steps, and the HUD's layouts, from saved states |
tools/check_hud.py | The HUD and the clock in a hidden window with no preset, an LCD dot matrix, crt-geom and a koko-aio bezel; no HUD and no switching on a town's street |
tools/map_extract.py | The map's research side: every room load, map and spawn record in the ROM |
tools/trace_writes.c | Which instructions write a memory range, from a saved state (research only) |
tools/check_rom_gate.py | Only the ROM the build was recompiled from starts: the launcher's "ROM verified" line and PLAY, and the runtime's own check, for the ROM, bad dumps made from it, and optionally another game |
tools/check_patches.py | Every patch in patches/ on the stock ROM: every instruction position it changes against the reference interpreter (whole_rom_check patches), recognized with all of them compiled, the native code against the interpreter on the patched ROM from a cold boot and from the first town's shop, the debug-grid route, scene 3C's text and the shop pictured; with the dance floor fix, the dance floor at night as a player gets there and a round's end against the interpreter; the patched ROM file accepted; a code patch the build was not made with stops with its message, a data patch runs, a size-changing one is refused; the launcher's line for each (docs/rom-patches.md) |
tools/check_dance_floor.py | The dance floor on the stock game: from a cold boot, the Zombie Caravan's gates, the warp dance, Scuttle Town at night, the Dance Parlor, a round to its end; it must return to the owner, and the round's end must match the interpreter (The dance floor) |
tools/check_leave.py | Leaving from the in-game menus: the first confirmed Return to Launcher ends the game and opens the launcher, Quit ends it and opens nothing, from the Escape menu, the settings window and the debug server |
tools/build_ghidraboy.py, tools/ghidraboy-ghidra12.patch | Rebuild/install the GhidraBoy extension, ported to Ghidra 12 by the patch (instructions in the script) |
tools/ghidra_listing.py, tools/ghidra/Listing.java | Ghidra's disassembly of ROM ranges (6:4AA7:4B19) from the command line: headless, read-only, far-call aware; imports the ROM into logs/ghidra on first use, and with --patch the ROM with an IPS/BPS patch applied |
tools/build_librashader.sh | Build librashader.dll (OpenGL runtime; x64, or x86/arm64 when named) and stage the slang-shaders presets |
third_party/librashader/ | Its output: the x64 DLL, windows-<arch>/librashader.dll, shaders/, and patches/ applied to the librashader source; linux-<arch>/librashader.so from tools/build_linux.sh |
tools/build_windows.sh, tools/windows/ | Windows releases for x64, x86 and ARM64 (below): pinned msys2 libraries, CMake toolchain, DLL staging, packaging, checks |
third_party/windows/ | The pinned msys2 packages and the x86/ARM64 sysroots unpacked from them (tools/windows/bootstrap.sh) |
tools/build_linux.sh, tools/linux/ | Linux releases for x86_64 and aarch64 (below): toolchain bootstrap, CMake toolchain, packaging, checks |
LICENSE, THIRD-PARTY-LICENSES.md | This project's license (PolyForm Noncommercial 1.0.0) and the components the releases carry |
Build and run
The recompiler and the Windows build run on Windows with MSYS2
installed at C:\msys64, plus Git and Python 3.11+:
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-SDL2 mingw-w64-x86_64-angleproject
git clone --recursive https://github.com/vibecodekun/shantaerecomp.git
cd shantaerecomp
mkdir -p roms && cp /path/to/your/Shantae.gbc roms/shantae.gbc
bash tools/build.sh # recompiler -> generated/ -> generated/build/shantae.exe
Already cloned without --recursive? Run git submodule update --init.
The generated project builds for size (MinSizeRel, the recompiled ROM code at
-O1); game_build.cmake builds the runtime and this game's modules
(expanded_view.c, object_slots.c, moveset.c, forms.c, dance.c, dialogue.c,
transform.c, extras.c, ram_native.c) at -O2,
since the PPU, APU, timers and the expanded-view compositor run every frame.
The ROM code keeps its own level, so this does not recompile it.
The build also runs a 120-frame startup smoke test through the actual game loop.
Run it independently with python tools/check_startup.py; --rom, --exe, and
--frames select another ROM path, binary, or duration. It uses temporary settings
and saves, checks that the frame limit was reached, and fails on native execution
errors or interpreter fallback. Its log is logs/startup-smoke.log.
Only the ROM the build was recompiled from starts. The recompiler embeds its SHA-256, the
generated main() hands it to the launcher before the launcher opens (the line under the box
art and the PLAY button follow it), and the runtime checks the file again at boot, which is what
stops a wrong ROM when "Skip launcher on boot" is on (on Windows a "Wrong ROM" box, then the file
picker). python tools/check_rom_gate.py checks both with the ROM, a one-bit bad dump and an
underdump made from it; --other adds another game's ROM.
The menus' Return to Launcher and Quit end the game at the confirming press. The runtime notes
the request in a flag that gb_platform_poll_events() reads; it used to push an SDL_QUIT, which
the menu's hold caught and pushed back every frame without the main loop ever seeing it (SDL_PollEvent
stops at the poll sentinel queued before the push), so the game ran on, unpaced, behind the open
menu until a second leave. The relaunch registered by Return to Launcher also now checks the
last leave asked for, so a Quit after it no longer opens the launcher.
python tools/check_leave.py leaves through the Escape menu, the settings window and the debug
server, and fails on v0.1.9.
Run generated/build/shantae.exe. In the launcher, Mods holds Shantae's options:
-
GBA Enhanced mode (on by default): boot as a Game Boy Advance, so the title shows "GBA Enhanced!" and the Bandit Town Tinkerbat secret is available. Off = plain Game Boy Color.
-
Colors: Original GBC colors (default) or GBA brightened colors.
-
Remove slowdown (on by default): the original drops to half speed whenever a frame's logic does not finish before VBlank, and the wider expanded views, which keep more enemies active, do so often (state1 at 426×240 lagged 86 of 240 frames). With this on, the frame waits at the end of line 143 until the game has finished it: PPU, timers, audio and serial stand still while only the CPU runs, so music and screen timing are unchanged. Every frame loop ends at 00:0851, which sets FF8F for the VBlank handler, so FF8F still clear at line 143 means an unfinished frame. A wait is capped at two frames of CPU time; after hitting the cap (a screen load, not lag) it resumes only once a frame finishes unaided. The mechanism is
gb_frame_hold_hookin the runtime (gbrt.h). Off keeps the original slowdown. The Esc → Shantae checkbox applies it immediately. -
Reduce input lag (on by default): sprites appear one frame sooner, and Shantae's moves start on the tick they are pressed. With the runtime's Preemptive Frames at 1 (below), a press shows on the very next picture: hold A or B in frame advance and the jump or whip shows on advance 1 (the original game: 4 and 3). Two changes:
- Sprites. The VBlank handler streams each tick's sprite graphics (C4C0) to
VRAM with an HBlank DMA (00:0A7D), finished only by the end of the next picture,
so 01:667C points the OAM DMA page FF81 at the previous tick's buffer (D700/D800).
The scroll is held back to match: each tick starts by committing the camera the
previous tick drew its sprites with (00:26F6 → 03:722F). With this on, hooks on
the
[[imm_override]]sites 00:0A50 and 00:0A7D copy the buffer the tick just wrote (FFD9), upload its graphics with a general DMA, and move the copied sprites by the camera's last step (FFE1/FFE3) onto the committed scroll, the camera the expanded view's surround uses. The committed scroll's camera is the scroll less the background offset (C9D2/C9D4) it was built with, noted at 03:730E after both camera routines' build: bosses move the offset later in the tick (Risky's ship bobs it), and the value at the VBlank could leave the copy a pixel off. FF81 is restored after the copy, so game state is unchanged; the camera still follows a frame behind, as in the original. - Moves. Every loop runs each object's movement routine (00:0C45) and then
each object's script (00:1305). The player's idle routine turns a press into a
script branch, and the script picks the new movement routine (jump 06:5520,
walk, whip, ...), which then first ran in the next tick. When the player's
script (slot at CA13) has picked a new routine, hooks on 00:130B and 00:1384 and
the runtime's
gb_step_hookrun it once right after the script, with the registers and banks it interrupts saved on the game's stack (so a save state taken meanwhile is safe). The whole move then runs a tick earlier, on the same path.
The Esc → Shantae checkbox applies it immediately.
- Sprites. The VBlank handler streams each tick's sprite graphics (C4C0) to
VRAM with an HBlank DMA (00:0A7D), finished only by the end of the next picture,
so 01:667C points the OAM DMA page FF81 at the previous tick's buffer (D700/D800).
The scroll is held back to match: each tick starts by committing the camera the
previous tick drew its sprites with (00:26F6 → 03:722F). With this on, hooks on
the
-
Smoother movement (on by default): Shantae's base form keeps her momentum. In the original, B is both the whip and the run: a B press on the ground is always a whip, which stops her for its 24 frames, and the run only starts once B has been held for 15; a jump keeps the speed she left the ground with, so a standing jump is at walking speed whatever is held; a whip begun in the air and landed finishes on the spot; a B or Select pressed as she lands from a standing jump, or in the 24 frames after, is lost (her landing takes A, a direction or Down, but no B or Select); the crawl moves half a pixel a frame, standing up into a run when B is held; and in a corridor too low to stand in, B while crawling is lost (only a crouch held still whips there). With this on, holding B runs at once, B whips and Select starts a dance from the frame she lands, B whips while crawling under a low ceiling as anywhere else, and three options choose the rest (details and addresses in docs/moveset.md):
- Whip on the move: what B does while a direction is held, standing or crouched. Slide (default): the whip comes out and hits as usual while she keeps moving, at running speed with B held and walking speed without, turning with the D-pad; the run follows without a frame at rest, a whip that lands carries on along the ground, and one that slides off a ledge carries on in the air. Cancel: B with a direction is no whip at all, she runs (or crawls on) at once, and a whip in progress ends when B and a direction are held; stand still to whip. Original: she stops for the whole whip.
- Air speed: Hold B for running speed (default): two pixels a fra