askill
n64-decomp-setup

n64-decomp-setupSafety --Repository

N64 game decompilation workflow assistant. Use when helping with Nintendo 64 reverse engineering tasks including: (1) Setting up new decomp projects with splat64 and uv, (2) Creating build systems with ninja, (3) Identifying libultra versions and symbols, (4) Finding compiler versions via decomp.me, (5) Converting assembly to C code. Triggers on mentions of N64/Nintendo 64 decompilation, splat, decomp.me, libultra, or MIPS assembly matching.

2 stars
1.2k downloads
Updated 2/4/2026

Package Files

Loading files...
SKILL.md

N64 Decompilation

Project Setup

First, ask user if they want to initialize a git repository for this project.

Initialize with uv (not pip) for better version locking:

uv init --bare
uv add "splat64[mips]"

Copy ROM to project folder as baserom.<extension> (keep original extension):

cp "/path/to/Game Name.n64" baserom.n64

Generate config from ROM (splat handles byteswapping internally):

uv run -m splat create_config baserom.n64

After config generation, splat creates a byteswapped baserom.z64. From this point on, always use baserom.z64 (the build system expects z64 format).

After config is generated, enable undefined symbol paths in the yaml (uncomment these lines):

undefined_funcs_auto_path: undefined_funcs_auto.txt
undefined_syms_auto_path: undefined_syms_auto.txt

Do NOT enable hardware_regs or libultra_symbols - they cause symbol mismatches and are unnecessary work.

Split the ROM:

uv run -m splat split <game>.yaml

Splat generates include/macro.inc automatically - do not create it manually.

Create .gitignore:

# ROMs (copyright)
*.z64
*.n64
*.v64

# Generated by splat
asm/
assets/
*.ld
.splat/
.splache

# Build artifacts
build/
*.o
*.bin
.ninja_log
build.ninja

# Auto-generated symbol files
undefined_funcs_auto.txt
undefined_syms_auto.txt

# Python
__pycache__/
.venv/
venv/

# Decomp tools
ctx.c
ctx.c.m2c
permuter_settings.toml
expected/

IMPORTANT: The asm/ folder is generated by splat. Never edit asm files directly - changes won't persist. Fix issues in the YAML config or symbol files instead.

Splat suggests file splits in output - these are "highly likely" object boundaries detected via zero-padding between functions aligned at 0x10. Add these to the yaml LATER, after basic build works.

Note on splits: Splat detects splits when a function's last instruction is at offset like 0x1C and the next function starts at 0x20 (aligned). If a function ends at 0x1C and the next starts at 0x20 without padding, splat cannot detect the boundary - but one may still exist.

Splat YAML Subsegment Format

Subsegments use shorthand: [offset, type] or [offset, type, name]

subsegments:
  - [0x1050, asm]                    # name defaults to "1050" (hex offset)
  - [0xA0CD0, asm, libultra/A0CD0]   # explicit name, creates libultra/ folder

To rename a file, add the third element. Names with slashes become folders.

IMPORTANT: After ANY changes to the YAML configuration, run python configure.py --clean before rebuilding.

Build System

CRITICAL: Use references/configure-simple.py for initial setup. It handles assembly-only builds. Do NOT use the complex Pokemon Snap example yet - that's for later when adding C compilation.

The configure script requires ninja_syntax:

uv add ninja_syntax

Update the BASENAME variable in configure.py to match your splat YAML filename (e.g., if splat generated capybara.yaml, set BASENAME = "capybara").

After creating configure.py, make it executable: chmod +x configure.py

Checksum File

Create checksum.sha1 to verify builds match the original ROM:

<sha1hash>  build/<basename>.z64

Generate the hash from your baserom:

sha1sum baserom.z64

Example checksum.sha1:

edc7c49cc568c045fe48be0d18011c30f393cbaf  build/pokemonsnap.z64

The build system uses this to validate the output matches the original.

Building

Run configure and build:

python configure.py
ninja

After YAML changes, always clean first: python configure.py --clean

The first build (before adding splits) should produce a matching checksum. If it doesn't match, use xxd or similar hex tools to compare build/<basename>.z64 against baserom.z64 and find where bytes differ - this indicates a toolchain or configuration issue that must be resolved before proceeding.

Once building succeeds:

  1. Add the suggested splits from splat output to the yaml
  2. Rebuild and verify still matches
  3. Commit your progress
  4. Proceed to libultra identification

