Skip to content

About

Matching decompilation of `Wild 9` for the Sony PlayStation 1, targeting the NTSC-U SLUS_004.25 executable, built with a reproducible GCC 2.8.1 PSX and GNU binutils 2.42 toolchain

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Repository files navigation

Wild 9 Decompilation

Matching decompilation of Wild 9 for the Sony PlayStation 1, NTSC-U executable SLUS_004.25

8c46df32775afcc338f82c9abe645cc2

Overview

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.

Useful links

Project status

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.

Features

  • 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) into asm/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, and mipsmatch.

Requirements

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 clang

Target 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.

Setup

1. Clone the repository

Clone the repository with all Git submodules:

git clone --recursive https://github.com/plinkr/Wild9-Decompilation.git
cd Wild9-Decompilation

If the repository was cloned without --recursive, initialize the submodules:

make tools

2. Python environment

Create the Python virtual environment and install pinned dependencies:

make install

This 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 ...

3. Toolchain installation

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 toolchain

This 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-toolchain

If you prefer to download and unpack the packages manually, see docs/TOOLCHAIN.md.

4. Load the environment (required for commands without Make)

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.sh

5. Install the original executable

Wild 9 (NTSC-U) ships as SLUS_004.25. You need this executable in iso/us/ before building.

Extract it from your own Wild 9 disc image

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.25

You 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.25

Or copy it manually

If 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.25

Either way, the final layout must be:

iso/us/SLUS_004.25

6. Verify the toolchain

Verify the installed tools:

make versions
make check-toolchain

Build

Splat disassembly and per-function splitting

Disassemble the original binary and generate per-function assembly:

make split

Why 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:

  1. Splat runs to disassemble the binary into asm/800.s and data files.
  2. scripts/split_800_functions.py runs automatically to parse asm/800.s and write each function to asm/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.

Re-splitting functions without re-running Splat

If asm/800.s already exists and you only want to regenerate the per-function files under asm/nonmatchings/main/:

make split-functions

You can also run the script directly using the virtual environment:

.venv/bin/python scripts/split_800_functions.py asm/800.s asm/nonmatchings/main

Recreating src/main.c (opt-in)

The repository provides a script to regenerate src/main.c with an INCLUDE_ASM stub for every function found in asm/800.s:

make generate-includes

Or run the script directly using the virtual environment:

.venv/bin/python scripts/generate_include_asm.py asm/800.s src/main.c

Why 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.

Building and verifying

Build and verify the baseline all-assembly reference:

make original-verify

Build the target executable:

make build

Verify that the built executable matches the original:

make verify

The 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.

Matching

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_XXXXXXXX

For example:

make match FUNCTION=func_800101A4

A 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.

Development Workflow

1. Decompiling a function

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.c

Write 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.

2. Testing and matching

Compile and compare the function:

make match FUNCTION=func_XXXXXXXX

Iterate 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.

3. Promoting a matched function

Once make match succeeds, promote the function:

make promote FUNCTION=func_XXXXXXXX

make promote performs the following steps:

  1. Verifies the standalone match with make match.
  2. Replaces the INCLUDE_ASM("asm/nonmatchings/main", func_XXXXXXXX); line in src/main.c with #include "functions/func_XXXXXXXX.c".
  3. Rebuilds the executable with make build.
  4. Runs make verify to ensure the entire binary matches byte-for-byte.
  5. If verification fails, it automatically restores src/main.c from backup.

4. Formatting

Format C source and header files:

make format

This 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-later

Do not alter code purely for formatting if the change modifies the generated assembly. Refer to docs/STYLE.md for coding and formatting conventions.

Project Structure

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.

Useful Commands

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

Contributing

  • 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.

License

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.

About

Matching decompilation of `Wild 9` for the Sony PlayStation 1, targeting the NTSC-U SLUS_004.25 executable, built with a reproducible GCC 2.8.1 PSX and GNU binutils 2.42 toolchain

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages