diff options
| -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"); |
