From 7ee008e150bc5bcf76082d726f719ee0fdfda982 Mon Sep 17 00:00:00 2001 From: SulfurNitride Date: Wed, 11 Feb 2026 02:37:39 -0600 Subject: Fluorine Manager: full Linux port of Mod Organizer 2 Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 --- libs/usvfs/src/thooklib/hooklib.cpp | 740 ++++++++++++++++++++++++++++++++++++ 1 file changed, 740 insertions(+) create mode 100644 libs/usvfs/src/thooklib/hooklib.cpp (limited to 'libs/usvfs/src/thooklib/hooklib.cpp') diff --git a/libs/usvfs/src/thooklib/hooklib.cpp b/libs/usvfs/src/thooklib/hooklib.cpp new file mode 100644 index 0000000..9dbbafa --- /dev/null +++ b/libs/usvfs/src/thooklib/hooklib.cpp @@ -0,0 +1,740 @@ +/* +Userspace Virtual Filesystem + +Copyright (C) 2015 Sebastian Herbord. All rights reserved. + +This file is part of usvfs. + +usvfs is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +usvfs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with usvfs. If not, see . +*/ +#include "udis86wrapper.h" +#include +#include +#include +#pragma warning(push, 3) +#include +#pragma warning(pop) +#include "hooklib.h" +#include "ttrampolinepool.h" +#include "utility.h" +#include +#include +#include +#include + +#if BOOST_ARCH_X86_64 +#pragma message("64bit build") +#define JUMP_SIZE 13 +#elif BOOST_ARCH_X86_32 +#define JUMP_SIZE 5 +#else +#error "unsupported architecture" +#endif + +using namespace asmjit; +// from here on out I'll only test for 64 or "other" + +using namespace HookLib; +using namespace asmjit; + +using namespace usvfs; + +struct THookInfo +{ + LPVOID originalFunction; + LPVOID replacementFunction; + LPVOID detour; // detour to call the original function after hook was installed. + LPVOID trampoline; // code fragment that decides whether the replacement function or + // detour is executed (preventing endless loops) + std::vector + preamble; // part of the detour that needs to be re-inserted into the original + // function to return it to vanilla state + bool stub; // if this is true, the trampoline calls the "replacement"-function that + // before the original function, not instead of it + enum + { + TYPE_HOTPATCH, // official hot-patch variant as used on 32-bit windows + TYPE_WIN64PATCH, // custom patch variant used on 64-bit windows + TYPE_CHAINPATCH, // the hook is part of the hook chain (and not the first) + TYPE_OVERWRITE, // full jump overwrite used if none of the above work + TYPE_RIPINDIRECT // the function already started on a rip-relative jump so we only + // modified that variable + } type; +}; + +UDis86Wrapper& disasm() +{ + static UDis86Wrapper instance; + return instance; +} + +void PauseOtherThreads() +{ + // TODO: implement me! +} + +void ResumePausedThreads() +{ + // TODO: implement me! should resume only the threads paused by PauseOtherThreads +} + +// not using the disassembler because this is simpler +LPBYTE ShortJumpTarget(LPBYTE address) +{ + int8_t off = *(address + 1); + return address + 2 + off; +} + +size_t GetJumpSize(LPBYTE, LPVOID) +{ + // TODO: it would be neater to use asmjit to generate this jump and ask asmjit for + // the size of this jump but with asmjit I can only generate absolute jumps, which + // take too much space. + + // Since trampoline buffers is always allocated within 32-bit + // address range of jump, we can say with confidence that a 5-byte jump is possible + return 5; +} + +void WriteLongJump(LPBYTE jumpAddr, LPVOID destination) +{ + // TODO: not using asmjit here because I couldn't figure out how to generate + // a working, space-optimized, relative jump to outside the generated code and + // we do want to optimize this jump +#if BOOST_ARCH_X86_64 + intptr_t dist = reinterpret_cast(destination) - + (reinterpret_cast(jumpAddr) + 5); + int32_t distShort = static_cast(dist); +#else + int32_t distShort = reinterpret_cast(destination) - + (reinterpret_cast(jumpAddr) + 5); +#endif + *jumpAddr = 0xE9; + *reinterpret_cast(jumpAddr + 1) = distShort; +} + +void WriteSingleJump(THookInfo& hookInfo, HookError* error) +{ + DWORD oldprotect, ignore; + // Set the target function to copy on write, so we don't modify code for other + // processes + if (!VirtualProtect(hookInfo.originalFunction, JUMP_SIZE, PAGE_EXECUTE_WRITECOPY, + &oldprotect)) { + throw std::runtime_error("failed to change virtual protection"); + } + + WriteLongJump(reinterpret_cast(hookInfo.originalFunction), + hookInfo.trampoline); + + // restore old memory protection + if (!VirtualProtect(hookInfo.originalFunction, JUMP_SIZE, oldprotect, &ignore)) { + throw std::runtime_error("failed to change virtual protection"); + } + + if (error != nullptr) { + *error = ERR_NONE; + } +} + +void WriteShortJump(LPBYTE jumpAddr, const signed char offset) +{ + *jumpAddr = 0xEB; + *(jumpAddr + 1) = offset; +} + +void WriteIndirectJump(THookInfo& hookInfo, size_t jumpSize, HookError* error) +{ + DWORD oldProtect = 0; + LPBYTE jumpAddr = reinterpret_cast(hookInfo.originalFunction) - jumpSize; + // allow write to jump address + the short jump inside the function + if (!VirtualProtect(jumpAddr, jumpSize + 2, PAGE_EXECUTE_WRITECOPY, &oldProtect)) { + throw std::runtime_error("failed to change virtual protection"); + } + + // insert the long jump first, then the short jump to the long jump, thus + // activating the reroute + WriteLongJump(jumpAddr, hookInfo.trampoline); + + PauseOtherThreads(); + WriteShortJump(jumpAddr + jumpSize, -(static_cast(jumpSize) + 2)); + ResumePausedThreads(); + + // restore access protection + if (!VirtualProtect(jumpAddr, jumpSize + 2, oldProtect, &oldProtect)) { + throw std::runtime_error("failed to change virtual protection"); + } + if (error != nullptr) { + *error = ERR_NONE; + } +} + +/// implements function hooking using the mechanism intended for hot patching +/// Explanation: the visual studio compiler offers an option to prepare functions +/// for hot patching. In this case the compiler leaves room for one far jump before +/// the actual function and a 2-byte nop inside the function. +/// to hook such a function we write a jump to our replacement function to the +/// space before the function and short jump to that jump to where the 2-byte nop +/// was. +/// On 32-bit windows, MS seems to have compiled all relevant functions in the +/// windows API for hot patching starting with Windows XP SP3 +/// On 64-bit windows a lot of functions have the space for a jump before the function +/// but they don't have the 2-byte nop so this function doesn't work +/// \param hookInfo info about the hook to be installed +/// \return true on success, false on error +BOOL HookHotPatch(THookInfo& hookInfo, HookError* error) +{ + LPVOID original = reinterpret_cast(hookInfo.originalFunction); + + if (hookInfo.stub) { + hookInfo.trampoline = TrampolinePool::instance().storeStub( + hookInfo.replacementFunction, + reinterpret_cast(hookInfo.originalFunction), + shared::AddrAdd(original, 2)); + } else { + hookInfo.trampoline = TrampolinePool::instance().storeTrampoline( + hookInfo.replacementFunction, + reinterpret_cast(hookInfo.originalFunction), + shared::AddrAdd(original, 2)); + } + + WriteIndirectJump(hookInfo, JUMP_SIZE, error); + hookInfo.type = THookInfo::TYPE_HOTPATCH; + // in this case we don't need a separate detour, we simply jump past the 2-byte nop + hookInfo.detour = reinterpret_cast(hookInfo.originalFunction) + 2; + + return TRUE; +} + +uintptr_t followJumps(THookInfo& hookInfo) +{ + LPBYTE original = reinterpret_cast(hookInfo.originalFunction); + LPBYTE shortTarget = ShortJumpTarget(original); + + // disassemble the long jump + disasm().setInputBuffer(shortTarget, JUMP_SIZE); + + ud_disassemble(disasm()); + if (ud_insn_mnemonic(disasm()) != UD_Ijmp) { + // this shouldn't happen, we only call this if the jump was discovered before + throw std::runtime_error("failed to find jump in patch"); + } + + uint64_t res = ud_insn_off(disasm()) + ud_insn_len(disasm()); + res += disasm().jumpOffset(); + + return static_cast(res); +} + +/// +/// \brief hook a call that is implemented as a short-jump to a long jump using a +/// rip-relative address variable +/// \param hookInfo info about the hook to be installed +/// \param error if the return code is false and this is not null, the referred-to +/// variable is set to an error code +/// \return true on success, false on error +/// +BOOL HookRIPIndirection(THookInfo& hookInfo, HookError* error) +{ + uintptr_t res = followJumps(hookInfo); + + const ud_operand_t* op = ud_insn_opr(disasm(), 0); + + if (op->base != UD_R_RIP) { + throw std::runtime_error("expected rip-relative addressing"); + } + + auto chainNext = disasm().jumpTarget(); + if (hookInfo.stub) { + hookInfo.trampoline = TrampolinePool::instance().storeStub( + hookInfo.replacementFunction, + reinterpret_cast(hookInfo.originalFunction), + reinterpret_cast(chainNext)); + } else { + hookInfo.trampoline = TrampolinePool::instance().storeTrampoline( + hookInfo.replacementFunction, + reinterpret_cast(hookInfo.originalFunction), + reinterpret_cast(chainNext)); + } + + DWORD oldProtect = 0; + if (!VirtualProtect(reinterpret_cast(res), 2, PAGE_EXECUTE_WRITECOPY, + &oldProtect)) { + throw std::runtime_error("failed to change virtual protection"); + } + + *reinterpret_cast(res) = reinterpret_cast(hookInfo.trampoline); + + if (!VirtualProtect(reinterpret_cast(res), 2, oldProtect, &oldProtect)) { + throw std::runtime_error("failed to change virtual protection"); + } + + hookInfo.type = THookInfo::TYPE_RIPINDIRECT; + hookInfo.detour = reinterpret_cast(chainNext); + + if (error != nullptr) { + *error = ERR_NONE; + } + + return TRUE; +} + +BOOL HookChainHook(THookInfo& hookInfo, LPBYTE jumpPos, HookError* error) +{ + // disassemble the long jump + disasm().setInputBuffer(jumpPos, JUMP_SIZE); + + ud_disassemble(disasm()); + if (ud_insn_mnemonic(disasm()) != UD_Ijmp) { + // this shouldn't happen, we only call this if the jump was discovered before + throw std::runtime_error("failed to find jump in patch"); + } + + auto chainTarget = disasm().jumpTarget(); + + size_t size = ud_insn_len(disasm()); + + // save the original code for the preamble so we can restore it later + hookInfo.preamble.resize(size); + memcpy(&hookInfo.preamble[0], jumpPos, size); + + spdlog::get("usvfs")->info("existing hook to {0:x} in {1}", chainTarget, + shared::string_cast( + winapi::ex::wide::getSectionName((void*)chainTarget))); + + if (hookInfo.stub) { + hookInfo.trampoline = TrampolinePool::instance().storeStub( + hookInfo.replacementFunction, + reinterpret_cast(hookInfo.originalFunction), + reinterpret_cast(chainTarget)); + } else { + hookInfo.trampoline = TrampolinePool::instance().storeTrampoline( + hookInfo.replacementFunction, + reinterpret_cast(hookInfo.originalFunction), + reinterpret_cast(chainTarget)); + } + + DWORD oldProtect = 0; + if (!VirtualProtect(jumpPos, size, PAGE_EXECUTE_WRITECOPY, &oldProtect)) { + throw std::runtime_error("failed to change virtual protection"); + } + + WriteLongJump(jumpPos, hookInfo.trampoline); + + if (!VirtualProtect(jumpPos, size, oldProtect, &oldProtect)) { + throw std::runtime_error("failed to change virtual protection"); + } + + hookInfo.type = THookInfo::TYPE_CHAINPATCH; + hookInfo.detour = reinterpret_cast(chainTarget); + + if (error != nullptr) { + *error = ERR_NONE; + } + + return TRUE; +} + +/// +/// implements hooking by chaining to an existing hook +/// \param hookInfo info about the hook to be installed +/// \param error if the return code is false and this is not null, +/// the referred-to variable is set to an error code +/// \return true on success, false on error +/// +BOOL HookChainHook(THookInfo& hookInfo, HookError* error) +{ + return HookChainHook(hookInfo, reinterpret_cast(hookInfo.originalFunction), + error); +} + +/// +/// implements hooking by chaining to an existing hot patch +/// \param hookInfo info about the hook to be installed +/// \param error if the return code is false and this is not null, +/// the referred-to variable is set to an error code +/// \return true on success, false on error +/// +BOOL HookChainPatch(THookInfo& hookInfo, HookError* error) +{ + LPBYTE original = reinterpret_cast(hookInfo.originalFunction); + LPBYTE shortTarget = ShortJumpTarget(original); + + return HookChainHook(hookInfo, shortTarget, error); +} + +/// implements function hooking by overwriting the first n bytes of the function +/// with a jump to the replacement function. Since this is destructive to the original +/// function code the first n bytes of the function need to be copied somewhere else +/// and that code needs to be called via a detour. This is a lot more complex than +/// the hotpatch mechanism. +/// \param hookInfo info about the hook to be installed +/// \param error if the return code is false and this is not null, the referred-to +/// variable is set to an error code +/// \return true on success, false on error +BOOL HookDisasm(THookInfo& hookInfo, HookError* error) +{ + LPBYTE address = reinterpret_cast(hookInfo.originalFunction); + ud_set_input_buffer(disasm(), address, 40); + + size_t jumpSize = GetJumpSize( + static_cast(hookInfo.originalFunction), + TrampolinePool::instance().currentBufferAddress(hookInfo.originalFunction)); + + // test if we have room for a jump before the function + bool jumpspace = true; + for (size_t i = 0; i < jumpSize; ++i) { + if (*(address - i - 1) != 0x90) { + jumpspace = false; + break; + } + } + + size_t minSize = jumpspace ? 2 : jumpSize; + + // iterate over all instructions that overlap with the jump instructions + // we want to write. + // TODO right now, this does not test if the function is smaller than the jump. + size_t size = 0; + while (size < minSize) { + if (ud_disassemble(disasm()) == 0) { + throw std::runtime_error("premature end of file in disassembly"); + } + + if ((size == 0) && (ud_insn_mnemonic(disasm()) == UD_Ijmp)) { + if (error != nullptr) { + *error = ERR_JUMP; + } + return FALSE; + } + + size += ud_insn_len(disasm()); + + // ret instruction or int3 smells like function end + if ((ud_insn_mnemonic(disasm()) == UD_Iret) || + (ud_insn_mnemonic(disasm()) == UD_Iint3)) { + if (error != nullptr) { + *error = ERR_FUNCEND; + } + return FALSE; + } + + // check the operands for relative addressing (not supported) + for (int i = 0; i < 3; ++i) { + const ud_operand* op = ud_insn_opr(disasm(), i); + if (op) { + if ( + // rip-relative are not handled, usually relative jumps + op->base == UD_R_RIP + + // rsp-relative call are not handled (there are valid rsp-relative + // instructions, e.g. sub) + || (ud_insn_mnemonic(disasm()) == UD_Icall && op->base == UD_R_RSP)) { + if (error != nullptr) { + *error = ERR_RIP; + } + return FALSE; + } + } + } + } + + // save the original code for the preamble so we can restore it later + hookInfo.preamble.resize(size); + memcpy(&hookInfo.preamble[0], hookInfo.originalFunction, size); + + size_t rerouteOffset = 0; + if (hookInfo.stub) { + hookInfo.trampoline = TrampolinePool::instance().storeStub( + hookInfo.replacementFunction, hookInfo.originalFunction, size, &rerouteOffset); + } else { + hookInfo.trampoline = TrampolinePool::instance().storeTrampoline( + hookInfo.replacementFunction, hookInfo.originalFunction, size, &rerouteOffset); + } + + if (jumpspace) { + WriteIndirectJump(hookInfo, jumpSize, error); + hookInfo.type = THookInfo::TYPE_WIN64PATCH; + } else { + WriteSingleJump(hookInfo, error); + hookInfo.type = THookInfo::TYPE_OVERWRITE; + } + hookInfo.detour = reinterpret_cast(hookInfo.trampoline) + rerouteOffset; + + return TRUE; +} + +enum EPreamble +{ + PRE_PATCHFREE, + PRE_PATCHUSED, + PRE_RIPINDIRECT, + PRE_FOREIGNHOOK, + PRE_UNKNOWN +}; + +EPreamble DeterminePreamble(LPBYTE address) +{ + ud_set_input_buffer(disasm(), address, JUMP_SIZE); + ud_disassemble(disasm()); + + if ((ud_insn_mnemonic(disasm()) == UD_Imov) && + (ud_insn_opr(disasm(), 0) == ud_insn_opr(disasm(), 1)) && + (ud_insn_opr(disasm(), 0)->type == UD_OP_REG)) { + // mov edi, edi + return PRE_PATCHFREE; + } else if ((ud_insn_mnemonic(disasm()) == UD_Ijmp) && (ud_insn_len(disasm()) == 2)) { + // determine target of the short jump + LPBYTE shortTarget = ShortJumpTarget(address); + + // test if that short jump leads to a long jump + ud_set_input_buffer(disasm(), shortTarget, JUMP_SIZE); + ud_disassemble(disasm()); + if (ud_insn_mnemonic(disasm()) == UD_Ijmp) { + const ud_operand* op = ud_insn_opr(disasm(), 0); + if (op->base == UD_R_RIP) { + return PRE_RIPINDIRECT; + } else { + return PRE_PATCHUSED; + } + } else { + return PRE_UNKNOWN; + } + } else if (ud_insn_mnemonic(disasm()) == UD_Ijmp) { + return PRE_FOREIGNHOOK; + } else { + return PRE_UNKNOWN; + } +} + +static std::map s_Hooks; + +static HOOKHANDLE GenerateHandle() +{ + static ULONG NextHandle = 1; + return NextHandle++; +} + +HOOKHANDLE applyHook(THookInfo info, HookError* error) +{ + // apply the correct hook function depending on how the function start looks + EPreamble preamble = DeterminePreamble((LPBYTE)info.originalFunction); + + BOOL success = FALSE; + switch (preamble) { + case PRE_PATCHUSED: { + success = HookChainPatch(info, error); + } break; + case PRE_PATCHFREE: { + success = HookHotPatch(info, error); + } break; + case PRE_RIPINDIRECT: { + success = HookRIPIndirection(info, error); + } break; + case PRE_FOREIGNHOOK: { + success = HookChainHook(info, error); + } break; + default: { + success = HookDisasm(info, error); + } break; + } + + if (success == TRUE) { + HOOKHANDLE handle = GenerateHandle(); + s_Hooks[handle] = info; + return handle; + } else { + return INVALID_HOOK; + } +} + +HOOKHANDLE HookLib::InstallStub(LPVOID functionAddress, LPVOID stubAddress, + HookError* error) +{ + if (functionAddress == nullptr) { + if (error != nullptr) + *error = ERR_INVALIDPARAMETERS; + return INVALID_HOOK; + } + + THookInfo info; + info.originalFunction = functionAddress; + info.replacementFunction = stubAddress; + info.stub = true; + info.detour = nullptr; + info.trampoline = nullptr; + info.type = THookInfo::TYPE_OVERWRITE; + + return applyHook(info, error); +} + +HOOKHANDLE HookLib::InstallStub(HMODULE module, LPCSTR functionName, LPVOID stubAddress, + HookError* error) +{ + LPVOID funcAddr = MyGetProcAddress(module, functionName); + return InstallStub(funcAddr, stubAddress, error); +} + +HOOKHANDLE HookLib::InstallHook(LPVOID functionAddress, LPVOID hookAddress, + HookError* error) +{ + if (functionAddress == nullptr) { + if (error != nullptr) + *error = ERR_INVALIDPARAMETERS; + return INVALID_HOOK; + } + THookInfo info; + info.originalFunction = functionAddress; + info.replacementFunction = hookAddress; + info.stub = false; + info.detour = nullptr; + info.trampoline = nullptr; + info.type = THookInfo::TYPE_OVERWRITE; + + return applyHook(info, error); +} + +HOOKHANDLE HookLib::InstallHook(HMODULE module, LPCSTR functionName, LPVOID hookAddress, + HookError* error) +{ + LPVOID funcAddr = MyGetProcAddress(module, functionName); + return InstallHook(funcAddr, hookAddress, error); +} + +void HookLib::RemoveHook(HOOKHANDLE handle) +{ + auto iter = s_Hooks.find(handle); + if (iter != s_Hooks.end()) { + THookInfo info = iter->second; + PauseOtherThreads(); + LPBYTE address = reinterpret_cast(info.originalFunction); + if (info.type == THookInfo::TYPE_HOTPATCH) { + // return the short jump to 2-byte nop + // TODO: This doesn't take into account if we chain-loaded another hook + + DWORD oldProtect = 0; + if (!VirtualProtect(address, 2, PAGE_EXECUTE_WRITECOPY, &oldProtect)) { + throw shared::windows_error("failed to gain write access to remove hook"); + } + *address = 0x8b; + *(address + 1) = 0xff; + VirtualProtect(address, 2, oldProtect, &oldProtect); + } else if ((info.type == THookInfo::TYPE_OVERWRITE) || + (info.type == THookInfo::TYPE_WIN64PATCH)) { + DWORD oldProtect = 0; + if (!VirtualProtect(address, info.preamble.size(), PAGE_EXECUTE_WRITECOPY, + &oldProtect)) { + throw shared::windows_error("failed to gain write access to remove hook"); + } + // TODO: remove hook by restoring the original function. This only works if we + // have the exact code available somewhere + memcpy(address, &info.preamble[0], info.preamble.size()); + VirtualProtect(address, info.preamble.size(), oldProtect, &oldProtect); + } else if (info.type == THookInfo::TYPE_CHAINPATCH) { + // we could attempt to restore the original function preamble but I'm not + // sure we can reliably write the jump with same (or lower) size. + // Instead overwrite our own trampoline + disasm().setInputBuffer(static_cast(info.originalFunction), JUMP_SIZE); + ud_disassemble(disasm()); + if (ud_insn_mnemonic(disasm()) != UD_Ijmp) { + // this shouldn't happen, we only call this if the jump was discovered before + throw std::runtime_error("failed to find jump in patch"); + } + + LPBYTE jumpPos = static_cast(info.originalFunction); + if (ud_insn_len(disasm()) == 2) { + jumpPos = reinterpret_cast(disasm().jumpTarget()); + } + + DWORD oldProtect = 0; + if (!VirtualProtect(jumpPos, info.preamble.size(), PAGE_EXECUTE_WRITECOPY, + &oldProtect)) { + throw shared::windows_error("failed to gain write access to remove hook"); + } + memcpy(jumpPos, info.preamble.data(), info.preamble.size()); + VirtualProtect(jumpPos, info.preamble.size(), oldProtect, &oldProtect); + } else if (info.type == THookInfo::TYPE_RIPINDIRECT) { + uintptr_t res = followJumps(info); + DWORD oldProtect = 0; + if (!VirtualProtect(reinterpret_cast(res), JUMP_SIZE, + PAGE_EXECUTE_WRITECOPY, &oldProtect)) { + throw shared::windows_error("failed to gain write access to remove hook"); + } + *reinterpret_cast(res) = + reinterpret_cast(info.originalFunction); + VirtualProtect(reinterpret_cast(res), JUMP_SIZE, oldProtect, &oldProtect); + } else { + spdlog::get("usvfs")->critical("can't remove hook, unknown hook type!"); + } + + s_Hooks.erase(iter); + ResumePausedThreads(); + } else { + spdlog::get("usvfs")->info("handle unknown: {0:x}", handle); + } +} + +const char* HookLib::GetErrorString(HookError err) +{ + switch (err) { + case ERR_NONE: + return "No Error"; + case ERR_INVALIDPARAMETERS: + return "Invalid parameters"; + case ERR_FUNCEND: + return "Function too short"; + case ERR_JUMP: + return "Function starts on a jump"; + case ERR_RIP: + return "RIP-relative addressing can't be relocated."; + case ERR_RELJUMP: + return "Relative Jump can't be relocated."; + default: + return "Unkown error code"; + } +} + +const char* HookLib::GetHookType(HOOKHANDLE handle) +{ + auto iter = s_Hooks.find(handle); + if (iter != s_Hooks.end()) { + THookInfo info = iter->second; + switch (info.type) { + case THookInfo::TYPE_HOTPATCH: + return "hot patch"; + case THookInfo::TYPE_WIN64PATCH: + return "64-bit hot patch"; + case THookInfo::TYPE_CHAINPATCH: + return "chained patch"; + case THookInfo::TYPE_OVERWRITE: + return "overwrite"; + case THookInfo::TYPE_RIPINDIRECT: + return "rip indirection modified"; + default: { + spdlog::get("usvfs")->error("invalid hook type {0}", info.type); + return "invalid hook type"; + } + } + } + return "invalid handle"; +} + +LPVOID HookLib::GetDetour(HOOKHANDLE handle) +{ + auto iter = s_Hooks.find(handle); + if (iter != s_Hooks.end()) { + THookInfo info = iter->second; + return info.detour; + } + return nullptr; +} -- cgit v1.3.1