aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Michael Smith <mikesmiffy128@gmail.com> 2025-09-29 23:11:55 +0100
committerGravatar Michael Smith <mikesmiffy128@gmail.com> 2025-10-01 21:37:26 +0100
commitb3c359826ae519ea2816128dfe641032b9e9e97f (patch)
tree065368fa828bbfc45ceddc58ef10dd82e02a3698 /src
parent88f12ae363758c9214942335b4cdb4b5c0e559c9 (diff)
downloadsst-b3c359826ae519ea2816128dfe641032b9e9e97f.tar.gz
sst-b3c359826ae519ea2816128dfe641032b9e9e97f.zip
Get it sort-of-mostly working in "Old Engine" HL2HEADmaster
While we're at it, come up with a way for certain gamedata matches to be Windows-only. Somewhat reduces ifdef usage, although does not entirely remove it of course. Tested in HL2 2707. Haven't tested other HL2 builds, or Episode 1. Doesn't seem to work in DMoMM yet either; not sure why. A big list of stuff still to fix follows. Hidden cvars are currently an issue. We still need to figure out what to do with the flag bits because FCVAR_HIDDEN just doesn't exist in OE and there's some other flag with the same value instead. We also need to do something about the flag setting in fixes.c since HIDDEN is again not a thing, and also DEVONLY is not a thing either. When the plugin is autoloaded, all the initial log text gets eaten, because there's some stupid crap we have to do to trick the engine into displaying coloured text otherwise it just won't. Not even stuff from Warning(). Very stupid, but Hayden already figured out a solution, so that'll be done in another upcoming commit. Apparently raw mouse input breaks the menu. We might need to bump up the priority on making that hook only be active when there's no UI open - something I wanted to do anyway due to the demo drive issues. Big thanks to Hayden for doing a lot of the initial groundwork on this, particularly the cvar registration stuff. He gets a copyright notice in con_.c even though I ended up doing a lot of stuff differently because quite a bit of his work is still in there. Don't blame him for the self-modifying code though, that was my crazy idea. Sorry, but, in my defence... Well, it works.
Diffstat (limited to 'src')
-rw-r--r--src/build/gluegen.c9
-rw-r--r--src/con_.c576
-rw-r--r--src/con_.h38
-rw-r--r--src/engineapi.c40
-rw-r--r--src/engineapi.h14
-rw-r--r--src/gametype.h73
-rw-r--r--src/sst.c93
7 files changed, 579 insertions, 264 deletions
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_";
diff --git a/src/con_.c b/src/con_.c
index 387cab4..896faea 100644
--- a/src/con_.c
+++ b/src/con_.c
@@ -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)
diff --git a/src/con_.h b/src/con_.h
index 33c170a..63e79c1 100644
--- a/src/con_.h
+++ b/src/con_.h
@@ -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
diff --git a/src/sst.c b/src/sst.c
index 7fd0275..7e1960e 100644
--- a/src/sst.c
+++ b/src/sst.c
@@ -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();