# Lufia II Recompiled A native recompilation of **Lufia II: Rise of the Sinistrals** using [`snesrecomp`](https://github.com/mstan/snesrecomp). The game boots from the original US ROM and supports the intro, title screen, normal gameplay, battles, audio, input, saves and the desktop launcher. Video output can use SDL, OpenGL 3.3 or a native Vulkan renderer.

The Lufia II title screen rendered at 16:9
Title screen

The Mode 7 intro flyover rendered at 16:9
Mode 7 intro flyover

The Mode 7 overworld in Lufia II Recompiled
Overworld
A town in Lufia II Recompiled
Town
A building interior in Lufia II Recompiled
Interior
A shop in Lufia II Recompiled
Shop
A dungeon in Lufia II Recompiled
Dungeon
The Lufia II save selection screen
Save selection
## ROM The ROM is not included. Use the clean, headerless US release: ```text Size: 2621440 bytes SHA1: A89931C1F29B161B8BE717DFAB4A4ADB54B42B84 SHA256: 7C34ECB16C10F551120ED7B86CFBC947042F479B52EE74BB3C40E92FDD192B3A ``` Place it in the repository root as `lufia2.sfc`. ## Build ### Windows Requirements: - Windows 10 or later - Visual Studio 2022 with Desktop development with C++ - CMake 3.24 or later - Python 3 Build from the command line: ```powershell git submodule update --init --recursive cmake --preset windows-release cmake --build --preset windows-release ``` The executable is written to: ```text build\windows\Release\Lufia2Recomp.exe build\windows\Release\LUFIA2_FUNCTION_SOURCES.txt ``` `LUFIA2_FUNCTION_SOURCES.txt` lists the standalone-decomp replacements used by that build, decomp candidates that stayed on static recomp fallback, and consumer-owned HLE overrides. You can also open `lufia2.sln` and build the Release configuration in Visual Studio. ### Linux (including the Steam Deck) Requirements: CMake 3.24 or later, Python 3, GCC or Clang, and the SDL3 build dependencies. On Debian or Ubuntu: ```bash sudo apt install build-essential cmake ninja-build pkg-config python3 \ libgl1-mesa-dev libegl1-mesa-dev libx11-dev libxext-dev libxrandr-dev \ libxcursor-dev libxfixes-dev libxi-dev libxss-dev libxkbcommon-dev \ libwayland-dev wayland-protocols libdecor-0-dev libdrm-dev libgbm-dev \ libasound2-dev libpulse-dev libudev-dev libdbus-1-dev ``` ```bash git submodule update --init --recursive cmake -S . -B build/linux -G Ninja -DCMAKE_BUILD_TYPE=Release cmake --build build/linux --parallel ``` The executable is written to `build/linux/Lufia2Recomp`, with `libSDL3.so.0` staged beside it and an `$ORIGIN` runpath, so the directory can be copied to a Steam Deck as it is. SteamOS is immutable: build elsewhere and copy it over, keeping the target glibc no older than the build machine's. ### Build options Everything experimental or diagnostic is off by default, so a normal build is silent and carries no measurement code. The native patches themselves are production code and are always built; only their instrumentation switches: | Option | Adds | |---|---| | `LUFIA2_ENABLE_RUNTIME_LOG` | map, MSU, widescreen and periodic state tracing | | `LUFIA2_ENABLE_PATCH_REPORTING` | patch counters and scheduler time every 120 boundaries | | `LUFIA2_ENABLE_PERF_AUDIT` | the finite Vita performance audit | | `LUFIA2_ENABLE_DECOMP` | select verified semantic replacements at runtime; OFF keeps the generated/original game-function path | | `LUFIA2_DECOMP_ROOT` | override the required `Lufia2Decomp` checkout; otherwise use the pinned submodule | | `LUFIA2_DECOMP_REFERENCE_ONLY` | expose the decomp library without activating native replacements | Errors are always reported. `Lufia2Decomp` is a required build dependency in every configuration. The host links its shared resource-format decoder even when semantic replacements are disabled; `LUFIA2_ENABLE_DECOMP` controls game-function source selection only. ## Run Start `Lufia2Recomp.exe` to open the launcher, or pass a ROM directly: ```powershell .\build\windows\Release\Lufia2Recomp.exe "D:\ROMs\lufia2.sfc" ``` On Linux it is `./Lufia2Recomp` in the build directory, same arguments. Save data is stored in `saves\save.srm` next to the executable. ## Bug fixes Some bugs from the original game are fixed: - Buying a spell no longer costs extra gold after a big shopping trip. In the original, spending 65,536 gold or more on items could make the next spell cost 65,536 gold more than its price tag. - Equipment names no longer leave leftover letters behind. In the original, switching characters with L or R in the equipment or status screen could show part of the previous name, such as "Gladiussword". - The play time clock keeps counting after 99 hours instead of jumping back to 00:00, and the menus show three digits for the hours. It stops at 255:59, the most an original save file can hold. - Item drops after a battle are always announced. In the original, some items you already carried were added without the message saying so. - HP and MP stop at 999, the most the menus can show. In the original, level-ups could push maximum HP past that, and a character with more than 1,023 loaded a save with wrong HP and MP. - Dragon eggs found in the Ancient Cave are no longer lost. In the original, an egg could appear in a cave chest and vanished when you left the cave or were defeated there, so all eight eggs could never be collected again. - Being defeated in the Ancient Cave while standing on stairs restarts the cave as it should. In the original, the stairs still took you one floor deeper with your stats reset, which was almost always fatal. ## Repository - `recomp/` contains the control-flow information used during recompilation. - `src/` contains the game host and runtime code. - `lib/snesrecomp-platform/` is the shared renderer and host submodule. ## Renderer The launcher offers SDL accelerated, SDL software and OpenGL 3.3. OpenGL can also use GLSL shader presets. If it cannot start, the game falls back to SDL for that run. A build configured with `-DLUFIA2_ENABLE_VULKAN=ON` adds a fourth entry, `Vulkan`, and accepts `OutputMethod = Vulkan` in `config.ini`. It is a direct Vulkan renderer: SDL still owns the window, input, audio and the surface, and everything below the surface is Vulkan. It presents the ordinary framebuffer, the overlays and HD Mode 7, and it implements Clean-HD as native passes rather than by loading a `.glslp` preset, so an arbitrary `Shader =` entry stays an OpenGL-only feature. Vulkan is not the default, and a run that cannot start it steps down to OpenGL and then to SDL, reporting which renderer it actually got. Building it needs a Vulkan SDK for the headers, the loader and `glslc`. `HDMode7` in `config.ini` takes `Off` or a scale from `1x` to `8x`; the launcher's checkbox turns that configured scale on and off rather than replacing it. The scale only decides how finely the affine plane is sampled, never where -- it is separate from `HDMode7Filter`, which stays off so the pass remains pixel exact. A renderer reports which scales it can allocate, and a request above that is announced and reduced to the largest one it offers instead of being dropped or silently substituted. `PresentRate = Display` in `config.ini` presents on the display's clock instead of the guest's, so a 144 Hz monitor shows the picture 144 times a second while the game still advances 60 times. It does not interpolate motion yet, and it does not touch guest timing, audio or input. Mode 7 scenes stay at guest cadence because their frame cannot be shown again without rebuilding it. `PresentRateHz` overrides the detected refresh, which a remote desktop session reports incorrectly. FPS, volume, toast and rewind overlays are composited after the game and its shaders. The default Lufia skin uses the game's logical coordinate space: one UI texture pixel receives the same aspect, resize, fullscreen, and letterbox transform as one SNES pixel while remaining a separate overlay. `SNESRECOMP_UI_SCALE=0.5..3.0` applies an optional user multiplier (rounded to whole game pixels in this mode). The game-owned skin in `src/lufia2_overlay_ui.c` supplies a replaceable bitmap font, nine-slice panel texture, colours and spacing; the shared presenter remains game-neutral. On desktop, Mode 1 battle backgrounds sample their native picture and authored side margins in one coordinate space. Heat shimmer, mosaic, colour math and monochrome palette flashes therefore reach the margins. The battle HUD and original sprite viewport remain centred; a separate magic tilemap can extend across the wide screen. Individual sprite spells retain their original geometry. `LUFIA2_BATTLE_WIDE_EFFECTS=0` restores the previous margin compositor for comparison. An optional external panel override can be placed beside the executable as `assets/img/lufia2_menu_panel.tga`. Put `left top right bottom tile` source-pixel margins in the adjacent `lufia2_menu_panel.9slice`; `tile` makes patterned edges repeat like the original SNES tilemap instead of stretching. Missing or invalid TGA files fall through to the authentic panel reconstructed in memory from the verified ROM. The embedded neutral skin remains the final fallback if that defensive extraction fails. No reconstructed pixels are written to disk and no panel image ships with the project. `scripts/ui_asset_tool.py` crops PNG/BMP/TGA screenshots, applies integer nearest-neighbour downscaling, losslessly compacts tiled panels, writes the runtime TGA, and renders a tiled or stretched 9-slice preview. It requires Pillow (`python -m pip install Pillow`). Example for the native 120x88 panel: ```powershell python scripts/ui_asset_tool.py panel.png ` build/windows/Debug/assets/img/lufia2_menu_panel.tga ` --slice 16 16 16 16 --tile 16x16 --compact --preview 240x88 ``` The 48x48 result contains both 16-pixel corner regions and one 16x16 repeat cell. It recreates the original patterned edges without scaling them. Rewind preview cells are copied pixel-for-pixel from the rewind core. The Lufia skin never downsizes them again; the moving filmstrip shows three cells in a 256-pixel 4:3 frame and four in the standard widescreen frame. Only the surrounding tiled panel and labels are rebuilt for the available width. Single-line status badges such as the FPS counter use a compact form of the same panel: a four-pixel border around a tiled clean centre. This permits a 16-pixel logical height without shrinking the font or stretching the artwork. While rewind is visible, the FPS badge shares its exact right-hand edge. See [ISSUES.md](ISSUES.md) for the current limitations.