Matching decompilation of Wild 9 for the Sony PlayStation 1, NTSC-U executable SLUS_004.25
This project reconstructs the NTSC-U Wild 9 PlayStation 1 executable as matching C source and MIPS R3000A assembly. The primary goal is a byte-for-byte matching rebuild of the original executable (SLUS_004.25).
The codebase is structured around progressive decompilation. Matched functions are written as standalone C files under src/functions/ and promoted into src/main.c, replacing the corresponding INCLUDE_ASM disassembly entries. Unmatched code remains as generated assembly slices. Each promoted function is independently compiled, linked, and matched against the original binary before being patched into the final rebuilt executable.
This is a work-in-progress decompilation project for the original Wild 9 PlayStation 1 executable. Its purpose is to reconstruct the game's source code and produce a byte-for-byte matching rebuild of the original binary.
This repository is not a playable version of Wild 9. It contains the decompilation sources, build scripts, tools, and generated code required to reproduce the original executable from a legally obtained copy of the game. The original game executable and other copyrighted game assets are not included in this repository.
For a playable native PC recompilation of Wild 9, see Wild 9 Recomp.
- Byte-for-byte matching rebuild against NTSC-U executable
SLUS_004.25. - Splat-based executable splitting and disassembly generation.
- Automated per-function assembly splitting (
split_800_functions.py) intoasm/nonmatchings/main/. - GCC 2.8.1 PSX compiler toolchain with
maspsx(ASPSX 2.77) assembly translation. - Isolated GNU binutils 2.42 environment targeting
mipsel-linux-gnu. - In-tree toolchain download and verification (
make toolchain). - Per-function standalone compilation, linking, and byte-exact verification.
- Automated function promotion workflow integrating matching C code into the main translation unit.
- Submodule integration for decompilation tools including
asm-differ,decomp-permuter,m2c,m2ctx,maspsx, andmipsmatch.
The build and matching toolchain targets x86_64 Linux.
Host packages:
# Debian or Ubuntu
sudo apt install git make python3 python3-pip python3-venv curl binutils zstd file clang-format
# Arch Linux, Manjaro, or CachyOS
sudo pacman -S git make python python-pip curl binutils zstd file clangTarget game data:
A legally obtained original NTSC-U Wild 9 PlayStation executable must be placed at:
iso/us/SLUS_004.25
Expected file identity:
Size: 425984 bytes
SHA-1: 1dac75ee3a0ef5a62e025a85183076fa1a6cf824
SHA-256: ab280169ed52906589b8353a00c5cb4e9851da21d1023c98569de51958a44012
The original executable is not included in this repository.
Clone the repository with all Git submodules:
git clone --recursive https://github.com/plinkr/Wild9-Decompilation.git
cd Wild9-DecompilationIf the repository was cloned without --recursive, initialize the submodules:
make toolsCreate the Python virtual environment and install pinned dependencies:
make installThis installs splat64 and spimdisasm into .venv.
Whenever you execute Python scripts or tools directly without Make, you must run them within this virtual environment. You can either activate the virtual environment in your current shell:
source .venv/bin/activate
python scripts/extract_psx_exe.py ...Or invoke the virtual environment Python interpreter directly without activating:
.venv/bin/python scripts/extract_psx_exe.py ...The matching build requires GCC 2.8.1 PSX and GNU binutils 2.42 (mipsel-linux-gnu). Both are installed into tools/toolchain/ without modifying system packages or requiring root access.
Install both automatically using the Makefile:
make toolchainThis downloads the precompiled GCC 2.8.1 PSX archive (decompals/old-gcc release 0.17) and the Ubuntu Noble binutils 2.42 packages, validates their SHA-256 checksums, and extracts them into tools/toolchain/.
To verify that the in-tree toolchain is present:
make check-toolchainIf you prefer to download and unpack the packages manually, see docs/TOOLCHAIN.md.
The Makefile automatically exports the private binutils PATH and LD_LIBRARY_PATH to all recipes, so executing make works without any manual environment setup.
However, to execute any commands without Make in your shell (such as mipsel-linux-gnu-as, mipsel-linux-gnu-ld, mipsel-linux-gnu-objcopy, or standalone compilation scripts), you must first load the binutils environment:
source scripts/binutils-env.shWild 9 (NTSC-U) ships as SLUS_004.25. You need this executable in iso/us/ before building.
If you have a Redump-style dump of Wild 9 (.cue + .bin, or a bare .bin), use the helper script in this repo:
.venv/bin/python scripts/extract_psx_exe.py "/path/to/Wild 9 (USA).cue"The script reads the filesystem inside the dump, follows SYSTEM.CNF to the boot executable, and writes SLUS_004.25 to the current directory. Then move it into place:
mkdir -p iso/us
mv SLUS_004.25 iso/us/SLUS_004.25You can also pass the output path directly:
.venv/bin/python scripts/extract_psx_exe.py "/path/to/Wild 9 (USA).bin" iso/us/SLUS_004.25If you already have SLUS_004.25 extracted (for example from DuckStation's disc browser), just place it at:
mkdir -p iso/us
cp /path/to/SLUS_004.25 iso/us/SLUS_004.25Either way, the final layout must be:
iso/us/SLUS_004.25
Verify the installed tools:
make versions
make check-toolchainDisassemble the original binary and generate per-function assembly:
make splitWhy this step exists:
Splat reads iso/us/SLUS_004.25 and disassembles the main code segment into a single large file, asm/800.s. Splat by itself does not split asm/800.s into individual function files.
Previously, that per-function splitting had to be done by hand locally, which meant that cloning the repository and running make split left asm/nonmatchings/main/ empty, breaking the build for new contributors.
Now, make split handles the full pipeline:
- Splat runs to disassemble the binary into
asm/800.sand data files. scripts/split_800_functions.pyruns automatically to parseasm/800.sand write each function toasm/nonmatchings/main/func_XXXXXXXX.s.
Running make (or make all) on a clean checkout will detect missing stamps and automatically bootstrap Splat and the function split before building.
If asm/800.s already exists and you only want to regenerate the per-function files under asm/nonmatchings/main/:
make split-functionsYou can also run the script directly using the virtual environment:
.venv/bin/python scripts/split_800_functions.py asm/800.s asm/nonmatchings/mainThe repository provides a script to regenerate src/main.c with an INCLUDE_ASM stub for every function found in asm/800.s:
make generate-includesOr run the script directly using the virtual environment:
.venv/bin/python scripts/generate_include_asm.py asm/800.s src/main.cWhy this is opt-in and NOT automatic:
src/main.c is the primary translation unit tracked in git. As functions are decompiled and matched, their INCLUDE_ASM calls are replaced with #include "functions/func_XXXXXXXX.c". If make split or make build regenerated src/main.c automatically, it would wipe out all promoted C functions. Only run make generate-includes if you are bootstrapping from scratch or intentionally resetting the translation unit.
Build and verify the baseline all-assembly reference:
make original-verifyBuild the target executable:
make buildVerify that the built executable matches the original:
make verifyThe build process starts from the byte-exact reference and replaces promoted functions defined in src/main.c. Each promoted function is matched independently before its bytes are patched into the final executable.
Each decompiled function lives in its own file under:
src/functions/func_XXXXXXXX.c
To build and compare a standalone function against the original binary:
make match FUNCTION=func_XXXXXXXXFor example:
make match FUNCTION=func_800101A4A successful match prints:
func_800101A4: MATCH
The standalone matching pipeline is:
src/functions/func_XXXXXXXX.c
-> C preprocessor (CPP)
-> GCC 2.8.1 PSX compiler
-> scripts/prepare_function_asm.py (small-data handling and ASPSX
delay-slot store preparation via scripts/aspsx_delay_slot.py)
-> maspsx (ASPSX 2.77 translation)
-> GNU assembler 2.42 (mipsel-linux-gnu-as)
-> scripts/link_function.py (standalone link at original address)
-> scripts/match_function.py (byte comparison against original binary)
The project does not modify the tools/maspsx submodule. scripts/prepare_function_asm.py handles small-data preparation before invoking maspsx, and scripts/aspsx_delay_slot.py restores the ASPSX expansion of bare macro stores that belong in a jump delay slot (lui $at,%hi(sym) hoisted before the jump, sw $reg,%lo(sym)($at) in the slot). The delay-slot rewrite only applies where the reference assembly proves the store is a delay-slot instruction.
Find an unpromoted function in src/main.c. Unpromoted functions appear as:
INCLUDE_ASM("asm/nonmatchings/main", func_XXXXXXXX);The disassembled assembly generated by make split is located at:
asm/nonmatchings/main/func_XXXXXXXX.s
If asm/nonmatchings/main/ is missing, run make split-functions to regenerate it from asm/800.s.
Create a standalone C source file:
$EDITOR src/functions/func_XXXXXXXX.cWrite the C implementation using fixed-width types from include/types.h and shared declarations from include/functions.h, include/globals.h, and include/decomp.h.
Compile and compare the function:
make match FUNCTION=func_XXXXXXXXIterate on the C code until make match reports MATCH. Decompilation helper submodules under tools/ (such as asm-differ or decomp-permuter) can be used to analyze differences.
Once make match succeeds, promote the function:
make promote FUNCTION=func_XXXXXXXXmake promote performs the following steps:
- Verifies the standalone match with
make match. - Replaces the
INCLUDE_ASM("asm/nonmatchings/main", func_XXXXXXXX);line insrc/main.cwith#include "functions/func_XXXXXXXX.c". - Rebuilds the executable with
make build. - Runs
make verifyto ensure the entire binary matches byte-for-byte. - If verification fails, it automatically restores
src/main.cfrom backup.
Format C source and header files:
make formatThis applies .clang-format rules and verifies that all C and header files start with the project SPDX license identifier:
// SPDX-License-Identifier: AGPL-3.0-or-laterDo not alter code purely for formatting if the change modifies the generated assembly. Refer to docs/STYLE.md for coding and formatting conventions.
src/
main.c
Main translation unit containing promoted C includes and remaining INCLUDE_ASM macros.
functions/
Standalone C source files, one per decompiled function.
include/
types.h
Fixed-width integer types (u8, s8, u16, s16, u32, s32, bool).
functions.h
Shared function declarations.
globals.h
Shared game global variable declarations.
decomp.h
Decompilation helpers, hardware structures, and macros.
include_asm.h
INCLUDE_ASM and INCLUDE_RODATA macro definitions.
macro.inc
Assembler macros.
labels.inc
Assembler label declarations.
gte_macros.inc
PlayStation Geometry Transformation Engine (GTE) assembly macros.
config/slus_004.25/
splat.yaml
Splat disassembly and section split configuration.
toolchain.json
Compiler and assembler flags and tool versions.
SLUS_004.25.sha1
SHA-1 checksum of the original executable.
SLUS_004.25.sha256
SHA-256 checksum of the original executable.
symbols/
slus_004.25.txt
Hardware register mappings, BIOS symbols, and known addresses.
scripts/
binutils-env.sh
Sets PATH and LD_LIBRARY_PATH for the private binutils 2.42 environment.
split_800_functions.py
Splits asm/800.s into per-function assembly files in asm/nonmatchings/main/.
generate_include_asm.py
Generates src/main.c with INCLUDE_ASM stubs from asm/800.s (opt-in).
match_function.py
Compares standalone function binary against the original executable.
link_function.py
Links a standalone function object at its original address.
prepare_function_asm.py
Prepares standalone GCC assembly output before passing to maspsx.
prepare_main_asm.py
Prepares main translation unit assembly output.
aspsx_delay_slot.py
ASPSX-compatible expansion of bare macro stores that belong in a jump
delay slot (used by the two prepare scripts above).
promote_function.py
Replaces INCLUDE_ASM entries with C includes in src/main.c.
normalize_splat_config.py
Normalizes generated Splat split configuration.
inspect_psx_exe.py
Parses and displays PlayStation executable header information.
check_prototypes.py
Verifies header prototypes match C function signatures.
sort_declarations.py
Sorts declarations in header files.
docs/
STYLE.md
C style guide, naming conventions, and matching guidelines.
TOOLCHAIN.md
Detailed toolchain setup instructions and package references.
REPOSITORY.md
Repository clean-room reverse engineering policy.
tools/
Git submodules for decompilation tooling and local toolchain files.
Generated assembly (asm/), per-function assembly (asm/nonmatchings/main/), linker scripts, object files, and build outputs (build/) are ignored by Git.
make help # Show available Makefile targets
make toolchain # Install GCC 2.8.1 PSX and binutils 2.42 in-tree
make check-toolchain # Report whether the in-tree toolchain is present
make versions # Print Python, splat, and spimdisasm versions
make info # Display PS-X EXE header fields
make fingerprint # Record SHA-1 and SHA-256 of the target binary
make split # Run Splat and regenerate per-function ASM
make split-functions # Regenerate asm/nonmatchings/main/ from asm/800.s
make generate-includes # Regenerate src/main.c INCLUDE_ASM stubs
make original-verify # Build and verify the baseline assembly reference
make build # Build the final executable with promoted functions
make verify # Compare build/SLUS_004.25 with the original
make match FUNCTION=func_XXXXXXXX # Build and match a single function
make promote FUNCTION=func_XXXXXXXX # Promote a matched function in src/main.c
make check-prototypes # Verify header prototypes against C definitions
make format # Format C/header files with clang-format
make clean # Remove build artifacts and nonmatchings
make distclean # Remove build artifacts and Python virtualenv- Keep one decompiled function per file under
src/functions/func_XXXXXXXX.c. - Keep promoted functions in original address order in
src/main.c. - Follow the conventions in
docs/STYLE.md. - Before submitting a pull request, run:
make format make verify
- Do not commit original game executables, disc images, extracted assets, generated assembly, object files, or other build artifacts.
Project source code, scripts, configuration, and documentation are licensed under the GNU Affero General Public License version 3. See LICENSE.md.
The Wild 9 executable, disc data, artwork, audio, and other original game assets are copyrighted by their respective rights holders and are not licensed by this project.