-
Notifications
You must be signed in to change notification settings - Fork 13
Hooking API
The launcher provides the following basic C API for hooking:
// #include <modloader/hook.h>
modloader_hook_t *modloader_hook(void *sym, void *hook, void **orig);
void modloader_destroy_hook(modloader_hook_t *hook);
The modloader_hook
function replaces the function given by sym
with hook
and stores a pointer that lets you call the original function in *orig
. All passed arguments must not be NULL. Note that *orig
will not exactly be the original function, but instead a trampoline function, but this should be considered an implementation detail.
modloader_hook
returns a pointer that you can use with modloader_destroy_hook
to remove the hook at runtime.
In case an error happens, modloader_hook
will return NULL. It's possible that the function will modify the orig
pointer, even if hooking fails.
The modloader_destroy_hook
function removes the hook specified by hook
. hook
must not be NULL. The implementation will try to remove the hook, however this is not guaranteed to always be the case. The API currently does not provide a way to tell if removing the hook succeeded.
#include <modloader/hook.h>
const char* what_we_will_replace(int x) {
return x == 0 ? "value is zero" : "value is not zero";
}
static const char* (*test_orig)(int x);
const char* test_hook(int x) {
// NOTE that calling what_we_will_replace directly from here would result in it calling test_hook recursively
return test_orig(1337);
}
void init() {
printf("%s\n", what_we_will_replace(0)); // will print "value is zero"
modloader_hook((void*) what_we_will_replace, (void*) test_hook, (void**) &test_orig);
printf("%s\n", what_we_will_replace(0)); // will print "value is not zero"
}
The library provides a simple C++ scoped-based hook (modloader::AutoHook
), and a macro-based static hook API is built upon it:
// #include <modloader/statichook.h>
#define THook2(iname, ret, sym, args...) ...
#define THook(ret, sym, args...) ...
#define TClasslessInstanceHook2(iname, ret, sym, args...) ...
#define TClasslessInstanceHook(ret, sym, args...) ...
#define TInstanceHook2(iname, ret, sym, type, args...) ...
#define TInstanceHook(ret, sym, type, args...) ...
#define TStaticHook2(iname, ret, sym, type, args...) ...
#define TStaticHook(ret, sym, type, args...) ...
The macros hook the function with the mangled symbol name sym
, with the return type of ret
and with the specified args
. In the case of TInstanceHook
and TStaticHook
a valid class name type
must also be given.
In the case of C++ executables, mangled symbol names start with _ZN
and look like the following: _ZN6Common22getServerVersionStringB5cxx11Ev
. C does not perform any sort of symbol mangling, and symbols marked as extern "C"
in C++ are also not mangled.
Variants explanation
- You should use
THook
for hooking global functions, that are not part of any class. - You should use
TInstanceHook
for hooking member functions. Requires a full class declaration- Use
TClasslessInstanceHook
if you do not want to use a full class declaration
- Use
- You should use
TStaticHook
for hooking static (class) functions. Requires a full class declaration- Use
TClasslessInstanceHook
if you do not want to use a full class declaration
- Use
Requires a full class declaration means that to use it you need to declare the class somewhere and include it (class DedicatedServer { };
in an included header is OK, but a forward declaration like class DedicatedServer;
is not enough).
If you need to hook a function more than once within the same mod, you'll need to use the macros suffixed with 2
, and pass different names as the first additional argument (eg. MenuHudRenderHook
and CoordinatesHudRenderHook
, make sure to maintain some sensible internal naming).
Usage explanations/examples
-
THook
- global C++ functions likebool ON_MAIN_THREAD(void)
, C functions (eg.printf
) -
TInstanceHook
/TClasslessInstanceHook
- for functions that are part of a class and are not static, eg.DedicatedServer::run()
-
TStaticHook
/THook
- for functions that are part of a class and are static, eg.Common::getServerVersionString()
#include <modloader/statichook.h>
extern "C" const char* what_we_will_replace(int x) {
return x == 0 ? "value is zero" : "value is not zero";
}
THook(const char*, what_we_will_replace, int x) {
return original(1337);
}
void init() {
printf("%s\n", what_we_will_replace(0)); // will print "value is not zero"
}