# Splatterhouse PC A native PC port of the 2010 Xbox 360 game *Splatterhouse*, made by **statically recompiling** the game's own PowerPC executable into C++ and running it on a reimplementation of the Xbox 360 platform. The game's own code does all the work. This repository supplies the console around it. **No game code or assets are included.** You build the port from your own retail disc image: the build script extracts it, decrypts the executable and recompiles it on your machine. Everything derived from the disc stays in gitignored directories and is never uploaded. - [The game](#the-game) - [Status](#status) - [Quick start](#quick-start) - [How it works](#how-it-works) - [Building in detail](#building-in-detail) - [Running](#running) - [Repository layout](#repository-layout) - [Credits and license](#credits-and-license) ## The game *Splatterhouse* (2010) is a reboot of Namco's 1988 arcade horror beat 'em up, released for Xbox 360 and PlayStation 3 by Namco Bandai Games. College student Rick Taylor is left dying in the mansion of Dr. Henry West. A Mayan blood mask, the Terror Mask, binds itself to him and turns him into a hulking brawler. He then fights through the mansion's horrors to rescue his girlfriend Jennifer. The game is a gory, heavy-metal third-person brawler with side-scrolling sections that nod to the original arcade game. Under the hood it is built on licensed middleware. Static analysis of the executable found: | Layer | Middleware | |---|---| | Engine, scene graph, rendering | Emergent **Gamebryo** 2.5 | | Physics | **Havok** 5.5 | | Menus and HUD (Flash / ActionScript 2) | **Scaleform GFx** 2.2 | | Audio engine | Audiokinetic **Wwise**, XMA-compressed audio | | Video | RAD **Bink** | | AI navigation | BabelFlux **NavPower** | The port runs all of it as the game shipped it: the Bink decoder, the Scaleform ActionScript VM and Wwise are the game's own recompiled code, not replacements. ## Status - Boots through the intro videos, the start screen, save-data setup and the main menu, with video and audio, all driven by the game's own code, and on into Story Mode. - Level 1 (the mansion) is playable with a gamepad or the keyboard. - Frame rate: the game paces itself at its own 30 fps cap; heavy combat can still dip below it. `SPLAT_MAX_FPS=60` is an opt-in unlock; some game systems are tied to the original pacing and misbehave at 60 fps. - Later levels are untested. - Linux only for now. On Windows the build runs up to the recompilation step; the runtime still uses Linux APIs. ## Quick start You need Linux on x86-64 with an AVX2 CPU (Haswell / Zen or newer), a Vulkan GPU, about 6 GB of free disk space, and an ISO dump of the retail disc. **1. Install the build tools** (Ubuntu / Debian; other distributions are listed [below](#build-without-nix)): ```sh sudo apt install clang cmake ninja-build git ca-certificates pkg-config python3 python3-venv \ libsdl2-dev libvulkan-dev libshaderc-dev libavcodec-dev libavutil-dev ``` **2. Clone and build:** ```sh git clone https://github.com/mattfj10/splatterhouse-PC.git cd splatterhouse-PC python3 tools/build_from_iso.py /path/to/Splatterhouse.iso ``` **3. Play:** ```sh dist/run.sh ``` The build takes a few minutes. It checks your tools first and prints the exact install command for anything missing. When it finishes, `dist/` is a self-contained, runnable copy of the game. ## How it works ### Emulation vs. static recompilation An emulator like [Xenia](https://github.com/xenia-project/xenia) reads the game's PowerPC machine code while the game runs and translates it on the fly, using an interpreter or a JIT. **Static recompilation** does that translation once, ahead of time: every function in the executable becomes a C++ function, and a normal C++ compiler turns them into a native x86-64 program. At run time no PowerPC code is decoded. What runs is ordinary compiled code that behaves instruction for instruction like the original. The recompiled code still expects to be on an Xbox 360. It calls the console's kernel, its GPU writes into GPU memory and it expects hardware interrupts. So the second half of the project is a **runtime**: a host implementation of those platform services. This is the same model [Unleashed Recompiled](https://github.com/hedge-dev/UnleashedRecomp) uses for *Sonic Unleashed*. ### The build pipeline ``` retail ISO │ verify-iso SHA-256 against the known retail dump ▼ XDVDFS disc image ──extract──▶ extracted/ game files (5.1 GB) │ default.xex encrypted, compressed executable ▼ decrypt (tools/xex_decrypt.py) │ XEX2 container → AES-decrypt + LZX-decompress → flat PowerPC image ▼ work/default.exe loaded at guest address 0x82000000 │ ▼ recompile (tools/recompile.py → XenonRecomp + recomp/*.toml + patches/) │ every guest function → C++; kernel imports → stub table ▼ work/xenon/ppc/*.cpp ~230 MB of generated C++ │ ▼ build (CMake + clang + ninja) │ generated game code + runtime/ → one native executable ▼ dist/splatterhouse + run.sh ``` - **XEX decryption.** Xbox 360 executables ship as XEX2 containers: a header plus an AES-encrypted, LZX-compressed PE image. `tools/xex_decrypt.py` unwraps it. It uses the publicly known retail key, the same one Xenia and other XEX tools use, and a Python port of libmspack's LZX decoder. - **Recompilation.** [XenonRecomp](https://github.com/hedge-dev/XenonRecomp) is cloned at a pinned commit and patched with `patches/xenonrecomp-s96-consolidated.patch`. Each patch fixes a code-generation bug this game exposed, for example update-form indexed loads and stores that emitted no code. `recomp/splatterhouse.toml` and `recomp/switch_table_overrides.toml` tell it what static analysis cannot work out alone: function boundaries and the targets of indirect `bctr` jump tables. - **No game code is committed.** Steps 2–4 run on your machine from your disc. Their outputs (`extracted/`, `work/`, `dist/`) are gitignored. ### The runtime The runtime (`runtime/`) is the "console" the recompiled game runs on. It follows one rule: **the game makes every decision.** The runtime answers the way the hardware and kernel would have. It never drives the game's menus, picks its assets or draws its UI on the game's behalf. | Subsystem | What the game expects | What the runtime does | |---|---|---| | **Memory** (`main.cpp`, `kernel/phys_layout.h`) | A 32-bit address space with the executable at `0x82000000`, and 512 MB of physical RAM visible through several aliased windows (cached, write-combined, physical) | Reserves 4 GiB of host address space and maps the image. Backs physical RAM with one `memfd` mapped at every alias, so a write through one window is visible through the others, as on the console. | | **CPU and threads** (`kernel/xboxkrnl_sync.cpp`) | Six hardware threads, `ExCreateThread`, kernel events, semaphores, mutants, critical sections, DPCs and timers | Each guest thread is a host thread. The kernel synchronisation objects follow the console's semantics, including thread affinity and the guest timebase (`mftb`, 50 MHz from the host clock). | | **Kernel and system** (`kernel/xboxkrnl_*.cpp`, `kernel/xam.cpp`) | `xboxkrnl.exe` and `xam.xex` imports: file I/O, memory allocation, clocks, the user profile, content and save devices, the system UI | High-level emulation (HLE) of each import the game uses. File I/O serves the extracted disc files; saves go to `work/xcontent/`. | | **GPU** (`kernel/xboxkrnl_video.cpp`, `gpu/`) | The Xenos GPU: a PM4 command ring, register writes, shader microcode, a 10 MB EDRAM render target with resolve copies, and vblank and command-completion interrupts | A command processor reads the game's own ring buffer and runs its draws on **Vulkan**. Xenos shader microcode is translated to GLSL (`gpu/xenos_shader.cpp`) and compiled to SPIR-V with shaderc. Textures are untiled and converted from Xenos formats. EDRAM resolves become render-to-texture copies. Vblank and completion interrupts are delivered through the callback the game registered (`VdSetGraphicsInterruptCallback`). | | **Audio** (`kernel/xma_decoder.cpp`, `kernel/xboxkrnl_audio.cpp`) | The XMA hardware decoder, driven through context structures and MMIO registers, and the audio driver's render callback | Models the XMA decoder hardware with libavcodec's XMA2 decoder. Wwise (game code) fills the contexts and mixes the result; the output goes to SDL audio. | | **Input** (`gpu/renderer.cpp`, `kernel/xam.cpp`) | `XamInputGetState` (the console's XInput) | SDL game controller and keyboard events, delivered through the XInput API. | | **Video** | Nothing special: Bink is game code | The game's own Bink decoder produces every video frame; the runtime only presents what the GPU draws. | ### Where Xenia (and others) come in [Xenia](https://github.com/xenia-project/xenia), the open-source Xbox 360 emulator, is the reference for how the console behaves. The runtime is not built on Xenia's code base, but much of it follows Xenia's documented semantics: kernel import behaviour, GPU register meanings, texture and vertex formats and their tiling, culling and depth-bias rules, the XMA context layout. Where an implementation follows Xenia closely, its BSD-3 notice applies (see `THIRD_PARTY_NOTICES.md`). - The shader decoder is adapted from **XenosRecomp**'s (the shader recompiler of Unleashed Recompiled). XenosRecomp builds on Xenia's microcode definitions. Here it emits GLSL for Vulkan instead of HLSL. - **ReXGlue SDK** (derived from Xenia) was studied as a reference for interrupt delivery in a recompilation runtime. - XenonRecomp itself comes from the Unleashed Recompiled authors. The difference from an emulator: Xenia must emulate everything generically for every game, while this port runs one game's precompiled code natively. Where something goes wrong, the fix goes into a faithful platform primitive, never into patching the game. ## Building in detail ### Requirements - Linux on x86-64 with an AVX2-capable CPU (Haswell / Zen or newer). - A Vulkan-capable GPU and driver (Mesa for AMD/Intel, or NVIDIA's driver). - The build tools from your distribution's packages (below), or, alternatively, [Nix](#build-with-nix). - Network access for the first build. The recompiler source is cloned from GitHub at a pinned commit. Two Python packages are installed from PyPI into a private environment if your Python does not already have them. - About 6 GB of free disk space on top of the ISO: 5.1 GB of extracted game files plus ~0.5 GB of generated C++ and build tree. The output directory hard-links the game files instead of copying them. - A dump of the retail disc as an ISO. The script checks it against the image the port is developed against: | | | |---|---| | Size | 7838695424 bytes | | SHA-256 | `303fa98211064e024fcc829be287270ec9e18f5586a71168e8493aea00ecb1c7` | | Executable | title ID `4E4D07F0`, media ID `6668E457`, XEX version 3, region-free | Other dumps or revisions stop with a warning. `--force` continues anyway, but the recompiler configuration hard-codes addresses of this exact executable. ### Build without Nix This is the standard path. 1. Install the build tools from your distribution: | Distribution | Command | |---|---| | Debian / Ubuntu | `sudo apt install clang cmake ninja-build git ca-certificates pkg-config python3 python3-venv libsdl2-dev libvulkan-dev libshaderc-dev libavcodec-dev libavutil-dev` | | Fedora | `sudo dnf install clang cmake ninja-build git pkgconf-pkg-config python3 SDL2-devel vulkan-loader-devel vulkan-headers libshaderc-devel ffmpeg-free-devel` | | Arch Linux | `sudo pacman -S --needed clang cmake ninja git pkgconf python sdl2 vulkan-headers vulkan-icd-loader shaderc ffmpeg` | | openSUSE | `sudo zypper install clang gcc-c++ cmake ninja git pkgconf-pkg-config python3 SDL2-devel vulkan-devel shaderc-devel ffmpeg-7-libavcodec-devel ffmpeg-7-libavutil-devel` | How each line was tested: - **Ubuntu 24.04:** a complete build and boot in a clean `ubuntu:24.04` container. - **Fedora 44, Arch, openSUSE Tumbleweed:** checked in clean containers, not by a full build. Every package installs, every pkg-config module and header is found, and clang++ compiles and links a C++20 program against them. - **Audio:** sound needs libavcodec's XMA2 decoder. Debian/Ubuntu's and Arch's FFmpeg have it. Fedora's `ffmpeg-free` and openSUSE's `ffmpeg-7` do not: the game builds and runs, but silently. Use RPM Fusion's `ffmpeg` or Packman's `ffmpeg-7` there. The build script checks for the decoder and warns. 2. From the repository root: ```sh python3 tools/build_from_iso.py /path/to/Splatterhouse.iso ``` **Pre-flight check.** Before building, the script checks: - every tool and library (via pkg-config); - the C++ compiler, by compiling and linking a test program; - the XMA2 decoder. Whatever is missing is listed in a table with the exact install command for your distribution. **Python.** Python 3.11 or newer runs the build steps. Python 3.9 can start the script, which then switches to a `python3.11`+ on `PATH` by itself. The script needs pycryptodome and capstone. It uses your Python's copies if present; otherwise it creates a private environment `work/pyenv` and pip-installs pinned versions there. Nothing is installed system-wide. **Disc extraction.** `extract-xiso` is not needed. The built-in `tools/xiso.py` produces exactly the same files; `python3 tools/test_xiso.py --compare ` checks that against an installed extract-xiso. The script runs these steps, with progress output and a per-step log in `work/build-logs/`: 1. **verify-iso**: SHA-256 of the image against the known retail dump. 2. **extract**: the disc's game partition → `extracted/` (`extract-xiso` when it is on `PATH`, else `tools/xiso.py`). 3. **decrypt**: `default.xex` → decrypted, decompressed image `work/default.exe`. 4. **recompile**: build the patched XenonRecomp, translate the game's code into `work/xenon/ppc/*.cpp`, generate the kernel-import stubs. 5. **symbols**: the (optional) guest symbol table. 6. **build**: Release build of `runtime/` with clang + ninja. This is the long step: the translated game is ~230 MB of C++. 7. **assemble**: a ready-to-run directory, `dist/` by default. A failing step stops the build with `BUILD FAILED in step ''` and the path of its log. A full run takes a few minutes on a 16-thread desktop: 2m17s measured with the Nix toolchain, about half of it the compile. The build is resumable. Re-running skips every step whose outputs are already up to date, so after an interruption or a source change, just run it again. | Option | | |---|---| | `--out ` | where to put the runnable game (default `dist/`) | | `--clean` | redo every step from scratch (saves are kept) | | `--force` | continue with an ISO that does not match the known hash | | `-j N` | parallel compile jobs (default: all CPUs) | | `--nix` | use the pinned Nix toolchain instead of the system's tools ([below](#build-with-nix)) | | `--no-nix` | never use Nix, even when tools are missing (stop with the install command instead) | Other entry points: - `python3 tools/build_from_iso.py --dry-run-platform linux` runs only the tool check and prints the table. - `python3 tools/recompile.py` runs step 4 on its own. It needs `extracted/` and `work/default.exe` from steps 2–3. - `python3 tools/test_build_pipeline.py` and `python3 tools/test_xiso.py` unit-test the build script's platform layer and the disc extractor. ### Build with Nix This is the alternative path, and the one the maintainer uses. `flake.nix` and `flake.lock` pin the whole toolchain (clang, cmake, ninja, SDL, FFmpeg, Vulkan loader, shaderc, extract-xiso, and Python with pycryptodome and capstone), so nothing else needs to be installed. It needs [Nix](https://nixos.org/download); the script enables the `nix-command flakes` features itself. ```sh python3 tools/build_from_iso.py --nix /path/to/Splatterhouse.iso ``` - **What `--nix` does:** re-runs the script inside `nix develop .#build`. Only `flake.nix`/`flake.lock` are staged into `work/nix-flake`, so the repository and the ISO are never copied into the Nix store. - **The automatic fallback:** without `--nix`, Nix is used only when some system tool is missing *and* Nix is installed. The script says when this happens; `--no-nix` turns it off. - **Linking:** the binary then links the Nix store's libraries, registered as GC roots under `/.gcroots`. - **GPU drivers:** on distributions other than NixOS, Nix-built programs sometimes cannot find the host's GPU driver. A wrapper such as [nixGL](https://github.com/nix-community/nixGL) solves that, or build without Nix. `bash tools/build_from_iso.sh …` still works: it is a thin wrapper that runs the Python script. ### Windows Not finished yet. `py tools\build_from_iso.py ` runs steps 1–5 on Windows (verify, extract, decrypt, recompile, symbols; XenonRecomp supports Windows). It then **stops before the runtime build**, because `runtime/` still uses Linux APIs: memfd, `mmap` placement, `sigaction`, `prctl`, `/proc`. **Tools.** There is no Nix on Windows. The tools come from `PATH`, or from these variables set to the full path of each `.exe`: `CMAKE`, `NINJA`, `CC`/`CXX` (clang-cl or clang), `EXTRACT_XISO` and `GIT`. - Run the script from an *x64 Native Tools Command Prompt for VS*, so clang finds the MSVC linker and the Windows SDK. - Needed: CMake, Ninja, LLVM/clang, Git and Python 3.11+. pycryptodome and capstone are pip-installed into `work\pyenv` when missing. extract-xiso is optional. - The runtime will also need the Vulkan SDK (headers, loader, shaderc), SDL2 and the FFmpeg development libraries. Missing tools are listed in a table with where to get them. `python3 tools/build_from_iso.py --dry-run-platform windows` prints that table on any OS without building anything. The output launcher on Windows is `run.bat`. ## Running ```sh dist/run.sh ``` What `dist/` contains: - the executable `splatterhouse`; - `work/default.exe`, the decrypted game executable it loads; - `extracted/`, the game files; - `run.sh`, which changes into that directory and starts the game. No arguments or environment variables are needed. Saves are written to `dist/work/xcontent/`. `Esc` or closing the window quits. ### Controls A gamepad works out of the box (SDL game-controller mapping, Xbox layout). Keyboard: | Key | Xbox 360 button | |---|---| | Enter | START | | Space / Z | A | | X / Backspace | B | | C | X | | V | Y | | Arrow keys | D-pad | | Q / E | LB / RB | | Tab | BACK | The keyboard has no analog-stick or trigger mapping, so a gamepad is recommended for play. ### Run-config options None is needed to play; `dist/run.sh --help` lists all of them. The ones worth knowing: - `SPLAT_MAX_FPS=60`: opt-in enhancement that lifts the game's own 30 fps cap. Known issue: some game systems are tied to the original pacing and misbehave at 60 fps. Default: off (the game paces itself at 30 fps, as on the console). - `SPLAT_FPS_CAP=`: an extra host-side main-loop sleep cap (default off). Example: `SPLAT_MAX_FPS=60 dist/run.sh`. ## Repository layout | Path | Contents | |---|---| | `tools/build_from_iso.py` | The end-to-end build (ISO in, `dist/` out). | | `tools/recompile.py` | The recompilation step on its own. | | `tools/build_platform.py` | The build's OS layer: tool discovery and checks, distribution install commands, the Nix choice, `work/pyenv`, launcher, links. | | `tools/xiso.py` | Built-in Xbox / Xbox 360 disc (XDVDFS) extractor. | | `tools/xex_decrypt.py`, `tools/xex_info.py` | XEX2 decryption and inspection. | | `recomp/` | XenonRecomp configuration: function boundaries, jump-table overrides. | | `patches/` | Fixes to XenonRecomp's code generation. | | `runtime/main.cpp` | Loader: guest address space, image mapping, entry point. | | `runtime/kernel/` | xboxkrnl and XAM HLE, XMA decoder, audio driver. | | `runtime/gpu/` | Xenos command processing → Vulkan renderer, shader translator. | | `runtime/debug_hooks.cpp` | Per-function overrides and run-config options. | | `docs/` | Design notes and investigations (below). | | `flake.nix` | Nix toolchain. `nix develop` gives the full reverse-engineering environment; `nix develop .#build` gives the lean build environment. | Useful starting points in `docs/`: - `subsystem-map.md`: the game's subsystems; - `gpu-design.md` and `texture-rt-design.md`: the renderer; - `audio-hle.md`: audio; - `jumptables.md`: recompiler jump tables; - `scheduler-model.md`: thread scheduling; - `frontend-authentic.md`: the session-by-session development log; - `TODO.md`: open work. ## Credits and license This project's code is MIT-licensed (see `LICENSE`). Third-party notices are in `THIRD_PARTY_NOTICES.md`. Thanks to: - the [Xenia](https://github.com/xenia-project/xenia) project, whose emulator and research are the reference for Xbox 360 hardware and kernel behaviour; - hedge-dev's [XenonRecomp](https://github.com/hedge-dev/XenonRecomp) and [XenosRecomp](https://github.com/hedge-dev/XenosRecomp), and [Unleashed Recompiled](https://github.com/hedge-dev/UnleashedRecomp), which proved this approach; - the [ReXGlue SDK](https://github.com/rexglue/rexglue-sdk); - [extract-xiso](https://github.com/XboxDev/extract-xiso); - [libmspack](https://www.cabextract.org.uk/libmspack/). *Splatterhouse* is a trademark of Bandai Namco Entertainment. This project is not affiliated with or endorsed by Bandai Namco Entertainment or Microsoft. You need your own copy of the game.