/* * Copyright © 2024 Matthew Wozniak * * 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. 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