• ⚠️ Mod Release Rules now apply to this board.

    All mods must include a license, source code (for executable mods), and proper attribution.

    Read the full rules here before posting.

Oxidizer – write KSA mods in Rust or Rhai scripts (mod loader)

SafeShows

Member
Messages
18
Reaction score
6
Oxidizer is a mod loader for Kitten Space Agency, written in Rust. It loads code mods written in Rust and mods written as Rhai scripts, so you can mod the game without writing any C#. It runs on its own or as a StarMap mod, next to your C# mods.

What mods can do
  • Run code every frame
  • Draw their own ImGui windows, using the game's own ImGui
  • Read and change game objects (vehicles, sim time, warp speed, …)
  • Patch game methods before or after they run, skip the original, or change what it returns
  • React to hotkeys and keep their own settings
  • Add entries to the game's Mods menu
  • Reload Rhai scripts while the game runs
1794063949025.png

Install
Download from GitHub Releases. Both zips work on Windows and Linux, and both are in the content index, so Borea can install them for you.

Using StarMap: unzip Oxidizer.StarMap-X.Y.Z.zip into Documents/My Games/Kitten Space Agency/mods/ and start the game through StarMap as usual.

Without StarMap:
  1. Unzip Oxidizer-X.Y.Z.zip anywhere.
  2. Run oxidizer.exe (Linux: ./oxidizer) instead of the game. If KSA isn't in the default folder, set game_path in the oxidizer.toml it creates next to it.
Put mods in Documents/My Games/Kitten Space Agency/mods/. The standalone zip includes two example mods in example-mods/ to try. In game, Mods > Oxidizer shows your loaded mods, any errors and the log.

To uninstall, delete the Oxidizer (or Oxidizer.StarMap) folder. The game runs unmodded again with no further cleanup.

Writing a mod
A mod is a folder in mods/ whose mod.toml has an [oxidizer] section:
Code
name = "my_mod"

[oxidizer]
script = "main.rhai"     # Rhai mod
# dll = "bin/my_mod"     # Rust mod: keep the library in bin/
Rhai: no compiling, just a text file. Save it while the game runs and the new code takes over.
Code
fn gui(dt) {
    if begin("My mod") {
        let v = static_get("KSA.Program, KSA", "ControlledVehicle");
        text(`vehicle: ${v.get("Id")}`);
        if button("Warp x10") {
            static_call("KSA.Universe, KSA", "SetSimulationSpeed", [10.0, true]);
        }
    }
    end();
}
Rust: make a cdylib crate and cargo add oxidizer_api (crates.io, docs). Run oxidizer --bindgen src/ksa.rs once to get typed bindings for the whole game, with autocomplete and compile-time checks:
Code
mod ksa;
use ksa::ksa::{Program, Universe};
use oxidizer_api::{Mod, imgui};

struct MyMod;
impl Mod for MyMod {
    fn init() -> Self { MyMod }
    fn gui(&mut self, _dt: f64) {
        if imgui::begin("My mod") {
            let v = Program::controlled_vehicle();
            imgui::text(&format!("{} at {:.0} s", v.id(), Universe::get_elapsed_seconds()));
        }
        imgui::end();
    }
}
oxidizer_api::oxidizer_mod!(MyMod);
Keep your Rust library in a bin/ folder: StarMap tries to load mods/<id>/<id>.dll as a C# mod. The README has the full API, the list of Rhai functions, and how to list your mod in the content index.

