aboutsummaryrefslogtreecommitdiff
path: root/src/sst.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sst.c')
-rw-r--r--src/sst.c188
1 files changed, 122 insertions, 66 deletions
diff --git a/src/sst.c b/src/sst.c
index 70921e0..3ed92ee 100644
--- a/src/sst.c
+++ b/src/sst.c
@@ -240,11 +240,11 @@ DEF_CCMD_HERE(sst_printversion, "Display plugin version information", 0) {
// interested parties identify the version of SST used by just writing a dummy
// cvar to the top of the demo. this will be removed later, once there's a less
// stupid way of achieving the same goal.
-#if VERSION_MAJOR != 0 || VERSION_MINOR != 12
+#if VERSION_MAJOR != 0 || VERSION_MINOR != 17
#error Need to change this manually, since gluegen requires it to be spelled \
out in DEF_CVAR - better yet, can we get rid of this yet?
#endif
-DEF_CVAR(__sst_0_12_beta, "", 0, CON_HIDDEN | CON_DEMO)
+DEF_CVAR(__sst_0_17_beta, "", 0, CON_INIT_HIDDEN | CON_DEMO)
// most plugin callbacks are unused - define dummy functions for each signature
static void VCALLCONV nop_v_v(void *this) {}
@@ -270,8 +270,7 @@ static bool already_loaded = false, skip_unload = false;
// auto-update message. see below in do_featureinit()
static const char *updatenotes = "\
-* Fixed unsupported console variables not being hidden\n\
-* Fixed some other smaller regressions in feature initialisation logic\n\
+* Fix sst_mouse_factor being hidden, causing toggle binds to break\n\
";
enum { // used in generated code, must line up with featmsgs arrays below
@@ -323,16 +322,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();
@@ -345,7 +343,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
@@ -396,8 +394,19 @@ static bool deferinit() {
// Arbitrary check to infer whether we've been early- or late-loaded.
// We used to just see whether gameui.dll/libgameui.so was loaded, but
// Portal 2 does away with the separate gameui library, so now we just call
- // CEngineVGui::IsInitialized() which works everywhere.
- if (VGuiIsInitialized(vgui)) return false;
+ // CEngineVGui::IsInitialized() which works everywhere on NE. On OE (Windows
+ // only), we still do the GameUI check, because I was struggling to get the
+ // vgui check to work consistently (maybe I just had the wrong vtable index
+ // for IsInitialized?). TODO(opt): I guess it would be faster to do the
+ // virtual call if we can figure out how to make it work...
+ if_hot (has_vtidx_VGuiIsInitialized) {
+ if (VGuiIsInitialized(vgui)) return false;
+ }
+#ifdef _WIN32
+ else {
+ if (GetModuleHandleW(L"GameUI.dll")) return false;
+ }
+#endif
sst_earlyloaded = true; // let other code know
if_cold (!os_mprot(vgui->vtable + vtidx_VGuiConnect, ssizeof(void *),
PAGE_READWRITE)) {
@@ -419,79 +428,116 @@ DEF_EVENT(PluginLoaded)
DEF_EVENT(PluginUnloaded)
static struct con_cmd *cmd_plugin_load, *cmd_plugin_unload;
-static con_cmdcb orig_plugin_load_cb, orig_plugin_unload_cb;
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!
}
-static void hook_plugin_load_cb(const struct con_cmdargs *args) {
- if (args->argc > 1 && !CHECK_AllowPluginLoading(true)) return;
+DEF_CCMD_COMPAT_HOOK(plugin_load) {
+ if (argc > 1 && !CHECK_AllowPluginLoading(true)) return;
int prevnplugins = pluginhandler->plugins.sz;
- orig_plugin_load_cb(args);
+ orig_plugin_load_cb(argc, argv);
// note: if loading fails, we won't see an increase in the plugin count.
// we of course only want to raise the PluginLoaded event on success
if (pluginhandler->plugins.sz != prevnplugins) EMIT_PluginLoaded();
}
-static void hook_plugin_unload_cb(const struct con_cmdargs *args) {
- if (args->argc > 1) {
- if (!CHECK_AllowPluginLoading(false)) return;
- if (!*args->argv[1]) {
+
+// plugin_unload hook is kind of special. We have to force a tail call when
+// unloading SST itself, so we can't use the regular hook wrappers. Instead we
+// need to specifically and directly hook the two callback interfaces.
+static union {
+ con_cmdcbv1 v1;
+ con_cmdcbv2 v2;
+} orig_plugin_unload_cb;
+
+enum unload_action {
+ UNLOAD_SKIP,
+ UNLOAD_SELF,
+ UNLOAD_OTHER
+};
+static int hook_plugin_unload_common(int argc, const char **argv) {
+ if (argc > 1) {
+ if (!CHECK_AllowPluginLoading(false)) return UNLOAD_SKIP;
+ if (!*argv[1]) {
errmsg_errorx("plugin_unload expects a number, got an empty string");
- return;
+ return UNLOAD_SKIP;
}
// catch the very common user error of plugin_unload <name> and try to
// hint people in the right direction. otherwise strings get atoi()d
// silently into zero, which is confusing and unhelpful. don't worry
// about numeric range/overflow, worst case scenario it's a sev 9.8 CVE.
char *end;
- int idx = strtol(args->argv[1], &end, 10);
- if (end == args->argv[1]) {
+ int idx = strtol(argv[1], &end, 10);
+ if (end == argv[1]) {
errmsg_errorx("plugin_unload takes a number, not a name");
errmsg_note("use plugin_print to get a list of plugin indices");
- return;
+ return UNLOAD_SKIP;
}
if (*end) {
errmsg_errorx("unexpected trailing characters "
"(plugin_unload takes a number)");
- return;
+ return UNLOAD_SKIP;
}
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;
-#ifdef __clang__
- // thanks clang for forcing use of return here and ALSO warning!
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wpedantic"
- __attribute__((musttail)) return orig_plugin_unload_cb(args);
-#pragma clang diagnostic pop
-#else
-#error We are tied to clang without an assembly solution for this!
-#endif
+ return UNLOAD_SELF;
}
// if it's some other plugin being unloaded, we can keep doing stuff
// after, so we raise the event.
- orig_plugin_unload_cb(args);
+ return UNLOAD_OTHER;
+ }
+ }
+ // error case, pass through to original to log the appropriate message
+ return UNLOAD_OTHER;
+}
+
+static void hook_plugin_unload_cbv1() {
+ extern int *_con_argc;
+ extern const char **_con_argv;
+ 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(struct con_cmdargs *args) {
+ int action = hook_plugin_unload_common(args->argc, args->argv);
+ switch_exhaust_enum(unload_action, action) {
+ case UNLOAD_SKIP:
return;
- }
+ case UNLOAD_SELF:
+ tailcall orig_plugin_unload_cb.v2(args);
+ case UNLOAD_OTHER:
+ orig_plugin_unload_cb.v2(args);
+ EMIT_PluginUnloaded();
}
- // if the index is either unspecified or out-of-range, let the original
- // handler produce an appropriate error message
- orig_plugin_unload_cb(args);
}
static bool do_load(ifacefactory enginef, ifacefactory serverf) {
@@ -501,6 +547,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
@@ -518,11 +565,15 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) {
if (!deferinit()) { do_featureinit(); fixes_apply(); }
if_hot (pluginhandler) {
cmd_plugin_load = con_findcmd("plugin_load");
- orig_plugin_load_cb = cmd_plugin_load->cb;
- cmd_plugin_load->cb = &hook_plugin_load_cb;
+ hook_plugin_load_cb(cmd_plugin_load);
cmd_plugin_unload = con_findcmd("plugin_unload");
- orig_plugin_unload_cb = cmd_plugin_unload->cb;
- cmd_plugin_unload->cb = &hook_plugin_unload_cb;
+ orig_plugin_unload_cb.v1 = cmd_plugin_unload->cb_v1; // n.b.: union!
+ 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;
}
@@ -530,18 +581,23 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) {
static void do_unload() {
// slow path: reloading shouldn't happen all the time, prioritise fast exit
if_cold (sst_userunloaded) { // note: if we're here, pluginhandler is set
- cmd_plugin_load->cb = orig_plugin_load_cb;
- cmd_plugin_unload->cb = orig_plugin_unload_cb;
+ unhook_plugin_load_cb(cmd_plugin_load);
+ cmd_plugin_unload->cb_v1 = orig_plugin_unload_cb.v1;
#ifdef _WIN32 // this bit is only relevant in builds that predate linux support
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();