Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
c1605f9
Initial Neon implementation using sse2neon
ZeroMemes Jun 14, 2026
39f9201
Ignore sse2neon warnings
ZeroMemes Jun 14, 2026
8df8dfc
Missing Mode name
ZeroMemes Jun 14, 2026
17ae035
Implementation throughput comparison benchmark
ZeroMemes Jun 15, 2026
ee66f43
Log processor name
ZeroMemes Jun 15, 2026
77fbf3e
Only build `libhat_benchmark_compare_impl`
ZeroMemes Jun 15, 2026
71fd67a
Neon substitutions
ZeroMemes Jun 15, 2026
d23f6da
Architecture guard + use time defaults
ZeroMemes Jun 15, 2026
c002976
Avoid double movemask
ZeroMemes Jun 15, 2026
aa6d5cc
Use 4-bit mask
ZeroMemes Jun 15, 2026
c7a5b5e
Fix warning
ZeroMemes Jun 15, 2026
4ff162a
Increase benchmark time
ZeroMemes Jun 15, 2026
2013e92
Remove sse2neon
ZeroMemes Jun 15, 2026
2fa461d
Missing neon header include
ZeroMemes Jun 15, 2026
1e42ef4
Improve neon intrinsic type conformance
ZeroMemes Jun 15, 2026
826b640
Hopefully resolve remaining macOS compile errors
ZeroMemes Jun 15, 2026
82a1517
Compile Windows on ARM benchmark using Clang
ZeroMemes Jun 15, 2026
ce4768e
Test manual bitwise optimization
ZeroMemes Jun 15, 2026
02dd07d
Merge branch 'master' into feat/neon
ZeroMemes Jun 15, 2026
16f2eed
Benchmark ARM Neon against Chromium
ZeroMemes Jun 15, 2026
af7e14f
Use cache for benchmark workflow
ZeroMemes Jun 15, 2026
45a85a2
Perhaps
ZeroMemes Jun 15, 2026
af24489
Extract masks using `vget_lane_u64`
ZeroMemes Jun 17, 2026
bab92dc
Merge branch 'master' into feat/neon
ZeroMemes Jun 17, 2026
3699104
Debug mode optimizations for NEON
ZeroMemes Jun 17, 2026
2169688
Revert unrolling
ZeroMemes Jun 17, 2026
df8bb55
Add macOS to ARM64 testing
ZeroMemes Jun 18, 2026
36b05c7
Partial mach-o Process
Imrglop May 31, 2026
ac4712e
Missing source file
ZeroMemes Jun 18, 2026
7e7c29d
Missing include for `std::abort`
ZeroMemes Jun 18, 2026
afc7c1d
Possibly fix macOS process APIs
ZeroMemes Jun 18, 2026
3cec491
Add Linux to ARM64 testing
ZeroMemes Jun 18, 2026
7663ec0
Fix Linux compile
ZeroMemes Jun 18, 2026
bf19f53
Actually fix compile + adjust workflow
ZeroMemes Jun 18, 2026
3671c7f
Implement `system_info_arm` on Linux
ZeroMemes Jun 18, 2026
b747018
Adjust mask iteration
ZeroMemes Jun 19, 2026
9ae6cfc
ALLEGEDLY I can't use `_CountTrailingZeros64`
ZeroMemes Jun 19, 2026
5ddba36
Optimize `frequency.py` using NumPy
ZeroMemes Jun 20, 2026
3d1e9ea
Avoid `bytes` copy with `memoryview`
ZeroMemes Jun 20, 2026
6343504
Merge branch 'master' into feat/neon
ZeroMemes Jun 22, 2026
95d2437
Add new NTTP to `segment_scan` call
ZeroMemes Jun 22, 2026
d280b25
Replace `u64` extract with `vmaxvq_u32`
ZeroMemes Jun 22, 2026
18e5111
Merge branch 'master' into feat/neon
ZeroMemes Jun 24, 2026
c0aa557
Update README with macOS support
ZeroMemes Jun 26, 2026
3c4cfda
Add test that validates presence of default module segments
ZeroMemes Jun 26, 2026
9c613eb
Missing include for `std::abort`
ZeroMemes Jun 26, 2026
769bfd1
Recognize `PT_GNU_RELRO` segment type
ZeroMemes Jun 27, 2026
61c7673
Fix some includes on macOS
ZeroMemes Jun 27, 2026
32cdfa4
Add Android build to CMake workflow
ZeroMemes Jun 28, 2026
998580d
Adjust the minimum Android SDK to `android-21`
ZeroMemes Jun 28, 2026
457bba0
Build all currently supported Android ABIs
ZeroMemes Jun 30, 2026
b51f235
`vmaxvq_u32` is not supported on ARMv7
ZeroMemes Jun 30, 2026
8fec0d8
Attempt android emulation for testing
ZeroMemes Jul 1, 2026
004966f
Cache AVD snapshot
ZeroMemes Jul 1, 2026
453c100
Exclude `armeabi-v7a` from AVD cache steps
ZeroMemes Jul 1, 2026
1f5c8a2
Include ABI in AVD cache key
ZeroMemes Jul 1, 2026
3b572dd
Fix options for Android test step
ZeroMemes Jul 1, 2026
57d79a5
Fix `get_module` on Android
ZeroMemes Jul 1, 2026
4f2a7a0
Exclude `armeabi-v7a` and `arm64-v8a` from testing
ZeroMemes Jul 1, 2026
44beb87
Add 4-byte alignment
ZeroMemes Jul 2, 2026
d74b8c4
Switch benchmark to manual dispatch
ZeroMemes Jul 2, 2026
eb3690b
Add AArch64 scan hint
ZeroMemes Jul 2, 2026
4a8e7db
Actually update the scan_hint enum
ZeroMemes Jul 2, 2026
f7dd712
Update README to reflect Android support
ZeroMemes Jul 3, 2026
9ee9705
Update README to include optimizations specific to ARM
ZeroMemes Jul 3, 2026
f7bede0
Adjust formatting
ZeroMemes Jul 3, 2026
053791e
Remove `-Wno-unused-command-line-argument` workaround
ZeroMemes Jul 3, 2026
57561b5
Detect ISA extensions at compile time
ZeroMemes Jul 3, 2026
946a81d
Theoretical 32-bit Mach-O support
ZeroMemes Jul 3, 2026
4f62dff
space
ZeroMemes Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Benchmark

