initial commit

This commit is contained in:
nexusverypro
2026-07-03 02:00:27 +01:00
parent 85fa958433
commit 3192afe07d
18 changed files with 1592 additions and 0 deletions

751
src/radhook.cpp Normal file
View File

@@ -0,0 +1,751 @@
#include "radhook/radhook.h"
#include <cstring>
#include <cstdint>
#include <cstdio>
#include <vector>
#include <memory>
#include <mutex>
#include <algorithm>
#if defined(RADIUM_PLATFORM_WINDOWS)
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <windows.h>
#elif defined(RADIUM_PLATFORM_ANDROID)
# include <sys/mman.h>
# include <unistd.h>
#endif
#undef min
#undef max
// result strings
const char* RadHookResultToString(RadHookResult result) {
switch (result) {
case RadHookResult::Success: return "Success";
case RadHookResult::AlreadyInstalled: return "AlreadyInstalled";
case RadHookResult::NotInstalled: return "NotInstalled";
case RadHookResult::AlreadyEnabled: return "AlreadyEnabled";
case RadHookResult::AlreadyDisabled: return "AlreadyDisabled";
case RadHookResult::MemoryAllocFailed: return "MemoryAllocFailed";
case RadHookResult::MemoryProtectFailed: return "MemoryProtectFailed";
case RadHookResult::DisassemblyFailed: return "DisassemblyFailed";
case RadHookResult::InvalidTarget: return "InvalidTarget";
case RadHookResult::InvalidDetour: return "InvalidDetour";
case RadHookResult::InvalidHandle: return "InvalidHandle";
case RadHookResult::TrampolineTooFar: return "TrampolineTooFar";
default: return "Unknown";
}
}
// hook record
struct RadHookOpaque {
void* target = nullptr;
void* detour = nullptr;
void* trampoline = nullptr;
void* trampolineAlloc = nullptr;
size_t trampolineAllocSize = 0;
unsigned char originalBytes[32] = {};
size_t originalLength = 0;
unsigned char stubBytes[32] = {};
size_t stubLength = 0;
bool enabled = false;
};
namespace {
std::vector<std::unique_ptr<RadHookOpaque>> g_hooks;
std::mutex g_mutex;
bool IsRegisteredLocked(RadHookHandle handle) {
if (!handle) return false;
for (auto& h : g_hooks) {
if (h.get() == handle) return true;
}
return false;
}
// platform memory backend
#if defined(RADIUM_PLATFORM_WINDOWS)
void* AllocExec(size_t size) {
return VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
}
void FreeExec(void* p, size_t /*size*/) {
if (p) VirtualFree(p, 0, MEM_RELEASE);
}
void* AllocExecNear(void* target, size_t size) {
SYSTEM_INFO si;
GetSystemInfo(&si);
const uintptr_t pageSize = si.dwPageSize;
const uintptr_t targetAddr = reinterpret_cast<uintptr_t>(target);
const uintptr_t margin = 0x7FFF0000ULL; // stay safely under INT32_MAX
uintptr_t minAddr = reinterpret_cast<uintptr_t>(si.lpMinimumApplicationAddress);
uintptr_t maxAddr = reinterpret_cast<uintptr_t>(si.lpMaximumApplicationAddress);
if (targetAddr > margin && targetAddr - margin > minAddr) minAddr = targetAddr - margin;
if (targetAddr + margin < maxAddr) maxAddr = targetAddr + margin;
uintptr_t alignedTarget = targetAddr & ~(pageSize - 1);
// search backward from target
for (uintptr_t addr = alignedTarget; addr >= minAddr && addr != 0; ) {
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery(reinterpret_cast<LPCVOID>(addr), &mbi, sizeof(mbi)) == 0) break;
uintptr_t regionBase = reinterpret_cast<uintptr_t>(mbi.BaseAddress);
if (mbi.State == MEM_FREE) {
uintptr_t allocBase = (regionBase + pageSize - 1) & ~(pageSize - 1);
if (allocBase >= minAddr && allocBase + size <= regionBase + mbi.RegionSize) {
void* p = VirtualAlloc(reinterpret_cast<LPVOID>(allocBase), size,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (p) return p;
}
}
if (regionBase <= minAddr || regionBase < pageSize) break;
addr = regionBase - pageSize;
}
// search forward from target
for (uintptr_t addr = alignedTarget; addr <= maxAddr; ) {
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery(reinterpret_cast<LPCVOID>(addr), &mbi, sizeof(mbi)) == 0) break;
uintptr_t regionBase = reinterpret_cast<uintptr_t>(mbi.BaseAddress);
if (mbi.State == MEM_FREE) {
uintptr_t allocBase = (regionBase + pageSize - 1) & ~(pageSize - 1);
if (allocBase + size <= regionBase + mbi.RegionSize && allocBase + size <= maxAddr) {
void* p = VirtualAlloc(reinterpret_cast<LPVOID>(allocBase), size,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (p) return p;
}
}
if (mbi.RegionSize == 0) break;
addr = regionBase + mbi.RegionSize;
}
return nullptr;
}
bool MakeWritableExecutable(void* addr, size_t size, unsigned long* oldProtect) {
DWORD prev = 0;
if (!VirtualProtect(addr, size, PAGE_EXECUTE_READWRITE, &prev)) {
return false;
}
if (oldProtect) *oldProtect = prev;
return true;
}
void RestoreProtection(void* addr, size_t size, unsigned long oldProtect) {
DWORD tmp;
VirtualProtect(addr, size, static_cast<DWORD>(oldProtect), &tmp);
}
void FlushICache(void* addr, size_t size) {
FlushInstructionCache(GetCurrentProcess(), addr, size);
}
#elif defined(RADIUM_PLATFORM_ANDROID)
size_t PageSize() {
static size_t sz = static_cast<size_t>(sysconf(_SC_PAGESIZE));
return sz;
}
void* AllocExec(size_t size) {
size_t pageSize = PageSize();
size_t allocSize = (size + pageSize - 1) & ~(pageSize - 1);
void* p = mmap(nullptr, allocSize, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
return (p == MAP_FAILED) ? nullptr : p;
}
void FreeExec(void* p, size_t size) {
if (!p) return;
size_t pageSize = PageSize();
size_t allocSize = (size + pageSize - 1) & ~(pageSize - 1);
munmap(p, allocSize);
}
#ifndef MAP_FIXED_NOREPLACE
# define MAP_FIXED_NOREPLACE 0x100000 // linux >= 4.17 defined manually for older headers
#endif
void* AllocExecNear(void* target, size_t size) {
size_t pageSize = PageSize();
uintptr_t targetAddr = reinterpret_cast<uintptr_t>(target);
const uintptr_t margin = 0x7FFF0000ULL;
uintptr_t rangeLo = (targetAddr > margin) ? targetAddr - margin : pageSize;
uintptr_t rangeHi = targetAddr + margin;
FILE* f = fopen("/proc/self/maps", "r");
if (!f) return nullptr;
struct Region { uintptr_t start, end; };
std::vector<Region> regions;
char line[512];
while (fgets(line, sizeof(line), f)) {
unsigned long long s = 0, e = 0;
if (sscanf(line, "%llx-%llx", &s, &e) == 2) {
regions.push_back({ static_cast<uintptr_t>(s), static_cast<uintptr_t>(e) });
}
}
fclose(f);
std::sort(regions.begin(), regions.end(),
[](const Region& a, const Region& b) { return a.start < b.start; });
std::vector<Region> gaps;
uintptr_t prevEnd = rangeLo;
for (auto& r : regions) {
if (r.start >= rangeHi) break;
uintptr_t gapStart = prevEnd;
uintptr_t gapEnd = std::min(r.start, rangeHi);
if (gapEnd > gapStart) gaps.push_back({ gapStart, gapEnd });
if (r.end > prevEnd) prevEnd = r.end;
}
if (prevEnd < rangeHi) gaps.push_back({ prevEnd, rangeHi });
size_t pageAlignedSize = (size + pageSize - 1) & ~(pageSize - 1);
void* best = nullptr;
uintptr_t bestDist = static_cast<uintptr_t>(-1);
for (auto& g : gaps) {
uintptr_t alignedStart = (g.start + pageSize - 1) & ~(pageSize - 1);
uintptr_t alignedEnd = g.end & ~(pageSize - 1);
if (alignedEnd < alignedStart + pageAlignedSize) continue;
uintptr_t lastValidStart = alignedEnd - pageAlignedSize; // already page-aligned
uintptr_t candidate;
if (targetAddr < alignedStart) candidate = alignedStart;
else if (targetAddr > lastValidStart) candidate = lastValidStart;
else candidate = targetAddr & ~(pageSize - 1);
// clamp
if (candidate > lastValidStart) candidate = lastValidStart;
if (candidate < alignedStart) candidate = alignedStart;
uintptr_t dist = (candidate > targetAddr) ? (candidate - targetAddr) : (targetAddr - candidate);
if (dist < bestDist) {
bestDist = dist;
best = reinterpret_cast<void*>(candidate);
}
}
if (!best) return nullptr;
void* p = mmap(best, pageAlignedSize, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
if (p == MAP_FAILED) return nullptr;
return p;
}
bool MakeWritableExecutable(void* addr, size_t size, unsigned long* oldProtect) {
if (oldProtect) *oldProtect = 0;
size_t pageSize = PageSize();
uintptr_t start = reinterpret_cast<uintptr_t>(addr) & ~(pageSize - 1);
uintptr_t end = (reinterpret_cast<uintptr_t>(addr) + size + pageSize - 1) & ~(pageSize - 1);
return mprotect(reinterpret_cast<void*>(start), end - start,
PROT_READ | PROT_WRITE | PROT_EXEC) == 0;
}
void RestoreProtection(void* /*addr*/, size_t /*size*/, unsigned long /*oldProtect*/) {
// no-op
}
void FlushICache(void* addr, size_t size) {
__builtin___clear_cache(reinterpret_cast<char*>(addr),
reinterpret_cast<char*>(addr) + size);
}
#endif
// x86-64 disassembler
#if defined(RADHOOK_ARCH_X64)
struct X64Insn {
size_t length = 0;
bool ripRelative = false;
size_t dispOffset = 0;
bool relativeControlFlow = false;
bool valid = false;
};
bool IsLegacyPrefix(uint8_t b) {
switch (b) {
case 0x66: case 0x67: case 0xF0: case 0xF2: case 0xF3:
case 0x2E: case 0x36: case 0x3E: case 0x26: case 0x64: case 0x65:
return true;
default:
return false;
}
}
X64Insn DecodeX64(const uint8_t* p, size_t avail) {
X64Insn insn;
size_t i = 0;
bool opSize16 = false;
bool rexW = false;
while (i < avail && IsLegacyPrefix(p[i])) {
if (p[i] == 0x66) opSize16 = true;
++i;
}
if (i < avail && (p[i] & 0xF0) == 0x40) {
rexW = (p[i] & 0x08) != 0;
++i;
}
if (i >= avail) return insn;
uint8_t opcode = p[i++];
bool twoByte = false;
if (opcode == 0x0F) {
if (i >= avail) return insn;
twoByte = true;
opcode = p[i++];
}
bool hasModRM = false;
int immSize = 0;
bool relBranch = false;
if (!twoByte) {
uint8_t lo = opcode & 0x0F;
if (opcode <= 0x3D && (opcode & 0xC0) == 0x00 && lo <= 0x05 &&
(opcode & 0x07) <= 0x05) {
if (lo == 0x04) { immSize = 1; }
else if (lo == 0x05) { immSize = opSize16 ? 2 : 4; }
else if (lo <= 0x03) { hasModRM = true; }
else { return insn; }
} else if (opcode >= 0x50 && opcode <= 0x5F) { /* push/pop r64, no operand bytes */ }
else if (opcode == 0x68) { immSize = opSize16 ? 2 : 4; }
else if (opcode == 0x6A) { immSize = 1; }
else if (opcode == 0x69) { hasModRM = true; immSize = opSize16 ? 2 : 4; }
else if (opcode == 0x6B) { hasModRM = true; immSize = 1; }
else if (opcode >= 0x70 && opcode <= 0x7F) { immSize = 1; relBranch = true; }
else if (opcode == 0x80) { hasModRM = true; immSize = 1; }
else if (opcode == 0x81) { hasModRM = true; immSize = opSize16 ? 2 : 4; }
else if (opcode == 0x83) { hasModRM = true; immSize = 1; }
else if (opcode >= 0x84 && opcode <= 0x8F) { hasModRM = true; }
else if (opcode >= 0x90 && opcode <= 0x97) { /* xchg/nop, no operand bytes */ }
else if (opcode == 0x98 || opcode == 0x99) { /* cbw/cwd family */ }
else if (opcode == 0xA8) { immSize = 1; }
else if (opcode == 0xA9) { immSize = opSize16 ? 2 : 4; }
else if (opcode >= 0xB0 && opcode <= 0xB7) { immSize = 1; }
else if (opcode >= 0xB8 && opcode <= 0xBF) { immSize = rexW ? 8 : (opSize16 ? 2 : 4); }
else if (opcode == 0xC0 || opcode == 0xC1) { hasModRM = true; immSize = 1; }
else if (opcode == 0xC2) { immSize = 2; }
else if (opcode == 0xC3) { /* ret */ }
else if (opcode == 0xC6) { hasModRM = true; immSize = 1; }
else if (opcode == 0xC7) { hasModRM = true; immSize = opSize16 ? 2 : 4; }
else if (opcode == 0xC9) { /* leave */ }
else if (opcode == 0xCC) { /* int3 */ }
else if (opcode == 0xCD) { immSize = 1; }
else if (opcode == 0xD0 || opcode == 0xD1 || opcode == 0xD2 || opcode == 0xD3) { hasModRM = true; }
else if (opcode == 0xE8) { immSize = 4; relBranch = true; }
else if (opcode == 0xE9) { immSize = 4; relBranch = true; }
else if (opcode == 0xEB) { immSize = 1; relBranch = true; }
else if (opcode == 0xF6) { hasModRM = true; immSize = 1; }
else if (opcode == 0xF7) { hasModRM = true; immSize = opSize16 ? 2 : 4; }
else if (opcode == 0xFE || opcode == 0xFF) { hasModRM = true; }
else {
return insn; // unrecognized opcode
}
} else {
if (opcode >= 0x80 && opcode <= 0x8F) { immSize = 4; relBranch = true; }
else if (opcode == 0x1E) { immSize = 1; }
else if (opcode == 0x1F) { hasModRM = true; }
else if (opcode == 0x05 || opcode == 0x31 || opcode == 0x34 || opcode == 0x35 || opcode == 0xA2) { /* syscall/rdtsc/cpuid etc */ }
else if (opcode >= 0x40 && opcode <= 0x4F) { hasModRM = true; } // CMOVcc
else if (opcode == 0xAF) { hasModRM = true; } // imul
else if (opcode == 0xB6 || opcode == 0xB7 || opcode == 0xBE || opcode == 0xBF) { hasModRM = true; } // movzx/movsx
else if (opcode >= 0x10 && opcode <= 0x17) { hasModRM = true; } // movups/movaps family
else if (opcode >= 0x28 && opcode <= 0x2F) { hasModRM = true; }
else if (opcode >= 0x54 && opcode <= 0x5F) { hasModRM = true; }
else if (opcode == 0x6E || opcode == 0x6F || opcode == 0x7E || opcode == 0x7F || opcode == 0xD6) { hasModRM = true; }
else {
return insn;
}
}
if (hasModRM) {
if (i >= avail) return insn;
uint8_t modrm = p[i++];
uint8_t mod = (modrm >> 6) & 0x3;
uint8_t rm = modrm & 0x7;
if (mod != 3 && rm == 4) {
if (i >= avail) return insn;
uint8_t sib = p[i++];
uint8_t base = sib & 0x7;
if (mod == 0 && base == 5) {
i += 4;
}
}
if (mod == 0 && rm == 5) {
insn.ripRelative = true;
insn.dispOffset = i;
i += 4;
} else if (mod == 1) {
i += 1;
} else if (mod == 2) {
i += 4;
}
}
i += static_cast<size_t>(immSize);
if (i > avail) return insn;
insn.length = i;
insn.relativeControlFlow = relBranch;
insn.valid = true;
return insn;
}
constexpr size_t kX64StubLen = 14; // FF25 00000000 + imm64
constexpr size_t kX64MaxStolen = 32;
constexpr size_t kX64MaxRipFixups = 8;
bool BuildStolenBytesX64(const uint8_t* target, size_t minLen,
uint8_t* outBytes, size_t& outLen,
size_t* outRipOffsets, size_t& outRipCount) {
size_t len = 0;
outRipCount = 0;
while (len < minLen) {
if (len >= kX64MaxStolen) return false;
X64Insn insn = DecodeX64(target + len, kX64MaxStolen - len);
if (!insn.valid) return false;
if (insn.relativeControlFlow) return false;
if (insn.ripRelative) {
if (outRipCount >= kX64MaxRipFixups) return false;
outRipOffsets[outRipCount++] = len + insn.dispOffset;
}
len += insn.length;
}
std::memcpy(outBytes, target, len);
outLen = len;
return true;
}
#endif // RADHOOK_ARCH_X64
// aarch64 prologue check
#if defined(RADHOOK_ARCH_ARM64)
constexpr size_t kArm64StubLen = 16;
bool IsArm64PcRelative(uint32_t instr) {
if ((instr & 0x9F000000) == 0x10000000) return true; // ADR
if ((instr & 0x9F000000) == 0x90000000) return true; // ADRP
if ((instr & 0xFC000000) == 0x14000000) return true; // B
if ((instr & 0xFC000000) == 0x94000000) return true; // BL
if ((instr & 0xFF000010) == 0x54000000) return true; // B.cond
if ((instr & 0x7E000000) == 0x34000000) return true; // CBZ/CBNZ
if ((instr & 0x7E000000) == 0x36000000) return true; // TBZ/TBNZ
if ((instr & 0xBF000000) == 0x18000000) return true; // LDR
if ((instr & 0x3B000000) == 0x18000000) return true; // LDR/LDRSW
return false;
}
void WriteArm64Stub(void* at, uintptr_t targetAddr) {
uint32_t* words = reinterpret_cast<uint32_t*>(at);
words[0] = 0x58000051; // LDR X17, #8
words[1] = 0xD61F0220; // BR X17
std::memcpy(&words[2], &targetAddr, 8);
}
#endif // RADHOOK_ARCH_ARM64
} // anonymous namespace
namespace {
RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
if (!target) return RadHookResult::InvalidTarget;
if (!detour) return RadHookResult::InvalidDetour;
#if defined(RADHOOK_ARCH_X64)
uint8_t stolen[kX64MaxStolen];
size_t stolenLen = 0;
size_t ripOffsets[kX64MaxRipFixups];
size_t ripCount = 0;
if (!BuildStolenBytesX64(reinterpret_cast<const uint8_t*>(target), kX64StubLen,
stolen, stolenLen, ripOffsets, ripCount)) {
return RadHookResult::DisassemblyFailed;
}
size_t allocSize = stolenLen + kX64StubLen;
void* trampolineAlloc = AllocExecNear(target, allocSize);
if (!trampolineAlloc) trampolineAlloc = AllocExec(allocSize); // best-effort fallback
if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed;
std::memcpy(trampolineAlloc, stolen, stolenLen);
for (size_t k = 0; k < ripCount; ++k) {
size_t off = ripOffsets[k];
int32_t originalDisp = 0;
std::memcpy(&originalDisp, reinterpret_cast<const uint8_t*>(target) + off, 4);
uintptr_t absTarget = reinterpret_cast<uintptr_t>(target) + off + 4 + static_cast<intptr_t>(originalDisp);
intptr_t newDisp = static_cast<intptr_t>(absTarget) -
(reinterpret_cast<intptr_t>(trampolineAlloc) + off + 4);
if (newDisp < INT32_MIN || newDisp > INT32_MAX) {
FreeExec(trampolineAlloc, allocSize);
return RadHookResult::TrampolineTooFar;
}
int32_t newDisp32 = static_cast<int32_t>(newDisp);
std::memcpy(reinterpret_cast<uint8_t*>(trampolineAlloc) + off, &newDisp32, 4);
}
uint8_t* jumpBack = reinterpret_cast<uint8_t*>(trampolineAlloc) + stolenLen;
uintptr_t backAddr = reinterpret_cast<uintptr_t>(target) + stolenLen;
jumpBack[0] = 0xFF; jumpBack[1] = 0x25;
jumpBack[2] = 0; jumpBack[3] = 0; jumpBack[4] = 0; jumpBack[5] = 0;
std::memcpy(jumpBack + 6, &backAddr, 8);
FlushICache(trampolineAlloc, allocSize);
unsigned long oldProtect = 0;
if (!MakeWritableExecutable(target, stolenLen, &oldProtect)) {
FreeExec(trampolineAlloc, allocSize);
return RadHookResult::MemoryProtectFailed;
}
std::memcpy(h->originalBytes, target, stolenLen);
h->originalLength = stolenLen;
uint8_t hookStub[kX64StubLen];
hookStub[0] = 0xFF; hookStub[1] = 0x25;
hookStub[2] = 0; hookStub[3] = 0; hookStub[4] = 0; hookStub[5] = 0;
uintptr_t detourAddr = reinterpret_cast<uintptr_t>(detour);
std::memcpy(hookStub + 6, &detourAddr, 8);
std::memcpy(h->stubBytes, hookStub, kX64StubLen);
h->stubLength = kX64StubLen;
std::memcpy(target, hookStub, kX64StubLen);
RestoreProtection(target, stolenLen, oldProtect);
FlushICache(target, stolenLen);
h->target = target;
h->detour = detour;
h->trampoline = trampolineAlloc;
h->trampolineAlloc = trampolineAlloc;
h->trampolineAllocSize = allocSize;
h->enabled = true;
return RadHookResult::Success;
#elif defined(RADHOOK_ARCH_ARM64)
const uint32_t* src = reinterpret_cast<const uint32_t*>(target);
for (size_t k = 0; k < kArm64StubLen / 4; ++k) {
if (IsArm64PcRelative(src[k])) {
return RadHookResult::DisassemblyFailed;
}
}
size_t allocSize = kArm64StubLen + kArm64StubLen;
void* trampolineAlloc = AllocExec(allocSize);
if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed;
std::memcpy(trampolineAlloc, target, kArm64StubLen);
uintptr_t backAddr = reinterpret_cast<uintptr_t>(target) + kArm64StubLen;
WriteArm64Stub(reinterpret_cast<uint8_t*>(trampolineAlloc) + kArm64StubLen, backAddr);
FlushICache(trampolineAlloc, allocSize);
unsigned long oldProtect = 0;
if (!MakeWritableExecutable(target, kArm64StubLen, &oldProtect)) {
FreeExec(trampolineAlloc, allocSize);
return RadHookResult::MemoryProtectFailed;
}
std::memcpy(h->originalBytes, target, kArm64StubLen);
h->originalLength = kArm64StubLen;
uint8_t hookStub[kArm64StubLen];
uintptr_t detourAddr = reinterpret_cast<uintptr_t>(detour);
WriteArm64Stub(hookStub, detourAddr);
std::memcpy(h->stubBytes, hookStub, kArm64StubLen);
h->stubLength = kArm64StubLen;
std::memcpy(target, hookStub, kArm64StubLen);
RestoreProtection(target, kArm64StubLen, oldProtect);
FlushICache(target, kArm64StubLen);
h->target = target;
h->detour = detour;
h->trampoline = trampolineAlloc;
h->trampolineAlloc = trampolineAlloc;
h->trampolineAllocSize = allocSize;
h->enabled = true;
return RadHookResult::Success;
#endif
}
RadHookResult EnableHookInternal(RadHookOpaque* h) {
if (!h->target) return RadHookResult::NotInstalled;
if (h->enabled) return RadHookResult::AlreadyEnabled;
unsigned long oldProtect = 0;
if (!MakeWritableExecutable(h->target, h->originalLength, &oldProtect)) {
return RadHookResult::MemoryProtectFailed;
}
std::memcpy(h->target, h->stubBytes, h->stubLength);
RestoreProtection(h->target, h->originalLength, oldProtect);
FlushICache(h->target, h->originalLength);
h->enabled = true;
return RadHookResult::Success;
}
RadHookResult DisableHookInternal(RadHookOpaque* h) {
if (!h->target) return RadHookResult::NotInstalled;
if (!h->enabled) return RadHookResult::AlreadyDisabled;
unsigned long oldProtect = 0;
if (!MakeWritableExecutable(h->target, h->originalLength, &oldProtect)) {
return RadHookResult::MemoryProtectFailed;
}
std::memcpy(h->target, h->originalBytes, h->originalLength);
RestoreProtection(h->target, h->originalLength, oldProtect);
FlushICache(h->target, h->originalLength);
h->enabled = false;
return RadHookResult::Success;
}
} // anonymous namespace
RadHookResult RadHookCreate(void* target, void* detour, RadHookHandle* outHandle) {
if (outHandle) *outHandle = nullptr;
if (!target) return RadHookResult::InvalidTarget;
if (!detour) return RadHookResult::InvalidDetour;
auto record = std::make_unique<RadHookOpaque>();
RadHookResult result = InstallHook(record.get(), target, detour);
if (result != RadHookResult::Success) {
return result;
}
std::lock_guard<std::mutex> lock(g_mutex);
RadHookOpaque* raw = record.get();
g_hooks.push_back(std::move(record));
if (outHandle) *outHandle = raw;
return RadHookResult::Success;
}
RadHookResult RadHookEnable(RadHookHandle handle) {
std::lock_guard<std::mutex> lock(g_mutex);
if (!IsRegisteredLocked(handle)) return RadHookResult::InvalidHandle;
return EnableHookInternal(handle);
}
RadHookResult RadHookDisable(RadHookHandle handle) {
std::lock_guard<std::mutex> lock(g_mutex);
if (!IsRegisteredLocked(handle)) return RadHookResult::InvalidHandle;
return DisableHookInternal(handle);
}
RadHookResult RadHookDestroy(RadHookHandle handle) {
std::lock_guard<std::mutex> lock(g_mutex);
if (!IsRegisteredLocked(handle)) return RadHookResult::InvalidHandle;
RadHookResult disableResult = RadHookResult::Success;
if (handle->enabled) {
disableResult = DisableHookInternal(handle);
}
if (handle->trampolineAlloc) {
FreeExec(handle->trampolineAlloc, handle->trampolineAllocSize);
handle->trampolineAlloc = nullptr;
}
g_hooks.erase(
std::remove_if(g_hooks.begin(), g_hooks.end(),
[handle](const std::unique_ptr<RadHookOpaque>& h) { return h.get() == handle; }),
g_hooks.end());
return disableResult;
}
RadHookResult RadHookEnableAll() {
std::lock_guard<std::mutex> lock(g_mutex);
RadHookResult last = RadHookResult::Success;
for (auto& h : g_hooks) {
RadHookResult r = EnableHookInternal(h.get());
if (r != RadHookResult::Success && r != RadHookResult::AlreadyEnabled) {
last = r;
}
}
return last;
}
RadHookResult RadHookDisableAll() {
std::lock_guard<std::mutex> lock(g_mutex);
RadHookResult last = RadHookResult::Success;
for (auto& h : g_hooks) {
RadHookResult r = DisableHookInternal(h.get());
if (r != RadHookResult::Success && r != RadHookResult::AlreadyDisabled) {
last = r;
}
}
return last;
}
size_t RadHookGetCount() {
std::lock_guard<std::mutex> lock(g_mutex);
return g_hooks.size();
}
size_t RadHookGetHandles(RadHookHandle* outHandles, size_t maxCount) {
std::lock_guard<std::mutex> lock(g_mutex);
size_t n = std::min(maxCount, g_hooks.size());
for (size_t i = 0; i < n; ++i) {
outHandles[i] = g_hooks[i].get();
}
return g_hooks.size();
}
bool RadHookIsValid(RadHookHandle handle) {
std::lock_guard<std::mutex> lock(g_mutex);
return IsRegisteredLocked(handle);
}
bool RadHookIsEnabled(RadHookHandle handle) {
std::lock_guard<std::mutex> lock(g_mutex);
if (!IsRegisteredLocked(handle)) return false;
return handle->enabled;
}
void* RadHookGetTarget(RadHookHandle handle) {
std::lock_guard<std::mutex> lock(g_mutex);
if (!IsRegisteredLocked(handle)) return nullptr;
return handle->target;
}
void* RadHookGetDetour(RadHookHandle handle) {
std::lock_guard<std::mutex> lock(g_mutex);
if (!IsRegisteredLocked(handle)) return nullptr;
return handle->detour;
}
void* RadHookGetOriginal(RadHookHandle handle) {
std::lock_guard<std::mutex> lock(g_mutex);
if (!IsRegisteredLocked(handle)) return nullptr;
return handle->trampoline;
}
size_t RadHookEnumerate(RadHookHandle* out, size_t maxCount) {
size_t written = 0;
for (auto const& h : g_hooks) {
if (written >= maxCount) break;
out[written++] = h.get();
}
return written;
}