diff options
Diffstat (limited to 'src/sst.c')
| -rw-r--r-- | src/sst.c | 231 |
1 files changed, 148 insertions, 83 deletions
@@ -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(); |