on: workflow_dispatch

jobs:
windows:
strategy:
matrix:
toolset: [ v145, ClangCL ]
runs-on: windows-11-vs2026-arm
steps:
- uses: actions/checkout@v6

- name: CPM Cache
uses: actions/cache@v5
with:
path: ${{github.workspace}}/.cpmcache
key: cpm-${{runner.os}}-ARM64-${{hashFiles('test/CMakeLists.txt')}}
restore-keys: |
cpm-${{runner.os}}-ARM64-

- name: Check Hardware
run: (Get-CimInstance Win32_Processor).Name

- name: Locate Visual Studio
run: |
$vsInstall = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath
echo "VS_INSTALL_DIR=$vsInstall" >> $env:GITHUB_ENV

- name: Configure
run: cmake -B ${{github.workspace}}/build -DCMAKE_CXX_STANDARD=23 -DLIBHAT_SHARED_C_LIB=ON -DLIBHAT_TESTING_SDE=OFF -A ARM64 -T ${{matrix.toolset}}

- name: Build
run: cmake --build ${{github.workspace}}/build -j 4 --config Release --target libhat_benchmark_compare_impl libhat_benchmark_chromium

- name: Test
working-directory: ${{github.workspace}}/build
shell: cmd
run: |
call "${{env.VS_INSTALL_DIR}}\VC\Auxiliary\Build\vcvarsarm64.bat"
ctest --verbose -C Release -R "(libhat_benchmark_compare_impl|libhat_benchmark_chromium)"
77 changes: 73 additions & 4 deletions .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,38 @@ jobs:
steps:
- uses: actions/checkout@v6

- name: CPM Cache
uses: actions/cache@v5
with:
path: ${{github.workspace}}/.cpmcache
key: cpm-${{runner.os}}-${{hashFiles('test/CMakeLists.txt')}}
restore-keys: |
cpm-${{runner.os}}-

- name: Configure
run: cmake -B ${{github.workspace}}/build -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBHAT_TESTING=OFF
run: cmake -B ${{github.workspace}}/build -DCPM_SOURCE_CACHE=${{github.workspace}}/.cpmcache -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBHAT_TESTING_SAMPLE_BIN=OFF -DLIBHAT_TESTING_SDE=OFF

