aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gamedata/engine.txt36
-rw-r--r--gamedata/gamelib.txt2
-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
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_";
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();