Implement iOS hook prepatching & improve MSVC PTMF support (#2222)

This commit is contained in:
Luke Street
2026-07-17 22:35:37 -06:00
committed by GitHub
parent 1bae8a5e6a
commit 8caab1a6ba
12 changed files with 707 additions and 65 deletions
+10
View File
@@ -73,6 +73,7 @@ struct ModMetaParsed {
std::vector<ModMetaExport*> exports;
std::vector<ModMetaHookFn*> hookFns;
std::vector<ModMetaHookMem*> hookMems;
std::vector<ModMetaHookMemExt*> hookMemExts;
std::vector<ModMetaHookName*> hookNames;
};
@@ -85,6 +86,15 @@ inline const char* hook_mem_display_name(const ModMetaHookMem& rec) {
return vtable + std::char_traits<char>::length(vtable) + 1;
}
inline const char* hook_mem_vtable_symbol(const ModMetaHookMemExt& rec) {
return reinterpret_cast<const char*>(&rec) + sizeof(ModMetaHookMemExt);
}
inline const char* hook_mem_display_name(const ModMetaHookMemExt& 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);
}
+28
View File
@@ -26,6 +26,9 @@
#include "miniz.h"
#include "native_module.hpp"
#include "nlohmann/json.hpp"
#if DUSK_HAS_PREPATCH
#include "prepatch.hpp"
#endif
using namespace std::string_literals;
using namespace std::string_view_literals;
@@ -411,6 +414,28 @@ static bool parse_meta(NativeMod& native, LoadedMod& mod) {
parsed.hookMems.push_back(record);
break;
}
case MOD_META_HOOK_MEM_EXT: {
if (size <= sizeof(ModMetaHookMemExt)) {
return invalid("truncated extended hook record");
}
auto* record = reinterpret_cast<ModMetaHookMemExt*>(const_cast<uint8_t*>(cursor));
if (record->pmf_size <= MOD_META_HOOK_MEM_CAPACITY ||
record->pmf_size > MOD_META_HOOK_MEM_EXT_CAPACITY || record->materialize == nullptr)
{
return invalid("bad extended hook member-pointer size");
}
const char* strings = reinterpret_cast<const char*>(cursor) + sizeof(ModMetaHookMemExt);
const size_t capacity = size - sizeof(ModMetaHookMemExt);
if (!terminated_within(strings, capacity)) {
return invalid("unterminated extended hook vtable symbol");
}
const size_t vtableLen = std::char_traits<char>::length(strings);
if (!terminated_within(strings + vtableLen + 1, capacity - vtableLen - 1)) {
return invalid("unterminated extended hook display name");
}
parsed.hookMemExts.push_back(record);
break;
}
case MOD_META_HOOK_NAME: {
if (size <= sizeof(ModMetaHookName)) {
return invalid("truncated hook record");
@@ -906,6 +931,9 @@ void ModLoader::init() {
m_initialized = true;
manifest::initialize();
#if DUSK_HAS_PREPATCH
prepatch::initialize();
#endif
if (m_searchDirs.empty()) {
Log.warn("no mod search directories configured; mod loading skipped");
+260
View File
@@ -0,0 +1,260 @@
#include "prepatch.hpp"
#include <atomic>
#include <cstdint>
#include <cstring>
#include <limits>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "aurora/lib/logging.hpp"
#if DUSK_HAS_PREPATCH
#include <mach-o/dyld.h>
#include <mach-o/loader.h>
#endif
namespace dusk::mods::prepatch {
namespace {
aurora::Module Log("dusk::mods::prepatch");
constexpr std::string_view kSiteMagic = "PS01";
constexpr size_t kSiteHeaderSize = 12;
constexpr size_t kGatewaySize = 28;
constexpr size_t kOriginalStubOffset = 20;
constexpr uint32_t kBranchMask = 0xfc000000u;
constexpr uint32_t kBranch = 0x14000000u;
constexpr uint32_t kLdarX16X16 = 0xc8dffe10u;
constexpr uint32_t kCbzX16Stub = 0xb4000050u;
constexpr uint32_t kBrX16 = 0xd61f0200u;
constexpr uint32_t kBtiC = 0xd503245fu;
struct SiteHeader {
char magic[4];
int32_t targetDelta;
int32_t slotDelta;
};
static_assert(sizeof(SiteHeader) == kSiteHeaderSize);
struct Range {
uintptr_t begin = 0;
uintptr_t end = 0;
bool contains(uintptr_t address, size_t size) const {
return address >= begin && address <= end && size <= end - address;
}
};
struct State {
std::vector<Range> executableRanges;
std::vector<Range> writableRanges;
std::string failureReason = "not initialized";
bool loaded = false;
bool initialized = false;
};
State s_state;
void fail(std::string reason) {
s_state.failureReason = std::move(reason);
Log.error("prepatch backend unavailable: {}", s_state.failureReason);
}
void unavailable(std::string reason) {
s_state.failureReason = std::move(reason);
Log.info("prepatch backend unavailable: {}", s_state.failureReason);
}
bool add_delta(uintptr_t address, int64_t delta, uintptr_t& out) {
if (delta >= 0) {
const auto offset = static_cast<uintptr_t>(delta);
if (address > std::numeric_limits<uintptr_t>::max() - offset) {
return false;
}
out = address + offset;
} else {
const auto offset = static_cast<uintptr_t>(-(delta + 1)) + 1;
if (address < offset) {
return false;
}
out = address - offset;
}
return true;
}
bool branch_target(uintptr_t address, uint32_t instruction, uintptr_t& out) {
if ((instruction & kBranchMask) != kBranch) {
return false;
}
int64_t words = instruction & 0x03ffffffu;
if ((words & 0x02000000) != 0) {
words -= int64_t{1} << 26;
}
return add_delta(address, words * 4, out);
}
const Range* containing(const std::vector<Range>& ranges, uintptr_t address, size_t size) {
for (const auto& range : ranges) {
if (range.contains(address, size)) {
return &range;
}
}
return nullptr;
}
#if DUSK_HAS_PREPATCH
bool slide_address(intptr_t slide, uint64_t vmaddr, uintptr_t& out) {
if (vmaddr > std::numeric_limits<uintptr_t>::max()) {
return false;
}
return add_delta(static_cast<uintptr_t>(vmaddr), slide, out);
}
#endif
} // namespace
void initialize() {
if (s_state.initialized) {
return;
}
s_state.initialized = true;
#if DUSK_HAS_PREPATCH
const auto* imageHeader = reinterpret_cast<const mach_header_64*>(_dyld_get_image_header(0));
if (imageHeader == nullptr || imageHeader->magic != MH_MAGIC_64 ||
imageHeader->cputype != CPU_TYPE_ARM64 ||
(imageHeader->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E ||
(imageHeader->cpusubtype & CPU_SUBTYPE_ARM64_PTR_AUTH_MASK) != 0)
{
fail("main image is not a supported 64-bit arm64 Mach-O image");
return;
}
const intptr_t slide = _dyld_get_image_vmaddr_slide(0);
const auto* commands = reinterpret_cast<const uint8_t*>(imageHeader + 1);
size_t commandOffset = 0;
for (uint32_t i = 0; i < imageHeader->ncmds; ++i) {
if (commandOffset > imageHeader->sizeofcmds ||
imageHeader->sizeofcmds - commandOffset < sizeof(load_command))
{
fail("main image has malformed load commands");
return;
}
const auto* command = reinterpret_cast<const load_command*>(commands + commandOffset);
if (command->cmdsize < sizeof(load_command) ||
command->cmdsize > imageHeader->sizeofcmds - commandOffset)
{
fail("main image has malformed load commands");
return;
}
if (command->cmd == LC_SEGMENT_64) {
if (command->cmdsize < sizeof(segment_command_64)) {
fail("main image has a truncated segment command");
return;
}
const auto* segment = reinterpret_cast<const segment_command_64*>(command);
const uint64_t vmEnd = segment->vmaddr + segment->vmsize;
if (vmEnd < segment->vmaddr) {
fail("main image segment range overflows");
return;
}
uintptr_t begin = 0;
uintptr_t end = 0;
if (!slide_address(slide, segment->vmaddr, begin) ||
!slide_address(slide, vmEnd, end) || end < begin)
{
fail("main image runtime segment range overflows");
return;
}
constexpr vm_prot_t kRx = VM_PROT_READ | VM_PROT_EXECUTE;
constexpr vm_prot_t kRw = VM_PROT_READ | VM_PROT_WRITE;
if (segment->initprot == kRx && segment->maxprot == kRx) {
s_state.executableRanges.push_back({begin, end});
} else if (segment->initprot == kRw && segment->maxprot == kRw) {
s_state.writableRanges.push_back({begin, end});
}
}
commandOffset += command->cmdsize;
}
if (commandOffset != imageHeader->sizeofcmds || s_state.executableRanges.empty() ||
s_state.writableRanges.empty())
{
fail("main image has no usable executable or writable segments");
return;
}
s_state.failureReason.clear();
s_state.loaded = true;
#else
unavailable("prepatch support is not available in this build");
#endif
}
bool available() {
return s_state.loaded;
}
const char* unavailable_reason() {
return s_state.failureReason.c_str();
}
std::optional<Site> lookup(void* runtimeTarget) {
if (!s_state.loaded || runtimeTarget == nullptr) {
return std::nullopt;
}
const auto target = reinterpret_cast<uintptr_t>(runtimeTarget);
if (containing(s_state.executableRanges, target, sizeof(uint32_t)) == nullptr) {
return std::nullopt;
}
uint32_t entry = 0;
std::memcpy(&entry, reinterpret_cast<const void*>(target), sizeof(entry));
uintptr_t gateway = 0;
if (!branch_target(target, entry, gateway) || gateway < kSiteHeaderSize) {
return std::nullopt;
}
const uintptr_t headerAddress = gateway - kSiteHeaderSize;
if (containing(s_state.executableRanges, headerAddress, kSiteHeaderSize + kGatewaySize) ==
nullptr)
{
return std::nullopt;
}
SiteHeader header{};
std::memcpy(&header, reinterpret_cast<const void*>(headerAddress), sizeof(header));
if (std::memcmp(header.magic, kSiteMagic.data(), kSiteMagic.size()) != 0) {
return std::nullopt;
}
uintptr_t recordedTarget = 0;
uintptr_t slot = 0;
if (!add_delta(gateway, header.targetDelta, recordedTarget) || recordedTarget != target ||
!add_delta(gateway, header.slotDelta, slot) || (slot & (alignof(void*) - 1)) != 0 ||
containing(s_state.writableRanges, slot, sizeof(void*)) == nullptr)
{
return std::nullopt;
}
uint32_t instructions[7]{};
std::memcpy(instructions, reinterpret_cast<const void*>(gateway), sizeof(instructions));
uintptr_t original = 0;
if (instructions[2] != kLdarX16X16 || instructions[3] != kCbzX16Stub ||
instructions[4] != kBrX16 || instructions[5] != kBtiC ||
target > std::numeric_limits<uintptr_t>::max() - 4 ||
!branch_target(gateway + 24, instructions[6], original) || original != target + 4)
{
return std::nullopt;
}
return Site{
reinterpret_cast<void**>(slot), reinterpret_cast<void*>(gateway + kOriginalStubOffset)};
}
void publish(const Site& site, void* trampoline) {
std::atomic_ref slot{*site.slot};
slot.store(trampoline, std::memory_order_release);
}
} // namespace dusk::mods::prepatch
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <optional>
namespace dusk::mods::prepatch {
struct Site {
void** slot = nullptr;
void* original = nullptr;
};
void initialize();
bool available();
const char* unavailable_reason();
// Returns the target function's prepatched hook site, or nullopt if it is not prepatched.
std::optional<Site> lookup(void* runtimeTarget);
// Publishes a trampoline (or nullptr to deactivate).
void publish(const Site& site, void* trampoline);
} // namespace dusk::mods::prepatch
+213 -43
View File
@@ -1,6 +1,9 @@
#include "registry.hpp"
#include "dusk/mods/loader/loader.hpp"
#if DUSK_HAS_PREPATCH
#include "dusk/mods/loader/prepatch.hpp"
#endif
#include "dusk/mods/manifest.hpp"
#include "mods/svc/hook.h"
@@ -9,11 +12,14 @@
#include "dusk/mods/log_buffer.hpp"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <exception>
#include <fmt/format.h>
#if DUSK_HAS_FUNCHOOK
#include <funchook.h>
#endif
#include <string>
#include <unordered_map>
#include <vector>
@@ -48,8 +54,7 @@ struct HookSlot {
// One per mod that requested a hook on a target: its template-generated trampoline and the
// address of its Hook::g_orig, both living in the mod's dylib. Any candidate's trampoline
// is interchangeable (dispatch walks the shared HookSlot), so when the active installer's mod
// unloads, the funchook detour is handed off to a surviving candidate and every candidate's
// *orig_store is rewritten to the new original pointer.
// unloads, the backend is handed off to a surviving candidate.
struct HookCandidate {
ModContext* context = nullptr;
void* trampoline = nullptr;
@@ -57,8 +62,28 @@ struct HookCandidate {
uint64_t order = 0;
};
struct InstalledHook {
enum class BackendKind {
None,
#if DUSK_HAS_FUNCHOOK
Funchook,
#endif
#if DUSK_HAS_PREPATCH
Prepatch,
#endif
};
struct InstalledBackend {
BackendKind kind = BackendKind::None;
#if DUSK_HAS_FUNCHOOK
funchook_t* handle = nullptr;
#endif
#if DUSK_HAS_PREPATCH
prepatch::Site prepatchSite{};
#endif
};
struct InstalledHook {
InstalledBackend backend{};
void* original = nullptr;
ModContext* active = nullptr;
std::vector<HookCandidate> candidates;
@@ -205,7 +230,8 @@ void* resolve_target(void* addr) {
return addr;
}
funchook_t* install_trampoline(void* fnAddr, void* trampoline, void** outOriginal) {
#if DUSK_HAS_FUNCHOOK
funchook_t* install_funchook(void* fnAddr, void* trampoline, void** outOriginal) {
funchook_t* fh = funchook_create();
if (fh == nullptr) {
DuskLog.warn("HookSystem: funchook_create failed for {:p}", fnAddr);
@@ -214,18 +240,101 @@ funchook_t* install_trampoline(void* fnAddr, void* trampoline, void** outOrigina
void* fn = fnAddr;
const int prep = funchook_prepare(fh, &fn, trampoline);
if (prep == 0) {
*outOriginal = fn;
}
const int inst = prep == 0 ? funchook_install(fh, 0) : -1;
if (prep != 0 || inst != 0) {
const char* message = funchook_error_message(fh);
DuskLog.warn("HookSystem: funchook failed for {:p} (prepare={} install={}): {}", fnAddr,
prep, inst, message != nullptr && message[0] != '\0' ? message : "no details");
funchook_destroy(fh);
*outOriginal = nullptr;
return nullptr;
}
*outOriginal = fn;
return fh;
}
#endif
bool install_backend(
void* fnAddr, void* trampoline, InstalledBackend& outBackend, void** originalStore) {
#if DUSK_HAS_PREPATCH
if (const auto site = prepatch::lookup(fnAddr)) {
*originalStore = site->original;
prepatch::publish(*site, trampoline);
outBackend.kind = BackendKind::Prepatch;
outBackend.prepatchSite = *site;
return true;
}
#endif
#if DUSK_HAS_FUNCHOOK
funchook_t* handle = install_funchook(fnAddr, trampoline, originalStore);
if (handle == nullptr) {
return false;
}
outBackend.kind = BackendKind::Funchook;
outBackend.handle = handle;
return true;
#else
#if DUSK_HAS_PREPATCH
DuskLog.warn("HookSystem: prepatch backend cannot install target {:p}: {}", fnAddr,
prepatch::available() ? "target has no valid gateway" : prepatch::unavailable_reason());
#else
DuskLog.warn("HookSystem: no hook backend can install target {:p}", fnAddr);
#endif
return false;
#endif
}
void deactivate_backend(void* target, InstalledBackend& backend) {
#if DUSK_HAS_PREPATCH
if (backend.kind == BackendKind::Prepatch) {
prepatch::publish(backend.prepatchSite, nullptr);
backend = {};
return;
}
#endif
#if DUSK_HAS_FUNCHOOK
if (backend.kind == BackendKind::Funchook) {
const int uninst = funchook_uninstall(backend.handle, 0);
const int destr = funchook_destroy(backend.handle);
if (uninst != 0 || destr != 0) {
DuskLog.warn("HookSystem: funchook uninstall/destroy for {:p} returned {}/{}", target,
uninst, destr);
}
}
#else
(void)target;
#endif
backend = {};
}
bool handoff_backend(
void* target, InstalledHook& entry, const HookCandidate& candidate, void** outOriginal) {
#if DUSK_HAS_PREPATCH
if (entry.backend.kind == BackendKind::Prepatch) {
*candidate.origStore = entry.original;
prepatch::publish(entry.backend.prepatchSite, candidate.trampoline);
*outOriginal = entry.original;
return true;
}
#endif
#if DUSK_HAS_FUNCHOOK
InstalledBackend backend;
if (!install_backend(target, candidate.trampoline, backend, candidate.origStore)) {
return false;
}
entry.backend = backend;
*outOriginal = *candidate.origStore;
return true;
#else
(void)target;
(void)candidate;
(void)outOriginal;
return false;
#endif
}
ModResult hook_install(ModContext* context, void* fnAddr, void* trampolineFn, void** outOriginal) {
if (fnAddr == nullptr || trampolineFn == nullptr || outOriginal == nullptr) {
@@ -272,18 +381,16 @@ ModResult hook_install(ModContext* context, void* fnAddr, void* trampolineFn, vo
name != nullptr ? name : "?", fnAddr, mod_id_from_context(context));
}
void* original = nullptr;
funchook_t* fh = install_trampoline(fnAddr, trampolineFn, &original);
if (fh == nullptr) {
InstalledBackend backend;
if (!install_backend(fnAddr, trampolineFn, backend, outOriginal)) {
return MOD_ERROR;
}
auto& entry = s_installed[key];
entry.handle = fh;
entry.original = original;
entry.backend = backend;
entry.original = *outOriginal;
entry.active = context;
entry.candidates.push_back({context, trampolineFn, outOriginal, s_nextOrder++});
*outOriginal = original;
return MOD_OK;
}
@@ -543,20 +650,67 @@ bool resolve_symbol_checked(const char* symbol, bool requireCode, void** out, st
return false;
}
/* Decode a HOOK_MEM record's pointer-to-member representation into the target code address,
* mirroring what calling through the mfp would invoke. Virtual members hook the class's own
* overrider, read from its vtable (resolved from the symbol manifest). */
void* resolve_member_record(
const ModMetaHookMem& record, const char* vtableSymbol, std::string& why) {
uintptr_t words[2];
std::memcpy(words, record.pmf, sizeof(words));
/*
* Decodes a member hook record's pointer-to-member representation into the target code address.
* Virtual members prefer their named overrider, then fall back to reading the primary vtable slot.
*/
void* resolve_member_record(const unsigned char* pmf, size_t pmfSize, const char* vtableSymbol,
const char* displayName, std::string& why) {
if (pmfSize < sizeof(uintptr_t)) {
why = "truncated pointer-to-member representation";
return nullptr;
}
uintptr_t words[2]{};
std::memcpy(words, pmf, std::min(sizeof(words), pmfSize));
#if defined(_WIN32)
const void* fn = reinterpret_cast<const void*>(words[0]);
if (fn == nullptr) {
why = "null pointer-to-member target";
return nullptr;
}
const size_t slot = vcall_slot_offset(fn);
if (slot == static_cast<size_t>(-1)) { // not a vcall thunk: direct address
return const_cast<void*>(fn);
}
// A display name resolves the actual overrider rather than an ABI vcall thunk. Besides being
// more direct, this covers secondary vtables: the MSVC representation has `this` adjustment
// but does not have the base-path suffix used by the decorated vtable symbol.
std::string displayWhy;
void* displayTarget = nullptr;
if (displayName[0] != '\0' &&
resolve_symbol_checked(displayName, true, &displayTarget, displayWhy))
{
return displayTarget;
}
int32_t thisAdjustment = 0;
if (pmfSize >= sizeof(uintptr_t) + sizeof(thisAdjustment)) {
std::memcpy(&thisAdjustment, pmf + sizeof(uintptr_t), sizeof(thisAdjustment));
}
if (thisAdjustment != 0) {
why = fmt::format(
"virtual member requires a {}-byte this adjustment and its overrider did not "
"resolve by name ({})",
thisAdjustment, displayWhy);
return nullptr;
}
int32_t firstVirtualField = 0;
if (pmfSize > MOD_META_HOOK_MEM_CAPACITY && pmfSize >= sizeof(uintptr_t) + 2 * sizeof(int32_t))
{
std::memcpy(&firstVirtualField, pmf + sizeof(uintptr_t) + sizeof(int32_t), sizeof(int32_t));
}
int32_t secondVirtualField = 0;
if (pmfSize > MOD_META_HOOK_MEM_CAPACITY && pmfSize >= sizeof(uintptr_t) + 3 * sizeof(int32_t))
{
std::memcpy(
&secondVirtualField, pmf + sizeof(uintptr_t) + 2 * sizeof(int32_t), sizeof(int32_t));
}
if (firstVirtualField != 0 || secondVirtualField != 0) {
why = fmt::format("virtual-base member did not resolve by name ({})", displayWhy);
return nullptr;
}
if (vtableSymbol[0] == '\0') {
why = "class name is not representable as a vtable symbol";
return nullptr;
@@ -583,10 +737,18 @@ void* resolve_member_record(
if (!isVirtual) { // non-virtual: the address itself
return reinterpret_cast<void*>(words[0]);
}
std::string displayWhy;
void* displayTarget = nullptr;
if (displayName[0] != '\0' &&
resolve_symbol_checked(displayName, true, &displayTarget, displayWhy))
{
return displayTarget;
}
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.
why = "virtual member of a secondary base; hook the overrider by name";
// The slot is relative to a secondary vtable whose base path is not encoded in the mfp.
why = fmt::format(
"virtual member of a secondary base did not resolve by name ({})", displayWhy);
return nullptr;
}
if (vtableSymbol[0] == '\0') {
@@ -638,35 +800,32 @@ void hook_remove_mod(LoadedMod& mod) {
}
auto* target = reinterpret_cast<void*>(it->first);
const int uninst = funchook_uninstall(entry.handle, 0);
const int destr = funchook_destroy(entry.handle);
if (uninst != 0 || destr != 0) {
DuskLog.warn("HookSystem: funchook uninstall/destroy for {:p} returned {}/{}", target,
uninst, destr);
}
entry.handle = nullptr;
entry.active = nullptr;
if (entry.candidates.empty()) {
deactivate_backend(target, entry.backend);
it = s_installed.erase(it);
continue;
}
// Hand the detour off to a surviving candidate (lowest registration order first; the
// vector is append-ordered). A candidate whose install fails stays in the list: its
// g_orig must still track the current original pointer.
// A prepatch may be atomically updated directly.
// Funchook must first restore the original instructions before reinstalling.
#if DUSK_HAS_PREPATCH
const bool prepatched = entry.backend.kind == BackendKind::Prepatch;
#else
constexpr bool prepatched = false;
#endif
if (!prepatched) {
deactivate_backend(target, entry.backend);
}
entry.active = nullptr;
for (auto& cand : entry.candidates) {
void* original = nullptr;
funchook_t* fh = install_trampoline(target, cand.trampoline, &original);
if (fh == nullptr) {
if (!handoff_backend(target, entry, cand, &original)) {
continue;
}
entry.handle = fh;
entry.original = original;
entry.active = cand.context;
DuskLog.info("HookSystem: reinstalled trampoline for {:p}: {} -> {} (tramp={:p})",
target, mod_id_from_context(context), mod_id_from_context(cand.context),
cand.trampoline);
DuskLog.info("HookSystem: replaced trampoline for {:p}: {} -> {} (tramp={:p})", target,
mod_id_from_context(context), mod_id_from_context(cand.context), cand.trampoline);
break;
}
@@ -677,6 +836,7 @@ void hook_remove_mod(LoadedMod& mod) {
for (auto& cand : entry.candidates) {
*cand.origStore = target;
}
deactivate_backend(target, entry.backend);
it = s_installed.erase(it);
continue;
}
@@ -772,14 +932,24 @@ void hook_resolve_mod_records(LoadedMod& mod) {
unresolved("<fn>", "null link-time target", &record->resolved);
}
}
for (auto* record : mod.native->parsed.hookMems) {
const auto resolveMember = [&](auto* record, const unsigned char* pmf, size_t pmfSize) {
const char* displayName = hook_mem_display_name(*record);
std::string why;
void* target = resolve_member_record(*record, hook_mem_vtable_symbol(*record), why);
void* target =
resolve_member_record(pmf, pmfSize, hook_mem_vtable_symbol(*record), displayName, why);
if (target != nullptr) {
resolved(target, &record->resolved);
} else {
unresolved(hook_mem_display_name(*record), why, &record->resolved);
unresolved(displayName, why, &record->resolved);
}
};
for (auto* record : mod.native->parsed.hookMems) {
resolveMember(record, record->pmf, sizeof(record->pmf));
}
for (auto* record : mod.native->parsed.hookMemExts) {
alignas(std::max_align_t) unsigned char pmf[MOD_META_HOOK_MEM_EXT_CAPACITY]{};
record->materialize(pmf);
resolveMember(record, pmf, record->pmf_size);
}
for (auto* record : mod.native->parsed.hookNames) {
const char* name = hook_name_symbol(*record);