- name: Build
run: cmake --build ${{github.workspace}}/build -j 4

- name: Test
working-directory: ${{github.workspace}}/build
run: ctest --verbose -C ${{env.BUILD_TYPE}} -R libhat_test_.*

linux:
strategy:
matrix:
target: [ x64, ARM64 ]
cxx_standard: [ 20, 23 ]
compiler:
- { pkg: g++, exe: g++, version: 14 }
- { pkg: clang, exe: clang++, version: 18 }
cxx_standard: [ 20, 23 ]
runs-on: ubuntu-24.04
include:
- target: x64
os: ubuntu-26.04
- target: ARM64
os: ubuntu-26.04-arm
runs-on: ${{matrix.os}}
steps:
- uses: actions/checkout@v6

Expand All @@ -51,7 +69,7 @@ jobs:
- name: Configure
env:
CXX: ${{matrix.compiler.exe}}-${{matrix.compiler.version}}
run: cmake -B ${{github.workspace}}/build -DCPM_SOURCE_CACHE=${{github.workspace}}/.cpmcache -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBHAT_TESTING=ON -DLIBHAT_TESTING_SAMPLE_BIN=OFF -DLIBHAT_TESTING_SANITIZE=OFF
run: cmake -B ${{github.workspace}}/build -DCPM_SOURCE_CACHE=${{github.workspace}}/.cpmcache -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBHAT_TESTING_SDE=${{startsWith(matrix.target, 'ARM') && 'OFF' || 'ON'}} -DLIBHAT_TESTING_SAMPLE_BIN=OFF -DLIBHAT_TESTING_SANITIZE=OFF

- name: Build
run: cmake --build ${{github.workspace}}/build -j 4
Expand All @@ -60,6 +78,57 @@ jobs:
working-directory: ${{github.workspace}}/build
run: ctest --verbose -C ${{env.BUILD_TYPE}} -R libhat_test_.*

android:
strategy:
matrix:
cxx_standard: [ 20 ]
abi: [ armeabi-v7a, arm64-v8a, x86, x86_64 ]
sdk: [ 24 ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: AVD Cache
if: ${{!startsWith(matrix.abi, 'arm')}}
uses: actions/cache@v5
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-${{matrix.sdk}}-${{matrix.abi}}

- name: Configure
run: cmake -B ${{github.workspace}}/build -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake -DANDROID_ABI=${{matrix.abi}} -DANDROID_PLATFORM=${{matrix.sdk}} -DCPM_SOURCE_CACHE=${{github.workspace}}/.cpmcache -DCMAKE_CXX_STANDARD=${{matrix.cxx_standard}} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBHAT_TESTING_SAMPLE_BIN=OFF -DLIBHAT_TESTING_SDE=OFF -DLIBHAT_TESTING_SANITIZE=OFF

- name: Build
run: cmake --build ${{github.workspace}}/build -j 4

- name: Create AVD and generate snapshot for caching
if: ${{!startsWith(matrix.abi, 'arm') && steps.avd-cache.outputs.cache-hit != 'true'}}
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{matrix.sdk}}
arch: ${{matrix.abi}}
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
script: echo "Generated AVD snapshot for caching."

- name: Test
if: ${{!startsWith(matrix.abi, 'arm')}}
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{matrix.sdk}}
arch: ${{matrix.abi}}
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: |
adb push ${{github.workspace}}/build/test /data/local/tmp/
adb shell chmod +x /data/local/tmp/test/libhat_test_process
adb shell /data/local/tmp/test/libhat_test_process

