From 44902cb49cd51e2bb2f1cef05cb3c0e799b83434 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sat, 3 Aug 2024 16:11:57 +0100 Subject: Rework OS abstractions - As much as possible avoid dragging system headers into translation units. This should avoid namespace pollution and, hopefully, speed up builds a little bit. - Avoid leaning on the UCRT so much on Windows - prefer native win32 calls and native file handles except where doing so is inconvenient (in particular, for stat(), which we might try and replace later). - Also, switch from SystemFunction036 to ProcessPrng on Windows. This requires us to generate a stub for bcryptprimitives.dll because Microsoft haven't bothered to provide a link library, but the function is better-documented and seems to be a more direct under-the-hood call as well. Apparently it's what's used by the major web browsers these days, which seems like a good indication it's stable and trusted. - Lastly, remove a bunch of functions and macros and stuff that weren't actually being used. It seems good to try and keep the scope of OS-dependent stuff relatively contained and only add to it when actually required. --- compile | 1 + compile.bat | 36 ++++-- src/3p/chibicc/tokenize.c | 4 + src/ac.c | 11 +- src/build/cmeta.c | 22 ++-- src/build/cmeta.h | 4 +- src/build/mkentprops.c | 8 +- src/build/mkgamedata.c | 8 +- src/build/skiplist.h | 3 +- src/errmsg.h | 20 +++- src/gameinfo.c | 5 +- src/hook.c | 7 ++ src/nomute.c | 11 +- src/os-unix.h | 125 -------------------- src/os-win32.h | 131 --------------------- src/os.c | 204 ++++++++++++++++++++++++++++++++ src/os.h | 260 +++++++++++++++++++++++++++++++++++------ src/sst.c | 82 +++++++------ src/stubs/BCryptPrimitives.c | 6 + src/stubs/bcryptprimitives.def | 7 ++ test/hook.test.c | 1 + 21 files changed, 585 insertions(+), 371 deletions(-) delete mode 100644 src/os-unix.h delete mode 100644 src/os-win32.h create mode 100644 src/os.c create mode 100644 src/stubs/BCryptPrimitives.c create mode 100644 src/stubs/bcryptprimitives.def diff --git a/compile b/compile index bcda4ec..d9ab4f6 100755 --- a/compile +++ b/compile @@ -78,6 +78,7 @@ src="\ l4dreset.c l4dwarp.c nosleep.c + os.c portalcolours.c sst.c xhair.c diff --git a/compile.bat b/compile.bat index a50dbb0..215fc32 100644 --- a/compile.bat +++ b/compile.bat @@ -13,6 +13,13 @@ if not exist .build\include\ md .build\include if "%CC%"=="" set CC=clang --target=i686-pc-windows-msvc if "%HOSTCC%"=="" set HOSTCC=clang +set host64=0 +( + echo:#ifndef _WIN64 + echo:#error + echo:#endif +) | %HOSTCC% -E - >nul 2>nul && set host64=1 + set warnings=-Wall -pedantic -Wno-parentheses -Wno-missing-braces ^ -Wno-gnu-zero-variadic-macro-arguments -Werror=implicit-function-declaration ^ -Werror=vla @@ -86,6 +93,7 @@ setlocal DisableDelayedExpansion :+ l4dwarp.c :+ nomute.c :+ nosleep.c +:+ os.c :+ portalcolours.c :+ rinput.c :+ sst.c @@ -96,19 +104,27 @@ if "%dbg%"=="1" set src=%src% src/dbg.c if "%dbg%"=="1" set src=%src% src/udis86.c if "%dbg%"=="0" set src=%src% src/wincrt.c -%HOSTCC% -fuse-ld=lld -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -include stdbool.h -ladvapi32 ^ --o .build/codegen.exe src/build/codegen.c src/build/cmeta.c || exit /b +%CC% -fuse-ld=lld -shared -O0 -w -o .build/bcryptprimitives.dll -Wl,-def:src/stubs/bcryptprimitives.def src/stubs/bcryptprimitives.c +set lbcryptprimitives_host=-lbcryptprimitives +if %host64%==1 ( + :: note: no mangling madness on x64, so we can just call the linker directly + lld-link -machine:x64 -def:src/stubs/bcryptprimitives.def -implib:.build/bcryptprimitives64.lib + set lbcryptprimitives_host=-lbcryptprimitives64 +) +%CC% -fuse-ld=lld -shared -O0 -w -o .build/tier0.dll src/stubs/tier0.c +%CC% -fuse-ld=lld -shared -O0 -w -o .build/vstdlib.dll src/stubs/vstdlib.c + %HOSTCC% -fuse-ld=lld -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -include stdbool.h ^ --o .build/mkgamedata.exe src/build/mkgamedata.c src/kv.c || exit /b -%HOSTCC% -fuse-ld=lld -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -include stdbool.h -ladvapi32 ^ --o .build/mkentprops.exe src/build/mkentprops.c src/kv.c || exit /b -.build\codegen.exe%src% || exit /b +-L.build %lbcryptprimitives_host% -o .build/codegen.exe src/build/codegen.c src/build/cmeta.c src/os.c || exit /b +%HOSTCC% -fuse-ld=lld -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -include stdbool.h ^ +-L.build %lbcryptprimitives_host% -o .build/mkgamedata.exe src/build/mkgamedata.c src/kv.c src/os.c || exit /b +%HOSTCC% -fuse-ld=lld -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -include stdbool.h ^ +-L.build %lbcryptprimitives_host% -o .build/mkentprops.exe src/build/mkentprops.c src/kv.c src/os.c || exit /b +.build\codegen.exe%src% || goto :end .build\mkgamedata.exe gamedata/engine.kv gamedata/gamelib.kv gamedata/inputsystem.kv ^ gamedata/matchmaking.kv gamedata/vgui2.kv gamedata/vguimatsurface.kv || exit /b .build\mkentprops.exe gamedata/entprops.kv || exit /b llvm-rc /FO .build\dll.res src\dll.rc || exit /b -%CC% -fuse-ld=lld -shared -O0 -w -o .build/tier0.dll src/stubs/tier0.c -%CC% -fuse-ld=lld -shared -O0 -w -o .build/vstdlib.dll src/stubs/vstdlib.c for %%b in (%src%) do ( call :cc %%b || exit /b ) :: we need different library names for debugging because Microsoft... :: actually, it's different anyway because we don't use vcruntime for releases @@ -119,7 +135,7 @@ if "%dbg%"=="1" ( set clibs=-lucrt ) %CC% -fuse-ld=lld -shared -flto %ldflags% -Wl,/IMPLIB:.build/sst.lib,/Brepro,/nodefaultlib ^ --L.build %clibs% -lkernel32 -luser32 -ladvapi32 -lshlwapi -ld3d9 -ldsound ^ +-L.build %clibs% -lkernel32 -luser32 -lbcryptprimitives -lshlwapi -ld3d9 -ldsound ^ -ltier0 -lvstdlib -lntdll -o sst.dll%objs% .build/dll.res || exit /b :: get rid of another useless file (can we just not create this???) del .build\sst.lib @@ -127,7 +143,7 @@ del .build\sst.lib %HOSTCC% -fuse-ld=lld -O2 -g -include test/test.h -o .build/bitbuf.test.exe test/bitbuf.test.c || exit /b .build\bitbuf.test.exe || exit /b :: special case: test must be 32-bit -%HOSTCC% -fuse-ld=lld -m32 -O2 -g -ladvapi32 -include test/test.h -o .build/hook.test.exe test/hook.test.c || exit /b +%HOSTCC% -fuse-ld=lld -m32 -O2 -g -L.build -lbcryptprimitives -include test/test.h -o .build/hook.test.exe test/hook.test.c || exit /b .build\hook.test.exe || exit /b %HOSTCC% -fuse-ld=lld -O2 -g -include test/test.h -o .build/kv.test.exe test/kv.test.c || exit /b .build\kv.test.exe || exit /b diff --git a/src/3p/chibicc/tokenize.c b/src/3p/chibicc/tokenize.c index b13edd5..6738206 100644 --- a/src/3p/chibicc/tokenize.c +++ b/src/3p/chibicc/tokenize.c @@ -1,5 +1,9 @@ #include "chibicc.h" +#ifdef _WIN32 +#define strncasecmp _strnicmp +#endif + // Input file static File *current_file; diff --git a/src/ac.c b/src/ac.c index 8b163b9..8c98cfb 100644 --- a/src/ac.c +++ b/src/ac.c @@ -17,6 +17,13 @@ #include +#ifdef _WIN32 +#include +#include +#else +#include +#endif + #include "alias.h" #include "bind.h" #include "chunklets/fastspin.h" @@ -41,10 +48,6 @@ #include "x86.h" #include "x86util.h" -#ifdef _WIN32 -#include // must be after Windows.h (via os.h) -#endif - FEATURE() REQUIRE(bind) REQUIRE(democustom) diff --git a/src/build/cmeta.c b/src/build/cmeta.c index 0c9b3ca..8a2416d 100644 --- a/src/build/cmeta.c +++ b/src/build/cmeta.c @@ -1,5 +1,5 @@ /* - * Copyright © 2023 Michael Smith + * Copyright © 2024 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 @@ -102,15 +102,15 @@ static void die(const char *s) { exit(100); } -static char *readsource(const os_char *f) { - int fd = os_open(f, O_RDONLY); - if (fd == -1) return 0; +static char *readsource(const os_char *path) { + int f = os_open_read(path); + if (f == -1) return 0; uint bufsz = 8192; char *buf = malloc(bufsz); if (!buf) die("couldn't allocate memory"); int nread; int off = 0; - while ((nread = read(fd, buf + off, bufsz - off)) > 0) { + while ((nread = os_read(f, buf + off, bufsz - off)) > 0) { off += nread; if (off == bufsz) { bufsz *= 2; @@ -122,24 +122,24 @@ static char *readsource(const os_char *f) { } if (nread == -1) die("couldn't read file"); buf[off] = 0; - close(fd); + os_close(f); return buf; } // as per cmeta.h this is totally opaque; it's actually just a Token in disguise struct cmeta; -const struct cmeta *cmeta_loadfile(const os_char *f) { - char *buf = readsource(f); +const struct cmeta *cmeta_loadfile(const os_char *path) { + char *buf = readsource(path); if (!buf) return 0; #ifdef _WIN32 - char *realname = malloc(wcslen(f) + 1); + char *realname = malloc(wcslen(path) + 1); if (!realname) die("couldn't allocate memory"); // XXX: being lazy about Unicode right now; a general purpose tool should // implement WTF8 or something. SST itself doesn't have any unicode paths // though, so don't really care as much. - *realname = *f; - for (const ushort *p = f + 1; p[-1]; ++p) realname[p - f] = *p; + *realname = *path; + for (const ushort *p = path + 1; p[-1]; ++p) realname[p - path] = *p; #else const char *realname = f; #endif diff --git a/src/build/cmeta.h b/src/build/cmeta.h index 48a2d6d..86ae0ec 100644 --- a/src/build/cmeta.h +++ b/src/build/cmeta.h @@ -1,5 +1,5 @@ /* - * Copyright © 2022 Michael Smith + * Copyright © 2024 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 @@ -21,7 +21,7 @@ struct cmeta; -const struct cmeta *cmeta_loadfile(const os_char *f); +const struct cmeta *cmeta_loadfile(const os_char *path); /* * Iterates through all the #include directives in a file, passing each one in diff --git a/src/build/mkentprops.c b/src/build/mkentprops.c index 0781f25..2043a9b 100644 --- a/src/build/mkentprops.c +++ b/src/build/mkentprops.c @@ -174,13 +174,13 @@ _( "}") int OS_MAIN(int argc, os_char *argv[]) { for (++argv; *argv; ++argv) { - int fd = os_open(*argv, O_RDONLY); - if (fd == -1) die("couldn't open file"); + int f = os_open_read(*argv); + if (f == -1) die("couldn't open file"); struct kv_parser kv = {0}; struct parsestate state = {*argv, &kv}; char buf[1024]; int nread; - while (nread = read(fd, buf, sizeof(buf))) { + while (nread = os_read(f, buf, sizeof(buf))) { if (nread == -1) die("couldn't read file"); if (!kv_parser_feed(&kv, buf, nread, &kv_cb, &state)) goto ep; } @@ -189,7 +189,7 @@ ep: fprintf(stderr, "mkentprops: %" fS ":%d:%d: bad syntax: %s\n", *argv, kv.line, kv.col, kv.errmsg); exit(1); } - close(fd); + os_close(f); } FILE *out = fopen(".build/include/entprops.gen.h", "wb"); diff --git a/src/build/mkgamedata.c b/src/build/mkgamedata.c index d49eef5..bf359b2 100644 --- a/src/build/mkgamedata.c +++ b/src/build/mkgamedata.c @@ -1,5 +1,5 @@ /* - * Copyright © 2022 Michael Smith + * Copyright © 2024 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 @@ -211,13 +211,13 @@ _( "}") int OS_MAIN(int argc, os_char *argv[]) { for (++argv; *argv; ++argv) { - int fd = os_open(*argv, O_RDONLY); + int fd = os_open_read(*argv); if (fd == -1) die("couldn't open file"); struct kv_parser kv = {0}; struct parsestate state = {*argv, &kv, &root}; char buf[1024]; int nread; - while (nread = read(fd, buf, sizeof(buf))) { + while (nread = os_read(fd, buf, sizeof(buf))) { if (nread == -1) die("couldn't read file"); if (!kv_parser_feed(&kv, buf, nread, &kv_cb, &state)) goto ep; } @@ -226,7 +226,7 @@ ep: fprintf(stderr, "mkgamedata: %" fS ":%d:%d: bad syntax: %s\n", *argv, kv.line, kv.col, kv.errmsg); exit(1); } - close(fd); + os_close(fd); } FILE *out = fopen(".build/include/gamedata.gen.h", "wb"); diff --git a/src/build/skiplist.h b/src/build/skiplist.h index b747d89..ab6a920 100644 --- a/src/build/skiplist.h +++ b/src/build/skiplist.h @@ -1,5 +1,5 @@ /* - * Copyright © 2022 Michael Smith + * Copyright © 2024 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 @@ -24,6 +24,7 @@ #ifdef _WIN32 static inline int _skiplist_ffs(uint x) { + uchar _BitScanForward(ulong *idx, ulong mask); uint ret; // on Windows, sizeof(ulong) == sizeof(uint) if (_BitScanForward((ulong *)&ret, x)) return ret + 1; else return 0; diff --git a/src/errmsg.h b/src/errmsg.h index 17a4457..cdf5095 100644 --- a/src/errmsg.h +++ b/src/errmsg.h @@ -1,5 +1,5 @@ /* - * Copyright © 2022 Michael Smith + * Copyright © 2024 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,6 +17,15 @@ #ifndef INC_ERRMSG_H #define INC_ERRMSG_H +#ifdef _WIN32 +#include + +// note: redundant declspec avoids warnings if Windows.h was also included +__declspec(dllimport) unsigned long __stdcall FormatMessageA(unsigned long, + const void *, unsigned long, unsigned long, char *, unsigned long, + va_list *); +#endif + #include "con_.h" #include "os.h" @@ -48,14 +57,17 @@ extern const char msg_note[], msg_warn[], msg_error[]; #ifdef _WIN32 #define _errmsg_sys(msg, ...) do { \ - char _warnsys_buf[512]; \ - OS_WINDOWS_ERROR(_warnsys_buf); \ - _errmsg_msg(_ERRMSG_STR(MODULE_NAME), msg, __VA_ARGS__, _warnsys_buf); \ + char _errmsg_buf[512]; \ + FormatMessageA(/*FORMAT_MESSAGE_FROM_SYSTEM*/ 4096, 0, os_lasterror(), \ + /*MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT)*/ 1024, _errmsg_buf, \ + sizeof(_errmsg_buf), 0); \ + _errmsg_msg(_ERRMSG_STR(MODULE_NAME), msg, __VA_ARGS__, _errmsg_buf); \ } while (0) #define _errmsg_dl _errmsg_sys #else #define _errmsg_sys _errmsg_std static inline const char *_errmsg_dlerror(void) { + char *dlerror(void); const char *e = dlerror(); if (!e) return "Unknown error"; // just in case, better avoid weirdness! return e; diff --git a/src/gameinfo.c b/src/gameinfo.c index 85220a8..d535a44 100644 --- a/src/gameinfo.c +++ b/src/gameinfo.c @@ -1,5 +1,5 @@ /* - * Copyright © 2023 Michael Smith + * Copyright © 2024 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 @@ -15,6 +15,9 @@ */ #include +#ifdef _WIN32 +#include // MultiByteToWideChar() +#endif #include "engineapi.h" #include "errmsg.h" diff --git a/src/hook.c b/src/hook.c index 9a6e885..fe311cc 100644 --- a/src/hook.c +++ b/src/hook.c @@ -23,6 +23,13 @@ #include "os.h" #include "x86.h" +#ifdef _WIN32 +// try to avoid pulling in all of Windows.h for this... (redundant dllimport +// avoids warnings in hook.test.c where Windows.h is included via test.h) +__declspec(dllimport) int __stdcall FlushInstructionCache( + void *, const void *, usize); +#endif + // Warning: half-arsed hacky implementation (because that's all we really need) // Almost certainly breaks in some weird cases. Oh well! Most of the time, // vtable hooking is more reliable, this is only for, uh, emergencies. diff --git a/src/nomute.c b/src/nomute.c index 93cfcd2..6a42d46 100644 --- a/src/nomute.c +++ b/src/nomute.c @@ -1,6 +1,6 @@ /* * Copyright © 2023 Willian Henrique - * Copyright © 2023 Michael Smith + * Copyright © 2024 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 @@ -18,16 +18,17 @@ // TODO(linux): this is currently only built on Windows, but a linux // implementation would be also useful for some games e.g. L4D2 +// NOTE: all three of these headers must be in this order. annoyingly. +#include +#include +#include + #include "con_.h" #include "errmsg.h" #include "feature.h" #include "os.h" #include "sst.h" -// these have to come after Windows.h (via os.h) and in this order -#include -#include - FEATURE("inactive window audio control") DEF_CVAR_UNREG(snd_mute_losefocus, diff --git a/src/os-unix.h b/src/os-unix.h deleted file mode 100644 index 097d300..0000000 --- a/src/os-unix.h +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright © 2023 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 - * 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. - */ - -/* Unix-specific definitions for os.h - don't include this directly! */ - -#ifdef __GNUC__ -#define EXPORT __attribute__((visibility("default"))) -#else -#define EXPORT int novis[-!!"visibility attribute requires Clang or GCC"]; -#endif -#define IMPORT - -typedef char os_char; -#define OS_LIT(x) x -#define os_snprintf snprintf -#define os_strchr strchr -#define os_strcmp strcmp -#define os_strcpy strcpy -#define os_strlen strlen -#define os_fopen fopen -#define os_open open -#define os_access access -#define os_stat stat -#define os_mkdir(f) mkdir(f, 0755) // use real mkdir(2) if the mode matters -#define os_unlink unlink -#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); -} -#define os_dlsym dlsym - -#ifdef __linux__ -// note: this is glibc-specific. it shouldn't be used in build-time code, just -// the plugin itself (that really shouldn't be a problem). - -// private struct hidden behind _GNU_SOURCE. see dlinfo(3) or -struct gnu_link_map { - unsigned long l_addr; - const char *l_name; - void *l_ld; - struct gnu_link_map *l_next, *l_prev; - // [more private stuff below] -}; - -static inline void *os_dlhandle(const char *name) { - extern struct gnu_link_map *_os_lmbase; // note: defined in sst.c for now - if (!_os_lmbase) { // IMPORTANT: not thread safe. don't forget later! - _os_lmbase = (struct gnu_link_map *)dlopen("libc.so.6", - RTLD_LAZY | RTLD_NOLOAD); - dlclose(_os_lmbase); // assume success - while (_os_lmbase->l_prev) _os_lmbase = _os_lmbase->l_prev; - } - // this is a tiny bit crude, but basically okay. we just want to find - // something that roughly matches the basename, rather than needing an exact - // path, in a manner vaguely similar to Windows' GetModuleHandle(). that way - // we can just look up client.so or something without having to figure out - // where exactly that is. - for (struct gnu_link_map *lm = _os_lmbase; lm; lm = lm->l_next) { - if (name[0] == '/') { - if (!strcmp(name, lm->l_name)) return lm; - continue; - } - int namelen = strlen(lm->l_name); - int sublen = strlen(name); - if (sublen >= namelen) continue; - if (lm->l_name[namelen - sublen - 1] == '/' && !memcmp( - lm->l_name + namelen - sublen, name, sublen)) { - return lm; - } - } - return 0; -} - -static inline int os_dlfile(void *m, char *buf, int sz) { - struct gnu_link_map *lm = m; - int ssz = strlen(lm->l_name) + 1; - if (ssz > sz) { errno = ENAMETOOLONG; return -1; } - memcpy(buf, lm->l_name, ssz); - return ssz; -} - -#endif - -// 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! -#define PAGE_NOACCESS 0 -#define PAGE_READONLY PROT_READ -#define PAGE_READWRITE PROT_READ | PROT_WRITE -#define PAGE_EXECUTE_READ PROT_READ | PROT_EXEC -#define PAGE_EXECUTE_READWRITE PROT_READ | PROT_WRITE | PROT_EXEC - -static inline bool os_mprot(void *addr, int len, int fl) { - // round down address and round up size - addr = (void *)((unsigned long)addr & ~(4095)); - len = len + 4095 & ~(4095); - return mprotect(addr, len, fl) != -1; -} - -static inline void os_randombytes(void *buf, int sz) { - // if this call fails, the system is doomed, so loop until success and pray. - while (getentropy(buf, sz) == -1); -} - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/os-win32.h b/src/os-win32.h deleted file mode 100644 index db20964..0000000 --- a/src/os-win32.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright © 2023 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 - * 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. - */ - -/* Windows-specific definitions for os.h - don't include this directly! */ - -#define IMPORT __declspec(dllimport) // only needed for variables -#define EXPORT __declspec(dllexport) - -typedef unsigned short os_char; -#define _OS_CAT(L, x) L##x -#define OS_LIT(x) _OS_CAT(L, x) - -#define os_snprintf _snwprintf -#define os_strchr wcschr -#define os_strcmp wcscmp -#define os_strcpy wcscpy -#define os_strlen wcslen -#define strncasecmp _strnicmp // stupid! - -#define os_fopen _wfopen -// yuck :( -#define _os_open3(path, flags, mode) _wopen((path), (flags) | _O_BINARY, (mode)) -#define _os_open2(path, flags) _wopen((path), (flags) | _O_BINARY) -#define _os_open(a, b, c, x, ...) x -#define os_open(...) _os_open(__VA_ARGS__, _os_open3, _os_open2, _)(__VA_ARGS__) -#define os_access _waccess -// ucrt defines __stat64 to _stat64. we want _wstat64 to be the actual function -#define _stat64(path, buf) _wstat64(path, buf) -#define os_stat _stat64 -#define os_mkdir _wmkdir -#define os_unlink _wunlink -#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 unsigned short *name) { - return LoadLibraryW(name); -} -static inline void *os_dlhandle(const unsigned short *name) { - return GetModuleHandleW(name); -} -static inline void *os_dlsym(void *m, const char *s) { - return (void *)GetProcAddress(m, s); -} - -static inline int os_dlfile(void *m, unsigned short *buf, int sz) { - unsigned int n = GetModuleFileNameW(m, buf, sz); - if (n == 0 || n == sz) return -1; - // 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 n; -} - -static inline bool os_mprot(void *addr, int len, int fl) { - unsigned long old; - return !!VirtualProtect(addr, len, fl, &old); -} - -// SystemFunction036 is the *real* name of "RtlGenRandom," and is also -// incorrectly defined in system headers. Yay. -int __stdcall SystemFunction036(void *buf, unsigned long sz); - -static inline void os_randombytes(void *buf, int sz) { - // if this call fails, the system is doomed, so loop until success and pray. - while (!SystemFunction036(buf, sz)); -} - -/* Further Windows-specific workarounds because Windows is great */ - -#ifndef PATH_MAX -// XXX: win32/crt has this dumb 260 limit even though the actual kernel imposes -// no limit (though apparently NTFS has a limit of 65535). Theoretically we -// could do some memes with UNC paths to extend it to at least have parity with -// Unix PATH_MAX (4096), but for now we just kind of accept that Windows is a -// disaster. -#define PATH_MAX MAX_PATH -#endif - -// why does Windows prefix so many things with underscores? -#define alloca _alloca -#define read _read -#define write _write -#define close _close -#define fdopen _fdopen -#define dup _dup -#define dup2 _dup2 -#define strdup _strdup -#define fstat _fstat64 -#define lseek _lseeki64 -#define O_RDONLY _O_RDONLY -#define O_RDWR _O_RDWR -#define O_CLOEXEC _O_NOINHERIT // and why did they rename this!? -#define O_CREAT _O_CREAT -#define O_EXCL _O_EXCL -// missing access() flags -#define F_OK 0 -#define R_OK 4 -#define W_OK 2 -#define X_OK R_OK // there's no actual X bit, just pretend - -// windows doesn't define this for some reason!? note: add others as needed... -#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) - -// just dump this boilerplate here as well, I spose -#define OS_WINDOWS_ERROR(arrayname) \ - FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, GetLastError(), \ - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), arrayname, \ - sizeof(arrayname), 0) - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/os.c b/src/os.c new file mode 100644 index 0000000..49cab63 --- /dev/null +++ b/src/os.c @@ -0,0 +1,204 @@ +/* + * Copyright © 2024 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 + * 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 +#include +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "intdefs.h" + +#ifdef _WIN32 + +int os_lasterror(void) { return GetLastError(); } + +// N.B. file handle values are 32-bit, even in 64-bit builds. I'm not crazy! + +int os_open_read(const ushort *path) { + return (int)(ssize)CreateFileW(path, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, 0); +} +int os_open_write(const ushort *file) { + return (int)(ssize)CreateFileW(file, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_ALWAYS, + FILE_ATTRIBUTE_NORMAL, 0); +} +int os_open_writetrunc(const ushort *file) { + return (int)(ssize)CreateFileW(file, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, 0); +} + +int os_read(int f, void *buf, int max) { + ulong n; + return ReadFile((void *)(ssize)f, buf, max, &n, 0) ? n : -1; +} +int os_write(int f, const void *buf, int len) { + ulong n; + return WriteFile((void *)(ssize)f, buf, len, &n, 0) ? n : -1; +} + +vlong os_fsize(int f) { + vlong ret; + if (!GetFileSizeEx((void *)(ssize)f, (LARGE_INTEGER *)&ret)) return -1; + return ret; +} + +void os_close(int f) { + CloseHandle((void *)(ssize)f); +} + +void os_getcwd(ushort buf[static 260]) { GetCurrentDirectoryW(260, buf); } + +bool os_mkdir(const ushort *path) { return CreateDirectoryW(path, 0); } +bool os_unlink(const ushort *path) { return DeleteFileW(path); } +bool os_rmdir(const ushort *path) { return RemoveDirectoryW(path); } + +int __stdcall ProcessPrng(char *data, usize sz); // from bcryptprimitives.dll +void os_randombytes(void *buf, int sz) { ProcessPrng(buf, sz); } + +void *os_dlhandle(const ushort *name) { + return GetModuleHandleW(name); +} +void *os_dlsym(void *restrict lib, const char *restrict s) { + return (void *)GetProcAddress(lib, s); +} +int os_dlfile(void *lib, ushort *buf, int sz) { + unsigned int n = GetModuleFileNameW(lib, buf, sz); + if (n == 0 || n == sz) return -1; + // 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 n; +} + +bool os_mprot(void *addr, int len, int mode) { + ulong old; + return !!VirtualProtect(addr, len, mode, &old); +} + +#else + +int os_lasterror(void) { return errno; } + +int os_open_read(const char *path) { + return open(path, O_RDONLY | O_CLOEXEC); +} +int os_open_write(const char *path) { + return open(path, O_RDWR | O_CLOEXEC | O_CREAT, 0644); +} +int os_open_writetrunc(const char *path) { + return open(path, O_RDWR | O_CLOEXEC | O_CREAT | O_TRUNC, 0644); +} +int os_read(int f, void *buf, int max) { return read(f, buf, max); } +int os_write(int f, const void *buf, int max) { return write(f, buf, max); } +void os_close(int f) { close(f); } + +vlong os_fsize(int f) { + struct stat s; + if (stat(f, &s) == -1) return -1; + return s.st_size; +} + +void os_getcwd(char buf[PATH_MAX]) { getcwd(buf, PATH_MAX); } + +bool os_mkdir(const char *path) { return mkdir(path, 0555) != -1; } +bool os_unlink(const char *path) { return unlink(path) != -1; } +bool os_rmdir(const char *path) { return rmdir(path) != -1; } + +void *os_dlsym(void *restrict lib, const char *restrict name) { + return dlsym(lib, name); +} + +bool os_mprot(void *addr, int len, int mode) { + // round down address and round up size + addr = (void *)((ulong)addr & ~(4095)); + len = len + 4095 & ~(4095); + return mprotect(addr, len, mode) != -1; +} + +void os_randombytes(void *buf, int sz) { while (getentropy(buf, sz) == -1); } + +#endif + +#ifdef __linux__ +// glibc-specific stuff here. it shouldn't be used in build-time code, just +// the plugin itself (that really shouldn't be a problem). + +// private struct hidden behind _GNU_SOURCE. see dlinfo(3) or +struct link_map { + ulong l_addr; + const char *l_name; + void *l_ld; + struct link_map *l_next, *l_prev; + // [more private stuff below] +}; + +static struct link_map *lmbase = 0; + +void *os_dlhandle(const char *name) { + extern struct link_map *lmbase; // note: defined in sst.c for now + if (!lmbase) { // IMPORTANT: not thread safe. don't forget later! + lmbase = (struct link_map *)dlopen("libc.so.6", RTLD_LAZY | RTLD_NOLOAD); + dlclose(lmbase); // assume success + while (lmbase->l_prev) lmbase = lmbase->l_prev; + } + // this is a tiny bit crude, but basically okay. we just want to find + // something that roughly matches the basename, rather than needing an exact + // path, in a manner vaguely similar to Windows' GetModuleHandle(). that way + // we can just look up client.so or something without having to figure out + // where exactly that is. + for (struct link_map *lm = lmbase; lm; lm = lm->l_next) { + if (name[0] == '/') { + if (!strcmp(name, lm->l_name)) return lm; + continue; + } + int namelen = strlen(lm->l_name); + int sublen = strlen(name); + if (sublen >= namelen) continue; + if (lm->l_name[namelen - sublen - 1] == '/' && !memcmp( + lm->l_name + namelen - sublen, name, sublen)) { + return lm; + } + } + return 0; +} + +int os_dlfile(void *lib, char *buf, int sz) { + struct link_map *lm = lib; + int ssz = strlen(lm->l_name) + 1; + if (ssz > sz) { errno = ENAMETOOLONG; return -1; } + memcpy(buf, lm->l_name, ssz); + return ssz; +} +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/os.h b/src/os.h index c954d1a..b599523 100644 --- a/src/os.h +++ b/src/os.h @@ -1,5 +1,5 @@ /* - * Copyright © 2022 Michael Smith + * Copyright © 2024 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,48 +17,238 @@ #ifndef INC_OS_H #define INC_OS_H -/* - * Here we declare an absolute ton of wrappers, macros, compatibility shims, - * reimplementations and so on to try in vain to sweep the inconsistencies - * between Windows and not-Windows under the rug. - */ +#include +#include // XXX: try abstracting stat() and avoiding ucrt dep here -#include -#include #ifdef _WIN32 -#include -#include -#include -// DUMB HACK: noreturn.h is alphabetically before os.h so including it after -// looks weird and inconsistent. including it before breaks Windows.h though -// because of __declspec(noreturn). Undef for now, and restore at the end of -// this header. -#undef noreturn -#include -#include -#else -#include -#include -#include -#include -#include -#include -#include -#include -#include + +#include // XXX: there's kind of a lot of junk in this header! + +#define IMPORT __declspec(dllimport) // only needed for variables +#define EXPORT __declspec(dllexport) + +typedef unsigned short os_char; +#define _OS_CAT(x, y) x##y +#define OS_LIT(x) _OS_CAT(L, x) +#define os_strcmp wcscmp +#define os_strlen wcslen +// ucrt defines __stat64 to _stat64. we want _wstat64 to be the actual function +#define _stat64(path, buf) _wstat64(path, buf) +#define os_stat _stat64 + +#define OS_DLPREFIX "" +#define OS_DLSUFFIX ".dll" + +#define OS_MAIN wmain + +// add to these as needed... +#define OS_EEXIST /*ERROR_ALREADY_EXISTS*/ 183 +#define OS_ENOENT /*ERROR_PATH_NOT_FOUND*/ 3 + +// Further Windows-specific workarounds because Windows is great... + +#ifndef PATH_MAX +// XXX: win32/crt has this dumb 260 limit even though the actual kernel imposes +// no limit (though apparently NTFS has a limit of 65535). Theoretically we +// could do some memes with UNC paths to extend it to at least have parity with +// Unix PATH_MAX (4096), but for now we just kind of accept that Windows is a +// disaster. +#define PATH_MAX 260 #endif -// Things were getting unwieldy so they're now split into these headers... -#ifdef _WIN32 -#include "os-win32.h" -// DUMB HACK part 2: restore what the #including source file had asked for -#ifdef INC_NORETURN_H -#define noreturn _Noreturn void +// windows doesn't define this for some reason!? note: add others as needed... +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) + +#define alloca _alloca // ugh! + +// one last thing: mprot constants are part of os.h API, whether or not +// Windows.h was included. this is a bit of a hack, but whatever! +#ifndef _INC_WINDOWS +#define PAGE_NOACCESS 1 +#define PAGE_READONLY 2 +#define PAGE_READWRITE 4 +#define PAGE_EXECUTE_READ 32 +#define PAGE_EXECUTE_READWRITE 64 #endif + #else -#include "os-unix.h" + +#include // meh + +#define IMPORT +#ifdef __GNUC__ +#define EXPORT __attribute__((visibility("default")) +#else +#define EXPORT int exp[-!!"compiler needs a way to export symbols!"]; +#endif + +// trying to avoid pulling in unnecessary headers as much as possible: define +// our own constants for os_mprot() / mprotect() +#if defined(__linux__) // apparently linux is pretty much the sole oddball here! +#define _OS_PROT_READ 4 +#define _OS_PROT_WRITE 2 +#define _OS_PROT_EXEC 1 +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || \ + defined(__DragonFly__) || defined(__sun) || defined(__serenity) +#define _OS_PROT_READ 1 +#define _OS_PROT_WRITE 2 +#define _OS_PROT_EXEC 4 +#elif !defined(_WIN32) // what else could this possibly even be!? +#include // just fall back on this. not the end of the world... +#define _OS_PROT_READ PROT_READ +#define _OS_PROT_WRITE PROT_WRITE +#define _OS_PROT_EXEC PROT_EXEC #endif +typedef char os_char; +#define OS_LIT(x) x +#define os_strcmp strcmp +#define os_strlen strlen +#define os_stat stat + +#define OS_DLPREFIX "lib" +#define OS_DLSUFFIX ".so" + +#define OS_MAIN main + +// unix mprotect 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! +#define PAGE_NOACCESS 0 +#define PAGE_READONLY _OS_PROT_READ +#define PAGE_READWRITE _OS_PROT_READ | _OS_PROT_WRITE +#define PAGE_EXECUTE_READ _OS_PROT_READ | _OS_PROT_EXEC +#define PAGE_EXECUTE_READWRITE _OS_PROT_READ | _OS_PROT_WRITE | _OS_PROT_EXEC + +#define OS_EEXIST EEXIST +#define OS_ENOENT ENOENT + +#endif + +/* Copies n characters from src to dest, using the OS-specific char type. */ +static inline void os_spancopy(os_char *restrict dest, + const os_char *restrict src, int n) { + memcpy(dest, src, n * sizeof(os_char)); +} + +/* + * Returns the last error code from an OS function - equivalent to calling + * GetLastError() in Windows and reading errno in Unix-like operating systems. + * For standard libc functions (implemented by UCRT on Windows), the value of + * errno should be used directly instead. + */ +int os_lasterror(void); + +/* + * Opens a file for reading. Returns an OS-specific file handle, or -1 on error. + */ +int os_open_read(const os_char *path); + +/* + * Opens a file for writing, creating it if required. Returns an OS-specific + * file handle, or -1 on error. + */ +int os_open_write(const os_char *path); + +/* + * Opens a file for writing, creating it if required, or truncating it if it + * already exists. Returns an OS-specific file handle, or -1 on error. + */ +int os_open_writetrunc(const os_char *path); + +/* + * Reads up to max bytes from OS-specific file handle f into buf. Returns the + * number of bytes read, or -1 on error. + */ +int os_read(int f, void *buf, int max); + +/* + * Reads up to len bytes from buf to OS-specific file handle f. Returns the + * number of bytes written, or -1 on error. Generally the number of bytes + * written will be len, unless writing to a pipe/socket, which has a limited + * internal buffer, or possibly being preempted by a signal handler. + */ +int os_write(int f, const void *buf, int len); + +/* + * Returns the length of the on-disk file referred to by OS-specific file handle + * f, or -1 on error (the most likely error would be that the file is not + * actually on disk and instead refers to a pipe or something). + */ +long long os_fsize(int f); + +/* + * Closes the OS-specific file handle f. On Windows, this causes pending writes + * to be flushed; on Unix-likes, this generally happens asynchronously. If + * blocking is a concern when writing files to disk, some sort of thread pool + * should always be used. + */ +void os_close(int f); + +/* + * Gets the current working directory, which may be up to PATH_MAX characters in + * length (using the OS-specific character type). + */ +void os_getcwd(os_char buf[static PATH_MAX]); + +/* + * Tries to create a directory at path. Returns true on success, false on + * failure. One may wish to ignore a failure if the directory already exists; + * this can be done by checking if os_lasterror() returns OS_EEXIST. + */ +bool os_mkdir(const os_char *path); + +/* + * Tries to delete a file(name) at path. Returns true on success, false on + * failure. One may wish to ignore a failure if the file already doesn't exist; + * this can be done by checking if os_lasterror() returns OS_ENOENT. + * + * On some Unix-like operating systems, this may work on empty directories, but + * for portably reliable results, one should call os_rmdir() for that instead. + */ +bool os_unlink(const os_char *path); + +/* + * Tries to delete a directory at path. Returns true on success, false on + * failure. One may wish to ignore a failure if the directory already doesn't + * exist; this can be done by checking if os_lasterror() returns OS_ENOENT. + */ +bool os_rmdir(const os_char *path); + +/* + * Tries to look up the symbol sym from the shared library handle lib. Returns + * an opaque pointer on success, or null on failure. + */ +void *os_dlsym(void *restrict lib, const char *restrict sym); + +#if defined(_WIN32) || defined(__linux__) +/* + * Tries to get a handle to an already-loaded shared library matching name. + * Returns the library handle on success, or null on failure. + */ +void *os_dlhandle(const os_char *name); + +/* + * Tries to determine the file path to the shared library handle lib, and stores + * it in buf (with max length given by sz). Returns the length of the resulting + * string, or -1 on failure. + */ +int os_dlfile(void *lib, os_char *buf, int sz); +#endif + +/* + * Changes memory protection for the address range given by addr and len, using + * one of the Win32-style PAGE_* flags specified above. Returns true on success + * and false on failure. + */ +bool os_mprot(void *addr, int len, int mode); + +/* + * Fills buf with up to sz cryptographically random bytes. sz has an OS-specific + * upper limit - a safe value across all major operating systems is 256. + */ +void os_randombytes(void *buf, int sz); + #endif // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/sst.c b/src/sst.c index de04b88..ce80032 100644 --- a/src/sst.c +++ b/src/sst.c @@ -14,13 +14,14 @@ * PERFORMANCE OF THIS SOFTWARE. */ -#ifndef _WIN32 -#include // unsetenv -#endif #include #ifdef _WIN32 +#include #include +#else +#include // unsetenv +#include #endif #include "con_.h" @@ -74,8 +75,6 @@ static void *ownhandle(void) { } return cached; } - -struct gnu_link_map *_os_lmbase = 0; // XXX: stupid place to put this, oh well #endif #ifdef _WIN32 @@ -99,7 +98,7 @@ DEF_CCMD_HERE(sst_autoload_enable, "Register SST to load on game startup", 0) { const os_char *startdir; if (ifacever == 2) { startdir = _startdir; - os_getcwd(_startdir, PATH_MAX); // if this fails, OS devs are all fired. + os_getcwd(_startdir); #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 @@ -112,13 +111,12 @@ DEF_CCMD_HERE(sst_autoload_enable, "Register SST to load on game startup", 0) { errmsg_errorx("path to game is too long"); return; } - memcpy(_startdir + len, #ifdef _WIN32 - L"\\bin", // PathRelativePathToW actually NEEDS a backslash, UGH + // PathRelativePathToW actually NEEDS a backslash, UGH + os_spancopy(_startdir + len, L"\\bin", 5); #else - "/bin", + os_spancopy(_startdir + len, L"/bin", 5); #endif - 5 * sizeof(os_char)); } else /* ifacever == 3 */ { // newer games load from the mod dir instead of engine bin, and search @@ -143,8 +141,17 @@ DEF_CCMD_HERE(sst_autoload_enable, "Register SST to load on game startup", 0) { errmsg_errorsys("couldn't compute a relative path"); return; } - // arbitrary aesthetic judgement - for (ushort *p = relpath; *p; ++p) if (*p == L'\\') *p = L'/'; + // arbitrary aesthetic judgement - use forward slashes. while we're at it, + // also make sure there's no unicode in there, just in case... + int rellen = 0; + for (ushort *p = relpath; *p; ++p, ++rellen) { + if (*p > 127) { + errmsg_errorx("mod dir contains Unicode characters which Source " + "doesn't handle well - autoload file not created"); + return; + } + if (*p == L'\\') *p = L'/'; + } #else const char *p = path, *q = startdir; int slash = 0; @@ -177,27 +184,34 @@ c: memcpy(r, p + slash + 1, rellen); errmsg_errorx("path to VDF is too long"); return; } - memcpy(path, gameinfo_gamedir, len * sizeof(*gameinfo_gamedir)); - memcpy(path + len, OS_LIT("/addons"), 8 * sizeof(os_char)); - if (os_mkdir(path) == -1 && errno != EEXIST) { - errmsg_errorstd("couldn't create %" fS, path); + os_spancopy(path, gameinfo_gamedir, len); + os_spancopy(path + len, OS_LIT("/addons"), 8); + if (!os_mkdir(path) && os_lasterror() != OS_EEXIST) { + errmsg_errorsys("couldn't create %" fS, path); return; } - memcpy(path + len + sizeof("/addons") - 1, + os_spancopy(path + len + sizeof("/addons") - 1, OS_LIT("/") OS_LIT(VDFBASENAME) OS_LIT(".vdf"), - sizeof("/" VDFBASENAME ".vdf") * sizeof(os_char)); - FILE *f = os_fopen(path, OS_LIT("wb")); - if (!f) { - errmsg_errorstd("couldn't open %" fS, path); - return; - } - // 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); + sizeof("/" VDFBASENAME ".vdf")); + int f = os_open_write(path); + if (f == -1) { errmsg_errorsys("couldn't open %" fS, path); return; } +#ifdef _WIN32 + char buf[19 + PATH_MAX]; + memcpy(buf, "Plugin { file \"", 15); + for (int i = 0; i < rellen; ++i) buf[i + 15] = relpath[i]; + memcpy(buf + 15 + rellen, "\" }\n", 4); + if (os_write(f, buf, rellen + 19) == -1) { // blegh +#else + struct iovec iov[3] = { + {"Plugin { file \"", 15}, + {relpath, rellen}, + {"\" }\n", 4} + }; + if (writev(fd, &iov, 3) == -1) { +#endif + errmsg_errorsys("couldn't write to %" fS, path); } - fclose(f); + os_close(f); } DEF_CCMD_HERE(sst_autoload_disable, "Stop loading SST on game startup", 0) { @@ -208,11 +222,11 @@ DEF_CCMD_HERE(sst_autoload_disable, "Stop loading SST on game startup", 0) { errmsg_errorx("path to VDF is too long"); return; } - memcpy(path, gameinfo_gamedir, len * sizeof(*gameinfo_gamedir)); - memcpy(path + len, OS_LIT("/addons/") OS_LIT(VDFBASENAME) OS_LIT(".vdf"), - sizeof("/addons/" VDFBASENAME ".vdf") * sizeof(os_char)); - if (os_unlink(path) == -1 && errno != ENOENT) { - errmsg_warnstd("couldn't delete %" fS, path); + os_spancopy(path, gameinfo_gamedir, len); + os_spancopy(path + len, OS_LIT("/addons/") OS_LIT(VDFBASENAME) OS_LIT(".vdf"), + sizeof("/addons/" VDFBASENAME ".vdf")); + if (!os_unlink(path) && os_lasterror() != OS_ENOENT) { + errmsg_warnsys("couldn't delete %" fS, path); } } diff --git a/src/stubs/BCryptPrimitives.c b/src/stubs/BCryptPrimitives.c new file mode 100644 index 0000000..afcaa7e --- /dev/null +++ b/src/stubs/BCryptPrimitives.c @@ -0,0 +1,6 @@ +/* This file is dedicated to the public domain. */ + +// We have to define a real function with the right number of arguments to get +// the mangled symbol _ProcessPrng@8, which the .def file apparently turns +// into an "undecorate" .lib entry through some undocumented magic (of course!). +int __stdcall ProcessPrng(void *x, unsigned int y) { return 0; } diff --git a/src/stubs/bcryptprimitives.def b/src/stubs/bcryptprimitives.def new file mode 100644 index 0000000..9021d7c --- /dev/null +++ b/src/stubs/bcryptprimitives.def @@ -0,0 +1,7 @@ +; This file is dedicated to the public domain. + +LIBRARY bcryptprimitives +EXPORTS + ProcessPrng + +; vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/test/hook.test.c b/test/hook.test.c index 7cbfd15..a2447cc 100644 --- a/test/hook.test.c +++ b/test/hook.test.c @@ -6,6 +6,7 @@ #include "../src/x86.c" #include "../src/hook.c" +#include "../src/os.c" #include #include -- cgit v1.2.3