95 lines
4.1 KiB
C++
95 lines
4.1 KiB
C++
#pragma once
|
|
#include <cstdint>
|
|
#include <type_traits>
|
|
#include <vector>
|
|
|
|
namespace FunctionHooking {
|
|
class Detour;
|
|
class VTable;
|
|
}
|
|
|
|
class FunctionHooking::Detour {
|
|
private:
|
|
void* source = nullptr;
|
|
void* destination = nullptr;
|
|
std::vector<uint8_t> overwritten_bytes{};
|
|
std::vector<uint8_t> hook_bytes{};
|
|
private:
|
|
void CreateHook(void* source_address, void* destination_address);
|
|
void RemoveHook();
|
|
public:
|
|
/// @returns True if our hook is present.
|
|
/// @note This allows you to know if something has overwritten our hook.
|
|
[[nodiscard]] bool Valid();
|
|
|
|
template <typename T, typename... Args>
|
|
inline typename std::enable_if<!std::is_void<T>::value, T>::type CallOriginal(Args... args) {
|
|
RemoveHook();
|
|
const T result = reinterpret_cast<T(*)(Args...)>(source)(args...);
|
|
CreateHook(source, destination);
|
|
return result;
|
|
}
|
|
|
|
template <typename T, typename... Args>
|
|
inline typename std::enable_if<std::is_void<T>::value, void>::type CallOriginal(Args... args) {
|
|
RemoveHook();
|
|
reinterpret_cast<void(*)(Args...)>(source)(args...);
|
|
CreateHook(source, destination);
|
|
}
|
|
|
|
public:
|
|
/// @returns True if the function is detour hooked.
|
|
/// @param function_address The function to check.
|
|
// TODO check for other instructions. There's a handful that are common.
|
|
static bool Hooked(void* function_address);
|
|
public:
|
|
/// Create a new detour hook.
|
|
/// @param source_address The function to be hooked.
|
|
/// @param destination_address The function we are redirecting execution to.
|
|
Detour(void* source_address, void* destination_address);
|
|
~Detour();
|
|
};
|
|
|
|
// This is *mostly* cpu inspecific.
|
|
// TODO test on Arm64 & Risc64.
|
|
class FunctionHooking::VTable {
|
|
private:
|
|
void* class_instance = nullptr;
|
|
void* destination = nullptr;
|
|
void* original = nullptr;
|
|
int vtable_offset = 0;
|
|
private:
|
|
void CreateHook(void* class_instance, int source_function_vtable_offset, void* destination);
|
|
void RemoveHook();
|
|
public:
|
|
template <typename T, typename... Args>
|
|
T CallOriginal(Args... args) { return reinterpret_cast<T(*)(Args...)>(original)(args...); }
|
|
public:
|
|
/// @returns True if our hook is present.
|
|
/// @note This allows you to know if something has overwritten our hook.
|
|
[[nodiscard]] bool Valid();
|
|
|
|
/// Create a new VTable hook.
|
|
/// @param class_instance The pointer to an instance of the class.
|
|
/// @param vtable_offset The position of the virtual function in the vtable for the *base class* of our target class instance.
|
|
/// @param destination The function that we are redirecting execution to.
|
|
/// @note vtable offsets are in the same order they appear in the class definition.
|
|
/// @note *every* instance of the class will be hooked. not just the instance you pass in.
|
|
/// @note if the class instance provided is destroyed before we remove our hook, We can't remove our hook anymore.
|
|
|
|
// TODO a constructor where we can pass in the function to be hooked and
|
|
// find it in the vtable so you don't have to provide the offset.
|
|
// This would only work if we know exactly what the class layout is, Like if we're running directly inside the process.
|
|
// But it's still a nice feature.
|
|
VTable(void* class_instance, int vtable_offset, void* destination);
|
|
~VTable();
|
|
public:
|
|
/// Checks for "Shadow VTable Hook" which is, some cheats will copy the entire vtable to a new
|
|
/// memory region and replace the pointer to the vtable on the target instance to that one.
|
|
/// The downfall of this is that it makes that instance of the class unique when it would be impossible otherwise.
|
|
/// @param class_instance_to_check The instance of our class we suspect to have been modified.
|
|
/// @param class_instance_to_compare_against A class instance of the exact same type created directly before this check.
|
|
/// @note Shoutouts to AimTux Fuzion for demonstrating how cheats do this.
|
|
// TODO provide some mechanism to detect the traditional VMT hook as-well *more tricky*
|
|
static bool Hooked(void* class_instance_to_check, void* class_instance_to_compare_against);
|
|
}; |