Add initial project files (excluding ignored content)
This commit is contained in:
15
external/safetyhook/include/safetyhook.hpp
vendored
Normal file
15
external/safetyhook/include/safetyhook.hpp
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "safetyhook/os.hpp"
|
||||
#include "safetyhook/easy.hpp"
|
||||
#include "safetyhook/inline_hook.hpp"
|
||||
#include "safetyhook/mid_hook.hpp"
|
||||
#include "safetyhook/vmt_hook.hpp"
|
||||
|
||||
using SafetyHookContext = safetyhook::Context;
|
||||
using SafetyHookInline = safetyhook::InlineHook;
|
||||
using SafetyHookMid = safetyhook::MidHook;
|
||||
using SafetyInlineHook [[deprecated("Use SafetyHookInline instead.")]] = safetyhook::InlineHook;
|
||||
using SafetyMidHook [[deprecated("Use SafetyHookMid instead.")]] = safetyhook::MidHook;
|
||||
using SafetyHookVmt = safetyhook::VmtHook;
|
||||
using SafetyHookVm = safetyhook::VmHook;
|
||||
133
external/safetyhook/include/safetyhook/allocator.hpp
vendored
Normal file
133
external/safetyhook/include/safetyhook/allocator.hpp
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
/// @file safetyhook/allocator.hpp
|
||||
/// @brief Allocator for allocating memory near target addresses.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef SAFETYHOOK_USE_CXXMODULES
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#else
|
||||
import std.compat;
|
||||
#endif
|
||||
|
||||
#include "safetyhook/common.hpp"
|
||||
|
||||
namespace safetyhook {
|
||||
class Allocator;
|
||||
|
||||
/// @brief A memory allocation.
|
||||
class SAFETYHOOK_API Allocation final {
|
||||
public:
|
||||
Allocation() = default;
|
||||
Allocation(const Allocation&) = delete;
|
||||
Allocation(Allocation&& other) noexcept;
|
||||
Allocation& operator=(const Allocation&) = delete;
|
||||
Allocation& operator=(Allocation&& other) noexcept;
|
||||
~Allocation();
|
||||
|
||||
/// @brief Frees the allocation.
|
||||
/// @note This is called automatically when the Allocation object is destroyed.
|
||||
void free();
|
||||
|
||||
/// @brief Returns a pointer to the data of the allocation.
|
||||
/// @return Pointer to the data of the allocation.
|
||||
[[nodiscard]] uint8_t* data() const noexcept { return m_address; }
|
||||
|
||||
/// @brief Returns the address of the allocation.
|
||||
/// @return The address of the allocation.
|
||||
[[nodiscard]] uintptr_t address() const noexcept { return reinterpret_cast<uintptr_t>(m_address); }
|
||||
|
||||
/// @brief Returns the size of the allocation.
|
||||
/// @return The size of the allocation.
|
||||
[[nodiscard]] size_t size() const noexcept { return m_size; }
|
||||
|
||||
/// @brief Tests if the allocation is valid.
|
||||
/// @return True if the allocation is valid, false otherwise.
|
||||
explicit operator bool() const noexcept { return m_address != nullptr && m_size != 0; }
|
||||
|
||||
protected:
|
||||
friend Allocator;
|
||||
|
||||
Allocation(std::shared_ptr<Allocator> allocator, uint8_t* address, size_t size) noexcept;
|
||||
|
||||
private:
|
||||
std::shared_ptr<Allocator> m_allocator{};
|
||||
uint8_t* m_address{};
|
||||
size_t m_size{};
|
||||
};
|
||||
|
||||
/// @brief Allocates memory near target addresses.
|
||||
class SAFETYHOOK_API Allocator final : public std::enable_shared_from_this<Allocator> {
|
||||
public:
|
||||
/// @brief Returns the global Allocator.
|
||||
/// @return The global Allocator.
|
||||
[[nodiscard]] static std::shared_ptr<Allocator> global();
|
||||
|
||||
/// @brief Creates a new Allocator.
|
||||
/// @return The new Allocator.
|
||||
[[nodiscard]] static std::shared_ptr<Allocator> create();
|
||||
|
||||
Allocator(const Allocator&) = delete;
|
||||
Allocator(Allocator&&) noexcept = delete;
|
||||
Allocator& operator=(const Allocator&) = delete;
|
||||
Allocator& operator=(Allocator&&) noexcept = delete;
|
||||
~Allocator() = default;
|
||||
|
||||
/// @brief The error type returned by the allocate functions.
|
||||
enum class Error : uint8_t {
|
||||
BAD_VIRTUAL_ALLOC, ///< VirtualAlloc failed.
|
||||
NO_MEMORY_IN_RANGE, ///< No memory in range.
|
||||
};
|
||||
|
||||
/// @brief Allocates memory.
|
||||
/// @param size The size of the allocation.
|
||||
/// @return The Allocation or an Allocator::Error if the allocation failed.
|
||||
[[nodiscard]] std::expected<Allocation, Error> allocate(size_t size);
|
||||
|
||||
/// @brief Allocates memory near a target address.
|
||||
/// @param desired_addresses The target address.
|
||||
/// @param size The size of the allocation.
|
||||
/// @param max_distance The maximum distance from the target address.
|
||||
/// @return The Allocation or an Allocator::Error if the allocation failed.
|
||||
[[nodiscard]] std::expected<Allocation, Error> allocate_near(
|
||||
const std::vector<uint8_t*>& desired_addresses, size_t size, size_t max_distance = 0x7FFF'FFFF);
|
||||
|
||||
protected:
|
||||
friend Allocation;
|
||||
|
||||
void free(uint8_t* address, size_t size);
|
||||
|
||||
private:
|
||||
struct FreeNode {
|
||||
std::unique_ptr<FreeNode> next{};
|
||||
uint8_t* start{};
|
||||
uint8_t* end{};
|
||||
};
|
||||
|
||||
struct Memory {
|
||||
uint8_t* address{};
|
||||
size_t size{};
|
||||
std::unique_ptr<FreeNode> freelist{};
|
||||
|
||||
~Memory();
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<Memory>> m_memory{};
|
||||
std::mutex m_mutex{};
|
||||
|
||||
Allocator() = default;
|
||||
|
||||
[[nodiscard]] std::expected<Allocation, Error> internal_allocate_near(
|
||||
const std::vector<uint8_t*>& desired_addresses, size_t size, size_t max_distance = 0x7FFF'FFFF);
|
||||
void internal_free(uint8_t* address, size_t size);
|
||||
|
||||
static void combine_adjacent_freenodes(Memory& memory);
|
||||
[[nodiscard]] static std::expected<uint8_t*, Error> allocate_nearby_memory(
|
||||
const std::vector<uint8_t*>& desired_addresses, size_t size, size_t max_distance);
|
||||
[[nodiscard]] static bool in_range(
|
||||
uint8_t* address, const std::vector<uint8_t*>& desired_addresses, size_t max_distance);
|
||||
};
|
||||
} // namespace safetyhook
|
||||
90
external/safetyhook/include/safetyhook/common.hpp
vendored
Normal file
90
external/safetyhook/include/safetyhook/common.hpp
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define SAFETYHOOK_COMPILER_MSVC 1
|
||||
#define SAFETYHOOK_COMPILER_GCC 0
|
||||
#define SAFETYHOOK_COMPILER_CLANG 0
|
||||
#elif defined(__GNUC__)
|
||||
#define SAFETYHOOK_COMPILER_MSVC 0
|
||||
#define SAFETYHOOK_COMPILER_GCC 1
|
||||
#define SAFETYHOOK_COMPILER_CLANG 0
|
||||
#elif defined(__clang__)
|
||||
#define SAFETYHOOK_COMPILER_MSVC 0
|
||||
#define SAFETYHOOK_COMPILER_GCC 0
|
||||
#define SAFETYHOOK_COMPILER_CLANG 1
|
||||
#else
|
||||
#error "Unsupported compiler"
|
||||
#endif
|
||||
|
||||
#if SAFETYHOOK_COMPILER_MSVC
|
||||
#if defined(_M_IX86)
|
||||
#define SAFETYHOOK_ARCH_X86_32 1
|
||||
#define SAFETYHOOK_ARCH_X86_64 0
|
||||
#elif defined(_M_X64)
|
||||
#define SAFETYHOOK_ARCH_X86_32 0
|
||||
#define SAFETYHOOK_ARCH_X86_64 1
|
||||
#else
|
||||
#error "Unsupported architecture"
|
||||
#endif
|
||||
#elif SAFETYHOOK_COMPILER_GCC || SAFETYHOOK_COMPILER_CLANG
|
||||
#if defined(__i386__)
|
||||
#define SAFETYHOOK_ARCH_X86_32 1
|
||||
#define SAFETYHOOK_ARCH_X86_64 0
|
||||
#elif defined(__x86_64__)
|
||||
#define SAFETYHOOK_ARCH_X86_32 0
|
||||
#define SAFETYHOOK_ARCH_X86_64 1
|
||||
#else
|
||||
#error "Unsupported architecture"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define SAFETYHOOK_OS_WINDOWS 1
|
||||
#define SAFETYHOOK_OS_LINUX 0
|
||||
#elif defined(__linux__)
|
||||
#define SAFETYHOOK_OS_WINDOWS 0
|
||||
#define SAFETYHOOK_OS_LINUX 1
|
||||
#else
|
||||
#error "Unsupported OS"
|
||||
#endif
|
||||
|
||||
#if SAFETYHOOK_OS_WINDOWS
|
||||
#if SAFETYHOOK_COMPILER_MSVC
|
||||
#define SAFETYHOOK_CCALL __cdecl
|
||||
#define SAFETYHOOK_STDCALL __stdcall
|
||||
#define SAFETYHOOK_FASTCALL __fastcall
|
||||
#define SAFETYHOOK_THISCALL __thiscall
|
||||
#elif SAFETYHOOK_COMPILER_GCC || SAFETYHOOK_COMPILER_CLANG
|
||||
#define SAFETYHOOK_CCALL __attribute__((cdecl))
|
||||
#define SAFETYHOOK_STDCALL __attribute__((stdcall))
|
||||
#define SAFETYHOOK_FASTCALL __attribute__((fastcall))
|
||||
#define SAFETYHOOK_THISCALL __attribute__((thiscall))
|
||||
#endif
|
||||
#else
|
||||
#define SAFETYHOOK_CCALL
|
||||
#define SAFETYHOOK_STDCALL
|
||||
#define SAFETYHOOK_FASTCALL
|
||||
#define SAFETYHOOK_THISCALL
|
||||
#endif
|
||||
|
||||
#if SAFETYHOOK_COMPILER_MSVC
|
||||
#define SAFETYHOOK_NOINLINE __declspec(noinline)
|
||||
#elif SAFETYHOOK_COMPILER_GCC || SAFETYHOOK_COMPILER_CLANG
|
||||
#define SAFETYHOOK_NOINLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
#if SAFETYHOOK_COMPILER_MSVC
|
||||
#define SAFETYHOOK_DLLEXPORT __declspec(dllexport)
|
||||
#define SAFETYHOOK_DLLIMPORT __declspec(dllimport)
|
||||
#elif SAFETYHOOK_COMPILER_GCC || SAFETYHOOK_COMPILER_CLANG
|
||||
#define SAFETYHOOK_DLLEXPORT __attribute__((visibility("default")))
|
||||
#define SAFETYHOOK_DLLIMPORT
|
||||
#endif
|
||||
|
||||
#if SAFETYHOOK_SHARED_LIB && SAFETYHOOK_BUILDING
|
||||
#define SAFETYHOOK_API SAFETYHOOK_DLLEXPORT
|
||||
#elif SAFETYHOOK_SHARED_LIB
|
||||
#define SAFETYHOOK_API SAFETYHOOK_DLLIMPORT
|
||||
#else
|
||||
#define SAFETYHOOK_API
|
||||
#endif
|
||||
57
external/safetyhook/include/safetyhook/context.hpp
vendored
Normal file
57
external/safetyhook/include/safetyhook/context.hpp
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
/// @file safetyhook/context.hpp
|
||||
/// @brief Context structure for MidHook.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef SAFETYHOOK_USE_CXXMODULES
|
||||
#include <cstdint>
|
||||
#else
|
||||
import std.compat;
|
||||
#endif
|
||||
|
||||
#include "safetyhook/common.hpp"
|
||||
|
||||
namespace safetyhook {
|
||||
union Xmm {
|
||||
uint8_t u8[16];
|
||||
uint16_t u16[8];
|
||||
uint32_t u32[4];
|
||||
uint64_t u64[2];
|
||||
float f32[4];
|
||||
double f64[2];
|
||||
};
|
||||
|
||||
/// @brief Context structure for 64-bit MidHook.
|
||||
/// @details This structure is used to pass the context of the hooked function to the destination allowing full access
|
||||
/// to the 64-bit registers at the moment the hook is called.
|
||||
/// @note rip will point to a trampoline containing the replaced instruction(s).
|
||||
/// @note rsp is read-only. Modifying it will have no effect. Use trampoline_rsp to modify rsp if needed but make sure
|
||||
/// the top of the stack is the rip you want to resume at.
|
||||
struct Context64 {
|
||||
Xmm xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, xmm14, xmm15;
|
||||
uintptr_t rflags, r15, r14, r13, r12, r11, r10, r9, r8, rdi, rsi, rdx, rcx, rbx, rax, rbp, rsp, trampoline_rsp, rip;
|
||||
};
|
||||
|
||||
/// @brief Context structure for 32-bit MidHook.
|
||||
/// @details This structure is used to pass the context of the hooked function to the destination allowing full access
|
||||
/// to the 32-bit registers at the moment the hook is called.
|
||||
/// @note eip will point to a trampoline containing the replaced instruction(s).
|
||||
/// @note esp is read-only. Modifying it will have no effect. Use trampoline_esp to modify esp if needed but make sure
|
||||
/// the top of the stack is the eip you want to resume at.
|
||||
struct Context32 {
|
||||
Xmm xmm0, xmm1, xmm2, xmm3, xmm4, xmm5, xmm6, xmm7;
|
||||
uintptr_t eflags, edi, esi, edx, ecx, ebx, eax, ebp, esp, trampoline_esp, eip;
|
||||
};
|
||||
|
||||
/// @brief Context structure for MidHook.
|
||||
/// @details This structure is used to pass the context of the hooked function to the destination allowing full access
|
||||
/// to the registers at the moment the hook is called.
|
||||
/// @note The structure is different depending on architecture.
|
||||
/// @note The structure only provides access to integer registers.
|
||||
#if SAFETYHOOK_ARCH_X86_64
|
||||
using Context = Context64;
|
||||
#elif SAFETYHOOK_ARCH_X86_32
|
||||
using Context = Context32;
|
||||
#endif
|
||||
|
||||
} // namespace safetyhook
|
||||
67
external/safetyhook/include/safetyhook/easy.hpp
vendored
Normal file
67
external/safetyhook/include/safetyhook/easy.hpp
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
/// @file safetyhook/easy.hpp
|
||||
/// @brief Easy to use API for creating hooks.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "safetyhook/common.hpp"
|
||||
#include "safetyhook/inline_hook.hpp"
|
||||
#include "safetyhook/mid_hook.hpp"
|
||||
#include "safetyhook/utility.hpp"
|
||||
#include "safetyhook/vmt_hook.hpp"
|
||||
|
||||
namespace safetyhook {
|
||||
/// @brief Easy to use API for creating an InlineHook.
|
||||
/// @param target The address of the function to hook.
|
||||
/// @param destination The address of the destination function.
|
||||
/// @param flags The flags to use.
|
||||
/// @return The InlineHook object.
|
||||
[[nodiscard]] InlineHook SAFETYHOOK_API create_inline(
|
||||
void* target, void* destination, InlineHook::Flags flags = InlineHook::Default);
|
||||
|
||||
/// @brief Easy to use API for creating an InlineHook.
|
||||
/// @param target The address of the function to hook.
|
||||
/// @param destination The address of the destination function.
|
||||
/// @param flags The flags to use.
|
||||
/// @return The InlineHook object.
|
||||
template <typename T, typename U>
|
||||
[[nodiscard]] InlineHook create_inline(T target, U destination, InlineHook::Flags flags = InlineHook::Default) {
|
||||
return create_inline(reinterpret_cast<void*>(target), reinterpret_cast<void*>(destination), flags);
|
||||
}
|
||||
|
||||
/// @brief Easy to use API for creating a MidHook.
|
||||
/// @param target the address of the function to hook.
|
||||
/// @param destination The destination function.
|
||||
/// @param flags The flags to use.
|
||||
/// @return The MidHook object.
|
||||
[[nodiscard]] MidHook SAFETYHOOK_API create_mid(
|
||||
void* target, MidHookFn destination, MidHook::Flags flags = MidHook::Default);
|
||||
|
||||
/// @brief Easy to use API for creating a MidHook.
|
||||
/// @param target the address of the function to hook.
|
||||
/// @param destination The destination function.
|
||||
/// @param flags The flags to use.
|
||||
/// @return The MidHook object.
|
||||
template <typename T>
|
||||
[[nodiscard]] MidHook create_mid(T target, MidHookFn destination, MidHook::Flags flags = MidHook::Default) {
|
||||
return create_mid(reinterpret_cast<void*>(target), destination, flags);
|
||||
}
|
||||
|
||||
/// @brief Easy to use API for creating a VmtHook.
|
||||
/// @param object The object to hook.
|
||||
/// @return The VmtHook object.
|
||||
[[nodiscard]] VmtHook SAFETYHOOK_API create_vmt(void* object);
|
||||
|
||||
/// @brief Easy to use API for creating a VmHook.
|
||||
/// @param vmt The VmtHook to use to create the VmHook.
|
||||
/// @param index The index of the method to hook.
|
||||
/// @param destination The destination function.
|
||||
/// @return The VmHook object.
|
||||
template <typename T> [[nodiscard]] VmHook create_vm(VmtHook& vmt, size_t index, T destination) {
|
||||
if (auto hook = vmt.hook_method(index, destination)) {
|
||||
return std::move(*hook);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace safetyhook
|
||||
363
external/safetyhook/include/safetyhook/inline_hook.hpp
vendored
Normal file
363
external/safetyhook/include/safetyhook/inline_hook.hpp
vendored
Normal file
@@ -0,0 +1,363 @@
|
||||
/// @file safetyhook/inline_hook.hpp
|
||||
/// @brief Inline hooking class.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef SAFETYHOOK_USE_CXXMODULES
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#else
|
||||
import std.compat;
|
||||
#endif
|
||||
|
||||
#include "safetyhook/allocator.hpp"
|
||||
#include "safetyhook/common.hpp"
|
||||
#include "safetyhook/utility.hpp"
|
||||
|
||||
namespace safetyhook {
|
||||
/// @brief An inline hook.
|
||||
class SAFETYHOOK_API InlineHook final {
|
||||
public:
|
||||
/// @brief Error type for InlineHook.
|
||||
struct Error {
|
||||
/// @brief The type of error.
|
||||
enum : uint8_t {
|
||||
BAD_ALLOCATION, ///< An error occurred when allocating memory.
|
||||
FAILED_TO_DECODE_INSTRUCTION, ///< Failed to decode an instruction.
|
||||
SHORT_JUMP_IN_TRAMPOLINE, ///< The trampoline contains a short jump.
|
||||
IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE, ///< An IP-relative instruction is out of range.
|
||||
UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE, ///< An unsupported instruction was found in the trampoline.
|
||||
FAILED_TO_UNPROTECT, ///< Failed to unprotect memory.
|
||||
NOT_ENOUGH_SPACE, ///< Not enough space to create the hook.
|
||||
} type;
|
||||
|
||||
/// @brief Extra information about the error.
|
||||
union {
|
||||
Allocator::Error allocator_error; ///< Allocator error information.
|
||||
uint8_t* ip; ///< IP of the problematic instruction.
|
||||
};
|
||||
|
||||
/// @brief Create a BAD_ALLOCATION error.
|
||||
/// @param err The Allocator::Error that failed.
|
||||
/// @return The new BAD_ALLOCATION error.
|
||||
[[nodiscard]] static Error bad_allocation(Allocator::Error err) {
|
||||
Error error{};
|
||||
error.type = BAD_ALLOCATION;
|
||||
error.allocator_error = err;
|
||||
return error;
|
||||
}
|
||||
|
||||
/// @brief Create a FAILED_TO_DECODE_INSTRUCTION error.
|
||||
/// @param ip The IP of the problematic instruction.
|
||||
/// @return The new FAILED_TO_DECODE_INSTRUCTION error.
|
||||
[[nodiscard]] static Error failed_to_decode_instruction(uint8_t* ip) {
|
||||
Error error{};
|
||||
error.type = FAILED_TO_DECODE_INSTRUCTION;
|
||||
error.ip = ip;
|
||||
return error;
|
||||
}
|
||||
|
||||
/// @brief Create a SHORT_JUMP_IN_TRAMPOLINE error.
|
||||
/// @param ip The IP of the problematic instruction.
|
||||
/// @return The new SHORT_JUMP_IN_TRAMPOLINE error.
|
||||
[[nodiscard]] static Error short_jump_in_trampoline(uint8_t* ip) {
|
||||
Error error{};
|
||||
error.type = SHORT_JUMP_IN_TRAMPOLINE;
|
||||
error.ip = ip;
|
||||
return error;
|
||||
}
|
||||
|
||||
/// @brief Create a IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE error.
|
||||
/// @param ip The IP of the problematic instruction.
|
||||
/// @return The new IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE error.
|
||||
[[nodiscard]] static Error ip_relative_instruction_out_of_range(uint8_t* ip) {
|
||||
Error error{};
|
||||
error.type = IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE;
|
||||
error.ip = ip;
|
||||
return error;
|
||||
}
|
||||
|
||||
/// @brief Create a UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE error.
|
||||
/// @param ip The IP of the problematic instruction.
|
||||
/// @return The new UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE error.
|
||||
[[nodiscard]] static Error unsupported_instruction_in_trampoline(uint8_t* ip) {
|
||||
Error error{};
|
||||
error.type = UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE;
|
||||
error.ip = ip;
|
||||
return error;
|
||||
}
|
||||
|
||||
/// @brief Create a FAILED_TO_UNPROTECT error.
|
||||
/// @param ip The IP of the problematic instruction.
|
||||
/// @return The new FAILED_TO_UNPROTECT error.
|
||||
[[nodiscard]] static Error failed_to_unprotect(uint8_t* ip) {
|
||||
Error error{};
|
||||
error.type = FAILED_TO_UNPROTECT;
|
||||
error.ip = ip;
|
||||
return error;
|
||||
}
|
||||
|
||||
/// @brief Create a NOT_ENOUGH_SPACE error.
|
||||
/// @param ip The IP of the problematic instruction.
|
||||
/// @return The new NOT_ENOUGH_SPACE error.
|
||||
[[nodiscard]] static Error not_enough_space(uint8_t* ip) {
|
||||
Error error{};
|
||||
error.type = NOT_ENOUGH_SPACE;
|
||||
error.ip = ip;
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Flags for InlineHook.
|
||||
enum Flags : int {
|
||||
Default = 0, ///< Default flags.
|
||||
StartDisabled = 1 << 0, ///< Start the hook disabled.
|
||||
};
|
||||
|
||||
/// @brief Create an inline hook.
|
||||
/// @param target The address of the function to hook.
|
||||
/// @param destination The destination address.
|
||||
/// @param flags The flags to use.
|
||||
/// @return The InlineHook or an InlineHook::Error if an error occurred.
|
||||
/// @note This will use the default global Allocator.
|
||||
/// @note If you don't care about error handling, use the easy API (safetyhook::create_inline).
|
||||
[[nodiscard]] static std::expected<InlineHook, Error> create(
|
||||
void* target, void* destination, Flags flags = Default);
|
||||
|
||||
/// @brief Create an inline hook.
|
||||
/// @param target The address of the function to hook.
|
||||
/// @param destination The destination address.
|
||||
/// @param flags The flags to use.
|
||||
/// @return The InlineHook or an InlineHook::Error if an error occurred.
|
||||
/// @note This will use the default global Allocator.
|
||||
/// @note If you don't care about error handling, use the easy API (safetyhook::create_inline).
|
||||
template <typename T, typename U>
|
||||
[[nodiscard]] static std::expected<InlineHook, Error> create(T target, U destination, Flags flags = Default) {
|
||||
return create(reinterpret_cast<void*>(target), reinterpret_cast<void*>(destination), flags);
|
||||
}
|
||||
|
||||
/// @brief Create an inline hook with a given Allocator.
|
||||
/// @param allocator The allocator to use.
|
||||
/// @param target The address of the function to hook.
|
||||
/// @param destination The destination address.
|
||||
/// @param flags The flags to use.
|
||||
/// @return The InlineHook or an InlineHook::Error if an error occurred.
|
||||
/// @note If you don't care about error handling, use the easy API (safetyhook::create_inline).
|
||||
[[nodiscard]] static std::expected<InlineHook, Error> create(
|
||||
const std::shared_ptr<Allocator>& allocator, void* target, void* destination, Flags flags = Default);
|
||||
|
||||
/// @brief Create an inline hook with a given Allocator.
|
||||
/// @param allocator The allocator to use.
|
||||
/// @param target The address of the function to hook.
|
||||
/// @param destination The destination address.
|
||||
/// @param flags The flags to use.
|
||||
/// @return The InlineHook or an InlineHook::Error if an error occurred.
|
||||
/// @note If you don't care about error handling, use the easy API (safetyhook::create_inline).
|
||||
template <typename T, typename U>
|
||||
[[nodiscard]] static std::expected<InlineHook, Error> create(
|
||||
const std::shared_ptr<Allocator>& allocator, T target, U destination, Flags flags = Default) {
|
||||
return create(allocator, reinterpret_cast<void*>(target), reinterpret_cast<void*>(destination), flags);
|
||||
}
|
||||
|
||||
InlineHook() = default;
|
||||
InlineHook(const InlineHook&) = delete;
|
||||
InlineHook(InlineHook&& other) noexcept;
|
||||
InlineHook& operator=(const InlineHook&) = delete;
|
||||
InlineHook& operator=(InlineHook&& other) noexcept;
|
||||
~InlineHook();
|
||||
|
||||
/// @brief Reset the hook.
|
||||
/// @details This will restore the original function and remove the hook.
|
||||
/// @note This is called automatically in the destructor.
|
||||
void reset();
|
||||
|
||||
/// @brief Get a pointer to the target.
|
||||
/// @return A pointer to the target.
|
||||
[[nodiscard]] uint8_t* target() const { return m_target; }
|
||||
|
||||
/// @brief Get the target address.
|
||||
/// @return The target address.
|
||||
[[nodiscard]] uintptr_t target_address() const { return reinterpret_cast<uintptr_t>(m_target); }
|
||||
|
||||
/// @brief Get a pointer ot the destination.
|
||||
/// @return A pointer to the destination.
|
||||
[[nodiscard]] uint8_t* destination() const { return m_destination; }
|
||||
|
||||
/// @brief Get the destination address.
|
||||
/// @return The destination address.
|
||||
[[nodiscard]] uintptr_t destination_address() const { return reinterpret_cast<uintptr_t>(m_destination); }
|
||||
|
||||
/// @brief Get the trampoline Allocation.
|
||||
/// @return The trampoline Allocation.
|
||||
[[nodiscard]] const Allocation& trampoline() const { return m_trampoline; }
|
||||
|
||||
/// @brief Tests if the hook is valid.
|
||||
/// @return True if the hook is valid, false otherwise.
|
||||
explicit operator bool() const { return static_cast<bool>(m_trampoline); }
|
||||
|
||||
/// @brief Returns the address of the trampoline to call the original function.
|
||||
/// @tparam T The type of the function pointer.
|
||||
/// @return The address of the trampoline to call the original function.
|
||||
template <typename T> [[nodiscard]] T original() const { return reinterpret_cast<T>(m_trampoline.address()); }
|
||||
|
||||
/// @brief Returns a vector containing the original bytes of the target function.
|
||||
/// @return A vector of the original bytes of the target function.
|
||||
[[nodiscard]] const auto& original_bytes() const { return m_original_bytes; }
|
||||
|
||||
/// @brief Calls the original function.
|
||||
/// @tparam RetT The return type of the function.
|
||||
/// @tparam ...Args The argument types of the function.
|
||||
/// @param ...args The arguments to pass to the function.
|
||||
/// @return The result of calling the original function.
|
||||
/// @note This function will use the default calling convention set by your compiler.
|
||||
template <typename RetT = void, typename... Args> RetT call(Args... args) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_trampoline ? original<RetT (*)(Args...)>()(args...) : RetT();
|
||||
}
|
||||
|
||||
/// @brief Calls the original function.
|
||||
/// @tparam RetT The return type of the function.
|
||||
/// @tparam ...Args The argument types of the function.
|
||||
/// @param ...args The arguments to pass to the function.
|
||||
/// @return The result of calling the original function.
|
||||
/// @note This function will use the __cdecl calling convention.
|
||||
template <typename RetT = void, typename... Args> RetT ccall(Args... args) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_trampoline ? original<RetT(SAFETYHOOK_CCALL*)(Args...)>()(args...) : RetT();
|
||||
}
|
||||
|
||||
/// @brief Calls the original function.
|
||||
/// @tparam RetT The return type of the function.
|
||||
/// @tparam ...Args The argument types of the function.
|
||||
/// @param ...args The arguments to pass to the function.
|
||||
/// @return The result of calling the original function.
|
||||
/// @note This function will use the __thiscall calling convention.
|
||||
template <typename RetT = void, typename... Args> RetT thiscall(Args... args) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_trampoline ? original<RetT(SAFETYHOOK_THISCALL*)(Args...)>()(args...) : RetT();
|
||||
}
|
||||
|
||||
/// @brief Calls the original function.
|
||||
/// @tparam RetT The return type of the function.
|
||||
/// @tparam ...Args The argument types of the function.
|
||||
/// @param ...args The arguments to pass to the function.
|
||||
/// @return The result of calling the original function.
|
||||
/// @note This function will use the __stdcall calling convention.
|
||||
template <typename RetT = void, typename... Args> RetT stdcall(Args... args) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_trampoline ? original<RetT(SAFETYHOOK_STDCALL*)(Args...)>()(args...) : RetT();
|
||||
}
|
||||
|
||||
/// @brief Calls the original function.
|
||||
/// @tparam RetT The return type of the function.
|
||||
/// @tparam ...Args The argument types of the function.
|
||||
/// @param ...args The arguments to pass to the function.
|
||||
/// @return The result of calling the original function.
|
||||
/// @note This function will use the __fastcall calling convention.
|
||||
template <typename RetT = void, typename... Args> RetT fastcall(Args... args) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_trampoline ? original<RetT(SAFETYHOOK_FASTCALL*)(Args...)>()(args...) : RetT();
|
||||
}
|
||||
|
||||
/// @brief Calls the original function.
|
||||
/// @tparam RetT The return type of the function.
|
||||
/// @tparam ...Args The argument types of the function.
|
||||
/// @param ...args The arguments to pass to the function.
|
||||
/// @return The result of calling the original function.
|
||||
/// @note This function will use the default calling convention set by your compiler.
|
||||
/// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook
|
||||
/// safety or are worried about the performance cost of locking the mutex.
|
||||
template <typename RetT = void, typename... Args> RetT unsafe_call(Args... args) {
|
||||
return original<RetT (*)(Args...)>()(args...);
|
||||
}
|
||||
|
||||
/// @brief Calls the original function.
|
||||
/// @tparam RetT The return type of the function.
|
||||
/// @tparam ...Args The argument types of the function.
|
||||
/// @param ...args The arguments to pass to the function.
|
||||
/// @return The result of calling the original function.
|
||||
/// @note This function will use the __cdecl calling convention.
|
||||
/// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook
|
||||
/// safety or are worried about the performance cost of locking the mutex.
|
||||
template <typename RetT = void, typename... Args> RetT unsafe_ccall(Args... args) {
|
||||
return original<RetT(SAFETYHOOK_CCALL*)(Args...)>()(args...);
|
||||
}
|
||||
|
||||
/// @brief Calls the original function.
|
||||
/// @tparam RetT The return type of the function.
|
||||
/// @tparam ...Args The argument types of the function.
|
||||
/// @param ...args The arguments to pass to the function.
|
||||
/// @return The result of calling the original function.
|
||||
/// @note This function will use the __thiscall calling convention.
|
||||
/// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook
|
||||
/// safety or are worried about the performance cost of locking the mutex.
|
||||
template <typename RetT = void, typename... Args> RetT unsafe_thiscall(Args... args) {
|
||||
return original<RetT(SAFETYHOOK_THISCALL*)(Args...)>()(args...);
|
||||
}
|
||||
|
||||
/// @brief Calls the original function.
|
||||
/// @tparam RetT The return type of the function.
|
||||
/// @tparam ...Args The argument types of the function.
|
||||
/// @param ...args The arguments to pass to the function.
|
||||
/// @return The result of calling the original function.
|
||||
/// @note This function will use the __stdcall calling convention.
|
||||
/// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook
|
||||
/// safety or are worried about the performance cost of locking the mutex.
|
||||
template <typename RetT = void, typename... Args> RetT unsafe_stdcall(Args... args) {
|
||||
return original<RetT(SAFETYHOOK_STDCALL*)(Args...)>()(args...);
|
||||
}
|
||||
|
||||
/// @brief Calls the original function.
|
||||
/// @tparam RetT The return type of the function.
|
||||
/// @tparam ...Args The argument types of the function.
|
||||
/// @param ...args The arguments to pass to the function.
|
||||
/// @return The result of calling the original function.
|
||||
/// @note This function will use the __fastcall calling convention.
|
||||
/// @note This function is unsafe because it doesn't lock the mutex. Only use this if you don't care about unhook
|
||||
/// safety or are worried about the performance cost of locking the mutex.
|
||||
template <typename RetT = void, typename... Args> RetT unsafe_fastcall(Args... args) {
|
||||
return original<RetT(SAFETYHOOK_FASTCALL*)(Args...)>()(args...);
|
||||
}
|
||||
|
||||
/// @brief Enable the hook.
|
||||
[[nodiscard]] std::expected<void, Error> enable();
|
||||
|
||||
/// @brief Disable the hook.
|
||||
[[nodiscard]] std::expected<void, Error> disable();
|
||||
|
||||
/// @brief Check if the hook is enabled.
|
||||
[[nodiscard]] bool enabled() const { return m_enabled; }
|
||||
|
||||
private:
|
||||
friend class MidHook;
|
||||
|
||||
enum class Type {
|
||||
Unset,
|
||||
E9,
|
||||
FF,
|
||||
};
|
||||
|
||||
uint8_t* m_target{};
|
||||
uint8_t* m_destination{};
|
||||
Allocation m_trampoline{};
|
||||
std::vector<uint8_t> m_original_bytes{};
|
||||
uintptr_t m_trampoline_size{};
|
||||
std::recursive_mutex m_mutex{};
|
||||
bool m_enabled{};
|
||||
Type m_type{Type::Unset};
|
||||
|
||||
std::expected<void, Error> setup(
|
||||
const std::shared_ptr<Allocator>& allocator, uint8_t* target, uint8_t* destination);
|
||||
std::expected<void, Error> e9_hook(const std::shared_ptr<Allocator>& allocator);
|
||||
|
||||
#if SAFETYHOOK_ARCH_X86_64
|
||||
std::expected<void, Error> ff_hook(const std::shared_ptr<Allocator>& allocator);
|
||||
#endif
|
||||
|
||||
void destroy();
|
||||
};
|
||||
} // namespace safetyhook
|
||||
165
external/safetyhook/include/safetyhook/mid_hook.hpp
vendored
Normal file
165
external/safetyhook/include/safetyhook/mid_hook.hpp
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
/// @file safetyhook/mid_hook.hpp
|
||||
/// @brief Mid function hooking class.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef SAFETYHOOK_USE_CXXMODULES
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#else
|
||||
import std.compat;
|
||||
#endif
|
||||
|
||||
#include "safetyhook/allocator.hpp"
|
||||
#include "safetyhook/common.hpp"
|
||||
#include "safetyhook/context.hpp"
|
||||
#include "safetyhook/inline_hook.hpp"
|
||||
#include "safetyhook/utility.hpp"
|
||||
|
||||
namespace safetyhook {
|
||||
|
||||
/// @brief A MidHook destination function.
|
||||
using MidHookFn = void (*)(Context& ctx);
|
||||
|
||||
/// @brief A mid function hook.
|
||||
class SAFETYHOOK_API MidHook final {
|
||||
public:
|
||||
/// @brief Error type for MidHook.
|
||||
struct Error {
|
||||
/// @brief The type of error.
|
||||
enum : uint8_t {
|
||||
BAD_ALLOCATION,
|
||||
BAD_INLINE_HOOK,
|
||||
} type;
|
||||
|
||||
/// @brief Extra error information.
|
||||
union {
|
||||
Allocator::Error allocator_error; ///< Allocator error information.
|
||||
InlineHook::Error inline_hook_error; ///< InlineHook error information.
|
||||
};
|
||||
|
||||
/// @brief Create a BAD_ALLOCATION error.
|
||||
/// @param err The Allocator::Error that failed.
|
||||
/// @return The new BAD_ALLOCATION error.
|
||||
[[nodiscard]] static Error bad_allocation(Allocator::Error err) {
|
||||
Error error{};
|
||||
error.type = BAD_ALLOCATION;
|
||||
error.allocator_error = err;
|
||||
return error;
|
||||
}
|
||||
|
||||
/// @brief Create a BAD_INLINE_HOOK error.
|
||||
/// @param err The InlineHook::Error that failed.
|
||||
/// @return The new BAD_INLINE_HOOK error.
|
||||
[[nodiscard]] static Error bad_inline_hook(InlineHook::Error err) {
|
||||
Error error{};
|
||||
error.type = BAD_INLINE_HOOK;
|
||||
error.inline_hook_error = err;
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Flags for MidHook.
|
||||
enum Flags : int {
|
||||
Default = 0, ///< Default flags.
|
||||
StartDisabled = 1, ///< Start the hook disabled.
|
||||
};
|
||||
|
||||
/// @brief Creates a new MidHook object.
|
||||
/// @param target The address of the function to hook.
|
||||
/// @param destination_fn The destination function.
|
||||
/// @param flags The flags to use.
|
||||
/// @return The MidHook object or a MidHook::Error if an error occurred.
|
||||
/// @note This will use the default global Allocator.
|
||||
/// @note If you don't care about error handling, use the easy API (safetyhook::create_mid).
|
||||
[[nodiscard]] static std::expected<MidHook, Error> create(
|
||||
void* target, MidHookFn destination_fn, Flags flags = Default);
|
||||
|
||||
/// @brief Creates a new MidHook object.
|
||||
/// @param target The address of the function to hook.
|
||||
/// @param destination_fn The destination function.
|
||||
/// @param flags The flags to use.
|
||||
/// @return The MidHook object or a MidHook::Error if an error occurred.
|
||||
/// @note This will use the default global Allocator.
|
||||
/// @note If you don't care about error handling, use the easy API (safetyhook::create_mid).
|
||||
template <typename T>
|
||||
[[nodiscard]] static std::expected<MidHook, Error> create(
|
||||
T target, MidHookFn destination_fn, Flags flags = Default) {
|
||||
return create(reinterpret_cast<void*>(target), destination_fn, flags);
|
||||
}
|
||||
|
||||
/// @brief Creates a new MidHook object with a given Allocator.
|
||||
/// @param allocator The Allocator to use.
|
||||
/// @param target The address of the function to hook.
|
||||
/// @param destination_fn The destination function.
|
||||
/// @param flags The flags to use.
|
||||
/// @return The MidHook object or a MidHook::Error if an error occurred.
|
||||
/// @note If you don't care about error handling, use the easy API (safetyhook::create_mid).
|
||||
[[nodiscard]] static std::expected<MidHook, Error> create(
|
||||
const std::shared_ptr<Allocator>& allocator, void* target, MidHookFn destination_fn, Flags flags = Default);
|
||||
|
||||
/// @brief Creates a new MidHook object with a given Allocator.
|
||||
/// @tparam T The type of the function to hook.
|
||||
/// @param allocator The Allocator to use.
|
||||
/// @param target The address of the function to hook.
|
||||
/// @param destination_fn The destination function.
|
||||
/// @param flags The flags to use.
|
||||
/// @return The MidHook object or a MidHook::Error if an error occurred.
|
||||
/// @note If you don't care about error handling, use the easy API (safetyhook::create_mid).
|
||||
template <typename T>
|
||||
[[nodiscard]] static std::expected<MidHook, Error> create(
|
||||
const std::shared_ptr<Allocator>& allocator, T target, MidHookFn destination_fn, Flags flags = Default) {
|
||||
return create(allocator, reinterpret_cast<void*>(target), destination_fn, flags);
|
||||
}
|
||||
|
||||
MidHook() = default;
|
||||
MidHook(const MidHook&) = delete;
|
||||
MidHook(MidHook&& other) noexcept;
|
||||
MidHook& operator=(const MidHook&) = delete;
|
||||
MidHook& operator=(MidHook&& other) noexcept;
|
||||
~MidHook() = default;
|
||||
|
||||
/// @brief Reset the hook.
|
||||
/// @details This will remove the hook and free the stub.
|
||||
/// @note This is called automatically in the destructor.
|
||||
void reset();
|
||||
|
||||
/// @brief Get a pointer to the target.
|
||||
/// @return A pointer to the target.
|
||||
[[nodiscard]] uint8_t* target() const { return m_target; }
|
||||
|
||||
/// @brief Get the address of the target.
|
||||
/// @return The address of the target.
|
||||
[[nodiscard]] uintptr_t target_address() const { return reinterpret_cast<uintptr_t>(m_target); }
|
||||
|
||||
/// @brief Get the destination function.
|
||||
/// @return The destination function.
|
||||
[[nodiscard]] MidHookFn destination() const { return m_destination; }
|
||||
|
||||
/// @brief Returns a vector containing the original bytes of the target function.
|
||||
/// @return A vector of the original bytes of the target function.
|
||||
[[nodiscard]] const auto& original_bytes() const { return m_hook.m_original_bytes; }
|
||||
|
||||
/// @brief Tests if the hook is valid.
|
||||
/// @return true if the hook is valid, false otherwise.
|
||||
explicit operator bool() const { return static_cast<bool>(m_stub); }
|
||||
|
||||
/// @brief Enable the hook.
|
||||
[[nodiscard]] std::expected<void, Error> enable();
|
||||
|
||||
/// @brief Disable the hook.
|
||||
[[nodiscard]] std::expected<void, Error> disable();
|
||||
|
||||
/// @brief Check if the hook is enabled.
|
||||
[[nodiscard]] bool enabled() const { return m_hook.enabled(); }
|
||||
|
||||
private:
|
||||
InlineHook m_hook{};
|
||||
uint8_t* m_target{};
|
||||
Allocation m_stub{};
|
||||
MidHookFn m_destination{};
|
||||
|
||||
std::expected<void, Error> setup(
|
||||
const std::shared_ptr<Allocator>& allocator, uint8_t* target, MidHookFn destination);
|
||||
};
|
||||
} // namespace safetyhook
|
||||
78
external/safetyhook/include/safetyhook/os.hpp
vendored
Normal file
78
external/safetyhook/include/safetyhook/os.hpp
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
// This is the OS abstraction layer.
|
||||
#pragma once
|
||||
|
||||
#ifndef SAFETYHOOK_USE_CXXMODULES
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#else
|
||||
import std.compat;
|
||||
#endif
|
||||
|
||||
#include "safetyhook/common.hpp"
|
||||
|
||||
namespace safetyhook {
|
||||
|
||||
enum class OsError {
|
||||
FAILED_TO_ALLOCATE,
|
||||
FAILED_TO_PROTECT,
|
||||
FAILED_TO_QUERY,
|
||||
FAILED_TO_GET_NEXT_THREAD,
|
||||
FAILED_TO_GET_THREAD_CONTEXT,
|
||||
FAILED_TO_SET_THREAD_CONTEXT,
|
||||
FAILED_TO_FREEZE_THREAD,
|
||||
FAILED_TO_UNFREEZE_THREAD,
|
||||
FAILED_TO_GET_THREAD_ID,
|
||||
};
|
||||
|
||||
struct VmAccess {
|
||||
bool read : 1;
|
||||
bool write : 1;
|
||||
bool execute : 1;
|
||||
|
||||
constexpr bool operator==(const VmAccess& other) const {
|
||||
return read == other.read && write == other.write && execute == other.execute;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr VmAccess VM_ACCESS_R{true, false, false};
|
||||
constexpr VmAccess VM_ACCESS_RW{true, true, false};
|
||||
constexpr VmAccess VM_ACCESS_RX{true, false, true};
|
||||
constexpr VmAccess VM_ACCESS_RWX{true, true, true};
|
||||
|
||||
struct VmBasicInfo {
|
||||
uint8_t* address;
|
||||
size_t size;
|
||||
VmAccess access;
|
||||
bool is_free;
|
||||
};
|
||||
|
||||
std::expected<uint8_t*, OsError> SAFETYHOOK_API vm_allocate(uint8_t* address, size_t size, VmAccess access);
|
||||
void SAFETYHOOK_API vm_free(uint8_t* address);
|
||||
std::expected<uint32_t, OsError> SAFETYHOOK_API vm_protect(uint8_t* address, size_t size, VmAccess access);
|
||||
std::expected<uint32_t, OsError> SAFETYHOOK_API vm_protect(uint8_t* address, size_t size, uint32_t access);
|
||||
std::expected<VmBasicInfo, OsError> SAFETYHOOK_API vm_query(uint8_t* address);
|
||||
bool SAFETYHOOK_API vm_is_readable(uint8_t* address, size_t size);
|
||||
bool SAFETYHOOK_API vm_is_writable(uint8_t* address, size_t size);
|
||||
bool SAFETYHOOK_API vm_is_executable(uint8_t* address);
|
||||
|
||||
struct SystemInfo {
|
||||
uint32_t page_size;
|
||||
uint32_t allocation_granularity;
|
||||
uint8_t* min_address;
|
||||
uint8_t* max_address;
|
||||
};
|
||||
|
||||
SystemInfo SAFETYHOOK_API system_info();
|
||||
|
||||
using ThreadContext = void*;
|
||||
|
||||
void SAFETYHOOK_API trap_threads(uint8_t* from, uint8_t* to, size_t len, const std::function<void()>& run_fn);
|
||||
|
||||
/// @brief Will modify the context of a thread's IP to point to a new address if its IP is at the old address.
|
||||
/// @param ctx The thread context to modify.
|
||||
/// @param old_ip The old IP address.
|
||||
/// @param new_ip The new IP address.
|
||||
void SAFETYHOOK_API fix_ip(ThreadContext ctx, uint8_t* old_ip, uint8_t* new_ip);
|
||||
|
||||
} // namespace safetyhook
|
||||
62
external/safetyhook/include/safetyhook/utility.hpp
vendored
Normal file
62
external/safetyhook/include/safetyhook/utility.hpp
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef SAFETYHOOK_USE_CXXMODULES
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#else
|
||||
import std.compat;
|
||||
#endif
|
||||
|
||||
#include "safetyhook/common.hpp"
|
||||
|
||||
namespace safetyhook {
|
||||
template <typename T> constexpr void store(uint8_t* address, const T& value) {
|
||||
std::copy_n(reinterpret_cast<const uint8_t*>(&value), sizeof(T), address);
|
||||
}
|
||||
|
||||
template <typename T, typename U> constexpr T address_cast(U address) {
|
||||
if constexpr (std::is_integral_v<T> && std::is_integral_v<U>) {
|
||||
return static_cast<T>(address);
|
||||
} else {
|
||||
return reinterpret_cast<T>(address);
|
||||
}
|
||||
}
|
||||
|
||||
bool SAFETYHOOK_API is_executable(uint8_t* address);
|
||||
|
||||
class SAFETYHOOK_API UnprotectMemory {
|
||||
public:
|
||||
UnprotectMemory() = delete;
|
||||
~UnprotectMemory();
|
||||
UnprotectMemory(const UnprotectMemory&) = delete;
|
||||
UnprotectMemory(UnprotectMemory&& other) noexcept;
|
||||
UnprotectMemory& operator=(const UnprotectMemory&) = delete;
|
||||
UnprotectMemory& operator=(UnprotectMemory&& other) noexcept;
|
||||
|
||||
private:
|
||||
friend std::optional<UnprotectMemory> SAFETYHOOK_API unprotect(uint8_t*, size_t);
|
||||
|
||||
UnprotectMemory(uint8_t* address, size_t size, uint32_t original_protection)
|
||||
: m_address{address}, m_size{size}, m_original_protection{original_protection} {}
|
||||
|
||||
uint8_t* m_address{};
|
||||
size_t m_size{};
|
||||
uint32_t m_original_protection{};
|
||||
};
|
||||
|
||||
[[nodiscard]] std::optional<UnprotectMemory> SAFETYHOOK_API unprotect(uint8_t* address, size_t size);
|
||||
|
||||
template <typename T> constexpr T align_up(T address, size_t align) {
|
||||
const auto unaligned_address = address_cast<uintptr_t>(address);
|
||||
const auto aligned_address = (unaligned_address + align - 1) & ~(align - 1);
|
||||
return address_cast<T>(aligned_address);
|
||||
}
|
||||
|
||||
template <typename T> constexpr T align_down(T address, size_t align) {
|
||||
const auto unaligned_address = address_cast<uintptr_t>(address);
|
||||
const auto aligned_address = unaligned_address & ~(align - 1);
|
||||
return address_cast<T>(aligned_address);
|
||||
}
|
||||
} // namespace safetyhook
|
||||
170
external/safetyhook/include/safetyhook/vmt_hook.hpp
vendored
Normal file
170
external/safetyhook/include/safetyhook/vmt_hook.hpp
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
/// @file safetyhook/vmt_hook.hpp
|
||||
/// @brief VMT hooking classes
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef SAFETYHOOK_USE_CXXMODULES
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <unordered_map>
|
||||
#else
|
||||
import std.compat;
|
||||
#endif
|
||||
|
||||
#include "safetyhook/allocator.hpp"
|
||||
#include "safetyhook/common.hpp"
|
||||
#include "safetyhook/utility.hpp"
|
||||
|
||||
namespace safetyhook {
|
||||
/// @brief A hook class that allows for hooking a single method in a VMT.
|
||||
class SAFETYHOOK_API VmHook final {
|
||||
public:
|
||||
VmHook() = default;
|
||||
VmHook(const VmHook&) = delete;
|
||||
VmHook(VmHook&& other) noexcept;
|
||||
VmHook& operator=(const VmHook&) = delete;
|
||||
VmHook& operator=(VmHook&& other) noexcept;
|
||||
~VmHook();
|
||||
|
||||
/// @brief Removes the hook.
|
||||
void reset();
|
||||
|
||||
/// @brief Gets the original method pointer.
|
||||
template <typename T> [[nodiscard]] T original() const { return reinterpret_cast<T>(m_original_vm); }
|
||||
|
||||
/// @brief Calls the original method.
|
||||
/// @tparam RetT The return type of the method.
|
||||
/// @tparam Args The argument types of the method.
|
||||
/// @param args The arguments to pass to the method.
|
||||
/// @return The return value of the method.
|
||||
/// @note This will call the original method with the default calling convention.
|
||||
template <typename RetT = void, typename... Args> RetT call(Args... args) {
|
||||
return original<RetT (*)(Args...)>()(args...);
|
||||
}
|
||||
|
||||
/// @brief Calls the original method with the __cdecl calling convention.
|
||||
/// @tparam RetT The return type of the method.
|
||||
/// @tparam Args The argument types of the method.
|
||||
/// @param args The arguments to pass to the method.
|
||||
/// @return The return value of the method.
|
||||
template <typename RetT = void, typename... Args> RetT ccall(Args... args) {
|
||||
return original<RetT(SAFETYHOOK_CCALL*)(Args...)>()(args...);
|
||||
}
|
||||
|
||||
/// @brief Calls the original method with the __thiscall calling convention.
|
||||
/// @tparam RetT The return type of the method.
|
||||
/// @tparam Args The argument types of the method.
|
||||
/// @param args The arguments to pass to the method.
|
||||
/// @return The return value of the method.
|
||||
template <typename RetT = void, typename... Args> RetT thiscall(Args... args) {
|
||||
return original<RetT(SAFETYHOOK_THISCALL*)(Args...)>()(args...);
|
||||
}
|
||||
|
||||
/// @brief Calls the original method with the __stdcall calling convention.
|
||||
/// @tparam RetT The return type of the method.
|
||||
/// @tparam Args The argument types of the method.
|
||||
/// @param args The arguments to pass to the method.
|
||||
/// @return The return value of the method.
|
||||
template <typename RetT = void, typename... Args> RetT stdcall(Args... args) {
|
||||
return original<RetT(SAFETYHOOK_STDCALL*)(Args...)>()(args...);
|
||||
}
|
||||
|
||||
/// @brief Calls the original method with the __fastcall calling convention.
|
||||
/// @tparam RetT The return type of the method.
|
||||
/// @tparam Args The argument types of the method.
|
||||
/// @param args The arguments to pass to the method.
|
||||
/// @return The return value of the method.
|
||||
template <typename RetT = void, typename... Args> RetT fastcall(Args... args) {
|
||||
return original<RetT(SAFETYHOOK_FASTCALL*)(Args...)>()(args...);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class VmtHook;
|
||||
|
||||
uint8_t* m_original_vm{};
|
||||
uint8_t* m_new_vm{};
|
||||
uint8_t** m_vmt_entry{};
|
||||
|
||||
// This keeps the allocation alive until the hook is destroyed.
|
||||
std::shared_ptr<Allocation> m_new_vmt_allocation{};
|
||||
|
||||
void destroy();
|
||||
};
|
||||
|
||||
/// @brief A hook class that copies an entire VMT for a given object and replaces it.
|
||||
class SAFETYHOOK_API VmtHook final {
|
||||
public:
|
||||
/// @brief Error type for VmtHook.
|
||||
struct Error {
|
||||
/// @brief The type of error.
|
||||
enum : uint8_t {
|
||||
BAD_ALLOCATION, ///< An error occurred while allocating memory.
|
||||
} type;
|
||||
|
||||
/// @brief Extra error information.
|
||||
union {
|
||||
Allocator::Error allocator_error; ///< Allocator error information.
|
||||
};
|
||||
|
||||
/// @brief Create a BAD_ALLOCATION error.
|
||||
/// @param err The Allocator::Error that failed.
|
||||
/// @return The new BAD_ALLOCATION error.
|
||||
[[nodiscard]] static Error bad_allocation(Allocator::Error err) {
|
||||
Error error{};
|
||||
error.type = BAD_ALLOCATION;
|
||||
error.allocator_error = err;
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Creates a new VmtHook object. Will clone the VMT of the given object and replace it.
|
||||
/// @param object The object to hook.
|
||||
/// @return The VmtHook object or a VmtHook::Error if an error occurred.
|
||||
[[nodiscard]] static std::expected<VmtHook, Error> create(void* object);
|
||||
|
||||
VmtHook() = default;
|
||||
VmtHook(const VmtHook&) = delete;
|
||||
VmtHook(VmtHook&& other) noexcept;
|
||||
VmtHook& operator=(const VmtHook&) = delete;
|
||||
VmtHook& operator=(VmtHook&& other) noexcept;
|
||||
~VmtHook();
|
||||
|
||||
/// @brief Applies the hook.
|
||||
/// @param object The object to apply the hook to.
|
||||
/// @note This will replace the VMT of the object with the new VMT. You can apply the hook to multiple objects.
|
||||
void apply(void* object);
|
||||
|
||||
/// @brief Removes the hook.
|
||||
/// @param object The object to remove the hook from.
|
||||
void remove(void* object);
|
||||
|
||||
/// @brief Removes the hook from all objects.
|
||||
void reset();
|
||||
|
||||
/// @brief Hooks a method in the VMT.
|
||||
/// @param index The index of the method to hook.
|
||||
/// @param new_function The new function to use.
|
||||
template <typename T> [[nodiscard]] std::expected<VmHook, Error> hook_method(size_t index, T new_function) {
|
||||
VmHook hook{};
|
||||
|
||||
++index; // Skip RTTI pointer.
|
||||
hook.m_original_vm = m_new_vmt[index];
|
||||
store(reinterpret_cast<uint8_t*>(&hook.m_new_vm), new_function);
|
||||
hook.m_vmt_entry = &m_new_vmt[index];
|
||||
hook.m_new_vmt_allocation = m_new_vmt_allocation;
|
||||
m_new_vmt[index] = hook.m_new_vm;
|
||||
|
||||
return hook;
|
||||
}
|
||||
|
||||
private:
|
||||
// Map of object instance to their original VMT.
|
||||
std::unordered_map<void*, uint8_t**> m_objects{};
|
||||
|
||||
// The allocation is a shared_ptr, so it can be shared with VmHooks to ensure the memory is kept alive.
|
||||
std::shared_ptr<Allocation> m_new_vmt_allocation{};
|
||||
uint8_t** m_new_vmt{};
|
||||
|
||||
void destroy();
|
||||
};
|
||||
} // namespace safetyhook
|
||||
Reference in New Issue
Block a user