diff options
| author | 2023-06-12 22:28:53 +0100 | |
|---|---|---|
| committer | 2023-06-12 23:06:47 +0100 | |
| commit | 7aa6bd1cd88db9cceef3d1c07cd7664cb47538be (patch) | |
| tree | f9db39fedd4ddcc3dc29e370af5b1b9b9894a948 | |
| parent | 7893ef46f85eb5a6021d6ab763ca84e382e64954 (diff) | |
| download | sst-7aa6bd1cd88db9cceef3d1c07cd7664cb47538be.tar.gz sst-7aa6bd1cd88db9cceef3d1c07cd7664cb47538be.zip | |
Remove the terrible gameinfo.txt garbage at last
This also tidies up library handle grabbing with more os.h stuff, and
improves the VDF creation logic - since we no longer store a couple of
paths which makes it necessary to change that a bit anyway.
| -rw-r--r-- | src/engineapi.c | 1 | ||||
| -rw-r--r-- | src/gameinfo.c | 276 | ||||
| -rw-r--r-- | src/gameinfo.h | 13 | ||||
| -rw-r--r-- | src/l4dmm.c | 8 | ||||
| -rw-r--r-- | src/os-unix.h | 9 | ||||
| -rw-r--r-- | src/os-win32.h | 7 | ||||
| -rw-r--r-- | src/sst.c | 115 | 
7 files changed, 109 insertions, 320 deletions
| diff --git a/src/engineapi.c b/src/engineapi.c index 831836b..f4a54d6 100644 --- a/src/engineapi.c +++ b/src/engineapi.c @@ -71,7 +71,6 @@ bool engineapi_init(int pluginver) {  	void *pim = factory_server("PlayerInfoManager002", 0);  	if (pim) globalvars = GetGlobalVars(pim); -	inputsystem = factory_inputsystem("InputSystemVersion001", 0);  	vgui = factory_engine("VEngineVGui001", 0);  	void *srvdll; diff --git a/src/gameinfo.c b/src/gameinfo.c index 71922a6..9a6686e 100644 --- a/src/gameinfo.c +++ b/src/gameinfo.c @@ -14,226 +14,25 @@   * PERFORMANCE OF THIS SOFTWARE.   */ -#ifdef _WIN32 -#include <shlwapi.h> -#endif - -#include "con_.h"  #include "engineapi.h"  #include "errmsg.h"  #include "gamedata.h"  #include "gametype.h" -#include "intdefs.h" -#include "kv.h"  #include "os.h"  #include "vcall.h"  #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 - -// ~~TODO(opt): get rid of the rest of the snprintf and strcpy, some day~~ -// TODO(opt): remove almost all this parsing nonsense, it's not needed any more! -// We can simply GetWindowText (and do a little more work on Linux...) and do -// away with absolute paths to DLLs which won't be required with deferred init. - -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 -// use both lowercase and TitleCase so this is just to be as lenient as possible -static bool matchtok(const char *s1, const char *s2, usize sz) { -	for (; sz; --sz, ++s1, ++s2) if (tolower(*s1) != *s2) return false; -	return true; -} - -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); -	} -	else if (errno != ENOENT) { -		errmsg_warnstd("failed to access %" fS, path); -	} -} - -// note: p and len are a non-null-terminated string -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, 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 -	// that anyway, so we just have to assume there *are no* non-ascii mod -	// names, since they'd also be clobbered, probably. NOTE that this -	// assumption does NOT apply to the absolute base path; see further down. -	const os_char *fmt = isgamebin ? -		OS_LIT("/%.*") Fs OS_LIT("/") : -		OS_LIT("/%.*") Fs OS_LIT("/bin/"); -	int spaceleft = PATH_MAX; -	if (len >= 25 && matchtok(p, "|all_source_engine_paths|", 25)) { -		// this special path doesn't seem any different to normal, -		// why is this a thing? -		p += 25; len -= 25; -	} -	else if (len >= 15 && matchtok(p, "|gameinfo_path|", 15)) { -		// search in the actual mod/game directory -		p += 15; len -= 15; -		int ret = os_snprintf(bindir, PATH_MAX, OS_LIT("%s"), gamedir); -		outp = bindir + ret; -		spaceleft -= ret; -	} -	else { -#ifdef _WIN32 -		// sigh -		char api_needs_null_term[PATH_MAX]; -		memcpy(api_needs_null_term, p, len * sizeof(*p)); -		api_needs_null_term[len] = L'\0'; -		if (!PathIsRelativeA(api_needs_null_term)) -#else -		if (*p == '/') // so much easier :') -#endif -	{ -		// the mod path is absolute, so we're not sticking anything else in -		// front of it, so skip the leading slash in fmt and point the pointer -		// at the start of the buffer -		++fmt; -		outp = bindir; -	}} - -	// leave room for server/client.dll/so (note: server and client happen to -	// conveniently have the same number of letters) -	int fmtspace = spaceleft - (sizeof("client" OS_DLSUFFIX) - 1); -	int ret = os_snprintf(outp, fmtspace, fmt, len, p); -	if (ret >= fmtspace) { -toobig: errmsg_warnx("skipping an overly long search path"); -		return; -	} -	outp += ret; -	if (!*gameinfo_clientlib) { -		os_strcpy(outp, OS_LIT("client" OS_DLSUFFIX)); -		trygamelib(bindir, clientlib); -	} -	if (!*gameinfo_serverlib) { -		os_strcpy(outp, OS_LIT("server" OS_DLSUFFIX)); -		trygamelib(bindir, serverlib); -	} -} - -// 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; -	// after parsing a key we *do* care about, which key in the matchkeys[] -	// array below are we looking for next? -	schar nestlvl; -	// what kind of key did we just match? -	schar matchtype; -}; - -// this is a sprawling mess. Too Bad! -static void kv_cb(enum kv_token type, const char *p, uint len, void *_ctxt) { -	struct kv_parsestate *ctxt = _ctxt; - -	static const struct { -		const char *s; -		uint len; -	} matchkeys[] = { -		{"gameinfo", 8}, -		{"filesystem", 10}, -		{"searchpaths", 11} -	}; - -	// values for ctxt->matchtype -	enum { mt_none, mt_title, mt_nest, mt_game, mt_gamebin }; - -	#define MATCH(s) (len == sizeof(s) - 1 && matchtok(p, s, sizeof(s) - 1)) -	switch (type) { -		case KV_IDENT: case KV_IDENT_QUOTED: -			if (ctxt->nestlvl == 1 && MATCH("game")) { -				ctxt->matchtype = mt_title; -			} -			else if (ctxt->nestlvl == 3) { -				// for some reason there's a million different ways of -				// specifying the same type of path -				if (MATCH("mod+game") || MATCH("game+mod") || MATCH("game") || -						MATCH("mod")) { -					ctxt->matchtype = mt_game; -				} -				else if (MATCH("gamebin")) { -					ctxt->matchtype = mt_gamebin; -				} -			} -			else if (len == matchkeys[ctxt->nestlvl].len && -					matchtok(p, matchkeys[ctxt->nestlvl].s, len)) { -				ctxt->matchtype = mt_nest; -			} -			break; -		case KV_NEST_START: -			if (ctxt->matchtype == mt_nest) ++ctxt->nestlvl; -			else ++ctxt->dontcarelvl; -			ctxt->matchtype = mt_none; -			break; -		case KV_VAL: case KV_VAL_QUOTED: -			if (ctxt->dontcarelvl) break; -			// 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(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; -				dolibsearch(p, len, ctxt->matchtype == mt_gamebin, ctxt->cwd); -			} -			ctxt->matchtype = mt_none; -			break; -		case KV_NEST_END: -			if (ctxt->dontcarelvl) --ctxt->dontcarelvl; else --ctxt->nestlvl; -			break; -		case KV_COND_PREFIX: case KV_COND_SUFFIX: -			errmsg_warnx("just ignoring conditional \"%.*s\"", len, p); -	} -	#undef MATCH -} -  DECL_VFUNC_DYN(const char *, GetGameDirectory)  bool gameinfo_init(void) { @@ -242,76 +41,39 @@ bool gameinfo_init(void) {  		return false;  	} -	// 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))) { -		errmsg_errorstd("couldn't get working directory"); -		return false; -	} -	int len = os_strlen(cwd); -	if (len + sizeof("/bin") > sizeof(bindir) / sizeof(*bindir)) { -		errmsg_errorx("working directory path is too long!"); -		return false; -	} -	memcpy(bindir, cwd, len * sizeof(*cwd)); -	memcpy(bindir + len, PATHSEP OS_LIT("bin"), 5 * sizeof(os_char)); -  #ifdef _WIN32  	// Although the engine itself uses Unicode-incompatible stuff everywhere so  	// supporting arbitrary paths is basically a no-go, turns out we still have -	// to respect the system code page setting, otherwise some users using e.g. -	// Cyrillic folder names and successfully loading their speedgames won't be -	// able to load SST. Thanks Windows! +	// to respect the system legacy code page setting, otherwise some users +	// using e.g. Cyrillic folder names and successfully loading their +	// speedgames won't be able to load SST. Thanks Windows!  	const char *lcpgamedir = GetGameDirectory(engclient); -	int gamedirlen = MultiByteToWideChar(CP_ACP, 0, lcpgamedir, -			strlen(lcpgamedir), gamedir, sizeof(gamedir) / sizeof(*gamedir)); -	if (!gamedirlen) { +	if (!MultiByteToWideChar(CP_ACP, 0, lcpgamedir, strlen(lcpgamedir), gamedir, +			sizeof(gamedir) / sizeof(*gamedir))) {  		errmsg_errorsys("couldn't convert game directory path character set");  		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]; -	if (gamedirlen + sizeof("/gameinfo.txt") > sizeof(gameinfopath) / -			sizeof(*gameinfopath)) { -		errmsg_errorx("game directory path is too long!"); -		return false; -	} -	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) { -		errmsg_errorstd("couldn't open gameinfo.txt"); -		return false; + +	// dumb hack: ignore Survivors title (they left it set to "Left 4 Dead 2" +	// but that game clearly isn't Left 4 Dead 2) +	if (GAMETYPE_MATCHES(L4DS)) { +		gameinfo_title = "Left 4 Dead: Survivors";  	} -	char buf[1024]; -	struct kv_parser kvp = {0}; -	struct kv_parsestate ctxt = {.cwd = cwd}; -	int nread; -	while (nread = read(fd, buf, sizeof(buf))) { -		if (nread == -1) { -			errmsg_errorstd("couldn't read gameinfo.txt"); -			goto e; -		} -		if (!kv_parser_feed(&kvp, buf, nread, &kv_cb, &ctxt)) goto ep; +	else { +#ifdef _WIN32 +		// XXX: this same FindWindow call happens in ac.c - maybe factor out? +		void *gamewin = FindWindowW(L"Valve001", 0); +		// assuming: all games/mods use narrow chars only; this won't fail. +		GetWindowTextA(gamewin, title, sizeof(title)); +#else +#erorr TODO(linux): grab window handle and title from SDL (a bit involved...) +#endif  	} -	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:	errmsg_errorx("couldn't parse gameinfo.txt (%d:%d): %s", kvp.line, kvp.col, -		kvp.errmsg); -e:	close(fd); -	return false;  }  // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/gameinfo.h b/src/gameinfo.h index 4948268..76c3c57 100644 --- a/src/gameinfo.h +++ b/src/gameinfo.h @@ -1,5 +1,5 @@  /* - * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2023 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 @@ -22,18 +22,11 @@  #include "intdefs.h"  #include "os.h" -/* These variables are only set after calling gameinfo_init(). */ -extern const os_char *gameinfo_bindir;    /* Absolute path to top-level bin/ */ +/* Miscellaneous metadata variables about the currently running game */  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. - */ +/* Called early in plugin initialisation to set up the variables above. */  bool gameinfo_init(void);  #endif diff --git a/src/l4dmm.c b/src/l4dmm.c index 3c7a07c..d391584 100644 --- a/src/l4dmm.c +++ b/src/l4dmm.c @@ -95,13 +95,7 @@ const char *l4dmm_curcampaign(void) {  }  INIT { -	// XXX: ugh, we NEED to centralise library stuff at some point, this sucks -#ifdef _WIN32 -	void *mmlib = GetModuleHandleW(L"matchmaking.dll"); -#else -	void *mmlib = dlopen("matchmaking.so", RTLD_NOW | RTLD_NOLOAD); -	if (mmlib) dlclose(mmlib); -#endif +	void *mmlib = os_dlhandle(OS_LIT("matchmaking") OS_LIT(OS_DLSUFFIX));  	if (mmlib) {  		ifacefactory factory = (ifacefactory)os_dlsym(mmlib, "CreateInterface");  		if (!factory) { diff --git a/src/os-unix.h b/src/os-unix.h index ec9a940..a25d8ed 100644 --- a/src/os-unix.h +++ b/src/os-unix.h @@ -39,10 +39,19 @@ typedef char os_char;  #define os_getenv getenv  #define os_getcwd getcwd +#define OS_DLPREFIX "lib"  #define OS_DLSUFFIX ".so"  #define OS_MAIN main +static inline void *os_dlopen(const char *name) { +	return dlopen(name, RTLD_NOW); +} +static inline void *os_dlhandle(const char *name) { +	void *ret = dlopen(name, RTLD_NOW | RTLD_NOLOAD); +	if (ret) dlclose(ret); +	return ret; +}  #define os_dlsym dlsym  #ifdef __linux__ diff --git a/src/os-win32.h b/src/os-win32.h index fe61f84..a006083 100644 --- a/src/os-win32.h +++ b/src/os-win32.h @@ -45,10 +45,17 @@ typedef unsigned short os_char;  #define os_getenv _wgetenv  #define os_getcwd _wgetcwd +#define OS_DLPREFIX ""  #define OS_DLSUFFIX ".dll"  #define OS_MAIN wmain +static inline void *os_dlopen(const ushort *name) { +	return LoadLibraryW(name); +} +static inline void *os_dlhandle(const ushort *name) { +	return GetModuleHandleW(name); +}  static inline void *os_dlsym(void *m, const char *s) {  	return (void *)GetProcAddress(m, s);  } @@ -20,7 +20,6 @@  #include <shlwapi.h>  #endif -#include "ac.h"  #include "con_.h"  #include "engineapi.h"  #include "errmsg.h" @@ -70,22 +69,63 @@ static inline void *ownhandle(void) {  #define VDFBASENAME "SourceSpeedrunTools" +#ifdef _WIN32 +// not a proper check, just a short-circuit check to avoid doing more work. +static inline bool checksamedrive(const ushort *restrict path1, +		const ushort *restrict path2) { +	bool ret = (path1[0] | 32) == (path2[0] | 32); +	if (!ret) errmsg_errorx("game and plugin must be on the same drive\n"); +	return ret; +} +#endif +  DEF_CCMD_HERE(sst_autoload_enable, "Register SST to load on game startup", 0) { -	// note: gamedir doesn't account for if the dll is in a base mod's -	// directory, although it will yield a valid/working relative path anyway. -	const os_char *searchdir = ifacever == 3 ? -			gameinfo_gamedir : gameinfo_bindir;  	os_char path[PATH_MAX];  	if (os_dlfile(ownhandle(), path, sizeof(path) / sizeof(*path)) == -1) {  		// hopefully by this point this won't happen, but, like, never know  		errmsg_errordl("failed to get path to plugin");  		return;  	} +	os_char _startdir[PATH_MAX]; +	const os_char *startdir; +	if (ifacever == 2) { +		startdir = _startdir; +		os_getcwd(_startdir, PATH_MAX); // if this fails, OS devs are all fired. +#ifdef _WIN32 +		// note: strictly speaking we *could* allow this with an absolute path +		// since old builds allow absolute plugin_load paths but since it's less +		// reliable if e.g. a disk is removed, and also doesn't work for all +		// games, just rule it out entirely to keep things simple. +		if (!checksamedrive(path, startdir)) return; +#endif +		int len = os_strlen(startdir); +		if (len + sizeof("/bin") >= PATH_MAX) { +			errmsg_errorx("path to game is too long"); +			return; +		} +		memcpy(_startdir + len, OS_LIT("/bin"), 5 * sizeof(os_char)); +	} +	else /* ifacever == 3 */ { +		// newer games load from the mod dir instead of engine bin, and search +		// in inherited search paths too, although we don't bother with those as +		// the actual VDF is only read from the mod itself so it's always enough +		// to make the path relative to that (and that makes the actual plugin +		// search fast too as it should find it in the first place it looks). +		// we *still* refuse to autoload across different drives even if some +		// obscure gameinfo.txt arrangement could technically allow that to work +		startdir = gameinfo_gamedir; +#ifdef _WIN32 +		if (!checksamedrive(path, startdir)) return; +#endif +	}  	os_char relpath[PATH_MAX];  #ifdef _WIN32 -	if (!PathRelativePathToW(relpath, searchdir, FILE_ATTRIBUTE_DIRECTORY, +	// note: dll isn't actually in gamedir if it's in a base mod directory +	// note: gamedir doesn't account for if the dll is in a base mod's +	// directory, although it will yield a valid/working relative path anyway. +	if (!PathRelativePathToW(relpath, startdir, FILE_ATTRIBUTE_DIRECTORY,  			path, 0)) { -		errmsg_errorsys("couldn't compute a relative path for some reason"); +		errmsg_errorsys("couldn't compute a relative path");  		return;  	}  	// arbitrary aesthetic judgement @@ -113,9 +153,8 @@ DEF_CCMD_HERE(sst_autoload_enable, "Register SST to load on game startup", 0) {  		errmsg_errorstd("couldn't open %" fS, path);  		return;  	} -	// XXX: oh, crap, we're clobbering unicode again. welp, let's hope the -	// theory that the engine is just as bad if not worse is true so that it -	// doesn't matter. +	// XXX: oh crap, we're clobbering unicode again. welp, let's continue +	// relying on the theory that the engine would fail to deal with it anyway.  	if (fprintf(f, "Plugin { file \"%" fS "\" }\n", relpath) < 0 ||  			fflush(f) == -1) {  		errmsg_errorstd("couldn't write to %" fS, path); @@ -183,6 +222,27 @@ static const char *updatenotes = "\  #include <featureinit.gen.h> // generated by build/codegen.c  static void do_featureinit(void) { +	// load libs that might not be there early (...at least on Linux???) +	clientlib = os_dlhandle(OS_LIT("client") OS_LIT(OS_DLSUFFIX)); +	if (!clientlib) { +		errmsg_warndl("couldn't get the game's client library"); +	} +	else if (!(factory_client = (ifacefactory)os_dlsym(clientlib, +			"CreateInterface"))) { +		errmsg_warndl("couldn't get client's CreateInterface"); +	} +	void *inputsystemlib = os_dlhandle(OS_LIT("bin/") OS_LIT(OS_DLPREFIX) +			OS_LIT("inputsystem") OS_LIT(OS_DLSUFFIX)); +	if (!inputsystemlib) { +		errmsg_warndl("couldn't get the input system library"); +	} +	else if (!(factory_inputsystem = (ifacefactory)os_dlsym(inputsystemlib, +			"CreateInterface"))) { +		errmsg_warndl("couldn't get input system's CreateInterface"); +	} +	inputsystem = factory_inputsystem("InputSystemVersion001", 0); +	if (!inputsystem) errmsg_warnx("missing input system interface"); +	// ... and now for the real magic!  	initfeatures();  	// if we're autoloaded and the external autoupdate script downloaded a new @@ -254,42 +314,8 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) {  		errmsg_warnsys("couldn't set up memory for function hooking");  		return false;  	} -  	factory_engine = enginef; factory_server = serverf; -#ifdef _WIN32 -	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 -#endif -	if (!inputsystemlib) { -		errmsg_warndl("couldn't get the input system library"); -	} -	else if (!(factory_inputsystem = (ifacefactory)os_dlsym(inputsystemlib, -			"CreateInterface"))) { -		errmsg_warndl("couldn't get input system's CreateInterface"); -	}  	if (!engineapi_init(ifacever)) return false; -#ifdef _WIN32 -	clientlib = GetModuleHandleW(gameinfo_clientlib); -#else -	// 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) { -		errmsg_warndl("couldn't get the game's client library"); -	} -	else if (!(factory_client = (ifacefactory)os_dlsym(clientlib, -			"CreateInterface"))) { -		errmsg_warndl("couldn't get client's CreateInterface"); -	} -  	const void **p = vtable_firstdiff;  	if (GAMETYPE_MATCHES(Portal2)) *p++ = (void *)&nop_p_v; // ClientFullyConnect  	*p++ = (void *)&nop_p_v;		  // ClientDisconnect @@ -303,7 +329,6 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) {  	*p++ = (void *)&nop_ipipp_v;	  // OnQueryCvarValueFinished (002+)  	*p++ = (void *)&nop_p_v;		  // OnEdictAllocated  	*p   = (void *)&nop_p_v;		  // OnEdictFreed -  	if (!deferinit()) { do_featureinit(); fixes_apply(); }  	return true;  } | 
