A modern, high-performance library for C++20 designed around game hacking
This project adheres to semantic versioning. Any declaration that
is within a detail
or experimental
namespace is not considered part of the public API, and usage
may break at any time without the MAJOR version number being incremented.
The table below compares the single threaded throughput in bytes/s (real time) between libhat and two other commonly used implementations for pattern scanning. The input buffers were randomly generated using a fixed seed, and the pattern scanned does not contain any match in the buffer. The benchmark was run on a system with an i7-9700K (which supports libhat's AVX2 scanner implementation). The full source code is available here.
---------------------------------------------------------------------------------------
Benchmark Time CPU Iterations bytes_per_second
---------------------------------------------------------------------------------------
BM_Throughput_Libhat/4MiB 131578 ns 48967 ns 21379 29.6876Gi/s
BM_Throughput_Libhat/16MiB 813977 ns 413524 ns 3514 19.1959Gi/s
BM_Throughput_Libhat/128MiB 6910936 ns 3993486 ns 403 18.0873Gi/s
BM_Throughput_Libhat/256MiB 13959379 ns 8121906 ns 202 17.9091Gi/s
BM_Throughput_UC1/4MiB 4739731 ns 2776015 ns 591 843.93Mi/s
BM_Throughput_UC1/16MiB 19011485 ns 10841837 ns 147 841.597Mi/s
BM_Throughput_UC1/128MiB 152277511 ns 82465278 ns 18 840.571Mi/s
BM_Throughput_UC1/256MiB 304964544 ns 180555556 ns 9 839.442Mi/s
BM_Throughput_UC2/4MiB 9633499 ns 4617698 ns 291 415.218Mi/s
BM_Throughput_UC2/16MiB 38507193 ns 22474315 ns 73 415.507Mi/s
BM_Throughput_UC2/128MiB 307989100 ns 164930556 ns 9 415.599Mi/s
BM_Throughput_UC2/256MiB 616449240 ns 331250000 ns 5 415.282Mi/s
#include <libhat/Scanner.hpp>
// Parse a pattern's string representation to an array of bytes at compile time
constexpr hat::fixed_signature pattern = hat::compile_signature<"48 8D 05 ? ? ? ? E8">();
// ...or parse it at runtime
using parsed_t = hat::result<hat::signature, hat::signature_parse_error>;
parsed_t runtime_pattern = hat::parse_signature("48 8D 05 ? ? ? ? E8");
// Scan for this pattern using your CPU's vectorization features
auto begin = /* a contiguous iterator over std::byte */;
auto end = /* ... */;
hat::scan_result result = hat::find_pattern(begin, end, pattern);
// Scan a section in the process's base module
hat::scan_result result = hat::find_pattern(pattern, ".text");
// Or another module loaded into the process
std::optional<hat::process::module> ntdll = hat::process::get_module("ntdll.dll");
assert(ntdll.has_value());
hat::scan_result result = hat::find_pattern(pattern, *ntdll, ".text");
// Get the address pointed at by the pattern
const std::byte* address = result.get();
// Resolve an RIP relative address at a given offset
//
// | signature matches here
// | | relative address located at +3
// v v
// 48 8D 05 BE 53 23 01 lea rax, [rip+0x12353be]
//
const std::byte* relative_address = result.rel(3);
#include <libhat/Access.hpp>
// An example struct and it's member offsets
struct S {
uint32_t a{}; // 0x0
uint32_t b{}; // 0x4
uint32_t c{}; // 0x8
uint32_t d{}; // 0xC
};
S s;
// Obtain a mutable reference to 's.b' via it's offset
uint32_t& b = hat::member_at<uint32_t>(&s, 0x4);
// If the provided pointer is const, the returned reference is const
const uint32_t& b = hat::member_at<uint32_t>(&std::as_const(s), 0x4);
#include <libhat/MemoryProtector.hpp>
uintptr_t* vftable = ...; // Pointer to a virtual function table in read-only data
size_t target_func_index = ...; // Index to an interesting function
// Use memory_protector to enable write protections
hat::memory_protector prot{
(uintptr_t) &vftable[target_func_index], // a pointer to the target memory
sizeof(uintptr_t), // the size of the memory block
hat::protection::Read | hat::protection::Write // the new protection flags
};
// Overwrite function table entry to redirect to a custom callback
vftable[target_func_index] = (uintptr_t) my_callback;
// On scope exit, original protections will be restored
prot.~memory_protector(); // compiler generated