summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/ac.c37
-rw-r--r--src/autojump.c23
-rw-r--r--src/demorec.c19
-rw-r--r--src/fastfwd.c33
-rw-r--r--src/fov.c26
-rw-r--r--src/hook.S26
-rw-r--r--src/hook.c74
-rw-r--r--src/hook.h336
-rw-r--r--src/hud.c9
-rw-r--r--src/inputhud.c49
-rw-r--r--src/kvsys.c13
-rw-r--r--src/l4d1democompat.c166
-rw-r--r--src/l4daddon.c29
-rw-r--r--src/l4dreset.c63
-rw-r--r--src/nosleep.c10
-rw-r--r--src/portalcolours.c39
-rw-r--r--src/rinput.c46
-rw-r--r--src/sst.c9
18 files changed, 553 insertions, 454 deletions
diff --git a/src/ac.c b/src/ac.c
index e762083..f5a2de5 100644
--- a/src/ac.c
+++ b/src/ac.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
diff --git a/src/fov.c b/src/fov.c
index b7a278c..07e9367 100644
--- a/src/fov.c
+++ b/src/fov.c
@@ -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
diff --git a/src/hook.c b/src/hook.c
index baaa710..845d8b1 100644
--- a/src/hook.c
+++ b/src/hook.c
@@ -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
diff --git a/src/hook.h b/src/hook.h
index 26796e2..e4be554 100644
--- a/src/hook.h
+++ b/src/hook.h
@@ -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
diff --git a/src/hud.c b/src/hud.c
index ef86c4d..87f40fc 100644
--- a/src/hud.c
+++ b/src/hud.c
@@ -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);
}
}
diff --git a/src/sst.c b/src/sst.c
index 64dd303..070ce12 100644
--- a/src/sst.c
+++ b/src/sst.c
@@ -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");