aboutsummaryrefslogtreecommitdiff
path: root/libs/usvfs/src/thooklib
diff options
context:
space:
mode:
Diffstat (limited to 'libs/usvfs/src/thooklib')
-rw-r--r--libs/usvfs/src/thooklib/CMakeLists.txt14
-rw-r--r--libs/usvfs/src/thooklib/asmjit_sane.h29
-rw-r--r--libs/usvfs/src/thooklib/hooklib.cpp740
-rw-r--r--libs/usvfs/src/thooklib/hooklib.h125
-rw-r--r--libs/usvfs/src/thooklib/pch.cpp1
-rw-r--r--libs/usvfs/src/thooklib/ttrampolinepool.cpp601
-rw-r--r--libs/usvfs/src/thooklib/ttrampolinepool.h222
-rw-r--r--libs/usvfs/src/thooklib/udis86wrapper.cpp102
-rw-r--r--libs/usvfs/src/thooklib/udis86wrapper.h62
-rw-r--r--libs/usvfs/src/thooklib/utility.cpp86
-rw-r--r--libs/usvfs/src/thooklib/utility.h35
11 files changed, 2017 insertions, 0 deletions
diff --git a/libs/usvfs/src/thooklib/CMakeLists.txt b/libs/usvfs/src/thooklib/CMakeLists.txt
new file mode 100644
index 0000000..48426db
--- /dev/null
+++ b/libs/usvfs/src/thooklib/CMakeLists.txt
@@ -0,0 +1,14 @@
+cmake_minimum_required(VERSION 3.16)
+
+find_package(asmjit CONFIG REQUIRED)
+find_library(LIBUDIS86_LIBRARY libudis86)
+find_package(spdlog CONFIG REQUIRED)
+
+file(GLOB sources CONFIGURE_DEPENDS "*.cpp" "*.h")
+source_group("" FILES ${sources})
+
+add_library(thooklib STATIC ${sources})
+target_link_libraries(thooklib PRIVATE shared asmjit::asmjit ${LIBUDIS86_LIBRARY} spdlog::spdlog_header_only)
+target_include_directories(thooklib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+set_target_properties(thooklib PROPERTIES FOLDER injection)
+target_precompile_headers(shared PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../shared/pch.h)
diff --git a/libs/usvfs/src/thooklib/asmjit_sane.h b/libs/usvfs/src/thooklib/asmjit_sane.h
new file mode 100644
index 0000000..365622a
--- /dev/null
+++ b/libs/usvfs/src/thooklib/asmjit_sane.h
@@ -0,0 +1,29 @@
+/*
+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 <http://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+#pragma warning(push)
+#pragma warning(disable : 4201)
+#pragma warning(disable : 4244)
+#pragma warning(disable : 4245)
+#include <asmjit/asmjit.h>
+#include <asmjit/x86.h>
+#pragma warning(pop)
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 <http://www.gnu.org/licenses/>.
+*/
+#include "udis86wrapper.h"
+#include <boost/format.hpp>
+#include <boost/predef.h>
+#include <map>
+#pragma warning(push, 3)
+#include <asmjit/asmjit.h>
+#pragma warning(pop)
+#include "hooklib.h"
+#include "ttrampolinepool.h"
+#include "utility.h"
+#include <addrtools.h>
+#include <formatters.h>
+#include <shmlogger.h>
+#include <winapi.h>
+
+#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<uint8_t>
+ 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<intptr_t>(destination) -
+ (reinterpret_cast<intptr_t>(jumpAddr) + 5);
+ int32_t distShort = static_cast<int32_t>(dist);
+#else
+ int32_t distShort = reinterpret_cast<intptr_t>(destination) -
+ (reinterpret_cast<intptr_t>(jumpAddr) + 5);
+#endif
+ *jumpAddr = 0xE9;
+ *reinterpret_cast<int32_t*>(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<LPBYTE>(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<LPBYTE>(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<int8_t>(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<LPVOID>(hookInfo.originalFunction);
+
+ if (hookInfo.stub) {
+ hookInfo.trampoline = TrampolinePool::instance().storeStub(
+ hookInfo.replacementFunction,
+ reinterpret_cast<LPVOID>(hookInfo.originalFunction),
+ shared::AddrAdd(original, 2));
+ } else {
+ hookInfo.trampoline = TrampolinePool::instance().storeTrampoline(
+ hookInfo.replacementFunction,
+ reinterpret_cast<LPVOID>(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<LPBYTE>(hookInfo.originalFunction) + 2;
+
+ return TRUE;
+}
+
+uintptr_t followJumps(THookInfo& hookInfo)
+{
+ LPBYTE original = reinterpret_cast<LPBYTE>(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<uintptr_t>(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<LPVOID>(hookInfo.originalFunction),
+ reinterpret_cast<LPVOID>(chainNext));
+ } else {
+ hookInfo.trampoline = TrampolinePool::instance().storeTrampoline(
+ hookInfo.replacementFunction,
+ reinterpret_cast<LPVOID>(hookInfo.originalFunction),
+ reinterpret_cast<LPVOID>(chainNext));
+ }
+
+ DWORD oldProtect = 0;
+ if (!VirtualProtect(reinterpret_cast<LPVOID>(res), 2, PAGE_EXECUTE_WRITECOPY,
+ &oldProtect)) {
+ throw std::runtime_error("failed to change virtual protection");
+ }
+
+ *reinterpret_cast<uintptr_t*>(res) = reinterpret_cast<uintptr_t>(hookInfo.trampoline);
+
+ if (!VirtualProtect(reinterpret_cast<LPVOID>(res), 2, oldProtect, &oldProtect)) {
+ throw std::runtime_error("failed to change virtual protection");
+ }
+
+ hookInfo.type = THookInfo::TYPE_RIPINDIRECT;
+ hookInfo.detour = reinterpret_cast<LPVOID>(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<std::string>(
+ winapi::ex::wide::getSectionName((void*)chainTarget)));
+
+ if (hookInfo.stub) {
+ hookInfo.trampoline = TrampolinePool::instance().storeStub(
+ hookInfo.replacementFunction,
+ reinterpret_cast<LPVOID>(hookInfo.originalFunction),
+ reinterpret_cast<LPVOID>(chainTarget));
+ } else {
+ hookInfo.trampoline = TrampolinePool::instance().storeTrampoline(
+ hookInfo.replacementFunction,
+ reinterpret_cast<LPVOID>(hookInfo.originalFunction),
+ reinterpret_cast<LPVOID>(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<LPVOID>(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<LPBYTE>(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<LPBYTE>(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<LPBYTE>(hookInfo.originalFunction);
+ ud_set_input_buffer(disasm(), address, 40);
+
+ size_t jumpSize = GetJumpSize(
+ static_cast<LPBYTE>(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<LPBYTE>(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<HOOKHANDLE, THookInfo> 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<LPBYTE>(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<uint8_t*>(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<LPBYTE>(info.originalFunction);
+ if (ud_insn_len(disasm()) == 2) {
+ jumpPos = reinterpret_cast<LPBYTE>(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<LPVOID>(res), JUMP_SIZE,
+ PAGE_EXECUTE_WRITECOPY, &oldProtect)) {
+ throw shared::windows_error("failed to gain write access to remove hook");
+ }
+ *reinterpret_cast<uintptr_t*>(res) =
+ reinterpret_cast<uintptr_t>(info.originalFunction);
+ VirtualProtect(reinterpret_cast<LPVOID>(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;
+}
diff --git a/libs/usvfs/src/thooklib/hooklib.h b/libs/usvfs/src/thooklib/hooklib.h
new file mode 100644
index 0000000..a549c03
--- /dev/null
+++ b/libs/usvfs/src/thooklib/hooklib.h
@@ -0,0 +1,125 @@
+/*
+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 <http://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+#include <map>
+#include <windows_sane.h>
+
+namespace HookLib
+{
+
+enum HookError
+{
+ ERR_NONE,
+ ERR_INVALIDPARAMETERS, // parameters are invalid
+ ERR_FUNCEND, // function is too short to be hooked
+ ERR_JUMP, // function consists only of an unconditional jump. Maybe it has already
+ // been hooked?
+ ERR_RIP, // segment of the function to be overwritten contains a instruction-relative
+ // operation
+ ERR_RELJUMP // segment of the function to be overwritten contains a relative jump we
+ // can't relocated
+};
+
+typedef ULONG HOOKHANDLE;
+static const HOOKHANDLE INVALID_HOOK = (HOOKHANDLE)-1;
+
+///
+/// \brief install a stub (function to be called before the target function)
+/// \param functionAddress address of the function to stub
+/// \param stubAddress address of the stub function. This function has to have the
+/// signature of void foobar(LPVOID address).
+/// address receives the address of the function.
+/// \param error (optional) if set, the referenced variable will receive an error code
+/// describing the problem (if any)
+/// \return a handle to reference the hook in later operations or INVALID_HOOK on error
+///
+HOOKHANDLE InstallStub(LPVOID functionAddress, LPVOID stubAddress,
+ HookError* error = nullptr);
+
+///
+/// \brief install a stub (function to be called before the target function)
+/// \param module the module containing the function to hook
+/// \param functionName name of the function to stub (as exported by the library)
+/// \param stubAddress address of the stub function. This function has to have the
+/// signature of void foobar(LPVOID address).
+/// address receives the address of the function.
+/// \param error (optional) if set, the referenced variable will receive an error code
+/// describing the problem (if any)
+/// \return a handle to reference the hook in later operations or INVALID_HOOK on error
+///
+HOOKHANDLE InstallStub(HMODULE module, LPCSTR functionName, LPVOID stubAddress,
+ HookError* error = nullptr);
+
+///
+/// \brief install a hook (function replacing the existing functionality of the
+/// function)
+/// \param functionAddress address of the function to hook
+/// \param hookAddress address of the replacement function. This function has to have
+/// the exact same signature as the replaced function
+/// \param error (optional) if set, the referenced variable will receive an error code
+/// describing the problem (if any)
+/// \return a handle to reference the hook in later operations or INVALID_HOOK on error
+///
+HOOKHANDLE InstallHook(LPVOID functionAddress, LPVOID hookAddress,
+ HookError* error = nullptr);
+
+///
+/// \brief install a hook (function replacing the existing functionality of the
+/// function)
+/// \param functionName name of the function to hook (as exported by the library)
+/// \param hookAddress address of the replacement function. This function has to have
+/// the exact same signature as the replaced function
+/// \param error (optional) if set, the referenced variable will receive an error code
+/// describing the problem (if any)
+/// \return a handle to reference the hook in later operations or INVALID_HOOK on error
+///
+HOOKHANDLE InstallHook(HMODULE module, LPCSTR functionName, LPVOID hookAddress,
+ HookError* error = nullptr);
+
+///
+/// \brief remove a hook
+/// \param handle handle returned in InstallStub or InstallHook
+///
+void RemoveHook(HOOKHANDLE handle);
+
+///
+/// \brief determine the type of a hook
+/// \param handle the handle to look up
+/// \return a string describing the used hooking mechanism
+///
+const char* GetHookType(HOOKHANDLE handle);
+
+///
+/// \brief retrieve the address that can be used to directly call a detour
+/// \param handle handle for the hook
+/// \return function address
+///
+LPVOID GetDetour(HOOKHANDLE handle);
+
+///
+/// \brief resolve an error code to a descriptive string
+/// \param err the error code to resolve
+/// \return the error string
+///
+const char* GetErrorString(HookError err);
+
+} // namespace HookLib
diff --git a/libs/usvfs/src/thooklib/pch.cpp b/libs/usvfs/src/thooklib/pch.cpp
new file mode 100644
index 0000000..1d9f38c
--- /dev/null
+++ b/libs/usvfs/src/thooklib/pch.cpp
@@ -0,0 +1 @@
+#include "pch.h"
diff --git a/libs/usvfs/src/thooklib/ttrampolinepool.cpp b/libs/usvfs/src/thooklib/ttrampolinepool.cpp
new file mode 100644
index 0000000..3b6a51f
--- /dev/null
+++ b/libs/usvfs/src/thooklib/ttrampolinepool.cpp
@@ -0,0 +1,601 @@
+/*
+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 <http://www.gnu.org/licenses/>.
+*/
+#include "ttrampolinepool.h"
+#include <addrtools.h>
+#include <shmlogger.h>
+// #include <boost/thread/lock_guard.hpp>
+#include "udis86wrapper.h"
+
+using namespace asmjit;
+#if BOOST_ARCH_X86_64
+using namespace x86;
+#elif BOOST_ARCH_X86_32
+using namespace asmjit::x86;
+#endif
+
+using namespace usvfs::shared;
+
+namespace HookLib
+{
+
+TrampolinePool* TrampolinePool::s_Instance = nullptr;
+
+TrampolinePool::TrampolinePool() : m_MaxTrampolineSize(sizeof(LPVOID))
+{
+ m_BarrierAddr = &TrampolinePool::barrier;
+ m_ReleaseAddr = &TrampolinePool::release;
+
+ SYSTEM_INFO sysInfo;
+ ::ZeroMemory(&sysInfo, sizeof(SYSTEM_INFO));
+ GetSystemInfo(&sysInfo);
+ m_BufferSize = sysInfo.dwPageSize;
+
+ // if search range = ffffff then addressmask = ffffffffff000000
+ // => all jumps between xxxxxxxxxx000000 and xxxxxxxxxxffffff will use the same buffer
+ // for trampolines which is guaranteed to be in that range
+ // TODO it should be valid to use 2 ^ 32 as the search range to increase our chances
+ // of finding a memory block we can reserve but then there is a problem with
+ // converting negative jump distances to 32 bit I didn't understand. Everything
+ // up to 2 ^ 31 seems to be fine though
+ m_SearchRange = static_cast<size_t>(pow(2, 30)) - 1;
+ m_AddressMask = std::numeric_limits<uint64_t>::max() - m_SearchRange;
+}
+
+// static
+void TrampolinePool::initialize()
+{
+ if (!s_Instance)
+ s_Instance = new TrampolinePool();
+}
+
+void TrampolinePool::setBlock(bool block)
+{
+ m_FullBlock = block;
+ if (m_ThreadGuards.get() == nullptr) {
+ m_ThreadGuards.reset(new TThreadMap());
+ }
+}
+
+#if BOOST_ARCH_X86_64
+// push all registers (except rax) and flags to the stack
+static void pushAll(X86Assembler& assembler)
+{
+ assembler.pushf();
+ assembler.push(rcx);
+ assembler.push(rdx);
+ assembler.push(rbx);
+ assembler.push(rbp);
+ assembler.push(rsi);
+ assembler.push(rdi);
+ assembler.push(r8);
+ assembler.push(r9);
+ assembler.push(r10);
+ assembler.push(r11);
+ assembler.push(r12);
+ assembler.push(r13);
+ assembler.push(r14);
+ assembler.push(r15);
+}
+
+// pop all registers (except rax) and flags from stack
+static void popAll(X86Assembler& assembler)
+{
+ assembler.pop(r15);
+ assembler.pop(r14);
+ assembler.pop(r13);
+ assembler.pop(r12);
+ assembler.pop(r11);
+ assembler.pop(r10);
+ assembler.pop(r9);
+ assembler.pop(r8);
+ assembler.pop(rdi);
+ assembler.pop(rsi);
+ assembler.pop(rbp);
+ assembler.pop(rbx);
+ assembler.pop(rdx);
+ assembler.pop(rcx);
+ assembler.popf();
+}
+#endif // BOOST_ARCH_X86_64
+
+void TrampolinePool::addBarrier(LPVOID rerouteAddr, LPVOID original,
+ X86Assembler& assembler)
+{
+ Label skipLabel = assembler.newLabel();
+
+#if BOOST_ARCH_X86_64
+ pushAll(assembler);
+ assembler.mov(rcx,
+ imm(reinterpret_cast<int64_t>(
+ original))); // set call parameter for call to barrier function
+ assembler.mov(rax, imm((intptr_t)(void*)barrier));
+ assembler.sub(rsp, 32);
+ assembler.call(rax);
+ assembler.add(rsp, 32);
+ popAll(assembler);
+ // test barrier
+ assembler.cmp(rax, 0); // test if the barrier is locked
+ assembler.jz(skipLabel); // skip if barrier was locked
+
+ // call replacement function
+ // for this call no registers are saved. the called function is a compiled function so
+ // it should correctly save non-volatile registers, and the caller can't expect the
+ // volatile ones to remain valid
+ assembler.pop(r10);
+ assembler.mov(dword_ptr(rax), r10); // store that return address to the variable
+ // supplied by the barrier function
+ assembler.mov(rax, imm((intptr_t)(LPVOID)rerouteAddr));
+ assembler.call(rax);
+ assembler.push(rax); // save away result
+
+ // open the barrier again
+ pushAll(assembler);
+ assembler.mov(rcx, imm(reinterpret_cast<int64_t>(original)));
+ assembler.mov(rax, imm((intptr_t)(void*)release));
+ assembler.sub(rsp, 32);
+ assembler.call(rax);
+ assembler.add(rsp, 32);
+ popAll(assembler);
+ assembler.pop(r10); // get the result from the replacement function to a register
+ assembler.push(rax); // push the original return address back on the stack
+ assembler.mov(rax, r10); // move result of actual call to rax
+ assembler.ret(); // return, using the original return address
+#else // BOOST_ARCH_X86_64
+ assembler.push(imm(void_ptr_cast<int32_t>(
+ original))); // push original function, as parameter to barrier
+ assembler.mov(ecx, (Ptr) static_cast<void*>(TrampolinePool::barrier));
+ assembler.call(ecx); // call barrier function
+ assembler.cmp(eax, 0);
+ assembler.jz(skipLabel); // if barrier is locked, jump to end of function
+
+ // case a: we got through the barrier
+ assembler.pop(ecx); // pop the return address into ecx
+ assembler.mov(dword_ptr(eax), ecx); // store that return address in the variable
+ // supplied by the barrier function
+
+ assembler.mov(eax, (Ptr) static_cast<void*>(rerouteAddr));
+ assembler.call(eax); // call replacement function (pointer was stored in front of the
+ // trampoline) (this function gets the parameters that were on
+ // the stack already and cleans
+ // them up itself (stdcall convention))
+ assembler.push(eax); // save away result
+ assembler.push(imm(void_ptr_cast<int32_t>(original))); // open the barrier again
+ assembler.mov(eax, (Ptr) static_cast<void*>(TrampolinePool::release));
+ assembler.call(eax);
+ assembler.pop(ecx); // pop the result from the actual call to ecx
+ assembler.push(eax); // push the original return address (returned by
+ // TTrampolinePool::release) back on the stack
+ assembler.mov(eax, ecx); // move result of actual call to eax
+ assembler.ret(); // return, using the original return address
+#endif // BOOST_ARCH_X86_64
+
+ assembler.bind(skipLabel);
+}
+
+LPVOID TrampolinePool::roundAddress(LPVOID address) const
+{
+ return reinterpret_cast<LPVOID>(reinterpret_cast<intptr_t>(address) & m_AddressMask);
+}
+
+TrampolinePool::BufferList& TrampolinePool::getBufferList(LPVOID address)
+{
+ LPVOID rounded = roundAddress(address);
+ auto iter = m_Buffers.find(rounded);
+ if (iter == m_Buffers.end()) {
+ BufferList newBufList = {0, std::vector<LPVOID>()};
+ m_Buffers[rounded] = newBufList;
+ iter = allocateBuffer(address);
+ }
+ return iter->second;
+}
+
+LPVOID TrampolinePool::storeStub(LPVOID reroute, LPVOID original, LPVOID returnAddress)
+{
+ BufferList& bufferList = getBufferList(original);
+ // first test to increase likelyhood we don't have to reallocate later
+ if (bufferList.offset + m_MaxTrampolineSize > m_BufferSize) {
+ allocateBuffer(original);
+ }
+
+ LPVOID spot = AddrAdd(*bufferList.buffers.rbegin(), bufferList.offset);
+
+ // ??? write address of reroute to trampoline and move past the address
+ *reinterpret_cast<LPVOID*>(spot) = reroute;
+ // coverity[suspicious_sizeof]
+ spot = AddrAdd(spot, sizeof(LPVOID));
+ bufferList.offset += sizeof(LPVOID);
+
+ JitRuntime runtime;
+#if BOOST_ARCH_X86_64
+ X86Assembler assembler(&runtime);
+#else
+ X86Assembler assembler(&runtime);
+#endif
+ addCallToStub(assembler, original, reroute);
+ addAbsoluteJump(assembler, reinterpret_cast<uint64_t>(returnAddress));
+
+ size_t codeSize = assembler.getCodeSize();
+
+ m_MaxTrampolineSize =
+ std::max(m_MaxTrampolineSize, static_cast<int>(codeSize + sizeof(LPVOID)));
+
+ // final test to see if we can store the trampoline in the buffer
+ if ((bufferList.offset + codeSize) > m_BufferSize) {
+ // can't place function in buffer, allocate another and try again
+ allocateBuffer(original);
+ // we could relocate the code and the data but this is simpler
+ return storeStub(reroute, original, returnAddress);
+ }
+
+ // adjust relative jumps for move to buffer
+ codeSize = assembler.relocCode(spot);
+
+ uint8_t* code = assembler.getBuffer();
+ memcpy(spot, code, codeSize);
+
+ bufferList.offset += codeSize;
+
+ return spot;
+}
+
+LPVOID TrampolinePool::storeTrampoline(LPVOID reroute, LPVOID original,
+ LPVOID returnAddress)
+{
+ BufferList& bufferList = getBufferList(original);
+ // first test to increase likelyhood we don't have to reallocate later
+ if (bufferList.offset + m_MaxTrampolineSize > m_BufferSize) {
+ allocateBuffer(original);
+ }
+
+ LPVOID spot = AddrAdd(*bufferList.buffers.rbegin(), bufferList.offset);
+
+ *reinterpret_cast<LPVOID*>(spot) = reroute;
+ // coverity[suspicious_sizeof]
+ spot = AddrAdd(spot, sizeof(LPVOID));
+ bufferList.offset += sizeof(LPVOID);
+
+ JitRuntime runtime;
+ X86Assembler assembler(&runtime);
+ addBarrier(reroute, original, assembler);
+#if BOOST_ARCH_X86_64
+ assembler.mov(rax, imm((intptr_t)(void*)(returnAddress)));
+ assembler.jmp(rax);
+#else
+ assembler.mov(eax, imm((intptr_t)(void*)(returnAddress)));
+ assembler.jmp(eax);
+#endif
+ size_t codeSize = assembler.getCodeSize();
+
+ m_MaxTrampolineSize =
+ std::max(m_MaxTrampolineSize, static_cast<int>(codeSize + sizeof(LPVOID)));
+
+ // final test to see if we can store the trampoline in the buffer
+ if ((bufferList.offset + codeSize) > m_BufferSize) {
+ // can't place function in buffer, allocate another and try again
+ allocateBuffer(original);
+ // we could relocate the code and the data but this is simpler
+ return storeTrampoline(reroute, original, returnAddress);
+ }
+
+ // adjust relative jumps for move to buffer
+ codeSize = assembler.relocCode(spot);
+
+ // copy code to buffer
+ uint8_t* code = assembler.getBuffer();
+ memcpy(spot, code, codeSize);
+
+ bufferList.offset += codeSize;
+ return spot;
+}
+
+#if BOOST_ARCH_X86_64
+void TrampolinePool::copyCode(X86Assembler& assembler, LPVOID source, size_t numBytes)
+{
+ static UDis86Wrapper disasm;
+
+ disasm.setInputBuffer(static_cast<const uint8_t*>(source), numBytes);
+
+ size_t offset = 0;
+
+ while (ud_disassemble(disasm) != 0) {
+ // rewrite relative jumps, blind copy everything else
+
+ offset += ud_insn_len(disasm);
+
+ // WARNING: doesn't support conditional jumps
+ if ((ud_insn_mnemonic(disasm) == UD_Ijmp) &&
+ (ud_insn_opr(disasm, 0)->type == UD_OP_JIMM)) {
+ uintptr_t dest = disasm.jumpTarget();
+ assembler.mov(rax, imm(static_cast<uint64_t>(dest)));
+ assembler.jmp(rax);
+ } else {
+ assembler.embed(ud_insn_ptr(&disasm.obj()), ud_insn_len(&disasm.obj()));
+ // assembler.data();
+ }
+ }
+}
+#endif
+
+void TrampolinePool::addCallToStub(X86Assembler& assembler, LPVOID original,
+ LPVOID reroute)
+{
+#if BOOST_ARCH_X86_64
+ pushAll(assembler);
+ assembler.mov(rcx, imm(reinterpret_cast<int64_t>(original)));
+ assembler.mov(rax, imm((intptr_t)(LPVOID)reroute));
+ assembler.sub(rsp, 32);
+ assembler.call(rax);
+ assembler.add(rsp, 32);
+ popAll(assembler);
+#else // BOOST_ARCH_X86_64
+ assembler.push(reinterpret_cast<int64_t>(original));
+ assembler.mov(ecx, imm((intptr_t)(LPVOID)reroute));
+ assembler.call(ecx);
+ assembler.pop(ecx); // remove argument from stack
+#endif // BOOST_ARCH_X86_64
+}
+
+void TrampolinePool::addAbsoluteJump(X86Assembler& assembler, uint64_t destination)
+{
+#if BOOST_ARCH_X86_64
+ assembler.push(rax);
+ assembler.push(rax);
+ assembler.mov(rax, imm(destination));
+ assembler.mov(ptr(rsp, 8), rax);
+ assembler.pop(rax);
+ assembler.ret();
+#else // BOOST_ARCH_X86_64
+ assembler.push(imm(destination));
+ assembler.ret();
+#endif // BOOST_ARCH_X86_64
+}
+
+LPVOID TrampolinePool::storeStub(LPVOID reroute, LPVOID original, size_t preambleSize,
+ size_t* rerouteOffset)
+{
+ BufferList& bufferList = getBufferList(original);
+ // first test to increase likelyhood we don't have to reallocate later
+ if (bufferList.offset + m_MaxTrampolineSize > m_BufferSize) {
+ allocateBuffer(original);
+ }
+
+ LPVOID spot = AddrAdd(*bufferList.buffers.rbegin(), bufferList.offset);
+
+ *reinterpret_cast<LPVOID*>(spot) = reroute;
+ // coverity[suspicious_sizeof]
+ spot = AddrAdd(spot, sizeof(LPVOID));
+ bufferList.offset += sizeof(LPVOID);
+
+ JitRuntime runtime;
+ X86Assembler assembler(&runtime);
+ addCallToStub(assembler, original, reroute);
+#if BOOST_ARCH_X86_64
+ // insert backup code
+ *rerouteOffset = assembler.getCodeSize();
+ copyCode(assembler, original, preambleSize);
+#else // BOOST_ARCH_X86_64
+ assembler.embed(original, preambleSize);
+#endif // BOOST_ARCH_X86_64
+ addAbsoluteJump(assembler, reinterpret_cast<uint64_t>(original) + preambleSize);
+
+ // adjust relative jumps for move to buffer
+ size_t codeSize = assembler.getCodeSize();
+
+ m_MaxTrampolineSize =
+ std::max(m_MaxTrampolineSize, static_cast<int>(codeSize + sizeof(LPVOID)));
+
+ // final test to see if we can store the trampoline in the buffer
+ if ((bufferList.offset + codeSize) > m_BufferSize) {
+ // can't place function in buffer, allocate another and try again
+ allocateBuffer(original);
+ // we could relocate the code and the data but this is simpler
+ return storeStub(reroute, original, preambleSize, rerouteOffset);
+ }
+
+ // copy code to buffer
+ codeSize = assembler.relocCode(spot);
+
+ bufferList.offset += preambleSize + codeSize;
+ return spot;
+}
+
+LPVOID TrampolinePool::storeTrampoline(LPVOID reroute, LPVOID original,
+ size_t preambleSize, size_t* rerouteOffset)
+{
+ BufferList& bufferList = getBufferList(original);
+ // first test to increase likelyhood we don't have to reallocate later
+ if (bufferList.offset + m_MaxTrampolineSize > m_BufferSize) {
+ allocateBuffer(original);
+ }
+
+ LPVOID spot = AddrAdd(*bufferList.buffers.rbegin(), bufferList.offset);
+
+ *reinterpret_cast<LPVOID*>(spot) = reroute;
+ // coverity[suspicious_sizeof]
+ spot = AddrAdd(spot, sizeof(LPVOID));
+ bufferList.offset += sizeof(LPVOID);
+
+ JitRuntime runtime;
+ X86Assembler assembler(&runtime);
+ addBarrier(reroute, original, assembler);
+ // insert backup code
+ *rerouteOffset = assembler.getCodeSize();
+ assembler.embed(original, static_cast<uint32_t>(preambleSize));
+ addAbsoluteJump(assembler, reinterpret_cast<uint64_t>(original) + preambleSize);
+
+ // adjust relative jumps for move to buffer
+ size_t codeSize = assembler.getCodeSize();
+
+ m_MaxTrampolineSize =
+ std::max(m_MaxTrampolineSize, static_cast<int>(codeSize + sizeof(LPVOID)));
+
+ // TODO this does not take into account that the code size may technically change
+ // after relocation in which case the following test may determine the code fits into
+ // the buffer when it really doesnt't. asmjit doesn't seem to provide a way to adjust
+ // jumps without actually moving the code though
+
+ // final test to see if we can store the trampoline in the buffer
+ if ((bufferList.offset + codeSize) > m_BufferSize) {
+ // can't place function in buffer, allocate another and try again
+ allocateBuffer(original);
+ // we could relocate the code and the data but this is simpler
+ return storeTrampoline(reroute, original, preambleSize, rerouteOffset);
+ }
+
+ // copy code to buffer
+ codeSize = static_cast<size_t>(assembler.relocCode(spot));
+
+ bufferList.offset += preambleSize + codeSize;
+
+ return spot;
+}
+
+LPVOID TrampolinePool::currentBufferAddress(LPVOID addressNear)
+{
+ LPVOID rounded = roundAddress(addressNear);
+ auto lookupAddress = m_Buffers.find(rounded);
+
+ if (lookupAddress == m_Buffers.end()) {
+ lookupAddress = m_Buffers.insert(std::make_pair(rounded, BufferList())).first;
+ }
+ if (lookupAddress->second.buffers.size() == 0) {
+ allocateBuffer(addressNear);
+ }
+
+ LPVOID res = *(lookupAddress->second.buffers.rbegin());
+ return res;
+}
+
+void TrampolinePool::forceUnlockBarrier()
+{
+ if (m_ThreadGuards.get() != nullptr) {
+ for (auto funcId : *m_ThreadGuards) {
+ (*m_ThreadGuards)[funcId.first] = nullptr;
+ }
+ } // else no barriers to unlock
+}
+
+TrampolinePool::BufferMap::iterator TrampolinePool::allocateBuffer(LPVOID addressNear)
+{
+ // allocate a buffer that we can write to and that is executable
+ SYSTEM_INFO sysInfo;
+ ::ZeroMemory(&sysInfo, sizeof(SYSTEM_INFO));
+ GetSystemInfo(&sysInfo);
+
+ LPVOID rounded = roundAddress(addressNear);
+ auto iter = m_Buffers.find(rounded);
+ uintptr_t lowerEnd = reinterpret_cast<uintptr_t>(rounded);
+ if (iter->second.buffers.size() > 0) {
+ // start searching were we last found a buffer
+ lowerEnd = reinterpret_cast<uintptr_t>(*iter->second.buffers.rbegin()) +
+ sysInfo.dwPageSize;
+ }
+
+ uintptr_t start =
+ std::max(std::max(lowerEnd, MIN_ALLOC_ADDR),
+ reinterpret_cast<uintptr_t>(sysInfo.lpMinimumApplicationAddress));
+ uintptr_t upperEnd = reinterpret_cast<uintptr_t>(rounded) + m_SearchRange;
+ uintptr_t end = std::min(
+ upperEnd, reinterpret_cast<uintptr_t>(sysInfo.lpMaximumApplicationAddress));
+
+ LPVOID buffer = nullptr;
+ for (uintptr_t cur = start; cur < end; cur += sysInfo.dwPageSize) {
+ buffer = VirtualAlloc(reinterpret_cast<LPVOID>(cur), m_BufferSize,
+ MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
+ if (buffer != nullptr) {
+ break;
+ }
+ }
+ if (buffer == nullptr) {
+ throw std::runtime_error("failed to allocate buffer in range");
+ }
+
+ // the caller must have looked up the bufferlist in order to determine that a
+ // buffer has to be allocated
+ assert(iter != m_Buffers.end());
+
+ iter->second.offset = 0;
+ iter->second.buffers.push_back(buffer);
+ spdlog::get("usvfs")->debug(
+ "allocated trampoline buffer for jumps between {0:p} and {1:x} at {2:p}"
+ "(size {3})",
+ rounded, (reinterpret_cast<uintptr_t>(rounded) + m_SearchRange), buffer,
+ m_BufferSize);
+ return iter;
+}
+
+LPVOID TrampolinePool::barrier(LPVOID function)
+{
+ DWORD err = GetLastError();
+ LPVOID res = instance().barrierInt(function);
+ SetLastError(err);
+ return res;
+}
+
+LPVOID TrampolinePool::release(LPVOID function)
+{
+ DWORD err = GetLastError();
+ LPVOID res = instance().releaseInt(function);
+ SetLastError(err);
+ return res;
+}
+
+LPVOID TrampolinePool::barrierInt(LPVOID func)
+{
+ if (m_FullBlock) {
+ return nullptr;
+ }
+
+ if (m_ThreadGuards.get() == nullptr) {
+ m_ThreadGuards.reset(new TThreadMap());
+ }
+
+ auto iter = m_ThreadGuards->find(func);
+ if ((iter == m_ThreadGuards->end()) || (iter->second == nullptr)) {
+ (*m_ThreadGuards)[func] = reinterpret_cast<LPVOID>(1);
+ return &(*m_ThreadGuards)[func];
+ } else {
+ return nullptr;
+ }
+}
+
+LPVOID TrampolinePool::releaseInt(LPVOID func)
+{
+ DWORD lastError = GetLastError();
+ if (m_ThreadGuards.get() == nullptr) {
+ m_ThreadGuards.reset(new TThreadMap());
+ }
+
+ auto iter = m_ThreadGuards->find(func);
+ if (iter == m_ThreadGuards->end()) {
+ spdlog::get("hooks")->error("failed to release barrier for func {}", func);
+ ::SetLastError(lastError);
+ return nullptr;
+ }
+
+ LPVOID res = (*m_ThreadGuards)[func];
+ (*m_ThreadGuards)[func] = nullptr;
+
+ ::SetLastError(lastError);
+ return res;
+}
+
+} // namespace HookLib
diff --git a/libs/usvfs/src/thooklib/ttrampolinepool.h b/libs/usvfs/src/thooklib/ttrampolinepool.h
new file mode 100644
index 0000000..5b44c51
--- /dev/null
+++ b/libs/usvfs/src/thooklib/ttrampolinepool.h
@@ -0,0 +1,222 @@
+/*
+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 <http://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+#include "asmjit_sane.h"
+#include <logging.h>
+#include <map>
+#include <vector>
+#include <windows_sane.h>
+
+#ifdef _MSC_VER
+#pragma warning(disable : 4714)
+#include <boost/config/compiler/visualc.hpp>
+#else
+#include <boost/config/compiler/gcc.hpp>
+#endif
+#include <boost/predef.h>
+#include <boost/thread.hpp>
+#include <mutex>
+
+// #include <boost/thread/mutex.hpp>
+
+namespace HookLib
+{
+
+///
+/// trampolines are runtime-generated mini-functions that are used to call the
+/// original code of a function
+///
+class TrampolinePool
+{
+public:
+ /// Call initialize before you use the TrampolinePool
+ static void initialize();
+
+ static TrampolinePool& instance()
+ {
+ // This is a very very sensitive place so we want to keep this function as simple as
+ // possible and having it inlined probably cann't hurt
+ return *s_Instance;
+ }
+
+ void setBlock(bool block);
+
+ ///
+ /// store a stub without moving code from the original function. This is used in cases
+ /// where the hook can be placed without overwriting logic (i.e. hot-patchable
+ /// functions and when chaining hooks)
+ /// \param reroute the stub function to call before the regular function (on x86 this
+ /// needs to be cdecl calling convention!)
+ /// \param original the original function
+ /// \param returnAddress address under which the original functionality can be
+ /// reached.
+ /// for the first hook this should be (original + 2), otherwise
+ /// the address of the next hook in the chain
+ /// \return address of the created trampoline function
+ ///
+ LPVOID storeStub(LPVOID reroute, LPVOID original, LPVOID returnAddress);
+
+ ///
+ /// store a stub, moving part of the original function to the trampoline
+ /// \param reroute the stub function to call before the regular function (on x86 this
+ /// needs to be cdecl calling convention!)
+ /// \param original the original function
+ /// \param preambleSize number of bytes from the original function to backup. Needs to
+ /// correspond to complete instructions
+ /// \param rerouteOffset offset in bytes from the created trampoline to the preamble
+ /// that leads back to the original code
+ /// \return address of the created trampoline function
+ ///
+ LPVOID storeStub(LPVOID reroute, LPVOID original, size_t preambleSize,
+ size_t* rerouteOffset);
+
+ ///
+ /// store a trampoline for hot-patchable functions, where the original function
+ /// is unharmed.
+ /// \param reroute the reroute function
+ /// \param original original function
+ /// \param returnAddress address under which the original functionality can be
+ /// reached.
+ /// \return address of the trampoline function
+ ///
+ LPVOID storeTrampoline(LPVOID reroute, LPVOID original, LPVOID returnAddress);
+
+ ///
+ /// store a trampoline, copying a part of the original function to the trampoline.
+ /// This is used for the case where the hooking mechanism needs to overwrite part of
+ /// the function
+ /// \param reroute the reroute function
+ /// \param original original function
+ /// \param preambleSize number of bytes from the original function to backup. Needs to
+ /// correspond to complete instructions
+ /// \param rerouteOffset offset in bytes from the created trampoline to the preamble
+ /// that leads us back to the original code
+ /// \return address of the trampoline function
+ ///
+ LPVOID storeTrampoline(LPVOID reroute, LPVOID original, size_t preambleSize,
+ size_t* rerouteOffset);
+
+ ///
+ /// \param addressNear used to find a trampoline buffer near the jump instruction
+ /// \return retrieve address of current trampoline buffer
+ ///
+ LPVOID currentBufferAddress(LPVOID addressNear);
+
+ ///
+ /// \brief forces the barrier(s) for the current thread to be released
+ ///
+ void forceUnlockBarrier();
+
+private:
+ struct BufferList
+ {
+ size_t offset;
+ std::vector<LPVOID> buffers;
+ };
+
+ typedef std::map<LPVOID, BufferList> BufferMap;
+ static const intptr_t ADDRESS_MASK =
+ 0xFFFFFFFFFF000000LL; // mask to "round" addresses to consolidate near
+ // trampolines
+
+private:
+ TrampolinePool();
+
+ TrampolinePool& operator=(const TrampolinePool& reference); // not implemented
+
+ /**
+ * @brief allocates a buffer with read, write and execute rights near the
+ * specified adress. The purpose is that we want to be able to jump from
+ * adressNear to generated code with a 5-byte jump, even on x64 systems.
+ * @param addressNear the reference adress
+ * @note the resulting buffer is stored in the m_Buffers map
+ */
+ BufferMap::iterator allocateBuffer(LPVOID addressNear);
+
+ void addBarrier(LPVOID rerouteAddr, LPVOID original, asmjit::X86Assembler& assembler);
+
+#if BOOST_ARCH_X86_64
+ void copyCode(asmjit::X86Assembler& assembler, LPVOID source, size_t numBytes);
+#endif // BOOST_ARCH_X86_64
+
+ BufferList& getBufferList(LPVOID address);
+
+ LPVOID roundAddress(LPVOID address) const;
+
+public:
+ static LPVOID __stdcall barrier(LPVOID function);
+ static LPVOID __stdcall release(LPVOID function);
+
+ LPVOID barrierInt(LPVOID function);
+ LPVOID releaseInt(LPVOID function);
+
+ void addCallToStub(asmjit::X86Assembler& assembler, LPVOID original, LPVOID reroute);
+
+private:
+ ///
+ /// \brief add a jump to an address outside the custom generated asm code (without
+ /// modifying registers)
+ /// \param assembler the assembler generator to write to
+ /// \param destination destination address
+ /// \note this currently generates a lot code on x64, may be overly complicated
+ ///
+ void addAbsoluteJump(asmjit::X86Assembler& assembler, uint64_t destination);
+
+ DWORD determinePageSize();
+
+private:
+#if BOOST_ARCH_X86_64
+ static const int SIZE_OF_JUMP = 13;
+#elif BOOST_ARCH_X86_32
+ static const int SIZE_OF_JUMP = 5;
+#endif
+
+ // the hook lib tries to allocate buffer close to the hooked functions,
+ // for 32-bits application, this often falls on 0x40000000, which is the
+ // address at which DLLs can be loaded on Windows
+ //
+ // some old-style DRM (e.g. Dragon Age 2) check that address and prevent
+ // the game from running if it's not 0x40000000, so we give them a bit of
+ // spaces before allocating our buffers
+ //
+ static constexpr uintptr_t MIN_ALLOC_ADDR = 0x40000000 + 0x200000;
+
+ static TrampolinePool* s_Instance;
+
+ bool m_FullBlock{false};
+
+ BufferMap m_Buffers;
+
+ typedef std::map<void*, void*> TThreadMap;
+ boost::thread_specific_ptr<TThreadMap> m_ThreadGuards;
+
+ LPVOID m_BarrierAddr;
+ LPVOID m_ReleaseAddr;
+
+ DWORD m_BufferSize = {1024};
+ size_t m_SearchRange;
+ uint64_t m_AddressMask;
+
+ int m_MaxTrampolineSize;
+};
+
+} // namespace HookLib
diff --git a/libs/usvfs/src/thooklib/udis86wrapper.cpp b/libs/usvfs/src/thooklib/udis86wrapper.cpp
new file mode 100644
index 0000000..1ac7332
--- /dev/null
+++ b/libs/usvfs/src/thooklib/udis86wrapper.cpp
@@ -0,0 +1,102 @@
+/*
+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 <http://www.gnu.org/licenses/>.
+*/
+#include "udis86wrapper.h"
+#include "pch.h"
+#include <boost/predef.h>
+#include <shmlogger.h>
+#include <stdexcept>
+
+namespace HookLib
+{
+
+UDis86Wrapper::UDis86Wrapper()
+{
+ ud_init(&m_Obj);
+ ud_set_syntax(&m_Obj, UD_SYN_INTEL);
+#if BOOST_ARCH_X86_64
+ ud_set_mode(&m_Obj, 64);
+#else
+ ud_set_mode(&m_Obj, 32);
+#endif
+}
+
+void UDis86Wrapper::setInputBuffer(const uint8_t* buffer, size_t size)
+{
+ m_Buffer = buffer;
+ ud_set_input_buffer(&m_Obj, buffer, size);
+ ud_set_pc(&m_Obj, reinterpret_cast<uint64_t>(m_Buffer));
+}
+
+ud_t& UDis86Wrapper::obj()
+{
+ return m_Obj;
+}
+
+bool UDis86Wrapper::isRelativeJump()
+{
+ ud_mnemonic_code code = ud_insn_mnemonic(&m_Obj);
+ // all conditional jumps and loops are relative, as are unconditional jumps with an
+ // offset operand
+ return (code == UD_Ijo) || (code == UD_Ijno) || (code == UD_Ijb) ||
+ (code == UD_Ijae) || (code == UD_Ijz) || (code == UD_Ijnz) ||
+ (code == UD_Ijbe) || (code == UD_Ija) || (code == UD_Ijs) ||
+ (code == UD_Ijns) || (code == UD_Ijp) || (code == UD_Ijnp) ||
+ (code == UD_Ijl) || (code == UD_Ijge) || (code == UD_Ijle) ||
+ (code == UD_Ijg) || (code == UD_Ijcxz) || (code == UD_Ijecxz) ||
+ (code == UD_Ijrcxz) || (code == UD_Iloop) || (code == UD_Iloope) ||
+ (code == UD_Iloopne) ||
+ ((code == UD_Icall) && (ud_insn_opr(&m_Obj, 0)->type == UD_OP_JIMM)) ||
+ ((code == UD_Ijmp) && (ud_insn_opr(&m_Obj, 0)->type == UD_OP_JIMM));
+}
+
+intptr_t UDis86Wrapper::jumpOffset()
+{
+ const ud_operand_t* op = ud_insn_opr(&m_Obj, 0);
+ switch (op->size) {
+ case 8:
+ return static_cast<intptr_t>(op->lval.sbyte);
+ case 16:
+ return static_cast<intptr_t>(op->lval.sword);
+ case 32:
+ return static_cast<intptr_t>(op->lval.sdword);
+ case 64:
+ return static_cast<intptr_t>(op->lval.sqword);
+ default:
+ throw std::runtime_error("unsupported jump size");
+ }
+}
+
+uint64_t UDis86Wrapper::jumpTarget()
+{
+ // TODO: assert we're actually on a jump
+
+ uint64_t res = ud_insn_off(&m_Obj) + ud_insn_len(&m_Obj);
+
+ res += jumpOffset();
+
+ if (ud_insn_opr(&m_Obj, 0)->base == UD_R_RIP) {
+ res = *reinterpret_cast<uintptr_t*>(res);
+ }
+
+ return res;
+}
+
+} // namespace HookLib
diff --git a/libs/usvfs/src/thooklib/udis86wrapper.h b/libs/usvfs/src/thooklib/udis86wrapper.h
new file mode 100644
index 0000000..48d8a42
--- /dev/null
+++ b/libs/usvfs/src/thooklib/udis86wrapper.h
@@ -0,0 +1,62 @@
+/*
+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 <http://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+#include <udis86.h>
+#undef inline // libudis86/types.h defines inline to __inline which is no longer legal
+ // since vs2012
+
+namespace HookLib
+{
+
+class UDis86Wrapper
+{
+
+public:
+ UDis86Wrapper();
+
+ void setInputBuffer(const uint8_t* buffer, size_t size);
+
+ ud_t& obj();
+
+ operator ud_t*() { return &m_Obj; }
+
+ bool isRelativeJump();
+
+ intptr_t jumpOffset();
+
+ ///
+ /// determines the absolute jump target at the current instruction, taking into
+ /// account relative instructions of all sizes and RIP-relative addressing.
+ /// \return absolute address of the jump at the current disassembler instruction
+ /// \note this works correctly ONLY if the input buffer has been set with
+ /// setInputBuffer or
+ /// if ud_set_pc has been called
+ ///
+ uint64_t jumpTarget();
+
+private:
+private:
+ ud_t m_Obj;
+ const uint8_t* m_Buffer{nullptr};
+};
+
+} // namespace HookLib
diff --git a/libs/usvfs/src/thooklib/utility.cpp b/libs/usvfs/src/thooklib/utility.cpp
new file mode 100644
index 0000000..124141f
--- /dev/null
+++ b/libs/usvfs/src/thooklib/utility.cpp
@@ -0,0 +1,86 @@
+/*
+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 <http://www.gnu.org/licenses/>.
+*/
+#include "utility.h"
+#include "exceptionex.h"
+#include <cstdlib>
+
+namespace HookLib
+{
+
+FARPROC MyGetProcAddress(HMODULE module, LPCSTR functionName)
+{
+ // determine position of the exports of the module
+ PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)module;
+ if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
+ return nullptr;
+ }
+
+ PIMAGE_NT_HEADERS ntHeaders =
+ (PIMAGE_NT_HEADERS)(((LPBYTE)dosHeader) + dosHeader->e_lfanew);
+ if (ntHeaders->Signature != IMAGE_NT_SIGNATURE) {
+ return nullptr;
+ }
+
+ PIMAGE_OPTIONAL_HEADER optionalHeader = &ntHeaders->OptionalHeader;
+ if (optionalHeader->NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT) {
+ return nullptr;
+ }
+ PIMAGE_DATA_DIRECTORY dataDirectory =
+ &optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
+ PIMAGE_EXPORT_DIRECTORY exportDirectory =
+ (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)dosHeader + dataDirectory->VirtualAddress);
+
+ ULONG* addressOfNames = (ULONG*)((LPBYTE)module + exportDirectory->AddressOfNames);
+ ULONG* funcAddr = (ULONG*)((LPBYTE)module + exportDirectory->AddressOfFunctions);
+
+ // search exports for the specified name
+ for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) {
+ char* curFunctionName = (char*)((LPBYTE)module + addressOfNames[i]);
+ USHORT* nameOrdinals =
+ (USHORT*)((LPBYTE)module + exportDirectory->AddressOfNameOrdinals);
+ if (strcmp(functionName, curFunctionName) == 0) {
+ if (funcAddr[nameOrdinals[i]] >= dataDirectory->VirtualAddress &&
+ funcAddr[nameOrdinals[i]] <
+ dataDirectory->VirtualAddress + dataDirectory->Size) {
+ char* forwardLibName = _strdup((LPSTR)module + funcAddr[nameOrdinals[i]]);
+ ON_BLOCK_EXIT([forwardLibName]() {
+ free(forwardLibName);
+ });
+ char* forwardFunctionName = strchr(forwardLibName, '.');
+ *forwardFunctionName = 0;
+ ++forwardFunctionName;
+
+ HMODULE forwardLib = LoadLibraryA(forwardLibName);
+ FARPROC forward = nullptr;
+ if (forwardLib != nullptr) {
+ forward = MyGetProcAddress(forwardLib, forwardFunctionName);
+ FreeLibrary(forwardLib);
+ }
+
+ return forward;
+ }
+ return (FARPROC)((LPBYTE)module + funcAddr[nameOrdinals[i]]);
+ }
+ }
+ return nullptr;
+}
+
+} // namespace HookLib
diff --git a/libs/usvfs/src/thooklib/utility.h b/libs/usvfs/src/thooklib/utility.h
new file mode 100644
index 0000000..e0be9f7
--- /dev/null
+++ b/libs/usvfs/src/thooklib/utility.h
@@ -0,0 +1,35 @@
+/*
+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 <http://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+#include <windows_sane.h>
+
+namespace HookLib
+{
+
+/// \brief reimplementation of GetProcAddress to circumvent foreign hooks of
+/// GetProcAddress like AcLayer
+/// \param module handle to the module that contains the function or variable
+/// \param functionName function to retrieve the address of
+/// \return address of the exported function
+FARPROC MyGetProcAddress(HMODULE module, LPCSTR functionName);
+
+} // namespace HookLib