diff --git a/cmake/CopyModAssets.cmake b/cmake/CopyModAssets.cmake
new file mode 100644
index 0000000000..ca16da6018
--- /dev/null
+++ b/cmake/CopyModAssets.cmake
@@ -0,0 +1,10 @@
+# Copies a mod asset directory for packaging, skipping dotfiles and dot-directories
+# (.gitkeep, .DS_Store, ...). Usage: cmake -DSRC=
-DDST= -P CopyModAssets.cmake
+file(MAKE_DIRECTORY "${DST}")
+file(GLOB_RECURSE _files RELATIVE "${SRC}" "${SRC}/*")
+foreach (_file IN LISTS _files)
+ if (NOT _file MATCHES "(^|/)\\.")
+ get_filename_component(_dir "${_file}" DIRECTORY)
+ file(COPY "${SRC}/${_file}" DESTINATION "${DST}/${_dir}")
+ endif ()
+endforeach ()
diff --git a/cmake/GameABIConfig.cmake b/cmake/GameABIConfig.cmake
index 8b95dcaf67..0b965aa082 100644
--- a/cmake/GameABIConfig.cmake
+++ b/cmake/GameABIConfig.cmake
@@ -47,6 +47,15 @@ target_link_libraries(dusklight_mod_feature_game INTERFACE
dusklight_mod_api
dusklight_game_abi_headers)
target_compile_definitions(dusklight_mod_feature_game INTERFACE DUSK_MOD_FEATURE_GAME=1)
+# Game headers assume global.h comes first in the translation unit (it defines DUSK_GAME_DATA
+# and friends); force-include it so mods don't depend on include order.
+if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
+ target_compile_options(dusklight_mod_feature_game INTERFACE
+ "$<$:/FIglobal.h>")
+else ()
+ target_compile_options(dusklight_mod_feature_game INTERFACE
+ "$<$:SHELL:-include global.h>")
+endif ()
target_sources(dusklight_mod_feature_game INTERFACE
${_game_root}/sdk/src/game_feature.cpp)
diff --git a/cmake/ModSDK.cmake b/cmake/ModSDK.cmake
index 87125e12fd..10a87e8525 100644
--- a/cmake/ModSDK.cmake
+++ b/cmake/ModSDK.cmake
@@ -2,6 +2,8 @@
# [RUNTIME_LIBRARIES ...] [RES_DIR ] [OVERLAY_DIR ]
# [TEXTURES_DIR ] [OUTPUT_DIR ] [BUNDLE])
set(DUSK_MODS_OUTPUT_DIR "${CMAKE_BINARY_DIR}/mods" CACHE PATH "Directory to write mod packages into")
+set(DUSKLIGHT_SDK_STUB_URL "https://github.com/encounter/dusklight/releases/download/sdk"
+ CACHE STRING "Base URL for game link stubs downloaded by out-of-tree mod builds")
function(_mod_lib_info out_platform_var out_name_var)
set(_arch "${CMAKE_SYSTEM_PROCESSOR}")
@@ -11,6 +13,18 @@ function(_mod_lib_info out_platform_var out_name_var)
message(FATAL_ERROR "add_mod: universal binaries are not supported")
endif ()
set(_arch "${CMAKE_OSX_ARCHITECTURES}")
+ elseif (WIN32)
+ set(_arch_id "${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}")
+ if (NOT _arch_id)
+ set(_arch_id "${CMAKE_C_COMPILER_ARCHITECTURE_ID}")
+ endif ()
+ if (_arch_id MATCHES "^ARM64(EC)?$")
+ set(_arch "arm64")
+ elseif (_arch_id STREQUAL "x64")
+ set(_arch "amd64")
+ elseif (_arch_id STREQUAL "X86")
+ set(_arch "x86")
+ endif ()
endif ()
string(TOLOWER "${CMAKE_SYSTEM_NAME}" _platform)
if (_platform STREQUAL "darwin")
@@ -29,6 +43,34 @@ function(_mod_lib_info out_platform_var out_name_var)
set(${out_name_var} "mod${_ext}" PARENT_SCOPE)
endfunction()
+# For out-of-tree builds without a game binary: download the version-independent link stub
+# (generated by symgen) for the target platform.
+function(_mod_download_link_stub out_var)
+ _mod_lib_info(_platform _lib_name)
+ if (WIN32)
+ set(_asset "${_platform}.lib")
+ elseif (ANDROID)
+ set(_asset "stub-${_platform}.so")
+ else ()
+ set(_asset "stub-${_platform}")
+ endif ()
+ set(_stub "${CMAKE_BINARY_DIR}/dusklight-sdk-stubs/${_asset}")
+ if (NOT EXISTS "${_stub}")
+ message(STATUS "Mod SDK: downloading link stub ${_asset}")
+ file(DOWNLOAD "${DUSKLIGHT_SDK_STUB_URL}/${_asset}" "${_stub}.tmp" STATUS _status)
+ list(GET _status 0 _code)
+ if (NOT _code EQUAL 0)
+ list(GET _status 1 _error)
+ file(REMOVE "${_stub}.tmp")
+ message(FATAL_ERROR
+ "Mod SDK: failed to download ${DUSKLIGHT_SDK_STUB_URL}/${_asset}: ${_error}\n"
+ "Set DUSK_GAME_EXE to a game binary or link stub to skip the download.")
+ endif ()
+ file(RENAME "${_stub}.tmp" "${_stub}")
+ endif ()
+ set(${out_var} "${_stub}" PARENT_SCOPE)
+endfunction()
+
function(_mod_resolve_source_path out_var path)
if (IS_ABSOLUTE "${path}")
set(_path "${path}")
@@ -44,7 +86,15 @@ function(_mod_collect_assets out_var dir)
endif ()
file(GLOB_RECURSE _files CONFIGURE_DEPENDS LIST_DIRECTORIES false "${dir}/*")
- set(${out_var} ${_files} PARENT_SCOPE)
+ # Dotfiles (.gitkeep, .DS_Store, ...) are not packaged; see CopyModAssets.cmake.
+ set(_assets "")
+ foreach (_file IN LISTS _files)
+ file(RELATIVE_PATH _rel "${dir}" "${_file}")
+ if (NOT _rel MATCHES "(^|/)\\.")
+ list(APPEND _assets "${_file}")
+ endif ()
+ endforeach ()
+ set(${out_var} ${_assets} PARENT_SCOPE)
endfunction()
function(_mod_add_webgpu_headers target_name)
@@ -150,8 +200,7 @@ function(add_mod target_name)
elseif (DUSK_GAME_EXE)
_mod_resolve_source_path(_game_exe "${DUSK_GAME_EXE}")
else ()
- message(FATAL_ERROR
- "add_mod: FEATURES ${_features} requires DUSK_GAME_EXE (game executable)")
+ _mod_download_link_stub(_game_exe)
endif ()
target_link_options(${target_name} PRIVATE
-Xlinker -bundle_loader -Xlinker "${_game_exe}")
@@ -164,12 +213,13 @@ function(add_mod target_name)
if (_needs_host_link)
if (TARGET dusklight)
target_link_libraries(${target_name} PRIVATE dusklight)
- elseif (DUSK_GAME_EXE)
- _mod_resolve_source_path(_game_lib "${DUSK_GAME_EXE}")
- target_link_libraries(${target_name} PRIVATE "${_game_lib}")
else ()
- message(FATAL_ERROR
- "add_mod: FEATURES ${_features} requires DUSK_GAME_EXE (libmain.so or stub)")
+ if (DUSK_GAME_EXE)
+ _mod_resolve_source_path(_game_lib "${DUSK_GAME_EXE}")
+ else ()
+ _mod_download_link_stub(_game_lib)
+ endif ()
+ target_link_libraries(${target_name} PRIVATE "${_game_lib}")
endif ()
endif ()
set_target_properties(${target_name} PROPERTIES
@@ -202,8 +252,7 @@ function(add_mod target_name)
"(sdk/windows-.lib)")
endif ()
else ()
- message(FATAL_ERROR
- "add_mod: FEATURES ${_features} requires DUSK_GAME_EXE (import library)")
+ _mod_download_link_stub(_game_lib)
endif ()
target_link_libraries(${target_name} PRIVATE "${_game_lib}")
endif ()
@@ -261,8 +310,8 @@ function(add_mod target_name)
list(APPEND _package_deps ${_res_deps})
list(APPEND _package_inputs "${_res_dir}" ${_res_deps})
list(APPEND _zip_args res)
- list(APPEND _extra_cmds COMMAND ${CMAKE_COMMAND} -E copy_directory
- "${_res_dir}" "${_stage}/res")
+ list(APPEND _extra_cmds COMMAND ${CMAKE_COMMAND} "-DSRC=${_res_dir}"
+ "-DDST=${_stage}/res" -P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CopyModAssets.cmake")
endif ()
if (ARG_OVERLAY_DIR)
_mod_resolve_source_path(_overlay_dir "${ARG_OVERLAY_DIR}")
@@ -270,8 +319,8 @@ function(add_mod target_name)
list(APPEND _package_deps ${_overlay_deps})
list(APPEND _package_inputs "${_overlay_dir}" ${_overlay_deps})
list(APPEND _zip_args overlay)
- list(APPEND _extra_cmds COMMAND ${CMAKE_COMMAND} -E copy_directory
- "${_overlay_dir}" "${_stage}/overlay")
+ list(APPEND _extra_cmds COMMAND ${CMAKE_COMMAND} "-DSRC=${_overlay_dir}"
+ "-DDST=${_stage}/overlay" -P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CopyModAssets.cmake")
endif ()
if (ARG_TEXTURES_DIR)
_mod_resolve_source_path(_textures_dir "${ARG_TEXTURES_DIR}")
@@ -279,8 +328,8 @@ function(add_mod target_name)
list(APPEND _package_deps ${_textures_deps})
list(APPEND _package_inputs "${_textures_dir}" ${_textures_deps})
list(APPEND _zip_args textures)
- list(APPEND _extra_cmds COMMAND ${CMAKE_COMMAND} -E copy_directory
- "${_textures_dir}" "${_stage}/textures")
+ list(APPEND _extra_cmds COMMAND ${CMAKE_COMMAND} "-DSRC=${_textures_dir}"
+ "-DDST=${_stage}/textures" -P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/CopyModAssets.cmake")
endif ()
set(_bundle_cmds "")
diff --git a/docs/modding.md b/docs/modding.md
index 8afe8701e5..2600b4721d 100644
--- a/docs/modding.md
+++ b/docs/modding.md
@@ -28,7 +28,8 @@ function, read and write data fields, and hook the vast majority of game functio
## Getting Started
-Fork the [mod template](../mods/template_mod/), a self-contained CMake project that uses the Dusklight mod SDK.
+Fork the [mod template](https://github.com/TwilitRealm/mod-template), a self-contained CMake project that uses the
+Dusklight mod SDK.
```
my_mod/
@@ -43,19 +44,22 @@ my_mod/
**CMakeLists.txt:**
```cmake
-cmake_minimum_required(VERSION 3.25)
+cmake_minimum_required(VERSION 3.26)
project(my_mod CXX)
-set(DUSKLIGHT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/dusklight" CACHE PATH "Path to dusklight source root")
+if (NOT DUSKLIGHT_VERSION)
+ set(DUSKLIGHT_VERSION "76b56cd8b81809fce0a5c2a44e2f6d437591132f")
+endif ()
+include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/FetchDusklight.cmake")
add_subdirectory("${DUSKLIGHT_DIR}/sdk" dusklight-sdk EXCLUDE_FROM_ALL)
add_mod(my_mod
- FEATURES game # optional
+ FEATURES game # remove for service/asset-only mods; add webgpu for GfxService
SOURCES src/mod.cpp
MOD_JSON mod.json
- RES_DIR res # optional
- OVERLAY_DIR overlay # optional
- TEXTURES_DIR textures # optional
+ RES_DIR res # mod resources, including icon.png and banner.png
+ OVERLAY_DIR overlay # game file overlays; remove if unused
+ TEXTURES_DIR textures # texture replacements; remove if unused
)
```
@@ -65,11 +69,7 @@ Available features:
- `webgpu`: Allows importing the WebGPU API (`webgpu/webgpu.h`). Must be enabled when using
[GfxService](#gfxservice-modssvcgfxh).
-Building produces `my_mod.dusk` in `build//mods/` (configurable via the `DUSK_MODS_OUTPUT_DIR` cache variable).
-Dusklight searches a `mods/` directory next to the app in addition to the user directory, so a dev build launched from
-`build//` picks these up automatically: rebuild, relaunch (or click Reload), done.
-
-For a regular game install, copy the `.dusk` into the user mods folder:
+Building produces `my_mod.dusk` in `build/mods/`. Copy the `.dusk` into the user mods folder:
- Windows: `%APPDATA%\TwilitRealm\Dusklight\mods`
- Linux: `~/.local/share/TwilitRealm/Dusklight/mods`
@@ -498,14 +498,14 @@ Run before the original. Return `HOOK_SKIP_ORIGINAL` to cancel it (post-hooks st
```cpp
HookAction on_pos_move_pre(ModContext*, void* args, void* retval, void* userdata) {
- daAlink_c* link = dusk::mods::arg(args, 0); // arg 0 is `this`
+ daAlink_c* link = mods::arg(args, 0); // arg 0 is `this`
if (link->shape_angle.y > 10000) {
return HOOK_SKIP_ORIGINAL;
}
return HOOK_CONTINUE;
}
-dusk::mods::hook_add_pre(svc_hook, on_pos_move_pre);
+mods::hook_add_pre(svc_hook, on_pos_move_pre);
```
### Post-hooks
@@ -516,7 +516,7 @@ if any.
```cpp
void on_pos_move_post(ModContext*, void* args, void* retval, void* userdata) { ... }
-dusk::mods::hook_add_post(svc_hook, on_pos_move_post);
+mods::hook_add_post(svc_hook, on_pos_move_post);
```
### Replace-hooks
@@ -525,13 +525,13 @@ Substitute the original entirely. Call through to it via the declaration's `g_or
```cpp
void on_execute_replace(ModContext*, void* args, void* retval, void*) {
- int result = LinkExecute::g_orig(dusk::mods::arg(args, 0));
+ int result = LinkExecute::g_orig(mods::arg(args, 0));
if (retval != nullptr) {
*static_cast(retval) = result;
}
}
-dusk::mods::hook_replace(svc_hook, on_execute_replace);
+mods::hook_replace(svc_hook, on_execute_replace);
```
By default a second replace-hook on the same function is a conflict; `HookOptions` (`replace_policy`, `priority`,
@@ -547,7 +547,7 @@ symbol name instead. You must supply the signature along with the name.
DEFINE_HOOK_SYMBOL("daAlink_hookshotAtHitCallBack",
void(fopAc_ac_c*, dCcD_GObjInf*, fopAc_ac_c*, dCcD_GObjInf*), HookshotHit);
-dusk::mods::hook_add_pre(svc_hook, on_hookshot_hit_pre);
+mods::hook_add_pre(svc_hook, on_hookshot_hit_pre);
...
HookshotHit::g_orig(link, atObjInf, target, tgObjInf); // call through to the original
```
@@ -571,8 +571,8 @@ call.
declaration order.
```cpp
-T value = dusk::mods::arg(args, n); // copy
-T& ref = dusk::mods::arg_ref(args, n); // read/write reference
+T value = mods::arg(args, n); // copy
+T& ref = mods::arg_ref(args, n); // read/write reference
```
```cpp
@@ -580,14 +580,14 @@ DEFINE_HOOK(fopAcM_createItem, CreateItem);
// fpc_ProcID fopAcM_createItem(..., int itemNo, ...): turn heart drops into green rupees
HookAction on_create_item_pre(ModContext*, void* args, void*, void*) {
- int& itemNo = dusk::mods::arg_ref(args, 1);
+ int& itemNo = mods::arg_ref(args, 1);
if (itemNo == dItemNo_HEART_e) {
itemNo = dItemNo_GREEN_RUPEE_e;
}
return HOOK_CONTINUE;
}
-dusk::mods::hook_add_pre(svc_hook, on_create_item_pre);
+mods::hook_add_pre(svc_hook, on_create_item_pre);
```
For reference parameters (e.g. `const cXyz& pos`), `arg_ref` yields a direct reference.
@@ -646,13 +646,13 @@ the stack for the whole session (e.g. the outermost main loop); a mod that does
Service calls report failure through `ModResult` return values (`MOD_OK`, `MOD_UNAVAILABLE`,
`MOD_INVALID_ARGUMENT`, ...). Lifecycle exports additionally receive a `ModError*`: fill it (e.g. with
-`dusk::mods::set_error(error, code, "message")`) and return the code, and the loader disables the mod and shows the
+`mods::set_error(error, code, "message")`) and return the code, and the loader disables the mod and shows the
message to the user.
```cpp
MOD_EXPORT ModResult mod_initialize(ModError* error) {
if (!load_my_data()) {
- return dusk::mods::set_error(error, MOD_ERROR, "failed to load data");
+ return mods::set_error(error, MOD_ERROR, "failed to load data");
}
return MOD_OK;
}
@@ -686,7 +686,7 @@ typedef struct MyModService {
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
-struct dusk::mods::ServiceTraits {
+struct mods::ServiceTraits {
static constexpr const char* id = MY_MOD_SERVICE_ID;
static constexpr uint16_t major_version = MY_MOD_SERVICE_MAJOR;
static constexpr uint16_t minor_version = MY_MOD_SERVICE_MINOR;
diff --git a/extern/aurora b/extern/aurora
index 1dde08fa0d..81f12f31d2 160000
--- a/extern/aurora
+++ b/extern/aurora
@@ -1 +1 @@
-Subproject commit 1dde08fa0d0030133788a6250a81c8b9c44f246f
+Subproject commit 81f12f31d23ec822d8bde2031c91e94c470911eb
diff --git a/mods/ao_mod/src/mod.cpp b/mods/ao_mod/src/mod.cpp
index a7eb4f4028..fb36ddb40c 100644
--- a/mods/ao_mod/src/mod.cpp
+++ b/mods/ao_mod/src/mod.cpp
@@ -752,7 +752,7 @@ ModResult register_bool_option(
cvarDesc.type = CONFIG_VAR_BOOL;
cvarDesc.default_bool = defaultValue;
if (svc_config->register_var(mod_ctx, &cvarDesc, &outHandle) != MOD_OK) {
- return dusk::mods::set_error(error, MOD_ERROR, "failed to register AO option");
+ return mods::set_error(error, MOD_ERROR, "failed to register AO option");
}
return MOD_OK;
}
@@ -764,7 +764,7 @@ ModResult register_int_option(
cvarDesc.type = CONFIG_VAR_INT;
cvarDesc.default_int = defaultValue;
if (svc_config->register_var(mod_ctx, &cvarDesc, &outHandle) != MOD_OK) {
- return dusk::mods::set_error(error, MOD_ERROR, "failed to register AO option");
+ return mods::set_error(error, MOD_ERROR, "failed to register AO option");
}
return MOD_OK;
}
@@ -785,7 +785,7 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
result = svc_resource->load(mod_ctx, "composite.wgsl", &g_compositeSource);
}
if (result != MOD_OK) {
- return dusk::mods::set_error(error, result, "failed to load AO shaders");
+ return mods::set_error(error, result, "failed to load AO shaders");
}
result = register_bool_option("effectEnabled", false, g_cvarEnabled, error);
@@ -814,7 +814,7 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
}
if (svc_gfx->get_device_info(mod_ctx, &g_deviceInfo) != MOD_OK) {
- return dusk::mods::set_error(error, MOD_ERROR, "failed to query device info");
+ return mods::set_error(error, MOD_ERROR, "failed to query device info");
}
if (!build_compute_pipeline("AO preprocess depth", g_preprocessSource, "preprocess_depth",
g_preprocessPipeline, g_preprocessLayout) ||
@@ -824,35 +824,35 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
!build_compute_pipeline(
"AO denoise", g_denoiseSource, "spatial_denoise", g_denoisePipeline, g_denoiseLayout))
{
- return dusk::mods::set_error(error, MOD_ERROR, "failed to create AO compute pipelines");
+ return mods::set_error(error, MOD_ERROR, "failed to create AO compute pipelines");
}
if (!build_composite_pipeline(true, g_compositePipeline, g_compositeLayout) ||
!build_composite_pipeline(false, g_compositeDebugPipeline, g_compositeDebugLayout))
{
- return dusk::mods::set_error(error, MOD_ERROR, "failed to create AO composite pipeline");
+ return mods::set_error(error, MOD_ERROR, "failed to create AO composite pipeline");
}
if (!build_hilbert_lut()) {
- return dusk::mods::set_error(error, MOD_ERROR, "failed to create AO noise LUT");
+ return mods::set_error(error, MOD_ERROR, "failed to create AO noise LUT");
}
GfxComputeTypeDesc computeDesc = GFX_COMPUTE_TYPE_DESC_INIT;
computeDesc.label = "AO chain";
computeDesc.callback = on_compute;
if (svc_gfx->register_compute_type(mod_ctx, &computeDesc, &g_computeType) != MOD_OK) {
- return dusk::mods::set_error(error, MOD_ERROR, "failed to register compute type");
+ return mods::set_error(error, MOD_ERROR, "failed to register compute type");
}
GfxDrawTypeDesc drawDesc = GFX_DRAW_TYPE_DESC_INIT;
drawDesc.label = "AO composite";
drawDesc.draw = on_draw;
if (svc_gfx->register_draw_type(mod_ctx, &drawDesc, &g_drawType) != MOD_OK) {
- return dusk::mods::set_error(error, MOD_ERROR, "failed to register draw type");
+ return mods::set_error(error, MOD_ERROR, "failed to register draw type");
}
GfxStageHookDesc stageDesc = GFX_STAGE_HOOK_DESC_INIT;
stageDesc.callback = on_scene_after_opaque;
if (svc_gfx->register_stage_hook(
mod_ctx, GFX_STAGE_SCENE_AFTER_OPAQUE, &stageDesc, &g_afterOpaqueHook) != MOD_OK)
{
- return dusk::mods::set_error(error, MOD_ERROR, "failed to register stage hook");
+ return mods::set_error(error, MOD_ERROR, "failed to register stage hook");
}
UiModsPanelDesc panelDesc = UI_MODS_PANEL_DESC_INIT;
diff --git a/mods/shadow_mod/src/mod.cpp b/mods/shadow_mod/src/mod.cpp
index 0319dab3dc..97439d7015 100644
--- a/mods/shadow_mod/src/mod.cpp
+++ b/mods/shadow_mod/src/mod.cpp
@@ -945,7 +945,7 @@ ModResult register_bool_option(
cvarDesc.type = CONFIG_VAR_BOOL;
cvarDesc.default_bool = defaultValue;
if (svc_config->register_var(mod_ctx, &cvarDesc, &outHandle) != MOD_OK) {
- return dusk::mods::set_error(error, MOD_ERROR, "failed to register shadow option");
+ return mods::set_error(error, MOD_ERROR, "failed to register shadow option");
}
return MOD_OK;
}
@@ -957,7 +957,7 @@ ModResult register_int_option(
cvarDesc.type = CONFIG_VAR_INT;
cvarDesc.default_int = defaultValue;
if (svc_config->register_var(mod_ctx, &cvarDesc, &outHandle) != MOD_OK) {
- return dusk::mods::set_error(error, MOD_ERROR, "failed to register shadow option");
+ return mods::set_error(error, MOD_ERROR, "failed to register shadow option");
}
return MOD_OK;
}
@@ -969,7 +969,7 @@ extern "C" {
MOD_EXPORT ModResult mod_initialize(ModError* error) {
ModResult result = svc_resource->load(mod_ctx, "shadow.wgsl", &g_shaderSource);
if (result != MOD_OK || g_shaderSource.data == nullptr) {
- return dusk::mods::set_error(error, result, "failed to load shadow.wgsl");
+ return mods::set_error(error, result, "failed to load shadow.wgsl");
}
result = register_bool_option("effectEnabled", false, g_cvarEnabled, error);
@@ -1014,56 +1014,56 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
}
if (svc_gfx->get_device_info(mod_ctx, &g_deviceInfo) != MOD_OK) {
- return dusk::mods::set_error(error, MOD_ERROR, "failed to query device info");
+ return mods::set_error(error, MOD_ERROR, "failed to query device info");
}
if (!build_composite_pipeline(true, g_compositePipeline, g_compositeLayout) ||
!build_composite_pipeline(false, g_compositeDebugPipeline, g_compositeDebugLayout))
{
- return dusk::mods::set_error(error, MOD_ERROR, "failed to create composite pipeline");
+ return mods::set_error(error, MOD_ERROR, "failed to create composite pipeline");
}
GfxDrawTypeDesc drawDesc = GFX_DRAW_TYPE_DESC_INIT;
drawDesc.label = "shadow composite";
drawDesc.draw = on_draw;
if (svc_gfx->register_draw_type(mod_ctx, &drawDesc, &g_drawType) != MOD_OK) {
- return dusk::mods::set_error(error, MOD_ERROR, "failed to register draw type");
+ return mods::set_error(error, MOD_ERROR, "failed to register draw type");
}
GfxStageHookDesc stageDesc = GFX_STAGE_HOOK_DESC_INIT;
stageDesc.callback = on_scene_begin;
if (svc_gfx->register_stage_hook(
mod_ctx, GFX_STAGE_SCENE_BEGIN, &stageDesc, &g_sceneBeginHook) != MOD_OK)
{
- return dusk::mods::set_error(error, MOD_ERROR, "failed to register stage hook");
+ return mods::set_error(error, MOD_ERROR, "failed to register stage hook");
}
stageDesc.callback = on_scene_after_terrain;
if (svc_gfx->register_stage_hook(
mod_ctx, GFX_STAGE_SCENE_AFTER_TERRAIN, &stageDesc, &g_sceneAfterTerrainHook) != MOD_OK)
{
- return dusk::mods::set_error(error, MOD_ERROR, "failed to register stage hook");
+ return mods::set_error(error, MOD_ERROR, "failed to register stage hook");
}
stageDesc.callback = on_frame_before_hud;
if (svc_gfx->register_stage_hook(
mod_ctx, GFX_STAGE_FRAME_BEFORE_HUD, &stageDesc, &g_frameBeforeHudHook) != MOD_OK)
{
- return dusk::mods::set_error(error, MOD_ERROR, "failed to register stage hook");
+ return mods::set_error(error, MOD_ERROR, "failed to register stage hook");
}
// Skip the game's own shadow rendering while the dynamic pass is active: the
// shadowControl pair covers the actor real/blob shadows, drawCloudShadow the weather
// cloud shadows.
- if (dusk::mods::hook_add_pre(svc_hook, on_game_shadow_pre) != MOD_OK ||
- dusk::mods::hook_add_pre(svc_hook, on_game_shadow_pre) != MOD_OK ||
- dusk::mods::hook_add_pre(svc_hook, on_game_shadow_pre) != MOD_OK)
+ if (mods::hook_add_pre(svc_hook, on_game_shadow_pre) != MOD_OK ||
+ mods::hook_add_pre(svc_hook, on_game_shadow_pre) != MOD_OK ||
+ mods::hook_add_pre(svc_hook, on_game_shadow_pre) != MOD_OK)
{
- return dusk::mods::set_error(error, MOD_ERROR, "failed to hook game shadow rendering");
+ return mods::set_error(error, MOD_ERROR, "failed to hook game shadow rendering");
}
- if (dusk::mods::hook_add_pre(svc_hook, on_frustum_clip_pre) != MOD_OK ||
- dusk::mods::hook_add_pre(svc_hook, on_frustum_clip_pre) != MOD_OK)
+ if (mods::hook_add_pre(svc_hook, on_frustum_clip_pre) != MOD_OK ||
+ mods::hook_add_pre(svc_hook, on_frustum_clip_pre) != MOD_OK)
{
- return dusk::mods::set_error(error, MOD_ERROR, "failed to hook frustum clipping");
+ return mods::set_error(error, MOD_ERROR, "failed to hook frustum clipping");
}
- if (dusk::mods::hook_add_pre(svc_hook, on_copy_tex_pre) != MOD_OK) {
- return dusk::mods::set_error(error, MOD_ERROR, "failed to hook GXCopyTex");
+ if (mods::hook_add_pre(svc_hook, on_copy_tex_pre) != MOD_OK) {
+ return mods::set_error(error, MOD_ERROR, "failed to hook GXCopyTex");
}
UiModsPanelDesc panelDesc = UI_MODS_PANEL_DESC_INIT;
panelDesc.build = build_panel;
diff --git a/sdk/CMakeLists.txt b/sdk/CMakeLists.txt
index 2f25c28efe..cd80781eb6 100644
--- a/sdk/CMakeLists.txt
+++ b/sdk/CMakeLists.txt
@@ -7,7 +7,8 @@
# add_subdirectory(/sdk dusk-sdk EXCLUDE_FROM_ALL)
# add_mod(my_mod FEATURES game webgpu SOURCES ... MOD_JSON mod.json)
#
-# TODO: auto-download link targets from tag
+# On platforms where mods link against the game binary (Windows/Apple/Android), a
+# version-independent link stub is downloaded automatically unless DUSK_GAME_EXE is set.
cmake_minimum_required(VERSION 3.25)
diff --git a/sdk/include/mods/hook.hpp b/sdk/include/mods/hook.hpp
index d48e9bff9f..07999da84c 100644
--- a/sdk/include/mods/hook.hpp
+++ b/sdk/include/mods/hook.hpp
@@ -9,7 +9,7 @@
#include
#include
-namespace dusk::mods {
+namespace mods {
template
T arg(void* argsRaw, int n) noexcept {
@@ -133,7 +133,7 @@ struct NamedHook : HookImpl, R, A...> {};
* DEFINE_HOOK_SYMBOL("daAlink_hookshotAtHitCallBack",
* void(fopAc_ac_c*, dCcD_GObjInf*, fopAc_ac_c*, dCcD_GObjInf*), HookshotHit);
*
- * dusk::mods::hook_add_pre(svc_hook, on_link_execute);
+ * mods::hook_add_pre(svc_hook, on_link_execute);
*
* DEFINE_HOOK_SYMBOL names may be the platform mangled name (dlopen convention, no Mach-O
* leading underscore) or the demangled qualified display name; overloaded display names are
@@ -141,19 +141,18 @@ struct NamedHook : HookImpl, R, A...> {};
*/
#define DEFINE_HOOK(target, alias) \
[[maybe_unused]] static const void* const mod_meta_hook_##alias = \
- &::dusk::mods::detail::HookRecordFor<(target), \
- ::dusk::mods::FixedString{#target}>::Holder::record; \
- struct alias : ::dusk::mods::Hook<(target)> { \
+ &::mods::detail::HookRecordFor<(target), ::mods::FixedString{#target}>::Holder::record; \
+ struct alias : ::mods::Hook<(target)> { \
static void* resolved_target() { \
- return ::dusk::mods::detail::HookRecordFor<(target), \
- ::dusk::mods::FixedString{#target}>::Holder::record.resolved; \
+ return ::mods::detail::HookRecordFor<(target), \
+ ::mods::FixedString{#target}>::Holder::record.resolved; \
} \
}
#define DEFINE_HOOK_SYMBOL(name, sig, alias) \
MOD_META_RECORD static constinit auto mod_meta_hook_##alias = \
- ::dusk::mods::detail::make_hook_name_record<::dusk::mods::FixedString{name}>(); \
- struct alias : ::dusk::mods::NamedHook<::dusk::mods::FixedString{name}, sig> { \
+ ::mods::detail::make_hook_name_record<::mods::FixedString{name}>(); \
+ struct alias : ::mods::NamedHook<::mods::FixedString{name}, sig> { \
static void* resolved_target() { return mod_meta_hook_##alias.resolved; } \
}
@@ -208,4 +207,4 @@ ModResult hook_replace(
return hooks->replace(mod_ctx, Entry::target, callback, options);
}
-} // namespace dusk::mods
+} // namespace mods
diff --git a/sdk/include/mods/meta.hpp b/sdk/include/mods/meta.hpp
index 7d5d1e5eec..023783cea1 100644
--- a/sdk/include/mods/meta.hpp
+++ b/sdk/include/mods/meta.hpp
@@ -52,7 +52,7 @@ extern "C" const unsigned char __stop_modmeta[];
#define MOD_META_BOUNDS_END (__stop_modmeta)
#endif
-namespace dusk::mods {
+namespace mods {
/* A string usable as a template argument: carries a symbol/target name into record builders
* and makes each hook declaration's static state unique. */
@@ -254,8 +254,7 @@ struct HookFnHolder {
{sizeof(HookFnRecord), MOD_META_HOOK_FN, 0}, 0, Target, nullptr};
};
-template >
+template >
struct HookRecordFor {
using Holder = HookFnHolder;
};
@@ -268,8 +267,7 @@ struct HookRecordFor {
struct Bind> {
using Type = HookMemHolder().chars[Is]...>;
};
- using Holder =
- Bind().len>>::Type;
+ using Holder = Bind().len>>::Type;
};
template
@@ -284,4 +282,4 @@ consteval auto make_hook_name_record() {
}
} // namespace detail
-} // namespace dusk::mods
+} // namespace mods
diff --git a/sdk/include/mods/service.hpp b/sdk/include/mods/service.hpp
index d97d8def31..a6010a375c 100644
--- a/sdk/include/mods/service.hpp
+++ b/sdk/include/mods/service.hpp
@@ -7,7 +7,7 @@
#include
#include
-namespace dusk::mods {
+namespace mods {
template
struct ServiceTraits;
@@ -23,14 +23,14 @@ inline ModResult set_error(ModError* outError, ModResult code, const char* messa
return code;
}
-} // namespace dusk::mods
+} // namespace mods
#define DEFINE_MOD() \
extern "C" { \
MOD_EXPORT ModContext* mod_ctx = nullptr; \
} \
MOD_META_RECORD static constinit ModMetaHeader mod_meta_header_record = \
- ::dusk::mods::detail::make_header(); \
+ ::mods::detail::make_header(); \
MOD_META_BOUNDS_DEFN \
extern "C" { \
MOD_EXPORT constinit const ModMeta mod_meta = { \
@@ -52,26 +52,26 @@ inline ModResult set_error(ModError* outError, ModResult code, const char* messa
static_cast(major_value), \
static_cast(min_minor_value), \
&(variable), \
- ::dusk::mods::detail::make_service_id(service_id_value), \
+ ::mods::detail::make_service_id(service_id_value), \
}
#define IMPORT_SERVICE_VERSION(service_type, variable, min_minor_value) \
- IMPORT_SERVICE_EX(service_type, variable, ::dusk::mods::ServiceTraits::id, \
- ::dusk::mods::ServiceTraits::major_version, min_minor_value, \
+ IMPORT_SERVICE_EX(service_type, variable, ::mods::ServiceTraits::id, \
+ ::mods::ServiceTraits::major_version, min_minor_value, \
SERVICE_IMPORT_REQUIRED)
#define IMPORT_SERVICE(service_type, variable) \
IMPORT_SERVICE_VERSION( \
- service_type, variable, ::dusk::mods::ServiceTraits::minor_version)
+ service_type, variable, ::mods::ServiceTraits::minor_version)
#define IMPORT_OPTIONAL_SERVICE_VERSION(service_type, variable, min_minor_value) \
- IMPORT_SERVICE_EX(service_type, variable, ::dusk::mods::ServiceTraits::id, \
- ::dusk::mods::ServiceTraits::major_version, min_minor_value, \
+ IMPORT_SERVICE_EX(service_type, variable, ::mods::ServiceTraits::id, \
+ ::mods::ServiceTraits::major_version, min_minor_value, \
SERVICE_IMPORT_OPTIONAL)
#define IMPORT_OPTIONAL_SERVICE(service_type, variable) \
IMPORT_OPTIONAL_SERVICE_VERSION( \
- service_type, variable, ::dusk::mods::ServiceTraits::minor_version)
+ service_type, variable, ::mods::ServiceTraits::minor_version)
#define EXPORT_SERVICE_AS(instance, service_id_value) \
MOD_META_RECORD static constinit ModMetaExport mod_meta_export_##instance = { \
@@ -79,12 +79,11 @@ inline ModResult set_error(ModError* outError, ModResult code, const char* messa
(instance).header.major_version, \
(instance).header.minor_version, \
&(instance), \
- ::dusk::mods::detail::make_service_id(service_id_value), \
+ ::mods::detail::make_service_id(service_id_value), \
}
#define EXPORT_SERVICE(instance) \
- EXPORT_SERVICE_AS( \
- instance, ::dusk::mods::ServiceTraits>::id)
+ EXPORT_SERVICE_AS(instance, ::mods::ServiceTraits>::id)
#define EXPORT_DEFERRED_SERVICE(token, service_id_value, major_value, minor_value) \
MOD_META_RECORD static constinit ModMetaExport mod_meta_export_##token = { \
@@ -92,5 +91,5 @@ inline ModResult set_error(ModError* outError, ModResult code, const char* messa
static_cast(major_value), \
static_cast(minor_value), \
nullptr, \
- ::dusk::mods::detail::make_service_id(service_id_value), \
+ ::mods::detail::make_service_id(service_id_value), \
}
diff --git a/sdk/include/mods/svc/camera.h b/sdk/include/mods/svc/camera.h
index ec7724a1de..f6de5b8146 100644
--- a/sdk/include/mods/svc/camera.h
+++ b/sdk/include/mods/svc/camera.h
@@ -57,7 +57,7 @@ typedef struct CameraService {
#include "mods/service.hpp"
template <>
-struct dusk::mods::ServiceTraits {
+struct mods::ServiceTraits {
static constexpr const char* id = CAMERA_SERVICE_ID;
static constexpr uint16_t major_version = CAMERA_SERVICE_MAJOR;
static constexpr uint16_t minor_version = CAMERA_SERVICE_MINOR;
diff --git a/sdk/include/mods/svc/config.h b/sdk/include/mods/svc/config.h
index 50c518bd40..cd4afdbbdf 100644
--- a/sdk/include/mods/svc/config.h
+++ b/sdk/include/mods/svc/config.h
@@ -100,7 +100,7 @@ typedef struct ConfigService {
#include "mods/service.hpp"
template <>
-struct dusk::mods::ServiceTraits {
+struct mods::ServiceTraits {
static constexpr const char* id = CONFIG_SERVICE_ID;
static constexpr uint16_t major_version = CONFIG_SERVICE_MAJOR;
static constexpr uint16_t minor_version = CONFIG_SERVICE_MINOR;
diff --git a/sdk/include/mods/svc/game.h b/sdk/include/mods/svc/game.h
index bdcf2d71f0..0a3585b912 100644
--- a/sdk/include/mods/svc/game.h
+++ b/sdk/include/mods/svc/game.h
@@ -23,7 +23,7 @@ typedef struct GameService {
#include
template <>
-struct dusk::mods::ServiceTraits {
+struct mods::ServiceTraits {
static constexpr const char* id = GAME_SERVICE_ID;
static constexpr uint16_t major_version = GAME_SERVICE_MAJOR;
static constexpr uint16_t minor_version = GAME_SERVICE_MINOR;
diff --git a/sdk/include/mods/svc/gfx.h b/sdk/include/mods/svc/gfx.h
index fad1b0bf9a..03b24ed986 100644
--- a/sdk/include/mods/svc/gfx.h
+++ b/sdk/include/mods/svc/gfx.h
@@ -206,7 +206,7 @@ typedef struct GfxService {
#include "mods/service.hpp"
template <>
-struct dusk::mods::ServiceTraits {
+struct mods::ServiceTraits {
static constexpr const char* id = GFX_SERVICE_ID;
static constexpr uint16_t major_version = GFX_SERVICE_MAJOR;
static constexpr uint16_t minor_version = GFX_SERVICE_MINOR;
diff --git a/sdk/include/mods/svc/hook.h b/sdk/include/mods/svc/hook.h
index 987c438fa0..3d4e9c0472 100644
--- a/sdk/include/mods/svc/hook.h
+++ b/sdk/include/mods/svc/hook.h
@@ -46,7 +46,7 @@ typedef enum HookReplacePolicy {
/*
* Hook callbacks. `args` is an array of pointers to the call's arguments (index 0 is `this`
* for member functions); `retval` points at the return slot (NULL for void). Read and write
- * them through dusk::mods::arg / arg_ref from mods/hook.hpp. `userdata` is the pointer
+ * them through mods::arg / arg_ref from mods/hook.hpp. `userdata` is the pointer
* from HookOptions. All run on the game thread, in the hooked call's own stack frame.
*/
typedef HookAction (*HookPreFn)(ModContext* ctx, void* args, void* retval, void* userdata);
@@ -118,7 +118,7 @@ typedef struct HookService {
#include "mods/service.hpp"
template <>
-struct dusk::mods::ServiceTraits {
+struct mods::ServiceTraits {
static constexpr const char* id = HOOK_SERVICE_ID;
static constexpr uint16_t major_version = HOOK_SERVICE_MAJOR;
static constexpr uint16_t minor_version = HOOK_SERVICE_MINOR;
diff --git a/sdk/include/mods/svc/host.h b/sdk/include/mods/svc/host.h
index 0f7851c7e1..9e278d5790 100644
--- a/sdk/include/mods/svc/host.h
+++ b/sdk/include/mods/svc/host.h
@@ -107,7 +107,7 @@ typedef struct HostService {
#include "mods/service.hpp"
template <>
-struct dusk::mods::ServiceTraits {
+struct mods::ServiceTraits {
static constexpr const char* id = HOST_SERVICE_ID;
static constexpr uint16_t major_version = HOST_SERVICE_MAJOR;
static constexpr uint16_t minor_version = HOST_SERVICE_MINOR;
diff --git a/sdk/include/mods/svc/log.h b/sdk/include/mods/svc/log.h
index 0dbf4ec48e..0bb9ca74ed 100644
--- a/sdk/include/mods/svc/log.h
+++ b/sdk/include/mods/svc/log.h
@@ -40,7 +40,7 @@ typedef struct LogService {
#include "mods/service.hpp"
template <>
-struct dusk::mods::ServiceTraits {
+struct mods::ServiceTraits {
static constexpr const char* id = LOG_SERVICE_ID;
static constexpr uint16_t major_version = LOG_SERVICE_MAJOR;
static constexpr uint16_t minor_version = LOG_SERVICE_MINOR;
diff --git a/sdk/include/mods/svc/overlay.h b/sdk/include/mods/svc/overlay.h
index 02f60bd727..76e24c878b 100644
--- a/sdk/include/mods/svc/overlay.h
+++ b/sdk/include/mods/svc/overlay.h
@@ -50,7 +50,7 @@ typedef struct OverlayService {
#include "mods/service.hpp"
template <>
-struct dusk::mods::ServiceTraits {
+struct mods::ServiceTraits {
static constexpr const char* id = OVERLAY_SERVICE_ID;
static constexpr uint16_t major_version = OVERLAY_SERVICE_MAJOR;
static constexpr uint16_t minor_version = OVERLAY_SERVICE_MINOR;
diff --git a/sdk/include/mods/svc/resource.h b/sdk/include/mods/svc/resource.h
index 1a8251d83a..05a22754a3 100644
--- a/sdk/include/mods/svc/resource.h
+++ b/sdk/include/mods/svc/resource.h
@@ -45,7 +45,7 @@ typedef struct ResourceService {
#include "mods/service.hpp"
template <>
-struct dusk::mods::ServiceTraits {
+struct mods::ServiceTraits {
static constexpr const char* id = RESOURCE_SERVICE_ID;
static constexpr uint16_t major_version = RESOURCE_SERVICE_MAJOR;
static constexpr uint16_t minor_version = RESOURCE_SERVICE_MINOR;
diff --git a/sdk/include/mods/svc/texture.h b/sdk/include/mods/svc/texture.h
index bb648cf860..cdcfbe7e2a 100644
--- a/sdk/include/mods/svc/texture.h
+++ b/sdk/include/mods/svc/texture.h
@@ -81,7 +81,7 @@ typedef struct TextureService {
#include "mods/service.hpp"
template <>
-struct dusk::mods::ServiceTraits {
+struct mods::ServiceTraits {
static constexpr const char* id = TEXTURE_SERVICE_ID;
static constexpr uint16_t major_version = TEXTURE_SERVICE_MAJOR;
static constexpr uint16_t minor_version = TEXTURE_SERVICE_MINOR;
diff --git a/sdk/include/mods/svc/ui.h b/sdk/include/mods/svc/ui.h
index 73df25cb47..e19ec65f6c 100644
--- a/sdk/include/mods/svc/ui.h
+++ b/sdk/include/mods/svc/ui.h
@@ -277,7 +277,7 @@ typedef struct UiService {
#include "mods/service.hpp"
template <>
-struct dusk::mods::ServiceTraits {
+struct mods::ServiceTraits {
static constexpr const char* id = UI_SERVICE_ID;
static constexpr uint16_t major_version = UI_SERVICE_MAJOR;
static constexpr uint16_t minor_version = UI_SERVICE_MINOR;
diff --git a/src/dusk/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp
index 1f304d4b3e..4009b37e39 100644
--- a/src/dusk/audio/DuskDsp.cpp
+++ b/src/dusk/audio/DuskDsp.cpp
@@ -457,10 +457,7 @@ static int ReadChannelSamplesChunk(
auto aramBase = static_cast(ARGetStorageAddress()) + channel.mWaveAramAddress;
- // Streaming logic directly modifies mSamplesLeft.
- // So we use that as our tracking of where we are.
- auto curSamplePosition = channel.mEndSample - channel.mSamplesLeft;
-
+ auto curSamplePosition = channel.mSamplePosition;
u32 skipSamples = curSamplePosition % channel.mSamplesPerBlock;
if (skipSamples != 0) {
// We need to start reading in the middle of a block. This can happen thanks to loops.