Mods: WindowService, log wrappers, external rendering (#2251)

This commit is contained in:
Luke Street
2026-07-30 00:24:24 -06:00
committed by GitHub
parent 48de4bcb08
commit 7305ef09b9
36 changed files with 2195 additions and 395 deletions
+17 -1
View File
@@ -20,7 +20,23 @@ extern "C" {
#ifdef __cplusplus
#define MOD_EXTERN_C extern "C"
#else
#define MOD_EXTERN_C
#define MOD_EXTERN_C extern
#endif
#ifdef __cplusplus
#define MOD_DECLARE_SERVICE( \
service_type, variable, service_id_value, major_value, minor_value) \
MOD_EXTERN_C const service_type* variable; \
template <> \
struct mods::ServiceTraits<service_type> { \
static constexpr const char* id = service_id_value; \
static constexpr uint16_t major_version = major_value; \
static constexpr uint16_t minor_version = minor_value; \
}
#else
#define MOD_DECLARE_SERVICE( \
service_type, variable, service_id_value, major_value, minor_value) \
MOD_EXTERN_C const service_type* variable
#endif
#define MOD_ABI_VERSION 1u
+25 -188
View File
@@ -1,219 +1,56 @@
#pragma once
#if !defined(DUSK_BUILDING_GAME) && !defined(DUSK_MOD_FEATURE_GAME)
#error "DEFINE_HOOK requires add_mod(... FEATURES game)"
#if defined(_MSC_VER)
#pragma message("warning: <mods/hook.hpp> is deprecated; include <mods/svc/hook.hpp> instead")
#else
#warning "<mods/hook.hpp> is deprecated; include <mods/svc/hook.hpp> instead"
#endif
#include <mods/svc/hook.h>
#include <memory>
#include <type_traits>
#include <mods/svc/hook.hpp>
namespace mods {
template <class T>
T arg(void* argsRaw, int n) noexcept {
void** args = static_cast<void**>(argsRaw);
return *static_cast<std::add_pointer_t<std::remove_reference_t<T>>>(args[n]);
}
template <class T>
std::remove_reference_t<T>& arg_ref(void* argsRaw, int n) noexcept {
void** args = static_cast<void**>(argsRaw);
return *static_cast<std::add_pointer_t<std::remove_reference_t<T>>>(args[n]);
}
/*
* Trampoline generator + per-target state. Tag makes each hooked target's statics distinct; the
* target address comes from the declaration's metadata record, resolved by the host at mod
* initialization.
*/
template <class Tag, class R, class... A>
struct HookImpl {
static inline R (*g_orig)(A...) = nullptr;
static inline const HookService* hooks = nullptr;
static inline void* target = nullptr;
static bool dispatch_pre(void* args, void* retval) {
if (hooks == nullptr) {
return false;
}
int skipOriginal = 0;
const ModResult result = hooks->dispatch_pre(mod_ctx, target, args, retval, &skipOriginal);
return result == MOD_OK && skipOriginal != 0;
}
static void dispatch_post(void* args, void* retval) {
if (hooks != nullptr) {
hooks->dispatch_post(mod_ctx, target, args, retval);
}
}
static R trampoline(A... args) {
if constexpr (sizeof...(A) == 0) {
if constexpr (std::is_void_v<R>) {
const bool skipOriginal = dispatch_pre(nullptr, nullptr);
if (!skipOriginal) {
g_orig(args...);
}
dispatch_post(nullptr, nullptr);
} else {
R result{};
const bool skipOriginal =
dispatch_pre(nullptr, static_cast<void*>(std::addressof(result)));
if (!skipOriginal) {
result = g_orig(args...);
}
dispatch_post(nullptr, static_cast<void*>(std::addressof(result)));
return result;
}
} else {
void* ptrs[] = {static_cast<void*>(std::addressof(args))...};
if constexpr (std::is_void_v<R>) {
const bool skipOriginal = dispatch_pre(static_cast<void*>(ptrs), nullptr);
if (!skipOriginal) {
g_orig(args...);
}
dispatch_post(static_cast<void*>(ptrs), nullptr);
} else {
R result{};
const bool skipOriginal = dispatch_pre(
static_cast<void*>(ptrs), static_cast<void*>(std::addressof(result)));
if (!skipOriginal) {
result = g_orig(args...);
}
dispatch_post(static_cast<void*>(ptrs), static_cast<void*>(std::addressof(result)));
return result;
}
}
}
};
namespace detail {
template <auto Target>
using TargetTag = std::integral_constant<decltype(Target), Target>;
template <FixedString Name>
struct NameTag {};
} // namespace detail
/*
* Typed base for a hook on a function named at compile time (&daAlink_c::execute, &free_fn).
* Instantiate through DEFINE_HOOK, which pairs it with the metadata record the host resolves.
*/
template <auto Target>
struct Hook;
template <class C, class R, class... A, R (C::*Target)(A...)>
struct Hook<Target> : HookImpl<detail::TargetTag<Target>, R, C*, A...> {};
template <class C, class R, class... A, R (C::*Target)(A...) const>
struct Hook<Target> : HookImpl<detail::TargetTag<Target>, R, const C*, A...> {};
template <class R, class... A, R (*Target)(A...)>
struct Hook<Target> : HookImpl<detail::TargetTag<Target>, R, A...> {};
/*
* Typed base for a hook on a function by its symbol name, for targets you can't name in C++:
* file-local statics, private members, or symbols without a header. The signature is written
* free-style with the receiver first and is *not* compiler-checked. Instantiate through
* DEFINE_HOOK_SYMBOL.
*/
template <FixedString Name, class Sig>
struct NamedHook;
template <FixedString Name, class R, class... A>
struct NamedHook<Name, R(A...)> : HookImpl<detail::NameTag<Name>, R, A...> {};
/*
* Declare a hook target. The declaration emits a metadata record that the host resolves at mod
* initialization. Every hook target must be declared.
*
* DEFINE_HOOK(&daAlink_c::execute, LinkExecute);
* DEFINE_HOOK_SYMBOL("daAlink_hookshotAtHitCallBack",
* void(fopAc_ac_c*, dCcD_GObjInf*, fopAc_ac_c*, dCcD_GObjInf*), HookshotHit);
*
* mods::hook_add_pre<LinkExecute>(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
* ambiguous and need the mangled form.
*/
#if defined(__GNUC__) && !defined(__clang__) && defined(__ELF__)
#define DEFINE_HOOK(target, alias) \
MOD_META_RECORD static constinit auto mod_meta_hook_##alias = \
::mods::detail::make_local_hook_record<(target), ::mods::FixedString{#target}>(); \
struct alias : ::mods::Hook<(target)> { \
static void* resolved_target() { return mod_meta_hook_##alias.resolved; } \
}
#else
#define DEFINE_HOOK(target, alias) \
[[maybe_unused]] static const void* const mod_meta_hook_##alias = \
&::mods::detail::HookRecordFor<(target), ::mods::FixedString{#target}>::Holder::record; \
struct alias : ::mods::Hook<(target)> { \
static void* resolved_target() { \
return ::mods::detail::HookRecordFor<(target), \
::mods::FixedString{#target}>::Holder::record.resolved; \
} \
}
#endif
#define DEFINE_HOOK_SYMBOL(name, sig, alias) \
MOD_META_RECORD static constinit auto mod_meta_hook_##alias = \
::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; } \
}
template <class Entry>
ModResult hook_install(const HookService* hooks) {
if (hooks == nullptr) {
return MOD_UNAVAILABLE;
}
return hook::install<Entry>(hooks);
}
Entry::hooks = hooks;
if (Entry::target == nullptr) {
void* resolved = Entry::resolved_target();
if (resolved == nullptr) {
return MOD_UNAVAILABLE;
}
Entry::target = resolved;
}
return hooks->install(mod_ctx, Entry::target, reinterpret_cast<void*>(Entry::trampoline),
reinterpret_cast<void**>(&Entry::g_orig));
template <class Entry>
ModResult hook_install() {
return hook::install<Entry>();
}
template <class Entry>
ModResult hook_add_pre(
const HookService* hooks, HookPreFn callback, const HookOptions* options = nullptr) {
const ModResult installed = hook_install<Entry>(hooks);
if (installed != MOD_OK) {
return installed;
}
return hook::add_pre<Entry>(hooks, callback, options);
}
return hooks->add_pre(mod_ctx, Entry::target, callback, options);
template <class Entry>
ModResult hook_add_pre(HookPreFn callback, const HookOptions* options = nullptr) {
return hook::add_pre<Entry>(callback, options);
}
template <class Entry>
ModResult hook_add_post(
const HookService* hooks, HookPostFn callback, const HookOptions* options = nullptr) {
const ModResult installed = hook_install<Entry>(hooks);
if (installed != MOD_OK) {
return installed;
}
return hook::add_post<Entry>(hooks, callback, options);
}
return hooks->add_post(mod_ctx, Entry::target, callback, options);
template <class Entry>
ModResult hook_add_post(HookPostFn callback, const HookOptions* options = nullptr) {
return hook::add_post<Entry>(callback, options);
}
template <class Entry>
ModResult hook_replace(
const HookService* hooks, HookReplaceFn callback, const HookOptions* options = nullptr) {
const ModResult installed = hook_install<Entry>(hooks);
if (installed != MOD_OK) {
return installed;
}
return hook::replace<Entry>(hooks, callback, options);
}
return hooks->replace(mod_ctx, Entry::target, callback, options);
template <class Entry>
ModResult hook_replace(HookReplaceFn callback, const HookOptions* options = nullptr) {
return hook::replace<Entry>(callback, options);
}
} // namespace mods
+2 -2
View File
@@ -40,13 +40,13 @@ inline ModResult set_error(ModError* outError, ModResult code, const char* messa
}; \
}
// Declares `static const service_type* variable`, filled in by the host before mod_initialize.
// Defines `const service_type* variable`, filled in by the host before mod_initialize.
// Required imports are guaranteed non-null (the mod fails to load otherwise); optional imports
// must be checked against nullptr before use. The unversioned macros use the latest minor version;
// set an explicit version to target an older minor version for backwards compatibility.
#define IMPORT_SERVICE_EX( \
service_type, variable, service_id_value, major_value, min_minor_value, flags_value) \
static const service_type* variable = nullptr; \
const service_type* variable = nullptr; \
MOD_META_RECORD static constinit ModMetaImport mod_meta_import_##variable = { \
{sizeof(ModMetaImport), MOD_META_IMPORT, static_cast<uint8_t>(flags_value)}, \
static_cast<uint16_t>(major_value), \
+6 -10
View File
@@ -2,6 +2,10 @@
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
#define CAMERA_SERVICE_ID "dev.twilitrealm.dusklight.camera"
#define CAMERA_SERVICE_MAJOR 1u
#define CAMERA_SERVICE_MINOR 0u
@@ -53,13 +57,5 @@ typedef struct CameraService {
ModResult (*get_camera)(ModContext* ctx, const void* game_view, CameraInfo* out_info);
} CameraService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct mods::ServiceTraits<CameraService> {
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;
};
#endif
MOD_DECLARE_SERVICE(
CameraService, svc_camera, CAMERA_SERVICE_ID, CAMERA_SERVICE_MAJOR, CAMERA_SERVICE_MINOR);
+6 -10
View File
@@ -2,6 +2,10 @@
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
#define CONFIG_SERVICE_ID "dev.twilitrealm.dusklight.config"
#define CONFIG_SERVICE_MAJOR 1u
#define CONFIG_SERVICE_MINOR 0u
@@ -96,13 +100,5 @@ typedef struct ConfigService {
ModResult (*unsubscribe)(ModContext* ctx, ConfigSubscriptionHandle handle);
} ConfigService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct mods::ServiceTraits<ConfigService> {
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;
};
#endif
MOD_DECLARE_SERVICE(
ConfigService, svc_config, CONFIG_SERVICE_ID, CONFIG_SERVICE_MAJOR, CONFIG_SERVICE_MINOR);
+5 -10
View File
@@ -2,6 +2,10 @@
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
/*
* The mod SDK imports this service automatically for mods built with FEATURES game; service-only
* and asset-only mods do not require it.
@@ -19,13 +23,4 @@ typedef struct GameService {
ServiceHeader header;
} GameService;
#ifdef __cplusplus
#include <mods/service.hpp>
template <>
struct mods::ServiceTraits<GameService> {
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;
};
#endif
MOD_DECLARE_SERVICE(GameService, svc_game, GAME_SERVICE_ID, GAME_SERVICE_MAJOR, GAME_SERVICE_MINOR);
+72 -12
View File
@@ -1,6 +1,11 @@
#pragma once
#include <mods/api.h>
#include <mods/svc/window.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
#if !defined(DUSK_BUILDING_GAME) && !defined(DUSK_MOD_FEATURE_WEBGPU)
#error "mods/svc/gfx.h requires add_mod(... FEATURES webgpu)"
@@ -28,7 +33,7 @@
#define GFX_SERVICE_ID "dev.twilitrealm.dusklight.gfx"
#define GFX_SERVICE_MAJOR 1u
#define GFX_SERVICE_MINOR 0u
#define GFX_SERVICE_MINOR 1u
/* Maximum size for push_draw payload */
#define GFX_INLINE_DRAW_PAYLOAD_SIZE 128u
@@ -37,6 +42,7 @@
typedef uint64_t GfxDrawTypeHandle;
typedef uint64_t GfxStageHookHandle;
typedef uint64_t GfxComputeTypeHandle;
typedef uint64_t GfxPresentTargetHandle;
/* A suballocation in one of the shared per-frame streaming buffers. */
typedef struct GfxRange {
@@ -56,11 +62,13 @@ typedef struct GfxDeviceInfo {
WGPUTextureFormat depth_format; /* scene depth target format */
uint32_t sample_count; /* scene pass MSAA sample count */
bool uses_reversed_z; /* true means depth 1.0 is near */
WGPUInstance instance; /* borrowed; added in GfxService 1.1 */
WGPUAdapter adapter; /* borrowed; added in GfxService 1.1 */
} GfxDeviceInfo;
#define GFX_DEVICE_INFO_INIT \
{sizeof(GfxDeviceInfo), NULL, NULL, WGPUTextureFormat_Undefined, WGPUTextureFormat_Undefined, \
1u, false}
1u, false, NULL, NULL}
/*
* Passed to GfxDrawFn on the render worker thread; valid only during the call. The pass pipeline,
@@ -168,6 +176,48 @@ typedef struct GfxComputeTypeDesc {
#define GFX_COMPUTE_TYPE_DESC_INIT {sizeof(GfxComputeTypeDesc), NULL, NULL, NULL}
/*
* Invoked on the render worker while the frame encoder is open. The target texture and view have
* been acquired by the host and are borrowed for the callback. Record all target work on encoder,
* leave no pass open, and do not finish, submit, or present it. The host submits the shared command
* buffer and presents the target after submission. The streaming buffers contain data appended on
* the game thread before push_present.
*/
typedef struct GfxPresentContext {
uint32_t struct_size;
WGPUDevice device;
WGPUQueue queue;
WGPUCommandEncoder encoder;
WGPUTexture target_texture;
WGPUTextureView target_view;
WGPUTextureFormat target_format;
uint32_t target_width;
uint32_t target_height;
WGPUBuffer vertex_buffer;
WGPUBuffer index_buffer;
WGPUBuffer uniform_buffer;
WGPUBuffer storage_buffer;
} GfxPresentContext;
typedef void (*GfxPresentFn)(ModContext* ctx, const GfxPresentContext* present_ctx,
const void* payload, size_t payload_size, void* user_data);
typedef struct GfxPresentTargetDesc {
uint32_t struct_size;
const char* label; /* optional debug label */
uint32_t width; /* required for raw surfaces; ignored for WindowService windows */
uint32_t height;
WGPUTextureUsage usage; /* 0 defaults to RenderAttachment */
WGPUTextureFormat preferred_format;
WGPUCompositeAlphaMode preferred_alpha_mode;
GfxPresentFn render;
void* user_data;
} GfxPresentTargetDesc;
#define GFX_PRESENT_TARGET_DESC_INIT \
{sizeof(GfxPresentTargetDesc), NULL, 0u, 0u, WGPUTextureUsage_None, \
WGPUTextureFormat_Undefined, WGPUCompositeAlphaMode_Auto, NULL, NULL}
typedef struct GfxService {
ServiceHeader header;
@@ -200,15 +250,25 @@ typedef struct GfxService {
ModResult (*resolve_pass)(
ModContext* ctx, const GfxResolveDesc* desc, GfxResolvedTargets* out_targets);
ModResult (*create_pass)(ModContext* ctx, uint32_t width, uint32_t height);
/* Minor version 1 */
ModResult (*register_present_target)(ModContext* ctx, WGPUSurface surface,
const GfxPresentTargetDesc* desc, GfxPresentTargetHandle* out_handle);
ModResult (*register_window_present_target)(ModContext* ctx, WindowHandle window,
const GfxPresentTargetDesc* desc, GfxPresentTargetHandle* out_handle);
/* Raw-surface targets only; WindowService target resizes are managed automatically. */
ModResult (*resize_present_target)(
ModContext* ctx, GfxPresentTargetHandle handle, uint32_t width, uint32_t height);
ModResult (*unregister_present_target)(ModContext* ctx, GfxPresentTargetHandle handle);
/*
* MOD_OK means the task was queued.
* MOD_UNAVAILABLE means no task could be queued now (for example, a window has no pixel size).
* MOD_ERROR means an earlier task found the surface lost or deterministically invalid;
* unregister and recreate the target before pushing again.
*/
ModResult (*push_present)(
ModContext* ctx, GfxPresentTargetHandle handle, const void* payload, size_t payload_size);
} GfxService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct mods::ServiceTraits<GfxService> {
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;
};
#endif
MOD_DECLARE_SERVICE(GfxService, svc_gfx, GFX_SERVICE_ID, GFX_SERVICE_MAJOR, GFX_SERVICE_MINOR);
+8 -13
View File
@@ -2,9 +2,13 @@
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
/*
* Intercept game functions by address. Prefer the typed helpers in mods/hook.hpp
* (hook_add_pre/hook_add_post/hook_replace over a &Class::method): they generate the
* Intercept game functions by address. Prefer the typed helpers in mods/svc/hook.hpp
* (mods::hook::add_pre/add_post/replace over a &Class::method): they generate the
* trampoline and hide install/dispatch, which are the low-level primitives those helpers
* build. resolve() maps a symbol name to an address for targets you can't name at compile time
* (file-local statics included).
@@ -46,7 +50,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 mods::arg<T> / arg_ref<T> from mods/hook.hpp. `userdata` is the pointer
* them through mods::arg<T> / arg_ref<T> from mods/svc/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);
@@ -114,13 +118,4 @@ typedef struct HookService {
ModContext* ctx, const char* symbol, void** out_addr, HookSymbolFlags* out_flags);
} HookService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct mods::ServiceTraits<HookService> {
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;
};
#endif
MOD_DECLARE_SERVICE(HookService, svc_hook, HOOK_SERVICE_ID, HOOK_SERVICE_MAJOR, HOOK_SERVICE_MINOR);
+242
View File
@@ -0,0 +1,242 @@
#pragma once
#if !defined(DUSK_BUILDING_GAME) && !defined(DUSK_MOD_FEATURE_GAME)
#error "DEFINE_HOOK requires add_mod(... FEATURES game)"
#endif
#include <mods/svc/hook.h>
#include <memory>
#include <type_traits>
namespace mods {
template <class T>
T arg(void* argsRaw, int n) noexcept {
void** args = static_cast<void**>(argsRaw);
return *static_cast<std::add_pointer_t<std::remove_reference_t<T>>>(args[n]);
}
template <class T>
std::remove_reference_t<T>& arg_ref(void* argsRaw, int n) noexcept {
void** args = static_cast<void**>(argsRaw);
return *static_cast<std::add_pointer_t<std::remove_reference_t<T>>>(args[n]);
}
/*
* Trampoline generator + per-target state. Tag makes each hooked target's statics distinct; the
* target address comes from the declaration's metadata record, resolved by the host at mod
* initialization.
*/
template <class Tag, class R, class... A>
struct HookImpl {
static inline R (*g_orig)(A...) = nullptr;
static inline const HookService* hooks = nullptr;
static inline void* target = nullptr;
static bool dispatch_pre(void* args, void* retval) {
if (hooks == nullptr) {
return false;
}
int skipOriginal = 0;
const ModResult result = hooks->dispatch_pre(mod_ctx, target, args, retval, &skipOriginal);
return result == MOD_OK && skipOriginal != 0;
}
static void dispatch_post(void* args, void* retval) {
if (hooks != nullptr) {
hooks->dispatch_post(mod_ctx, target, args, retval);
}
}
static R trampoline(A... args) {
if constexpr (sizeof...(A) == 0) {
if constexpr (std::is_void_v<R>) {
const bool skipOriginal = dispatch_pre(nullptr, nullptr);
if (!skipOriginal) {
g_orig(args...);
}
dispatch_post(nullptr, nullptr);
} else {
R result{};
const bool skipOriginal =
dispatch_pre(nullptr, static_cast<void*>(std::addressof(result)));
if (!skipOriginal) {
result = g_orig(args...);
}
dispatch_post(nullptr, static_cast<void*>(std::addressof(result)));
return result;
}
} else {
void* ptrs[] = {static_cast<void*>(std::addressof(args))...};
if constexpr (std::is_void_v<R>) {
const bool skipOriginal = dispatch_pre(static_cast<void*>(ptrs), nullptr);
if (!skipOriginal) {
g_orig(args...);
}
dispatch_post(static_cast<void*>(ptrs), nullptr);
} else {
R result{};
const bool skipOriginal = dispatch_pre(
static_cast<void*>(ptrs), static_cast<void*>(std::addressof(result)));
if (!skipOriginal) {
result = g_orig(args...);
}
dispatch_post(static_cast<void*>(ptrs), static_cast<void*>(std::addressof(result)));
return result;
}
}
}
};
namespace detail {
template <auto Target>
using TargetTag = std::integral_constant<decltype(Target), Target>;
template <FixedString Name>
struct NameTag {};
} // namespace detail
/*
* Typed base for a hook on a function named at compile time (&daAlink_c::execute, &free_fn).
* Instantiate through DEFINE_HOOK, which pairs it with the metadata record the host resolves.
*/
template <auto Target>
struct Hook;
template <class C, class R, class... A, R (C::*Target)(A...)>
struct Hook<Target> : HookImpl<detail::TargetTag<Target>, R, C*, A...> {};
template <class C, class R, class... A, R (C::*Target)(A...) const>
struct Hook<Target> : HookImpl<detail::TargetTag<Target>, R, const C*, A...> {};
template <class R, class... A, R (*Target)(A...)>
struct Hook<Target> : HookImpl<detail::TargetTag<Target>, R, A...> {};
/*
* Typed base for a hook on a function by its symbol name, for targets you can't name in C++:
* file-local statics, private members, or symbols without a header. The signature is written
* free-style with the receiver first and is *not* compiler-checked. Instantiate through
* DEFINE_HOOK_SYMBOL.
*/
template <FixedString Name, class Sig>
struct NamedHook;
template <FixedString Name, class R, class... A>
struct NamedHook<Name, R(A...)> : HookImpl<detail::NameTag<Name>, R, A...> {};
/*
* Declare a hook target. The declaration emits a metadata record that the host resolves at mod
* initialization. Every hook target must be declared.
*
* DEFINE_HOOK(&daAlink_c::execute, LinkExecute);
* DEFINE_HOOK_SYMBOL("daAlink_hookshotAtHitCallBack",
* void(fopAc_ac_c*, dCcD_GObjInf*, fopAc_ac_c*, dCcD_GObjInf*), HookshotHit);
*
* mods::hook::add_pre<LinkExecute>(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
* ambiguous and need the mangled form.
*/
#if defined(__GNUC__) && !defined(__clang__) && defined(__ELF__)
#define DEFINE_HOOK(target, alias) \
MOD_META_RECORD static constinit auto mod_meta_hook_##alias = \
::mods::detail::make_local_hook_record<(target), ::mods::FixedString{#target}>(); \
struct alias : ::mods::Hook<(target)> { \
static void* resolved_target() { return mod_meta_hook_##alias.resolved; } \
}
#else
#define DEFINE_HOOK(target, alias) \
[[maybe_unused]] static const void* const mod_meta_hook_##alias = \
&::mods::detail::HookRecordFor<(target), ::mods::FixedString{#target}>::Holder::record; \
struct alias : ::mods::Hook<(target)> { \
static void* resolved_target() { \
return ::mods::detail::HookRecordFor<(target), \
::mods::FixedString{#target}>::Holder::record.resolved; \
} \
}
#endif
#define DEFINE_HOOK_SYMBOL(name, sig, alias) \
MOD_META_RECORD static constinit auto mod_meta_hook_##alias = \
::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; } \
}
namespace hook {
template <class Entry>
ModResult install(const HookService* hooks) {
if (hooks == nullptr) {
return MOD_UNAVAILABLE;
}
Entry::hooks = hooks;
if (Entry::target == nullptr) {
void* resolved = Entry::resolved_target();
if (resolved == nullptr) {
return MOD_UNAVAILABLE;
}
Entry::target = resolved;
}
return hooks->install(mod_ctx, Entry::target, reinterpret_cast<void*>(Entry::trampoline),
reinterpret_cast<void**>(&Entry::g_orig));
}
template <class Entry>
ModResult install() {
return install<Entry>(svc_hook);
}
template <class Entry>
ModResult add_pre(
const HookService* hooks, HookPreFn callback, const HookOptions* options = nullptr) {
const ModResult installed = install<Entry>(hooks);
if (installed != MOD_OK) {
return installed;
}
return hooks->add_pre(mod_ctx, Entry::target, callback, options);
}
template <class Entry>
ModResult add_pre(HookPreFn callback, const HookOptions* options = nullptr) {
return add_pre<Entry>(svc_hook, callback, options);
}
template <class Entry>
ModResult add_post(
const HookService* hooks, HookPostFn callback, const HookOptions* options = nullptr) {
const ModResult installed = install<Entry>(hooks);
if (installed != MOD_OK) {
return installed;
}
return hooks->add_post(mod_ctx, Entry::target, callback, options);
}
template <class Entry>
ModResult add_post(HookPostFn callback, const HookOptions* options = nullptr) {
return add_post<Entry>(svc_hook, callback, options);
}
template <class Entry>
ModResult replace(
const HookService* hooks, HookReplaceFn callback, const HookOptions* options = nullptr) {
const ModResult installed = install<Entry>(hooks);
if (installed != MOD_OK) {
return installed;
}
return hooks->replace(mod_ctx, Entry::target, callback, options);
}
template <class Entry>
ModResult replace(HookReplaceFn callback, const HookOptions* options = nullptr) {
return replace<Entry>(svc_hook, callback, options);
}
} // namespace hook
} // namespace mods
+5 -10
View File
@@ -2,6 +2,10 @@
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
/*
* The host service: the calling mod's identity and its runtime interface to the loader.
* Always available; every other service can be reached from it.
@@ -103,13 +107,4 @@ typedef struct HostService {
const char* (*native_dir)(ModContext* ctx);
} HostService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct mods::ServiceTraits<HostService> {
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;
};
#endif
MOD_DECLARE_SERVICE(HostService, svc_host, HOST_SERVICE_ID, HOST_SERVICE_MAJOR, HOST_SERVICE_MINOR);
+5 -10
View File
@@ -2,6 +2,10 @@
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
/*
* Logging into the game's console and log files. Messages are attributed to the calling mod
* (prefixed with its ID).
@@ -36,13 +40,4 @@ typedef struct LogService {
void (*error)(ModContext* ctx, const char* message);
} LogService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct mods::ServiceTraits<LogService> {
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;
};
#endif
MOD_DECLARE_SERVICE(LogService, svc_log, LOG_SERVICE_ID, LOG_SERVICE_MAJOR, LOG_SERVICE_MINOR);
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#if !defined(DUSK_BUILDING_GAME) && !defined(DUSK_MOD_FEATURE_FMT)
#error "mods/svc/log.hpp requires add_mod(... FEATURES fmt)"
#endif
#include <mods/svc/log.h>
#include <fmt/format.h>
#include <utility>
namespace mods::log {
template <typename... Args>
void write(LogLevel level, fmt::format_string<Args...> formatString, Args&&... args) {
const auto message = fmt::format(formatString, std::forward<Args>(args)...);
svc_log->write(mod_ctx, level, message.c_str());
}
template <typename... Args>
void trace(fmt::format_string<Args...> formatString, Args&&... args) {
write(LOG_LEVEL_TRACE, formatString, std::forward<Args>(args)...);
}
template <typename... Args>
void debug(fmt::format_string<Args...> formatString, Args&&... args) {
write(LOG_LEVEL_DEBUG, formatString, std::forward<Args>(args)...);
}
template <typename... Args>
void info(fmt::format_string<Args...> formatString, Args&&... args) {
write(LOG_LEVEL_INFO, formatString, std::forward<Args>(args)...);
}
template <typename... Args>
void warn(fmt::format_string<Args...> formatString, Args&&... args) {
write(LOG_LEVEL_WARN, formatString, std::forward<Args>(args)...);
}
template <typename... Args>
void error(fmt::format_string<Args...> formatString, Args&&... args) {
write(LOG_LEVEL_ERROR, formatString, std::forward<Args>(args)...);
}
} // namespace mods::log
+6 -10
View File
@@ -2,6 +2,10 @@
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
#define OVERLAY_SERVICE_ID "dev.twilitrealm.dusklight.overlay"
#define OVERLAY_SERVICE_MAJOR 1u
#define OVERLAY_SERVICE_MINOR 0u
@@ -46,13 +50,5 @@ typedef struct OverlayService {
ModResult (*remove)(ModContext* ctx, OverlayHandle handle);
} OverlayService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct mods::ServiceTraits<OverlayService> {
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;
};
#endif
MOD_DECLARE_SERVICE(
OverlayService, svc_overlay, OVERLAY_SERVICE_ID, OVERLAY_SERVICE_MAJOR, OVERLAY_SERVICE_MINOR);
+6 -10
View File
@@ -2,6 +2,10 @@
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
/*
* Read-only access to the res/ tree of the calling mod's own bundle. Reload serves the new
* bundle's contents. For writable storage, use HostService::mod_dir.
@@ -41,13 +45,5 @@ typedef struct ResourceService {
void (*free)(ModContext* ctx, ResourceBuffer* buffer);
} ResourceService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct mods::ServiceTraits<ResourceService> {
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;
};
#endif
MOD_DECLARE_SERVICE(ResourceService, svc_resource, RESOURCE_SERVICE_ID, RESOURCE_SERVICE_MAJOR,
RESOURCE_SERVICE_MINOR);
+8 -12
View File
@@ -2,6 +2,10 @@
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
#define TEXTURE_SERVICE_ID "dev.twilitrealm.dusklight.texture"
#define TEXTURE_SERVICE_MAJOR 1u
#define TEXTURE_SERVICE_MINOR 0u
@@ -70,20 +74,12 @@ typedef struct TextureService {
* "tex1_{w}x{h}_{hash}_{fmt}.dds"); "_mipN" sidecars next to it are picked up automatically.
* The file is decoded lazily on first use by the renderer.
*/
ModResult (*register_file)(ModContext* ctx, const char* bundle_path,
TextureReplacementHandle* out_handle);
ModResult (*register_file)(
ModContext* ctx, const char* bundle_path, TextureReplacementHandle* out_handle);
/* Remove a replacement previously registered by the calling mod. */
ModResult (*unregister)(ModContext* ctx, TextureReplacementHandle handle);
} TextureService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct mods::ServiceTraits<TextureService> {
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;
};
#endif
MOD_DECLARE_SERVICE(
TextureService, svc_texture, TEXTURE_SERVICE_ID, TEXTURE_SERVICE_MAJOR, TEXTURE_SERVICE_MINOR);
+5 -10
View File
@@ -3,6 +3,10 @@
#include <mods/api.h>
#include <mods/svc/config.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
#define UI_SERVICE_ID "dev.twilitrealm.dusklight.ui"
#define UI_SERVICE_MAJOR 1u
#define UI_SERVICE_MINOR 0u
@@ -273,13 +277,4 @@ typedef struct UiService {
ModResult (*unregister_menu_tab)(ModContext* ctx, UiMenuTabHandle tab);
} UiService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct mods::ServiceTraits<UiService> {
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;
};
#endif
MOD_DECLARE_SERVICE(UiService, svc_ui, UI_SERVICE_ID, UI_SERVICE_MAJOR, UI_SERVICE_MINOR);
+103
View File
@@ -0,0 +1,103 @@
#pragma once
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
#include <limits.h>
#define WINDOW_SERVICE_ID "dev.twilitrealm.dusklight.window"
#define WINDOW_SERVICE_MAJOR 1u
#define WINDOW_SERVICE_MINOR 0u
#define WINDOW_POSITION_UNDEFINED INT32_MIN
typedef uint64_t WindowHandle;
typedef enum WindowFlags {
WINDOW_FLAG_NONE = 0u,
WINDOW_FLAG_RESIZABLE = 1u << 0u,
WINDOW_FLAG_HIDDEN = 1u << 1u,
WINDOW_FLAG_BORDERLESS = 1u << 2u,
WINDOW_FLAG_ALWAYS_ON_TOP = 1u << 3u,
WINDOW_FLAG_TRANSPARENT = 1u << 4u,
} WindowFlags;
typedef enum WindowEventType {
WINDOW_EVENT_CLOSE_REQUESTED = 0,
WINDOW_EVENT_RESIZED = 1,
WINDOW_EVENT_MOVED = 2,
WINDOW_EVENT_FOCUS_GAINED = 3,
WINDOW_EVENT_FOCUS_LOST = 4,
WINDOW_EVENT_SHOWN = 5,
WINDOW_EVENT_HIDDEN = 6,
} WindowEventType;
typedef struct WindowEvent {
uint32_t struct_size;
WindowEventType type;
int32_t x;
int32_t y;
uint32_t width;
uint32_t height;
uint32_t pixel_width;
uint32_t pixel_height;
float display_scale;
} WindowEvent;
typedef void (*WindowEventFn)(
ModContext* ctx, WindowHandle window, const WindowEvent* event, void* user_data);
typedef struct WindowDesc {
uint32_t struct_size;
const char* title;
uint32_t width;
uint32_t height;
int32_t x;
int32_t y;
uint32_t flags;
WindowEventFn on_event;
void* user_data;
} WindowDesc;
#define WINDOW_DESC_INIT \
{sizeof(WindowDesc), NULL, 640u, 480u, WINDOW_POSITION_UNDEFINED, WINDOW_POSITION_UNDEFINED, \
WINDOW_FLAG_RESIZABLE | WINDOW_FLAG_HIDDEN, NULL, NULL}
typedef struct WindowInfo {
uint32_t struct_size;
int32_t x;
int32_t y;
uint32_t width;
uint32_t height;
uint32_t pixel_width;
uint32_t pixel_height;
float display_scale;
bool visible;
bool focused;
} WindowInfo;
#define WINDOW_INFO_INIT {sizeof(WindowInfo), 0, 0, 0u, 0u, 0u, 0u, 1.0f, false, false}
/*
* Auxiliary native windows. All functions and callbacks run on the game thread. Window close
* events are requests; the window remains alive until destroy_window is called. Any graphics
* present target attached to a window must be unregistered before the window can be destroyed;
* at most one present target may be attached to a window at a time.
*/
typedef struct WindowService {
ServiceHeader header;
ModResult (*create_window)(ModContext* ctx, const WindowDesc* desc, WindowHandle* out_window);
ModResult (*destroy_window)(ModContext* ctx, WindowHandle window);
ModResult (*show_window)(ModContext* ctx, WindowHandle window);
ModResult (*hide_window)(ModContext* ctx, WindowHandle window);
ModResult (*set_title)(ModContext* ctx, WindowHandle window, const char* title);
ModResult (*set_size)(ModContext* ctx, WindowHandle window, uint32_t width, uint32_t height);
ModResult (*get_info)(ModContext* ctx, WindowHandle window, WindowInfo* out_info);
} WindowService;
MOD_DECLARE_SERVICE(
WindowService, svc_window, WINDOW_SERVICE_ID, WINDOW_SERVICE_MAJOR, WINDOW_SERVICE_MINOR);