#pragma once #include "mods/api.h" #include #include #include #include /* * 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 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 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(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 constexpr auto vtable_symbol() { constexpr std::string_view name = class_name(); constexpr bool simple = name.find_first_of(":<> ") == std::string_view::npos; // "_ZTV" + decimal length + name / "??_7" + name + "@@6B@", NUL-terminated std::array 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('0' + len % 10); len /= 10; } while (d != 0) { out[n++] = digits[--d]; } for (char c : name) { out[n++] = c; } #endif return out; } template struct member_traits; template struct member_traits { using Class = C; }; template struct member_traits { 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 struct HookFnRecord { ModMetaRecord rec; uint32_t reserved; F target; void* resolved; }; template struct HookMemRecord { ModMetaRecord rec; uint32_t reserved; union { F fn; unsigned char raw[16]; } pmf; void* resolved; char names[N]; }; template struct HookNameRecord { ModMetaRecord rec; uint32_t reserved; void* resolved; char name[N]; }; template constexpr size_t align_up(size_t n) { return (n + (N - 1)) & ~(N - 1); } template 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) { using C = member_traits::Class; static_assert(sizeof(F) <= 16, "unsupported pointer-to-member representation"); constexpr auto vtbl = vtable_symbol(); constexpr size_t vtblLen = std::string_view{vtbl.data()}.size(); constexpr size_t n = align_up<8>(vtblLen + 1 + dispLen + 1); HookMemRecord 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 && std::is_function_v>, "hook target must be a function or member function"); HookFnRecord r{}; r.rec = {sizeof(r), MOD_META_HOOK_FN, 0}; r.target = Target; return r; } } template consteval auto make_hook_name_record() { constexpr size_t len = sizeof(Name.chars) - 1; HookNameRecord(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