1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
// SPDX-License-Identifier: ISC
// SPDX-FileCopyrightText: 2024 Matthew Wozniak <me@woz.blue>
#define NO_EXTERNS
#include "api.h"
#undef NO_EXTERNS
#include "os.h"
#include "log.h"
#include "sst/x86.h"
struct engserver *engserver = NULL;
struct engclient *engclient = NULL;
struct engineapi *engineapi = NULL;
struct enginetools *enginetools = NULL;
struct demoplayer *demoplayer = NULL;
struct videomode **videomode = NULL;
struct movieinfo *movieinfo = NULL;
void (*cbuf_addtext)(char *) = NULL;
// TODO: should this be split up?
bool api_init(void) {
void *engine_dll = os_dlhandle("engine");
createinterface_func engine_factory =
(createinterface_func)os_dlsym(engine_dll, "CreateInterface");
if (!engine_factory) bail("couldn't get engine factory");
engserver = engine_factory(VENGINE_SERVER_INTERFACE_VERSION, NULL);
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");
engineapi = engine_factory(VENGINE_LAUNCHER_INTERFACE_VERSION, NULL);
if (!engineapi) bail("couldn't get IEngineAPI from engine");
enginetools = engine_factory(VENGINE_TOOL_INTERFACE_VERSION, NULL);
if (!engineapi) bail("couldn't get IEngineTools 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;
// CEngineClient::IsPlayingDemo is a wrapper around a demoplayer call
// The first thing it does should be load a ptr to 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);
// find videomode
// CEngineAPI::SetEngineWindow calls videomode->SetGameWindow after
// detaching the current window with another vcall. Look for the second one.
// This, like the demoplayer, is a pointer. Mind the double dereference.
instr = (const u8 *)engineapi->vt->set_engine_window;
int mov_ecx_counter = 0;
for (const u8 *p = instr; p - instr < 64;) {
if (p[0] == X86_MOVRMW && p[1] == X86_MODRM(0, 1, 5)
&& ++mov_ecx_counter == 2) {
videomode = *(struct videomode ***)(p + 2);
break;
}
int l = x86_len(p);
if (l == -1)
bail("invalid instruction looking for videomode");
p += l;
}
debug("videomode = %p", (void *)videomode);
return true;
}
// vi: sw=4 ts=4 noet tw=80 cc=80
|