aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile9
-rw-r--r--api.c44
-rw-r--r--api.h41
-rw-r--r--compile_flags.txt8
-rw-r--r--hook.c5
-rw-r--r--log.h9
-rw-r--r--main.c44
-rw-r--r--os.h1
9 files changed, 139 insertions, 23 deletions
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 <mikesmiffy128@gmail.com>
-// SPDX-FileCopyrightText: 2022 Willian Henrique <wsimanbrazil@yahoo.com.br>//
+// SPDX-FileCopyrightText: 2022 Willian Henrique <wsimanbrazil@yahoo.com.br>
// SPDX-FileCopyrightText: 2024 Matthew Wozniak <me@woz.blue>
#include <string.h>
@@ -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 <me@woz.blue>
#include <stdio.h>
-#include <stdlib.h>
#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 <Windows.h>
+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) {