mirror of
https://git.recroomarchive.org/Radium/libradhook.git
synced 2026-08-07 09:10:59 +00:00
161 lines
6.3 KiB
Markdown
161 lines
6.3 KiB
Markdown
# libradhook
|
|
A minimal, cross-platform inline function hooking library for x86-64 and AArch64,
|
|
targeting Windows and Android, built as a lightweight alternative to libraries like
|
|
MinHook and Dobby with a flat, C-style, handle-based API.
|
|
|
|
## Features
|
|
- Inline hooking on x86-64 and AArch64
|
|
- Vtable hooking via direct slot swapping, no trampoline required
|
|
- Windows and Android support
|
|
- Flat C API with opaque handles
|
|
- Typed convenience overload of `RadHookCreate` for function pointers
|
|
- Trampolines to call the original function from within a detour
|
|
- Enable / disable / destroy individual hooks, or all hooks at once
|
|
- Deferred enable/disable via a queue, applied atomically in one pass
|
|
- Hook enumeration and introspection (target, detour, trampoline, enabled state)
|
|
- Human-readable status strings
|
|
- Runtime version query for ABI sanity checks
|
|
- Builds as a static or shared library via CMake
|
|
|
|
## Requirements
|
|
- CMake 3.21+
|
|
- A C++20 compiler (msvc, gcc, or clang)
|
|
- Android NDK (only if targeting android)
|
|
|
|
## Building
|
|
|
|
### CMake
|
|
```sh
|
|
cmake -S . -B build
|
|
cmake --build build
|
|
```
|
|
|
|
Useful options:
|
|
|
|
| Option | Default | Description |
|
|
|------------------------|------------------------|---------------------------------------------------------|
|
|
| `BUILD_SHARED_LIBS` | `ON` | Build radhook as a shared library instead of static |
|
|
| `RADHOOK_BUILD_TESTS` | `ON` when top-level | Build the test suite |
|
|
|
|
### Build.ps1
|
|
A helper PowerShell script is provided for common build targets:
|
|
|
|
```powershell
|
|
./Build.ps1 -Target msvc -Config Release # msvc, release, shared lib
|
|
./Build.ps1 -Target gcc -Config Release # mingw/gcc via ninja
|
|
./Build.ps1 -Target android -Config Release # android
|
|
./Build.ps1 -Target msvc -Static -RunTests # static build with tests
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Inline hooking
|
|
```cpp
|
|
#include <radhook/radhook.h>
|
|
|
|
int TargetFunction() { return 42; }
|
|
int DetourFunction() { return 1337; }
|
|
|
|
int main() {
|
|
RadHookHandle handle = nullptr;
|
|
|
|
// create and install the hook
|
|
RadHookResult result = RadHookCreate(TargetFunction, (void*)DetourFunction, &handle);
|
|
if (result != RadHookResult::Success) {
|
|
// handle error, RadHookResultToString(result) for a message
|
|
}
|
|
|
|
// call through the trampoline
|
|
using Fn = int(*)();
|
|
Fn original = RadHookGetOriginalAs<Fn>(handle);
|
|
original();
|
|
|
|
RadHookDisable(handle);
|
|
RadHookEnable(handle);
|
|
|
|
RadHookDestroy(handle);
|
|
return 0;
|
|
}
|
|
```
|
|
|
|
### Vtable hooking
|
|
```cpp
|
|
struct IFoo {
|
|
virtual int Value() { return 7; }
|
|
virtual ~IFoo() = default;
|
|
};
|
|
|
|
int Detour(IFoo* self) { return 777; }
|
|
|
|
IFoo obj;
|
|
void** vtable = *reinterpret_cast<void***>(&obj);
|
|
|
|
RadHookHandle handle = nullptr;
|
|
RadHookVTableCreate(vtable, /*index=*/0, (void*)Detour, &handle);
|
|
|
|
// no relocation is needed for vtable hooks
|
|
using Fn = int(*)(IFoo*);
|
|
Fn original = RadHookGetOriginalAs<Fn>(handle);
|
|
original(&obj);
|
|
|
|
RadHookDestroy(handle);
|
|
```
|
|
|
|
### Deferred apply
|
|
Stage several enable/disable transitions and flip them together in one pass,
|
|
instead of one hook at a time:
|
|
|
|
```cpp
|
|
RadHookQueueEnable(handleA);
|
|
RadHookQueueDisable(handleB);
|
|
|
|
// nothing has changed yet
|
|
Foo();
|
|
|
|
// then apply
|
|
RadHookApplyQueued(); // both transitions land together
|
|
```
|
|
|
|
If a handle is queued more than once before `RadHookApplyQueued` runs, the
|
|
most recent call wins.
|
|
|
|
## API overview
|
|
|
|
| Function Name | Description |
|
|
|-----------------------------------------------------|-----------------------------------------------------------------|
|
|
| `RadHookCreate` | Create and install a hook, enabled by default |
|
|
| `RadHookVTableCreate` | Create and install a hook on a vtable slot |
|
|
| `RadHookEnable` | Enable an installed hook |
|
|
| `RadHookDisable` | Disable an installed hook, restoring original bytes |
|
|
| `RadHookDestroy` | Disable, release, and invalidate a hook |
|
|
| `RadHookEnableAll` / `RadHookDisableAll` | Enable or disable every registered hook |
|
|
| `RadHookQueueEnable` / `RadHookQueueDisable` | Stage a hook's enable/disable state without applying it |
|
|
| `RadHookApplyQueued` | Apply every staged enable/disable transition at once |
|
|
| `RadHookGetCount` | Number of currently registered hooks |
|
|
| `RadHookGetHandles` / `RadHookEnumerate` | Enumerate registered hook handles |
|
|
| `RadHookIsValid` | Check whether a handle refers to a registered hook |
|
|
| `RadHookIsEnabled` | Check whether a hook is currently enabled |
|
|
| `RadHookGetTarget` | Get the hooked target function pointer |
|
|
| `RadHookGetDetour` | Get the detour function pointer |
|
|
| `RadHookGetOriginal` / `RadHookGetOriginalAs<T>` | Get the trampoline to call the original implementation |
|
|
| `RadHookResultToString` | Human-readable name for a `RadHookResult` |
|
|
| `RadHookGetVersion` | Query the library's major/minor/patch version |
|
|
|
|
All fallible operations return a `RadHookResult`, including granular error
|
|
codes such as `TrampolineTooFar`, `DisassemblyFailed`, and
|
|
`MemoryProtectFailed`.
|
|
|
|
## Testing
|
|
Tests are built with CTest and cover creation, enable/disable and lifecycle
|
|
transitions, trampoline behavior, vtable hooking, queued apply, invalid
|
|
input handling, failure cases, memory integrity, and stress/churn
|
|
scenarios.
|
|
|
|
```sh
|
|
cmake -S . -B build -DRADHOOK_BUILD_TESTS=ON
|
|
cmake --build build
|
|
ctest --test-dir build --output-on-failure
|
|
```
|
|
|
|
## License
|
|
Licensed under the GNU Lesser General Public License v3 (LGPLv3). See [LICENSE](LICENSE) for details. |