aboutsummaryrefslogtreecommitdiff
path: root/api.c
blob: 0562bbaa9d8b44a6b86941a659e3e6d65f3d9339 (plain) (blame)
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/*
 * Copyright © 2024 Matthew Wozniak <me@woz.blue>
 *
 * 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.
 */

#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. We need to keep the double
	// pointer because when we get this, it is null. It is set by the engine
	// later.
	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);

	// find cl_movieinfo
	{
		// step 1: find CL_IsRecordingMovie() in
		// CEngineTools::StartMovieRecording
		instr = (const u8 *)enginetools->vt->start_movie_recording;
		void *cl_isrecordingmovie = NULL;
		for (const u8 *p = instr; p - instr < 64;) {
			// this is the first call in the function
			if (*p == X86_CALL) {
				cl_isrecordingmovie = (void *)(p + 5 + *(i32 *)(p + 1));
			}
			int l = x86_len(p);
			if (l == -1)
				bail("invalid instruction looking for CL_IsRecordingMovie");
			p += l;
		}
		if (!cl_isrecordingmovie)
			bail("couldn't find cbuf_addtext CL_IsRecordingMovie");

		// step 2: get the pointer to cl_movieinfo
		// should be in the first instruction
		instr = (const u8 *)cl_isrecordingmovie;
		if (instr[0] != X86_ALUMI8 || instr[1] != X86_MODRM(0, 7, 5))
			bail("couldn't get movieinfo");
		movieinfo = *(struct movieinfo **)(instr + 2);
		debug("movieinfo = %p", (void *)movieinfo);
	}
	
	return true;
}

// vi: sw=4 ts=4 noet tw=80 cc=80