From c83de76ad2f59927cf01bcfbae18a5d598159bb7 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 19 May 2022 12:51:06 +0100 Subject: Properly solve load order issues via deferred init --- gamedata/engine.kv | 3 ++ src/build/mkentprops.c | 3 ++ src/fixes.c | 8 --- src/fixes.h | 7 --- src/sst.c | 129 ++++++++++++++++++++++++++++++++----------------- 5 files changed, 92 insertions(+), 58 deletions(-) diff --git a/gamedata/engine.kv b/gamedata/engine.kv index 840f9a1..022ccbd 100644 --- a/gamedata/engine.kv +++ b/gamedata/engine.kv @@ -79,4 +79,7 @@ off_SP_offset { //2013 72 // TODO(compat): not sure about 2013/009 yet pt3 } +// IEngineVGuiInternal/CEngineVGui +vtidx_VGuiConnect "3 + NVDTOR" // note: real name is Connect, way too generic + // vi: sw=4 ts=4 noet tw=80 cc=80 ft=plain diff --git a/src/build/mkentprops.c b/src/build/mkentprops.c index 321b954..9c366e5 100644 --- a/src/build/mkentprops.c +++ b/src/build/mkentprops.c @@ -163,6 +163,9 @@ F( " has_%s = true;", (*pp)->varname) // > can detect that and set the SPROP_IS_VECTOR_ELEM flag. // apparently if we're loaded via VDF, it hasn't been flipped back // yet. just calling abs() on everything as an easy solution. + // TODO(opt): if we moved this into deferred init we wouldn't need + // to bother with this, but that might involve untangling more + // engineapi stuff F( " %s = abs(*(int *)mem_offset(p, off_SP_offset));", (*pp)->varname) _( " if (!--needprops) break;") diff --git a/src/fixes.c b/src/fixes.c index a90d878..d055ba8 100644 --- a/src/fixes.c +++ b/src/fixes.c @@ -161,12 +161,4 @@ void fixes_apply(void) { else if (GAMETYPE_MATCHES(L4D2x)) l4d2specific(); } -void fixes_tryagainlater(void) { - if (GAMETYPE_MATCHES(L4D1)) { - // whatever dll this is in seems to load late (mm_l4d_debug is fine) - struct con_var *v = con_findvar("ui_l4d_debug"); - if (v) con_setvari(v, 0); - } -} - // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/fixes.h b/src/fixes.h index 54d8d06..ae863a8 100644 --- a/src/fixes.h +++ b/src/fixes.h @@ -20,11 +20,4 @@ */ void fixes_apply(void); -/* - * Applies specific fixes *again*, in cases where they wouldn't work very early - * in game startup (like if the plugin is loaded via VDF). Isn't guaranteed to - * be called if the plugin is loaded later, so must do things redundantly. - */ -void fixes_tryagainlater(void); - // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/sst.c b/src/sst.c index 3ad48cc..554cfe5 100644 --- a/src/sst.c +++ b/src/sst.c @@ -160,15 +160,6 @@ static const char *VCALLCONV GetStringForSymbol_hook(void *this, int s) { return ret; } -// by hooking stuffcmds, we get a callback after the last of the startup -// commands have run (the command line ones) -static struct con_cmd *cmd_stuffcmds = 0; -static con_cmdcb orig_stuffcmds_cb; -static void hook_stuffcmds_cb(const struct con_cmdargs *args) { - orig_stuffcmds_cb(args); - fixes_tryagainlater(); -} - // vstdlib symbol, only currently used in l4d2 but exists everywhere so oh well IMPORT void *KeyValuesSystem(void); @@ -207,6 +198,75 @@ static bool already_loaded = false, skip_unload = false; #define RGBA(r, g, b, a) (&(struct con_colour){(r), (g), (b), (a)}) +static void do_featureinit(void) { + has_autojump = autojump_init(); + has_demorec = demorec_init(); + // not enabling demorec_custom yet - kind of incomplete and currently unused + //if (has_demorec) demorec_custom_init(); + bool has_ent = ent_init(); + has_fov = fov_init(has_ent); + if (has_ent) l4dwarp_init(); + has_nosleep = nosleep_init(); +#ifdef _WIN32 + has_rinput = rinput_init(); +#endif + fixes_apply(); + + con_colourmsg(RGBA(64, 255, 64, 255), + LONGNAME " v" VERSION " successfully loaded"); + con_colourmsg(RGBA(255, 255, 255, 255), " for game "); + con_colourmsg(RGBA(0, 255, 255, 255), "%s\n", gameinfo_title); +} + +static void *vgui; +typedef void (*VCALLCONV VGuiConnect_func)(void); +static VGuiConnect_func orig_VGuiConnect; +static void VCALLCONV hook_VGuiConnect(void) { + orig_VGuiConnect(); + do_featureinit(); + unhook_vtable(*(void ***)vgui, vtidx_VGuiConnect, (void *)orig_VGuiConnect); +} + +// --- Magical deferred load order hack nonsense! --- +// The engine loads VDF plugins basically right after server.dll, but long +// before most other stuff, which makes hooking certain other stuff a pain. We +// still want to be able to load via VDF as it's the only reasonable way to get +// in before config.cfg, which is needed for any kind of configuration to work +// correctly. +// +// So here, we hook CEngineVGui::Connect() which is pretty much the last thing +// that gets called on init, and defer feature init till afterwards. That allows +// us to touch pretty much any engine stuff without worrying about load order +// nonsense. +// +// In do_load() below, we check to see whether we're loading early by checking +// whether gameui.dll is loaded yet; this is one of several possible arbitrary +// checks. If it's loaded already, we assume we're getting loaded late via the +// console and just init everything immediately. +// +// Route credit to Bill for helping figure a lot of this out - mike +static void deferinit(void) { + vgui = factory_engine("VEngineVGui001", 0); + if (!vgui) { + con_warn("sst: warning: couldn't get VEngineVGui for deferred " + "feature setup\n"); + goto e; + } + if (!os_mprot(*(void ***)vgui + vtidx_VGuiConnect, sizeof(void *), + PAGE_READWRITE)) { + con_warn("sst: warning: couldn't unprotect CEngineVGui vtable for " + "deferred feature setup\n"); + goto e; + } + orig_VGuiConnect = (VGuiConnect_func)hook_vtable(*(void ***)vgui, + vtidx_VGuiConnect, (void *)&hook_VGuiConnect); + return; + +e: con_warn("!!! SOME FEATURES MAY BE BROKEN !!!\n"); + // I think this is the lesser of two evils! Unlikely to happen anyway. + do_featureinit(); +} + static bool do_load(ifacefactory enginef, ifacefactory serverf) { factory_engine = enginef; factory_server = serverf; if (!engineapi_init(ifacever)) return false; @@ -226,14 +286,12 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) { *p = (void *)&nop_p_v; // OnEdictFreed #ifdef _WIN32 - //serverlib = GetModuleHandleW(gameinfo_serverlib); void *clientlib = GetModuleHandleW(gameinfo_clientlib); #else - // Linux Source load order seems to be different to the point where if we - // +plugin_load or use a vdf then RTLD_NOLOAD won't actually find these, so - // we have to just dlopen them normally - and then remember to decrement the - // refcount again later in do_unload() so nothing gets leaked - //serverlib = dlopen(gameinfo_serverlib, RTLD_NOW); + // Apparently on Linux, the client library isn't actually loaded yet here, + // so RTLD_NOLOAD won't actually find it. We have to just dlopen it + // normally - and then remember to decrement the refcount again later in + // do_unload() so nothing gets leaked! clientlib = dlopen(gameinfo_clientlib, RTLD_NOW); #endif if (!clientlib) { @@ -247,6 +305,7 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) { void *inputsystemlib = GetModuleHandleW(L"inputsystem.dll"); #else // TODO(linux): assuming the above doesn't apply to this; check if it does! + // ... actually, there's a good chance this assumption is now wrong! void *inputsystemlib = dlopen("bin/libinputsystem.so", RTLD_NOW | RLTD_NOLOAD); if (inputsystemlib) dlclose(inputsystemlib); // blegh @@ -259,19 +318,6 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) { con_warn("sst: warning: couldn't get input system's CreateInterface\n"); } - has_autojump = autojump_init(); - has_demorec = demorec_init(); - // not enabling demorec_custom yet - kind of incomplete and currently unused - //if (has_demorec) demorec_custom_init(); - bool has_ent = ent_init(); - has_fov = fov_init(has_ent); - if (has_ent) l4dwarp_init(); - has_nosleep = nosleep_init(); -#ifdef _WIN32 - has_rinput = rinput_init(); -#endif - fixes_apply(); - // NOTE: this is technically redundant for early versions but I CBA writing // a version check; it's easier to just do this unilaterally. if (GAMETYPE_MATCHES(L4D2x)) { @@ -280,22 +326,21 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) { if (!os_mprot(kvsvt + 4, sizeof(void *), PAGE_READWRITE)) { con_warn("sst: warning: couldn't unprotect KeyValuesSystem " "vtable; won't be able to prevent nag message\n"); - goto e; } - orig_GetStringForSymbol = (GetStringForSymbol_func)hook_vtable(kvsvt, 4, - (void *)GetStringForSymbol_hook); - } - - cmd_stuffcmds = con_findcmd("stuffcmds"); - if (cmd_stuffcmds) { - orig_stuffcmds_cb = cmd_stuffcmds->cb; - cmd_stuffcmds->cb = hook_stuffcmds_cb; + else { + orig_GetStringForSymbol = (GetStringForSymbol_func)hook_vtable( + kvsvt, 4, (void *)GetStringForSymbol_hook); + } } -e: con_colourmsg(RGBA(64, 255, 64, 255), - LONGNAME " v" VERSION " successfully loaded"); - con_colourmsg(RGBA(255, 255, 255, 255), " for game "); - con_colourmsg(RGBA(0, 255, 255, 255), "%s\n", gameinfo_title); +#ifdef _WIN32 + bool isvdf = !GetModuleHandleW(L"gameui.dll"); +#else + void *gameuilib = dlopen("bin/libgameui.so", RTLD_NOW | RLTD_NOLOAD); + bool isvdf = !gameuilib; + if (gameuilib) dlclose(gameuilib); +#endif + if (isvdf) deferinit(); else do_featureinit(); return true; } @@ -345,7 +390,6 @@ static void do_unload(void) { #endif #ifdef __linux__ - //if (serverlib) dlclose(serverlib); if (clientlib) dlclose(clientlib); #endif con_disconnect(); @@ -353,7 +397,6 @@ static void do_unload(void) { if (orig_GetStringForSymbol) { unhook_vtable(kvsvt, 4, (void *)orig_GetStringForSymbol); } - if (cmd_stuffcmds) cmd_stuffcmds->cb = orig_stuffcmds_cb; } static bool VCALLCONV Load(void *this, ifacefactory enginef, -- cgit v1.2.3