A working example of a mod for Snap64 Recomp,
and the files to start one of your own. A mod is code that runs inside the
recompiled game: it can hook a game function, run after one, or replace one,
and it can read options the player sets in the port. Mods are the same
.nrm files the other N64 recompilations use, built with the same tools.
A mod built from this template needs Snap64 Recomp 1.0.9 or later.
src/mod_example.c-- the example: a hook on the game's scene set-up that writes a line to the port's log, behind an option.mod.toml-- the mod's manifest (id, name, version, the game it is for, its options) and the inputs the mod tool reads.Makefile,mod.ld-- the MIPS build, with clang and lld.include/-- the modding headers:modding.h(the hook and patch macros),recomputils.h(printing and memory),recompconfig.h(reading the mod's options),recompdata.h(the hashmaps, hashsets and slotmaps a mod keeps between calls, held by the port). The other recompilations'recompui.h(menus a mod draws) is not here: the port does not provide those functions, and a mod that imports them does not load.pokemonsnap/-- the game's decompilation, as a submodule, for its headers and its function and variable names.Snap64RecompSyms/-- the symbol files the mod tool resolves those names with, as a submodule.thunderstore/-- the mod's Thunderstore page (README.md), its changelog and a placeholder icon, andtools/pack_thunderstore.py, which packs them with the.nrm(see "Publishing")..github/workflows/build.yml-- builds and packs the example on every push, from a clean clone, the way the section below says to.
-
clangandld.lldthat can target MIPS, andmake. On Debian or Ubuntu:clang,lld,make. The other recompilations' templates say, for Windows, LLVM 18 (19.1.0 does not handle MIPS correctly for this build) and make, for example through Chocolatey (llvm --version 18.1.8,make); and for macOS, Homebrew'sllvmandmake, withCC=/opt/homebrew/opt/llvm/bin/clang LD=/opt/homebrew/opt/llvm/bin/ld.lldpassed to make, because Apple's clang has no MIPS target. I build on Ubuntu and have not run the Windows or the macOS lines. -
RecompModTool, from N64Recomp. Built from source at the commit the port itself is built against:git clone --recurse-submodules https://github.com/N64Recomp/N64Recomp cd N64Recomp git checkout ffb39cdad1da5de07eaaa48bd1db4a89a7986771 git submodule update --init --recursive cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --target RecompModToolN64Recomp also publishes binaries for Windows and Linux (the
mod-tool-releasetag, May 2025). I have read that version's source and not run the binary: it reads every key this template'smod.tomluses, and it is older than optional dependencies, so a mod that declares one needs the tool built from source. -
On Linux and macOS the mod tool calls
zipto write the.nrm, and says onlyFailed to run "zip"when it is missing: install it (zipon Debian or Ubuntu).
git clone --recurse-submodules https://github.com/JackandBeans/Snap64RecompModTemplate
cd Snap64RecompModTemplate
make
RecompModTool mod.toml build
build/snap64_recomp_mod_template.nrm is the mod. The decompilation is only
read for its headers and needs no build of its own.
Put the .nrm in the mods/ folder next to Snap64Recomp.exe (or the
Linux binary; on a Mac, the mods/ folder in
~/Library/Application Support/Snap64 Recomp/) and start the port. From 1.1.0 the port also takes the .nrm,
or the Thunderstore zip, dropped on its window, and unpacks a zip left in
mods/; either way the mod loads at the next start. A mod is turned on the first time the port
finds it; the log, snap64.log, names it as it opens and loads it, and the
example writes its line each time the game sets up a scene. The port's Mods
page (Options > Mods; Esc, or Select on a pad, opens the Options anywhere)
lists the mod with its version and author, turns it off or on, opens its
options and changes the load order. A change there takes effect the next
time the game starts, and mods.json beside the executable records it.
Hook a game function by name with RECOMP_HOOK("name") to run before it,
RECOMP_HOOK_RETURN("name") to run after it (the recomphook_get_return_*
functions read what it returned), or replace it with RECOMP_PATCH on a
function of the same name and signature. The names are the decompilation's:
pokemonsnap/include/functions.h and the sources under pokemonsnap/src,
and Snap64RecompSyms/pokemonsnap.us.syms.toml lists every function the
tool knows. Game variables are named too (pokemonsnap.us.datasyms.toml);
declare one extern with the decompilation's own type, exactly -- a
variable declared wider than it is reads and writes its neighbours -- and
use it. The game's headers (common.h, sys/om.h and the rest) include
as they are.
Options go in mod.toml under [[manifest.config_options]] and are read
with the functions in recompconfig.h. recomp_printf writes to the port's
log, snap64.log. recompdata.h gives a mod hashmaps, hashsets and
slotmaps that live for the run; the example keeps a set in one.
Two things the port does that a mod meets:
- The port replaces some of the game's functions itself (the sources
under
patches/srcin the port's repository: the Options screen, the pause menu, the fade and a few more). A mod that replaces one of those withRECOMP_PATCHdoes not load, and the port says which function it was.RECOMP_FORCE_PATCHreplaces it all the same, and takes the port's own change to that function away with it. A few more functions are wrapped by the port rather than replaced (the effect system's particle draw, the Pokémon add routines and a handful more, listed in the port'stools/hook_funcs.py); a hook on one of those still runs, on the port's wrapper. A hook (RECOMP_HOOK,RECOMP_HOOK_RETURN) on a function the port replaces works from 1.1.0. - At 1.0.9 three kinds of hook failed: a hook on a function that calls
the game's
memcpy, on a function the port replaces, and on a function that waits a frame (ohWait, which most of the game's processes do; the game stopped when the process ended). 1.1.0 fixes all three. A mod with such a hook should ask for 1.1.0 inminimum_recomp_version. minimum_recomp_versionis checked against the port's own version. 1.0.9 is the first release that providesrecomp_printfand the collections, so it is the lowest that makes sense here; a mod that asks for a release newer than the player's is refused with a message that names the version it wants.
Thunderstore is where the other N64 recompilations list their mods. A
Thunderstore package is a zip with a manifest.json, a README.md (the
package's page) and a 256x256 icon.png at its root, beside the mod.
-
Edit
thunderstore/README.mdandthunderstore/CHANGELOG.md, and replacethunderstore/icon.png(the placeholder is the port's film canister with a plus). -
Build the mod, then run
python tools/pack_thunderstore.py --team YourTeam --website https://github.com/you/your-modThe zip lands in
dist/, namedYourTeam-Name-1.0.0.zip: the package's name isdisplay_namewith spaces as underscores, its descriptionshort_description(Thunderstore takes 250 characters), and its version the mod'sversion. -
Upload it at https://thunderstore.io/package/create/ under your team.
Snap64 Recomp has no Thunderstore community yet; until it has, a GitHub
release carrying the .nrm and the zip does the same job. Mod managers do
not support the port, so a package's page should tell players to use
Manual Download, as the other recompilations' pages do. The port's Mods page
shows short_description on one line of about 45 characters, so a short one
reads whole there.
From a clean clone, on Ubuntu 24.04 under WSL with clang and lld 18.1.3 and
a RecompModTool built from N64Recomp ffb39cd: the example builds and
packs, and the released 1.0.9 Windows executable loads it, turned on at
first sight, and writes its line at each scene. A second mod, which is not
in this repository, exercised the rest against the same executable: the
game's headers from the unbuilt decompilation checkout, a game variable by
name, a hook after a function reading its return value, a function
replaced, the replacement of a function the port itself replaces (refused,
then forced), and a manifest asking for a newer port (refused, with the
message). Not checked: the Windows and macOS tool lines above, the published
RecompModTool binary, native libraries, and dependencies between mods.
This template is GPLv3, like the port. modding.h, recomputils.h and
recompconfig.h are the files of Zelda64Recomp's
MMRecompModTemplate,
unchanged, and the Makefile and mod.ld are adapted from its own; that
template is CC0. recompdata.h declares the same functions as the other
recompilations' header of that name, which the port provides. The
decompilation this reads headers from,
ethteck/pokemonsnap, carries no
licence file; it is checked out in place rather than copied, as the other
recompilations' templates do, and nothing of the game is in this repository.