Files
vsdbg-fix/3rdparty/hooker.hpp
2024-09-13 23:01:27 +08:00

355 lines
18 KiB
C++

/*
* MIT License
*
* Copyright (c) 2017 Rokas Kupstys
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#pragma once
#include <type_traits>
#include <stdexcept>
#include <cstdint>
namespace hooker
{
namespace detail
{
extern "C"
{
#include "hooker.h"
}
struct Helper { };
}
#if __cplusplus >= 201402L
/// Pattern for find_pattern() function.
template <size_t N>
struct pattern
{
/// Bytes to find. Value of wildcard byte or byte half can be anything.
uint8_t pattern[N];
/// Wildcard pattern. Byte value may be one of: 0? = 1, ?0 = 2, ?? = 3.
uint8_t wildcard[N];
};
# define CPP14(x) x
namespace detail
{
// Convert hex character to a number.
constexpr uint8_t char_to_byte(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
else if (c >= 'a' && c <= 'f')
return 0x0A + c - 'a';
else if (c >= 'A' && c <= 'F')
return 0x0A + c - 'A';
else if (c == '?')
return 0;
else
throw std::runtime_error("Not a hex character.");
}
// Convert text hex byte at `idx` to binary.
template <size_t N>
constexpr uint8_t get_pattern_byte(const char(&s)[N], size_t idx)
{
if (s[idx * 3 + 2] != ' ' && s[idx * 3 + 2] != '\0')
throw std::runtime_error("Improperly formatted pattern.");
else
return (char_to_byte(s[idx * 3]) << 4) | char_to_byte(s[idx * 3 + 1]);
}
// Convert text wildcard to binary mask.
template <size_t N>
constexpr uint8_t get_wildcard_byte(const char(&s)[N], size_t idx)
{
return (s[idx * 3] == '?' ? 2 : 0) | (s[idx * 3 + 1] == '?' ? 1 : 0);
}
// Convert a character array to binary version and wildcard array.
template <size_t N, size_t... Is>
constexpr pattern<sizeof...(Is)> decode_string_pattern(const char(&s)[N], std::index_sequence<Is...>)
{
return {
{ get_pattern_byte(s, Is)... },
{ get_wildcard_byte(s, Is)... },
};
}
}
// Create a binary pattern from raw string litteral in format: "AB C? ?D ??".
template <size_t N>
constexpr const pattern<N / 3> mkpat(const char(&s)[N])
{
if ((N % 3) == 0)
return detail::decode_string_pattern(s, std::make_index_sequence<N / 3>());
else
throw std::runtime_error("Improperly formatted pattern.");
}
#else
# define CPP14(x)
#endif
#if __cplusplus > 201703L
using bit_cast = std::bit_cast;
#else
template<typename To, typename From>
#if __cplusplus >= 201703L
[[nodiscard]]
#endif
To bit_cast(From &&from)
#if __cplusplus >= 201402L
noexcept(std::is_nothrow_constructible<To>::value)
#endif
{
static_assert(std::is_trivially_copyable<typename std::remove_cv<typename std::remove_reference<From>::type>::type>::value, "From type must be trivially copable.");
static_assert(std::is_trivially_copyable<typename std::remove_cv<typename std::remove_reference<To>::type>::type>::value, "To type must be trivially copiable.");
static_assert(sizeof(From) == sizeof(To), "Sizes of From and To types must be the same.");
static_assert(std::is_default_constructible<To>::value, "To type must be default constructible.");
#if __cplusplus >= 201402L
auto result = (typename std::aligned_storage_t<sizeof(To), alignof(To)>){};
#else
To result{};
#endif
return *static_cast<To*>(memcpy(&result, &from, sizeof(To)));
}
#endif
namespace detail
{
template<typename Addr>
typename std::enable_if<std::is_integral<Addr>::value, void*>::type
any_to_voidp(Addr addr) { return reinterpret_cast<void*>(addr); }
template<typename Addr>
typename std::enable_if<!std::is_integral<Addr>::value, void*>::type
any_to_voidp(Addr addr) { return bit_cast<void*>(addr); }
}
#if _WIN32
/// Calls specified address as __stdcall function passing any amount of arguments. Return type is specified as first template argument. Available only on windows.
/// \param address of function to call.
/// \param ... any amount of arguments with any types.
/// \returns value of type specified as first template argument, or none if no type is specified.
template<typename Result, typename Addr, typename... Args>
Result stdcall(Addr address, Args... arguments)
{
typedef Result(__stdcall*UniversalCall)(Args...);
return UniversalCall(address)(arguments...);
}
# define HOOKER_CDECL __cdecl
#else
# define HOOKER_CDECL
#endif
/// Calls specified address as __cdecl function passing any amount of arguments. Return type is specified as first template argument.
/// \param address of function to call.
/// \param ... any amount of arguments with any types.
/// \returns value of type specified as first template argument, or none if no type is specified.
template<typename Result, typename Addr, typename... Args>
Result ccall(Addr address, Args... arguments)
{
typedef Result(HOOKER_CDECL*UniversalCall)(Args...);
return reinterpret_cast<UniversalCall>(address)(arguments...);
}
#undef HOOKER_CDECL
/// Calls specified address as __thiscall function with provided address as 'this' pointer, passing any amount of arguments. Return type is specified as first template argument.
/// \param address of function to call.
/// \param ... any amount of arguments with any types.
/// \returns value of type specified as first template argument, or none if no type is specified.
template<typename Result, typename Addr, typename This, typename... Args>
Result thiscall(Addr address, This thisPtr, Args... arguments)
{
detail::Helper* thisHelper = bit_cast<detail::Helper*>(thisPtr);
typedef Result(detail::Helper::*TUniversalCall)(Args...);
TUniversalCall UniversalCall = bit_cast<TUniversalCall>(address);
return (thisHelper->*UniversalCall)(arguments...);
}
/// Return object of specified `Type` which is located at `base + offset`.
template<typename Type, typename Base>
Type at_offset(Base base, unsigned offset)
{
return bit_cast<Type>(bit_cast<std::uintptr_t>(base) + offset);
}
/// Return object of specified `Type` which is located at `base + offset`.
template<typename Type, typename Base>
Type from_offset(Base base, unsigned offset)
{
return *at_offset<Type*>(base, offset);
}
/// Change protection of memory range.
/// \param p memory address.
/// \param size of memory at address p.
/// \param protection a combination of HOOKER_MEM_* flags.
/// \param original_protection on supported platforms will be set to current memory protection mode. May be null. If not null - always initialize to a best-guess current protection flags value, because on some platforms (like linux) this variable will not be set.
template<typename Type CPP14(=void*), typename Addr>
bool mem_protect(Addr p, size_t size, size_t protection, size_t* original_protection=nullptr) { return detail::hooker_mem_protect(detail::any_to_voidp(p), size, protection, original_protection) == HOOKER_SUCCESS; }
/// Get mnemonic size of current platform.
template<typename Addr>
size_t get_mnemonic_size(Addr address, size_t min_size) { return detail::hooker_get_mnemonic_size(detail::any_to_voidp(address), min_size); }
/// Hotpatch a call.
template<typename OriginalProc CPP14(=void*), typename Addr, typename ProcAddr>
OriginalProc hotpatch(Addr location, ProcAddr new_proc) { return bit_cast<OriginalProc>(detail::hooker_hotpatch(detail::any_to_voidp(location), detail::any_to_voidp(new_proc))); }
/// Unhotpatch a call.
template<typename Type CPP14(=void*), typename Addr>
bool unhotpatch(Addr location) { return detail::hooker_unhotpatch(detail::any_to_voidp(location)) == HOOKER_SUCCESS; }
/// Writes a jump or call hook from `address` to `new_proc`.
/// \param address a pointer where hook should be written
/// \param new_proc a pointer where hook should point to.
/// \param flags any of HOOKER_HOOK_* flags. They may not be combined.
/// \param nops of bytes to nop after hook instruction. Specify -1 to autocalculate.
/// \returns null on failure or non-null on success.
template<typename Addr, typename ProcAddr>
bool write_instruction(Addr address, ProcAddr new_proc, size_t flags, size_t nops=-1) { return detail::hooker_write_instruction(detail::any_to_voidp(address), detail::any_to_voidp(new_proc), flags, nops) == HOOKER_SUCCESS; }
/// Writes a 5 byte jump with E9 opcode. Difference between pointers is limited to 32bit.
/// \param address a pointer where hook should be written
/// \param new_proc a pointer where hook should point to.
/// \returns null on failure or non-null on success.
template<typename Addr, typename ProcAddr>
bool write_jmp(Addr address, ProcAddr new_proc) { return detail::hooker_write_jmp(detail::any_to_voidp(address), detail::any_to_voidp(new_proc)) == HOOKER_SUCCESS; }
/// Writes a 5 byte call with E8 opcode. Difference between pointers is limited to 32bit.
/// \param address a pointer where hook should be written
/// \param new_proc a pointer where hook should point to.
/// \returns null on failure or non-null on success.
template<typename Addr, typename ProcAddr>
bool write_call(Addr address, ProcAddr new_proc) { return detail::hooker_write_call(detail::any_to_voidp(address), detail::any_to_voidp(new_proc)) == HOOKER_SUCCESS; }
/// Redirect call to custom proc.
/// \param address a start of original call. Warning: It should not contain any relatively-addressed instructions like calls or jumps.
/// \param new_proc a proc that will be called instead of original one.
/// \returns pointer, calling which will invoke original proc. It is user's responsibility to call original code when necessary.
template<typename OriginalProc CPP14(=void*), typename Addr, typename ProcAddr>
OriginalProc hook(Addr address, ProcAddr new_proc, size_t flags=0) { return bit_cast<OriginalProc>(detail::hooker_hook(detail::any_to_voidp(address), detail::any_to_voidp(new_proc), flags)); }
/// Unhook a hook created by hooker::hook(.., .., HOOKER_HOOK_REDIRECT, ..).
/// \param address where hook was written to.
/// \param original result of hooker::redirect() call.
template<typename Addr, typename OriginalProc>
void unhook(Addr address, OriginalProc original) { detail::hooker_unhook(detail::any_to_voidp(address), detail::any_to_voidp(original)); }
/// Return address in object's vmt which is pointing to specified method.
/// \param object is a pointer to a c++ object.
/// \param method is a pointer to a c++ object method.
template<typename Proc, typename Addr, typename ProcAddr>
Proc& get_vmt_address(Addr object, ProcAddr method) { return *bit_cast<Proc*>(detail::hooker_get_vmt_address(reinterpret_cast<void*>(object), detail::any_to_voidp(method))); }
/// Find a first occurrence of memory pattern.
/// \param start a pointer to beginning of memory range.
/// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards.
/// \param pattern a array of bytes to search for.
/// \param pattern_len a length of pattern array.
/// \param a wildcard byte in the pattern array.
template<typename Type CPP14(=uint8_t*), typename Addr, typename Pattern>
Type find_pattern(Addr start, int size, const Pattern* pattern, size_t pattern_len, uint8_t wildcard) { return bit_cast<Type>(detail::hooker_find_pattern(detail::any_to_voidp(start), size, reinterpret_cast<const uint8_t*>(pattern), pattern_len, wildcard)); }
#if __cplusplus >= 201402L
/// Find a first occurrence of memory pattern.
/// \param start a pointer to beginning of memory range.
/// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards.
/// \param pattern and wildcard mask.
template<typename Type CPP14(=uint8_t*), typename Addr, size_t N>
Type find_pattern(Addr start, int size, const pattern<N>& pattern) { return bit_cast<Type>(detail::hooker_find_pattern_ex(detail::any_to_voidp(start), size, pattern.pattern, N, pattern.wildcard)); }
#endif
/// Find a first occurrence of string pattern.
/// \param start a pointer to beginning of memory range.
/// \param size a size of memory range. If size is 0 then entire memory space will be searched. If pattern does not exist this will likely result in a crash. Negative size will search backwards.
/// \param pattern a string to search.
/// \param wildcard a wildcard character.
template<typename Type CPP14(=char*), typename Addr>
Type find_pattern(Addr start, int size, const char* pattern, char wildcard='?') { return bit_cast<Type>(detail::hooker_find_pattern(detail::any_to_voidp(start), size, reinterpret_cast<const uint8_t*>(pattern), strlen(pattern), static_cast<uint8_t>(wildcard))); }
/// Fill memory with nops (0x90 opcode).
/// \param start of the memory address.
/// \param size of the memory that will be filled. Nops a single instruction when not specified.
/// \returns true on success or false on failure.
template<typename Addr>
bool nop(Addr start, size_t size=-1) { return detail::hooker_nop(detail::any_to_voidp(start), size) == HOOKER_SUCCESS; }
/// Write a value to specified memory address.
/// \param start of the memory address.
/// \param value to be written.
/// \returns true on success or false on failure.
template<typename Type, typename Addr>
bool write(Addr address, const Type value) { return detail::hooker_write(detail::any_to_voidp(address), (void*)&value, sizeof(value)) == HOOKER_SUCCESS; }
/// Write an array to specified memory address.
/// \param start of the memory address.
/// \param value to be written.
/// \param count of elements in the array.
template<typename Type, typename Addr>
bool write(Addr address, const Type* value, size_t count) { return detail::hooker_write(detail::any_to_voidp(address), (void*)value, sizeof(Type) * count) == HOOKER_SUCCESS; }
/// Write bytes to specified memory address.
/// \param start of the memory address.
/// \param data to be written.
/// \param size of data.
/// \returns true on success or false on failure.
template<typename Addr>
bool write(Addr address, const void* data, size_t size) { return detail::hooker_write(detail::any_to_voidp(address), data, size) == HOOKER_SUCCESS; }
/// Searches for symbol in specified library. On Windows LoadLibrary() will be called if its not loaded yet, otherwise GetModuleHandle() will be used.
/// On linux dlopen(RTLD_NODELETE) and dlclose() will always be called.
/// \param lib_name string with dynamic library name.
/// \param sym_name string with exported symbol name.
/// \returns pointer to resolved dynamic symbol.
template<typename Proc CPP14(=void*)>
Proc dlsym(const char* lib_name, const char* sym_name) { return bit_cast<Proc>(detail::hooker_dlsym(lib_name, sym_name)); }
#if _WIN32
/// Replaces entry in import address table of specified module.
/// \param mod_name string with name of module whose import table is to be modified.
/// \param imp_mod_name string with name of module which module specified in `mod_name` imports.
/// \param imp_proc_name string with name of symbol imported from module specified in `imp_mod_name`.
/// \param new_proc a pointer that should replace old entry in import address table.
/// \returns original value that was in import address table or null pointer on failure.
template<typename OrigProc CPP14(= void*), typename NewProc>
OrigProc hook_iat(const char* mod_name, const char* imp_mod_name, const char* imp_proc_name, NewProc new_proc) { return bit_cast<OrigProc>(detail::hooker_hook_iat(mod_name, imp_mod_name, imp_proc_name, detail::any_to_voidp(new_proc))); }
/// Replaces entry in import address table of specified module.
/// \param mod_name string with name of module whose import table is to be modified.
/// \param imp_mod_name string with name of module which module specified in `mod_name` imports.
/// \param imp_proc_name string with name of symbol imported from module specified in `imp_mod_name`.
/// \param new_proc a pointer that should replace old entry in import address table.
/// \param old_proc a pointer which will receive Can be null.
/// \returns true on success or false on failure.
template<typename OrigProc CPP14(=void*), typename NewProc>
bool hook_iat(const char* mod_name, const char* imp_mod_name, const char* imp_proc_name, NewProc new_proc, OrigProc* old_proc)
{
auto result = hook_iat<OrigProc>(mod_name, imp_mod_name, imp_proc_name, detail::any_to_voidp(new_proc));
if (old_proc)
*old_proc = result;
return result != HOOKER_ERROR;
}
#endif
};
#if HOOKER_USE_SHORT_NAMESPACE
# ifndef HOOKER_SHORT_NAMESPACE
# define HOOKER_SHORT_NAMESPACE hk
# endif
namespace HOOKER_SHORT_NAMESPACE = hooker;
#endif