Good to know
  • Windows, Linux and macOS (Apple Silicon, as a StarMap mod with beik's macOS port).
  • Works on its own or as a StarMap mod next to C# mods.
  • Still early (0.5.0), so the API may still change. A mod built for an older API version is skipped with a message telling you to rebuild it, not crashed.
Credits
  • Harmony by Andreas Pardeike (MIT), used for patching game methods
  • Rhai (MIT or Apache-2.0), the scripting language
  • libloading (ISC) and toml (MIT or Apache-2.0)
  • Thanks to Klaas (StarMap) and Maxi (content index) for their help
Links
License: MIT. Feedback and mod ideas welcome. Post them here or open an issue.
 
Last edited:
Oxidizer 0.2.0 is out
Download: GitHub Releases. Unzip it over your old version, or anywhere new.

What's new
  • Mods menu: Oxidizer adds a Mods menu to the game's menu bar. Mods > Oxidizer opens a window that lists your loaded mods, shows any errors in red, and has the log, so you don't need the console anymore.
  • Your own menu entries: mods can add entries to the Mods menu, for example to show or hide their window. Use fn menu() in Rhai or Mod::menu in Rust.
  • Hot reload for Rhai: save your .rhai file while the game runs and the new code takes over within a second. Your state in this is kept. If the new version has an error, the old one keeps running and the error shows in the Oxidizer window.
  • Instances: -InstancePath <folder> (or the OXIDIZER_INSTANCE_PATH environment variable) runs the game with mods, saves and settings from that folder, for separate game profiles in mod managers like Borea.
  • License notices: the zip now includes THIRD-PARTY-NOTICES.txt for the libraries Oxidizer is built with.
oxidizer-0.2.0.png

Hot reload in a Rhai mod looks like this. init() only runs once, and reload() runs after each save:
Code
fn init() { this.visible = true; }

fn reload() { print("reloaded!"); }

fn menu() {
    if menu_item("My mod", this.visible) {
        this.visible = !this.visible;
    }
}
For Rust modders
oxidizer_api 0.2.0 is on crates.io with ImGui menus, closable windows, scrolling regions and colored text. Mods built with 0.1 keep working, no rebuild needed.

Full changelog: https://github.com/SafeShows/Oxidizer/blob/master/CHANGELOG.md
 
Last edited:
Oxidizer 0.3.0: now on Linux 🐧
Download: GitHub Releases. There's Oxidizer-v0.3.0.zip for Windows and Oxidizer-v0.3.0-linux.zip for Linux.

What's new
  • Linux support: unzip, set game_path in oxidizer.toml to your KSA folder, and run ./oxidizer. Everything works the same as on Windows: Rhai and Rust mods, hot reload, the Mods menu, instances.
  • Cross-platform Rust mods: in mod.toml you can now write dll = "my_mod" without an extension. Oxidizer loads my_mod.dll on Windows and libmy_mod.so on Linux, so one mod folder can ship both.
For Rust modders
Build with oxidizer_api 0.2.1 (cargo update) if your mod should run on Linux. Mods built with 0.2.0 keep working on Windows.

Linux testers wanted
Linux support is new. I've tested it on a Linux build of KSA, but only with software rendering, so it hasn't had a proper play-through yet. If you're on Linux, I'd love to hear whether the Mods menu and the Mods > Oxidizer window work for you. Post here or open an issue.

Full changelog: https://github.com/SafeShows/Oxidizer/blob/master/CHANGELOG.md
 
Oxidizer 0.4.0: typed Rust bindings, hotkeys, settings
Download: GitHub Releases. Grab -windows.zip or -linux.zip (the plain .zip has both).

Typed Rust bindings
No more member names in strings. Run oxidizer --bindgen src/ksa.rs once, and your Rust mod gets a typed API for all of KSA, with autocomplete and compile-time checks:
Code
mod ksa;
use ksa::ksa::{Program, Universe};

let v = Program::controlled_vehicle();   // a typed Vehicle
oxidizer_api::log(&format!("{} at {:.0} s", v.id(), Universe::get_elapsed_seconds()));
Calls go to the exact game method, so overloads aren't guessed anymore. Regenerate after game updates; an outdated file tells you so instead of misbehaving.

Also new (Rhai and Rust)
  • Change what the game does: hooks can read and replace a method's return value (result() / set_result(v)).
  • Hotkeys: key_pressed("F5") and key_down("F5"). Typing in text fields or the console doesn't trigger them.
  • Settings: config_get(key, default) / config_set(key, value) save per mod, outside the mod folder so updates don't wipe them.
For Rust modders
Use oxidizer_api = "0.3" for the new features. Mods built with 0.2 keep working.

Full changelog: https://github.com/SafeShows/Oxidizer/blob/master/CHANGELOG.md
 
Oxidizer 0.5.0: now works with StarMap
Download: GitHub Releases

The biggest request so far: you no longer have to choose between StarMap and Oxidizer. Oxidizer now also comes as a StarMap mod, so your Rhai and Rust mods run side by side with StarMap's C# mods (tested next to KSAMultiplayer).

Which zip?
  • Using StarMap: unzip Oxidizer.StarMap-0.5.0.zip into Documents/My Games/Kitten Space Agency/mods/ and start the game through StarMap as usual.
  • No StarMap: Oxidizer-0.5.0.zip is the standalone loader, same as before.
Both work on Windows and Linux. Both are in the content index (Oxidizer.StarMap and Oxidizer), so Borea can install them for you.

For mod authors
  • Rust mods: put your library in a bin/ folder (dll = "bin/my_mod" in mod.toml). StarMap tries to load mods/<id>/<id>.dll as a C# mod, and a native library there stops the game.
  • Listing your mod in the content index: use [loader] id = "StarMap" plus a required dependency on Oxidizer.StarMap, so mod managers install everything your mod needs. The README has the exact snippet. Your mod still runs with standalone Oxidizer too.
Thanks to Klaas for the StarMap help and Maxi for the index advice!

Full changelog: https://github.com/SafeShows/Oxidizer/blob/master/CHANGELOG.md
 
Oxidizer 0.5.1: now on macOS
Download: GitHub Releases

Oxidizer now runs on Apple Silicon Macs, together with beik's macOS port of the game.

How to set it up
  1. Set up the macOS port, and add its StarMap with ./starmap.sh.
  2. Unzip Oxidizer.StarMap-0.5.1.zip into ~/Documents/My Games/Kitten Space Agency/mods/, the same as on Windows and Linux.
  3. If you downloaded the zip in a browser, clear the quarantine flag first, or macOS won't load the libraries:
    Code
    xattr -dr com.apple.quarantine ~/Documents/My\ Games/Kitten\ Space\ Agency/mods
Rhai and Rust mods, hooks and hot reload all work there. Rust mods ship their library as a .dylib in bin/, next to the .dll and .so. Oxidizer.StarMap is listed for macOS in the content index too.

Huge thanks to @beik for the port and for all the details that made this easy!

Full changelog: https://github.com/SafeShows/Oxidizer/blob/master/CHANGELOG.md
 
Oxidizer 0.6.0 is out - mostly for Rhai scripters.

Typed game access in Rhai
The game is now available as typed modules, built from your installed game when Oxidizer starts, so they always match your game version:
Code
let v = ksa::Program::controlled_vehicle();
print(`${v.id} at ${ksa::Universe::get_elapsed_seconds()} s`);
v.throttle = 0.5;
Numbers, bools and strings come back as plain Rhai values. Names are the game's names in snake_case. The old static_get / obj.get functions still work.

Autocomplete in your editor
On each new game version, Oxidizer writes Rhai definition files for the whole game (and Oxidizer's own functions) to Documents/My Games/Kitten Space Agency/oxidizer/definitions/. Add that folder to your editor's workspace (with a Rhai extension) and you get completion for game types and members.

Picking overloads
static_call, call and patch accept a signature to pick an exact overload: "SetSimulationSpeed(double, bool)". Without one, Oxidizer picks the overload that fits the arguments, and if it can't tell, the error lists the overloads.

Hooks between scripts
Rhai scripts no longer block each other: if one script calls a method another script hooks, the hook now runs.

Works standalone and as the StarMap mod (tested with StarMap 0.5.2, whose Mods menu merges with Oxidizer's). Rust mods need no changes.

The Rhai mod template is updated to the typed API.

Download: https://github.com/SafeShows/Oxidizer/releases/tag/v0.6.0
 
Oxidizer 0.6.1: standalone on macOS

Oxidizer now runs standalone on a Mac with the macOS port, no StarMap needed:
  1. Set up the port as usual.
  2. Unzip Oxidizer-0.6.1.zip anywhere.
  3. Run ./oxidizer-macos from that folder.
It finds the port's app in ~/Applications on its own and starts the game with the same setup the port's launcher uses, Metal hooks included. Mods go in the usual mods folder. As with the port's launcher, the game's output (Oxidizer's log too) goes to logs/console.log in the game's documents folder. In game, Mods > Oxidizer shows the log as well.

If you downloaded the zip in a browser, clear the quarantine flag first: xattr -dr com.apple.quarantine <the Oxidizer folder>

The StarMap mod (Oxidizer.StarMap) works on Mac as before.

Download: https://github.com/SafeShows/Oxidizer/releases/tag/v0.6.1
 
Oxidizer 0.7.0

New Rhai mods start while the game runs

Drop a new Rhai mod into the mods folder (or make one with an editor) and Oxidizer starts it within a second, no restart. Editing a script still reloads it on save, as before.

oxidizer_api 0.4.0 for Rust mods (on crates.io)
  • Read Oxidizer's log (log_lines) and which mods run or failed (mod_statuses).
  • Run Rhai against the live game from Rust (rhai_eval), like a console: variables stay between calls.
  • ImGui text fields (one line and multi-line), tabs, and ctrl_shortcut('s') for shortcuts that work while typing.
Mods built with 0.4.0 need Oxidizer 0.7.0; older mods keep working.

Also since the last update
  • 0.6.3: editor autocomplete for Rhai now knows the types of game object members too (get_surface_speed(obj) -> float), and the definition files refresh when Oxidizer updates.
  • 0.6.2: calling a game method by name in Rhai is obj.invoke("Method"); the old obj.call clashed with Rhai's built-in call and never worked. A method call on a property (vehicle.parent.get(...)) no longer logs a "Property set method not found" error.
  • The Rhai engine now starts only when something needs it, so a game without Rhai mods skips its startup cost.
Made with it: Rhai Studio
These features power Rhai Studio, an in-game editor for Rhai mods: syntax colors, save to hot-reload, make a new mod from inside the game, and a console that runs Rhai against your current flight. It gets its own thread.

Download: https://github.com/SafeShows/Oxidizer/releases/tag/v0.7.0
 
Oxidizer 0.8.0 + a modding guide

New: the Oxidizer modding guide
https://safeshows.github.io/Oxidizer/
Step-by-step guides for Rhai and Rust mods, a full API reference for both, and a searchable Game API page: every type and member of the game, with its C# signature, its Rust binding and how to call it from Rhai.

Dependencies and load order
Mods can now say what they need in mod.toml:
Code
[oxidizer]
script = "main.rhai"
version = "1.2.0"
dependencies = [
    { id = "weather_core", min = "1.0", max = "1.9" },
    { id = "fancy_ui", optional = true },
]
Mods start after their dependencies, Rust and Rhai mods in one shared order. A mod whose dependency is missing or the wrong version doesn't start, and Mods > Oxidizer says why. A Rhai mod waiting for a dependency starts by itself as soon as it's installed.

loaded()
A new callback once the game has loaded its content and mods (the same moment as StarMap's AllModsLoaded): fn loaded() in Rhai, Mod::loaded in Rust. init() still runs earlier, before the game starts.

More UI for Rhai
Text fields (input_text, input_multiline), number fields, tabs, colored text, scrolling regions, window sizes and Ctrl shortcuts.

Also
  • oxidizer --bindgen game.json writes the game's API as data (that's what the Game API page is built from).
  • Rust: oxidizer_api 0.5 is on crates.io. Mods built with it need Oxidizer 0.8; older mods keep working.
  • The Rhai and Rust mod templates are updated: version and dependencies in mod.toml, set from your release tag.
About today's game update (2026.11.10.5801)
Standalone Oxidizer kept working through it. StarMap needed an update for it: if you use Oxidizer.StarMap, update StarMap to 0.5.3. Both 0.7.0 and 0.8.0 are tested with it.

Download: https://github.com/SafeShows/Oxidizer/releases/tag/v0.8.0
 
Oxidizer 0.9.0: events and services between mods

Mods can now talk to each other, Rust and Rhai alike, without depending on each other's code.

Events: publish a value on a topic, and every subscribed mod gets it at the start of the next frame.
Services: provide a named function, and other mods call it and get a value back.
Code
// Rhai
subscribe("weather_core.weather", "on_weather");
let sum = call_service("my_mod.add", #{ a: 2.0, b: 3.0 });

// Rust: typed with serde
oxidizer_api::subscribe(|w: Weather| log(&format!("{w:?}")))?;
let sum = oxidizer_api::call::<Add>(&AddArgs { a: 2.0, b: 3.0 })?;
  • Typed in Rust: events and services are your own structs and enums. A Rhai map with the same fields is the same value.
  • Retained topics: publish_retained keeps the current value, so mods that subscribe later still get it.
  • Names belong to their mod: only weather_core can publish on weather_core.*. Anyone can listen or call.
  • Safe by default: a broken subscriber stops only itself. A script calling back into itself gets a "busy" error instead of hanging. Subscriptions survive hot reload.
  • Mods > Oxidizer > Event bus shows topics, subscribers, retained values, services and recent events.
Rust mods need oxidizer_api = "0.6". Older mods keep working.

Guide: https://safeshows.github.io/Oxidizer/events/
Download: https://github.com/SafeShows/Oxidizer/releases/tag/v0.9.0
Examples (a Rust mod and a Rhai mod talking both ways): https://github.com/SafeShows/Oxidizer/tree/master/examples
 
Back
Top