// DO NOT EDIT. This file is auto-generated by `amalgamate.py`. // // Header: safetyhook.hpp // #pragma once // // Header: safetyhook/easy.hpp // // Include stack: // - safetyhook.hpp // /// @file safetyhook/easy.hpp /// @brief Easy to use API for creating hooks. #pragma once // // Header: safetyhook/inline_hook.hpp // // Include stack: // - safetyhook.hpp // - safetyhook/easy.hpp // /// @file safetyhook/inline_hook.hpp /// @brief Inline hooking class. #pragma once #ifndef SAFETYHOOK_USE_CXXMODULES #include #include #include #include #include #include #else import std.compat; #endif // // Header: safetyhook/allocator.hpp // // Include stack: // - safetyhook.hpp // - safetyhook/easy.hpp // - safetyhook/inline_hook.hpp // /// @file safetyhook/allocator.hpp /// @brief Allocator for allocating memory near target addresses. #pragma once #ifndef SAFETYHOOK_USE_CXXMODULES #include #include #include #include #include #else import std.compat; #endif namespace safetyhook { class Allocator; /// @brief A memory allocation. class 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 (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, uint8_t* address, size_t size) noexcept; private: std::shared_ptr m_allocator{}; uint8_t* m_address{}; size_t m_size{}; }; /// @brief Allocates memory near target addresses. class Allocator final : public std::enable_shared_from_this { public: /// @brief Returns the global Allocator. /// @return The global Allocator. [[nodiscard]] static std::shared_ptr global(); /// @brief Creates a new Allocator. /// @return The new Allocator. [[nodiscard]] static std::shared_ptr 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]] tl::expected 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]] tl::expected allocate_near( const std::vector& 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 next{}; uint8_t* start{}; uint8_t* end{}; }; struct Memory { uint8_t* address{}; size_t size{}; std::unique_ptr freelist{}; ~Memory(); }; std::vector> m_memory{}; std::mutex m_mutex{}; Allocator() = default; [[nodiscard]] tl::expected internal_allocate_near( const std::vector& 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 tl::expected allocate_nearby_memory( const std::vector& desired_addresses, size_t size, size_t max_distance); [[nodiscard]] static bool in_range( uint8_t* address, const std::vector& desired_addresses, size_t max_distance); }; } // namespace safetyhook // // Header: safetyhook/common.hpp // // Include stack: // - safetyhook.hpp // - safetyhook/easy.hpp // - safetyhook/inline_hook.hpp // #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 // // Header: safetyhook/utility.hpp // // Include stack: // - safetyhook.hpp // - safetyhook/easy.hpp // - safetyhook/inline_hook.hpp // #pragma once #ifndef SAFETYHOOK_USE_CXXMODULES #include #include #include #include #else import std.compat; #endif namespace safetyhook { template constexpr void store(uint8_t* address, const T& value) { std::copy_n(reinterpret_cast(&value), sizeof(T), address); } //template //concept FnPtr = requires(T f) { std::is_pointer_v&& std::is_function_v>; }; typedef void* FnPtr; bool is_executable(uint8_t* address); class 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 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 unprotect(uint8_t* address, size_t size); template constexpr T align_up(T address, size_t align) { const auto unaligned_address = (uintptr_t)address; const auto aligned_address = (unaligned_address + align - 1) & ~(align - 1); return (T)aligned_address; } template constexpr T align_down(T address, size_t align) { const auto unaligned_address = (uintptr_t)address; const auto aligned_address = unaligned_address & ~(align - 1); return (T)aligned_address; } } // namespace safetyhook namespace safetyhook { /// @brief An inline hook. class 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 retErr; retErr.type = BAD_ALLOCATION; retErr.allocator_error = err; return retErr; } /// @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 retErr; retErr.type = FAILED_TO_DECODE_INSTRUCTION; retErr.ip = ip; return retErr; } /// @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 retErr; retErr.type = SHORT_JUMP_IN_TRAMPOLINE; retErr.ip = ip; return retErr; } /// @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 retErr; retErr.type = IP_RELATIVE_INSTRUCTION_OUT_OF_RANGE; retErr.ip = ip; return retErr; } /// @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 retErr; retErr.type = UNSUPPORTED_INSTRUCTION_IN_TRAMPOLINE; retErr.ip = ip; return retErr; } /// @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 retErr; retErr.type = FAILED_TO_UNPROTECT; retErr.ip = ip; return retErr; } /// @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 retErr; retErr.type = NOT_ENOUGH_SPACE; retErr.ip = ip; return retErr; } }; /// @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 tl::expected 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). /*[[nodiscard]] static tl::expected create( FnPtr auto target, FnPtr auto destination, Flags flags = Default) { return create(reinterpret_cast(target), reinterpret_cast(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 tl::expected create( const std::shared_ptr& 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). /*[[nodiscard]] static tl::expected create( const std::shared_ptr& allocator, FnPtr auto target, FnPtr auto destination, Flags flags = Default) { return create(allocator, reinterpret_cast(target), reinterpret_cast(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(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(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(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 [[nodiscard]] T original() const { return reinterpret_cast(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 RetT call(Args... args) { std::scoped_lock lock{m_mutex}; return m_trampoline ? original()(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 RetT ccall(Args... args) { std::scoped_lock lock{m_mutex}; return m_trampoline ? original()(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 RetT thiscall(Args... args) { std::scoped_lock lock{m_mutex}; return m_trampoline ? original()(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 RetT stdcall(Args... args) { std::scoped_lock lock{m_mutex}; return m_trampoline ? original()(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 RetT fastcall(Args... args) { std::scoped_lock lock{m_mutex}; return m_trampoline ? original()(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 RetT unsafe_call(Args... args) { return original()(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 RetT unsafe_ccall(Args... args) { return original()(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 RetT unsafe_thiscall(Args... args) { return original()(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 RetT unsafe_stdcall(Args... args) { return original()(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 RetT unsafe_fastcall(Args... args) { return original()(args...); } /// @brief Enable the hook. [[nodiscard]] tl::expected enable(); /// @brief Disable the hook. [[nodiscard]] tl::expected 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 m_original_bytes{}; uintptr_t m_trampoline_size{}; std::recursive_mutex m_mutex{}; bool m_enabled{}; Type m_type{Type::Unset}; tl::expected setup( const std::shared_ptr& allocator, uint8_t* target, uint8_t* destination); tl::expected e9_hook(const std::shared_ptr& allocator); #if SAFETYHOOK_ARCH_X86_64 tl::expected ff_hook(const std::shared_ptr& allocator); #endif void destroy(); }; } // namespace safetyhook // // Header: safetyhook/mid_hook.hpp // // Include stack: // - safetyhook.hpp // - safetyhook/easy.hpp // /// @file safetyhook/mid_hook.hpp /// @brief Mid function hooking class. #pragma once #ifndef SAFETYHOOK_USE_CXXMODULES #include #include #else import std.compat; #endif // // Header: safetyhook/context.hpp // // Include stack: // - safetyhook.hpp // - safetyhook/easy.hpp // - safetyhook/mid_hook.hpp // /// @file safetyhook/context.hpp /// @brief Context structure for MidHook. #pragma once #ifndef SAFETYHOOK_USE_CXXMODULES #include #else import std.compat; #endif 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 namespace safetyhook { /// @brief A MidHook destination function. using MidHookFn = void (*)(Context& ctx); /// @brief A mid function hook. class 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 retErr; retErr.type = BAD_ALLOCATION; retErr.allocator_error = err; return retErr; } /// @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 retErr; retErr.type = BAD_INLINE_HOOK; retErr.inline_hook_error = err; return retErr; } }; /// @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 tl::expected 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). /*[[nodiscard]] static tl::expected create( FnPtr auto target, MidHookFn destination_fn, Flags flags = Default) { return create(reinterpret_cast(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 tl::expected create( const std::shared_ptr& 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). /*[[nodiscard]] static tl::expected create(const std::shared_ptr& allocator, FnPtr auto target, MidHookFn destination_fn, Flags flags = Default) { return create(allocator, reinterpret_cast(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(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(m_stub); } /// @brief Enable the hook. [[nodiscard]] tl::expected enable(); /// @brief Disable the hook. [[nodiscard]] tl::expected 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{}; tl::expected setup( const std::shared_ptr& allocator, uint8_t* target, MidHookFn destination); }; } // namespace safetyhook // // Header: safetyhook/vmt_hook.hpp // // Include stack: // - safetyhook.hpp // - safetyhook/easy.hpp // /// @file safetyhook/vmt_hook.hpp /// @brief VMT hooking classes #pragma once #ifndef SAFETYHOOK_USE_CXXMODULES #include #include #include #else import std.compat; #endif namespace safetyhook { /// @brief A hook class that allows for hooking a single method in a VMT. class 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 [[nodiscard]] T original() const { return reinterpret_cast(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 RetT call(Args... args) { return original()(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 RetT ccall(Args... args) { return original()(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 RetT thiscall(Args... args) { return original()(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 RetT stdcall(Args... args) { return original()(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 RetT fastcall(Args... args) { return original()(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 m_new_vmt_allocation{}; void destroy(); }; /// @brief A hook class that copies an entire VMT for a given object and replaces it. class 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 retErr; retErr.type = BAD_ALLOCATION; retErr.allocator_error = err; return retErr; } }; /// @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 tl::expected 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. [[nodiscard]] tl::expected hook_method(size_t index, FnPtr new_function) { VmHook hook{}; ++index; // Skip RTTI pointer. hook.m_original_vm = m_new_vmt[index]; store(reinterpret_cast(&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 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 m_new_vmt_allocation{}; uint8_t** m_new_vmt{}; void destroy(); }; } // namespace safetyhook 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 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. /*[[nodiscard]] InlineHook create_inline( FnPtr auto target, FnPtr auto destination, InlineHook::Flags flags = InlineHook::Default) { return create_inline(reinterpret_cast(target), reinterpret_cast(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 create_mid(void* target, MidHookFn destination, MidHook::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. /*[[nodiscard]] MidHook create_mid(FnPtr auto target, MidHookFn destination, MidHook::Flags flags = MidHook::Default) { return create_mid(reinterpret_cast(target), destination, flags); }*/ /// @brief Easy to use API for creating a VmtHook. /// @param object The object to hook. /// @return The VmtHook object. [[nodiscard]] VmtHook 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. [[nodiscard]] inline VmHook create_vm(VmtHook& vmt, size_t index, FnPtr destination) { if (auto hook = vmt.hook_method(index, destination)) { return std::move(*hook); } else { return {}; } } } // namespace safetyhook 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;