mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-23 13:42:25 -04:00
288 lines
8.7 KiB
C++
288 lines
8.7 KiB
C++
#pragma once
|
|
|
|
#include "mods/api.h"
|
|
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <string_view>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
/*
|
|
* 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)
|
|
#if defined(__clang__)
|
|
#define MOD_META_RECORD __declspec(allocate("modmeta$d")) __attribute__((used))
|
|
#else
|
|
#define MOD_META_RECORD __declspec(allocate("modmeta$d"))
|
|
#endif
|
|
#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 ModMetaServiceId make_service_id(const char* id) {
|
|
ModMetaServiceId out{};
|
|
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";
|
|
}
|
|
out.chars[n] = id[n];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
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 <size_t N>
|
|
struct HookMemNames {
|
|
char chars[N]{};
|
|
size_t len{};
|
|
};
|
|
|
|
template <auto Target, FixedString Disp>
|
|
consteval auto make_hook_mem_names() {
|
|
using C = member_traits<decltype(Target)>::Class;
|
|
// 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;
|
|
constexpr auto vtbl = vtable_symbol<C>();
|
|
constexpr size_t vtblLen = std::string_view{vtbl.data()}.size();
|
|
HookMemNames<align_up<8>(vtblLen + 1 + dispLen + 1)> r{};
|
|
size_t at = 0;
|
|
for (size_t i = 0; i < vtblLen; ++i) {
|
|
r.chars[at++] = vtbl[i];
|
|
}
|
|
r.chars[at++] = '\0';
|
|
for (size_t i = 0; i < dispLen; ++i) {
|
|
r.chars[at++] = Disp.chars[dispFrom + i];
|
|
}
|
|
r.len = sizeof(r.chars);
|
|
return r;
|
|
}
|
|
|
|
/*
|
|
* MSVC constant-evaluates a pointer-to-member only when every other operand in the
|
|
* initializer is a literal: no consteval calls, constexpr-object copies, or default
|
|
* member initializers.
|
|
*/
|
|
template <auto Target, char... Cs>
|
|
struct HookMemHolder {
|
|
using F = decltype(Target);
|
|
static_assert(sizeof(F) <= 16, "unsupported pointer-to-member representation");
|
|
MOD_META_RECORD static constinit inline HookMemRecord<F, sizeof...(Cs)> record = {
|
|
{sizeof(HookMemRecord<F, sizeof...(Cs)>), MOD_META_HOOK_MEM, 0}, 0, {Target}, nullptr,
|
|
{Cs...}};
|
|
};
|
|
|
|
template <auto Target>
|
|
struct HookFnHolder {
|
|
using F = decltype(Target);
|
|
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");
|
|
MOD_META_RECORD static constinit inline HookFnRecord<F> record = {
|
|
{sizeof(HookFnRecord<F>), MOD_META_HOOK_FN, 0}, 0, Target, nullptr};
|
|
};
|
|
|
|
template <auto Target, FixedString Disp,
|
|
bool = std::is_member_function_pointer_v<decltype(Target)>>
|
|
struct HookRecordFor {
|
|
using Holder = HookFnHolder<Target>;
|
|
};
|
|
|
|
template <auto Target, FixedString Disp>
|
|
struct HookRecordFor<Target, Disp, true> {
|
|
template <class Seq>
|
|
struct Bind;
|
|
template <size_t... Is>
|
|
struct Bind<std::index_sequence<Is...>> {
|
|
using Type = HookMemHolder<Target, make_hook_mem_names<Target, Disp>().chars[Is]...>;
|
|
};
|
|
using Holder =
|
|
Bind<std::make_index_sequence<make_hook_mem_names<Target, Disp>().len>>::Type;
|
|
};
|
|
|
|
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
|