From a2f7e37d8adf2047e1f3b0ea1227ac9d51514783 Mon Sep 17 00:00:00 2001 From: Matthew Wozniak Date: Fri, 1 Nov 2024 13:47:31 -0400 Subject: play demo using the demoplayer object --- .gitignore | 1 - Makefile | 9 ++++++--- api.c | 44 +++++++++++++++++++++++++++++++++++++++----- api.h | 41 ++++++++++++++++++++++++++++++++++++++++- compile_flags.txt | 8 ++++++++ hook.c | 5 +++-- log.h | 9 ++++++++- main.c | 44 ++++++++++++++++++++++++++++++++++---------- os.h | 1 + 9 files changed, 139 insertions(+), 23 deletions(-) create mode 100644 compile_flags.txt diff --git a/.gitignore b/.gitignore index 165f4d7..03c125a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -compile_flags.txt *.o *.pdb *.exe diff --git a/Makefile b/Makefile index b03699c..61e4b7d 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,16 @@ CC = clang OBJS = main.o api.o 3p/sst/x86.o hook.o -WARNINGS=-Wpedantic -Wno-gnu-zero-variadic-macro-arguments # clang is stupid! +WARNINGS=-Wpedantic -Wextra -Wno-gnu-zero-variadic-macro-arguments \ + -D_CRT_SECURE_NO_WARNINGS all: rt.exe rt.exe: $(OBJS) - $(CC) -m32 -fuse-ld=lld $(LDFLAGS) $(OBJS) -o rt.exe + $(CC) -m32 -fuse-ld=lld $(CFLAGS) $(LDFLAGS) $(OBJS) -o rt.exe %.o: %.c - $(CC) -c -m32 -include stdbool.h -std=c23 $(WARNINGS) $(CFLAGS) $< -o $@ + $(CC) -c -m32 -include stdbool.h -I3p -std=c23 $(WARNINGS) $(CFLAGS) $< -o $@ clean: rm -f *.o 3p/sst/*.o *.exe *.pdb + +rebuild: clean all diff --git a/api.c b/api.c index af7bb5e..8001f8f 100644 --- a/api.c +++ b/api.c @@ -6,16 +6,50 @@ #undef NO_EXTERNS #include "os.h" #include "log.h" +#include "sst/x86.h" -struct engserver *engserver; +struct engserver *engserver = NULL; +struct engclient *engclient = NULL; +struct demoplayer *demoplayer = NULL; +void (*cbuf_addtext)(char *) = NULL; -void api_init() { - void *engine_dll = os_dlopen("engine"); +bool api_init(void) { + void *engine_dll = os_dlhandle("engine"); createinterface_func engine_factory = (createinterface_func)os_dlsym(engine_dll, "CreateInterface"); - if (!engine_factory) die("couldn't get engine factory"); + if (!engine_factory) bail("couldn't get engine factory"); engserver = engine_factory(INTERFACEVERSION_VENGINESERVER, NULL); - if (!engserver) die("couldn't get IVEngineServer from engine"); + if (!engserver) bail("couldn't get IVEngineServer from engine"); + engclient = engine_factory(VENGINE_CLIENT_INTERFACE_VERSION, NULL); + if (!engclient) bail("couldn't get IVEngineClient from engine"); + + // find cbuf_addtext + const u8 *instr = (const u8 *)engserver->vt->server_command; + // ServerCommand() calls a few small functions before Cbuf_AddText but they + // get inlined. look for 'push esi' and then a call. + for (const u8 *p = instr; p - instr < 64;) { + if (*p == X86_PUSHESI && *++p == X86_CALL) { + // jump is relative to after the instruction + cbuf_addtext = (void (*)(char *))(p + 5 + *(i32 *)(p + 1)); + } + int l = x86_len(p); + if (l == -1) + bail("invalid instruction looking for cbuf_addtext"); + p += l; + } + if (!cbuf_addtext) bail("couldn't find cbuf_addtext"); + + // find demoplayer + instr = (const u8 *)engclient->vt->is_playing_demo; + debug("is_playing_demo = %p", (void *)instr); + // CEngineClient::IsPlayingDemo is a wrapper around a demoplayer call + // The first thing it does should be load demoplayer into ECX + if (instr[0] != X86_MOVRMW || instr[1] != X86_MODRM(0, 1, 5)) + bail("couldn't get demoplayer"); + demoplayer = **(struct demoplayer ***)(instr + 2); + debug("demoplayer = %p", (void *)demoplayer); + + return true; } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/api.h b/api.h index 1a841f5..086ef46 100644 --- a/api.h +++ b/api.h @@ -5,6 +5,7 @@ #define ENGINEAPI_H #define INTERFACEVERSION_VENGINESERVER "VEngineServer021" +#define VENGINE_CLIENT_INTERFACE_VERSION "VEngineClient013" #include "intdef.h" @@ -17,11 +18,49 @@ struct engserver { } *vt; }; +struct engclient { + struct { + usize _pad[75]; + void (*__thiscall is_playing_demo)(struct engclient *this); + } *vt; +}; + +struct demoplayer { + struct { + void *_destructor; + void * (*__thiscall get_demo_file)(struct demoplayer *this); + int (*__thiscall get_playback_tick)(struct demoplayer *this); + int (*__thiscall get_total_ticks)(struct demoplayer *this); + void *_whatisthisihavenoidea; + bool (*__thiscall start_playback)(struct demoplayer *this, + const char *filename, bool as_time_demo); + bool (*__thiscall is_playing_back)(struct demoplayer *this); + bool (*__thiscall is_playback_paused)(struct demoplayer *this); + bool (*__thiscall is_playing_time_demo)(struct demoplayer *this); + bool (*__thiscall is_skipping)(struct demoplayer *this); + bool (*__thiscall can_skip_backwards)(struct demoplayer *this); + void (*__thiscall set_playback_time_scale)(struct demoplayer *this, + float timescale); + float (*__thiscall get_playback_time_scale)(struct demoplayer *this); + void (*__thiscall pause_playback)(struct demoplayer *this, + float seconds); + void (*__thiscall skip_to_tick)(struct demoplayer *this, int tick, + bool relative, bool pause); + void (*__thiscall resume_playback)(struct demoplayer *this); + void (*__thiscall stop_playback)(struct demoplayer *this); + void (*__thiscall interpolate_viewpoint)(struct demoplayer *this); + } *vt; +}; + #ifndef NO_EXTERNS extern struct engserver *engserver; +extern struct engclient *engclient; +extern struct demoplayer *demoplayer; +extern void (*cbuf_addtext)(char *); #endif -void api_init(void); +// initializes required engine apis. returns false on error. +bool api_init(void); #endif diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..7745765 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,8 @@ +-target +i686-pc-windows-msvc +-include +stdbool.h +-I3p +-Wpedantic +-Wextra +-Wno-gnu-zero-variadic-macro-arguments diff --git a/hook.c b/hook.c index 8d7f409..fe8c555 100644 --- a/hook.c +++ b/hook.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: ISC // SPDX-FileCopyrightText: 2024 Michael Smith -// SPDX-FileCopyrightText: 2022 Willian Henrique // +// SPDX-FileCopyrightText: 2022 Willian Henrique // SPDX-FileCopyrightText: 2024 Matthew Wozniak #include @@ -70,7 +70,7 @@ void *hook_inline(void *func_, void *target) { } } // for simplicity, just bump alloc the trampoline. no need to free anyway - if (nexttrampoline - trampolines > sizeof(trampolines) - len - 6) { + if (nexttrampoline - trampolines > (int)sizeof(trampolines) - len - 6) { warn("out of trampoline space\n"); return 0; } @@ -99,6 +99,7 @@ void unhook_inline(void *orig) { void *hook_dllapi(const char *module, const char *name, void *target) { void *func = os_dlsym(os_dlopen(module), name); + debug("%s = %p", name, func); if (!func) warn("couldn't find function %s in %s", name, module); else return hook_inline(func, target); return NULL; diff --git a/log.h b/log.h index 61bf8a9..f0655b2 100644 --- a/log.h +++ b/log.h @@ -2,17 +2,24 @@ // SPDX-FileCopyrightText: 2024 Matthew Wozniak #include -#include #define die(fmt, ...) {\ fprintf(stderr, "err: %s: " fmt "\n", __func__ __VA_OPT__(,) __VA_ARGS__); \ exit(1); \ } +#define bail(fmt, ...) {\ + fprintf(stderr, "err: %s: " fmt "\n", __func__ __VA_OPT__(,) __VA_ARGS__); \ + return false; \ +} + #define warn(fmt, ...) \ fprintf(stderr, "warn: %s: " fmt "\n", __func__ __VA_OPT__(,) __VA_ARGS__) #define info(fmt, ...) \ fprintf(stderr, "info: %s: " fmt "\n", __func__ __VA_OPT__(,) __VA_ARGS__) +#define debug(fmt, ...) \ + fprintf(stderr, "dbg: %s: " fmt "\n", __func__ __VA_OPT__(,) __VA_ARGS__) + // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/main.c b/main.c index e16786e..376c615 100644 --- a/main.c +++ b/main.c @@ -10,20 +10,38 @@ #define WIN32_LEAN_AND_MEAN #include +void (*orig_cbuf_addtext)(char *); +void hook_cbuf_addtext(char *str) { + orig_cbuf_addtext(str); + info("%s", str); + // this is the last thing that happens when the game is opened + if (!strcmp(str, "exec modsettings.cfg mod\n")) { + demoplayer->vt->start_playback(demoplayer, "demo.dem", false); + } +} + +char *cmdline; +char WINAPI *hook_GetCommandLineA(void) { + return cmdline; +} + void *(WINAPI *orig_LoadLibraryExA)(const char *, void *, int); void WINAPI *hook_LoadLibraryExA(const char *filename, void *hfile, int flags) { + // if the dll is already loaded, don't run our code again + if (os_dlhandle(filename)) + return orig_LoadLibraryExA(filename, hfile, flags); void *ret = orig_LoadLibraryExA(filename, hfile, flags); if (!ret) return ret; - // cut down to basename + // cut down to basename for display const char *basename = filename; for (const char *p = filename; *p; p++) if (*p == '\\') basename = p + 1; info("loaded %s", basename); - // last dll to load - if (!strcmp(basename, "serverbrowser.dll")) { - api_init(); - // TODO: figure out hooks that run AFTER valve.rc is called + if (!strcmp(basename, "engine.dll")) { + if (!api_init()) die("couldn't get apis"); + orig_cbuf_addtext = (void (*)(char *)) + hook_inline((void *)cbuf_addtext, (void *)hook_cbuf_addtext); } return ret; } @@ -31,19 +49,25 @@ void WINAPI *hook_LoadLibraryExA(const char *filename, void *hfile, int flags) { typedef int (*LauncherMain_t)(void *instance, void *prev_inst, char *cmdline, int cmd_show); -int main(int argc, char **argv) { +int main(/* int argc, char **argv */) { SetDllDirectoryA("bin/"); - void *launcher_dll = os_dlopen("launcher"); - LauncherMain_t launcher_main = - (LauncherMain_t)os_dlsym(launcher_dll, "LauncherMain"); + // TODO: make this changeable by the user + cmdline = "hl2.exe -console -w 1280 -h 720 -window -high -dxlevel 95"; hook_init(); orig_LoadLibraryExA = (typeof(orig_LoadLibraryExA))hook_dllapi("kernel32", "LoadLibraryExA", (void *)hook_LoadLibraryExA); + hook_dllapi("kernel32", "GetCommandLineA", (void *)hook_GetCommandLineA); + + info("GetCommandLineA() = %s", GetCommandLineA()); + + void *launcher_dll = os_dlopen("launcher"); + LauncherMain_t launcher_main = + (LauncherMain_t)os_dlsym(launcher_dll, "LauncherMain"); if (!launcher_main) die("couldn't open launcher"); - launcher_main(NULL, NULL, NULL, 0); + launcher_main(NULL, NULL, cmdline, 0); } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/os.h b/os.h index 0c8edfb..594ccad 100644 --- a/os.h +++ b/os.h @@ -12,6 +12,7 @@ #define os_dlopen LoadLibraryA #define os_dlsym (void *)GetProcAddress +#define os_dlhandle GetModuleHandleA #define os_dlclose FreeLibrary inline bool os_mprot(void *mem, int len, int mode) { -- cgit v1.2.3-54-g00ecf