diff options
-rw-r--r-- | gamedata/engine.txt | 36 | ||||
-rw-r--r-- | gamedata/gamelib.txt | 2 | ||||
-rw-r--r-- | src/build/gluegen.c | 9 | ||||
-rw-r--r-- | src/con_.c | 576 | ||||
-rw-r--r-- | src/con_.h | 38 | ||||
-rw-r--r-- | src/engineapi.c | 40 | ||||
-rw-r--r-- | src/engineapi.h | 14 | ||||
-rw-r--r-- | src/gametype.h | 73 | ||||
-rw-r--r-- | src/sst.c | 93 |
9 files changed, 603 insertions, 278 deletions
diff --git a/gamedata/engine.txt b/gamedata/engine.txt index a50f9ce..f13a63a 100644 --- a/gamedata/engine.txt +++ b/gamedata/engine.txt @@ -1,22 +1,28 @@ # ICvar # XXX: const and non-const entries might be flipped here on Windows, not 100% # sure. kind of just choosing not to care thusfar, as it still works the same! -vtidx_AllocateDLLIdentifier 5 - Portal2 8 -vtidx_RegisterConCommand 6 - Portal2 9 -vtidx_UnregisterConCommands 8 - Portal2 11 -# unused: -#vtidx_FindCommandBase 10 -# Portal2 13 +vtidx_AllocateDLLIdentifier + NE 5 + Portal2 8 +vtidx_RegisterConCommand + OE 5 # named RegisterConCommandBase here, but same thing + NE 6 + Portal2 9 +vtidx_UnregisterConCommands + NE 8 + Portal2 11 vtidx_FindVar 12 + OE 7 Portal2 15 -vtidx_FindCommand 14 - Portal2 17 -vtidx_CallGlobalChangeCallbacks 20 - L4Dx 18 - Portal2 21 +vtidx_FindCommand + NE 14 + Portal2 17 +vtidx_CallGlobalChangeCallbacks + NE 20 + L4Dx 18 + Portal2 21 +vtidx_CallGlobalChangeCallbacks_OE # different function signature, no float arg + OE 12 vtidx_ConsoleColorPrintf OrangeBoxbased 23 L4Dx 21 @@ -148,6 +154,8 @@ vtidx_VGuiConnect 3 + NVDTOR vtidx_VGuiIsInitialized 6 + NVDTOR # this is also just called IsInitialized() L4Dbased 7 + NVDTOR L4DS 8 + NVDTOR + OE 5 + NVDTOR + #DMoMM 10 + NVDTOR # untested, can't remember where I got this... # CDedicatedServerAPI vtidx_RunFrame 7 diff --git a/gamedata/gamelib.txt b/gamedata/gamelib.txt index 486a216..26e4758 100644 --- a/gamedata/gamelib.txt +++ b/gamedata/gamelib.txt @@ -1,11 +1,13 @@ # CGameMovement vtidx_CheckJumpButton + OE 14 + NVDTOR Portal1_3420 22 + NVDTOR 2013 28 + NVDTOR L4D 32 + NVDTOR L4DS 33 + NVDTOR Portal2 35 + NVDTOR off_mv 8 + OE 4 Portal1_3420 4 # IServerGameDLL diff --git a/src/build/gluegen.c b/src/build/gluegen.c index e2bc2f3..3d1eef8 100644 --- a/src/build/gluegen.c +++ b/src/build/gluegen.c @@ -873,6 +873,15 @@ F( " extfree(con_getvarcommon(%.*s)->strval);", cvar_names[i].len, cvar_names[i].s) } _( "}") +_( "") +_( "static inline void shuntvars() {") +_( "#ifdef _WIN32") + for (int i = 1; i < ncvars; ++i) { +F( " memmove(&%.*s->v1, &%.*s->v2, sizeof(struct con_var_common));", + cvar_names[i].len, cvar_names[i].s, cvar_names[i].len, cvar_names[i].s) + } +_( "#endif") +_( "}") for (int i = 1; i < nevents; ++i) { const char *prefix = event_predicateflags[i] ? "bool CHECK_" : "void EMIT_"; @@ -1,6 +1,7 @@ /* THIS FILE SHOULD BE CALLED `con.c` BUT WINDOWS IS STUPID */ /* * Copyright © Michael Smith <mikesmiffy128@gmail.com> + * Copyright © Hayden K <imaciidz@gmail.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,10 +19,12 @@ #include <stddef.h> // should be implied by stdlib but glibc is dumb (offsetof) #include <stdlib.h> #include <stdio.h> +#include <string.h> #include "abi.h" #include "con_.h" #include "engineapi.h" // for factories and rgba - XXX: is this a bit circular? +#include "errmsg.h" #include "extmalloc.h" #include "gamedata.h" #include "gametype.h" @@ -30,22 +33,22 @@ #include "os.h" #include "vcall.h" #include "version.h" +#include "x86.h" +#include "x86util.h" /******************************************************************************\ * Have you ever noticed that when someone comments "here be dragons" there's * * no actual dragons? Turns out, that's because the dragons all migrated over * * here, so that they could build multiple inheritance vtables in C, by hand. * * * + * Also there's self-modifying code now. * + * * * Don't get set on fire. * \******************************************************************************/ static int dllid; // from AllocateDLLIdentifier(), lets us unregister in bulk int con_cmdclient; -DECL_VFUNC(struct ICvar, void *, FindCommandBase_p2, 13, const char *) -DECL_VFUNC(struct ICvar, void *, FindCommand_nonp2, 14, const char *) -DECL_VFUNC(struct ICvar, void *, FindVar_nonp2, 12, const char *) - DECL_VFUNC_DYN(struct ICvar, int, AllocateDLLIdentifier) DECL_VFUNC_DYN(struct ICvar, void, RegisterConCommand, /*ConCommandBase*/ void *) DECL_VFUNC_DYN(struct ICvar, void, UnregisterConCommands, int) @@ -55,73 +58,103 @@ DECL_VFUNC_DYN(struct ICvar, struct con_cmd *, FindCommand, const char *) DECL_VFUNC_DYN(struct ICvar, void, CallGlobalChangeCallbacks, struct con_var *, const char *, float) +#ifdef _WIN32 +DECL_VFUNC_DYN(struct ICvar, void, CallGlobalChangeCallbacks_OE, + struct con_var *, const char *) + +// other OE stuff. TODO(compat): should this be in gamedata? fine for now? +DECL_VFUNC(struct ICvar, struct con_cmdbase *, GetCommands_OE, 9) +DECL_VFUNC(struct VEngineClient, void *, Cmd_Argv, 32) +#endif + +// bootstrap things for con_detect(), not used after that +DECL_VFUNC(struct ICvar, void *, FindCommandBase_p2, 13, const char *) +DECL_VFUNC(struct ICvar, void *, FindCommand_nonp2, 14, const char *) +DECL_VFUNC(struct ICvar, void *, FindVar_nonp2, 12, const char *) +#ifdef _WIN32 +DECL_VFUNC(struct ICvar, void *, FindVar_OE, 7, const char *) +#endif + static struct ICvar *coniface; static void *colourmsgf; -asm_only void _con_colourmsg(void *dummy, const struct rgba *c, const char *fmt, ...) { +#ifdef _WIN32 +#pragma section("selfmod", execute) +__attribute((used, section("selfmod"), noinline)) +#endif +asm_only void _con_colourmsg(void *dummy, const struct rgba *c, + const char *fmt, ...) { // NE: ConsoleColorPrintf is virtual, so the dummy param is a carve-out for // `this` (which is coniface). - // OE: it's a global function, with no this param, so we'd simply fix up the - // stack (pop return address, put it in the dummy slot). - // TODO(compat): actually implementing the OE case might be a bit tricky. it - // seems the most efficient thing would be self-modifying code (i.e. replace - // this function's asm with different asm). that'll be interesting... __asm volatile ( "mov eax, %0\n" "mov [esp + 4], eax\n" // put coniface in the empty stack slot "jmp dword ptr %1\n" // jump to the real function : : "m" (coniface), "m" (colourmsgf) - : "eax", "edi", "memory" + : "eax", "memory" ); } -static inline void initval(struct con_var *v) { - v->v2.strval = extmalloc(v->v2.strlen); // note: _DEF_CVAR() sets strlen - memcpy(v->v2.strval, v->v2.defaultval, v->v2.strlen); +#ifdef _WIN32 +// this function is defined as data because we'll be using it to self-modify the +// main _con_colourmsg function! +__attribute((used, section("rdata"))) +asm_only static void _con_colourmsg_OE(void *dummy, const struct rgba *c, + const char *fmt, ...) { + // OE: it's a global function, with no this param, so we have to fix up the + // stack a bit. This will be less efficient than NE, but that seems like a + // reasonable tradeoff considering most games are NE. We could in theory + // self-modify every single call site to avoid the fixups but haha are you + // out of your mind we're not doing that. + __asm volatile ( + "pop ebx\n" // pop return address, store in callee-save (*see header!*) + "add esp, 4\n" // pop the dummy stack slot, it's only useful for NE + "call dword ptr %1\n" // jump to the real function + "sub esp, 4\n" // pad the stack back out for the caller + "jmp ebx\n" // return to saved address + : + : "m" (coniface), "m" (colourmsgf) + : "eax", "ebx", "memory" + ); +} +#define SELFMOD_LEN 15 // above instructions assemble to this many bytes! + +static bool selfmod() { + if (!os_mprot((void *)_con_colourmsg, SELFMOD_LEN, PAGE_EXECUTE_READWRITE)) { + errmsg_errorsys("couldn't make memory writable"); + return false; + } + memcpy((void *)&_con_colourmsg, (void *)&_con_colourmsg_OE, SELFMOD_LEN); + if (!os_mprot((void *)_con_colourmsg, SELFMOD_LEN, PAGE_EXECUTE_READ)) { + errmsg_warnsys("couldn't restore self-modified page to read-only"); + } + return true; } +#endif static void VCALLCONV dtor(void *_) {} // we don't use constructors/destructors static bool VCALLCONV IsCommand_cmd(void *this) { return true; } static bool VCALLCONV IsCommand_var(void *this) { return false; } -static bool VCALLCONV IsFlagSet_cmd(struct con_cmd *this, int flags) { - return !!(this->base.flags & flags); -} -static bool VCALLCONV IsFlagSet_var(struct con_var *this, int flags) { - return !!(this->base.flags & flags); -} -static void VCALLCONV AddFlags_cmd(struct con_cmd *this, int flags) { - this->base.flags |= flags; -} -static void VCALLCONV AddFlags_var(struct con_var *this, int flags) { - this->base.flags |= flags; -} -static void VCALLCONV RemoveFlags_cmd(struct con_cmd *this, int flags) { - this->base.flags &= ~flags; -} -static void VCALLCONV RemoveFlags_var(struct con_var *this, int flags) { - this->base.flags &= ~flags; -} -static int VCALLCONV GetFlags_cmd(struct con_cmd *this) { - return this->base.flags; +static bool VCALLCONV IsFlagSet(struct con_cmdbase *this, int flags) { + return !!(this->flags & flags); } -static int VCALLCONV GetFlags_var(struct con_var *this) { - return this->base.flags; +static void VCALLCONV AddFlags(struct con_cmdbase *this, int flags) { + this->flags |= flags; } - -static const char *VCALLCONV GetName_cmd(struct con_cmd *this) { - return this->base.name; +static void VCALLCONV RemoveFlags(struct con_cmdbase *this, int flags) { + this->flags &= ~flags; } -static const char *VCALLCONV GetName_var(struct con_var *this) { - return this->base.name; +static int VCALLCONV GetFlags(struct con_cmdbase *this) { + return this->flags; } -static const char *VCALLCONV GetHelpText_cmd(struct con_cmd *this) { - return this->base.help; +static const char *VCALLCONV GetName(struct con_cmdbase *this) { + return this->name; } -static const char *VCALLCONV GetHelpText_var(struct con_var *this) { - return this->base.help; +static const char *VCALLCONV GetHelpText(struct con_cmdbase *this) { + return this->help; } static bool VCALLCONV IsRegistered(struct con_cmdbase *this) { return this->registered; @@ -133,14 +166,42 @@ static void VCALLCONV Create_base(struct con_cmdbase *this, const char *name, const char *help, int flags) {} // nop, we static init already static void VCALLCONV Init(struct con_cmdbase *this) {} // "" +static bool ClampValue_common(struct con_var_common *this, float *f) { + if (this->hasmin && this->minval > *f) { *f = this->minval; return true; } + if (this->hasmax && this->maxval < *f) { *f = this->maxval; return true; } + return false; +} static bool VCALLCONV ClampValue(struct con_var *this, float *f) { - if (this->v2.hasmin && this->v2.minval > *f) { - *f = this->v2.minval; - return true; + return ClampValue_common(&this->v2, f); +} +#ifdef _WIN32 +static bool VCALLCONV ClampValue_OE(struct con_var *this, float *f) { + return ClampValue_common(&this->v1, f); +} +#endif + +// global argc/argv. also OE only. extern for use in sst.c plugin_unload hook +// as well as in DEF_CCMD_COMPAT_HOOK +int *_con_argc; +const char *(*_con_argv)[80]; + +static bool find_argcargv() { + const uchar *insns = (const uchar *)VFUNC(engclient, Cmd_Argv); + for (const uchar *p = insns; p - insns < 32;) { + if (p[0] == X86_CALL) { insns = p + 5 + mem_loads32(p + 1); goto _1; } + NEXT_INSN(p, "global Cmd_Argv function"); } - if (this->v2.hasmax && this->v2.maxval < *f) { - *f = this->v2.maxval; - return true; + return false; +_1: for (const uchar *p = insns; p - insns < 32;) { + if (p[0] == X86_CMPRMW && p[1] == X86_MODRM(0, 0, 5)) { + _con_argc = mem_loadptr(p + 2); + } + else if (p[0] == X86_MOVRMW && p[1] == X86_MODRM(0, 0, 4) && + p[2] == X86_TESTMRW) { + _con_argv = mem_loadptr(p + 3); + } + if (_con_argc && _con_argv) return true; + NEXT_INSN(p, "global argc and argv variables"); } return false; } @@ -156,67 +217,119 @@ bool VCALLCONV CanAutoComplete(struct con_cmd *this) { void VCALLCONV Dispatch(struct con_cmd *this, const struct con_cmdargs *args) { this->cb(args->argc, args->argv); } +#ifdef _WIN32 +void VCALLCONV Dispatch_OE(struct con_cmd *this) { + this->cb(*_con_argc, *_con_argv); +} +#endif -static void VCALLCONV ChangeStringValue(struct con_var *this, const char *s, - float oldf) { - char *old = alloca(this->v2.strlen); - memcpy(old, this->v2.strval, this->v2.strlen); +static void ChangeStringValue_common(struct con_var *this, + struct con_var_common *common, char *old, const char *s) { + memcpy(old, common->strval, common->strlen); int len = strlen(s) + 1; - if (len > this->v2.strlen) { - this->v2.strval = extrealloc(this->v2.strval, len); - this->v2.strlen = len; + if (len > common->strlen) { + common->strval = extrealloc(common->strval, len); + common->strlen = len; } - memcpy(this->v2.strval, s, len); + memcpy(common->strval, s, len); // callbacks don't matter as far as ABI compat goes (and thank goodness // because e.g. portal2 randomly adds a *list* of callbacks!?). however we // do need callbacks for at least one feature, so do our own minimal thing if (this->cb) this->cb(this); - // also call global callbacks, as is polite. +} +static void VCALLCONV ChangeStringValue(struct con_var *this, const char *s, + float oldf) { + char *old = alloca(this->v2.strlen); + ChangeStringValue_common(this, &this->v2, old, s); CallGlobalChangeCallbacks(coniface, this, old, oldf); } +#ifdef _WIN32 +static void VCALLCONV ChangeStringValue_OE(struct con_var *this, const char *s) { + char *old = alloca(this->v1.strlen); + ChangeStringValue_common(this, &this->v1, old, s); + CallGlobalChangeCallbacks_OE(coniface, this, old); +} +#endif // NOTE: these Internal* functions are virtual in the engine, but nowadays we // just call them directly since they're private to us. We still put them in the // vtable just in case (see below), though arguably nothing in the engine // *should* be calling these internal things anyway. -static void VCALLCONV InternalSetValue(struct con_var *this, const char *v) { - float oldf = this->v2.fval; +static void InternalSetValue_common(struct con_var *this, + struct con_var_common *common, const char *v) { float newf = atof(v); char tmp[32]; - if (ClampValue(this, &newf)) { + if (ClampValue_common(common, &newf)) { snprintf(tmp, sizeof(tmp), "%f", newf); v = tmp; } - this->v2.fval = newf; - this->v2.ival = (int)newf; + common->fval = newf; + common->ival = (int)newf; +} +static void VCALLCONV InternalSetValue(struct con_var *this, const char *v) { + float oldf = this->v2.fval; + InternalSetValue_common(this, &this->v2, v); if (!(this->base.flags & CON_NOPRINT)) ChangeStringValue(this, v, oldf); } +#ifdef _WIN32 +static void VCALLCONV InternalSetValue_OE(struct con_var *this, const char *v) { + InternalSetValue_common(this, &this->v1, v); + if (!(this->base.flags & CON_NOPRINT)) ChangeStringValue_OE(this, v); +} +#endif static void VCALLCONV InternalSetFloatValue(struct con_var *this, float v) { if (v == this->v2.fval) return; - ClampValue(this, &v); float old = this->v2.fval; - this->v2.fval = v; this->v2.ival = (int)this->v2.fval; + ClampValue_common(&this->v2, &v); + this->v2.fval = v; this->v2.ival = (int)v; if (!(this->base.flags & CON_NOPRINT)) { char tmp[32]; snprintf(tmp, sizeof(tmp), "%f", this->v2.fval); ChangeStringValue(this, tmp, old); } } +#ifdef _WIN32 +static void VCALLCONV InternalSetFloatValue_OE(struct con_var *this, float v) { + if (v == this->v1.fval) return; + ClampValue_common(&this->v1, &v); + this->v1.fval = v; this->v1.ival = (int)v; + if (!(this->base.flags & CON_NOPRINT)) { + char tmp[32]; + snprintf(tmp, sizeof(tmp), "%f", this->v1.fval); + ChangeStringValue_OE(this, tmp); + } +} +#endif +static void InternalSetIntValue_impl(struct con_var *this, + struct con_var_common *common, int v) { + float f = (float)v; + if (ClampValue_common(common, &f)) v = (int)f; + common->fval = f; common->ival = v; +} static void VCALLCONV InternalSetIntValue(struct con_var *this, int v) { if (v == this->v2.ival) return; - float f = (float)v; - if (ClampValue(this, &f)) v = (int)f; float old = this->v2.fval; - this->v2.fval = f; this->v2.ival = v; + InternalSetIntValue_impl(this, &this->v2, v); if (!(this->base.flags & CON_NOPRINT)) { char tmp[32]; snprintf(tmp, sizeof(tmp), "%f", this->v2.fval); ChangeStringValue(this, tmp, old); } } +#ifdef _WIN32 +static void VCALLCONV InternalSetIntValue_OE(struct con_var *this, int v) { + if (v == this->v1.ival) return; + InternalSetIntValue_impl(this, &this->v1, v); + if (!(this->base.flags & CON_NOPRINT)) { + char tmp[32]; + snprintf(tmp, sizeof(tmp), "%f", this->v1.fval); + ChangeStringValue_OE(this, tmp); + } +} +#endif // IConVar calls get this-adjusted pointers, so just subtract the offset static void VCALLCONV SetValue_str_thunk(void *thisoff, const char *v) { @@ -240,16 +353,16 @@ static void VCALLCONV SetValue_colour_thunk(void *thisoff, struct rgba v) { InternalSetIntValue(this, v.val); } -// more misc thunks, hopefully these just compile to a lea and a jmp +// more misc thunks for IConVar, hopefully these just compile to a lea and a jmp static const char *VCALLCONV GetName_thunk(void *thisoff) { struct con_var *this = mem_offset(thisoff, -offsetof(struct con_var, vtable_iconvar)); - return GetName_var(this); + return GetName(&this->base); } static bool VCALLCONV IsFlagSet_thunk(void *thisoff, int flags) { struct con_var *this = mem_offset(thisoff, -offsetof(struct con_var, vtable_iconvar)); - return IsFlagSet_var(this, flags); + return IsFlagSet(&this->base, flags); } // dunno what this is actually for... @@ -269,8 +382,8 @@ void *_con_vtab_cmd[14 + NVDTOR] = { (void *)&dtor, #endif (void *)&IsCommand_cmd, - (void *)&IsFlagSet_cmd, - (void *)&AddFlags_cmd + (void *)&IsFlagSet, + (void *)&AddFlags }; // the engine does dynamic_casts on ConVar at some points so we have to fill out @@ -291,8 +404,8 @@ struct _con_vtab_var_wrap _con_vtab_var_wrap = { (void *)&dtor, #endif (void *)&IsCommand_var, - (void *)&IsFlagSet_var, - (void *)&AddFlags_var + (void *)&IsFlagSet, + (void *)&AddFlags }; struct _con_vtab_iconvar_wrap _con_vtab_iconvar_wrap = { @@ -309,8 +422,20 @@ struct _con_vtab_iconvar_wrap _con_vtab_iconvar_wrap = { #endif }; +#ifdef _WIN32 +static int off_cvar_common = offsetof(struct con_var, v2); +#else +enum { off_cvar_common = offsetof(struct con_var, v2) }; +#endif + +struct con_var_common *con_getvarcommon(const struct con_var *v) { + return mem_offset(v, off_cvar_common); +} + void con_regvar(struct con_var *v) { - initval(v); + struct con_var_common *c = con_getvarcommon(v); + c->strval = extmalloc(c->strlen); // note: _DEF_CVAR() sets strlen member + memcpy(c->strval, c->defaultval, c->strlen); RegisterConCommand(coniface, v); } @@ -324,77 +449,48 @@ void con_regcmd(struct con_cmd *c) { // just hacked in for now to get things working because it was broken before... #ifdef _WIN32 static int vtidx_SetValue_str = 2, vtidx_SetValue_f = 1, vtidx_SetValue_i = 0; +static int off_setter_vtable = offsetof(struct con_var, vtable_iconvar); #else enum { vtidx_SetValue_str = 0, vtidx_SetValue_f = 1, vtidx_SetValue_i = 2 }; #endif -void con_init() { - colourmsgf = coniface->vtable[vtidx_ConsoleColorPrintf]; - dllid = AllocateDLLIdentifier(coniface); - - void **pc = _con_vtab_cmd + 3 + NVDTOR, **pv = _con_vtab_var + 3 + NVDTOR, - **pi = _con_vtab_iconvar -#ifndef _WIN32 - + 3 -#endif - ; - if (GAMETYPE_MATCHES(L4Dbased)) { // 007 base - *pc++ = (void *)&RemoveFlags_cmd; - *pc++ = (void *)&GetFlags_cmd; - *pv++ = (void *)&RemoveFlags_var; - *pv++ = (void *)&GetFlags_var; +#ifdef _WIN32 +struct con_cmdbase **linkedlist = 0; // indirect command list, OE only! + +static bool find_linkedlist(const uchar *insns) { + // note: it's a jmp in the disasm I've seen but a call seems plausible too + if (insns[0] != X86_JMPIW && *insns != X86_CALL) return false; + insns += 5 + mem_loads32(insns + 1); // follow the call + if (insns[0] != X86_MOVEAXII || insns[5] != X86_RET) return false; + linkedlist = mem_loadptr(insns + 1); + return true; +} + +static bool find_Con_ColorPrintf() { + typedef void *(*GetSpewOutputFunc_func)(); + void *tier0 = os_dlhandle(L"tier0.dll"); + if_cold (!tier0) { + errmsg_errorsys("couldn't get tier0.dll handle"); + return false; } - // base stuff in cmd - *pc++ = (void *)&GetName_cmd; - *pc++ = (void *)&GetHelpText_cmd; - *pc++ = (void *)&IsRegistered; - *pc++ = (void *)&GetDLLIdentifier; - *pc++ = (void *)&Create_base; - *pc++ = (void *)&Init; - // cmd-specific - *pc++ = (void *)&AutoCompleteSuggest; - *pc++ = (void *)&CanAutoComplete; - *pc++ = (void *)&Dispatch; - // base stuff in var - *pv++ = (void *)&GetName_var; - *pv++ = (void *)&GetHelpText_var; - *pv++ = (void *)&IsRegistered; - *pv++ = (void *)&GetDLLIdentifier; - *pv++ = (void *)&Create_base; - *pv++ = (void *)&Init; - // var-specific - *pv++ = (void *)&InternalSetValue; - *pv++ = (void *)&InternalSetFloatValue; - *pv++ = (void *)&InternalSetIntValue; - if (GAMETYPE_MATCHES(L4D2x) || GAMETYPE_MATCHES(Portal2)) { // ugh, annoying - // InternalSetColorValue, literally the same machine instructions as int - *pv++ = (void *)&InternalSetIntValue; + GetSpewOutputFunc_func GetSpewOutputFunc = (GetSpewOutputFunc_func)os_dlsym( + tier0, "GetSpewOutputFunc"); + if_cold (!GetSpewOutputFunc) { + errmsg_errorx("couldn't find GetSpewOutputFunc symbol"); + return false; } - *pv++ = (void *)&ClampValue;; - *pv++ = (void *)&ChangeStringValue; - *pv++ = (void *)&Create_var; - if (GAMETYPE_MATCHES(L4D2x) || GAMETYPE_MATCHES(Portal2)) { - *pi++ = (void *)&SetValue_colour_thunk; -#ifdef _WIN32 - // stupid hack for above mentioned crazy overload ordering - ++vtidx_SetValue_str; - ++vtidx_SetValue_i; - ++vtidx_SetValue_f; -#endif + uchar *insns = (uchar *)GetSpewOutputFunc(); + for (uchar *p = insns; p - insns < 320;) { + if (p[0] == X86_PUSHECX && p[1] == X86_PUSHIW && p[6] == X86_CALL && + p[11] == X86_ALUMI8S && p[12] == X86_MODRM(3, 0, 4)) { + colourmsgf = p + 11 + mem_loads32(p + 7); + return true; + } + NEXT_INSN(p, "Con_ColorPrintf function"); } -#ifdef _WIN32 - // see above: these aren't prefilled due the the reverse order - *pi++ = (void *)&SetValue_i_thunk; - *pi++ = (void *)&SetValue_f_thunk; - *pi++ = (void *)&SetValue_str_thunk; -#endif - *pi++ = (void *)&GetName_thunk; - // GetBaseName (we just return actual name in all cases) - if (GAMETYPE_MATCHES(L4Dbased)) *pi++ = (void *)&GetName_thunk; - *pi++ = (void *)&IsFlagSet_thunk; - // last one: not in 004, but doesn't matter. one less branch! - *pi++ = (void *)&GetSplitScreenPlayerSlot; + return false; } +#endif static void helpuserhelpus(int pluginver, char ifaceverchar) { con_msg("\n"); @@ -407,7 +503,7 @@ static void helpuserhelpus(int pluginver, char ifaceverchar) { // note: for now at least, not using errmsg_*() macros here because it doesn't // really make sense for these messages to be coming from "con" -static void warnoe() { +static void badver() { con_warn("sst: error: this engine version is not yet supported\n"); } @@ -433,10 +529,25 @@ bool con_detect(int pluginver) { else { _gametype_tag |= _gametype_tag_L4D2; } + if (FindVar_nonp2(coniface, "sv_zombie_touch_trigger_delay")) { + _gametype_tag |= _gametype_tag_L4D2_2125plus; + } + if (FindVar_nonp2(coniface, "director_cs_weapon_spawn_chance")) { + _gametype_tag |= _gametype_tag_TheLastStand; + } return true; } if (FindVar_nonp2(coniface, "z_difficulty")) { _gametype_tag |= _gametype_tag_L4D1; + // Crash Course update + if (FindCommand_nonp2(coniface, "director_log_scavenge_items")) { + _gametype_tag |= _gametype_tag_L4D1_1015plus; + // seems there was some code shuffling in the Mac update (1022). + // this update came out like 2-3 weeks after The Sacrifice + if (con_findvar("tank_stasis_time_suicide")) { + _gametype_tag |= _gametype_tag_L4D1_1022plus; + } + } return true; } con_warn("sst: error: game is unsupported (using VEngineCvar007)\n"); @@ -448,16 +559,49 @@ bool con_detect(int pluginver) { // this crash? find out! if (pluginver == 3) _gametype_tag |= _gametype_tag_2013; else _gametype_tag |= _gametype_tag_OrangeBox; + // detect Portal 1 versions while we're here... + if (FindCommand_nonp2(coniface, "upgrade_portalgun")) { + _gametype_tag |= _gametype_tag_Portal1; + if (!FindVar_nonp2(coniface, "tf_escort_score_rate")) { + _gametype_tag |= _gametype_tag_Portal1_3420; + } + } + else if (FindCommand_nonp2(coniface, "phys_swap")) { + _gametype_tag |= _gametype_tag_HL2series; + } return true; } - if (factory_engine("VEngineCvar003", 0)) { - warnoe(); - helpuserhelpus(pluginver, '3'); - return false; + if (coniface = factory_engine("VEngineCvar003", 0)) { +#ifdef _WIN32 // there's no OE on linux! + _gametype_tag |= _gametype_tag_OE; + // for deletion/unlinking on unload, we need an indirect linked list + // pointer. calling GetCommands gives us a direct pointer. so we have to + // actually pull out the indirect pointer from the actual asm lol. + if (!find_linkedlist((uchar *)VFUNC(coniface, GetCommands_OE))) { + errmsg_errorx("couldn't find command list pointer"); + return false; + } + if (!find_argcargv()) return false; + if (!find_Con_ColorPrintf()) return false; + if (!selfmod()) return false; + // NOTE: the default static struct layout is for NE; immediately after + // engineapi init finishes, the generated glue code will shunt + // everything along for OE if required, in shuntvars(). since all the + // gluegen code is currently hooked up in sst.c this is a little bit + // annoyingly removed from here. not sure how to do it better, sorry. + off_cvar_common = offsetof(struct con_var, v1); + if (FindVar_OE(coniface, "mm_ai_facehugger_enablehugeattack")) { + _gametype_tag |= _gametype_tag_DMoMM; + } + return true; +#else + badver(); + helpuserhelpus(pluginver, '2'); +#endif } // I don't suppose there's anything below 002 worth caring about? Shrug. if (factory_engine("VEngineCvar002", 0)) { - warnoe(); + badver(); helpuserhelpus(pluginver, '2'); return false; } @@ -466,7 +610,125 @@ bool con_detect(int pluginver) { return false; } +void con_init() { + if (!GAMETYPE_MATCHES(OE)) { + colourmsgf = coniface->vtable[vtidx_ConsoleColorPrintf]; + dllid = AllocateDLLIdentifier(coniface); + } + + void **pc = _con_vtab_cmd + 3 + NVDTOR, **pv = _con_vtab_var + 3 + NVDTOR, +#ifdef _WIN32 + **pi = _con_vtab_iconvar; +#else + **pi = _con_vtab_iconvar + 3; +#endif + if (GAMETYPE_MATCHES(L4Dbased)) { // 007 base + *pc++ = (void *)&RemoveFlags; + *pc++ = (void *)&GetFlags; + *pv++ = (void *)&RemoveFlags; + *pv++ = (void *)&GetFlags; + } + // base stuff in cmd + *pc++ = (void *)&GetName; + *pc++ = (void *)&GetHelpText; + *pc++ = (void *)&IsRegistered; + if (!GAMETYPE_MATCHES(OE)) *pc++ = (void *)&GetDLLIdentifier; + *pc++ = (void *)&Create_base; + *pc++ = (void *)&Init; + // cmd-specific + *pc++ = (void *)&AutoCompleteSuggest; + *pc++ = (void *)&CanAutoComplete; + if (GAMETYPE_MATCHES(OE)) { +#ifdef _WIN32 // function only defined in windows + *pc++ = (void *)&Dispatch_OE; +#endif + } + else { + *pc++ = (void *)&Dispatch; + } + // base stuff in var + *pv++ = (void *)&GetName; + *pv++ = (void *)&GetHelpText; + *pv++ = (void *)&IsRegistered; + if (!GAMETYPE_MATCHES(OE)) *pv++ = (void *)&GetDLLIdentifier; + *pv++ = (void *)&Create_base; + *pv++ = (void *)&Init; + // var-specific + if (GAMETYPE_MATCHES(OE)) { +#ifdef _WIN32 + // these there are for the SetValue overloads but we effectively inline + // them by putting in pointers to call the Internal ones directly. this + // specifically works now that we've opted not to bother with the parent + // pointer stuff, otherwise we'd still need wrappers here. + vtidx_SetValue_i = pv - _con_vtab_var; + *pv++ = (void *)&InternalSetIntValue_OE; + vtidx_SetValue_f = pv - _con_vtab_var; + *pv++ = (void *)&InternalSetFloatValue_OE; + vtidx_SetValue_str = pv - _con_vtab_var; + *pv++ = (void *)&InternalSetValue_OE; + off_setter_vtable = 0; // setters should use the single vtable (below) + *pv++ = (void *)&InternalSetValue_OE; + *pv++ = (void *)&InternalSetFloatValue_OE; + *pv++ = (void *)&InternalSetIntValue_OE; + *pv++ = (void *)&ClampValue_OE; + *pv++ = (void *)&ChangeStringValue_OE; +#endif + } + else { + *pv++ = (void *)&InternalSetValue; + *pv++ = (void *)&InternalSetFloatValue; + *pv++ = (void *)&InternalSetIntValue; + if (GAMETYPE_MATCHES(L4D2x) || GAMETYPE_MATCHES(Portal2)) { // ugh. + // InternalSetColorValue, exact same machine instructions as for int + *pv++ = (void *)&InternalSetIntValue; + } + *pv++ = (void *)&ClampValue; + *pv++ = (void *)&ChangeStringValue; + } + *pv++ = (void *)&Create_var; + if (GAMETYPE_MATCHES(OE)) return; // we can just skip the rest on OE! + if (GAMETYPE_MATCHES(L4D2x) || GAMETYPE_MATCHES(Portal2)) { + *pi++ = (void *)&SetValue_colour_thunk; +#ifdef _WIN32 + // stupid hack for above mentioned crazy overload ordering + ++vtidx_SetValue_str; + ++vtidx_SetValue_i; + ++vtidx_SetValue_f; +#endif + } +#ifdef _WIN32 + // see above: these aren't prefilled due to the reverse order + *pi++ = (void *)&SetValue_i_thunk; + *pi++ = (void *)&SetValue_f_thunk; + *pi++ = (void *)&SetValue_str_thunk; +#endif + *pi++ = (void *)&GetName_thunk; + // GetBaseName (we just return actual name in all cases) + if (GAMETYPE_MATCHES(L4Dbased)) *pi++ = (void *)&GetName_thunk; + *pi++ = (void *)&IsFlagSet_thunk; + // last one: not in 004, but doesn't matter. one less branch! + *pi++ = (void *)&GetSplitScreenPlayerSlot; +} + void con_disconnect() { +#ifdef _WIN32 + if (linkedlist) { + // there's no DLL identifier system in OE so we have to manually unlink + // our commands and variables from the global list. + for (struct con_cmdbase **pp = linkedlist; *pp; ) { + struct con_cmdbase **next = &(*pp)->next; + // HACK: easiest way to do this is by vtable. dumb, but whatever! + const struct con_cmdbase *p = *pp; + if (p->vtable == _con_vtab_cmd || p->vtable == _con_vtab_var) { + *pp = *next; + } + else { + pp = next; + } + } + return; + } +#endif UnregisterConCommands(coniface, dllid); } @@ -475,6 +737,18 @@ struct con_var *con_findvar(const char *name) { } struct con_cmd *con_findcmd(const char *name) { +#ifdef _WIN32 + if (linkedlist) { + // OE has a FindVar but no FindCommand. interesting oversight... + for (struct con_cmdbase *p = *linkedlist; p; p = p->next) { + if (!_stricmp(name, p->name)) { + // FIXME: this'll get variables too! make the appropriate vcall! + return (struct con_cmd *)p; + } + } + return 0; + } +#endif return FindCommand(coniface, name); } @@ -483,19 +757,17 @@ struct con_cmd *con_findcmd(const char *name) { // engine. a little less efficient, but safest and simplest for now. #define GETTER(T, N, M) \ T N(const struct con_var *v) { \ - return v->v2.parent->v2.M; \ + return con_getvarcommon(con_getvarcommon(v)->parent)->M; \ } GETTER(const char *, con_getvarstr, strval) GETTER(float, con_getvarf, fval) GETTER(int, con_getvari, ival) #undef GETTER -// XXX: move this to vcall/gamedata (will require win/linux conditionals first!) -// see also above comment on the vtidx definitions #define SETTER(T, I, N) \ void N(struct con_var *v, T x) { \ - ((void (*VCALLCONV)(void *, T))(v->vtable_iconvar[I]))( \ - &v->vtable_iconvar, x); \ + void (***VCALLCONV vtp)(void *, T) = mem_offset(v, off_setter_vtable); \ + (*vtp)[I](vtp, x); \ } SETTER(const char *, vtidx_SetValue_str, con_setvarstr) SETTER(float, vtidx_SetValue_f, con_setvarf) @@ -134,7 +134,7 @@ struct con_var_common { struct con_var { // ConVar in engine struct con_cmdbase base; union { - struct con_var_common v1; + struct con_var_common v1; // OE struct { void **vtable_iconvar; // IConVar in engine (pure virtual) struct con_var_common v2; @@ -152,7 +152,6 @@ struct con_var { // ConVar in engine /* The change callback used in most branches of Source. Takes an IConVar :) */ typedef void (*con_varcb)(void *v, const char *, float); - /* Returns a registered variable with the given name, or null if not found. */ struct con_var *con_findvar(const char *name); @@ -164,9 +163,7 @@ struct con_cmd *con_findcmd(const char *name); * offset of which varies by engine version. This sub-struct contains * essentially all the actual cvar-specific data. */ -static inline struct con_var_common *con_getvarcommon(struct con_var *v) { - return &v->v2; -} +struct con_var_common *con_getvarcommon(const struct con_var *v); /* * These functions get and set the values of console variables in a @@ -196,36 +193,45 @@ con_cmdcbv1 con_getcmdcbv1(const struct con_cmd *cmd); * respectively. They are aliases to direct tier0 calls, so they work early on * even before anything else is initialised. */ -#if defined(__GNUC__) || defined(__clang__) #ifdef _WIN32 -#define __asm(x) __asm("_" x) // stupid mangling meme, only on windows! -#endif +void con_msg(const char *fmt, ...) _CON_PRINTF(1, 2) __asm("_Msg"); +void con_warn(const char *fmt, ...) _CON_PRINTF(1, 2) __asm("_Warning"); +#else void con_msg(const char *fmt, ...) _CON_PRINTF(1, 2) __asm("Msg"); void con_warn(const char *fmt, ...) _CON_PRINTF(1, 2) __asm("Warning"); -#undef __asm -#else -#error Need an equivalent of asm names for your compiler! #endif struct rgba; // in engineapi.h - forward declare here to avoid warnings struct ICvar; // " +// DO NOT CALL THIS DIRECTLY UNDER ANY CIRCUMSTANCES. void _con_colourmsg(void *dummy, const struct rgba *c, const char *fmt, ...) _CON_PRINTF(3, 4); + /* * This provides the same functionality as ConColorMsg which was removed from * tier0 in the L4D engine branch - specifically, it allows printing a message * with an arbitrary RGBA colour. It must only be used after a successful * con_init() call. */ -#define con_colourmsg(...) do { \ +#define con_colourmsg(/*c, fmt, */...) do { \ _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wuninitialized\"") \ + _Pragma("GCC diagnostic ignored \"-Wunused\"") \ /* intentionally uninitialised value allows the compiler to just create a - * hole in the stack without actually writing anything. this has been - * confirmed by looking at the asm, because I'm that type of weirdo :^) */ \ + hole in the stack without actually writing anything. this has been + confirmed by looking at the asm, because I'm that type of weirdo :^) */ \ void *_dummy; \ + /* we also have to reserve EBX as a register that our wrapper can clobber + but the callee (engine function) won't (as it's normally callee-save). + the way we do this is by marking the register as clobbered both before + and after the call and tying both to the lifetime of a dummy variable. + this ensures anything that'd otherwise get put in ebx is spilled + elsewhere until after the call has returned. */ \ + register uint _ebx __asm("ebx"); \ + __asm volatile ("" : "=r" (_ebx)); \ _con_colourmsg(_dummy, __VA_ARGS__); \ + __asm volatile ("" : "=r" (_ebx)); \ _Pragma("GCC diagnostic pop") \ } while (0) @@ -407,7 +413,9 @@ extern struct _con_vtab_iconvar_wrap { static void (*orig_##name##_cb)(int argc, const char *const *argv); \ static void _hook_##name##_cb(int argc, const char *const *argv); \ static void _hook_##name##_cbv1() { \ - _hook_##name##_cb(0, 0); /* XXX: ??? */ \ + extern int *_con_argc; \ + extern const char *(*_con_argv)[80]; \ + _hook_##name##_cb(*_con_argc, *_con_argv); \ } \ static void _hook_##name##_cbv2(const struct con_cmdargs *args) { \ _hook_##name##_cb(args->argc, args->argv); \ diff --git a/src/engineapi.c b/src/engineapi.c index 44713da..1893e0c 100644 --- a/src/engineapi.c +++ b/src/engineapi.c @@ -55,7 +55,9 @@ DECL_VFUNC_DYN(struct IServerGameDLL, struct ServerClass *, GetAllServerClasses) #include <gamedatainit.gen.h> // generated by build/mkgamedata.c bool engineapi_init(int pluginver) { - if_cold (!con_detect(pluginver)) return false; + // set up all these interfaces first, so con_detect can use them (currently + // it just uses engclient for OE, and arguably that usage should also be + // moved out of con_detect, but whatever, it'll do.) pluginhandler = factory_engine("ISERVERPLUGINHELPERS001", 0); if (engclient = factory_engine("VEngineClient015", 0)) { @@ -93,41 +95,9 @@ bool engineapi_init(int pluginver) { _gametype_tag |= _gametype_tag_SrvDLL005; } - // detect p1 for the benefit of specific features - if (!GAMETYPE_MATCHES(Portal2)) { - if (con_findcmd("upgrade_portalgun")) { - _gametype_tag |= _gametype_tag_Portal1; - if (!con_findvar("tf_escort_score_rate")) { - _gametype_tag |= _gametype_tag_Portal1_3420; - } - } - else if (con_findcmd("phys_swap")) { - _gametype_tag |= _gametype_tag_HL2series; - } - } - - if (GAMETYPE_MATCHES(L4D1)) { - // Crash Course update - if (con_findcmd("director_log_scavenge_items")) { - _gametype_tag |= _gametype_tag_L4D1_1015plus; - } - // seems there was some code shuffling in the Mac update (1022) - // this update came like 2-3 weeks after The Sacrifice itself released - if (con_findvar("tank_stasis_time_suicide")) { - _gametype_tag |= _gametype_tag_L4D1_1022plus; - } - } - - if (GAMETYPE_MATCHES(L4D2)) { - if (con_findvar("sv_zombie_touch_trigger_delay")) { - _gametype_tag |= _gametype_tag_L4D2_2125plus; - } - if (con_findvar("director_cs_weapon_spawn_chance")) { - _gametype_tag |= _gametype_tag_TheLastStand; - } - } + if_cold (!con_detect(pluginver)) return false; initgamedata(); - con_init(); + con_init(); // rest of console setup requires having gamedata in place if_cold (!gameinfo_init()) { con_disconnect(); return false; } return true; } diff --git a/src/engineapi.h b/src/engineapi.h index 4118a3c..f644a0c 100644 --- a/src/engineapi.h +++ b/src/engineapi.h @@ -128,9 +128,9 @@ extern struct IInputSystem *inputsystem; extern struct CEngineVGui *vgui; // XXX: not exactly engine *API* but not currently clear where else to put this -struct CPlugin_common { +struct CPlugin_common_v2v3 { bool paused; - void *theplugin; // our own "this" pointer (or whichever other plugin it is) + void *theplugin; // plugin's own "this" pointer int ifacever; // should be the plugin library, but in old Source branches it's just null, // because CServerPlugin::Load() erroneously shadows this field with a local @@ -139,10 +139,16 @@ struct CPlugin_common { struct CPlugin { char description[128]; union { - struct CPlugin_common v1; + struct { + // same again, but no ifacever member, for OE. + bool paused; + void *theplugin; + void *module; + } v1; + struct CPlugin_common_v2v3 v2; struct { char basename[128]; // WHY VALVE WHYYYYYYY!!!! - struct CPlugin_common v2; + struct CPlugin_common_v2v3 v3; }; }; }; diff --git a/src/gametype.h b/src/gametype.h index fa899c2..6f6a712 100644 --- a/src/gametype.h +++ b/src/gametype.h @@ -23,51 +23,66 @@ extern u32 _gametype_tag; -#define GAMETYPE_BASETAGS(X) \ +#define GAMETYPE_BASETAGS(ALL, WINDOWSONLY) \ /* general engine branches used in a bunch of stuff */ \ - X(OE) \ - X(OrangeBox) \ - X(2013) \ + WINDOWSONLY(OE) \ + ALL(OrangeBox) \ + ALL(2013) \ \ /* specific games with dedicated branches / engine changes */ \ /* TODO(compat): detect dmomm, if only to fail (VEngineServer broke) */ \ - X(DMoMM) \ - X(L4D1) \ - X(L4D2) \ - X(L4DS) /* Survivors (weird arcade port) */ \ - X(Portal2) \ + WINDOWSONLY(DMoMM) \ + WINDOWSONLY(L4D1) \ + ALL(L4D2) \ + WINDOWSONLY(L4DS) /* Survivors (weird arcade port) */ \ + ALL(Portal2) \ \ /* games needing game-specific stuff, but not tied to a singular branch */ \ - X(Portal1) \ - X(HL2series) /* HL2, episodes, mods */ \ + ALL(Portal1) \ + ALL(HL2series) /* HL2, episodes, mods */ \ \ /* VEngineClient versions */ \ - X(Client015) \ - X(Client014) \ - X(Client013) \ - X(Client012) \ + ALL(Client015) \ + ALL(Client014) \ + ALL(Client013) \ + ALL(Client012) \ \ /* VEngineServer versions */ \ - X(Server021) \ + ALL(Server021) \ \ /* ServerGameDLL versions */ \ - X(SrvDLL009) /* 2013-ish */ \ - X(SrvDLL005) /* mostly everything else, it seems */ \ + ALL(SrvDLL009) /* 2013-ish */ \ + ALL(SrvDLL005) /* mostly everything else, it seems */ \ \ /* games needing version-specific stuff */ \ - X(Portal1_3420) \ - X(L4D1_1015plus) /* Crash Course update */ \ - X(L4D1_1022plus) /* Mac update, bunch of code reshuffling */ \ - X(L4D2_2125plus) \ - X(TheLastStand) /* The JAiZ update */ \ + WINDOWSONLY(Portal1_3420) \ + WINDOWSONLY(L4D1_1015plus) /* Crash Course update */ \ + WINDOWSONLY(L4D1_1022plus) /* Mac update, bunch of code reshuffling */ \ + ALL(L4D2_2125plus) \ + ALL(TheLastStand) /* The JAiZ update */ \ enum { + // here we define the enum values in such a way that on linux, the windows- + // only tags are still defined as zero. that way we can use GAMETYPE_MATCHES + // checks in some cases without needing #ifdef _WIN32 and the optimiser can + // throw it out. #define _GAMETYPE_ENUMBIT(x) _gametype_tagbit_##x, -GAMETYPE_BASETAGS(_GAMETYPE_ENUMBIT) -#undef _GAMETYPE_ENUMBIT #define _GAMETYPE_ENUMVAL(x) _gametype_tag_##x = 1 << _gametype_tagbit_##x, -GAMETYPE_BASETAGS(_GAMETYPE_ENUMVAL) +#define _GAMETYPE_DISCARD(x) +#define _GAMETYPE_ZERO(x) _gametype_tag_##x = 0, +#ifdef _WIN32 +GAMETYPE_BASETAGS(_GAMETYPE_ENUMBIT, _GAMETYPE_ENUMBIT) +GAMETYPE_BASETAGS(_GAMETYPE_ENUMVAL, _GAMETYPE_ENUMVAL) +#else +GAMETYPE_BASETAGS(_GAMETYPE_ENUMBIT, _GAMETYPE_DISCARD) +GAMETYPE_BASETAGS(_GAMETYPE_ENUMVAL, _GAMETYPE_DISCARD) +GAMETYPE_BASETAGS(_GAMETYPE_DISCARD, _GAMETYPE_ZERO) +#endif +#define _GAMETYPE_ENUMVAL(x) _gametype_tag_##x = 1 << _gametype_tagbit_##x, +#undef _GAMETYPE_ZERO +#undef _GAMETYPE_DISCARD #undef _GAMETYPE_ENUMVAL +#undef _GAMETYPE_ENUMBIT }; /* Matches for any of multiple possible tags */ @@ -80,6 +95,12 @@ GAMETYPE_BASETAGS(_GAMETYPE_ENUMVAL) (_gametype_tag_OrangeBox | _gametype_tag_2013) #define _gametype_tag_Portal (_gametype_tag_Portal1 | _gametype_tag_Portal2) +/* Match for stuff that's specifically NOT OE. */ +// TODO(compat): maybe we should add a specific !Tag syntax to mkgamedata, +// which would make this redundant. as of now this is just a low-effort way to +// keep some cvar things undefined under OE to avoid confusion +#define _gametype_tag_NE (~_gametype_tag_OE) + #define GAMETYPE_MATCHES(x) !!(_gametype_tag & (_gametype_tag_##x)) #endif @@ -37,6 +37,7 @@ #include "hook.h" #include "intdefs.h" #include "langext.h" +#include "mem.h" // for shuntvars() in generated code #include "os.h" #include "sst.h" #include "vcall.h" @@ -322,16 +323,15 @@ static void do_featureinit() { } void *inputsystemlib = os_dlhandle(OS_LIT("bin/") OS_LIT("inputsystem") OS_LIT(OS_DLSUFFIX)); - if_cold (!inputsystemlib) { - errmsg_warndl("couldn't get the input system library"); - } - else if_cold (!(factory_inputsystem = (ifacefactory)os_dlsym(inputsystemlib, - "CreateInterface"))) { - errmsg_warndl("couldn't get input system's CreateInterface"); - } - else if_cold (!(inputsystem = factory_inputsystem( - "InputSystemVersion001", 0))) { - errmsg_warnx("missing input system interface"); + if (inputsystemlib) { // might not have this, e.g. in OE. no point warning + if_cold (!(factory_inputsystem = (ifacefactory)os_dlsym(inputsystemlib, + "CreateInterface"))) { + errmsg_warndl("couldn't get input system's CreateInterface"); + } + else if_cold (!(inputsystem = factory_inputsystem( + "InputSystemVersion001", 0))) { + errmsg_warnx("missing input system interface"); + } } // ... and now for the real magic! (n.b. this also registers feature cvars) initfeatures(); @@ -344,7 +344,7 @@ if (GAMETYPE_MATCHES(x)) { \ con_colourmsg(&purple, "%s%s", first ? "" : ", ", #x); \ first = false; \ } - GAMETYPE_BASETAGS(PRINTTAG) + GAMETYPE_BASETAGS(PRINTTAG, PRINTTAG) #undef PRINTTAG con_colourmsg(&purple, "\n"); // xkcd 2109-compliant whitespace #endif @@ -421,13 +421,17 @@ static struct con_cmd *cmd_plugin_load, *cmd_plugin_unload; static int ownidx; // XXX: super hacky way of getting this to do_unload() -static bool ispluginv1(const struct CPlugin *plugin) { - // basename string is set with strncpy(), so if there's null bytes with more - // stuff after, we can't be looking at a v2 struct. and we expect null bytes - // in ifacever, since it's a small int value - return (plugin->basename[0] == 0 || plugin->basename[0] == 1) && - plugin->v1.theplugin && plugin->v1.ifacever < 256 && - plugin->v1.ifacever; +static int detectpluginver(const struct CPlugin *plugin) { + // if the first byte of basename is not 0 or 1, it can't be a bool value for + // paused, so this must be v3. XXX: there's an edge case where a string + // that starts with a 1 byte could be miscategorised but that should never + // happen in practice. still if we can think of a cleverer way around that + // it might be nice for the sake of absolute robustness. + if ((uchar)plugin->basename[0] > 1) return 3; + // if ifacever is not a small nonzero integer, it's not a version. treat it + // as the (possibly null) plugin pointer - meaning this is v1. + if (!plugin->v2.ifacever || (uint)plugin->v2.ifacever > 255) return 1; + return 2; // otherwise we just assume it must be v2! } DEF_CCMD_COMPAT_HOOK(plugin_load) { @@ -478,10 +482,14 @@ static int hook_plugin_unload_common(int argc, const char *const *argv) { struct CPlugin **plugins = pluginhandler->plugins.m.mem; if_hot (idx >= 0 && idx < pluginhandler->plugins.sz) { const struct CPlugin *plugin = plugins[idx]; - // XXX: *could* memoise the ispluginv1 call, but... meh. effort. - const struct CPlugin_common *common = ispluginv1(plugin) ? - &plugin->v1 : &plugin->v2; - if (common->theplugin == &plugin_obj) { + // XXX: *could* memoise the detect call somehow, but... meh. effort. + const void *theplugin; + switch_exhaust (detectpluginver(plugin)) { + case 1: theplugin = plugin->v1.theplugin; break; + case 2: theplugin = plugin->v2.theplugin; break; + case 3: theplugin = plugin->v3.theplugin; + } + if (theplugin == &plugin_obj) { sst_userunloaded = true; ownidx = idx; return UNLOAD_SELF; @@ -495,9 +503,20 @@ static int hook_plugin_unload_common(int argc, const char *const *argv) { return UNLOAD_OTHER; } -// TODO(compat): we'd have cbv1 here for OE. but we don't yet have the global -// argc/argv access so there's no way to implement that. - +static void hook_plugin_unload_cbv1() { + extern int *_con_argc; + extern const char *(*_con_argv)[80]; + int action = hook_plugin_unload_common(*_con_argc, *_con_argv); + switch_exhaust_enum(unload_action, action) { + case UNLOAD_SKIP: + return; + case UNLOAD_SELF: + tailcall orig_plugin_unload_cb.v1(); + case UNLOAD_OTHER: + orig_plugin_unload_cb.v1(); + EMIT_PluginUnloaded(); + } +} static void hook_plugin_unload_cbv2(const struct con_cmdargs *args) { int action = hook_plugin_unload_common(args->argc, args->argv); switch_exhaust_enum(unload_action, action) { @@ -518,6 +537,7 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) { } factory_engine = enginef; factory_server = serverf; if_cold (!engineapi_init(ifacever)) return false; + if (GAMETYPE_MATCHES(OE)) shuntvars(); // see also comment in con_detect() const void **p = vtable_firstdiff; if (GAMETYPE_MATCHES(Portal2)) *p++ = (void *)&nop_p_v; // ClientFullyConnect *p++ = (void *)&nop_p_v; // ClientDisconnect @@ -538,8 +558,12 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) { hook_plugin_load_cb(cmd_plugin_load); cmd_plugin_unload = con_findcmd("plugin_unload"); orig_plugin_unload_cb.v1 = cmd_plugin_unload->cb_v1; // n.b.: union! - // TODO(compat): detect OE/v1 and use that hook here when required - cmd_plugin_unload->cb_v2 = &hook_plugin_unload_cbv2; + if (GAMETYPE_MATCHES(OE)) { + cmd_plugin_unload->cb_v1 = &hook_plugin_unload_cbv1; + } + else { + cmd_plugin_unload->cb_v2 = &hook_plugin_unload_cbv2; + } } return true; } @@ -553,12 +577,17 @@ static void do_unload() { struct CPlugin **plugins = pluginhandler->plugins.m.mem; // see comment in CPlugin struct. setting this to the real handle right // before the engine tries to unload us allows it to actually do so. in - // newer branches this is redundant but doesn't do any harm so it's just - // unconditional (for v1). NOTE: old engines ALSO just leak the handle - // and never call Unload() if Load() fails; can't really do anything - // about that. + // some newer branches still on v2 this is redundant but doesn't do any + // harm so it's just unconditional for simplicity. in v3 it's totally + // unnecessary so we skip it.. NOTE: older engines ALSO just leak the + // handle and never call Unload() if Load() fails; can't really do + // anything about that though. struct CPlugin *plugin = plugins[ownidx]; - if (ispluginv1(plugin)) plugins[ownidx]->v1.module = ownhandle(); + switch_exhaust (detectpluginver(plugin)) { + case 1: plugin->v1.module = ownhandle(); break; + case 2: plugin->v2.module = ownhandle(); break; + case 3:; + } #endif } endfeatures(); |