From 00ad7cdd3d05d09a43bda972c823fdc440feabb9 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 24 Mar 2022 02:06:28 +0000 Subject: Clean up gameinfo_init() and other random stuff - Just ask the engine for the game directory instead of doing the stupid argv sniffing hacks from the early days of trying to get the damn thing working. - Also add some other path variables, functions and whatnot, and do some other minor tidying up. - Also also, another damn copyright year, somebody please help me. Unfortunate negative effect off this change: con_init() no longer reports the game name, because it has to happen before gameinfo_init(). I've decided I don't really care, though. --- src/con_.c | 8 +-- src/gameinfo.c | 212 +++++++++++++++++++++++---------------------------------- src/gameinfo.h | 14 ++-- src/gametype.h | 17 +++-- src/hook.c | 4 +- src/os-unix.h | 11 +++ src/os-win32.h | 13 ++++ src/os.h | 1 + src/sst.c | 21 +++--- 9 files changed, 145 insertions(+), 156 deletions(-) diff --git a/src/con_.c b/src/con_.c index ab1cd1e..925eafb 100644 --- a/src/con_.c +++ b/src/con_.c @@ -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 + * Copyright © 2022 Michael Smith * * 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 + #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)) diff --git a/src/hook.c b/src/hook.c index 61cf614..6b9036c 100644 --- a/src/hook.c +++ b/src/hook.c @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Michael Smith + * Copyright © 2022 Michael Smith * Copyright © 2022 Willian Henrique * * 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); diff --git a/src/os.h b/src/os.h index 5d62c2a..0e30005 100644 --- a/src/os.h +++ b/src/os.h @@ -42,6 +42,7 @@ #else #include #include +#include #include #include #include diff --git a/src/sst.c b/src/sst.c index aeb7f4c..1d0d18a 100644 --- a/src/sst.c +++ b/src/sst.c @@ -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; } } -- cgit v1.2.3