Getting started¶
Where mods/ lives¶
| Install | mods/ location |
|---|---|
| Windows | Next to openpete-spyro1.exe |
Linux .tar.xz, AppImage |
~/.local/share/openpete/mods/ ($XDG_DATA_HOME/openpete/mods/ when set) |
portable = true at the top level of openpete.toml |
Next to the executable, when that directory is writable |
On Linux a writable mods/ directory already present next to the
executable is used in place of the per-user path. --mods-dir DIR or
OPENPETE_MODS_DIR overrides every location above.
Enabling mods¶
mods/enabled.txt lists enabled mod IDs, one per line; # starts a
comment. Without the file every mod in the directory loads, in
lexicographic order. Order is priority: on a function hooked by several
mods, the mod listed later runs first and base() descends toward earlier
mods, then the original game code.
--mods a,b or OPENPETE_MODS=a,b replaces the list for one run.
--mods "" runs without mods.
The in-game settings overlay (opened with the M key, "the M overlay" below) has a Mods section that toggles a loaded mod at the next tick boundary and edits its settings. It does not reorder mods.
A distribution packaged with the SDK carries working examples under
<exe>/sdk/examples/<id>/; copy one into your mods directory to run
it. The Examples page lists them first.
Directory layout¶
| Path | Contents |
|---|---|
mod.toml |
Manifest. See Manifest. |
config.toml |
Optional player-editable settings, read through api->config_*. |
src/*.c |
C sources, compiled on load against <exe>/sdk/. |
shaders/*.vert, *.frag |
GLSL, compiled on load. See Shaders. |
assets/ |
Overlay root: textures/, music/, sfx/, wad/, levels/. See Textures and music, Sounds, Levels. |
data/ |
Per-mod persistent directory, created on first api->data_dir() call. |
.build/ |
The engine's compile cache. Not part of a release. |
An <id>.zip with mod.toml at the archive root loads the same way: the
engine unpacks it under mods/.unpacked/<id>/ and skips the unpack while
the zip's hash is unchanged. A folder and a zip with the same ID resolve
to the folder.
A declarative mod¶
Assets are picked up by file convention, with no manifest block:
assets/music/track_NN.wavreplaces music track NN.assets/textures/<name>.pngreplaces a texture in the native renderer.assets/sfx/<name>.wavreplaces a sound effect.
Manifest blocks cover what files alone cannot: [[level]] wires a custom
level, [[material]] re-shades geometry the engine draws, [[config]]
declares settings the engine renders, [[binding]] declares a
rebindable key. See Manifest.
A code mod¶
A code mod compiles on the player's machine. The Windows archive and the
Linux AppImage bundle the compiler; the Linux .tar.xz uses cc on
PATH, or the compiler named by OPENPETE_CC.
-
mods/hello/mod.toml:[mod] id = "hello" name = "Hello" version = "0.1.0" api = 1 sim_mutative = false # never writes guest RAM -
mods/hello/src/hello.c:#include <openpete_mod_api.h> static const openpete_mod_api_t* g_api; static openpete_mod_t* g_self; static unsigned calls; static void on_camera_update(CPUState* cpu) { if ((++calls % 300u) == 0u) /* every ~10 s at 29.913 ticks/s */ g_api->log(g_self, OP_MOD_LOG_INFO, "CameraUpdate call #%u", calls); g_api->base(cpu); } int openpete_mod_entry(const openpete_mod_api_t* api, openpete_mod_t* self) { g_api = api; g_self = self; return api->override_name(self, "CameraUpdate", on_camera_update); } -
Add
hellotomods/enabled.txtand launch.
The engine hashes the sources, compiles them when the hash changed (a
"COMPILING MODS" splash stage), loads the object, and runs
openpete_mod_entry. A compile failure disables the mod and writes the
error to mods/hello/.build/compile.log; the game still starts. A
non-zero return from the entry point disables the mod.
api = N is the minimum engine API version the mod needs. The loader
refuses a mod whose pin exceeds the engine's version before compiling it.
The API struct is append-only, so a newer engine always satisfies an older
pin. Never compare api->api_version with !=; guard a single late entry
with if (api->api_version >= N).
Ways to hook a function¶
- By name.
api->override_name(self, "CameraUpdate", fn). Names come from the function reference and survive engine updates. - Through a prototype. Functions with a natural signature get typed
wrappers in
openpete_sdk_wrappers.h:OP_OVERRIDE_IMPL_Foo,op_base_Foo(cpu, ...),op_call_Foo(cpu, ...), with guest-to-host pointer marshalling. - By address.
api->override_addr(self, 0x8003xxxx, fn). Arguments are incpu->a0..a3, the return value incpu->v0; the register fields need#include <psx_runtime.h>(insdk/), since the API header only forward-declaresCPUState. Addresses are pinned to the SCUS-94228 binary; the loader logs a warning on a mod's first raw registration.