Diffing Tools

Create tools/ folder for helper scripts.

make_expected.sh - Save a known-good build for comparison:

#!/bin/bash
mkdir -p expected && cp -r build expected/

Only run this after a build passes checksum verification - expected/ should only contain verified matching builds.

tools/first_diff.py - Find first difference between builds. Use references/first_diff.py as a starting point. Update BASENAME to match your project, then run with uv run tools/first_diff.py (dependencies are specified inline). The script:

  • Compares built ROM against expected
  • Decodes MIPS instructions for readable diff
  • Resolves addresses to symbol names from map file

Identifying libultra

Find Version

Check asm/header.s for revision field:

.word 0x00001448       /* Revision */

The byte (0x48 = 'H') encodes libultra SDK version as ASCII (E=2.0E, F=2.0F, ... L=2.0L). See https://n64brew.dev/wiki/Libultra for version history.

Setup

git clone https://github.com/decompals/ultralib
rm -rf ultralib/.git
uv add git+https://github.com/matt-kempster/m2c

Download ultralib into the project and commit it (remove .git, not a submodule). Do NOT add ultralib to .gitignore.

After adding ultralib, update configure.py to include its headers and add BUILD_VERSION:

INCLUDES = "-I include -I ultralib/include -I ultralib/include/PR"
DEFINES = "-D_FINALROM -DNDEBUG -DBUILD_VERSION=VERSION_H"  # Adjust letter to match SDK version

BUILD_VERSION must match the SDK version letter identified above (H for 2.0H, etc.).

Get n64sym Hints First

BEFORE doing any analysis, ask user to run n64sym and paste the output: https://shygoo.github.io/n64sym/web/

Wait for user to provide n64sym output. Use it to estimate:

  • Where libultra might start (look for first os* function address)
  • Where libultra might end
  • Which functions might be present

WARNING: n64sym is UNRELIABLE - pattern-matches against known binaries. NEVER add symbols directly. Use ONLY as hints for where to look.

Find libultra Boundaries

NOTE: Do not read raw asm files - they are large and token-inefficient. Always use m2c first:

uv run m2c asm/<file>.s
  1. Find start of libultra: Based on n64sym hints, check candidate files with m2c

    • Compare m2c output against ultralib source
    • When matched, rename in yaml: [0xA0CD0, asm, libultra/A0CD0]
  2. Find end of libultra: Same process for last libultra function

  3. Rename ALL files in the libultra range in the yaml subsegments

  4. Rebuild and verify still matches

Libultra is typically one continuous block of modules.

Identify Called Functions

Find ALL function calls INTO the libultra VRAM range:

# Find calls to libultra address range (adjust range for your ROM)
rg "jal.*0x800[a-f]" asm/

Continue until you have identified ALL unique libultra functions called from game code. These are the priority - game code needs to know their signatures.

Add Symbols Module-by-Module

For each called function:

  1. Run m2c on the file containing that address
  2. Match m2c output to ultralib source
  3. Add function symbol to symbol_addrs.txt (text symbols more important than data)
  4. Rebuild and verify

For symbol syntax, see https://github.com/ethteck/splat/wiki/Adding-Symbols

Do not stop until all calls into libultra are identified. Verify build matches and commit before proceeding.

Identifying Compiler Version

The compiler type is in the yaml under options.compiler (detected by splat).

Using decomp.me

  1. Find a small, linear function outside libultra
  2. Run through m2c: uv run m2c asm/<file>.s
  3. Guide user to https://decomp.me/new with:
    • Target assembly (from the .s file)
    • m2c output as starting point
    • Function signatures for called functions
    • Typedefs (s32, u32, etc.)
  4. User tries presets until one matches

Prefer functions with simple control flow - fewer branches = fewer ways to write equivalent C.

IDO Specifics

If compiler is IDO (most common), see the n64-decomp-ido skill for setting up asm-processor and build integration.

For compiler downloads, see https://github.com/decompme/compilers/blob/main/values.yaml

Converting to C

Change file type from asm to c in the yaml and rebuild. This generates:

  • C file with function stubs
  • Separate .s files per function in asm/nonmatchings/

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

AI review pending.

Metadata

Licenseunknown
Version-
Updated2/4/2026
Publishermarijnvdwerf

Tags

ci-cdgithubprompting