windows:
strategy:
matrix:
Expand Down
13 changes: 8 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ option(LIBHAT_USE_STD_MODULE "Compile the module target using the std module" OF
option(LIBHAT_EXAMPLES "Include example targets" ${PROJECT_IS_TOP_LEVEL})

option(LIBHAT_HINT_X86_64 "Enables support for the x86_64 scan hint, requires a small (2KB) data table" ON)
option(LIBHAT_HINT_AARCH64 "Enables support for the aarch64 scan hint, requires a small (2KB) data table" ON)

if(LIBHAT_TESTING AND LIBHAT_TESTING_SANITIZE)
if(MSVC)
Expand All @@ -42,6 +43,8 @@ set(LIBHAT_SRC
src/Scanner.cpp
src/System.cpp

src/os/mac/Process.cpp

src/os/linux/MemoryProtector.cpp
src/os/linux/Process.cpp

Expand All @@ -57,7 +60,9 @@ set(LIBHAT_SRC
src/arch/x86/AVX512.cpp
src/arch/x86/System.cpp

src/arch/arm/System.cpp)
src/arch/arm/Neon.cpp
src/arch/arm/System.cpp
)

add_library(libhat STATIC ${LIBHAT_SRC})
add_library(libhat::libhat ALIAS libhat)
Expand All @@ -75,10 +80,7 @@ if (MSVC)
target_compile_options(libhat PRIVATE /W3 /WX)
endif()
elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(libhat PRIVATE -Wall -Wextra -Wconversion -Werror
# temp fix for macOS CI failing due to incorrect LIBHAT_COMPILER_X86_OPTIONS value
-Wno-unused-command-line-argument
)
target_compile_options(libhat PRIVATE -Wall -Wextra -Wconversion -Werror)
endif()

target_include_directories(libhat PUBLIC
Expand All @@ -90,6 +92,7 @@ target_compile_definitions(libhat PUBLIC
"$<$<BOOL:${LIBHAT_DISABLE_SSE}>:LIBHAT_DISABLE_SSE>"
"$<$<BOOL:${LIBHAT_DISABLE_AVX512}>:LIBHAT_DISABLE_AVX512>"
"$<$<BOOL:${LIBHAT_HINT_X86_64}>:LIBHAT_HINT_X86_64>"
"$<$<BOOL:${LIBHAT_HINT_AARCH64}>:LIBHAT_HINT_AARCH64>"
)

if(LIBHAT_STATIC_C_LIB OR LIBHAT_SHARED_C_LIB)
Expand Down
50 changes: 29 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
A modern, high-performance library for C++20 designed around game hacking

## Feature overview
- Windows x86/x64 support
- Partial Linux and macOS support
- Vectorized scanning for byte patterns
- SSE 4.1 and AVX2 on x86/x64
- AVX-512 on x64
- Neon on ARM/ARM64
- RAII memory protector
- Convenience wrappers over OS APIs
- Language bindings (C, C#, Java)
- Full Windows support
- Partial (WIP) Linux, macOS, and Android support

## Versioning
This project adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html). Any declaration that
Expand Down Expand Up @@ -100,19 +101,21 @@ BM_Throughput_UC2/256MiB 261157833 ns 261160714 ns

Below is a summary of the current support for libhat's platform-dependent APIs:

| | Windows | Linux | macOS |
|--------------------------------|:-------:|:-----:|:-----:|
| `hat::get_system` | ✅ | ✅ | ✅ |
| `hat::memory_protector` | ✅ | ✅ | |
| `hp::get_process_module` | ✅ | ✅ | |
| `hp::get_module` | ✅ | ✅ | |
| `hp::module_at` | ✅ | | |
| `hp::is_readable` | ✅ | ✅ | |
| `hp::is_writable` | ✅ | ✅ | |
| `hp::is_executable` | ✅ | ✅ | |
| `hp::module::get_module_data` | ✅ | ✅ | |
| `hp::module::get_section_data` | ✅ | | |
| `hp::module::for_each_segment` | ✅ | ✅ | |
### APIs

| | Windows | Linux | macOS | Android |
|--------------------------------|:-------:|:-----:|:-----:|:-------:|
| `hat::get_system` | ✅ | ✅ | ✅ | ✅ |
| `hat::memory_protector` | ✅ | ✅ | | ✅ |
| `hp::get_process_module` | ✅ | ✅ | ✅ | ✅ |
| `hp::get_module` | ✅ | ✅ | ✅ | ✅ |
| `hp::module_at` | ✅ | | | |
| `hp::is_readable` | ✅ | ✅ | | ✅ |
| `hp::is_writable` | ✅ | ✅ | | ✅ |
| `hp::is_executable` | ✅ | ✅ | | ✅ |
| `hp::module::get_module_data` | ✅ | ✅ | | ✅ |
| `hp::module::get_section_data` | ✅ | | | |
| `hp::module::for_each_segment` | ✅ | ✅ | ✅ | ✅ |

## Quick start
### Defining patterns
Expand Down Expand Up @@ -189,19 +192,24 @@ const std::byte* address = result.get();
const std::byte* relative_address = result.rel(3);
```

libhat has a few optimizations for searching for patterns in `x86_64` machine code:
libhat has a few optimizations for searching for patterns in `x86_64` and `AArch64` machine code:
```cpp
#include <libhat/scanner.hpp>

// If a byte pattern matches at the start of a function, the result will be aligned on 16-bytes.
// This can be indicated via the defaulted `alignment` parameter (all overloads have this parameter):
// Compilers will often align the start address of a function on 16-bytes. Scanning for patterns that
// match the start of a function can take advantage of this by specifying the defaulted `alignment`
// parameter (all overloads have this parameter):
std::span<std::byte> range = /* ... */;
hat::signature_view pattern = /* ... */;
hat::scan_result result = hat::find_pattern(range, pattern, hat::scan_alignment::X16);

// Additionally, x86_64 contains a non-uniform distribution of byte pairs. By passing the `x86_64`
// scan hint, the search can be based on the least common byte pair that is found in the pattern.
hat::scan_result result = hat::find_pattern(range, pattern, hat::scan_alignment::X1, hat::scan_hint::x86_64);
// Or, if the architecture has byte-aligned instructions (such as ARM and AArch64):
hat::scan_result result = hat::find_pattern(range, pattern, hat::scan_alignment::X4);

// Additionally, machine code contains a non-uniform distribution of bytes. By passing the respective
// scan hint (either `x86_64` or `aarch64`), the search anchor can be tuned to the least frequent
// bytes that are present in the pattern.
hat::scan_result result = hat::find_pattern(range, pattern, hat::scan_alignment::X1, hat::scan_hint::x86_64);
```

### Accessing members
Expand Down
4 changes: 2 additions & 2 deletions bindings/java/src/main/java/me/zero/libhat/Hat.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public static OptionalInt findPattern(@NotNull final Signature signature, @NotNu
Objects.requireNonNull(signature.handle),
new Pointer(start),
count,
alignment.ordinal()
alignment.alignment()
);

if (result == Pointer.NULL) {
Expand Down Expand Up @@ -175,7 +175,7 @@ public static Optional<Pointer> findPattern(@NotNull final Signature signature,
Objects.requireNonNull(signature.handle),
module.handle,
section,
alignment.ordinal()
alignment.alignment()
);

return Optional.ofNullable(result);
Expand Down
22 changes: 20 additions & 2 deletions bindings/java/src/main/java/me/zero/libhat/ScanAlignment.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,28 @@ public enum ScanAlignment {
/**
* No byte alignment
*/
X1,
X1(1),

/**
* 4 byte alignment
*/
X4(4),

/**
* 16 byte alignment
*/
X16
X16(16);

private final int alignment;

ScanAlignment(final int alignment) {
this.alignment = alignment;
}

/**
* @return The scan result alignment requirement, in bytes
*/
public final int alignment() {
return this.alignment;
}
}
5 changes: 3 additions & 2 deletions include/libhat/c/libhat.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ typedef enum libhat_status_t {
} libhat_status_t;

typedef enum scan_alignment {
scan_alignment_x1,
scan_alignment_x16,
scan_alignment_x1 = 1,
scan_alignment_x4 = 4,
scan_alignment_x16 = 16,
} scan_alignment_t;

typedef struct signature {
Expand Down
9 changes: 9 additions & 0 deletions include/libhat/platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,21 @@
#error Unsupported Architecture
#endif

#if defined(__LP64__) || defined(_M_X64)
#define LIBHAT_LP64
#else
#define LIBHAT_LP32
#endif

// Detect Operating System
#if defined(_WIN32)
#define LIBHAT_WINDOWS
#elif defined(linux) || defined(__linux__) || defined(__linux)
#define LIBHAT_UNIX
#define LIBHAT_LINUX
#if defined(__ANDROID__)
#define LIBHAT_ANDROID
#endif
#elif defined(__APPLE__) && defined(__MACH__)
#define LIBHAT_UNIX
#define LIBHAT_MAC
Expand Down
Loading
Loading