diff options
| author | 2025-12-26 23:12:11 +0000 | |
|---|---|---|
| committer | 2026-02-16 19:11:24 +0000 | |
| commit | af370088fc178996a46ee508aaa1a2c46318bbf2 (patch) | |
| tree | cf1f6c01ad7c101d3fd4abb44835d9deb600e30d | |
| parent | 40f9d989df2c1ff2f567656ccbdbfc0d97e34a77 (diff) | |
| download | sst-af370088fc178996a46ee508aaa1a2c46318bbf2.tar.gz sst-af370088fc178996a46ee508aaa1a2c46318bbf2.zip | |
Move to static trampolines and type-safe hooks
For some reason the DLL got a tiny little bit bigger again but that's
fine. This will make orig_ calls more efficient in the inline case, and
also make it harder to screw up and hook the wrong thing by mistake.
Self-explanatory-ish, apart from the fact it's a fairly large API change
of course. And it relies on some more bonkers assembler directive
hackery.
But it works!
The only complaint one might have is that the featsetup functions no
longer take an explicit string which occasionally yields slightly less
perfect error messages, but I've decided this isn't really a problem and
makes the API nicer to use. It's a tradeoff, innit.
| -rw-r--r-- | DevDocs/code-style.txt | 5 | ||||
| -rwxr-xr-x | compile | 1 | ||||
| -rw-r--r-- | compile.bat | 6 | ||||
| -rw-r--r-- | src/ac.c | 37 | ||||
| -rw-r--r-- | src/autojump.c | 23 | ||||
| -rw-r--r-- | src/demorec.c | 19 | ||||
| -rw-r--r-- | src/fastfwd.c | 33 | ||||
| -rw-r--r-- | src/fov.c | 26 | ||||
| -rw-r--r-- | src/hook.S | 26 | ||||
| -rw-r--r-- | src/hook.c | 74 | ||||
| -rw-r--r-- | src/hook.h | 336 | ||||
| -rw-r--r-- | src/hud.c | 9 | ||||
| -rw-r--r-- | src/inputhud.c | 49 | ||||
| -rw-r--r-- | src/kvsys.c | 13 | ||||
| -rw-r--r-- | src/l4d1democompat.c | 166 | ||||
| -rw-r--r-- | src/l4daddon.c | 29 | ||||
| -rw-r--r-- | src/l4dreset.c | 63 | ||||
| -rw-r--r-- | src/nosleep.c | 10 | ||||
| -rw-r--r-- | src/portalcolours.c | 39 | ||||
| -rw-r--r-- | src/rinput.c | 46 | ||||
| -rw-r--r-- | src/sst.c | 9 |
21 files changed, 560 insertions, 459 deletions
diff --git a/DevDocs/code-style.txt b/DevDocs/code-style.txt index cdbc666..11d9405 100644 --- a/DevDocs/code-style.txt +++ b/DevDocs/code-style.txt @@ -785,8 +785,9 @@ Some other common prefixes and suffixes are just good for making the whole thing feel coherent to a human reader even if nothing really requires them: • *_func for function pointer typedefs. -• orig_* for a function pointer used to call an original function from a hook. -• hook_* for the hook function itself, which intercepts calls to the original. +• orig_* for a pointer (or static trampoline) to a function that has been hooked + in some manner. +• hookdest_* for the intercepting function called in place of a hooked function. • find_* for a function within a feature which attempts to find some variable or function buried within the engine, usually by chasing pointers through machine instructions or internal engine data structures or something. @@ -76,7 +76,6 @@ src="\ gameserver.c hexcolour.c hook.c - hook.S hud.c inputhud.c kvsys.c diff --git a/compile.bat b/compile.bat index 518fd5d..2958186 100644 --- a/compile.bat +++ b/compile.bat @@ -90,7 +90,6 @@ setlocal DisableDelayedExpansion :+ gameserver.c
:+ hexcolour.c
:+ hook.c
-:+ hook.S
:+ hud.c
:+ inputhud.c
:+ kvsys.c
@@ -109,7 +108,10 @@ setlocal DisableDelayedExpansion :+ trace.c
:+ xhair.c
:: just tack these on, whatever (repeated condition because of expansion memes)
-if "%dbg%"=="1" set src=%src% src/dbg.c
+if "%dbg%"=="1" (
+ set src=%src% src/dbg.c
+ set metasrc=%metasrc% src/dbg.c
+)
if "%dbg%"=="1" set src=%src% src/udis86.c
if "%dbg%"=="0" set src=%src% src/wincrt.c
@@ -154,7 +154,7 @@ static ulong __stdcall inhookthrmain(void *param) { } static ssize orig_wndproc; -static ssize __stdcall hook_wndproc(void *wnd, uint msg, usize wp, ssize lp) { +static ssize __stdcall hookdest_wndproc(void *wnd, uint msg, usize wp, ssize lp) { if_cold (msg == WM_COPYDATA && enabled) { return DefWindowProcW(wnd, msg, wp, lp); } @@ -171,7 +171,7 @@ static inline bool win32_init() { return false; } orig_wndproc = SetWindowLongPtrA(gamewin, GWLP_WNDPROC, - (ssize)&hook_wndproc); + (ssize)&hookdest_wndproc); if_cold (!orig_wndproc) { // XXX: assuming 0 won't be legitimately returned errmsg_errorsys("failed to attach message handler"); return false; @@ -288,8 +288,8 @@ DECL_VFUNC_DYN(struct IGameUIFuncs, void, GetDesktopResolution, int *, int *) DECL_VFUNC_DYN(struct IGame, void, DispatchAllStoredGameMessages) typedef void (*Key_Event_func)(struct inputevent *); -static Key_Event_func orig_Key_Event; -static void hook_Key_Event(struct inputevent *ev) { +DEF_INLINE_HOOK(Key_Event_func, Key_Event) +static void hookdest_Key_Event(struct inputevent *ev) { //const char *desc[] = {"DOWN", "UP", "DBL"}; //const char desclen[] = {4, 2, 3}; switch (ev->type) { @@ -309,7 +309,7 @@ static void hook_Key_Event(struct inputevent *ev) { orig_Key_Event(ev); } -static bool find_Key_Event() { +static Key_Event_func find_Key_Event() { #ifdef _WIN32 // Crazy pointer-chasing path to get to DispatchInputEvent: // IGameUIFuncs interface @@ -321,7 +321,7 @@ static bool find_Key_Event() { "VENGINE_GAMEUIFUNCS_VERSION005", 0); if_cold (!gameuifuncs) { errmsg_errorx("couldn't get engine game UI interface"); - return false; + return 0; } struct IGame *cgame; const uchar *insns = gameuifuncs->vtable[vtidx_GetDesktopResolution]; @@ -334,36 +334,37 @@ static bool find_Key_Event() { NEXT_INSN(p, "CGame instance pointer"); } errmsg_errorx("couldn't find pointer to CGame instance"); - return false; + return 0; ok: insns = cgame->vtable[vtidx_DispatchAllStoredGameMessages]; + Key_Event_func Key_Event; for (const uchar *p = insns; p - insns < 128;) { if (p[0] == X86_CALL) { - orig_Key_Event = (Key_Event_func)(p + 5 + mem_loads32(p + 1)); + Key_Event = (Key_Event_func)(p + 5 + mem_loads32(p + 1)); goto ok2; } NEXT_INSN(p, "DispatchInputEvent/Key_Event function"); } errmsg_errorx("couldn't find DispatchInputEvent/Key_Event function"); - return false; + return 0; -ok2:insns = (const uchar *)orig_Key_Event; +ok2:insns = (const uchar *)Key_Event; // Depending on compiler inlining decisions, the function we just found can // be either DispatchInputEvent or Key_Event. If another CALL is found at // the start of this function, that means that we actually found // DispatchInputEvent and this CALL points to Key_Event. for (const uchar *p = insns; p - insns < 32;) { if (p[0] == X86_CALL) { - orig_Key_Event = (Key_Event_func)(p + 5 + mem_loads32(p + 1)); + Key_Event = (Key_Event_func)(p + 5 + mem_loads32(p + 1)); break; } NEXT_INSN(p, "Key_Event function"); } - return true; + return Key_Event; #else #warning TODO(linux): more find-y stuff #endif - return false; + return 0; } HANDLE_EVENT(AllowPluginLoading, bool loading) { @@ -383,9 +384,9 @@ HANDLE_EVENT(PluginUnloaded) { } INIT { - if_cold (!find_Key_Event()) return FEAT_INCOMPAT; - struct hook_inline_featsetup_ret h = hook_inline_featsetup( - (void *)orig_Key_Event, (void **)&orig_Key_Event, "Key_Event"); + Key_Event_func Key_Event = find_Key_Event(); + if_cold (!Key_Event) return FEAT_INCOMPAT; + struct hook_featsetup_ret_Key_Event h = hook_featsetup_Key_Event(Key_Event); if_cold (h.err) return h.err; #ifdef _WIN32 @@ -424,7 +425,7 @@ INIT { // run of bytes memcpy(keybox->lbpub, lbpubkeys[LBPK_L4D], 32); } - hook_inline_commit(h.prologue, (void *)hook_Key_Event); + hook_commit_Key_Event(h.hookpos, hookdest_Key_Event); return FEAT_OK; #ifdef _WIN32 @@ -445,7 +446,7 @@ END { munmap(keybox, 4096); // TODO(linux): call other cleanup things #endif - unhook_inline((void *)orig_Key_Event); + unhook_Key_Event(); } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/autojump.c b/src/autojump.c index eb0e34f..48caf90 100644 --- a/src/autojump.c +++ b/src/autojump.c @@ -44,20 +44,21 @@ static inline int handleidx(ulong h) { return h & (1 << 11) - 1; } static struct CGameMovement *gmsv = 0, *gmcl = 0; typedef bool (*VCALLCONV CheckJumpButton_func)(struct CGameMovement *); -static CheckJumpButton_func origsv, origcl; +DEF_VTABLE_HOOK(CheckJumpButton_func, CheckJumpButton_sv) +DEF_VTABLE_HOOK(CheckJumpButton_func, CheckJumpButton_cl) -static bool VCALLCONV hooksv(struct CGameMovement *this) { +static bool VCALLCONV hookdest_sv(struct CGameMovement *this) { struct CMoveData *mv = get_mv(this); int idx = handleidx(mv->playerhandle); if (con_getvari(sst_autojump) && mv->firstrun && !justjumped[idx]) { mv->oldbuttons &= ~IN_JUMP; } - bool ret = origsv(this); + bool ret = orig_CheckJumpButton_sv(this); if (mv->firstrun) justjumped[idx] = ret; return ret; } -static bool VCALLCONV hookcl(struct CGameMovement *this) { +static bool VCALLCONV hookdest_cl(struct CGameMovement *this) { struct CMoveData *mv = get_mv(this); // FIXME: this will stutter in the rare case where justjumped is true. // currently doing clientside justjumped handling makes multiplayer @@ -65,13 +66,13 @@ static bool VCALLCONV hookcl(struct CGameMovement *this) { // properly. //if (con_getvari(sst_autojump) && !justjumped[0]) mv->oldbuttons &= ~IN_JUMP; if (con_getvari(sst_autojump)) mv->oldbuttons &= ~IN_JUMP; - return justjumped[0] = origcl(this); + return justjumped[0] = orig_CheckJumpButton_cl(this); } static bool unprot(struct CGameMovement *gm) { bool ret = os_mprot(gm->vtable + vtidx_CheckJumpButton, sizeof(void *), PAGE_READWRITE); - if (!ret) errmsg_errorsys("couldn't make virtual table writable"); + if_cold (!ret) errmsg_errorsys("couldn't make virtual table writable"); return ret; } @@ -98,10 +99,8 @@ INIT { return FEAT_FAIL; } if_cold (!unprot(gmcl)) return FEAT_FAIL; - origsv = (CheckJumpButton_func)hook_vtable(gmsv->vtable, - vtidx_CheckJumpButton, (void *)&hooksv); - origcl = (CheckJumpButton_func)hook_vtable(gmcl->vtable, - vtidx_CheckJumpButton, (void *)&hookcl); + hook_CheckJumpButton_sv(gmsv->vtable, vtidx_CheckJumpButton, &hookdest_sv); + hook_CheckJumpButton_cl(gmcl->vtable, vtidx_CheckJumpButton, &hookdest_cl); if (GAMETYPE_MATCHES(Portal1)) { // this is a stupid, stupid policy that doesn't make any sense, but I've @@ -118,8 +117,8 @@ INIT { } END { - unhook_vtable(gmsv->vtable, vtidx_CheckJumpButton, (void *)origsv); - unhook_vtable(gmcl->vtable, vtidx_CheckJumpButton, (void *)origcl); + unhook_CheckJumpButton_sv(gmsv->vtable, vtidx_CheckJumpButton); + unhook_CheckJumpButton_cl(gmcl->vtable, vtidx_CheckJumpButton); } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/demorec.c b/src/demorec.c index 0511d46..6070bee 100644 --- a/src/demorec.c +++ b/src/demorec.c @@ -62,8 +62,9 @@ DEF_EVENT(DemoRecordStopped, int) struct CDemoRecorder; typedef void (*VCALLCONV SetSignonState_func)(struct CDemoRecorder *, int); -static SetSignonState_func orig_SetSignonState; -static void VCALLCONV hook_SetSignonState(struct CDemoRecorder *this, int state) { +DEF_VTABLE_HOOK(SetSignonState_func, SetSignonState) +static void VCALLCONV hookdest_SetSignonState(struct CDemoRecorder *this, + int state) { // NEW fires once every map or save load, but only bumps number if demo file // was left open (i.e. every transition). bump it unconditionally instead! if (state == SIGNONSTATE_NEW) { @@ -81,8 +82,8 @@ static void VCALLCONV hook_SetSignonState(struct CDemoRecorder *this, int state) } typedef void (*VCALLCONV StopRecording_func)(struct CDemoRecorder *); -static StopRecording_func orig_StopRecording; -static void VCALLCONV hook_StopRecording(struct CDemoRecorder *this) { +DEF_VTABLE_HOOK(StopRecording_func, StopRecording) +static void VCALLCONV hookdest_StopRecording(struct CDemoRecorder *this) { bool wasrecording = *recording; int lastnum = *demonum; orig_StopRecording(this); @@ -277,10 +278,8 @@ INIT { errmsg_errorx("couldn't find demo basename variable"); return FEAT_INCOMPAT; } - orig_SetSignonState = (SetSignonState_func)hook_vtable(vtable, - vtidx_SetSignonState, (void *)&hook_SetSignonState); - orig_StopRecording = (StopRecording_func)hook_vtable(vtable, - vtidx_StopRecording, (void *)&hook_StopRecording); + hook_SetSignonState(vtable, vtidx_SetSignonState, &hookdest_SetSignonState); + hook_StopRecording(vtable, vtidx_StopRecording, &hookdest_StopRecording); hook_record_cb(cmd_record); hook_stop_cb(cmd_stop); return FEAT_OK; @@ -291,8 +290,8 @@ END { // avoid dumb edge case if someone somehow records and immediately unloads if (*recording && *demonum == 0) *demonum = 1; void **vtable = demorecorder->vtable; - unhook_vtable(vtable, vtidx_SetSignonState, (void *)orig_SetSignonState); - unhook_vtable(vtable, vtidx_StopRecording, (void *)orig_StopRecording); + unhook_SetSignonState(vtable, vtidx_SetSignonState); + unhook_StopRecording(vtable, vtidx_StopRecording); unhook_record_cb(cmd_record); unhook_stop_cb(cmd_stop); } diff --git a/src/fastfwd.c b/src/fastfwd.c index 78f2d47..0e6cb06 100644 --- a/src/fastfwd.c +++ b/src/fastfwd.c @@ -17,8 +17,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ -#include <stdlib.h> - #include "chunklets/x86.h" #include "engineapi.h" #include "errmsg.h" @@ -42,12 +40,12 @@ REQUIRE_GAMEDATA(vtidx_Frame) REQUIRE_GAMEDATA(vtidx_GetRealTime) REQUIRE_GAMEDATA(vtidx_HostFrameTime) -typedef void (*Host_AccumulateTime_func)(float dt); -static Host_AccumulateTime_func orig_Host_AccumulateTime; static float *realtime, *host_frametime; - static float skiptime = 0.0, skiprate; -static void hook_Host_AccumulateTime(float dt) { + +typedef void (*Host_AccumulateTime_func)(float dt); +DEF_INLINE_HOOK(Host_AccumulateTime_func, Host_AccumulateTime) +static void hookdest_Host_AccumulateTime(float dt) { float skipinc = skiprate * dt; if_hot (!skiptime) { orig_Host_AccumulateTime(dt); @@ -127,7 +125,8 @@ static inline void *find_FrameUpdate(void *HostState_Frame) { return 0; } -static inline bool find_Host_AccumulateTime(void *_Host_RunFrame) { +static inline Host_AccumulateTime_func find_Host_AccumulateTime( + void *_Host_RunFrame) { #ifdef _WIN32 const uchar *insns = (const uchar *)_Host_RunFrame; for (const uchar *p = insns; p - insns < 384;) { @@ -135,17 +134,16 @@ static inline bool find_Host_AccumulateTime(void *_Host_RunFrame) { NEXT_INSN(p, "Host_AccumulateTime"); while (p - insns < 384) { if (p[0] == X86_CALL) { - orig_Host_AccumulateTime = (Host_AccumulateTime_func)( + return (Host_AccumulateTime_func)( p + 5 + mem_loads32(p + 1)); - return true; } NEXT_INSN(p, "Host_AccumulateTime"); } - return false; + return 0; } NEXT_INSN(p, "Host_AccumulateTime"); } - return false; + return 0; #else #warning TODO(linux): yet another assembly thing #endif @@ -232,21 +230,22 @@ INIT { errmsg_errorx("couldn't find _Host_RunFrame function"); return FEAT_INCOMPAT; } - if_cold (!find_Host_AccumulateTime(func)) { + Host_AccumulateTime_func Host_AccumulateTime = + find_Host_AccumulateTime(func); + if_cold (!Host_AccumulateTime) { errmsg_errorx("couldn't find Host_AccumulateTime function"); return FEAT_INCOMPAT; } - struct hook_inline_featsetup_ret h = hook_inline_featsetup( - (void *)orig_Host_AccumulateTime, (void **)&orig_Host_AccumulateTime, - "Host_AccumulateTime"); + struct hook_featsetup_ret_Host_AccumulateTime h = + hook_featsetup_Host_AccumulateTime(Host_AccumulateTime); if_cold (h.err) return h.err; - hook_inline_commit(h.prologue, (void *)&hook_Host_AccumulateTime); + hook_commit_Host_AccumulateTime(h.hookpos, &hookdest_Host_AccumulateTime); return FEAT_OK; } END { if_hot (!sst_userunloaded) return; - unhook_inline((void *)orig_Host_AccumulateTime); + unhook_Host_AccumulateTime(); } // vi: sw=4 ts=4 noet tw=80 cc=80 @@ -46,26 +46,23 @@ DEF_CVAR_MINMAX_UNREG(fov_desired, static struct con_var *real_fov_desired; // engine's if it has it, or ours typedef void (*VCALLCONV SetDefaultFOV_func)(void *, int); -static SetDefaultFOV_func orig_SetDefaultFOV; -static void VCALLCONV hook_SetDefaultFOV(void *this, int fov) { +DEF_INLINE_HOOK(SetDefaultFOV_func, SetDefaultFOV) +static void VCALLCONV hookdest_SetDefaultFOV(void *this, int fov) { // disregard server-side clamped value and force our own value instead orig_SetDefaultFOV(this, con_getvari(real_fov_desired)); } -static bool find_SetDefaultFOV(struct con_cmd *fov) { - const uchar *insns = (const uchar *)fov->cb; +static SetDefaultFOV_func find_SetDefaultFOV(const uchar *insns) { int callcnt = 0; for (const uchar *p = insns; p - insns < 96;) { // The fov command calls 4 functions, one of them virtual. Of the 3 // direct calls, SetDefaultFOV() is the third. if (p[0] == X86_CALL && ++callcnt == 3) { - orig_SetDefaultFOV = (SetDefaultFOV_func)(p + 5 + - mem_loads32(p + 1)); - return true; + return (SetDefaultFOV_func)(p + 5 + mem_loads32(p + 1)); } NEXT_INSN(p, "SetDefaultFOV function"); } - return false; + return 0; } // replacement cvar needs to actively set player fov if in a map @@ -98,16 +95,15 @@ INIT { con_regvar(fov_desired); real_fov_desired = fov_desired; } - if_cold (!find_SetDefaultFOV(cmd_fov)) { + SetDefaultFOV_func SetDefaultFOV = find_SetDefaultFOV(cmd_fov->cb_insns); + if_cold (!SetDefaultFOV) { errmsg_errorx("couldn't find SetDefaultFOV function"); return FEAT_INCOMPAT; } - - struct hook_inline_featsetup_ret h = hook_inline_featsetup( - (void *)orig_SetDefaultFOV, (void **)&orig_SetDefaultFOV, - "SetDefaultFov"); + struct hook_featsetup_ret_SetDefaultFOV h = + hook_featsetup_SetDefaultFOV(SetDefaultFOV); if_cold (h.err) return h.err; - hook_inline_commit(h.prologue, (void *)&hook_SetDefaultFOV); + hook_commit_SetDefaultFOV(h.hookpos, &hookdest_SetDefaultFOV); // we might not be using our cvar but simpler to do this unconditionally fov_desired->cb = &fovcb; @@ -130,7 +126,7 @@ END { void *player = ent_get(1); // also singleplayer only if (player) orig_SetDefaultFOV(player, 75); } - unhook_inline((void *)orig_SetDefaultFOV); + unhook_SetDefaultFOV(); cmd_fov->base.flags &= ~_CON_NE_DEVONLY; } diff --git a/src/hook.S b/src/hook.S deleted file mode 100644 index 7baa856..0000000 --- a/src/hook.S +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright © Michael Smith <mikesmiffy128@gmail.com> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ - -#include "asm.h" -#include "rwx.h" - -#define SPACE 2048 // NOTE: MUST match SPACE in hook.c! - -.section RWX_SECTION, RWX_SECTION_FLAGS -.globl ASM_MANGLE(_hook_trampolinespc) -.comm ASM_MANGLE(_hook_trampolinespc), SPACE - -// vi: sw=4 ts=4 noet tw=80 cc=80 @@ -28,71 +28,61 @@ // Almost certainly breaks in some weird cases. Oh well! Most of the time, // vtable hooking is more reliable, this is only for, uh, emergencies. -#define SPACE 2048 // NOTE: MUST match SPACE in hook.S! -extern uchar _hook_trampolinespc[]; // defined in hook.S -static uchar *curtrampoline = _hook_trampolinespc; - -struct hook_inline_prep_ret hook_inline_prep(void *func, void **trampoline) { - uchar *p = func; - // dumb hack: if we hit some thunk that immediately jumps elsewhere (which +uchar *_hook_getpos(uchar *func) { + // if we are hooking some thunk that immediately jumps elsewhere (which // seems common for win32 API functions), hook the underlying thing instead. - // later: that dumb hack has now ended up having implications in the - // redesign of the entire API. :-) - while (*p == X86_JMPIW) p += mem_loads32(p + 1) + 5; - void *prologue = p; + // we return this back as the thing that actually ends up getting hooked + // after memory protections are changed. + while (*func == X86_JMPIW) func += mem_loads32(func + 1) + 5; + return func; +} + +struct _hook_prep_ret _hook_prep(uchar *func, uchar *trampoline) { + func = _hook_getpos(func); + const uchar *p = func; int len = 0; for (;;) { if_cold (p[len] == X86_CALL) { - return (struct hook_inline_prep_ret){ - 0, "can't trampoline call instructions" + return (struct _hook_prep_ret){ + 0, 0, "can't trampoline call instructions" }; } int ilen = x86_len(p + len); if_cold (ilen == -1) { - return (struct hook_inline_prep_ret){ - 0, "unknown or invalid instruction" + return (struct _hook_prep_ret){ + 0, 0, "unknown or invalid instruction" }; } len += ilen; if (len >= 5) { - // we should have statically made trampoline buffer size big enough - assume(curtrampoline - (uchar *)_hook_trampolinespc < - SPACE - len - 6); - *curtrampoline = len; // stuff length in there for quick unhooking - uchar *newtrampoline = curtrampoline + 1; - curtrampoline += len + 6; - memcpy(newtrampoline, p, len); - newtrampoline[len] = X86_JMPIW; - u32 diff = p - (newtrampoline + 5); // goto the continuation - memcpy(newtrampoline + len + 1, &diff, 4); - *trampoline = newtrampoline; - return (struct hook_inline_prep_ret){prologue, 0}; + memcpy(trampoline, p, len); + trampoline[len] = X86_JMPIW; + s32 diff = p - (trampoline + 5); // goto the continuation + memcpy(trampoline + len + 1, &diff, 4); + return (struct _hook_prep_ret){func, len, 0}; } if_cold (p[len] == X86_JMPIW) { - return (struct hook_inline_prep_ret){ - 0, "can't trampoline jump instructions" + return (struct _hook_prep_ret){ + 0, 0, "can't trampoline jump instructions" }; } } } -bool hook_inline_mprot(void *prologue) { - return os_mprot(prologue, 5, PAGE_EXECUTE_READWRITE); +bool hook_inline_mprot(void *hookpos) { + return os_mprot(hookpos, 5, PAGE_EXECUTE_READWRITE); } -void hook_inline_commit(void *restrict prologue, void *restrict target) { - uchar *p = prologue; - u32 diff = (uchar *)target - (p + 5); // goto the hook target - p[0] = X86_JMPIW; - memcpy(p + 1, &diff, 4); +void _hook_inline_commit(uchar *restrict hookpos, const uchar *restrict target) { + s32 diff = (uchar *)target - (hookpos + 5); // goto the hook target + hookpos[0] = X86_JMPIW; + memcpy(hookpos + 1, &diff, 4); } -void unhook_inline(void *orig) { - uchar *p = orig; - int len = p[-1]; - int off = mem_loads32(p + len + 1); - uchar *q = p + off + 5; - memcpy(q, p, 5); // XXX: not atomic atm! (does any of it even need to be?) +void _unhook_inline(uchar *trampoline, int len) { + s32 off = mem_loads32(trampoline + len + 1); + uchar *orig = trampoline + off + 5; + memcpy(orig, trampoline, 5); } // vi: sw=4 ts=4 noet tw=80 cc=80 @@ -17,137 +17,263 @@ #ifndef INC_HOOK_H #define INC_HOOK_H +#include "asm.h" #include "intdefs.h" #include "errmsg.h" #include "feature.h" #include "langext.h" -/* - * Replaces a vtable entry with a target function and returns the original - * function. - */ -static inline void *hook_vtable(void **vtable, usize off, void *target) { - void *orig = vtable[off]; - vtable[off] = target; - return orig; -} +#define _HOOK_STR2(x) #x +#define _HOOK_STR(x) _HOOK_STR2(x) +#define _HOOK_CAT2(a, b, c, d) a##b##c##d +#define _HOOK_CAT(a, b, c, d) _HOOK_CAT2(a, b, c, d) +#ifdef MODULE_NAME +#define _HOOK_MODNAME MODULE_NAME +#else +#define _HOOK_MODNAME G // kinda arbitrary thing for "global" +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define _HOOK_UNUSED __attribute((unused)) +#else +#define _HOOK_UNUSED +#endif + +// internal helpers, do not call directly +uchar *_hook_getpos(uchar *); +struct _hook_prep_ret { + void *hookpos; + int inslen; + const char *err; +} _hook_prep(uchar *func, uchar *trampoline); +void _hook_inline_commit(uchar *restrict hookpos, const uchar *restrict target); +void _unhook_inline(uchar *trampoline, int len); + +#define _DEF_TRAMPOLINE_ASM(symb) \ + __asm ( \ + ".pushsection " ASM_RWX_SECTION_STR ", \"" ASM_RWX_SECTION_FLAGS "\"\n" \ + ".globl " symb "\n" \ + symb ":\n" \ + ".space 24\n" \ + ".popsection\n" \ + ); + +// internal macro detail, don't use +#define _DEF_TRAMPOLINE(ftype, name, symb) \ + _DEF_TRAMPOLINE_ASM(symb) \ + typeof(*(typeof(ftype))0) name __asm(symb); /* this declares the function! */ /* - * Puts an original function back after hooking. + * Creates a callable trampoline function backed by a chunk of uninitialised rwx + * memory. Calling this on its own will crash, but the inline hooking system + * can use it to create the wrapper/trampoline function used to call an original + * function from a hook. + * + * Note that it's usually unnecessary to create these manually. Most of the + * time this is handled by DEF_TRAMPOLINE(). */ -static inline void unhook_vtable(void **vtable, usize off, void *orig) { - vtable[off] = orig; -} +#define DEF_TRAMPOLINE(ftype, name) \ + _DEF_TRAMPOLINE(ftype, name, ASM_MANGLE_STR( \ + _HOOK_STR(_HOOK_CAT(_hook_t_, _HOOK_MODNAME, _, name)))) /* - * Finds the correct function prologue location to install an inline hook, and - * tries to initialise a trampoline with sufficient instructions and a jump back - * to enable calling the original function. + * Equivalent to DEF_INLINE_HOOK(), but with a manually-specified trampoline + * function (see DEF_TRAMPOLINE() for how to create one of those). * - * This is a low-level API and in most cases, if doing hooking from inside a - * plugin feature, the hook_inline_featsetup() function should be used instead. - * It automatically performs conventional error logging for both this step and - * the hook_inline_mprot() call below, and returns error codes that are - * convenient for use in a feature INIT function. - * - * When this function succeeds, the returned struct will have the prologue - * member set to the prologue or starting point of the hooked function (which is - * not always the same as the original function pointer). The trampoline - * parameter, being a pointer-to-pointer, is an output parameter to which a - * trampoline pointer will be written. The trampoline is a small run of - * instructions from the original function, followed by a jump back to it, - * allowing the original to be seamlessly called from a hook. - * - * In practically rare cases, this function will fail due to unsupported - * instructions in the function prologue. In such instances, the returned struct - * will have a null prologue, and the second member err, will point to a - * null-terminated string for error logging. In this case, the trampoline - * pointer will remain untouched. + * It is typically unnecessary to use this and DEF_INLINE_HOOK() should be used + * instead. */ -struct hook_inline_prep_ret { - void *prologue; - const char *err; -} hook_inline_prep(void *func, void **trampoline); +#define DEF_INLINE_HOOK_WITHTRAMPOLINE(ftype, name, trampoline) \ + static schar _hook_inslen_##name; \ + static inline struct hook_prep_ret_##name { \ + void *hookpos; \ + const char *err; \ + } hook_prep_##name(typeof(ftype) func) { \ + struct _hook_prep_ret r = _hook_prep((uchar *)func, \ + (uchar *)&trampoline); \ + _hook_inslen_##name = r.inslen; \ + return (struct hook_prep_ret_##name){r.hookpos, r.err}; \ + } \ + static inline void hook_commit_##name(void *hookpos, \ + typeof(ftype) target) { \ + _hook_inline_commit((uchar *)hookpos, (const uchar *)target); \ + } \ + static inline _HOOK_UNUSED void unhook_##name() { \ + _unhook_inline((void *)&trampoline, _hook_inslen_##name); \ + } \ + static inline _HOOK_UNUSED struct hook_featsetup_ret_##name { \ + void *hookpos; \ + int err; \ + } hook_featsetup_##name(typeof(ftype) f) { \ + struct hook_prep_ret_##name ret = hook_prep_##name(f); \ + if_cold (ret.err) { \ + errmsg_warnx("couldn't hook %s function: %s", #name, ret.err); \ + return (struct hook_featsetup_ret_##name){0, FEAT_INCOMPAT}; \ + } \ + if_cold (!hook_inline_mprot(ret.hookpos)) { \ + errmsg_errorsys("couldn't hook %s function: %s", #name, \ + "couldn't make hook point writable"); \ + return (struct hook_featsetup_ret_##name){0, FEAT_FAIL}; \ + } \ + return (struct hook_featsetup_ret_##name){ret.hookpos, 0}; \ + } /* - * This is a small helper function to make the memory page containing a - * function's prologue writable, allowing an inline hook to be inserted with - * hook_inline_commit(). + * Creates a set of inline hooking functions for hooking a particular named + * function. ftype specifies a function pointer type, generally defined first as + * <name>_func as a matter of convention. * - * This is a low-level API and in most cases, if doing hooking from inside a - * plugin feature, the hook_inline_featsetup() function should be used instead. - * It automatically performs conventional error logging for both this step and - * the prior hook_inline_prep() call documented above, and returns error codes - * that are convenient for use in a feature INIT function. - * - * After using hook_inline_prep() to obtain the prologue and an appropriate - * trampoline, call this to unlock the prologue, and then use - * hook_inline_commit() to finalise the hook. In the event that multiple - * functions need to be hooked at once, the commit calls can be batched up at - * the end, removing the need for rollbacks since commitment is guaranteed to - * succeed after all setup is complete. - * - * This function returns true on success, or false if a failure occurs at the - * level of the OS memory protection API. os_lasterror() or errmsg_*sys() can be - * used to report such an error. + * Defines the following functions: + * + * static <rettype> orig_<name>(<args>); + * + * This is the trampoline function, which is dynamically generated by + * hook_prep_<name>() and allows wrapping the original function while it is + * otherwise redirected to the hook target. + * + * Calling this before the inline hook has been committed will likely crash + * or otherwise result in undefined behaviour. + * + * static inline struct hook_featsetup_ret_<name> { + * void *hookpos; + * int err; + * } hook_featsetup_<name>(ftype f); + * + * This is a higher-level helper function intended for use in plugin feature + * initialisation (see INIT in feature.h). It combines the efforts of + * hook_prep_<name>() and hook_inline_mprot() (see below), and also performs + * appropriate error logging. + * + * The err member of the returned struct is 0 on success, or a suitable + * feature return code (see FEAT_* in feature.h) on failure, so the status can + * just be returned directly from INIT if nonzero. + * + * On success, the hookpos member of the struct is ready for passing to + * hook_commit_<name>() to finish setting up the hook. + * + * Generally, in feature code, there is no reason to call hook_prep_<name>() + * directly, but it is documented below anyway. + * + * static struct hook_prep_ret_<name> { + * void *hookpos; + * const char *err; + * } hook_prep_<name>(ftype func); + * + * This finds the correct jump point to pass to hook_inline_mprot() and then + * hook_commit_<name>(), and sets up the trampoline (see orig_<name>() above). + * + * The struct field hookpos, if not null, should be passed to + * hook_commit_<name>() to finish installing the hook. + * + * If hookpos is null, something went wrong, and the null-terminated string + * err will provide a log message. + * + * static void hook_commit_<name>(void *hookpos, ftype target); + * + * This finalises an inline hook to jump to the function pointed to by target. + * hookpos should have been successfully passed to hook_inline_mprot() first, + * otherwise the code will still be read only, likely causing a crash. + * + * static void unhook_<name>() + * + * This undoes an inline hook, allowing the original code to function as it + * did before without further interception. */ -bool hook_inline_mprot(void *func); +#define DEF_INLINE_HOOK(ftype, name) \ + DEF_TRAMPOLINE(ftype, orig_##name) \ + DEF_INLINE_HOOK_WITHTRAMPOLINE(ftype, name, orig_##name) /* - * Finalises an inline hook set up using the hook_inline_prep() and - * hook_inline_mprot() functions above (or the hook_inline_featsetup() helper - * function below). prologue must be the prologue obtained via the - * aforementioned functons and target must be the function that will be jumped - * to in place of the original. It is very important that these functions are - * ABI-compatible lest obvious bad things happen. - * - * The resulting hook can be removed later by calling unhook_inline(). + * Very similar to DEF_INLINE_HOOK, except does not allow calling into the + * original function while the hook is installed. The hook_prep_<name>() and + * hook_featsetup_<name>() functions have simpler return values as a result: + * hook_prep_<name>() cannot fail, stores the hook point internally and just + * returns it as a convenience for passing to hook_mprot(); and + * hook_featsetup_<name>() only returns an error code since the hook point is + * no longer required for hook_commit_<name>(). hook_commit_<name> also lacks + * the hookpos parameter as a result. */ -void hook_inline_commit(void *restrict prologue, void *restrict target); +#define DEF_INLINE_HOOK_NOTRAMPOLINE(ftype, name) \ + static uchar *_hook_origpos_##name; \ + static uchar _hook_origbytes_##name[5]; \ + static inline void *hook_prep_##name(typeof(ftype) func) { \ + _hook_origpos_##name = _hook_getpos((uchar *)func); \ + *(int *)_hook_origbytes_##name = *(int *)_hook_origpos_##name; \ + _hook_origbytes_##name[4] = ((uchar *)_hook_origpos_##name)[4]; \ + return _hook_origpos_##name; \ + } \ + static inline void hook_commit_##name(typeof(ftype) target) { \ + _hook_inline_commit(_hook_origpos_##name, (const uchar *)target); \ + } \ + static inline _HOOK_UNUSED void unhook_##name() { \ + *(int *)_hook_origpos_##name = *(int *)_hook_origbytes_##name; \ + _hook_origpos_##name[4] = _hook_origbytes_##name[4]; \ + } \ + static inline _HOOK_UNUSED int hook_featsetup_##name(typeof(ftype) f) { \ + void *hookpos = hook_prep_##name(f); \ + if_cold (!hook_inline_mprot(hookpos)) { \ + errmsg_errorsys("couldn't hook %s function: %s", #name, \ + "couldn't make hook point writable"); \ + return FEAT_FAIL; \ + } \ + return 0; \ + } /* - * This is a helper specifically for use in feature INIT code. It doesn't make - * much sense to call it elsewhere. - * - * Combines the functionality of the hook_inline_prep() and hook_inline_mprot() - * functions above, logs to the console on error automatically in a conventional - * format, and returns an error status that can be propagated straight from a - * feature INIT function. - * - * func must point to the original function to be hooked, orig must point to - * your trampoline pointer (which can in turn be used to call the original - * function indirectly from within your hook or elsewhere), and fname should be - * the name of the function for error logging purposes. - * - * If the err member of the returned struct is nonzero, simply return it as-is. - * Otherwise, the prologue member will contain the prologue pointer to pass to - * hook_inline_commit() to finalise the hook. + * Equivalent to DEF_VTABLE_HOOK(), but with a manually specified original + * function pointer which has to have been defined already. + * + * In most cases, DEF_VTABLE_HOOK() should be used instead. This exists mainly + * for space-saving union shenanigans which are done in a very small handful of + * places (and with highly dubious necessity). */ -static inline struct hook_inline_featsetup_ret { - void *prologue; - int err; -} hook_inline_featsetup(void *func, void **orig, const char *fname) { - void *trampoline; - struct hook_inline_prep_ret prep = hook_inline_prep(func, &trampoline); - if_cold (prep.err) { - errmsg_warnx("couldn't hook %s function: %s", fname, prep.err); - return (struct hook_inline_featsetup_ret){0, FEAT_INCOMPAT}; - } - if_cold (!hook_inline_mprot(prep.prologue)) { - errmsg_errorsys("couldn't hook %s function: %s", fname, - "couldn't make prologue writable"); - return (struct hook_inline_featsetup_ret){0, FEAT_FAIL}; +#define DEF_VTABLE_HOOK_WITHORIG(ftype, name, origp) \ + static inline void hook_##name(void **vtable, ssize idx, \ + typeof(ftype) target) { \ + (origp) = (typeof(ftype))vtable[idx]; \ + vtable[idx] = (void *)(target); \ + } \ + static inline void _HOOK_UNUSED unhook_##name(void **vtable, ssize idx) { \ + vtable[idx] = (void *)(origp); \ } - *orig = trampoline; - return (struct hook_inline_featsetup_ret){prep.prologue, 0}; -} /* - * Reverts a function to its original unhooked state. Takes the pointer to the - * callable "original" function, i.e. the trampoline, NOT the initial function - * pointer from before hooking. + * Creates a set of virtual table hooking functions for hooking a particular + * named function. ftype specifies a function pointer type, generally defined + * first as <name>_func as a matter of convention. + * + * Defines the following functions: + * + * static <rettype> orig_<name>(<args>); + * + * As an implementation detail, this is really a function pointer, but exists + * to allow calling the original function. It is also used to unhook the + * function again later, so generally should not be modified/used to point to + * something else. + * + * static void hook_<name>(void **vtable, ssize idx, ftype target); + * + * Installs a virtual table hook by swapping the function at the given index. + * The virtual table must have first been made writable with os_mprot(). + * + * static void unhook_<name>(void **vtable, ssize idx); + * + * Removes a virtual table hook by swapping back the original function + * pointer. The index must be the same one that was used for hook_<name>(). + */ +#define DEF_VTABLE_HOOK(ftype, name) \ + static typeof(ftype) orig_##name; \ + DEF_VTABLE_HOOK_WITHORIG(ftype, name, orig_##name) + +/* + * This is a small helper function to make a function's hook point - found by + * hook_prep_*() - writable, allowing an inline hook to be inserted with + * hook_commit_*(). + * + * This is a low-level API and in most cases, if doing hooking from inside a + * plugin feature, the hook_inline_featsetup() function should be used instead. */ -void unhook_inline(void *orig); +bool hook_inline_mprot(void *hookpos); #endif @@ -96,8 +96,8 @@ static struct IPanel *toolspanel; static struct IScheme *scheme; typedef void (*VCALLCONV Paint_func)(struct IPanel *); -static Paint_func orig_Paint; -void VCALLCONV hook_Paint(struct IPanel *this) { +DEF_VTABLE_HOOK(Paint_func, Paint) +void VCALLCONV hookdest_Paint(struct IPanel *this) { if (this == toolspanel) { int width, height; hud_screensize(&width, &height); @@ -188,8 +188,7 @@ INIT { errmsg_errorsys("couldn't make virtual table writable"); return FEAT_FAIL; } - orig_Paint = (Paint_func)hook_vtable(toolspanel->vtable, vtidx_Paint, - (void *)&hook_Paint); + hook_Paint(toolspanel->vtable, vtidx_Paint, &hookdest_Paint); SetPaintEnabled(toolspanel, true); // 1 is the default, first loaded scheme. should always be sourcescheme.res scheme = GetIScheme(schememgr, (struct handlewrap){1}); @@ -199,7 +198,7 @@ INIT { END { // don't unhook toolspanel if exiting: it's already long gone! if_cold (sst_userunloaded) { - unhook_vtable(toolspanel->vtable, vtidx_Paint, (void *)orig_Paint); + unhook_Paint(toolspanel->vtable, vtidx_Paint); SetPaintEnabled(toolspanel, false); } } diff --git a/src/inputhud.c b/src/inputhud.c index b4b101f..fe19afc 100644 --- a/src/inputhud.c +++ b/src/inputhud.c @@ -101,10 +101,10 @@ struct CUserCmd { DECL_VFUNC_DYN(struct CInput, struct CUserCmd *, GetUserCmd, int) DECL_VFUNC_DYN(struct CInput, struct CUserCmd *, GetUserCmd_l4dbased, int, int) -typedef void (*VCALLCONV CreateMove_func)(void *, int, float, bool); -static CreateMove_func orig_CreateMove; -static void VCALLCONV hook_CreateMove(void *this, int seq, float ft, - bool active) { +typedef void (*VCALLCONV CreateMove_func)(struct CInput *, int, float, bool); +DEF_VTABLE_HOOK(CreateMove_func, CreateMove) +static void VCALLCONV hookdest_CreateMove(struct CInput *this, int seq, + float ft, bool active) { orig_CreateMove(this, seq, ft, active); struct CUserCmd *cmd = GetUserCmd(this, seq); // trick: to ensure every input (including scroll wheel) is displayed for at @@ -114,17 +114,17 @@ static void VCALLCONV hook_CreateMove(void *this, int seq, float ft, if (cmd) { heldbuttons = cmd->buttons; tappedbuttons |= cmd->buttons; } } // basically a dupe, but calling the other version of GetUserCmd -static void VCALLCONV hook_CreateMove_l4dbased(struct CInput *this, int seq, +static void VCALLCONV hookdest_CreateMove_l4dbased(struct CInput *this, int seq, float ft, bool active) { orig_CreateMove(this, seq, ft, active); struct CUserCmd *cmd = GetUserCmd_l4dbased(this, -1, seq); if (cmd) { heldbuttons = cmd->buttons; tappedbuttons |= cmd->buttons; } } -typedef void (*VCALLCONV DecodeUserCmdFromBuffer_func)(struct CInput *, - void *, int); -typedef void (*VCALLCONV DecodeUserCmdFromBuffer_l4dbased_func)(struct CInput *, - int, void *, int); +typedef void (*VCALLCONV DecodeUserCmdFromBuffer_func)( + struct CInput *, void *, int); +typedef void (*VCALLCONV DecodeUserCmdFromBuffer_l4dbased_func)( + struct CInput *, int, void *, int); static union { DecodeUserCmdFromBuffer_func prel4d; DecodeUserCmdFromBuffer_l4dbased_func l4dbased; @@ -132,14 +132,19 @@ static union { #define orig_DecodeUserCmdFromBuffer _orig_DecodeUserCmdFromBuffer.prel4d #define orig_DecodeUserCmdFromBuffer_l4dbased \ _orig_DecodeUserCmdFromBuffer.l4dbased -static void VCALLCONV hook_DecodeUserCmdFromBuffer(struct CInput *this, + +DEF_VTABLE_HOOK_WITHORIG(DecodeUserCmdFromBuffer_func, + DecodeUserCmdFromBuffer, orig_DecodeUserCmdFromBuffer) +DEF_VTABLE_HOOK_WITHORIG(DecodeUserCmdFromBuffer_l4dbased_func, + DecodeUserCmdFromBuffer_l4dbased, orig_DecodeUserCmdFromBuffer_l4dbased) +static void VCALLCONV hookdest_DecodeUserCmdFromBuffer(struct CInput *this, void *reader, int seq) { orig_DecodeUserCmdFromBuffer(this, reader, seq); struct CUserCmd *cmd = GetUserCmd(this, seq); if (cmd) { heldbuttons = cmd->buttons; tappedbuttons |= cmd->buttons; } } -static void VCALLCONV hook_DecodeUserCmdFromBuffer_l4dbased(struct CInput *this, - int slot, void *reader, int seq) { +static void VCALLCONV hookdest_DecodeUserCmdFromBuffer_l4dbased( + struct CInput *this, int slot, void *reader, int seq) { orig_DecodeUserCmdFromBuffer_l4dbased(this, slot, reader, seq); struct CUserCmd *cmd = GetUserCmd_l4dbased(this, slot, seq); if (cmd) { heldbuttons = cmd->buttons; tappedbuttons |= cmd->buttons; } @@ -417,18 +422,15 @@ INIT { return FEAT_FAIL; } if (GAMETYPE_MATCHES(L4Dbased)) { - orig_CreateMove = (CreateMove_func)hook_vtable(vtable, vtidx_CreateMove, - (void *)&hook_CreateMove_l4dbased); - orig_DecodeUserCmdFromBuffer = (DecodeUserCmdFromBuffer_func)hook_vtable( + hook_CreateMove(vtable, vtidx_CreateMove, &hookdest_CreateMove_l4dbased); + hook_DecodeUserCmdFromBuffer_l4dbased( vtable, vtidx_DecodeUserCmdFromBuffer, - (void *)&hook_DecodeUserCmdFromBuffer_l4dbased); + &hookdest_DecodeUserCmdFromBuffer_l4dbased); } else { - orig_CreateMove = (CreateMove_func)hook_vtable(vtable, vtidx_CreateMove, - (void *)&hook_CreateMove); - orig_DecodeUserCmdFromBuffer = (DecodeUserCmdFromBuffer_func)hook_vtable( - vtable, vtidx_DecodeUserCmdFromBuffer, - (void *)&hook_DecodeUserCmdFromBuffer); + hook_CreateMove(vtable, vtidx_CreateMove, &hookdest_CreateMove); + hook_DecodeUserCmdFromBuffer(vtable, vtidx_DecodeUserCmdFromBuffer, + &hookdest_DecodeUserCmdFromBuffer); } if (GAMETYPE_MATCHES(Portal1)) layout = &layout_portal1.l; @@ -460,11 +462,10 @@ INIT { } END { - unhook_vtable(input->vtable, vtidx_CreateMove, (void *)orig_CreateMove); + unhook_CreateMove(input->vtable, vtidx_CreateMove); // N.B.: since the orig_ function is in a union, we don't have to worry // about which version we're unhooking - unhook_vtable(input->vtable, vtidx_DecodeUserCmdFromBuffer, - (void *)orig_DecodeUserCmdFromBuffer); + unhook_DecodeUserCmdFromBuffer(input->vtable, vtidx_DecodeUserCmdFromBuffer); } // vi: sw=4 ts=4 noet tw=80 cc=80 fdm=marker diff --git a/src/kvsys.c b/src/kvsys.c index 25a6672..32e9906 100644 --- a/src/kvsys.c +++ b/src/kvsys.c @@ -70,8 +70,9 @@ void kvsys_free(struct KeyValues *kv) { // We can suppress this by catching the message string that's passed from // engine.dll to gameui.dll through KeyValuesSystem in vstdlib.dll and just // replacing it with some other arbitrary string that gameui won't match. -static GetStringForSymbol_func orig_GetStringForSymbol = 0; -static const char *VCALLCONV hook_GetStringForSymbol(void *this, int s) { +DEF_VTABLE_HOOK(GetStringForSymbol_func, GetStringForSymbol) +static const char *VCALLCONV hookdest_GetStringForSymbol( + struct IKeyValuesSystem *this, int s) { const char *ret = orig_GetStringForSymbol(this, s); if (!strcmp(ret, "OnClientPluginWarning")) ret = "sstBlockedThisEvent"; return ret; @@ -108,9 +109,8 @@ INIT { errmsg_note("won't be able to prevent any nag messages"); } else { - orig_GetStringForSymbol = (GetStringForSymbol_func)hook_vtable( - vtable, vtidx_GetStringForSymbol, - (void *)hook_GetStringForSymbol); + hook_GetStringForSymbol(vtable, vtidx_GetStringForSymbol, + &hookdest_GetStringForSymbol); } } return FEAT_OK; @@ -118,8 +118,7 @@ INIT { END { if (orig_GetStringForSymbol) { - unhook_vtable(kvs->vtable, vtidx_GetStringForSymbol, - (void *)orig_GetStringForSymbol); + unhook_GetStringForSymbol(kvs->vtable, vtidx_GetStringForSymbol); } } diff --git a/src/l4d1democompat.c b/src/l4d1democompat.c index 4280d53..b6a16b3 100644 --- a/src/l4d1democompat.c +++ b/src/l4d1democompat.c @@ -17,6 +17,7 @@ */ #include "accessor.h" +#include "asm.h" #include "chunklets/x86.h" #include "con_.h" #include "errmsg.h" @@ -46,127 +47,148 @@ DEF_ACCESSORS(struct CDemoFile, int, CDemoFile_protocol) // 1022+ demos on any later version of the game. typedef int (*GetHostVersion_func)(); -static GetHostVersion_func orig_GetHostVersion; +DEF_INLINE_HOOK_NOTRAMPOLINE(GetHostVersion_func, GetHostVersion) -typedef void (*VCALLCONV ReadDemoHeader_func)(void *); -static ReadDemoHeader_func orig_ReadDemoHeader; +typedef void (*VCALLCONV ReadDemoHeader_func)(struct CDemoFile *); +DEF_INLINE_HOOK(ReadDemoHeader_func, ReadDemoHeader) -static inline bool find_ReadDemoHeader(const uchar *insns) { +int _l4d1democompat_demover; // extern for asm +static int gameversion; + +static int hookdest_GetHostVersion() { + // If the demo version is 1022 or later, and not newer than the version we + // are currently using, then we spoof the game version to let the demo play. + if (_l4d1democompat_demover >= 1022 && + _l4d1democompat_demover <= gameversion) { + return _l4d1democompat_demover; + } + return gameversion; +} + +int *_l4d1democompat_thisproto; // extern for asm +static void VCALLCONV hookdest_ReadDemoHeader(struct CDemoFile *this) { + // The mid-function hook needs to get the protocol from `this`, but by that + // point we won't be able to rely on the ECX register and/or any particular + // stack spill layout. So... offset the pointer and stick it in a global. + _l4d1democompat_thisproto = getptr_CDemoFile_protocol(this); + orig_ReadDemoHeader(this); +} + +// should this be in its own .S file? meh, leaving it here for now. +__asm ( + ".pushsection " ASM_RWX_SECTION_STR ", \"" ASM_RWX_SECTION_FLAGS "\"\n" + ".globl " ASM_MANGLE_STR("_l4d1democompat_midpoint_hook") "\n" + ASM_MANGLE_STR("_l4d1democompat_midpoint_hook") ":\n" + // like in con_.S, we have to assemble stuff manually here, annoyingly. + // push eax + ".byte 0x50\n" + // mov eax, [_l4d1democompat_thisproto] + ".byte 0xA1\n" + ".long " ASM_MANGLE_STR("_l4d1democompat_thisproto") "\n" + // mov eax, [eax] + ".byte 0x8B\n" + ".byte 0x00\n" + // mov [_l4d1democompat_demover], eax + ".byte 0xA3\n" + ".long " ASM_MANGLE_STR("_l4d1democompat_demover") "\n" + // pop eax + ".byte 0x58\n" + // and then we leave trampoline space immediately after; we can jump + // straight back from here to the rest of the function! + ".globl " ASM_MANGLE_STR("_l4d1democompat_midpoint_trampoline") "\n" + ASM_MANGLE_STR("_l4d1democompat_midpoint_trampoline") ":\n" + ".space 24\n" + ".popsection\n" +); +// XXX: dummy function prototype. not to be called directly, just to pass into +// the inline hooking machinery. +// maybe one day we'll come up with a marginally cleaner way of doing these +// midpoint hooks, although there's not many uses currently so no big deal. +int _l4d1democompat_midpoint_hook(); +int _l4d1democompat_midpoint_trampoline(); +DEF_INLINE_HOOK_WITHTRAMPOLINE(int (*)(), ReadDemoHeader_midpoint, + _l4d1democompat_midpoint_trampoline) + +static inline ReadDemoHeader_func find_ReadDemoHeader(const uchar *insns) { // Find the call to ReadDemoHeader in the listdemo callback for (const uchar *p = insns; p - insns < 192;) { if (p[0] == X86_LEA && p[1] == X86_MODRM(2, 1, 4) && p[2] == 0x24 && p[7] == X86_CALL && p[12] == X86_LEA && p[13] == X86_MODRM(2, 1, 4) && p[14] == 0x24) { - orig_ReadDemoHeader = - (ReadDemoHeader_func)(p + 12 + mem_loads32(p + 8)); - return true; + return (ReadDemoHeader_func)(p + 12 + mem_loads32(p + 8)); } NEXT_INSN(p, "ReadDemoHeader"); } - return false; + return 0; } -static void *ReadDemoHeader_midpoint; - -static inline bool find_midpoint() { - uchar *insns = (uchar *)orig_ReadDemoHeader; +static inline void *find_midpoint(ReadDemoHeader_func ReadDemoHeader) { + uchar *insns = (uchar *)ReadDemoHeader; for (uchar *p = insns; p - insns < 128;) { if (p[0] == X86_PUSHIW && p[5] == X86_PUSHEBX && p[6] == X86_CALL && !memcmp(mem_loadptr(p + 1), "HL2DEMO", 7)) { - ReadDemoHeader_midpoint = (p + 11); - return true; + return p + 11; } NEXT_INSN(p, "ReadDemoHeader hook midpoint"); } - return false; + return 0; } -static inline bool find_GetHostVersion() { - uchar *insns = (uchar *)orig_ReadDemoHeader; +static inline GetHostVersion_func find_GetHostVersion( + ReadDemoHeader_func ReadDemoHeader) { + uchar *insns = (uchar *)ReadDemoHeader; int jzcnt = 0; for (uchar *p = insns; p - insns < 192;) { // GetHostVersion() is called right after the third JZ insn in // ReadDemoHeader() if (p[0] == X86_JZ && ++jzcnt == 3) { - orig_GetHostVersion = - (GetHostVersion_func)(p + 7 + mem_loads32(p + 3)); - return true; + return (GetHostVersion_func)(p + 7 + mem_loads32(p + 3)); } NEXT_INSN(p, "GetHostVersion"); } - return false; -} - -static int demoversion, gameversion; - -static int hook_GetHostVersion() { - // If the demo version is 1022 or later, and not newer than the version we - // are currently using, then we spoof the game version to let the demo play. - if (demoversion >= 1022 && demoversion <= gameversion) return demoversion; - return gameversion; -} - -static int *this_protocol; -static void VCALLCONV hook_ReadDemoHeader(struct CDemoFile *this) { - // The mid-function hook needs to get the protocol from `this`, but by that - // point we won't be able to rely on the ECX register and/or any particular - // stack spill layout. So... offset the pointer and stick it in a global. - this_protocol = getptr_CDemoFile_protocol(this); - orig_ReadDemoHeader(this); -} - -static asm_only int hook_midpoint() { - __asm volatile ( - "push eax\n" - "mov eax, %1\n" - "mov eax, [eax]\n" // dereference this_protocol - "mov %0, eax\n" // store in demoversion - "pop eax\n" - "jmp dword ptr %2\n" - : "=m" (demoversion) - : "m" (this_protocol), "m" (ReadDemoHeader_midpoint) - ); + return 0; } INIT { struct con_cmd *cmd_listdemo = con_findcmd("listdemo"); if_cold (!cmd_listdemo) return FEAT_INCOMPAT; // should never happen! - if_cold (!find_ReadDemoHeader(cmd_listdemo->cb_insns)) { + ReadDemoHeader_func ReadDemoHeader = find_ReadDemoHeader( + cmd_listdemo->cb_insns); + if_cold (!ReadDemoHeader) { errmsg_errorx("couldn't find ReadDemoHeader function"); return FEAT_INCOMPAT; } - if_cold (!find_midpoint()) { + void *midpoint = find_midpoint(ReadDemoHeader); + if_cold (!midpoint) { errmsg_errorx("couldn't find mid-point for ReadDemoHeader hook"); return FEAT_INCOMPAT; } - if_cold (!find_GetHostVersion()) { + GetHostVersion_func GetHostVersion = find_GetHostVersion(ReadDemoHeader); + if_cold (!GetHostVersion) { errmsg_errorx("couldn't find GetHostVersion function"); return FEAT_INCOMPAT; } - gameversion = orig_GetHostVersion(); - struct hook_inline_featsetup_ret h1 = hook_inline_featsetup( - (void *)orig_GetHostVersion, (void **)&orig_GetHostVersion, - "GetHostVersion"); - if_cold (h1.err) return h1.err; - struct hook_inline_featsetup_ret h2 = hook_inline_featsetup( - (void *)orig_ReadDemoHeader, (void **)&orig_ReadDemoHeader, - "ReadDemoHeader"); + gameversion = GetHostVersion(); + int err = hook_featsetup_GetHostVersion(GetHostVersion); + if_cold (err) return err; + struct hook_featsetup_ret_ReadDemoHeader h2 = + hook_featsetup_ReadDemoHeader(ReadDemoHeader); if_cold (h2.err) return h2.err; - struct hook_inline_featsetup_ret h3 = hook_inline_featsetup( - ReadDemoHeader_midpoint, &ReadDemoHeader_midpoint, - "ReadDemoHeader midpoint"); + struct hook_featsetup_ret_ReadDemoHeader_midpoint h3 = + hook_featsetup_ReadDemoHeader_midpoint((int (*)())midpoint); if_cold (h3.err) return h3.err; - hook_inline_commit(h1.prologue, (void *)&hook_GetHostVersion); - hook_inline_commit(h2.prologue, (void *)&hook_ReadDemoHeader); - hook_inline_commit(h3.prologue, (void *)&hook_midpoint); + hook_commit_GetHostVersion(&hookdest_GetHostVersion); + hook_commit_ReadDemoHeader(h2.hookpos, &hookdest_ReadDemoHeader); + hook_commit_ReadDemoHeader_midpoint(h3.hookpos, + &_l4d1democompat_midpoint_hook); return FEAT_OK; } END { if_cold (sst_userunloaded) { - unhook_inline((void *)ReadDemoHeader_midpoint); - unhook_inline((void *)orig_ReadDemoHeader); - unhook_inline((void *)orig_GetHostVersion); + unhook_ReadDemoHeader_midpoint(); + unhook_ReadDemoHeader(); + unhook_GetHostVersion(); } } diff --git a/src/l4daddon.c b/src/l4daddon.c index 5336199..79c5e72 100644 --- a/src/l4daddon.c +++ b/src/l4daddon.c @@ -61,9 +61,9 @@ DECL_VFUNC_DYN(struct VEngineClient, void, ManageAddonsForActiveSession) // don't have to worry about that since it's cdecl (and we don't use it // ourselves, just pass it straight through). typedef void (*FS_MAFAS_func)(bool, char *, char *, bool); -static FS_MAFAS_func orig_FS_MAFAS; -static void hook_FS_MAFAS(bool disallowaddons, char *mission, char *gamemode, - bool ismutation) { +DEF_INLINE_HOOK(FS_MAFAS_func, FS_MAFAS) +static void hookdest_FS_MAFAS(bool disallowaddons, char *mission, + char *gamemode, bool ismutation) { // At the start of a map, particularly in 2204+ in campaigns with L4D1 // commons, there can be a ton of hitches due to the game trying to // load uncached materials as models are drawn. This FS_MAFAS function is @@ -139,21 +139,20 @@ static void hook_FS_MAFAS(bool disallowaddons, char *mission, char *gamemode, orig_FS_MAFAS(disallowaddons, mission, gamemode, ismutation); } -static inline bool find_FS_MAFAS() { +static inline FS_MAFAS_func find_FS_MAFAS() { #ifdef _WIN32 const uchar *insns = engclient->vtable[vtidx_ManageAddonsForActiveSession]; // CEngineClient::ManageAddonsForActiveSession just calls FS_MAFAS for (const uchar *p = insns; p - insns < 32;) { if (p[0] == X86_CALL) { - orig_FS_MAFAS = (FS_MAFAS_func)(p + 5 + mem_loads32(p + 1)); - return true; + return (FS_MAFAS_func)(p + 5 + mem_loads32(p + 1)); } NEXT_INSN(p, "FileSystem_ManageAddonsForActiveSession function"); } #else #warning: TODO(linux): asm search stuff #endif - return false; + return 0; } static inline bool find_addonvecsz(const uchar *insns) { @@ -176,8 +175,7 @@ static inline bool find_addonvecsz(const uchar *insns) { static void *broken_addon_check = 0; static uchar orig_broken_addon_check_bytes[13]; -static inline void try_fix_broken_addon_check() { - uchar *insns = (uchar *)orig_FS_MAFAS; +static inline void try_fix_broken_addon_check(uchar *insns) { for (uchar *p = insns; p - insns < 32;) { if (p[0] == X86_ALUMI8S && p[1] == X86_MODRM(0, 7, 5) && mem_loadptr(p + 2) == addonvecsz) { @@ -227,23 +225,22 @@ INIT { errmsg_errorx("couldn't find pointer to addon list"); return FEAT_INCOMPAT; } - if_cold (!find_FS_MAFAS()) { + FS_MAFAS_func FS_MAFAS = find_FS_MAFAS(); + if_cold (!FS_MAFAS) { errmsg_errorx("couldn't find FileSystem_ManageAddonsForActiveSession"); return FEAT_INCOMPAT; } - try_fix_broken_addon_check(); - struct hook_inline_featsetup_ret h = hook_inline_featsetup( - (void *)orig_FS_MAFAS, (void **)&orig_FS_MAFAS, - "FileSystem_ManageAddonsForActiveSession"); + try_fix_broken_addon_check((uchar *)FS_MAFAS); + struct hook_featsetup_ret_FS_MAFAS h = hook_featsetup_FS_MAFAS(FS_MAFAS); if_cold (h.err) return h.err; - hook_inline_commit(h.prologue, (void *)&hook_FS_MAFAS); + hook_commit_FS_MAFAS(h.hookpos, &hookdest_FS_MAFAS); return FEAT_OK; } END { // TODO(opt): can this unhook be made conditional too? bill suggested it // before but I don't know. maybe Hayden knows - mike - unhook_inline((void *)orig_FS_MAFAS); + unhook_FS_MAFAS(); if_cold (sst_userunloaded) { if (broken_addon_check) { memcpy(broken_addon_check, orig_broken_addon_check_bytes, 13); diff --git a/src/l4dreset.c b/src/l4dreset.c index dd59c60..25d3d10 100644 --- a/src/l4dreset.c +++ b/src/l4dreset.c @@ -235,10 +235,7 @@ HANDLE_EVENT(Tick, bool simulating) { } } -typedef void (*VCALLCONV OnGameplayStart_func)(struct CDirector *this); -static OnGameplayStart_func orig_OnGameplayStart; -static void VCALLCONV hook_OnGameplayStart(struct CDirector *this) { - orig_OnGameplayStart(this); +static void onstart() { if (nextmapnum) { // if we changed map more than 1 time, cancel the reset. this'll happen // if someone prematurely disconnects and then starts a new session. @@ -247,11 +244,21 @@ static void VCALLCONV hook_OnGameplayStart(struct CDirector *this) { } nextmapnum = 0; // resume countdown if there is one! otherwise do nothing. } -// Simply reuse the above for L4D1, since the calling ABI is the exact same! -#define UnfreezeTeam_func OnGameplayStart_func -#define UnfreezeTeam OnGameplayStart -#define orig_UnfreezeTeam orig_OnGameplayStart -#define hook_UnfreezeTeam hook_OnGameplayStart + +typedef void (*VCALLCONV OnGameplayStart_func)(struct CDirector *this); +DEF_VTABLE_HOOK(OnGameplayStart_func, OnGameplayStart) +static void VCALLCONV hookdest_OnGameplayStart(struct CDirector *this) { + orig_OnGameplayStart(this); + onstart(); +} +typedef void (*VCALLCONV UnfreezeTeam_func)(struct CDirector *this); +DEF_INLINE_HOOK(UnfreezeTeam_func, UnfreezeTeam) +// UnfreezeTeam is pretty much identical to OnGameplayStart except for using +// a direct orig_ call (static trampoline) rather than a function pointer. +static void VCALLCONV hookdest_UnfreezeTeam(struct CDirector *this) { + orig_UnfreezeTeam(this); + onstart(); +} static int getffidx(const char *campaign) { if (GAMETYPE_MATCHES(L4D1)) { @@ -471,7 +478,9 @@ static inline bool find_TheDirector(void *GameShutdown) { } #endif -static inline bool find_UnfreezeTeam(void *GameFrame) { // note: L4D1 only +// note: this function is for L4D1 only +static inline UnfreezeTeam_func find_UnfreezeTeam(void *GameFrame) { +#ifdef _WIN32 // CServerGameDLL::GameFrame() loads TheDirector into ECX and then calls // Director::Update() const uchar *insns = (const uchar *)GameFrame, *p = insns; @@ -485,20 +494,19 @@ static inline bool find_UnfreezeTeam(void *GameFrame) { // note: L4D1 only } NEXT_INSN(p, "Director::Update call"); } - return false; + return 0; ok: // Director::Update calls UnfreezeTeam after the first jmp instruction while (p - insns < 96) { // jz XXX; mov ecx, <reg>; call Director::UnfreezeTeam if (p[0] == X86_JZ && p[2] == X86_MOVRMW && (p[3] & 0xF8) == 0xC8 && p[4] == X86_CALL) { - p += 9 + mem_loads32(p + 5); - orig_UnfreezeTeam = (UnfreezeTeam_func)p; - return true; + return (UnfreezeTeam_func)(p + 9 + mem_loads32(p + 5)); } NEXT_INSN(p, "Director::UnfreezeTeam call"); } - return false; - } +#endif + return 0; +} // XXX: duped def in democustom: should this belong somewhere else? DECL_VFUNC_DYN(struct VEngineClient, int, GetEngineBuildNumber) @@ -529,32 +537,28 @@ INIT { #warning TODO(linux): should be able to just dlsym(server, "TheDirector") return FEAT_INCOMPAT; #endif -#ifdef _WIN32 // L4D1 has no Linux build, no need to check whether L4D2 if (GAMETYPE_MATCHES(L4D2)) { -#endif vtable = director->vtable; if_cold (!os_mprot(vtable + vtidx_OnGameplayStart, sizeof(*vtable), PAGE_READWRITE)) { errmsg_errorsys("couldn't make virtual table writable"); return FEAT_FAIL; } - orig_OnGameplayStart = (OnGameplayStart_func)hook_vtable(vtable, - vtidx_OnGameplayStart, (void *)&hook_OnGameplayStart); -#ifdef _WIN32 // L4D1 has no Linux build! + hook_OnGameplayStart(vtable, vtidx_OnGameplayStart, + &hookdest_OnGameplayStart); } else /* L4D1 */ { void *GameFrame = srvdll->vtable[vtidx_GameFrame]; - if_cold (!find_UnfreezeTeam(GameFrame)) { + UnfreezeTeam_func UnfreezeTeam = find_UnfreezeTeam(GameFrame); + if_cold (!UnfreezeTeam) { errmsg_errorx("couldn't find UnfreezeTeam function"); return FEAT_INCOMPAT; } - struct hook_inline_featsetup_ret h = hook_inline_featsetup( - (void *)orig_UnfreezeTeam, (void **)&orig_UnfreezeTeam, - "UnfreezeTeam"); + struct hook_featsetup_ret_UnfreezeTeam h = + hook_featsetup_UnfreezeTeam( UnfreezeTeam); if_cold (h.err) return h.err; - hook_inline_commit(h.prologue, (void *)&hook_UnfreezeTeam); + hook_commit_UnfreezeTeam(h.hookpos, &hookdest_UnfreezeTeam); } -#endif // Only try cooldown stuff for L4D2, since L4D1 always had unlimited votes. if (GAMETYPE_MATCHES(L4D2)) { // g_voteController is invalid if not running a server so get the @@ -589,11 +593,10 @@ nocd: errmsg_note("resetting a first map will not clear vote cooldowns"); END { if (GAMETYPE_MATCHES(L4D2)) { - unhook_vtable(mem_loadptr(director), vtidx_OnGameplayStart, - (void *)orig_OnGameplayStart); + unhook_OnGameplayStart(mem_loadptr(director), vtidx_OnGameplayStart); } else { - unhook_inline((void *)orig_UnfreezeTeam); + unhook_UnfreezeTeam(); } } diff --git a/src/nosleep.c b/src/nosleep.c index 3ac4069..a170781 100644 --- a/src/nosleep.c +++ b/src/nosleep.c @@ -36,8 +36,8 @@ DEF_CVAR_UNREG(engine_no_focus_sleep, static void **vtable; typedef void (*VCALLCONV SleepUntilInput_func)(void *this, int timeout); -static SleepUntilInput_func orig_SleepUntilInput; -static void VCALLCONV hook_SleepUntilInput(void *this, int timeout) { +DEF_VTABLE_HOOK(SleepUntilInput_func, SleepUntilInput) +static void VCALLCONV hookdest_SleepUntilInput(void *this, int timeout) { orig_SleepUntilInput(this, con_getvari(engine_no_focus_sleep)); } @@ -54,14 +54,14 @@ INIT { errmsg_errorx("couldn't make virtual table writable"); return FEAT_FAIL; } - orig_SleepUntilInput = (SleepUntilInput_func)hook_vtable(vtable, - vtidx_SleepUntilInput, (void *)&hook_SleepUntilInput); + hook_SleepUntilInput(vtable, vtidx_SleepUntilInput, + &hookdest_SleepUntilInput); con_unhide(&engine_no_focus_sleep->base); return FEAT_OK; } END { - unhook_vtable(vtable, vtidx_SleepUntilInput, (void *)orig_SleepUntilInput); + unhook_SleepUntilInput(vtable, vtidx_SleepUntilInput); } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/portalcolours.c b/src/portalcolours.c index f04a9a8..ff9ab46 100644 --- a/src/portalcolours.c +++ b/src/portalcolours.c @@ -28,7 +28,6 @@ #include "mem.h" #include "ppmagic.h" #include "sst.h" -#include "vcall.h" FEATURE("portal gun colour customisation") GAMESPECIFIC(Portal1) @@ -61,12 +60,13 @@ static void colourcb(struct con_var *v) { // Original sig is the following but we wanna avoid calling convention weirdness //typedef struct rgba (*UTIL_Portal_Color_func)(int); typedef void (*UTIL_Portal_Color_func)(struct rgba *out, int portal); -static UTIL_Portal_Color_func orig_UTIL_Portal_Color; -static void hook_UTIL_Portal_Color(struct rgba *out, int portal) { +static void hookdest_UTIL_Portal_Color(struct rgba *out, int portal) { if (portal < 0 || portal > 2) *out = (struct rgba){255, 255, 255, 255}; else *out = colours[portal]; } +DEF_INLINE_HOOK_NOTRAMPOLINE(UTIL_Portal_Color_func, UTIL_Portal_Color) + // TODO(compat): would like to do the usual pointer-chasing business instead of // using hardcoded offsets, but that's pretty hard here. Would probably have to // do the entprops stuff for ClientClass, get at the portalgun factory, get a @@ -76,38 +76,37 @@ static void hook_UTIL_Portal_Color(struct rgba *out, int portal) { // // For now we do this! -static bool find_UTIL_Portal_Color(void *base) { +static UTIL_Portal_Color_func find_UTIL_Portal_Color(void *base) { static const uchar x[] = HEXBYTES(8B, 44, 24, 08, 83, E8, 00, 74, 37, 83, E8, 01, B1, FF, 74, 1E, 83, E8, 01, 8B, 44, 24, 04, 88); // 5135 - orig_UTIL_Portal_Color = (UTIL_Portal_Color_func)mem_offset(base, 0x1BF090); - if (!memcmp((void *)orig_UTIL_Portal_Color, x, sizeof(x))) return true; + void *f = mem_offset(base, 0x1BF090); + if (!memcmp(f, x, sizeof(x))) return (UTIL_Portal_Color_func)f; // 4104 - orig_UTIL_Portal_Color = (UTIL_Portal_Color_func)mem_offset(base, 0x1ADC30); - if (!memcmp((void *)orig_UTIL_Portal_Color, x, sizeof(x))) return true; + f = mem_offset(base, 0x1ADC30); + if (!memcmp(f, x, sizeof(x))) return (UTIL_Portal_Color_func)f; // 3420 - orig_UTIL_Portal_Color = (UTIL_Portal_Color_func)mem_offset(base, 0x1AA810); - if (!memcmp((void *)orig_UTIL_Portal_Color, x, sizeof(x))) return true; + f = mem_offset(base, 0x1AA810); + if (!memcmp(f, x, sizeof(x))) return (UTIL_Portal_Color_func)f; // SteamPipe (7197370) - almost sure to break in a later update! // TODO(compat): this has indeed been broken for ages. static const uchar y[] = HEXBYTES(55, 8B, EC, 8B, 45, 0C, 83, E8, 00, 74, 24, 48, 74, 16, 48, 8B, 45, 08, 74, 08, C7, 00, FF, FF); - orig_UTIL_Portal_Color = (UTIL_Portal_Color_func)mem_offset(base, 0x234C00); - if (!memcmp((void *)orig_UTIL_Portal_Color, y, sizeof(y))) return true; - return false; + f = mem_offset(base, 0x234C00); + if (!memcmp(f, y, sizeof(y))) return (UTIL_Portal_Color_func)f; + return 0; } INIT { #ifdef _WIN32 - if_cold (!find_UTIL_Portal_Color(clientlib)) { + UTIL_Portal_Color_func UTIL_Portal_Color = find_UTIL_Portal_Color(clientlib); + if_cold (!UTIL_Portal_Color) { errmsg_errorx("couldn't find UTIL_Portal_Color"); return FEAT_INCOMPAT; } - struct hook_inline_featsetup_ret h = hook_inline_featsetup( - (void *)orig_UTIL_Portal_Color, (void **)&orig_UTIL_Portal_Color, - "UTIL_Portal_Color"); - if_cold (h.err) return h.err; - hook_inline_commit(h.prologue, (void *)&hook_UTIL_Portal_Color); + int err = hook_featsetup_UTIL_Portal_Color(UTIL_Portal_Color); + if_cold (err) return err; + hook_commit_UTIL_Portal_Color(&hookdest_UTIL_Portal_Color); sst_portal_colour0->cb = &colourcb; sst_portal_colour1->cb = &colourcb; sst_portal_colour2->cb = &colourcb; @@ -120,7 +119,7 @@ INIT { END { if_hot (!sst_userunloaded) return; - unhook_inline((void *)orig_UTIL_Portal_Color); + unhook_UTIL_Portal_Color(); } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/rinput.c b/src/rinput.c index a7ed1a4..8d3a6d9 100644 --- a/src/rinput.c +++ b/src/rinput.c @@ -91,27 +91,24 @@ static ssize __stdcall inproc(void *wnd, uint msg, usize wp, ssize lp) { typedef int (*__stdcall GetCursorPos_func)(POINT *p); typedef uint (*VCALLCONV GetRawMouseAccumulators_func)(void *, int *, int *); -static union { // more cheeky space saving - GetCursorPos_func orig_GetCursorPos; - GetRawMouseAccumulators_func orig_GetRawMouseAccumulators; -} u2; -#define orig_GetCursorPos u2.orig_GetCursorPos -#define orig_GetRawMouseAccumulators u2.orig_GetRawMouseAccumulators - -static int __stdcall hook_GetCursorPos(POINT *p) { +DEF_INLINE_HOOK(GetCursorPos_func, GetCursorPos) +DEF_VTABLE_HOOK(GetRawMouseAccumulators_func, GetRawMouseAccumulators) + +static int __stdcall hookdest_GetCursorPos(POINT *p) { if (!con_getvari(m_rawinput)) return orig_GetCursorPos(p); p->x = cx; p->y = cy; return 1; } typedef int (*__stdcall SetCursorPos_func)(int x, int y); -static SetCursorPos_func orig_SetCursorPos = 0; -static int __stdcall hook_SetCursorPos(int x, int y) { +DEF_INLINE_HOOK(SetCursorPos_func, SetCursorPos) +static int __stdcall hookdest_SetCursorPos(int x, int y) { cx = x; cy = y; return orig_SetCursorPos(x, y); } -static uint VCALLCONV hook_GetRawMouseAccumulators(void *this, int *x, int *y) { +static uint VCALLCONV hookdest_GetRawMouseAccumulators(void *this, + int *x, int *y) { int dx, dy; uint ret = orig_GetRawMouseAccumulators(this, &dx, &dy); int d = con_getvari(sst_mouse_factor); @@ -135,9 +132,8 @@ INIT { errmsg_errorx("couldn't make virtual table writable"); return FEAT_FAIL; } - orig_GetRawMouseAccumulators = (GetRawMouseAccumulators_func)hook_vtable( - vtable_insys, vtidx_GetRawMouseAccumulators, - (void *)&hook_GetRawMouseAccumulators); + hook_GetRawMouseAccumulators(vtable_insys, vtidx_GetRawMouseAccumulators, + &hookdest_GetRawMouseAccumulators); } else { // create cvar hidden so config is still preserved if we fail to init @@ -181,11 +177,11 @@ INIT { } int err; - struct hook_inline_featsetup_ret h1 = hook_inline_featsetup( - (void *)GetCursorPos, (void **)&orig_GetCursorPos, "GetCursorPos"); + struct hook_featsetup_ret_GetCursorPos h1 = + hook_featsetup_GetCursorPos(&GetCursorPos); if_cold (err = h1.err) goto e0; - struct hook_inline_featsetup_ret h2 = hook_inline_featsetup( - (void *)SetCursorPos, (void **)&orig_SetCursorPos, "SetCursorPos"); + struct hook_featsetup_ret_SetCursorPos h2 = + hook_featsetup_SetCursorPos(&SetCursorPos); if_cold (err = h2.err) goto e0; inwin = CreateWindowExW(0, L"RInput", L"RInput", 0, 0, 0, 0, 0, 0, 0, 0, 0); if_cold (!inwin) { @@ -202,8 +198,8 @@ INIT { err = FEAT_FAIL; goto e1; } - hook_inline_commit(h1.prologue, (void *)&hook_GetCursorPos); - hook_inline_commit(h2.prologue, (void *)&hook_SetCursorPos); + hook_commit_GetCursorPos(h1.hookpos, &hookdest_GetCursorPos); + hook_commit_SetCursorPos(h2.hookpos, &hookdest_SetCursorPos); ok: // XXX: this is a little tricky and a little clunky. we have registered // m_rawinput above but sst_mouse_factor will get auto-registered after init @@ -218,7 +214,7 @@ e0: UnregisterClassW(L"RInput", 0); } END { - if_hot (orig_SetCursorPos) { // we inited our own implementation + if_hot (inwin) { // we inited our own implementation RAWINPUTDEVICE rd = { .dwFlags = RIDEV_REMOVE, .hwndTarget = 0, @@ -229,13 +225,13 @@ END { DestroyWindow(inwin); if_hot (!sst_userunloaded) return; UnregisterClassW(L"RInput", 0); - unhook_inline((void *)orig_GetCursorPos); - unhook_inline((void *)orig_SetCursorPos); + unhook_SetCursorPos(); + unhook_GetCursorPos(); } else if_cold (sst_userunloaded) { // we must have hooked the *existing* implementation - unhook_vtable(vtable_insys, vtidx_GetRawMouseAccumulators, - (void *)orig_GetRawMouseAccumulators); + unhook_GetRawMouseAccumulators(vtable_insys, + vtidx_GetRawMouseAccumulators); } } @@ -367,12 +367,12 @@ if (GAMETYPE_MATCHES(x)) { \ } typedef void (*VCALLCONV VGuiConnect_func)(struct CEngineVGui *this); -static VGuiConnect_func orig_VGuiConnect; -static void VCALLCONV hook_VGuiConnect(struct CEngineVGui *this) { +DEF_VTABLE_HOOK(VGuiConnect_func, VGuiConnect) +static void VCALLCONV hookdest_VGuiConnect(struct CEngineVGui *this) { orig_VGuiConnect(this); do_featureinit(); fixes_apply(); - unhook_vtable(vgui->vtable, vtidx_VGuiConnect, (void *)orig_VGuiConnect); + unhook_VGuiConnect(vgui->vtable, vtidx_VGuiConnect); } DECL_VFUNC_DYN(struct CEngineVGui, bool, VGuiIsInitialized) @@ -413,8 +413,7 @@ static bool deferinit() { "feature setup"); goto e; } - orig_VGuiConnect = (VGuiConnect_func)hook_vtable(vgui->vtable, - vtidx_VGuiConnect, (void *)&hook_VGuiConnect); + hook_VGuiConnect(vgui->vtable, vtidx_VGuiConnect, &hookdest_VGuiConnect); return true; e: con_warn("!!! SOME FEATURES MAY BE BROKEN !!!\n"); |
