api changes, check README

This commit is contained in:
nexusverypro
2026-07-03 22:00:03 +01:00
parent ca6b22a2b5
commit 35f52a4115
7 changed files with 549 additions and 88 deletions

68
tests/test_queue.cpp Normal file
View File

@@ -0,0 +1,68 @@
#include <cassert>
#include "radhook/radhook.h"
static int fnA() { return 1; }
static int detourA() { return 100; }
static int fnB() { return 2; }
static int detourB() { return 200; }
static int fnC() { return 3; }
static int detourC() { return 300; }
int main() {
RadHookHandle hA = nullptr;
RadHookHandle hB = nullptr;
assert(RadHookCreate((void*)fnA, (void*)detourA, &hA) == RadHookResult::Success);
assert(RadHookCreate((void*)fnB, (void*)detourB, &hB) == RadHookResult::Success);
// start both from a known, disabled state
assert(RadHookDisable(hA) == RadHookResult::Success);
assert(RadHookDisable(hB) == RadHookResult::Success);
assert(fnA() == 1);
assert(fnB() == 2);
// queueing must not apply immediately
assert(RadHookQueueEnable(hA) == RadHookResult::Success);
assert(RadHookQueueEnable(hB) == RadHookResult::Success);
assert(!RadHookIsEnabled(hA));
assert(!RadHookIsEnabled(hB));
assert(fnA() == 1);
assert(fnB() == 2);
// apply enables both together
assert(RadHookApplyQueued() == RadHookResult::Success);
assert(RadHookIsEnabled(hA));
assert(RadHookIsEnabled(hB));
assert(fnA() == 100);
assert(fnB() == 200);
// last queued action before apply wins
assert(RadHookQueueDisable(hA) == RadHookResult::Success);
assert(RadHookQueueEnable(hA) == RadHookResult::Success);
assert(RadHookApplyQueued() == RadHookResult::Success);
assert(RadHookIsEnabled(hA));
// an apply with nothing queued is a no-op
assert(RadHookApplyQueued() == RadHookResult::Success);
// queueing a redundant transition (already enabled) still applies cleanly
assert(RadHookQueueEnable(hA) == RadHookResult::Success);
assert(RadHookApplyQueued() == RadHookResult::Success);
assert(RadHookIsEnabled(hA));
// invalid handles
assert(RadHookQueueEnable(nullptr) == RadHookResult::InvalidHandle);
assert(RadHookQueueDisable(nullptr) == RadHookResult::InvalidHandle);
// a destroyed hook can no longer be queued
RadHookHandle hC = nullptr;
assert(RadHookCreate((void*)fnC, (void*)detourC, &hC) == RadHookResult::Success);
RadHookDestroy(hC);
assert(RadHookQueueEnable(hC) == RadHookResult::InvalidHandle);
RadHookDestroy(hA);
RadHookDestroy(hB);
return 0;
}

20
tests/test_version.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include <cassert>
#include "radhook/radhook.h"
int main() {
int major = -1, minor = -1, patch = -1;
RadHookGetVersion(&major, &minor, &patch);
assert(major == RADHOOK_VERSION_MAJOR);
assert(minor == RADHOOK_VERSION_MINOR);
assert(patch == RADHOOK_VERSION_PATCH);
// individual out params may be omitted independently
int onlyMajor = -1;
RadHookGetVersion(&onlyMajor, nullptr, nullptr);
assert(onlyMajor == RADHOOK_VERSION_MAJOR);
// must not crash
RadHookGetVersion(nullptr, nullptr, nullptr);
return 0;
}

68
tests/test_vtable.cpp Normal file
View File

@@ -0,0 +1,68 @@
#include <cassert>
#include "radhook/radhook.h"
struct IFoo {
virtual int Value() { return 7; }
virtual ~IFoo() = default;
};
static int DetourValue(IFoo* /*self*/) { return 777; }
using Fn = int(*)(IFoo*);
int main() {
IFoo obj;
void** vtable = *reinterpret_cast<void***>(&obj);
RadHookHandle h = nullptr;
assert(RadHookVTableCreate(vtable, 0, (void*)DetourValue, &h) == RadHookResult::Success);
assert(RadHookIsValid(h));
assert(RadHookIsEnabled(h));
// handle is created enabled, so the swap must already be visible
assert(obj.Value() == 777);
assert(reinterpret_cast<Fn>(vtable[0])(&obj) == 777);
void* originalEntry = RadHookGetTarget(h);
assert(originalEntry != nullptr);
assert(RadHookGetDetour(h) == reinterpret_cast<void*>(DetourValue));
// disable restores the original vtable entry
assert(RadHookDisable(h) == RadHookResult::Success);
assert(!RadHookIsEnabled(h));
assert(obj.Value() == 7);
assert(vtable[0] == originalEntry);
// re-enable re-applies the swap
assert(RadHookEnable(h) == RadHookResult::Success);
assert(obj.Value() == 777);
// no relocation is needed for vtable hooks -- the trampoline is the
// original entry itself and can be called directly while the hook is live
auto original = RadHookGetOriginalAs<Fn>(h);
assert(reinterpret_cast<void*>(original) == originalEntry);
assert(original(&obj) == 7);
RadHookDestroy(h);
assert(!RadHookIsValid(h));
assert(obj.Value() == 7);
// queueing works uniformly across inline and vtable hook kinds
RadHookHandle h2 = nullptr;
assert(RadHookVTableCreate(vtable, 0, (void*)DetourValue, &h2) == RadHookResult::Success);
assert(RadHookQueueDisable(h2) == RadHookResult::Success);
assert(RadHookIsEnabled(h2)); // not applied yet
assert(RadHookApplyQueued() == RadHookResult::Success);
assert(!RadHookIsEnabled(h2));
assert(obj.Value() == 7);
RadHookDestroy(h2);
// invalid inputs
RadHookHandle bad = nullptr;
assert(RadHookVTableCreate(nullptr, 0, (void*)DetourValue, &bad) == RadHookResult::InvalidTarget);
assert(bad == nullptr);
assert(RadHookVTableCreate(vtable, 0, nullptr, &bad) == RadHookResult::InvalidDetour);
assert(bad == nullptr);
return 0;
}