Oxidizer
A mod loader for Kitten Space Agency, written in Rust. Mods can be written in Rust (cdylib) or as Rhai scripts.
KSA is a .NET 10 game, so the loader:
- loads the game's own
hostfxr.dlland starts its runtime withKSA.dll, - injects
bridge/(a small C# DLL plus Harmony), which hooksProgram.OnFrameandProgram.OnDrawUiViewportsand gives mods reflection access to game objects, - loads the mods, then runs the game's
Main.
Build
Run
On first run without --game, the loader creates oxidizer.toml next to the exe (game_path = '...'). It defaults to C:\Program Files\Ahwoo\Games\Kitten Space Agency.
Mods
A mod is a folder in Documents/My Games/Kitten Space Agency/mods/<id>/ whose mod.toml has a [oxidizer] section:
= "my_mod"
[]
= "my_mod.dll" # Rust mod
= "main.rhai" # and/or Rhai script
Mods set enabled = false in KSA's manifest.toml are skipped. See examples/ for working mods. To install one, copy its folder into mods/; for a Rust mod, also copy in the built .dll.
Rust
The crate is a cdylib that depends on oxidizer_api:
use ;
;
oxidizer_mod!;
Rhai
A script can define init(), frame(t, dt), gui(dt), and unload(). Keep state in this (an object map).
| Area | Functions |
|---|---|
| Game objects | static_get(type, member), static_set(type, member, v), static_call(type, method[, args]), obj.get(m), obj.set(m, v), obj.call(m[, args]), obj.float(), obj.is_null(), obj.len(), obj[i], obj.to_string() |
| Patching | patch(type, method, "prefix"|"postfix", "fn_name"). The callback is fn_name(instance, args); returning false from a prefix skips the original method. |
| ImGui | begin(title) / end(), text, button, checkbox(label, v) -> v, slider(label, v, min, max) -> v, same_line, separator, header |
| Logging | print(...) |
Notes and limits
- Members are looked up by name through reflection. Type names can be assembly-qualified (
"KSA.Universe, KSA"), which works even before the game has loaded that type. callpicks the first overload with a matching argument count and converts arguments to the parameter types.- Patching uses the first overload of the method. Hooks receive
__instanceand__args, but not__result. - A Rhai script that throws an error is disabled, so the same error isn't logged every frame. Errors from reflection calls are logged once each.
ref structmembers (for exampleReadOnlySpanproperties such asProgram.VehiclesInFrame) can't be read through reflection.