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/gameinfo.c | 212 +++++++++++++++++++++++---------------------------------- 1 file changed, 86 insertions(+), 126 deletions(-) (limited to 'src/gameinfo.c') 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", -- cgit v1.2.3