mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-10 04:46:48 -04:00
modding.md, test mod, template mod, better imgui context
This commit is contained in:
Vendored
+1
-1
@@ -6,7 +6,7 @@
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"program": "${command:cmake.launchTargetPath}",
|
||||
"args": ["-l", "1", "--dvd", "${workspaceRoot}/orig/GZ2E01/GZ2E01.iso", "--console", "--mods", "${workspaceRoot}/tools/mod_template/mods"],
|
||||
"args": ["-l", "1", "--dvd", "${workspaceRoot}/orig/GZ2E01/GZ2E01.iso", "--console", "--mods", "${workspaceRoot}/mods"],
|
||||
"MIMode": "gdb",
|
||||
"miDebuggerPath": "gdb",
|
||||
"symbolSearchPath": "${command:cmake.launchTargetPath}",
|
||||
|
||||
@@ -7,7 +7,8 @@ function(add_dusk_mod target_name)
|
||||
message(FATAL_ERROR "add_dusk_mod: MOD_JSON is required")
|
||||
endif()
|
||||
|
||||
add_library(${target_name} SHARED ${ARG_SOURCES})
|
||||
add_library(${target_name} SHARED ${ARG_SOURCES}
|
||||
"${CMAKE_CURRENT_FUNCTION_LIST_DIR}/dusk_imgui_ctx.cpp")
|
||||
set_target_properties(${target_name} PROPERTIES PREFIX "" WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
target_compile_features(${target_name} PRIVATE cxx_std_20)
|
||||
target_link_libraries(${target_name} PRIVATE dusk_game_headers)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#include "imgui.h"
|
||||
|
||||
extern "C" void dusk_mod_set_imgui_ctx(void* ctx) {
|
||||
ImGui::SetCurrentContext(static_cast<ImGuiContext*>(ctx));
|
||||
}
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
# Dusk Mod API
|
||||
|
||||
Mods are shared libraries packaged into a `.dusk` zip archive. The loader scans the `mods/` directory at startup, extracts each library, and calls your exports each frame.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Getting Started](#getting-started)
|
||||
2. [mod.json](#modjson)
|
||||
3. [Required Exports](#required-exports)
|
||||
4. [DuskModAPI Reference](#duskmodapi-reference)
|
||||
5. [Logging](#logging)
|
||||
6. [Loading Resources](#loading-resources)
|
||||
7. [ImGui Integration](#imgui-integration)
|
||||
8. [Hooking Game Functions](#hooking-game-functions)
|
||||
- [Pre-hooks](#pre-hooks)
|
||||
- [Post-hooks](#post-hooks)
|
||||
- [Replace hooks](#replace-hooks)
|
||||
- [Reading and writing arguments](#reading-and-writing-arguments)
|
||||
- [Raw C hook API](#raw-c-hook-api)
|
||||
9. [Full Example](#full-example)
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
Fork the [mod template](../tools/mod_template/), it is a self-contained CMake project that references dusk as a subdirectory.
|
||||
|
||||
```
|
||||
my_mod/
|
||||
├── CMakeLists.txt
|
||||
├── mod.json
|
||||
├── src/mod.cpp
|
||||
└── res/ (optional bundled resources)
|
||||
```
|
||||
|
||||
**CMakeLists.txt:**
|
||||
|
||||
```cmake
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
project(my_mod CXX)
|
||||
|
||||
set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dusk" CACHE PATH "Path to dusk source root")
|
||||
add_subdirectory("${DUSK_DIR}" dusk EXCLUDE_FROM_ALL)
|
||||
|
||||
add_dusk_mod(my_mod
|
||||
SOURCES src/mod.cpp
|
||||
MOD_JSON mod.json
|
||||
RES_DIR res # optional
|
||||
)
|
||||
```
|
||||
|
||||
After building, `my_mod.dusk` is placed in `mods/` next to the project root (`DUSK_MODS_OUTPUT_DIR` cache variable). Copy it to the game's `mods/` folder and launch.
|
||||
|
||||
The `.dusk` archive is a standard zip containing `mod.json`, the compiled library, and an optional `res/` tree. `add_dusk_mod()` creates it automatically.
|
||||
|
||||
---
|
||||
|
||||
## mod.json
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "My Mod",
|
||||
"version": "1.0.0",
|
||||
"author": "Your Name",
|
||||
"description": "A short description shown in the mod manager."
|
||||
}
|
||||
```
|
||||
|
||||
All fields are optional but recommended. `name` falls back to the filename, `version` to `"?"`.
|
||||
|
||||
---
|
||||
|
||||
## Required Exports
|
||||
|
||||
```cpp
|
||||
#include "dusk/mod_api.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void mod_init (DuskModAPI* api); // required — called once at startup
|
||||
void mod_tick (DuskModAPI* api); // required — called every frame
|
||||
void mod_cleanup(DuskModAPI* api); // optional — called on shutdown
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## DuskModAPI Reference
|
||||
|
||||
The `api` pointer is valid for the lifetime of the mod. When using `hook.hpp`, call `dusk::init(api)` once and `dusk::g_api` is set for you.
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `api_version` | ABI version, check against `DUSK_MOD_API_VERSION` if needed |
|
||||
| `mod_dir` | Absolute path to the extracted mod cache directory |
|
||||
| `log_info` / `log_warn` / `log_error` | `printf`-style logging, prefixed with the mod name |
|
||||
| `load_resource` / `free_resource` | Load files from the `res/` tree in the `.dusk` archive |
|
||||
| `register_tab_content` | Add a panel to the mod manager's per-mod tab |
|
||||
| `register_menu_item` | Add an item to the quick-access menu |
|
||||
| `hook_install` / `hook_pre` / `hook_post` / `hook_replace` | Low-level hook registration (see [Raw C hook API](#raw-c-hook-api)) |
|
||||
| `hook_dispatch_pre` / `hook_dispatch_post` | Called by the trampoline, do not call directly |
|
||||
|
||||
---
|
||||
|
||||
## Logging
|
||||
|
||||
```cpp
|
||||
api->log_info("Player health: %d", hp);
|
||||
api->log_warn("Something looks wrong");
|
||||
api->log_error("Fatal: %s", msg);
|
||||
```
|
||||
|
||||
Output appears in the dusk console as `[My Mod] ...`
|
||||
|
||||
The format string is `printf`-compatible.
|
||||
|
||||
---
|
||||
|
||||
## Loading Resources
|
||||
|
||||
```cpp
|
||||
size_t size = 0;
|
||||
void* data = api->load_resource("config.txt", &size);
|
||||
if (data) {
|
||||
std::string text(static_cast<char*>(data), size);
|
||||
api->free_resource(data);
|
||||
}
|
||||
```
|
||||
|
||||
- Path is relative to `res/`, pass `"config.txt"` not `"res/config.txt"`
|
||||
- Always call `free_resource`, the buffer is owned by miniz
|
||||
- For writable storage, write files under `api->mod_dir`
|
||||
|
||||
---
|
||||
|
||||
## ImGui Integration
|
||||
|
||||
**Tab content:** shown in the mod's panel in the Mods window, called every frame while visible:
|
||||
|
||||
```cpp
|
||||
static void DrawPanel(void* userdata) {
|
||||
ImGui::Text("Hello!");
|
||||
}
|
||||
api->register_tab_content(DrawPanel, nullptr);
|
||||
```
|
||||
|
||||
Pass a pointer through `userdata` if your callback needs state:
|
||||
|
||||
```cpp
|
||||
api->register_tab_content(DrawPanel, &g_state);
|
||||
```
|
||||
|
||||
**Menu items:** added to the quick-access menu. Use `ImGui::MenuItem`, `ImGui::Separator`, etc.:
|
||||
|
||||
```cpp
|
||||
static void DrawMenuEntry(void*) {
|
||||
if (ImGui::MenuItem("Reset rotation")) { ... }
|
||||
}
|
||||
api->register_menu_item(DrawMenuEntry, nullptr);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hooking Game Functions
|
||||
|
||||
Call `dusk::init(api)` first.
|
||||
|
||||
```cpp
|
||||
#include "dusk/hook.hpp"
|
||||
|
||||
extern "C" void mod_init(DuskModAPI* api) {
|
||||
dusk::init(api);
|
||||
dusk::hookAddPre<&ClassName::Method>(callback);
|
||||
}
|
||||
```
|
||||
|
||||
The trampoline is installed once per address. Multiple mods can register pre/post callbacks for the same function independently.
|
||||
|
||||
### Pre-hooks
|
||||
|
||||
Run before the original. Return `0` to let it proceed, non-zero to cancel it. Post-hooks still run either way.
|
||||
|
||||
```cpp
|
||||
static int32_t on_posMove_pre(void* args) {
|
||||
daAlink_c* link = dusk::arg<daAlink_c*>(args, 0); // this
|
||||
if (link->shape_angle.y > 10000)
|
||||
return 1; // cancel
|
||||
return 0;
|
||||
}
|
||||
dusk::hookAddPre<&daAlink_c::posMove>(on_posMove_pre);
|
||||
```
|
||||
|
||||
### Post-hooks
|
||||
|
||||
Run after the original (or replace-hook).
|
||||
|
||||
```cpp
|
||||
static void on_posMove_post(void* args) {
|
||||
daAlink_c* link = dusk::arg<daAlink_c*>(args, 0);
|
||||
dusk::g_api->log_info("New Y angle: %d", (int)link->shape_angle.y);
|
||||
}
|
||||
dusk::hookAddPost<&daAlink_c::posMove>(on_posMove_post);
|
||||
```
|
||||
|
||||
### Replace hooks
|
||||
|
||||
Completely substitutes the original. Only one replace-hook per function, a second install overwrites with a warning.
|
||||
|
||||
```cpp
|
||||
static void on_posMove_replace(void* args) {
|
||||
daAlink_c* link = dusk::arg<daAlink_c*>(args, 0);
|
||||
link->shape_angle.y += 100;
|
||||
}
|
||||
dusk::hookSetReplace<&daAlink_c::posMove>(on_posMove_replace);
|
||||
```
|
||||
|
||||
To call the original from inside a replace-hook:
|
||||
|
||||
```cpp
|
||||
using Entry = dusk::HookEntry<&daAlink_c::posMove>;
|
||||
|
||||
static void on_posMove_replace(void* args) {
|
||||
daAlink_c* link = dusk::arg<daAlink_c*>(args, 0);
|
||||
link->shape_angle.y = 0;
|
||||
Entry::g_orig(link);
|
||||
}
|
||||
```
|
||||
|
||||
### Reading and writing arguments
|
||||
|
||||
`args` is a `void*[N]` array. Index `0` is `this`, subsequent indices are parameters in declaration order.
|
||||
|
||||
```cpp
|
||||
T value = dusk::arg <T>(args, n); // copy
|
||||
T& ref = dusk::argRef<T>(args, n); // reference (read/write)
|
||||
```
|
||||
|
||||
**Example:** halve incoming damage
|
||||
|
||||
```cpp
|
||||
// void daEnemy_c::takeDamage(int amount, daActor_c* source)
|
||||
static int32_t on_takeDamage_pre(void* args) {
|
||||
dusk::argRef<int>(args, 1) /= 2;
|
||||
return 0;
|
||||
}
|
||||
dusk::hookAddPre<&daEnemy_c::takeDamage>(on_takeDamage_pre);
|
||||
```
|
||||
|
||||
For reference parameters (e.g. `const cXyz& pos`), use `argRef<cXyz>` to get a direct reference.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Full Example
|
||||
|
||||
```cpp
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#include "dusk/hook.hpp"
|
||||
#include "dusk/mod_api.h"
|
||||
#include "imgui.h"
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
|
||||
static int g_ticks = 0;
|
||||
|
||||
static int32_t on_posMove_pre(void* args) {
|
||||
daAlink_c* link = dusk::arg<daAlink_c*>(args, 0);
|
||||
if (mDoCPd_c::getHoldR(PAD_1)) {
|
||||
link->shape_angle.y -= 2048;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void DrawPanel(void*) {
|
||||
daAlink_c* link = daAlink_getAlinkActorClass();
|
||||
ImGui::Text("Ticks: %d", g_ticks);
|
||||
if (link) {
|
||||
ImGui::Text("Y angle: %d", (int)link->shape_angle.y);
|
||||
if (ImGui::Button("Reset rotation")) {
|
||||
link->shape_angle.y = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawMenuEntry(void*) {
|
||||
daAlink_c* link = daAlink_getAlinkActorClass();
|
||||
if (ImGui::MenuItem("Reset rotation", nullptr, false, link != nullptr)) {
|
||||
link->shape_angle.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void mod_init(DuskModAPI* api) {
|
||||
dusk::init(api);
|
||||
dusk::hookAddPre<&daAlink_c::posMove>(on_posMove_pre);
|
||||
api->register_tab_content(DrawPanel, nullptr);
|
||||
api->register_menu_item(DrawMenuEntry, nullptr);
|
||||
}
|
||||
|
||||
void mod_tick(DuskModAPI* api) {
|
||||
++g_ticks;
|
||||
}
|
||||
|
||||
void mod_cleanup(DuskModAPI* api) {
|
||||
api->log_info("Unloaded after %d ticks.", g_ticks);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,2 +0,0 @@
|
||||
This text has been loaded from the mods resources!
|
||||
Press R to rotate Link!
|
||||
@@ -1,80 +1,18 @@
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#include "dusk/hook.hpp"
|
||||
#include "dusk/mod_api.h"
|
||||
#include "imgui.h"
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
static int TickCount = 0;
|
||||
static std::string TextContents;
|
||||
|
||||
static int32_t on_posMove_pre(void* args) {
|
||||
if (!mDoCPd_c::getHoldR(PAD_1))
|
||||
return 0;
|
||||
daAlink_c* link = dusk::arg<daAlink_c*>(args, 0);
|
||||
link->shape_angle.y -= 2048;
|
||||
dusk::g_api->log_info("ROTATING %d", link->shape_angle.y);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void DrawTabContent(void*) {
|
||||
daAlink_c* link = daAlink_getAlinkActorClass();
|
||||
if (link) {
|
||||
ImGui::Text("Y angle: %d", (int)link->shape_angle.y);
|
||||
ImGui::Spacing();
|
||||
if (ImGui::Button("Reset rotation")) {
|
||||
link->shape_angle.y = 0;
|
||||
}
|
||||
}
|
||||
if (!TextContents.empty()) {
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted(TextContents.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawMenuItem(void*) {
|
||||
if (ImGui::MenuItem("Reset rotation")) {
|
||||
daAlink_c* link = daAlink_getAlinkActorClass();
|
||||
if (link) {
|
||||
link->shape_angle.y = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void dusk_mod_set_imgui_ctx(void* ctx) {
|
||||
ImGui::SetCurrentContext(static_cast<ImGuiContext*>(ctx));
|
||||
}
|
||||
|
||||
void mod_init(DuskModAPI* api) {
|
||||
api->log_info("Test Mod initializing...");
|
||||
|
||||
dusk::init(api);
|
||||
dusk::hookAddPre<&daAlink_c::posMove>(on_posMove_pre);
|
||||
|
||||
size_t size = 0;
|
||||
void* data = api->load_resource("text.txt", &size);
|
||||
if (data) {
|
||||
TextContents.assign(static_cast<char*>(data), size);
|
||||
api->free_resource(data);
|
||||
api->log_info("Loaded text.txt (%zu bytes)", size);
|
||||
} else {
|
||||
api->log_warn("Failed to load text.txt");
|
||||
}
|
||||
|
||||
api->register_tab_content(DrawTabContent, nullptr);
|
||||
api->register_menu_item(DrawMenuItem, nullptr);
|
||||
api->log_info("Test Mod ready. Mod folder: %s", api->mod_dir);
|
||||
}
|
||||
|
||||
void mod_tick(DuskModAPI* api) {
|
||||
++TickCount;
|
||||
(void)api;
|
||||
}
|
||||
|
||||
void mod_cleanup(DuskModAPI* api) {
|
||||
api->log_info("Test Mod unloading after %d ticks.", TickCount);
|
||||
(void)api;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
project(mod_test CXX)
|
||||
|
||||
set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root")
|
||||
add_subdirectory("${DUSK_DIR}" dusk EXCLUDE_FROM_ALL)
|
||||
|
||||
set(DUSK_MODS_OUTPUT_DIR "${CMAKE_SOURCE_DIR}/mods" CACHE PATH "Directory to write .dusk packages into")
|
||||
|
||||
add_dusk_mod(mod_test
|
||||
SOURCES src/mod.cpp
|
||||
MOD_JSON mod.json
|
||||
RES_DIR res
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "API Test Mod",
|
||||
"version": "1.0.0",
|
||||
"author": "dusk",
|
||||
"description": "Exercises every feature of the Dusk mod API."
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Hello from the mod archive!
|
||||
@@ -0,0 +1,154 @@
|
||||
// Tests every feature of the Dusk mod API. Results shown in the mod tab.
|
||||
|
||||
#include "d/actor/d_a_alink.h"
|
||||
#include "dusk/hook.hpp"
|
||||
#include "dusk/mod_api.h"
|
||||
#include "imgui.h"
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
static int g_ticks = 0;
|
||||
static bool g_pre_fired = false;
|
||||
static bool g_post_fired = false;
|
||||
static bool g_replace_fired = false;
|
||||
static bool g_arg_write_ok = false;
|
||||
static int g_pre_cancel_count = 0;
|
||||
static int g_post_count = 0;
|
||||
static bool g_resource_ok = false;
|
||||
static std::string g_resource_text;
|
||||
static char g_mod_dir_snippet[64] = {};
|
||||
|
||||
// Pre-hook on posMove. Hold L to test argRef write and cancellation.
|
||||
static int32_t on_posMove_pre(void* args) {
|
||||
g_pre_fired = true;
|
||||
if (mDoCPd_c::getHoldL(PAD_1)) {
|
||||
dusk::argRef<daAlink_c*>(args, 0)->shape_angle.y = 0;
|
||||
g_arg_write_ok = true;
|
||||
++g_pre_cancel_count;
|
||||
return 1; // cancel
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Post-hook on posMove. Fires even when the pre-hook cancelled.
|
||||
static void on_posMove_post(void* args) {
|
||||
g_post_fired = true;
|
||||
++g_post_count;
|
||||
(void)args;
|
||||
}
|
||||
|
||||
// Replace-hook on execute. Calls through to the original so gameplay is unaffected.
|
||||
using ExecuteEntry = dusk::HookEntry<&daAlink_c::execute>;
|
||||
static void on_execute_replace(void* args) {
|
||||
g_replace_fired = true;
|
||||
ExecuteEntry::g_orig(dusk::arg<daAlink_c*>(args, 0));
|
||||
}
|
||||
|
||||
static void DrawPanel(void*) {
|
||||
auto status = [](const char* label, bool ok) {
|
||||
ImGui::TextColored(ok ? ImVec4(0, 1, 0, 1) : ImVec4(1, 0.35f, 0.35f, 1),
|
||||
ok ? "[PASS]" : "[WAIT]");
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("%s", label);
|
||||
};
|
||||
|
||||
ImGui::SeparatorText("Hooks");
|
||||
status("pre-hook fired (posMove)", g_pre_fired);
|
||||
status("post-hook fired (posMove)", g_post_fired);
|
||||
status("replace-hook fired (execute)", g_replace_fired);
|
||||
status("argRef write + pre cancel (hold L)", g_arg_write_ok);
|
||||
ImGui::Text(" pre cancels: %d post calls: %d", g_pre_cancel_count, g_post_count);
|
||||
|
||||
ImGui::SeparatorText("Resources");
|
||||
status("load_resource (hello.txt)", g_resource_ok);
|
||||
if (!g_resource_text.empty())
|
||||
ImGui::TextWrapped(" \"%s\"", g_resource_text.c_str());
|
||||
|
||||
ImGui::SeparatorText("API Fields");
|
||||
status("mod_dir non-empty", g_mod_dir_snippet[0] != '\0');
|
||||
ImGui::TextWrapped(" %s", g_mod_dir_snippet);
|
||||
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("Reset results")) {
|
||||
g_pre_fired = false;
|
||||
g_post_fired = false;
|
||||
g_replace_fired = false;
|
||||
g_arg_write_ok = false;
|
||||
g_pre_cancel_count = 0;
|
||||
g_post_count = 0;
|
||||
}
|
||||
daAlink_c* link = daAlink_getAlinkActorClass();
|
||||
if (link) {
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("(Link y angle: %d)", (int)link->shape_angle.y);
|
||||
}
|
||||
}
|
||||
|
||||
static void DrawMenuEntry(void*) {
|
||||
if (ImGui::MenuItem("Test: log all levels")) {
|
||||
dusk::g_api->log_info("log_info test");
|
||||
dusk::g_api->log_warn("log_warn test");
|
||||
dusk::g_api->log_error("log_error test");
|
||||
}
|
||||
if (ImGui::MenuItem("Test: reset Link y angle")) {
|
||||
daAlink_c* link = daAlink_getAlinkActorClass();
|
||||
if (link) link->shape_angle.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void mod_init(DuskModAPI* api) {
|
||||
dusk::init(api);
|
||||
|
||||
api->log_info("mod_test initializing");
|
||||
api->log_warn("log_warn smoke test");
|
||||
api->log_error("log_error smoke test");
|
||||
|
||||
std::snprintf(g_mod_dir_snippet, sizeof(g_mod_dir_snippet), "%.60s", api->mod_dir);
|
||||
|
||||
size_t size = 0;
|
||||
void* data = api->load_resource("hello.txt", &size);
|
||||
if (data) {
|
||||
g_resource_text.assign(static_cast<char*>(data), size);
|
||||
while (!g_resource_text.empty() && g_resource_text.back() == '\n')
|
||||
g_resource_text.pop_back();
|
||||
api->free_resource(data);
|
||||
g_resource_ok = true;
|
||||
api->log_info("load_resource OK: \"%s\"", g_resource_text.c_str());
|
||||
} else {
|
||||
api->log_error("load_resource FAILED for hello.txt");
|
||||
}
|
||||
|
||||
// Missing file should return nullptr gracefully.
|
||||
void* missing = api->load_resource("does_not_exist.bin", nullptr);
|
||||
if (!missing)
|
||||
api->log_info("load_resource missing-file: correctly returned nullptr");
|
||||
else {
|
||||
api->log_error("load_resource missing-file: unexpectedly returned data");
|
||||
api->free_resource(missing);
|
||||
}
|
||||
|
||||
dusk::hookAddPre <&daAlink_c::posMove>(on_posMove_pre);
|
||||
dusk::hookAddPost<&daAlink_c::posMove>(on_posMove_post);
|
||||
dusk::hookSetReplace<&daAlink_c::execute>(on_execute_replace);
|
||||
|
||||
api->register_tab_content(DrawPanel, nullptr);
|
||||
api->register_menu_item(DrawMenuEntry, nullptr);
|
||||
|
||||
api->log_info("mod_test ready");
|
||||
}
|
||||
|
||||
void mod_tick(DuskModAPI* api) {
|
||||
++g_ticks;
|
||||
(void)api;
|
||||
}
|
||||
|
||||
void mod_cleanup(DuskModAPI* api) {
|
||||
api->log_info("mod_test unloaded after %d ticks", g_ticks);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user