Add mods::hook::uninstall

This commit is contained in:
Luke Street
2026-08-06 23:59:26 -06:00
parent 2c7ab755a0
commit 3ac0fcb156
4 changed files with 175 additions and 56 deletions
+10
View File
@@ -53,4 +53,14 @@ ModResult hook_replace(HookReplaceFn callback, const HookOptions* options = null
return hook::replace<Entry>(callback, options);
}
template <class Entry>
ModResult hook_uninstall(const HookService* hooks) {
return hook::uninstall<Entry>(hooks);
}
template <class Entry>
ModResult hook_uninstall() {
return hook::uninstall<Entry>();
}
} // namespace mods
+33 -14
View File
@@ -7,20 +7,22 @@
#endif
/*
* Intercept game functions by address. Prefer the typed helpers in mods/svc/hook.hpp
* (mods::hook::add_pre/add_post/replace over a &Class::method): they generate the
* trampoline and hide install/dispatch, which are the low-level primitives those helpers
* build. resolve() maps a symbol name to an address for targets you can't name at compile time
* (file-local statics included).
* Hooks allow intercepting calls to game functions, allowing you to:
* - Modify arguments
* - Perform your own work before (pre), after (post) or instead of (replace) the original call
* - From a pre hook, conditionally skip the original call and return your own value
*
* Every call is game-thread-only. Install and removal must run with no hooked function on the
* stack; the loader guarantees this by applying mod lifecycle changes between frames, which is
* why hooking a function that never returns (the outermost loop) makes a mod un-unloadable.
* In most cases, you'll want to instead use the C++ helpers in mods/svc/hook.hpp
* (mods::hook::add_pre/add_post/replace). They generate the trampoline passed to
* install and provide compile-time type checking.
*
* resolve() resolves an address by symbol name for targets you can't name at compile time
* (file-local statics included).
*/
#define HOOK_SERVICE_ID "dev.twilitrealm.dusklight.hook"
#define HOOK_SERVICE_MAJOR 1u
#define HOOK_SERVICE_MINOR 0u
#define HOOK_SERVICE_MINOR 1u
/* Symbol flags reported by resolve() */
typedef enum HookSymbolFlags {
@@ -72,11 +74,18 @@ typedef struct HookService {
ServiceHeader header;
/*
* Install a trampoline detour on fn_addr and return the address to call the original through in
* *out_original_fn. The typed helpers generate the trampoline and call this; mods normally
* don't. The first mod to install a given target owns the live detour; later mods register as
* candidates so a hook survives the owner unloading (the detour is handed off and every
* original pointer is rewritten). Idempotent per (mod, out slot).
* Install a hook on fn_addr.
*
* trampoline_fn must point to a function that matches the original function's signature and
* dispatches pre- and post- hooks. This dispatch trampoline is normally generated at compile
* time using C++ template instantiation (see mods/svc/hook.hpp).
*
* The first hook install on a target will implicitly install a detour (patched instructions
* on the target that jump to the dispatch trampoline). When all hooks are uninstalled from a
* target, the detour is completely uninstalled.
*
* The address that the dispatch trampoline should call the original function through is written
* to out_original_fn.
*/
ModResult (*install)(
ModContext* ctx, void* fn_addr, void* trampoline_fn, void** out_original_fn);
@@ -116,6 +125,16 @@ typedef struct HookService {
*/
ModResult (*resolve)(
ModContext* ctx, const char* symbol, void** out_addr, HookSymbolFlags* out_flags);
/* Minor version 1 */
/*
* Uninstall the current mod's hook on fn_addr and unregister all callbacks.
* If no other mods have a hook installed on the target, the detour is uninstalled entirely.
*
* original_fn_slot must match the out_original_fn passed to install.
*/
ModResult (*uninstall)(ModContext* ctx, void* fn_addr, void** original_fn_slot);
} HookService;
MOD_DECLARE_SERVICE(HookService, svc_hook, HOOK_SERVICE_ID, HOOK_SERVICE_MAJOR, HOOK_SERVICE_MINOR);
+22
View File
@@ -238,5 +238,27 @@ ModResult replace(HookReplaceFn callback, const HookOptions* options = nullptr)
return replace<Entry>(svc_hook, callback, options);
}
template <class Entry>
ModResult uninstall(const HookService* hooks) {
if (hooks == nullptr || !SERVICE_HAS(hooks, HookService, uninstall) ||
hooks->uninstall == nullptr || Entry::target == nullptr)
{
return MOD_UNAVAILABLE;
}
const ModResult result =
hooks->uninstall(mod_ctx, Entry::target, reinterpret_cast<void**>(&Entry::g_orig));
if (result == MOD_OK) {
Entry::hooks = nullptr;
Entry::g_orig = nullptr;
}
return result;
}
template <class Entry>
ModResult uninstall() {
return uninstall<Entry>(svc_hook);
}
} // namespace hook
} // namespace mods