mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-24 05:46:51 -04:00
Mod API: Static import/export/hook metadata & resolution
This commit is contained in:
@@ -67,9 +67,32 @@ struct ModSearchDir {
|
||||
std::filesystem::path nativeLibDir;
|
||||
};
|
||||
|
||||
struct ModMetaParsed {
|
||||
uint32_t abiVersion = 0;
|
||||
std::vector<ModMetaImport*> imports;
|
||||
std::vector<ModMetaExport*> exports;
|
||||
std::vector<ModMetaHookFn*> hookFns;
|
||||
std::vector<ModMetaHookMem*> hookMems;
|
||||
std::vector<ModMetaHookName*> hookNames;
|
||||
};
|
||||
|
||||
inline const char* hook_mem_vtable_symbol(const ModMetaHookMem& rec) {
|
||||
return reinterpret_cast<const char*>(&rec) + sizeof(ModMetaHookMem);
|
||||
}
|
||||
|
||||
inline const char* hook_mem_display_name(const ModMetaHookMem& rec) {
|
||||
const char* vtable = hook_mem_vtable_symbol(rec);
|
||||
return vtable + std::char_traits<char>::length(vtable) + 1;
|
||||
}
|
||||
|
||||
inline const char* hook_name_symbol(const ModMetaHookName& rec) {
|
||||
return reinterpret_cast<const char*>(&rec) + sizeof(ModMetaHookName);
|
||||
}
|
||||
|
||||
struct NativeMod {
|
||||
std::unique_ptr<loader::NativeModule> handle;
|
||||
const ModManifest* manifest = nullptr;
|
||||
const ModMeta* meta = nullptr;
|
||||
ModMetaParsed parsed;
|
||||
ModContext** contextSymbol = nullptr;
|
||||
|
||||
ModInitializeFn fn_initialize = nullptr;
|
||||
@@ -111,6 +134,11 @@ enum class NativeModStatus : u8 {
|
||||
*/
|
||||
MissingExport,
|
||||
|
||||
/**
|
||||
* Mod's metadata record section is malformed.
|
||||
*/
|
||||
InvalidMetadata,
|
||||
|
||||
/**
|
||||
* Unknown error loading the native mod.
|
||||
*/
|
||||
@@ -126,7 +154,7 @@ struct LoadedMod {
|
||||
// Native lib is dlopen'd in place and stays resident for the session. Reload is unsupported.
|
||||
bool inPlace = false;
|
||||
|
||||
std::unique_ptr<ConfigVar<bool> > cvarIsEnabled;
|
||||
std::unique_ptr<ConfigVar<bool>> cvarIsEnabled;
|
||||
config::Subscription enabledSubscription = 0;
|
||||
|
||||
bool active = false;
|
||||
@@ -200,7 +228,7 @@ private:
|
||||
std::string path;
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<LoadedMod> > m_mods;
|
||||
std::vector<std::unique_ptr<LoadedMod>> m_mods;
|
||||
std::vector<ModSearchDir> m_searchDirs;
|
||||
std::filesystem::path m_cacheDir;
|
||||
std::vector<Request> m_pendingRequests;
|
||||
|
||||
+114
-24
@@ -23,7 +23,7 @@ extern "C" {
|
||||
#define MOD_EXTERN_C
|
||||
#endif
|
||||
|
||||
#define MOD_ABI_VERSION 5u
|
||||
#define MOD_ABI_VERSION 6u
|
||||
#define MOD_ERROR_MESSAGE_SIZE 512u
|
||||
|
||||
typedef struct ModContext ModContext;
|
||||
@@ -37,7 +37,8 @@ typedef enum ModResult {
|
||||
MOD_INVALID_ARGUMENT = 5,
|
||||
} ModResult;
|
||||
|
||||
static_assert(sizeof(ModResult) == 4, "mod SDK enums must be int-sized; do not build mods with -fshort-enums");
|
||||
static_assert(sizeof(ModResult) == 4,
|
||||
"mod SDK enums must be int-sized; do not build mods with -fshort-enums");
|
||||
|
||||
typedef struct ModError {
|
||||
uint32_t struct_size;
|
||||
@@ -108,41 +109,130 @@ typedef enum ServiceExportFlags {
|
||||
SERVICE_EXPORT_DEFERRED = 1u << 0u,
|
||||
} ServiceExportFlags;
|
||||
|
||||
typedef struct ServiceImport {
|
||||
uint32_t struct_size;
|
||||
const char* service_id;
|
||||
/*
|
||||
* Mod metadata records.
|
||||
*
|
||||
* A mod's manifest is a sequence of records in a dedicated section of the native library ("modmeta"
|
||||
* on ELF, "__DATA,__modmeta" on Mach-O, "modmeta$a/$d/$z" on PE).
|
||||
*
|
||||
* The records are pure data. Every string is inline and NUL-terminated. Fields documented as
|
||||
* runtime-only hold relocated pointers that are meaningless on disk; static parsers recover their
|
||||
* targets from the file's relocation/bind entries instead.
|
||||
*
|
||||
* Layout rules:
|
||||
* - Little-endian, 8-byte aligned; every record size is a multiple of 8.
|
||||
* - Parsers must skip all-zero 8-byte units (linker padding) and records of unknown kind.
|
||||
* - Exactly one MOD_META_HEADER record per library.
|
||||
*
|
||||
* The IMPORT_SERVICE/EXPORT_SERVICE/DEFINE_HOOK macros emit these records; mods do not construct
|
||||
* them by hand.
|
||||
*/
|
||||
|
||||
#define MOD_META_SERVICE_ID_SIZE 64u
|
||||
|
||||
/* Records are 8-byte aligned so the linker packs them without padding; most are naturally
|
||||
* aligned by their pointer fields, the alignment below covers the rest. */
|
||||
#if defined(__cplusplus)
|
||||
#define MOD_META_ALIGN alignas(8)
|
||||
#elif defined(_MSC_VER)
|
||||
#define MOD_META_ALIGN __declspec(align(8))
|
||||
#else
|
||||
#define MOD_META_ALIGN __attribute__((aligned(8)))
|
||||
#endif
|
||||
|
||||
typedef enum ModMetaKind {
|
||||
MOD_META_PAD = 0,
|
||||
MOD_META_HEADER = 1,
|
||||
MOD_META_IMPORT = 2,
|
||||
MOD_META_EXPORT = 3,
|
||||
MOD_META_HOOK_FN = 4,
|
||||
MOD_META_HOOK_MEM = 5,
|
||||
MOD_META_HOOK_NAME = 6,
|
||||
} ModMetaKind;
|
||||
|
||||
typedef struct ModMetaRecord {
|
||||
uint16_t size; /* total record size in bytes, a multiple of 8 */
|
||||
uint8_t kind; /* ModMetaKind */
|
||||
uint8_t flags; /* ServiceImportFlags / ServiceExportFlags for imports/exports */
|
||||
} ModMetaRecord;
|
||||
|
||||
typedef struct MOD_META_ALIGN ModMetaHeader {
|
||||
ModMetaRecord rec;
|
||||
uint32_t abi_version;
|
||||
} ModMetaHeader;
|
||||
|
||||
static_assert(sizeof(ModMetaHeader) == 8);
|
||||
|
||||
typedef struct MOD_META_ALIGN ModMetaImport {
|
||||
ModMetaRecord rec;
|
||||
uint16_t major_version;
|
||||
uint16_t min_minor_version;
|
||||
uint32_t flags;
|
||||
void* slot;
|
||||
} ServiceImport;
|
||||
void* slot; /* runtime only */
|
||||
char service_id[MOD_META_SERVICE_ID_SIZE];
|
||||
} ModMetaImport;
|
||||
|
||||
typedef struct ServiceExport {
|
||||
uint32_t struct_size;
|
||||
const char* service_id;
|
||||
static_assert(sizeof(ModMetaImport) == 16 + MOD_META_SERVICE_ID_SIZE);
|
||||
|
||||
typedef struct MOD_META_ALIGN ModMetaExport {
|
||||
ModMetaRecord rec;
|
||||
uint16_t major_version;
|
||||
uint16_t minor_version;
|
||||
uint32_t flags;
|
||||
const void* service;
|
||||
} ServiceExport;
|
||||
const void* service; /* runtime only */
|
||||
char service_id[MOD_META_SERVICE_ID_SIZE];
|
||||
} ModMetaExport;
|
||||
|
||||
typedef struct ModManifest {
|
||||
static_assert(sizeof(ModMetaExport) == 16 + MOD_META_SERVICE_ID_SIZE);
|
||||
|
||||
/* Hook on a function named at link time: `target` carries the &fn relocation. */
|
||||
typedef struct MOD_META_ALIGN ModMetaHookFn {
|
||||
ModMetaRecord rec;
|
||||
uint32_t reserved;
|
||||
void* target; /* runtime only */
|
||||
void* resolved; /* runtime only */
|
||||
} ModMetaHookFn;
|
||||
|
||||
static_assert(sizeof(ModMetaHookFn) == 24);
|
||||
|
||||
/*
|
||||
* Hook on a member function: `pmf` holds the compiler's pointer-to-member representation
|
||||
* (non-virtual: a function address relocation; virtual Itanium/AAPCS: literal slot words). Two
|
||||
* NUL-terminated strings follow `resolved`: the class vtable symbol (empty if the class name is not
|
||||
* representable), then the stringified target for tooling display.
|
||||
*/
|
||||
typedef struct MOD_META_ALIGN ModMetaHookMem {
|
||||
ModMetaRecord rec;
|
||||
uint32_t reserved;
|
||||
unsigned char pmf[16];
|
||||
void* resolved; /* runtime only */
|
||||
} ModMetaHookMem;
|
||||
|
||||
static_assert(sizeof(ModMetaHookMem) == 32);
|
||||
|
||||
/*
|
||||
* Hook on a function by symbol name, for targets that cannot be named in C++ (file-local statics,
|
||||
* private members). One NUL-terminated string follows `resolved`; it may be either the platform
|
||||
* mangled name or the demangled qualified display name.
|
||||
*/
|
||||
typedef struct MOD_META_ALIGN ModMetaHookName {
|
||||
ModMetaRecord rec;
|
||||
uint32_t reserved;
|
||||
void* resolved; /* runtime only */
|
||||
} ModMetaHookName;
|
||||
|
||||
static_assert(sizeof(ModMetaHookName) == 16);
|
||||
|
||||
typedef struct ModMeta {
|
||||
uint32_t struct_size;
|
||||
uint32_t abi_version;
|
||||
const ServiceImport* imports;
|
||||
size_t import_count;
|
||||
const ServiceExport* exports;
|
||||
size_t export_count;
|
||||
} ModManifest;
|
||||
const void* records_begin;
|
||||
const void* records_end;
|
||||
} ModMeta;
|
||||
|
||||
typedef const ModManifest* (*ModGetManifestFn)(void);
|
||||
MOD_EXPORT extern const ModMeta mod_meta;
|
||||
|
||||
typedef ModResult (*ModInitializeFn)(ModError* out_error);
|
||||
typedef ModResult (*ModUpdateFn)(ModError* out_error);
|
||||
typedef ModResult (*ModShutdownFn)(ModError* out_error);
|
||||
|
||||
MOD_EXPORT const ModManifest* mod_get_manifest(void);
|
||||
|
||||
MOD_EXPORT ModResult mod_initialize(ModError* out_error);
|
||||
MOD_EXPORT ModResult mod_update(ModError* out_error);
|
||||
MOD_EXPORT ModResult mod_shutdown(ModError* out_error);
|
||||
|
||||
+48
-309
@@ -2,11 +2,7 @@
|
||||
|
||||
#include "mods/svc/hook.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
namespace dusk::mods {
|
||||
@@ -14,255 +10,20 @@ namespace dusk::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]);
|
||||
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]);
|
||||
return *static_cast<std::add_pointer_t<std::remove_reference_t<T>>>(args[n]);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
void* mfp_addr(F fn) noexcept {
|
||||
void* p = nullptr;
|
||||
static_assert(sizeof(fn) >= sizeof(void*), "unexpected function pointer size");
|
||||
std::memcpy(&p, &fn, sizeof(void*));
|
||||
return p;
|
||||
}
|
||||
|
||||
/* A string usable as a template argument: carries the hook target's symbol name and
|
||||
* makes each NamedHook instantiation's static state unique. */
|
||||
template <size_t N>
|
||||
struct FixedString {
|
||||
char chars[N]{};
|
||||
constexpr FixedString(const char (&s)[N]) noexcept {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
chars[i] = s[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
constexpr std::string_view class_name() {
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
// "... class_name() [T = daAlink_c]" / "... [with T = daAlink_c; ...]"
|
||||
constexpr std::string_view fn = __PRETTY_FUNCTION__;
|
||||
constexpr size_t start = fn.find("T = ") + 4;
|
||||
return fn.substr(start, fn.find_first_of(";]", start) - start);
|
||||
#elif defined(_MSC_VER)
|
||||
// "... class_name<class daAlink_c>(void)"
|
||||
constexpr std::string_view fn = __FUNCSIG__;
|
||||
constexpr size_t start = fn.find("class_name<") + 11;
|
||||
constexpr std::string_view name = fn.substr(start, fn.rfind(">(") - start);
|
||||
if constexpr (name.starts_with("class ")) {
|
||||
return name.substr(6);
|
||||
} else if constexpr (name.starts_with("struct ")) {
|
||||
return name.substr(7);
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
#else
|
||||
#error "unsupported compiler"
|
||||
#endif
|
||||
}
|
||||
|
||||
/* The manifest name of C's vtable. Only unscoped, non-template class names are
|
||||
* supported (an empty result fails resolution and the install reports it). */
|
||||
template <class C>
|
||||
constexpr auto vtable_symbol() {
|
||||
constexpr std::string_view name = class_name<C>();
|
||||
constexpr bool simple = name.find_first_of(":<> ") == std::string_view::npos;
|
||||
// "_ZTV" + decimal length + name / "??_7" + name + "@@6B@", NUL-terminated
|
||||
std::array<char, name.size() + 12> out{};
|
||||
if constexpr (!simple) {
|
||||
return out;
|
||||
}
|
||||
size_t n = 0;
|
||||
#if defined(_WIN32)
|
||||
for (char c : {'?', '?', '_', '7'}) {
|
||||
out[n++] = c;
|
||||
}
|
||||
for (char c : name) {
|
||||
out[n++] = c;
|
||||
}
|
||||
for (char c : {'@', '@', '6', 'B', '@'}) {
|
||||
out[n++] = c;
|
||||
}
|
||||
#else
|
||||
for (char c : {'_', 'Z', 'T', 'V'}) {
|
||||
out[n++] = c;
|
||||
}
|
||||
size_t len = name.size();
|
||||
char digits[8]{};
|
||||
size_t d = 0;
|
||||
while (len != 0) {
|
||||
digits[d++] = static_cast<char>('0' + len % 10);
|
||||
len /= 10;
|
||||
}
|
||||
while (d != 0) {
|
||||
out[n++] = digits[--d];
|
||||
}
|
||||
for (char c : name) {
|
||||
out[n++] = c;
|
||||
}
|
||||
#endif
|
||||
return out;
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
/* Follow jump stubs, then match the MSVC vcall thunk a virtual mfp points at.
|
||||
* Returns the vtable slot's byte offset, or npos when fn is not a vcall thunk. */
|
||||
inline size_t vcall_slot_offset(const void*& fn) noexcept {
|
||||
constexpr size_t npos = static_cast<size_t>(-1);
|
||||
#if defined(_M_X64) || defined(__x86_64__)
|
||||
const auto* p = static_cast<const uint8_t*>(fn);
|
||||
for (int i = 0; i < 8 && p[0] == 0xE9; ++i) { // incremental-link stubs
|
||||
int32_t rel;
|
||||
std::memcpy(&rel, p + 1, 4);
|
||||
p += 5 + rel;
|
||||
}
|
||||
fn = p;
|
||||
// The vptr load. Unoptimized clang-cl thunks spill/reload rcx first
|
||||
// (push rax; mov [rsp], rcx; mov rcx, [rsp]), so scan a short window.
|
||||
const uint8_t* q = nullptr;
|
||||
for (int i = 0; i <= 12; ++i) {
|
||||
if (p[i] == 0x48 && p[i + 1] == 0x8B && p[i + 2] == 0x01) { // mov rax, [rcx]
|
||||
q = p + i + 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (q == nullptr) {
|
||||
return npos;
|
||||
}
|
||||
if (q[0] == 0xFF && q[1] == 0x20) { // jmp [rax] (MSVC)
|
||||
return 0;
|
||||
}
|
||||
if (q[0] == 0xFF && q[1] == 0x60) { // jmp [rax + imm8]
|
||||
return static_cast<int8_t>(q[2]);
|
||||
}
|
||||
if (q[0] == 0xFF && q[1] == 0xA0) { // jmp [rax + imm32]
|
||||
int32_t off;
|
||||
std::memcpy(&off, q + 2, 4);
|
||||
return off;
|
||||
}
|
||||
// clang-cl: mov rax, [rax + off]; (pop r10;) jmp rax. Requiring the jmp rax
|
||||
// distinguishes the thunk from an ordinary getter that begins the same way.
|
||||
if (q[0] == 0x48 && q[1] == 0x8B && (q[2] == 0x00 || q[2] == 0x40 || q[2] == 0x80)) {
|
||||
size_t off = 0;
|
||||
const uint8_t* r = q + 3;
|
||||
if (q[2] == 0x40) {
|
||||
off = static_cast<int8_t>(q[3]);
|
||||
r = q + 4;
|
||||
} else if (q[2] == 0x80) {
|
||||
int32_t off32;
|
||||
std::memcpy(&off32, q + 3, 4);
|
||||
off = off32;
|
||||
r = q + 7;
|
||||
}
|
||||
for (int i = 0; i <= 8; ++i) {
|
||||
if (r[i] == 0xFF && r[i + 1] == 0xE0) { // jmp rax (48 REX optional)
|
||||
return off;
|
||||
}
|
||||
}
|
||||
}
|
||||
return npos;
|
||||
#elif defined(_M_ARM64) || defined(__aarch64__)
|
||||
const auto* p = static_cast<const uint8_t*>(fn);
|
||||
uint32_t insn[3];
|
||||
for (int i = 0; i < 8; ++i) { // incremental-link `b` stubs
|
||||
std::memcpy(insn, p, 4);
|
||||
if ((insn[0] & 0xFC000000u) != 0x14000000u) {
|
||||
break;
|
||||
}
|
||||
const auto imm26 = static_cast<int32_t>(insn[0] << 6) >> 6;
|
||||
p += static_cast<intptr_t>(imm26) * 4;
|
||||
}
|
||||
fn = p;
|
||||
std::memcpy(insn, p, 12);
|
||||
// ldr Xt, [x0]; ldr Xs, [Xt, #imm12*8]; br Xs
|
||||
if ((insn[0] & 0xFFFFFFE0u) != 0xF9400000u) {
|
||||
return npos;
|
||||
}
|
||||
const uint32_t t = insn[0] & 0x1Fu;
|
||||
if ((insn[1] & 0xFFC003E0u) != (0xF9400000u | (t << 5))) {
|
||||
return npos;
|
||||
}
|
||||
const uint32_t s = insn[1] & 0x1Fu;
|
||||
if (insn[2] != (0xD61F0000u | (s << 5))) {
|
||||
return npos;
|
||||
}
|
||||
return ((insn[1] >> 10) & 0xFFFu) * 8;
|
||||
#else
|
||||
(void)fn;
|
||||
return npos;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Code address of the member function a mfp designates. Virtual mfps don't carry
|
||||
* one; recover it from the class's vtable (resolved from the symbol manifest), so
|
||||
* Hook works uniformly on virtual and non-virtual members. */
|
||||
template <class C, class F>
|
||||
ModResult member_target(const HookService* hooks, F mfp, void** out) {
|
||||
*out = nullptr;
|
||||
uintptr_t words[sizeof(F) > sizeof(uintptr_t) ? 2 : 1] = {};
|
||||
std::memcpy(words, &mfp, sizeof(words) < sizeof(F) ? sizeof(words) : sizeof(F));
|
||||
|
||||
#if defined(_WIN32)
|
||||
const void* fn = reinterpret_cast<const void*>(words[0]);
|
||||
const size_t slot = vcall_slot_offset(fn);
|
||||
if (slot == static_cast<size_t>(-1)) { // not a vcall thunk: direct address
|
||||
*out = const_cast<void*>(fn);
|
||||
return MOD_OK;
|
||||
}
|
||||
void* vtable = nullptr;
|
||||
const ModResult resolved = hooks->resolve(mod_ctx, vtable_symbol<C>().data(), &vtable, nullptr);
|
||||
if (resolved != MOD_OK) {
|
||||
return resolved;
|
||||
}
|
||||
// ??_7 points at the first slot.
|
||||
*out = *reinterpret_cast<void**>(static_cast<char*>(vtable) + slot);
|
||||
#else
|
||||
#if defined(__aarch64__) || defined(__arm__)
|
||||
// AAPCS C++ ABI: the virtual flag is bit 0 of the adjustment word (function
|
||||
// addresses can't spare their low bit), and ptr holds the slot offset directly.
|
||||
const bool isVirtual = (words[1] & 1) != 0;
|
||||
const uintptr_t thisAdjust = words[1] >> 1;
|
||||
const uintptr_t slotOffset = words[0];
|
||||
#else
|
||||
// Itanium C++ ABI: virtual mfps set bit 0 of ptr; the slot offset is ptr - 1.
|
||||
const bool isVirtual = (words[0] & 1) != 0;
|
||||
const uintptr_t thisAdjust = words[1];
|
||||
const uintptr_t slotOffset = words[0] - 1;
|
||||
#endif
|
||||
if (!isVirtual) { // non-virtual: the address itself
|
||||
*out = reinterpret_cast<void*>(words[0]);
|
||||
return MOD_OK;
|
||||
}
|
||||
if (thisAdjust != 0) {
|
||||
// this-adjusting mfp (member of a secondary base): the slot offset is
|
||||
// relative to a vtable we can't locate. Hook the overrider by name instead.
|
||||
return MOD_UNSUPPORTED;
|
||||
}
|
||||
void* vtable = nullptr;
|
||||
const ModResult resolved = hooks->resolve(mod_ctx, vtable_symbol<C>().data(), &vtable, nullptr);
|
||||
if (resolved != MOD_OK) {
|
||||
return resolved;
|
||||
}
|
||||
// _ZTV points at the offset-to-top slot; the address point mfps index from is
|
||||
// two pointers in (past offset-to-top and the typeinfo pointer).
|
||||
*out = *reinterpret_cast<void**>(static_cast<char*>(vtable) + 2 * sizeof(void*) + slotOffset);
|
||||
#endif
|
||||
return *out != nullptr ? MOD_OK : MOD_UNAVAILABLE;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/* Trampoline generator + per-target state shared by Hook and NamedHook. Tag makes
|
||||
* each hooked target's statics distinct; the target address is filled in at install. */
|
||||
/*
|
||||
* 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;
|
||||
@@ -333,60 +94,60 @@ struct NameTag {};
|
||||
} // namespace detail
|
||||
|
||||
/*
|
||||
* Typed hook on a function named at compile time (&daAlink_c::execute, &free_fn).
|
||||
* Member functions may be virtual: the install decodes the member function pointer and hooks the
|
||||
* class's own overrider.
|
||||
* 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...> {
|
||||
static ModResult resolve_target(const HookService* hooks, void** out) {
|
||||
return detail::member_target<C>(hooks, Target, out);
|
||||
}
|
||||
};
|
||||
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...> {
|
||||
static ModResult resolve_target(const HookService* hooks, void** out) {
|
||||
return detail::member_target<C>(hooks, Target, out);
|
||||
}
|
||||
};
|
||||
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...> {
|
||||
static ModResult resolve_target(const HookService*, void** out) {
|
||||
*out = mfp_addr(Target);
|
||||
return MOD_OK;
|
||||
}
|
||||
};
|
||||
struct Hook<Target> : HookImpl<detail::TargetTag<Target>, R, A...> {};
|
||||
|
||||
/*
|
||||
* Typed 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.
|
||||
*
|
||||
* using HookshotHit = dusk::mods::NamedHook<
|
||||
* "daAlink_hookshotAtHitCallBack",
|
||||
* void(fopAc_ac_c*, dCcD_GObjInf*, fopAc_ac_c*, dCcD_GObjInf*)>;
|
||||
* dusk::mods::hook_add_pre<HookshotHit>(svc_hook, on_hookshot_hit);
|
||||
* 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...> {
|
||||
static ModResult resolve_target(const HookService* hooks, void** out) {
|
||||
HookSymbolFlags flags{};
|
||||
const ModResult resolved = hooks->resolve(mod_ctx, Name.chars, out, &flags);
|
||||
if (resolved == MOD_OK && (flags & HOOK_SYMBOL_CODE) == 0) {
|
||||
*out = nullptr;
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return resolved;
|
||||
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);
|
||||
*
|
||||
* dusk::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.
|
||||
*/
|
||||
#define DEFINE_HOOK(target, alias) \
|
||||
MOD_META_RECORD static constinit auto mod_meta_hook_##alias = \
|
||||
::dusk::mods::detail::make_hook_record<(target), ::dusk::mods::FixedString{#target}>(); \
|
||||
struct alias : ::dusk::mods::Hook<(target)> { \
|
||||
static void* resolved_target() { return mod_meta_hook_##alias.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> { \
|
||||
static void* resolved_target() { return mod_meta_hook_##alias.resolved; } \
|
||||
}
|
||||
};
|
||||
|
||||
template <class Entry>
|
||||
ModResult hook_install(const HookService* hooks) {
|
||||
@@ -396,20 +157,16 @@ ModResult hook_install(const HookService* hooks) {
|
||||
|
||||
Entry::hooks = hooks;
|
||||
if (Entry::target == nullptr) {
|
||||
const ModResult resolved = Entry::resolve_target(hooks, &Entry::target);
|
||||
if (resolved != MOD_OK) {
|
||||
return resolved;
|
||||
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 <auto Target>
|
||||
ModResult hook_install(const HookService* hooks) {
|
||||
return hook_install<Hook<Target> >(hooks);
|
||||
}
|
||||
|
||||
template <class Entry>
|
||||
ModResult hook_add_pre(
|
||||
const HookService* hooks, HookPreFn callback, const HookOptions* options = nullptr) {
|
||||
@@ -421,12 +178,6 @@ ModResult hook_add_pre(
|
||||
return hooks->add_pre(mod_ctx, Entry::target, callback, options);
|
||||
}
|
||||
|
||||
template <auto Target>
|
||||
ModResult hook_add_pre(
|
||||
const HookService* hooks, HookPreFn callback, const HookOptions* options = nullptr) {
|
||||
return hook_add_pre<Hook<Target> >(hooks, callback, options);
|
||||
}
|
||||
|
||||
template <class Entry>
|
||||
ModResult hook_add_post(
|
||||
const HookService* hooks, HookPostFn callback, const HookOptions* options = nullptr) {
|
||||
@@ -438,12 +189,6 @@ ModResult hook_add_post(
|
||||
return hooks->add_post(mod_ctx, Entry::target, callback, options);
|
||||
}
|
||||
|
||||
template <auto Target>
|
||||
ModResult hook_add_post(
|
||||
const HookService* hooks, HookPostFn callback, const HookOptions* options = nullptr) {
|
||||
return hook_add_post<Hook<Target> >(hooks, callback, options);
|
||||
}
|
||||
|
||||
template <class Entry>
|
||||
ModResult hook_replace(
|
||||
const HookService* hooks, HookReplaceFn callback, const HookOptions* options = nullptr) {
|
||||
@@ -455,10 +200,4 @@ ModResult hook_replace(
|
||||
return hooks->replace(mod_ctx, Entry::target, callback, options);
|
||||
}
|
||||
|
||||
template <auto Target>
|
||||
ModResult hook_replace(
|
||||
const HookService* hooks, HookReplaceFn callback, const HookOptions* options = nullptr) {
|
||||
return hook_replace<Hook<Target> >(hooks, callback, options);
|
||||
}
|
||||
|
||||
} // namespace dusk::mods
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
#pragma once
|
||||
|
||||
#include "mods/api.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
/*
|
||||
* modmeta records. Each IMPORT_SERVICE/EXPORT_SERVICE/DEFINE_HOOK use places one
|
||||
* constant-initialized record object in the metadata section.
|
||||
*/
|
||||
#if defined(_WIN32)
|
||||
#pragma section("modmeta$a", read, write)
|
||||
#pragma section("modmeta$d", read, write)
|
||||
#pragma section("modmeta$z", read, write)
|
||||
#define MOD_META_RECORD __declspec(allocate("modmeta$d"))
|
||||
#elif defined(__APPLE__)
|
||||
#define MOD_META_RECORD __attribute__((section("__DATA,__modmeta"), used))
|
||||
#elif defined(__has_attribute) && __has_attribute(retain)
|
||||
#define MOD_META_RECORD __attribute__((section("modmeta"), used, retain))
|
||||
#else
|
||||
#define MOD_META_RECORD __attribute__((section("modmeta"), used))
|
||||
#endif
|
||||
|
||||
/* Section bounds for the mod_meta descriptor */
|
||||
#if defined(_WIN32)
|
||||
#define MOD_META_BOUNDS_DEFN \
|
||||
extern "C" { \
|
||||
__declspec(allocate("modmeta$a")) constinit unsigned long long mod_meta_bounds_begin = 0; \
|
||||
__declspec(allocate("modmeta$z")) constinit unsigned long long mod_meta_bounds_end = 0; \
|
||||
}
|
||||
#define MOD_META_BOUNDS_BEGIN (&mod_meta_bounds_begin)
|
||||
#define MOD_META_BOUNDS_END (&mod_meta_bounds_end)
|
||||
#elif defined(__APPLE__)
|
||||
extern "C" const unsigned char mod_meta_bounds_begin[] __asm("section$start$__DATA$__modmeta");
|
||||
extern "C" const unsigned char mod_meta_bounds_end[] __asm("section$end$__DATA$__modmeta");
|
||||
#define MOD_META_BOUNDS_DEFN
|
||||
#define MOD_META_BOUNDS_BEGIN (mod_meta_bounds_begin)
|
||||
#define MOD_META_BOUNDS_END (mod_meta_bounds_end)
|
||||
#else
|
||||
extern "C" const unsigned char __start_modmeta[];
|
||||
extern "C" const unsigned char __stop_modmeta[];
|
||||
#define MOD_META_BOUNDS_DEFN
|
||||
#define MOD_META_BOUNDS_BEGIN (__start_modmeta)
|
||||
#define MOD_META_BOUNDS_END (__stop_modmeta)
|
||||
#endif
|
||||
|
||||
namespace dusk::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. */
|
||||
template <size_t N>
|
||||
struct FixedString {
|
||||
char chars[N]{};
|
||||
constexpr FixedString(const char (&s)[N]) noexcept {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
chars[i] = s[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
constexpr std::string_view class_name() {
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
// "... class_name() [T = daAlink_c]" / "... [with T = daAlink_c; ...]"
|
||||
constexpr std::string_view fn = __PRETTY_FUNCTION__;
|
||||
constexpr size_t start = fn.find("T = ") + 4;
|
||||
return fn.substr(start, fn.find_first_of(";]", start) - start);
|
||||
#elif defined(_MSC_VER)
|
||||
// "... class_name<class daAlink_c>(void)"
|
||||
constexpr std::string_view fn = __FUNCSIG__;
|
||||
constexpr size_t start = fn.find("class_name<") + 11;
|
||||
constexpr std::string_view name = fn.substr(start, fn.rfind(">(") - start);
|
||||
if constexpr (name.starts_with("class ")) {
|
||||
return name.substr(6);
|
||||
} else if constexpr (name.starts_with("struct ")) {
|
||||
return name.substr(7);
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
#else
|
||||
#error "unsupported compiler"
|
||||
#endif
|
||||
}
|
||||
|
||||
/* The symbol name of C's vtable. Only unscoped, non-template class names are supported (an
|
||||
* empty result makes hooks on virtual members of C fail resolution, which is reported). */
|
||||
template <class C>
|
||||
constexpr auto vtable_symbol() {
|
||||
constexpr std::string_view name = class_name<C>();
|
||||
constexpr bool simple = name.find_first_of(":<> ") == std::string_view::npos;
|
||||
// "_ZTV" + decimal length + name / "??_7" + name + "@@6B@", NUL-terminated
|
||||
std::array<char, name.size() + 12> out{};
|
||||
if constexpr (!simple) {
|
||||
return out;
|
||||
}
|
||||
size_t n = 0;
|
||||
#if defined(_WIN32)
|
||||
for (char c : {'?', '?', '_', '7'}) {
|
||||
out[n++] = c;
|
||||
}
|
||||
for (char c : name) {
|
||||
out[n++] = c;
|
||||
}
|
||||
for (char c : {'@', '@', '6', 'B', '@'}) {
|
||||
out[n++] = c;
|
||||
}
|
||||
#else
|
||||
for (char c : {'_', 'Z', 'T', 'V'}) {
|
||||
out[n++] = c;
|
||||
}
|
||||
size_t len = name.size();
|
||||
char digits[8]{};
|
||||
size_t d = 0;
|
||||
while (len != 0) {
|
||||
digits[d++] = static_cast<char>('0' + len % 10);
|
||||
len /= 10;
|
||||
}
|
||||
while (d != 0) {
|
||||
out[n++] = digits[--d];
|
||||
}
|
||||
for (char c : name) {
|
||||
out[n++] = c;
|
||||
}
|
||||
#endif
|
||||
return out;
|
||||
}
|
||||
|
||||
template <class F>
|
||||
struct member_traits;
|
||||
template <class C, class R, class... A>
|
||||
struct member_traits<R (C::*)(A...)> {
|
||||
using Class = C;
|
||||
};
|
||||
template <class C, class R, class... A>
|
||||
struct member_traits<R (C::*)(A...) const> {
|
||||
using Class = C;
|
||||
};
|
||||
|
||||
consteval size_t cstr_len(const char* s) {
|
||||
size_t n = 0;
|
||||
while (s[n] != '\0') {
|
||||
++n;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
consteval void copy_service_id(char (&dst)[MOD_META_SERVICE_ID_SIZE], const char* id) {
|
||||
size_t n = 0;
|
||||
for (; id[n] != '\0'; ++n) {
|
||||
if (n + 1 >= MOD_META_SERVICE_ID_SIZE) {
|
||||
throw "service id exceeds MOD_META_SERVICE_ID_SIZE";
|
||||
}
|
||||
dst[n] = id[n];
|
||||
}
|
||||
}
|
||||
|
||||
consteval ModMetaImport make_import(
|
||||
const char* serviceId, uint16_t major, uint16_t minMinor, uint8_t flags, void* slot) {
|
||||
ModMetaImport r{};
|
||||
r.rec = {sizeof(ModMetaImport), MOD_META_IMPORT, flags};
|
||||
r.major_version = major;
|
||||
r.min_minor_version = minMinor;
|
||||
r.slot = slot;
|
||||
copy_service_id(r.service_id, serviceId);
|
||||
return r;
|
||||
}
|
||||
|
||||
consteval ModMetaExport make_export(
|
||||
const char* serviceId, uint16_t major, uint16_t minor, uint8_t flags, const void* service) {
|
||||
ModMetaExport r{};
|
||||
r.rec = {sizeof(ModMetaExport), MOD_META_EXPORT, flags};
|
||||
r.major_version = major;
|
||||
r.minor_version = minor;
|
||||
r.service = service;
|
||||
copy_service_id(r.service_id, serviceId);
|
||||
return r;
|
||||
}
|
||||
|
||||
consteval ModMetaHeader make_header() {
|
||||
ModMetaHeader r{};
|
||||
r.rec = {sizeof(ModMetaHeader), MOD_META_HEADER, 0};
|
||||
r.abi_version = MOD_ABI_VERSION;
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Typed record variants: embedding the target as its native type makes the compiler emit the
|
||||
* on-disk representation (relocations, PMF slot words) that static parsers read; the layouts
|
||||
* match the byte-view structs in api.h.
|
||||
*/
|
||||
|
||||
template <class F>
|
||||
struct HookFnRecord {
|
||||
ModMetaRecord rec;
|
||||
uint32_t reserved;
|
||||
F target;
|
||||
void* resolved;
|
||||
};
|
||||
|
||||
template <class F, size_t N>
|
||||
struct HookMemRecord {
|
||||
ModMetaRecord rec;
|
||||
uint32_t reserved;
|
||||
union {
|
||||
F fn;
|
||||
unsigned char raw[16];
|
||||
} pmf;
|
||||
void* resolved;
|
||||
char names[N];
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
struct HookNameRecord {
|
||||
ModMetaRecord rec;
|
||||
uint32_t reserved;
|
||||
void* resolved;
|
||||
char name[N];
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
constexpr size_t align_up(size_t n) {
|
||||
return (n + (N - 1)) & ~(N - 1);
|
||||
}
|
||||
|
||||
template <auto Target, FixedString Disp>
|
||||
consteval auto make_hook_record() {
|
||||
using F = decltype(Target);
|
||||
// Strip the leading '&' of the stringified target expression for display.
|
||||
constexpr size_t dispFrom = Disp.chars[0] == '&' ? 1 : 0;
|
||||
constexpr size_t dispLen = sizeof(Disp.chars) - 1 - dispFrom;
|
||||
if constexpr (std::is_member_function_pointer_v<F>) {
|
||||
using C = member_traits<F>::Class;
|
||||
static_assert(sizeof(F) <= 16, "unsupported pointer-to-member representation");
|
||||
constexpr auto vtbl = vtable_symbol<C>();
|
||||
constexpr size_t vtblLen = std::string_view{vtbl.data()}.size();
|
||||
constexpr size_t n = align_up<8>(vtblLen + 1 + dispLen + 1);
|
||||
HookMemRecord<F, n> r{};
|
||||
r.rec = {sizeof(r), MOD_META_HOOK_MEM, 0};
|
||||
r.pmf.fn = Target;
|
||||
size_t at = 0;
|
||||
for (size_t i = 0; i < vtblLen; ++i) {
|
||||
r.names[at++] = vtbl[i];
|
||||
}
|
||||
r.names[at++] = '\0';
|
||||
for (size_t i = 0; i < dispLen; ++i) {
|
||||
r.names[at++] = Disp.chars[dispFrom + i];
|
||||
}
|
||||
return r;
|
||||
} else {
|
||||
static_assert(std::is_pointer_v<F> && std::is_function_v<std::remove_pointer_t<F>>,
|
||||
"hook target must be a function or member function");
|
||||
HookFnRecord<F> r{};
|
||||
r.rec = {sizeof(r), MOD_META_HOOK_FN, 0};
|
||||
r.target = Target;
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
template <FixedString Name>
|
||||
consteval auto make_hook_name_record() {
|
||||
constexpr size_t len = sizeof(Name.chars) - 1;
|
||||
HookNameRecord<align_up<8>(len + 1)> r{};
|
||||
r.rec = {sizeof(r), MOD_META_HOOK_NAME, 0};
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
r.name[i] = Name.chars[i];
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace dusk::mods
|
||||
+21
-78
@@ -1,60 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "mods/api.h"
|
||||
#include "mods/meta.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace dusk::mods {
|
||||
|
||||
template <class Service>
|
||||
struct ServiceTraits;
|
||||
|
||||
namespace detail {
|
||||
|
||||
inline std::vector<ServiceImport>& imports() {
|
||||
static std::vector<ServiceImport> entries;
|
||||
return entries;
|
||||
}
|
||||
|
||||
inline std::vector<ServiceExport>& exports() {
|
||||
static std::vector<ServiceExport> entries;
|
||||
return entries;
|
||||
}
|
||||
|
||||
inline int register_import(ServiceImport entry) {
|
||||
imports().push_back(entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline int register_export(ServiceExport entry) {
|
||||
exports().push_back(entry);
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline const ModManifest* manifest() {
|
||||
static ModManifest manifest{
|
||||
sizeof(ModManifest),
|
||||
MOD_ABI_VERSION,
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
0,
|
||||
};
|
||||
|
||||
auto& importEntries = imports();
|
||||
auto& exportEntries = exports();
|
||||
manifest.imports = importEntries.data();
|
||||
manifest.import_count = importEntries.size();
|
||||
manifest.exports = exportEntries.data();
|
||||
manifest.export_count = exportEntries.size();
|
||||
return &manifest;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
inline ModResult set_error(ModError* outError, ModResult code, const char* message) {
|
||||
if (outError != nullptr && outError->struct_size >= sizeof(ModError)) {
|
||||
outError->code = code;
|
||||
@@ -71,9 +28,16 @@ inline ModResult set_error(ModError* outError, ModResult code, const char* messa
|
||||
#define DEFINE_MOD() \
|
||||
extern "C" { \
|
||||
MOD_EXPORT ModContext* mod_ctx = nullptr; \
|
||||
MOD_EXPORT const ModManifest* mod_get_manifest(void) { \
|
||||
return ::dusk::mods::detail::manifest(); \
|
||||
} \
|
||||
MOD_META_RECORD static constinit ModMetaHeader mod_meta_header_record = \
|
||||
::dusk::mods::detail::make_header(); \
|
||||
MOD_META_BOUNDS_DEFN \
|
||||
extern "C" { \
|
||||
MOD_EXPORT constinit const ModMeta mod_meta = { \
|
||||
sizeof(ModMeta), \
|
||||
MOD_META_BOUNDS_BEGIN, \
|
||||
MOD_META_BOUNDS_END, \
|
||||
}; \
|
||||
}
|
||||
|
||||
// Declares `static const service_type* variable`, filled in by the host before mod_initialize.
|
||||
@@ -82,15 +46,10 @@ inline ModResult set_error(ModError* outError, ModResult code, const char* messa
|
||||
#define IMPORT_SERVICE_EX( \
|
||||
service_type, variable, service_id_value, major_value, min_minor_value, flags_value) \
|
||||
static const service_type* variable = nullptr; \
|
||||
[[maybe_unused]] static const int mod_import_registration_##variable = \
|
||||
::dusk::mods::detail::register_import(ServiceImport{ \
|
||||
sizeof(ServiceImport), \
|
||||
(service_id_value), \
|
||||
static_cast<uint16_t>(major_value), \
|
||||
static_cast<uint16_t>(min_minor_value), \
|
||||
static_cast<uint32_t>(flags_value), \
|
||||
&(variable), \
|
||||
})
|
||||
MOD_META_RECORD static constinit ModMetaImport mod_meta_import_##variable = \
|
||||
::dusk::mods::detail::make_import((service_id_value), static_cast<uint16_t>(major_value), \
|
||||
static_cast<uint16_t>(min_minor_value), static_cast<uint8_t>(flags_value), \
|
||||
&(variable))
|
||||
|
||||
#define IMPORT_SERVICE_VERSION(service_type, variable, min_minor_value) \
|
||||
IMPORT_SERVICE_EX(service_type, variable, ::dusk::mods::ServiceTraits<service_type>::id, \
|
||||
@@ -108,31 +67,15 @@ inline ModResult set_error(ModError* outError, ModResult code, const char* messa
|
||||
IMPORT_OPTIONAL_SERVICE_VERSION(service_type, variable, 0)
|
||||
|
||||
#define EXPORT_SERVICE_AS(instance, service_id_value) \
|
||||
namespace { \
|
||||
const int mod_export_registration_##instance = \
|
||||
::dusk::mods::detail::register_export(ServiceExport{ \
|
||||
sizeof(ServiceExport), \
|
||||
(service_id_value), \
|
||||
(instance).header.major_version, \
|
||||
(instance).header.minor_version, \
|
||||
SERVICE_EXPORT_STATIC, \
|
||||
&(instance), \
|
||||
}); \
|
||||
}
|
||||
MOD_META_RECORD static constinit ModMetaExport mod_meta_export_##instance = \
|
||||
::dusk::mods::detail::make_export((service_id_value), (instance).header.major_version, \
|
||||
(instance).header.minor_version, SERVICE_EXPORT_STATIC, &(instance))
|
||||
|
||||
#define EXPORT_SERVICE(instance) \
|
||||
EXPORT_SERVICE_AS( \
|
||||
instance, ::dusk::mods::ServiceTraits<std::remove_cv_t<decltype(instance)> >::id)
|
||||
instance, ::dusk::mods::ServiceTraits<std::remove_cv_t<decltype(instance)>>::id)
|
||||
|
||||
#define EXPORT_DEFERRED_SERVICE(token, service_id_value, major_value, minor_value) \
|
||||
namespace { \
|
||||
const int mod_deferred_export_registration_##token = \
|
||||
::dusk::mods::detail::register_export(ServiceExport{ \
|
||||
sizeof(ServiceExport), \
|
||||
(service_id_value), \
|
||||
static_cast<uint16_t>(major_value), \
|
||||
static_cast<uint16_t>(minor_value), \
|
||||
SERVICE_EXPORT_DEFERRED, \
|
||||
nullptr, \
|
||||
}); \
|
||||
}
|
||||
MOD_META_RECORD static constinit ModMetaExport mod_meta_export_##token = \
|
||||
::dusk::mods::detail::make_export((service_id_value), static_cast<uint16_t>(major_value), \
|
||||
static_cast<uint16_t>(minor_value), SERVICE_EXPORT_DEFERRED, nullptr)
|
||||
|
||||
Reference in New Issue
Block a user