mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-08 02:00:17 -04:00
Mods: WindowService, log wrappers, external rendering (#2251)
This commit is contained in:
+17
-1
@@ -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
@@ -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
|
||||
|
||||
@@ -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), \
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user