aboutsummaryrefslogtreecommitdiff
path: root/src/con_.c
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/con_.c
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/con_.c')
-rw-r--r--src/con_.c576
1 files changed, 424 insertions, 152 deletions
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)