// SPDX-License-Identifier: ISC // SPDX-FileCopyrightText: 2024 Matthew Wozniak #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 demoplayer *demoplayer = NULL; void (*cbuf_addtext)(char *) = NULL; 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(INTERFACEVERSION_VENGINESERVER, 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"); // 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