diff options
Diffstat (limited to 'src/sst.c')
-rw-r--r-- | src/sst.c | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/src/sst.c b/src/sst.c new file mode 100644 index 0000000..dbee4b7 --- /dev/null +++ b/src/sst.c @@ -0,0 +1,206 @@ +/* + * Copyright © 2021 Michael Smith <mikesmiffy128@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 + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdbool.h> + +#include "con_.h" +#include "demorec.h" +#include "factory.h" +#include "gamedata.h" +#include "gameinfo.h" +#include "gametype.h" +#include "hook.h" +#include "os.h" +#include "vcall.h" +#include "version.h" + +#define RGBA(r, g, b, a) (&(struct con_colour){(r), (g), (b), (a)}) + +u32 _gametype_tag = 0; // spaghetti: no point making a .c file for 1 variable + +static int plugin_ver; +// this is where we start dynamically adding virtual functions, see vtable[] +// array below +static const void **vtable_firstdiff; + +// most plugin callbacks are unused - define dummy functions for each signature +static void VCALLCONV nop_v_v(void *this) {} +static void VCALLCONV nop_b_v(void *this, bool b) {} +static void VCALLCONV nop_p_v(void *this, void *p) {} +static void VCALLCONV nop_pp_v(void *this, void *p1, void *p2) {} +static void VCALLCONV nop_pii_v(void *this, void *p, int i1, int i2) {} +static int VCALLCONV nop_p_i(void *this, void *p) { return 0; } +static int VCALLCONV nop_pp_i(void *this, void *p1, void *p2) { return 0; } +static int VCALLCONV nop_5pi_i(void *this, void *p1, void *p2, void *p3, + void *p4, void *p5, int i) { return 0; } +static void VCALLCONV nop_ipipp_v(void *this, int i1, void *p1, int i2, + void *p2, void *p3) {} + +#ifdef __linux__ +// we need to keep this reference to dlclose() it later - see below +static void *clientlib = 0; +#endif + +// more source spaghetti wow! +static void VCALLCONV SetCommandClient(void *this, int i) { con_cmdclient = i; } + +ifacefactory factory_client = 0, factory_server = 0, factory_engine = 0; + +// TODO(featgen): I wanted some nice fancy automatic feature system that +// figures out the dependencies at build time and generates all the init glue +// but we want to actually release the plugin this decade so for now I'm just +// plonking ~~some bools~~ one bool here and worrying about it later. :^) +static bool has_demorec = false; + +static bool do_load(ifacefactory enginef, ifacefactory serverf) { + factory_engine = enginef; factory_server = serverf; +#ifndef __linux__ + void *clientlib = 0; +#endif + if (!gameinfo_init() || !con_init(enginef, plugin_ver)) return false; + const void **p = vtable_firstdiff; + if (GAMETYPE_MATCHES(Portal2)) *p++ = (void *)&nop_p_v; // ClientFullyConnect + *p++ = (void *)&nop_p_v; // ClientDisconnect + *p++ = (void *)&nop_pp_v; // ClientPutInServer + *p++ = (void *)&SetCommandClient; // SetCommandClient + *p++ = (void *)&nop_p_v; // ClientSettingsChanged + *p++ = (void *)&nop_5pi_i; // ClientConnect + *p++ = plugin_ver > 1 ? (void *)&nop_pp_i : (void *)&nop_p_i; // ClientCommand + *p++ = (void *)&nop_pp_i; // NetworkIDValidated + // remaining stuff here is backwards compatible, so added unconditionally + *p++ = (void *)&nop_ipipp_v; // OnQueryCvarValueFinished (002+) + *p++ = (void *)&nop_p_v; // OnEdictAllocated + *p = (void *)&nop_p_v; // OnEdictFreed + +#ifdef _WIN32 + //if (gameinfo_serverlib) serverlib = GetModuleHandleW(gameinfo_serverlib); + if (gameinfo_clientlib) 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 + //if (gameinfo_serverlib) serverlib = dlopen(gameinfo_serverlib, 0); + if (gameinfo_clientlib) clientlib = dlopen(gameinfo_clientlib, 0); +#endif + if (!clientlib) { + con_warn("sst: warning: couldn't get the game's client library\n"); + goto nc; + } + factory_client = (ifacefactory)os_dlsym(clientlib, "CreateInterface"); + if (!factory_client) { + con_warn("sst: warning: couldn't get client's CreateInterface\n"); + } + +nc: gamedata_init(); + // TODO(autojump): we'd init that here + has_demorec = demorec_init(); + + con_colourmsg(RGBA(64, 255, 64, 255), + NAME " v" VERSION " successfully loaded"); + con_colourmsg(RGBA(255, 255, 255, 255), " for game "); + con_colourmsg(RGBA(0, 255, 255, 255), "%s\n", gameinfo_title); + return true; +} + +static void do_unload(void) { + // TODO(autojump): we'd end that here + if (has_demorec) demorec_end(); + +#ifdef __linux__ + //if (serverlib) dlclose(serverlib); + if (clientlib) dlclose(clientlib); +#endif + con_disconnect(); +} + +// since this is static/global, it only becomes false again when the plugin SO +// is unloaded/reloaded +static bool already_loaded = false; +static bool skip_unload = false; + +static bool VCALLCONV Load(void *this, ifacefactory enginef, + ifacefactory serverf) { + if (already_loaded) { + con_warn("Already loaded! Doing nothing!\n"); + skip_unload = true; + return false; + } + already_loaded = do_load(enginef, serverf); + skip_unload = !already_loaded; + return already_loaded; +} + +static void Unload(void *this) { + // the game tries to unload on a failed load, for some reason + if (skip_unload) { + skip_unload = false; + return; + } + do_unload(); +} + +static void VCALLCONV Pause(void *this) { + con_warn(NAME " doesn't support plugin_pause - ignoring\n"); +} +static void VCALLCONV UnPause(void *this) { + con_warn(NAME " doesn't support plugin_unpause - ignoring\n"); +} + +static const char *VCALLCONV GetPluginDescription(void *this) { + return LONGNAME " v" VERSION; +} + +DEF_CCMD_HERE(sst_printversion, "Display plugin version information", 0) { + con_msg("v" VERSION "\n"); +} + +#define MAX_VTABLE_FUNCS 21 +static const void *vtable[MAX_VTABLE_FUNCS] = { + // start off with the members which (thankfully...) are totally stable + // between interface versions - the *remaining* members get filled in just + // in time by do_load() once we've figured out what engine branch we're on + (void *)&Load, + (void *)&Unload, + (void *)&Pause, + (void *)&UnPause, + (void *)&GetPluginDescription, + (void *)&nop_p_v, // LevelInit + (void *)&nop_pii_v, // ServerActivate + (void *)&nop_b_v, // GameFrame + (void *)&nop_v_v, // LevelShutdown + (void *)&nop_p_v // ClientActive + // At this point, Alien Swarm and Portal 2 add ClientFullyConnect, so we + // can't hardcode any more of the layout! +}; +// end MUST point AFTER the last of the above entries +static const void **vtable_firstdiff = vtable + 10; +// this is equivalent to a class with no members! +static const void *const *const plugin_obj = vtable; + +EXPORT const void *CreateInterface(const char *name, int *ret) { + if (!strncmp(name, "ISERVERPLUGINCALLBACKS00", 24)) { + if ((name[24] >= '1' || name[24] <= '3') && name[25] == '\0') { + if (ret) *ret = 0; + plugin_ver = name[24] - '0'; + return &plugin_obj; + } + } + if (ret) *ret = 1; + return 0; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 |