summaryrefslogtreecommitdiff
path: root/src/l4d1democompat.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/l4d1democompat.c')
-rw-r--r--src/l4d1democompat.c166
1 files changed, 94 insertions, 72 deletions
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();
}
}