6#include <Zydis/Zydis.h>
49 MODULEINFO module_info;
55 if (!GetModuleInformation(
64 const uint8_t *base = (
const uint8_t *)module_info.lpBaseOfDll;
65 const size_t size = module_info.SizeOfImage;
69#define DTTR_HOOK_PATCH_SIZE 5u
70#define DTTR_HOOK_MIN_PROLOGUE 5u
71#define DTTR_HOOK_MAX_PROLOGUE 64u
72#define DTTR_HOOK_MAX_INSN 32u
102 const ZyanStatus status = ZydisDecoderInit(
104 ZYDIS_MACHINE_MODE_LEGACY_32,
107 if (!ZYAN_SUCCESS(status)) {
109 "hook_attach_function: ZydisDecoderInit failed (status=0x%08X)",
121 switch (protect & 0xFFu) {
126 case PAGE_EXECUTE_READ:
127 case PAGE_EXECUTE_READWRITE:
128 case PAGE_EXECUTE_WRITECOPY:
139 while (copied <
size) {
140 const uintptr_t cur = addr + copied;
141 MEMORY_BASIC_INFORMATION mbi;
142 if (!VirtualQuery((
const void *)cur, &mbi,
sizeof(mbi))) {
144 "hook_attach_function: VirtualQuery failed at 0x%08X (err=%lu)",
151 if (mbi.State != MEM_COMMIT) {
153 "hook_attach_function: unreadable memory state=0x%lX at 0x%08X",
154 (
unsigned long)mbi.State,
160 if ((mbi.Protect & PAGE_GUARD) || (mbi.Protect & PAGE_NOACCESS)
163 "hook_attach_function: unreadable memory protect=0x%lX at 0x%08X",
164 (
unsigned long)mbi.Protect,
170 const uintptr_t region_end = (uintptr_t)mbi.BaseAddress + mbi.RegionSize;
171 size_t chunk = (size_t)(region_end - cur);
172 if (chunk >
size - copied) {
173 chunk =
size - copied;
176 memcpy(out + copied, (
const void *)cur, chunk);
189 const int wrote = snprintf(
194 (i + 1 <
size) ?
" " :
""
200 const size_t w = (size_t)wrote;
201 if (
w >=
sizeof(hex) - pos) {
202 pos =
sizeof(hex) - 1;
211 "hook_validate: site=0x%08X prologue_bytes[%u]=%s",
223 size_t *out_insn_count,
224 size_t *out_prologue_size,
225 uint8_t *out_prologue_bytes,
226 size_t out_prologue_bytes_cap
232 if (!insns || !out_insn_count || !out_prologue_size || !out_prologue_bytes
233 || out_prologue_bytes_cap == 0) {
235 "hook_attach_function: invalid decode parameters for 0x%08X",
242 if (requested_size > 0) {
243 need = (size_t)requested_size;
247 }
else if (requested_size < 0) {
249 "hook_attach_function: negative requested prologue=%d at 0x%08X; using "
258 "hook_attach_function: invalid requested prologue=%u at 0x%08X",
265 size_t decode_window = need + (size_t)ZYDIS_MAX_INSTRUCTION_LENGTH - 1u;
273 "hook_attach_function: failed to read decode window at 0x%08X (size=%u)",
275 (
unsigned)decode_window
283 while (offset < need) {
286 "hook_attach_function: too many instructions while decoding 0x%08X",
292 ZydisDecodedInstruction inst;
293 const ZyanStatus status = ZydisDecoderDecodeInstruction(
296 code_window + offset,
297 decode_window - offset,
300 if (!ZYAN_SUCCESS(status) || inst.length == 0) {
302 "hook_attach_function: decode failed at 0x%08X+0x%X (status=0x%08X)",
310 const char *mnemonic = ZydisMnemonicGetString(inst.mnemonic);
312 mnemonic =
"unknown";
317 "hook_attach_function: decoded prologue exceeded %u bytes at 0x%08X",
325 out->
offset = (uint8_t)offset;
326 out->
length = inst.length;
330 for (
size_t imm_idx = 0; imm_idx < 2; imm_idx++) {
331 if (!inst.raw.imm[imm_idx].size || !inst.raw.imm[imm_idx].is_relative) {
335 out->
rel_offset = inst.raw.imm[imm_idx].offset;
336 out->
rel_size = (uint8_t)(inst.raw.imm[imm_idx].size / 8);
342 "hook_attach_function: unsupported relative immediate size=%u at "
354 "hook_attach_function: invalid relative-immediate layout at "
363 "hook_decode: site=0x%08X off=0x%02X len=%u mnemonic=%s rel_off=%u "
367 (
unsigned)inst.length,
373 offset += inst.length;
377 if (offset > out_prologue_bytes_cap) {
379 "hook_attach_function: prologue output overflow at 0x%08X (%u > %u)",
382 (
unsigned)out_prologue_bytes_cap
387 memcpy(out_prologue_bytes, code_window, offset);
390 *out_insn_count =
count;
391 *out_prologue_size = offset;
403 for (
size_t i = 0; i < insn_count; i++) {
411 "hook_attach_function: relocate unsupported rel_size=%u at 0x%08X+0x%X",
420 memcpy(&old_rel, trampoline + insn->
offset + insn->
rel_offset,
sizeof(old_rel));
422 const intptr_t old_next = (intptr_t)(site + insn->
offset + insn->
length);
423 const intptr_t old_target = old_next + (intptr_t)old_rel;
424 const intptr_t new_next = (intptr_t)((uintptr_t)trampoline + insn->
offset
426 const int64_t new_rel64 = (int64_t)(old_target - new_next);
427 if (new_rel64 < INT32_MIN || new_rel64 > INT32_MAX) {
429 "hook_attach_function: relocated target out of range at 0x%08X+0x%X",
436 const int32_t new_rel = (int32_t)new_rel64;
437 memcpy(trampoline + insn->
offset + insn->
rel_offset, &new_rel,
sizeof(new_rel));
440 "hook_reloc: site=0x%08X off=0x%02X target=0x%08X rel=%d",
443 (
unsigned)old_target,
451KHASH_MAP_INIT_INT64(sigscan_cache, uintptr_t)
456static uint64_t sigscan_key(HMODULE mod, const
char *sig, const
char *mask) {
457 const size_t mask_len = strlen(mask);
459 XXH3_64bits_reset(&
state);
460 XXH3_64bits_update(&
state, &mod,
sizeof(mod));
461 XXH3_64bits_update(&
state, sig, mask_len);
462 XXH3_64bits_update(&
state, mask, mask_len);
463 return XXH3_64bits_digest(&
state);
468 if (!mod || !sig || !mask) {
473 cache = kh_init(sigscan_cache);
479 const uint64_t key = sigscan_key(mod, sig, mask);
480 khiter_t it = kh_get(sigscan_cache, cache, key);
482 if (it != kh_end(cache)) {
483 return kh_val(cache, it);
489 it = kh_put(sigscan_cache, cache, key, &ret);
494 kh_val(cache, it) = result;
501static hook_vec hooks;
502static void *hook_owner =
NULL;
511 VirtualFree(hook->
next_thunk, 0, MEM_RELEASE);
519 VirtualFree(hook->
trampoline, 0, MEM_RELEASE);
532 VirtualFree(chain->
trampoline, 0, MEM_RELEASE);
541 const uintptr_t end = addr +
size;
543 for (
size_t i = 0; i < kv_size(hooks); i++) {
545 const uintptr_t h_end =
h->addr +
h->size;
547 if (addr < h_end && h->addr < end) {
549 "hook overlap: [0x%08X, +%zu) conflicts with [0x%08X, +%zu)",
566 DTTR_LOG_ERROR(
"%s: hook alloc failed for 0x%08X", op, (
unsigned)addr);
572 hook->
owner = hook_owner;
575 DTTR_LOG_ERROR(
"%s: original-bytes alloc failed for 0x%08X", op, (
unsigned)addr);
585 for (
size_t i = 0; i < kv_size(hooks); i++) {
586 if (kv_A(hooks, i) == hook) {
591 return kv_size(hooks);
595 for (
size_t i = 0; i < kv_size(hooks); i++) {
608 if (!FlushInstructionCache(GetCurrentProcess(), (
const void *)addr,
size)) {
609 DTTR_LOG_WARN(
"%s: FlushInstructionCache failed for 0x%08X", op, (
unsigned)addr);
613static bool write_bytes(
const char *op, uintptr_t addr,
const uint8_t *bytes,
size_t size) {
615 if (!VirtualProtect((
void *)addr,
size, PAGE_EXECUTE_READWRITE, &old_protect)) {
616 DTTR_LOG_ERROR(
"%s: VirtualProtect failed for 0x%08X", op, (
unsigned)addr);
620 memcpy((
void *)addr, bytes,
size);
621 VirtualProtect((
void *)addr,
size, old_protect, &old_protect);
632 const int64_t rel64 = (int64_t)(uintptr_t)
target
634 if (rel64 < INT32_MIN || rel64 > INT32_MAX) {
638 const int32_t rel = (int32_t)rel64;
639 memcpy(out + 1, &rel,
sizeof(rel));
647 "hook_attach_function: handler jump out of range at 0x%08X -> 0x%08X",
649 (
unsigned)(uintptr_t)
target
654 return write_bytes(
"hook_attach_function", site, jmp,
sizeof(jmp));
664 const uint32_t slot = (uint32_t)(uintptr_t)(thunk + 6);
665 const uint32_t target32 = (uint32_t)(uintptr_t)
target;
666 memcpy(thunk + 2, &slot,
sizeof(slot));
667 memcpy(thunk + 6, &target32,
sizeof(target32));
688 "hook_attach_function: hook link alloc failed for 0x%08X",
689 (
unsigned)(chain ? chain->
addr : 0u)
695 VirtualAlloc(
NULL, 10u, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
698 "hook_attach_function: hook link thunk alloc failed for 0x%08X",
699 (
unsigned)(chain ? chain->
addr : 0u)
708 hook->
owner = hook_owner;
781 const bool removing_head = chain->
head == hook;
782 const bool removing_last = !hook->
prev && !hook->
next;
787 "hook_detach: leaving function hook registered because restore failed "
790 (
unsigned)chain->
addr
794 }
else if (removing_head) {
797 "hook_detach: leaving function hook registered because relink failed "
800 (
unsigned)chain->
addr
840 kv_A(hooks, index) = kv_A(hooks, kv_size(hooks) - 1);
848 "hook_detach: leaving hook registered because restore failed at 0x%08X",
854 kv_A(hooks, index) = kv_A(hooks, kv_size(hooks) - 1);
868 if (!addr || !handler) {
870 "hook_attach_function: invalid parameters site=0x%08X handler=0x%08X",
872 (
unsigned)(uintptr_t)handler
878 if (existing_chain) {
879 if (prologue_size > 0 && (
size_t)prologue_size > existing_chain->
prologue_size) {
881 "hook chain unsupported: site=0x%08X requested prologue %d exceeds "
882 "active prologue %u",
889 "function hook chain does not support a larger prologue"
900 "function hook chain is unsupported for an overlapping hook range"
907 size_t insn_count = 0;
908 size_t actual_prologue_size = 0;
914 &actual_prologue_size,
916 sizeof(prologue_bytes)
922 "hook_attach_function: site=0x%08X requested=%d aligned=%u insn_count=%u",
925 (
unsigned)actual_prologue_size,
929 uint8_t *trampoline = (uint8_t *)VirtualAlloc(
932 MEM_COMMIT | MEM_RESERVE,
933 PAGE_EXECUTE_READWRITE
938 "hook_attach_function: trampoline alloc failed for 0x%08X",
944 memcpy(trampoline, prologue_bytes, actual_prologue_size);
946 VirtualFree(trampoline, 0, MEM_RELEASE);
950 trampoline[actual_prologue_size] = 0xE9;
951 const int64_t jmp_back64 = (int64_t)(addr + actual_prologue_size)
952 - (int64_t)((uintptr_t)trampoline + actual_prologue_size
954 if (jmp_back64 < INT32_MIN || jmp_back64 > INT32_MAX) {
956 "hook_attach_function: trampoline jmp-back out of range at 0x%08X",
959 VirtualFree(trampoline, 0, MEM_RELEASE);
963 const int32_t jmp_back = (int32_t)jmp_back64;
964 memcpy(trampoline + actual_prologue_size + 1, &jmp_back, 4);
969 "hook_attach_function: chain alloc failed for 0x%08X",
972 VirtualFree(trampoline, 0, MEM_RELEASE);
979 "hook_attach_function: original-bytes alloc failed for 0x%08X",
982 VirtualFree(trampoline, 0, MEM_RELEASE);
1021 *out_original = *(
void **)addr;
1024 const uintptr_t value = (uintptr_t)new_value;
1031 const uint8_t *bytes,
1034 if (!addr || !bytes || !
size) {
1036 "hook_patch_bytes: invalid parameters addr=0x%08X bytes=0x%08X size=%zu",
1038 (
unsigned)(uintptr_t)bytes,
1069 if (index == kv_size(hooks)) {
1070 DTTR_LOG_DEBUG(
"hook_detach: ignoring stale or already detached hook handle");
1092 for (
size_t i = kv_size(hooks); i > 0; i--) {
1093 if (kv_A(hooks, i - 1)->owner != owner) {
1105 void *previous = hook_owner;
1117 kh_destroy(sigscan_cache, cache);
1124 while (kv_size(hooks) > 0) {
1125 const size_t previous_count = kv_size(hooks);
1132 if (kv_size(hooks) == 0) {
1145 "hook_cleanup_all: stopped after restore failure; hooks remain registered"
DTTR_Graphics_COM_Direct3DDevice7 void DWORD flags DWORD count
const DTTR_BackendState * state
DTTR_Graphics_COM_DirectDraw7 *self DWORD DWORD h
DTTR_Graphics_COM_DirectDraw7 *self DWORD w
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
DTTR_Graphics_COM_DirectDrawSurface7 DWORD DWORD void void DWORD flags DTTR_Graphics_COM_DirectDrawSurface7 void void *cb void * target
static DTTR_Result dttr_core_result(DTTR_Status status, const char *message)
#define DTTR_LOG_DEBUG(...)
#define DTTR_LOG_WARN(...)
#define DTTR_LOG_ERROR(...)
@ DTTR_ERR_HOOK_CHAIN_UNSUPPORTED
static const uint8_t * DTTR_Sigscan_Bytes(const uint8_t *base, size_t size, const void *sig, const char *mask)
static khash_t(dttr_config_lookup)
static bool write_bytes(const char *op, uintptr_t addr, const uint8_t *bytes, size_t size)
#define DTTR_HOOK_MAX_PROLOGUE
void dttr_core_hook_set_last_error(DTTR_Status status, const char *message)
static DTTR_Result hook_last_error
static void log_prologue_bytes(uintptr_t site, const uint8_t *bytes, size_t size)
static bool decoder_initialized
static void * function_link_next_target(const DTTR_Core_Hook *hook)
DTTR_Core_Hook * DTTR_Core_HookAttachPointer(uintptr_t addr, void *new_value, void **out_original)
@ DTTR_HOOK_RECORD_FUNCTION
static size_t hook_find_index(DTTR_Core_Hook *hook)
static bool trampoline_relocate(uint8_t *trampoline, uintptr_t site, const trampoline_insn *insns, size_t insn_count)
static bool write_function_jump(uintptr_t site, void *target)
static bool function_link_detach(DTTR_Core_Hook *hook)
bool DTTR_Core_HookIsActive(DTTR_Core_Hook *hook)
static bool decode_prologue(uintptr_t addr, int requested_size, trampoline_insn *insns, size_t *out_insn_count, size_t *out_prologue_size, uint8_t *out_prologue_bytes, size_t out_prologue_bytes_cap)
static DTTR_Core_Hook * function_link_create(hook_chain *chain, void *detour, void *next_target)
static bool check_overlap(uintptr_t addr, size_t size)
bool DTTR_Core_HookDetachOwnerChecked(void *owner)
static bool is_readable_page_protect(DWORD protect)
DTTR_Core_Hook * DTTR_Core_HookAttachFunction(uintptr_t addr, int prologue_size, void *handler, void **out_original)
static hook_chain * hook_find_function_chain(uintptr_t addr)
DTTR_Core_Hook * DTTR_Core_HookPatchBytes(uintptr_t addr, const uint8_t *bytes, size_t size)
#define DTTR_HOOK_MAX_INSN
#define DTTR_HOOK_PATCH_SIZE
static bool function_chain_push_head(hook_chain *chain, DTTR_Core_Hook *hook, void **out_original)
bool DTTR_Core_HookCleanupAllChecked()
void DTTR_Core_HookCleanupAll()
Detach all remaining hooks and free the sigscan cache.
static void cleanup_sigscan_cache()
typedef kvec_t(DTTR_Core_Hook *)
static bool hook_detach_index(size_t index)
static void flush_patched_range(const char *op, uintptr_t addr, size_t size)
uintptr_t DTTR_Core_HookCachedSigscan(HMODULE mod, const char *sig, const char *mask)
void DTTR_Core_HookDetach(DTTR_Core_Hook *hook)
uintptr_t DTTR_Core_HookSigscan(HMODULE mod, const char *sig, const char *mask)
static DTTR_Core_Hook * hook_create(const char *op, uintptr_t addr, size_t size)
static bool copy_memory_checked(uintptr_t addr, uint8_t *out, size_t size)
#define DTTR_HOOK_MIN_PROLOGUE
static DTTR_Core_Hook * function_chain_append(hook_chain *chain, void *handler, void **out_original)
static bool hook_thunk_set_target(uint8_t *thunk, void *target)
static void hook_chain_destroy(hook_chain *chain)
static void hook_error_clear()
static bool decoder_init()
static ZydisDecoder decoder
static bool build_rel32_jump(uintptr_t site, void *target, uint8_t out[DTTR_HOOK_PATCH_SIZE])
DTTR_Result dttr_core_hook_last_error()
bool DTTR_Core_HookDetachChecked(DTTR_Core_Hook *hook)
void DTTR_Core_HookDetachOwner(void *owner)
void * DTTR_Core_HookSetOwner(void *owner)