aboutsummaryrefslogtreecommitdiff
path: root/src/sst.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sst.c')
-rw-r--r--src/sst.c231
1 files changed, 148 insertions, 83 deletions
diff --git a/src/sst.c b/src/sst.c
index 7628007..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 != 11
+#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_11_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,7 +270,7 @@ static bool already_loaded = false, skip_unload = false;
// auto-update message. see below in do_featureinit()
static const char *updatenotes = "\
-* Fixed buggy addon fix behaviour in some L4D2 versions\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
@@ -322,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();
@@ -344,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
@@ -395,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)) {
@@ -418,71 +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->v2.basename[0] == 0 || plugin->v2.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) return;
- if (!CHECK_AllowPluginLoading(true)) return;
- orig_plugin_load_cb(args);
- EMIT_PluginLoaded();
+DEF_CCMD_COMPAT_HOOK(plugin_load) {
+ if (argc > 1 && !CHECK_AllowPluginLoading(true)) return;
+ int prevnplugins = pluginhandler->plugins.sz;
+ 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) return;
- if (!CHECK_AllowPluginLoading(false)) return;
- if (!*args->argv[1]) {
- errmsg_errorx("plugin_unload expects a numeric index");
- return;
- }
- // 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 just confusing and unhelpful. don't worry about
- // numeric range/overflow, worst case scenario we get a sev 9.8 CVE for it.
- char *end;
- int idx = strtol(args->argv[1], &end, 10);
- if (end == args->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;
+
+// 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 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(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 UNLOAD_SKIP;
+ }
+ if (*end) {
+ errmsg_errorx("unexpected trailing characters "
+ "(plugin_unload takes a number)");
+ 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 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;
+ }
+ // if it's some other plugin being unloaded, we can keep doing stuff
+ // after, so we raise the event.
+ return UNLOAD_OTHER;
+ }
}
- if (*end) {
- errmsg_errorx("unexpected trailing characters "
- "(plugin_unload takes a number)");
- return;
+ // 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();
}
- 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.common;
- if (common->theplugin == &plugin_obj) {
- sst_userunloaded = true;
- ownidx = idx;
- }
-#ifdef __clang__
- // thanks clang for forcing use of return here and THEN warning about it
-#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
+}
+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 it's some other plugin being unloaded, we can keep doing stuff after
- orig_plugin_unload_cb(args);
- EMIT_PluginUnloaded();
}
static bool do_load(ifacefactory enginef, ifacefactory serverf) {
@@ -492,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
@@ -509,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;
}
@@ -521,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();