DeltaLoaderDocs
  1. Home
  2. API Wiki
  3. Recipes

Recipes

Short, complete patterns that only use calls from the public headers.

Each recipe only uses calls that exist in the headers of the SDK. Calls that talk to the game return false or nullptr when the game build does not match, so always check the result.

1. The smallest mod#

cpp
#include "sw/mod.hpp"

SW_MOD("my_mod")

void sw_mod_start() {
    sw::mod::log("started");
    while (sw::mod::sleep(1000)) {      // false as soon as the mod is unloaded
        // work once a second
    }
}
void sw_mod_stop() {}

sw_mod_start() runs on its own thread. A loop must check sw::mod::sleep() or sw::mod::running(), otherwise unloading waits for it.

2. Hook a game function and count calls#

cpp
#include "sw/mod.hpp"
#include <atomic>

SW_MOD("counter")
static std::atomic<int> g_calls{0};

static void on_call(void* self, void* user) { ++g_calls; }   // keep it short

void sw_mod_start() {
    std::string err;
    auto status = sw::mod::rt().hook_api("server.getTime", on_call, nullptr, err);
    if (status != sw::HookStatus::ok) {
        sw::mod::log("hook failed: %s (%s)", sw::to_string(status), err.c_str());
        return;
    }
    while (sw::mod::sleep(5000)) sw::mod::log("calls: %d", g_calls.load());
}
void sw_mod_stop() {}

Other entry points: hook_ctor(class, ...), hook_method(class, slot, ...), hook_rva(rva, ...). A callback reads integer arguments with sw::Hook::arg(n) and cannot change them. Hooks are removed when the mod unloads.

3. Find living objects of a class#

cpp
std::string err;
sw::Instances::track("c_scene_ropes", err);                 // once, in sw_mod_start
for (auto obj : sw::Instances::list("c_scene_ropes")) {      // later, any time
    sw::mod::log("rope object at %p", (void*)obj);
}

4. Weather and time#

cpp
#include "sw/mod.hpp"
#include "sw/weather.hpp"

SW_MOD("storm")
void sw_mod_start() {
    if (!sw::weather::watch()) { sw::mod::log("weather: game does not match"); return; }
    while (!sw::weather::ready() && sw::mod::sleep(500)) {}   // waits until the environment object was seen
    sw::weather::set_weather(0.8f, 0.9f, 0.7f);                // fog, rain, wind (local only)
    sw::weather::TimeOfDay t;
    if (sw::weather::time_of_day(t)) sw::mod::log("it is %02d:%02d", t.hour, t.minute);
    while (sw::mod::sleep(1000)) {}
}
void sw_mod_stop() {}

5. Set a shader uniform each frame#

cpp
#include "sw/mod.hpp"
#include "sw/render.hpp"

SW_MOD("uniform_demo")
static void on_frame(void* window, void* user) {          // render thread, GL context current
    float v[3] = {1.f, 0.5f, 0.f};
    sw::render::set_uniform("my_color", v, 3);               // every program that declares my_color
}
void sw_mod_start() {
    if (!sw::render::watch()) return;
    sw::render::on_frame_end(on_frame, nullptr);
    while (sw::mod::sleep(1000)) {}
}
void sw_mod_stop() { sw::render::remove_callbacks(); }

A uniform belongs to a program object and keeps its value. Declare it in a shader of your own mod folder (Graphics\shaders, which needs the overlay start of the launcher); the game only sets the uniforms its own shader classes know.

6. Tiles and a start card (the SDK example)#

cpp
/* Hello Reader! If you're reading this, it means that you are interested in our modding system! This is written by Shayans, 
(Pretty much the only thing I did for the DeltaLoader, my main job was the launcher and the site). Oh and I also wrote the STEX better space code for Argon spawning.
I'm pretty much at the beginners level of C++. This is why basically this entire repository was written by Malionaro.
So let's just start!
*/
#include "sw/mod.hpp"
#include "sw/tiles.hpp" // If you want custom tiles
#include "sw/start_tiles.hpp"

#include <string>
using std::string;

// Here we can define the name of our mod, this is especially interesting for the log.
SW_MOD("delta_example")

// If we want custom tiles, we should create a function that's called after the game is done setting the vanilla tiles:
static void on_tiles_placed(const sw::tiles::EventInfo& info, void* user) { // These parameters are wanted by EventCallback, but we don't need them
    for (const sw::tiles::Cell& cell : sw::tiles::cells()) { // So that our island doesn't spawn multiple times (I used to have a horrible fix for this)
        if (cell.name == "data/tiles/argon_island.xml"){return;}
    }

    string error; // If there's an error, it's written inside of this string
    if (!sw::tiles::place_random("argon_island.xml", 0, 0, 10, 15, error)) { // Second parameter: X-Center of where the tile radius is, Third: Y-Center of where the tile radius is, Fourth parameter: Minimum radius of where it can spawn
        sw::mod::log("customtile was not placed: %s", error.c_str()); // You need the %s to properly display the error
    }
    /*
    if (!sw::tiles::place("customtile.xml", 10, 10, error)) { // This would place a fixed tile at the coordinates 10.000; 10.000
        sw::mod::log("customtile was not placed: %s", error.c_str());
    }
    */
}
// This function is called on the start of your mod
void sw_mod_start() {
    // This is a simple log output.
    sw::mod::log("DeltaLoader mod loaded.");

    if (!sw::tiles::watch()) { // watch() from tiles.hpp has to be called to setup the tile management
        return;
    }

    if (!sw::start_tiles::watch()) { //Same thing goes for here, you need this to do this start "card" in the menu for a base
        return;
    }
    string error; // Hope you didn't forget what this was
    if (!sw::tiles::on(sw::tiles::Event::tiles_placed, &on_tiles_placed, nullptr, error)) { // This listens for the event that the game placed all of it's own tiles and then calls on_tiles_placed, and we don't need a user here.
        sw::mod::log("Something is wrong, can't listen for the tile placed event: %s", error.c_str()); // Because we love logging
        return;
    }

    sw::start_tiles::Base customBase; // We need a base object
    customBase.key = "argon_island"; // This grabs the tile xml and the map geometry, so set it to whatever your tile name is (without the file extension!)
    customBase.title = "Running Out Of Ideas"; // This is the display name of the base in the menu
    customBase.description = "Lorem Ipsum Dolor Sit Amet."; // This is the description of the base in the menu
    string errorBase; // Again, to write any errors inside this string if there are any
    if (!sw::start_tiles::add_base(customBase, errorBase)) {
        sw::mod::log("Could not add the base to the start things: %s", errorBase.c_str());
    }

    // There are other things I could explain now but I'm tired and many of the other things (like flags, menu icons and stuff like that) are broken right now
    // So you shouldn't use them anyway. (Except if you're a genius that's able to fix them)
    
    // But I will update this with future updates of the source code and this project in general.

    // Oh, and don't forget to update CMakeLists.txt!
}

// Called when you stop the game
void sw_mod_stop() {
    // Do whatever you want here, most of the time it just doesn't matter.
}

The folder name of a tile is the name without the extension. Never place a tile for a start card from a timer or after the world was built: wait for the tiles_placed event, as the example does.

7. Rules that prevent most crashes#

  • Check every return value; a mismatching game build returns false or nullptr and nothing else happens.
  • Callbacks run on game threads. Keep them short; hand slow work to the mod thread.
  • Use the engine allocator for objects the game frees; never malloc them.
  • Never write game memory you cannot name. write_bytes and write_rva exist but are for single-player experiments.
  • After a game update mods stop on purpose until the SDK is updated.

Documentation for game build 1.15.26. Found a mistake? Open an issue.