diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/con_.c | 8 | ||||
| -rw-r--r-- | src/gameinfo.c | 212 | ||||
| -rw-r--r-- | src/gameinfo.h | 14 | ||||
| -rw-r--r-- | src/gametype.h | 17 | ||||
| -rw-r--r-- | src/hook.c | 4 | ||||
| -rw-r--r-- | src/os-unix.h | 11 | ||||
| -rw-r--r-- | src/os-win32.h | 13 | ||||
| -rw-r--r-- | src/os.h | 1 | ||||
| -rw-r--r-- | src/sst.c | 21 | 
9 files changed, 145 insertions, 156 deletions
@@ -23,7 +23,6 @@  #include "abi.h"  #include "con_.h"  #include "extmalloc.h" -#include "gameinfo.h"  #include "gametype.h"  #include "mem.h"  #include "os.h" @@ -427,9 +426,6 @@ bool con_init(void *(*f)(const char *, int *), int plugin_ver) {  			// (which for some other reason also has some vtable changes)  			if (VCALL(_con_iface, FindVar, "avatarbasemodel")) {  				_gametype_tag |= _gametype_tag_L4DS; -				// stupid hack: gameinfo.txt still just says Left 4 Dead 2 but -				// this is _not_ Left 4 Dead 2, dammit -				gameinfo_title = "Left 4 Dead: Survivors";  			}  			else {  				_gametype_tag |= _gametype_tag_L4D2; @@ -441,8 +437,7 @@ bool con_init(void *(*f)(const char *, int *), int plugin_ver) {  			_gametype_tag |= _gametype_tag_L4D1;  		}  		else { -			con_warn("sst: error: game \"%s\" is unsupported (using " -						"VEngineCvar007)\n", gameinfo_title); +			con_warn("sst: error: game is unsupported (using VEngineCvar007)\n");  			ifacever = 7;  			goto e;  		} @@ -478,7 +473,6 @@ warnoe:	con_warn("sst: error: old engine console support is not implemented\n");  e:	con_msg("\n\n");  	con_msg("-- Please include ALL of the following if asking for help:\n");  	con_msg("--   plugin:     " LONGNAME " v" VERSION "\n"); -	con_msg("--   game:       %s\n", gameinfo_title);  	con_msg("--   interfaces: %d/%d\n", plugin_ver, ifacever);  	con_msg("\n\n");  	return false; diff --git a/src/gameinfo.c b/src/gameinfo.c index 7dfcc53..42bf3b4 100644 --- a/src/gameinfo.c +++ b/src/gameinfo.c @@ -22,40 +22,41 @@  #endif  #include "con_.h" +#include "gametype.h"  #include "intdefs.h"  #include "kv.h"  #include "os.h" +#include "vcall.h" -// Formatting for os_char * -> char * (or vice versa) - needed for con_warn()s -// with file paths, etc  #ifdef _WIN32  #define fS "S" // os string (wide string) to regular string  #define Fs L"S" // regular string to os string (wide string) +#define PATHSEP L"\\" // for joining. could just be / but \ is more consistent  #else  // everything is just a regular string already  #define fS "s"  #define Fs "s" +#define PATHSEP "/"  #endif -static os_char exedir[PATH_MAX]; -static os_char gamedir[PATH_MAX]; -static char _gameinfo_title[64] = {0}; -const char *gameinfo_title = _gameinfo_title; -static os_char _gameinfo_clientlib[PATH_MAX] = {0}; -const os_char *gameinfo_clientlib = _gameinfo_clientlib; -static os_char _gameinfo_serverlib[PATH_MAX] = {0}; -const os_char *gameinfo_serverlib = _gameinfo_serverlib; +// TODO(opt): get rid of the rest of the snprintf and strcpy, some day -// magical argc/argv grabber so we don't have to go through procfs -#ifdef __linux__ -static const char *const *prog_argv; -static int storeargs(int argc, char *argv[]) { -	prog_argv = (const char *const *)argv; -	return 0; -} -__attribute__((used, section(".init_array"))) -static void *pstoreargs = (void *)&storeargs; +static os_char bindir[PATH_MAX] = {0}; +#ifdef _WIN32 +static os_char gamedir[PATH_MAX] = {0}; +#endif +static os_char clientlib[PATH_MAX] = {0}; +static os_char serverlib[PATH_MAX] = {0}; +static char title[64] = {0}; + +const os_char *gameinfo_bindir = bindir; +const os_char *gameinfo_gamedir +#ifdef _WIN32 +	= gamedir; // on linux, the pointer gets directly set in gameinfo_init()  #endif +const os_char *gameinfo_clientlib = clientlib; +const os_char *gameinfo_serverlib = serverlib; +const char *gameinfo_title = title;  // case insensitive substring match, expects s2 to be lowercase already!  // note: in theory this shouldn't need to be case sensitive, but I've seen mods @@ -65,7 +66,7 @@ static bool matchtok(const char *s1, const char *s2, usize sz) {  	return true;  } -static void try_gamelib(const os_char *path, os_char *outpath) { +static void trygamelib(const os_char *path, os_char *outpath) {  	// _technically_ this is toctou, but I don't think that matters here  	if (os_access(path, F_OK) != -1) {  		os_strcpy(outpath, path); @@ -77,13 +78,14 @@ static void try_gamelib(const os_char *path, os_char *outpath) {  }  // note: p and len are a non-null-terminated string -static inline void do_gamelib_search(const char *p, uint len, bool isgamebin) { +static inline void dolibsearch(const char *p, uint len, bool isgamebin, +		const os_char *cwd) {  	// sanity check: don't do a bunch of work for no reason  	if (len >= PATH_MAX - 1 - (sizeof("client" OS_DLSUFFIX) - 1)) goto toobig;  	os_char bindir[PATH_MAX];  	os_char *outp = bindir;  	// this should really be an snprintf, meh whatever -	os_strcpy(bindir, exedir); +	os_strcpy(bindir, cwd);  	outp = bindir + os_strlen(bindir);  	// quick note about windows encoding conversion: this MIGHT clobber the  	// encoding of non-ascii mod names, but it's unclear if/how source handles @@ -136,17 +138,18 @@ toobig: con_warn("gameinfo: skipping an overly long search path\n");  	outp += ret;  	if (!*gameinfo_clientlib) {  		os_strcpy(outp, OS_LIT("client" OS_DLSUFFIX)); -		try_gamelib(bindir, _gameinfo_clientlib); +		trygamelib(bindir, clientlib);  	}  	if (!*gameinfo_serverlib) {  		os_strcpy(outp, OS_LIT("server" OS_DLSUFFIX)); -		try_gamelib(bindir, _gameinfo_serverlib); +		trygamelib(bindir, serverlib);  	}  } -// state for the callback below to keep it somewhat reentrant-ish (except where -// it isn't because I got lazy and wrote some spaghetti) +// state for the callback below to keep it somewhat reentrant (except where +// it's not because I got lazy and wrote some spaghetti)  struct kv_parsestate { +	const os_char *cwd;  	// after parsing a key we *don't* care about, how many nested subkeys have  	// we come across?  	short dontcarelvl; @@ -208,21 +211,21 @@ static void kv_cb(enum kv_token type, const char *p, uint len, void *_ctxt) {  			break;  		case KV_VAL: case KV_VAL_QUOTED:  			if (ctxt->dontcarelvl) break; -			if (ctxt->matchtype == mt_title) { +			// dumb hack: ignore Survivors title (they left it set to "Left 4 +			// Dead 2" but it clearly isn't Left 4 Dead 2) +			if (ctxt->matchtype == mt_title && !GAMETYPE_MATCHES(L4DS)) {  				// title really shouldn't get this long, but truncate just to  				// avoid any trouble...  				// also note: leaving 1 byte of space for null termination (the  				// buffer is already zeroed initially) -				if (len > sizeof(_gameinfo_title) - 1) { -					len = sizeof(_gameinfo_title) - 1; -				} -				memcpy(_gameinfo_title, p, len); +				if (len > sizeof(title) - 1) len = sizeof(title) - 1; +				memcpy(title, p, len);  			}  			else if (ctxt->matchtype == mt_game ||  					ctxt->matchtype == mt_gamebin) {  				// if we already have everything, we can just stop!  				if (*gameinfo_clientlib && *gameinfo_serverlib) break; -				do_gamelib_search(p, len, ctxt->matchtype == mt_gamebin); +				dolibsearch(p, len, ctxt->matchtype == mt_gamebin, ctxt->cwd);  			}  			ctxt->matchtype = mt_none;  			break; @@ -236,112 +239,67 @@ static void kv_cb(enum kv_token type, const char *p, uint len, void *_ctxt) {  	#undef MATCH  } -bool gameinfo_init(void) { -	const os_char *modname = OS_LIT("hl2"); -#ifdef _WIN32 -	int len = GetModuleFileNameW(0, exedir, PATH_MAX); -	if (!len) { -		char err[128]; -		OS_WINDOWS_ERROR(err); -		con_warn("gameinfo: couldn't get EXE path: %s\n", err); -		return false; -	} -	// if the buffer is full and has no null, it's truncated -	if (len == PATH_MAX && exedir[len - 1] != L'\0') { -		con_warn("gameinfo: EXE path is too long!\n"); -		return false; +bool gameinfo_init(void *(*ifacef)(const char *, int *)) { +	typedef char *(*VCALLCONV GetGameDirectory_func)(void *this); +	GetGameDirectory_func **engclient; +	int off; +	if (engclient = ifacef("VEngineClient014", 0)) { // bms, l4d2 2000 +		off = 36;  	} -#else -	int len = readlink("/proc/self/exe", exedir, PATH_MAX); -	if (len == -1) { -		con_warn("gameinfo: couldn't get program path: %s\n", strerror(errno)); -		return false; +	else if (engclient = ifacef("VEngineClient013", 0)) { // ...most things? +		if (GAMETYPE_MATCHES(L4D2)) off = 36; // they changed it BACK?!? +		else off = 35;  	} -	// if the buffer is full at all, it's truncated (readlink never writes \0) -	if (len == PATH_MAX) { -		con_warn("gameinfo: program path is too long!\n"); -		return false; +	else if (engclient = ifacef("VEngineClient012", 0)) { // dmomm, ep1, ... +		off = 37;  	}  	else { -		exedir[len] = '\0'; +		con_warn("gameinfo: unsupported VEngineClient interface\n"); +		return false;  	} -#endif -	// find the last slash -	os_char *p; -	for (p = exedir + len - 1; *p != OS_LIT('/') -#ifdef _WIN32 -			&& *p != L'\\' -#endif -	;		--p); -	// ... and split on it -	*p = 0; -	const os_char *exename = p + 1; -#ifdef _WIN32 -	// try and infer the default mod name (when -game isn't given) from the exe -	// name for a few known games -	if (!_wcsicmp(exename, L"left4dead2.exe")) modname = L"left4dead2"; -	else if (!_wcsicmp(exename, L"left4dead.exe")) modname = L"left4dead"; -	else if (!_wcsicmp(exename, L"portal2.exe")) modname = L"portal2"; +	GetGameDirectory_func GetGameDirectory = (*engclient)[off]; -	const ushort *args = GetCommandLineW(); -	const ushort *argp = args; -	ushort modbuf[PATH_MAX]; -	// have to take the _last_ occurence of -game because sourcemods get the -	// flag twice, for some reason -	while (argp = wcsstr(argp, L" -game ")) { -		argp += 7; -		while (*argp == L' ') ++argp; -		ushort sep = L' '; -		// WARNING: not handling escaped quotes and such nonsense, since you -		// can't have quotes in filepaths anyway outside of UNC and I'm just -		// assuming there's no way Source could even be started with such an -		// insanely named mod. We'll see how this assumption holds up! -		if (*argp == L'"') { -			++argp; -			sep = L'"'; -		} -		ushort *bufp = modbuf; -		for (; *argp != L'\0' && *argp != sep; ++argp, ++bufp) { -			if (bufp - modbuf == PATH_MAX - 1) { -				con_warn("gameinfo: mod name parameter is too long\n"); -				return false; -			} -			*bufp = *argp; -		} -		*bufp = L'\0'; -		modname = modbuf; +	// engine always calls chdir() with its own base path on startup, so engine +	// base dir is just cwd +	os_char cwd[PATH_MAX]; +	if (!os_getcwd(cwd, sizeof(cwd) / sizeof(*cwd))) { +		con_warn("gameinfo: couldn't get working directory: %s\n", +				strerror(errno)); +		return false;  	} -	bool isrelative = PathIsRelativeW(modname); -#else -	// also do the executable name check just for portal2_linux -	if (!strcmp(exename, "portal2_linux")) modname = "portal2"; -	// ah, the sane, straightforward world of unix command line arguments :) -	for (const char *const *pp = prog_argv + 1; *pp; ++pp) { -		if (!strcmp(*pp, "-game")) { -			if (!*++pp) break; -			modname = *pp; -		} +	int len = os_strlen(cwd); +	if (len + sizeof("/bin") > sizeof(bindir) / sizeof(*bindir)) { +		con_warn("gameinfo: working directory path is too long!\n"); +		return false;  	} -	// ah, the sane, straightforward world of unix paths :) -	bool isrelative = modname[0] != '/'; -#endif +	memcpy(bindir, cwd, len * sizeof(*cwd)); +	memcpy(bindir + len, PATHSEP OS_LIT("bin"), 5 * sizeof(os_char)); -	int ret = isrelative ? -		os_snprintf(gamedir, PATH_MAX, OS_LIT("%s/%s"), exedir, modname) : -		// mod name might actually be an absolute (if installed in steam -		// sourcemods for example) -		os_snprintf(gamedir, PATH_MAX, OS_LIT("%s"), modname); -	if (ret >= PATH_MAX) { -		con_warn("gameinfo: game directory path is too long!\n"); +#ifdef _WIN32 +	int gamedirlen = _snwprintf(gamedir, sizeof(gamedir) / sizeof(*gamedir), +				L"%S", GetGameDirectory(engclient)); +	if (gamedirlen < 0) { // encoding error??? ugh... +		con_warn("gameinfo: invalid game directory path!\n");  		return false;  	} -	os_char gameinfopath[PATH_MAX]; -	if (os_snprintf(gameinfopath, PATH_MAX, OS_LIT("%s/gameinfo.txt"), -			gamedir) >= PATH_MAX) { -		con_warn("gameinfo: gameinfo.txt path is too long!\n"); +	// immediately bounds check /gameinfo as we cat that into an equal sized +	// buffer down below :^) +	if (gamedirlen + sizeof("/gameinfo.txt") > sizeof(gamedir) / +			sizeof(*gamedir)) { +		con_warn("gameinfo: game directory path is too long!\n");  		return false;  	} +#else +	// no need to munge charset, use the string pointer directly +	gameinfo_gamedir = GetGameDirectory(engclient); +	int gamedirlen = strlen(gameinfo_gamedir); +#endif +	os_char gameinfopath[PATH_MAX]; +	memcpy(gameinfopath, gameinfo_gamedir, gamedirlen * +			sizeof(*gameinfo_gamedir)); +	memcpy(gameinfopath + gamedirlen, PATHSEP OS_LIT("gameinfo.txt"), +			14 * sizeof(os_char));  	int fd = os_open(gameinfopath, O_RDONLY);  	if (fd == -1) {  		con_warn("gameinfo: couldn't open gameinfo.txt: %s\n", strerror(errno)); @@ -349,7 +307,7 @@ bool gameinfo_init(void) {  	}  	char buf[1024];  	struct kv_parser kvp = {0}; -	struct kv_parsestate ctxt = {0}; +	struct kv_parsestate ctxt = {.cwd = cwd};  	int nread;  	while (nread = read(fd, buf, sizeof(buf))) {  		if (nread == -1) { @@ -360,8 +318,10 @@ bool gameinfo_init(void) {  		if (!kv_parser_feed(&kvp, buf, nread, &kv_cb, &ctxt)) goto ep;  	}  	if (!kv_parser_done(&kvp)) goto ep; -  	close(fd); + +	// dumb hack pt2, see also kv callback above +	if (GAMETYPE_MATCHES(L4DS)) gameinfo_title = "Left 4 Dead: Survivors";  	return true;  ep:	con_warn("gameinfo: couldn't parse gameinfo.txt (%d:%d): %s\n", diff --git a/src/gameinfo.h b/src/gameinfo.h index d94b117..eac5009 100644 --- a/src/gameinfo.h +++ b/src/gameinfo.h @@ -1,5 +1,5 @@  /* - * Copyright © 2021 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2022 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 @@ -17,20 +17,24 @@  #ifndef INC_GAMEINFO_H  #define INC_GAMEINFO_H +#include <stdio.h> +  #include "intdefs.h"  #include "os.h"  /* These variables are only set after calling gameinfo_init(). */ -extern const char *gameinfo_title;			/* Name of the game (window title) */ -extern const os_char *gameinfo_clientlib;	/* Path to the client library */ -extern const os_char *gameinfo_serverlib;	/* Path to the server library */ +extern const os_char *gameinfo_bindir;    /* Absolute path to top-level bin/ */ +extern const os_char *gameinfo_gamedir;   /* Absolute path to game directory */ +extern const char    *gameinfo_title;     /* Name of the game (window title) */ +extern const os_char *gameinfo_clientlib; /* Absolute path to the client lib */ +extern const os_char *gameinfo_serverlib; /* Absolute path to the server lib */  /*   * This function is called early in the plugin load and does a whole bunch of   * spaghetti magic to figure out which game/engine we're in and where its   * libraries (which we want to hook) are located.   */ -bool gameinfo_init(void); +bool gameinfo_init(void *(*ifacef)(const char *, int *));  #endif diff --git a/src/gametype.h b/src/gametype.h index c8bdf8a..fbf8db2 100644 --- a/src/gametype.h +++ b/src/gametype.h @@ -22,18 +22,23 @@  extern u32 _gametype_tag;  #define _gametype_tag_OE		1 -#define _gametype_tag_OrangeBox	2 -#define _gametype_tag_L4D1		4 -#define _gametype_tag_L4D2		8 -#define _gametype_tag_L4DS		16 -#define _gametype_tag_Portal2	32 -#define _gametype_tag_2013		64 +// TODO(compat): detect in con_init, even if just to fail (VEngineServer broke) +// TODO(compat): buy dmomm in a steam sale to implement and test the above, lol +#define _gametype_tag_DMoMM		2 +#define _gametype_tag_OrangeBox	4 +#define _gametype_tag_L4D1		8 +#define _gametype_tag_L4D2		16 +#define _gametype_tag_L4DS		32 +#define _gametype_tag_Portal2	64 +#define _gametype_tag_2013		128  #define _gametype_tag_L4D		(_gametype_tag_L4D1 | _gametype_tag_L4D2)  // XXX: *stupid* naming, refactor later (damn Survivors ruining everything)  #define _gametype_tag_L4D2x		(_gametype_tag_L4D2 | _gametype_tag_L4DS)  #define _gametype_tag_L4Dbased \  	(_gametype_tag_L4D1 | _gametype_tag_L4D2x | _gametype_tag_Portal2) +#define _gametype_tag_OrangeBoxbased \ +	(_gametype_tag_OrangeBox | _gametype_tag_2013)  #define GAMETYPE_MATCHES(x) !!(_gametype_tag & (_gametype_tag_##x)) @@ -1,5 +1,5 @@  /* - * Copyright © 2021 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com>   * Copyright © 2022 Willian Henrique <wsimanbrazil@yahoo.com.br>   *   * Permission to use, copy, modify, and/or distribute this software for any @@ -97,7 +97,7 @@ void unhook_inline(void *orig) {  	int len = p[-1];  	int off = mem_load32(p + len + 1);  	uchar *q = p + off + 5; -	memcpy(q, p, 5); // XXX not atomic atm! (does any of it even need to be?) +	memcpy(q, p, 5); // XXX: not atomic atm! (does any of it even need to be?)  	FlushInstructionCache(GetCurrentProcess(), q, 5);  } diff --git a/src/os-unix.h b/src/os-unix.h index 3b63bcc..6522029 100644 --- a/src/os-unix.h +++ b/src/os-unix.h @@ -35,6 +35,7 @@ typedef char os_char;  #define os_access access  #define os_stat stat  #define os_getenv getenv +#define os_getcwd getcwd  #define OS_DLSUFFIX ".so" @@ -42,6 +43,16 @@ typedef char os_char;  #define os_dlsym dlsym +static inline bool os_dlfile(void *m, char *buf, int sz) { +	// NOTE: this might be linux/glibc-specific (I haven't checked every +	// implementation). this is fine as we don't use it in any build-time code, +	// only in the plugin itself. just keep it in mind! +	struct link_map *lm = m; +	ssz len = strlen(lm->l_name) + 1; +	if (ssz > sz) { errno = ENAMETOOLONG; return false; } +	memcpy(buf, lm->l_name, ssz); return true; +} +  // unix mprot flags are much nicer but cannot be defined in terms of the windows  // ones, so we use the windows ones and define them in terms of the unix ones.  // another victory for stupid! diff --git a/src/os-win32.h b/src/os-win32.h index fb31d67..c7c0bae 100644 --- a/src/os-win32.h +++ b/src/os-win32.h @@ -41,6 +41,7 @@ typedef unsigned short os_char;  #define _stat64(path, buf) _wstat64(path, buf)  #define os_stat _stat64  #define os_getenv _wgetenv +#define os_getcwd _wgetcwd  #define OS_DLSUFFIX ".dll" @@ -50,6 +51,18 @@ static inline void *os_dlsym(void *m, const char *s) {  	return (void *)GetProcAddress(m, s);  } +static inline bool os_dlfile(void *m, unsigned short *buf, int sz) { +	unsigned int n = GetModuleFileNameW(m, buf, sz); +	if (n == 0 || n == sz) return false; +	// get the canonical capitalisation, as for some reason GetModuleFileName() +	// returns all lowercase. this doesn't really matter except it looks nicer +	GetLongPathNameW(buf, buf, n + 1); +	// the drive letter will also be lower case, if it is an actual drive letter +	// of course. it should be; I'm not gonna lose sleep over UNC paths and such +	if (buf[0] >= L'a' && buf[0] <= L'z' && buf[1] == L':') buf[0] &= ~32u; +	return true; +} +  static inline bool os_mprot(void *addr, int len, int fl) {  	unsigned long old;  	return !!VirtualProtect(addr, len, fl, &old); @@ -42,6 +42,7 @@  #else  #include <dlfcn.h>  #include <limits.h> +#include <link.h>  #include <stdio.h>  #include <string.h>  #include <strings.h> @@ -35,7 +35,7 @@  u32 _gametype_tag = 0; // spaghetti: no point making a .c file for 1 variable -static int plugin_ver; +static int ifacever;  // this is where we start dynamically adding virtual functions, see vtable[]  // array below  static const void **vtable_firstdiff; @@ -75,6 +75,11 @@ static bool has_demorec_custom = false;  static bool has_rinput = false;  #endif +// 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; +  // HACK: later versions of L4D2 show an annoying dialog on every plugin_load.  // We can suppress this by catching the message string that's passed from  // engine.dll to gameui.dll through KeyValuesSystem in vstdlib.dll and just @@ -147,7 +152,8 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) {  #ifndef __linux__  	void *clientlib = 0;  #endif -	if (!gameinfo_init() || !con_init(enginef, plugin_ver)) return false; +	if (!con_init(enginef, ifacever)) return false; +	if (!gameinfo_init(enginef)) { con_disconnect(); return false; }  	const void **p = vtable_firstdiff;  	if (GAMETYPE_MATCHES(Portal2)) *p++ = (void *)&nop_p_v; // ClientFullyConnect  	*p++ = (void *)&nop_p_v;		  // ClientDisconnect @@ -155,9 +161,9 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) {  	*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 +	*p++ = ifacever > 1 ? (void *)&nop_pp_i : (void *)&nop_p_i; // ClientCommand  	// remaining stuff here is backwards compatible, so added unconditionally +	*p++ = (void *)&nop_pp_i;		  // NetworkIDValidated  	*p++ = (void *)&nop_ipipp_v;	  // OnQueryCvarValueFinished (002+)  	*p++ = (void *)&nop_p_v;		  // OnEdictAllocated  	*p   = (void *)&nop_p_v;		  // OnEdictFreed @@ -248,11 +254,6 @@ static void do_unload(void) {  	}  } -// 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) { @@ -316,7 +317,7 @@ 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'; +			ifacever = name[24] - '0';  			return &plugin_obj;  		}  	}  | 
