diff options
52 files changed, 1666 insertions, 1606 deletions
@@ -1,6 +1,6 @@ Except where otherwise noted, the following terms apply: ════════════════════════════════════════════════════════════════════════════════ -Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> +Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> Copyright © 2024 Willian Henrique <wsimanbrazil@yahoo.com.br> Copyright © 2024 Hayden K <imaciidz@gmail.com> Copyright © 2023 Matthew Wozniak <sirtomato999@gmail.com> @@ -21,6 +21,8 @@ warnings="-Wall -pedantic -Wno-parentheses -Wno-missing-braces \ -Wno-gnu-zero-variadic-macro-arguments -Werror=implicit-function-declaration \ -Werror=vla" +stdflags="-std=c2x -D_DEFAULT_SOURCE -D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64" + dbg=0 if [ "$dbg" = 1 ]; then cflags="-O0 -g3" @@ -38,10 +40,8 @@ cc() { # ugly annoying special case if [ "$_mn" = " -DMODULE_NAME=con_" ]; then _mn=" -DMODULE_NAME=con" elif [ "$_mn" = "-DMODULE_NAME=sst" ]; then _mn=; fi - # note: using typeof and bool from C23 - see detailed comment in compile.bat $CC -c -flto -fpic -fno-ident $cflags $warnings -I.build/include \ - -D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64$_mn \ - -Dtypeof=__typeof -include stdbool.h -o ".build/${_bn%%.c}.o" "src/$1" + $stdflags$_mn -o ".build/${_bn%%.c}.o" "src/$1" } ld() { @@ -69,7 +69,6 @@ src="\ fastfwd.c fixes.c fov.c - gamedata.c gameinfo.c gameserver.c hexcolour.c @@ -85,20 +84,20 @@ src="\ portalcolours.c sst.c trace.c - xhair.c - x86.c" + x86.c + xhair.c" if [ "$dbg" = 1 ]; then src="$src \ dbg.c udis86.c" fi -$HOSTCC -O2 -fuse-ld=lld $warnings -D_FILE_OFFSET_BITS=64 -include stdbool.h \ - -o .build/codegen src/build/codegen.c src/build/cmeta.c src/os.c -$HOSTCC -O2 -fuse-ld=lld $warnings -D_FILE_OFFSET_BITS=64 -include stdbool.h \ +$HOSTCC -O2 -fuse-ld=lld $warnings $stdflags \ + -o .build/gluegen src/build/gluegen .c src/build/cmeta.c src/os.c +$HOSTCC -O2 -fuse-ld=lld $warnings $stdflags \ -o .build/mkgamedata src/build/mkgamedata.c src/os.c -$HOSTCC -O2 -fuse-ld=lld $warnings -D_FILE_OFFSET_BITS=64 -include stdbool.h \ +$HOSTCC -O2 -fuse-ld=lld $warnings $stdflags \ -o .build/mkentprops src/build/mkentprops.c src/os.c -.build/codegen `for s in $src; do echo "src/$s"; done` +.build/gluegen `for s in $src; do echo "src/$s"; done` .build/mkgamedata gamedata/engine.txt gamedata/gamelib.txt gamedata/inputsystem.txt \ gamedata/matchmaking.txt gamedata/vgui2.txt gamedata/vguimatsurface.txt .build/mkentprops gamedata/entprops.txt @@ -107,14 +106,14 @@ $CC -shared -fpic -fuse-ld=lld -O0 -w -o .build/libtier0.so src/stubs/tier0.c $CC -shared -fpic -fuse-ld=lld -O0 -w -o .build/libvstdlib.so src/stubs/vstdlib.c ld -$HOSTCC -O2 -g3 -include test/test.h -o .build/bitbuf.test test/bitbuf.test.c +$HOSTCC -O2 -g3 $warnings $stdflags -include test/test.h -o .build/bitbuf.test test/bitbuf.test.c .build/bitbuf.test # skipping this test on linux for now, since inline hooks aren't compiled in #$HOSTCC -m32 -O2 -g3 -include test/test.h -o .build/hook.test test/hook.test.c #.build/hook.test -$HOSTCC -O2 -g3 -include test/test.h -o .build/kv.test test/kv.test.c +$HOSTCC -O2 -g3 $warnings $stdflags -include test/test.h -o .build/kv.test test/kv.test.c .build/kv.test -$HOSTCC -O2 -g3 -include test/test.h -o .build/x86.test test/x86.test.c +$HOSTCC -O2 -g3 $warnings $stdflags -include test/test.h -o .build/x86.test test/x86.test.c .build/x86.test # vi: sw=4 tw=4 noet tw=80 cc=80 diff --git a/compile.bat b/compile.bat index 9f736e4..8b0e877 100644 --- a/compile.bat +++ b/compile.bat @@ -47,9 +47,8 @@ set objs=%objs% .build/%basename%.o :: year to get anything done. typeof=__typeof prevents pedantic warnings caused
:: by typeof still technically being an extension, and stdbool gives us
:: predefined bool/true/false before compilers start doing that by default
-%CC% -c -flto -mno-stack-arg-probe %cflags% %warnings% -I.build/include ^
--D_CRT_SECURE_NO_WARNINGS -D_DLL -DWIN32_LEAN_AND_MEAN -DNOMINMAX%dmodname% ^
--Dtypeof=__typeof -include stdbool.h -o .build/%basename%.o %1 || goto :end
+%CC% -c -flto -mno-stack-arg-probe %cflags% %warnings% %stdflags% -I.build/include ^
+-D_DLL%dmodname% -o .build/%basename%.o %1 || goto :end
goto :eof
:src
@@ -57,6 +56,8 @@ goto :eof :main
+set stdflags=-std=c2x -D_CRT_SECURE_NO_WARNINGS -DWIN32_LEAN_AND_MEAN -DNOMINMAX
+
set src=
:: funny hack to build a list conveniently, lol.
setlocal EnableDelayedExpansion
@@ -80,7 +81,6 @@ setlocal DisableDelayedExpansion :+ fastfwd.c
:+ fixes.c
:+ fov.c
-:+ gamedata.c
:+ gameinfo.c
:+ gameserver.c
:+ hexcolour.c
@@ -98,8 +98,8 @@ setlocal DisableDelayedExpansion :+ rinput.c
:+ sst.c
:+ trace.c
-:+ xhair.c
:+ x86.c
+:+ xhair.c
:: just tack these on, whatever (repeated condition because of expansion memes)
if "%dbg%"=="1" set src=%src% src/dbg.c
if "%dbg%"=="1" set src=%src% src/udis86.c
@@ -115,13 +115,13 @@ if %host64%==1 ( %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 -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -include stdbool.h ^
--L.build %lbcryptprimitives_host% -o .build/codegen.exe src/build/codegen.c src/build/cmeta.c src/os.c || goto :end
-%HOSTCC% -fuse-ld=lld -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -include stdbool.h ^
+%HOSTCC% -fuse-ld=lld -O2 %warnings% %stdflags% -include stdbool.h ^
+-L.build %lbcryptprimitives_host% -o .build/gluegen.exe src/build/gluegen.c src/build/cmeta.c src/os.c || goto :end
+%HOSTCC% -fuse-ld=lld -O2 %warnings% %stdflags% -include stdbool.h ^
-L.build %lbcryptprimitives_host% -o .build/mkgamedata.exe src/build/mkgamedata.c src/os.c || goto :end
-%HOSTCC% -fuse-ld=lld -O2 -g %warnings% -D_CRT_SECURE_NO_WARNINGS -include stdbool.h ^
+%HOSTCC% -fuse-ld=lld -O2 %warnings% %stdflags% -include stdbool.h ^
-L.build %lbcryptprimitives_host% -o .build/mkentprops.exe src/build/mkentprops.c src/os.c || goto :end
-.build\codegen.exe%src% || goto :end
+.build\gluegen.exe%src% || goto :end
.build\mkgamedata.exe gamedata/engine.txt gamedata/gamelib.txt gamedata/inputsystem.txt ^
gamedata/matchmaking.txt gamedata/vgui2.txt gamedata/vguimatsurface.txt || goto :end
.build\mkentprops.exe gamedata/entprops.txt || goto :end
@@ -158,12 +158,12 @@ move /y .build\sst.dll sst.dll >nul 2>nul || ( :: in some eventual future invocation
if exist .build\sst.old.dll del .build\sst.old.dll >nul 2>nul
-%HOSTCC% -fuse-ld=lld -O2 -g -include test/test.h -o .build/bitbuf.test.exe test/bitbuf.test.c || goto :end
+%HOSTCC% -fuse-ld=lld -O2 -g %warnings% %stdflags% -include test/test.h -o .build/bitbuf.test.exe test/bitbuf.test.c || goto :end
.build\bitbuf.test.exe || goto :end
:: special case: test must be 32-bit
-%HOSTCC% -fuse-ld=lld -m32 -O2 -g -L.build -lbcryptprimitives -include test/test.h -o .build/hook.test.exe test/hook.test.c || goto :end
+%HOSTCC% -fuse-ld=lld -m32 -O2 -g %warnings% %stdflags% -L.build -lbcryptprimitives -include test/test.h -o .build/hook.test.exe test/hook.test.c || goto :end
.build\hook.test.exe || goto :end
-%HOSTCC% -fuse-ld=lld -O2 -g -include test/test.h -o .build/x86.test.exe test/x86.test.c || goto :end
+%HOSTCC% -fuse-ld=lld -O2 -g %warnings% %stdflags% -include test/test.h -o .build/x86.test.exe test/x86.test.c || goto :end
.build\x86.test.exe || goto :end
:end
diff --git a/dist/LICENCE.linux b/dist/LICENCE.linux index 2647bda..8991329 100644 --- a/dist/LICENCE.linux +++ b/dist/LICENCE.linux @@ -1,6 +1,6 @@ Source Speedrun Tools is released under the following copyright licence: ════════════════════════════════════════════════════════════════════════════════ -Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> +Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> Copyright © 2024 Willian Henrique <wsimanbrazil@yahoo.com.br> Copyright © 2024 Hayden K <imaciidz@gmail.com> Copyright © 2023 Matthew Wozniak <sirtomato999@gmail.com> diff --git a/dist/LICENCE.windows b/dist/LICENCE.windows index 7905ac8..1691946 100644 --- a/dist/LICENCE.windows +++ b/dist/LICENCE.windows @@ -1,6 +1,6 @@ Source Speedrun Tools is released under the following copyright licence:
════════════════════════════════════════════════════════════════════════════════
-Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com>
+Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com>
Copyright © 2024 Willian Henrique <wsimanbrazil@yahoo.com.br>
Copyright © 2024 Hayden K <imaciidz@gmail.com>
Copyright © 2023 Matthew Wozniak <sirtomato999@gmail.com>
diff --git a/src/3p/chibicc/chibicc.h b/src/3p/chibicc/chibicc.h index 2a80ecf..f3f87ab 100644 --- a/src/3p/chibicc/chibicc.h +++ b/src/3p/chibicc/chibicc.h @@ -117,9 +117,6 @@ static inline char *format(const char *fmt, ...) { return ret; } -#define unreachable() \ - error("internal error at %s:%d", __FILE__, __LINE__) - // // type.c // diff --git a/src/3p/chibicc/hashmap.c b/src/3p/chibicc/hashmap.c index 47110c6..2090274 100644 --- a/src/3p/chibicc/hashmap.c +++ b/src/3p/chibicc/hashmap.c @@ -2,6 +2,9 @@ #include "chibicc.h" +// mike: moved from chibicc.h and also renamed, to avoid conflicts with langext.h +#define chibi_unreachable() error("internal error at %s:%d", __FILE__, __LINE__) + // Initial hash bucket size #define INIT_SIZE 16 @@ -70,7 +73,7 @@ static HashEntry *get_entry(HashMap *map, char *key, int keylen) { if (ent->key == NULL) return NULL; } - unreachable(); + chibi_unreachable(); } static HashEntry *get_or_insert_entry(HashMap *map, char *key, int keylen) { @@ -102,7 +105,7 @@ static HashEntry *get_or_insert_entry(HashMap *map, char *key, int keylen) { return ent; } } - unreachable(); + chibi_unreachable(); } void *hashmap_get(HashMap *map, char *key) { @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> * Copyright © 2023 Willian Henrique <wsimanbrazil@yahoo.com.br> * * Permission to use, copy, modify, and/or distribute this software for any @@ -32,13 +32,13 @@ #include "crypto.h" #include "democustom.h" #include "demorec.h" -#include "hook.h" #include "engineapi.h" #include "errmsg.h" #include "event.h" #include "feature.h" #include "gamedata.h" #include "gametype.h" +#include "hook.h" #include "intdefs.h" #include "langext.h" #include "mem.h" @@ -50,6 +50,7 @@ #include "x86util.h" FEATURE() +GAMESPECIFIC(L4D) // TODO(compat): wanna add support for more stuff, obviously! REQUIRE(bind) REQUIRE(democustom) REQUIRE_GAMEDATA(vtidx_GetDesktopResolution) @@ -291,7 +292,7 @@ static void hook_Key_Event(struct inputevent *ev) { //const char *desc[] = {"DOWN", "UP", "DBL"}; //const char desclen[] = {4, 2, 3}; switch (ev->type) { - CASES(BTNDOWN, BTNUP, BTNDOUBLECLICK):; + CASES(BTNDOWN, BTNUP, BTNDOUBLECLICK): // TODO(rta): do something interesting with button data //uchar buf[28], *p = buf; //msg_putasz4(p, 2); p += 1; @@ -378,24 +379,20 @@ HANDLE_EVENT(PluginUnloaded, void) { // TODO(rta): do something with plugin list here } -PREINIT { - return GAMETYPE_MATCHES(L4D); // TODO(compat): add more here obviously -} - INIT { - if_cold (!find_Key_Event()) return false; + if_cold (!find_Key_Event()) return FEAT_INCOMPAT; orig_Key_Event = (Key_Event_func)hook_inline((void *)orig_Key_Event, (void *)&hook_Key_Event); if_cold (!orig_Key_Event) { errmsg_errorsys("couldn't hook Key_Event function"); - return false; + return FEAT_FAIL; } #ifdef _WIN32 keybox = VirtualAlloc(0, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if_cold (!keybox) { errmsg_errorsys("couldn't allocate memory for session state"); - return false; + return FEAT_FAIL; } if_cold (!VirtualLock(keybox, 4096)) { errmsg_errorsys("couldn't secure session state"); @@ -411,7 +408,7 @@ INIT { keybox = mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); if_cold (keybox == MAP_FAILED) { errmsg_errorstd("couldn't allocate memory for session state"); - return false; + return FEAT_FAIL; } // linux-specific madvise stuff (there are some equivalents in OpenBSD and // FreeBSD, if anyone's wondering, but we don't need to worry about those) @@ -432,7 +429,7 @@ INIT { // run of bytes memcpy(keybox->lbpub, lbpubkeys[LBPK_L4D], 32); } - return true; + return FEAT_OK; #ifdef _WIN32 e: WerUnregisterExcludedMemoryBlock(keybox); // this'd better not fail! @@ -441,7 +438,7 @@ e2: VirtualFree(keybox, 4096, MEM_RELEASE); e: munmap(keybox, 4096); #endif unhook_inline((void *)orig_Key_Event); - return false; + return FEAT_FAIL; } END { diff --git a/src/alias.c b/src/alias.c index e6f5f7e..a2d2b21 100644 --- a/src/alias.c +++ b/src/alias.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -50,7 +50,7 @@ void alias_rm(const char *name) { } } -DEF_CCMD_HERE_UNREG(sst_alias_clear, "Remove all command aliases", 0) { +DEF_FEAT_CCMD_HERE(sst_alias_clear, "Remove all command aliases", 0) { if (cmd->argc != 1) { con_warn("usage: sst_alias_clear\n"); return; @@ -58,7 +58,7 @@ DEF_CCMD_HERE_UNREG(sst_alias_clear, "Remove all command aliases", 0) { alias_nuke(); } -DEF_CCMD_HERE_UNREG(sst_alias_remove, "Remove a command alias", 0) { +DEF_FEAT_CCMD_HERE(sst_alias_remove, "Remove a command alias", 0) { if (cmd->argc != 2) { con_warn("usage: sst_alias_remove name\n"); return; @@ -91,16 +91,14 @@ static bool find_alias_head(con_cmdcb alias_cb) { INIT { // TODO(compat): no idea why sst_alias_clear crashes in p2, figure out later - if (GAMETYPE_MATCHES(Portal2)) return false; + if (GAMETYPE_MATCHES(Portal2)) return FEAT_INCOMPAT; struct con_cmd *cmd_alias = con_findcmd("alias"); if_cold (!find_alias_head(con_getcmdcb(cmd_alias))) { errmsg_warnx("couldn't find alias list"); - return false; + return FEAT_INCOMPAT; } - con_reg(sst_alias_clear); - con_reg(sst_alias_remove); - return true; + return FEAT_OK; } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/autojump.c b/src/autojump.c index bc6739f..23bc2e0 100644 --- a/src/autojump.c +++ b/src/autojump.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -35,8 +35,8 @@ REQUIRE_GLOBAL(factory_client) // note: server will never be null DEF_ACCESSORS(struct CMoveData *, mv) -DEF_CVAR(sst_autojump, "Jump upon hitting the ground while holding space", 0, - CON_REPLICATE | CON_DEMO | CON_HIDDEN) +DEF_FEAT_CVAR(sst_autojump, "Jump upon hitting the ground while holding space", 0, + CON_REPLICATE | CON_DEMO) #define NIDX 256 // *completely* arbitrary lol static bool justjumped[NIDX] = {0}; @@ -90,21 +90,20 @@ INIT { gmsv = factory_server("GameMovement001", 0); if_cold (!gmsv) { errmsg_errorx("couldn't get server-side game movement interface"); - return false; + return FEAT_FAIL; } if_cold (!unprot(gmsv)) return false; gmcl = factory_client("GameMovement001", 0); if_cold (!gmcl) { errmsg_errorx("couldn't get client-side game movement interface"); - return false; + return FEAT_FAIL; } - if_cold (!unprot(gmcl)) return false; + if_cold (!unprot(gmcl)) return FEAT_FAIL; origsv = (CheckJumpButton_func)hook_vtable(*(void ***)gmsv, vtidx_CheckJumpButton, (void *)&hooksv); origcl = (CheckJumpButton_func)hook_vtable(*(void ***)gmcl, vtidx_CheckJumpButton, (void *)&hookcl); - sst_autojump->base.flags &= ~CON_HIDDEN; if (GAMETYPE_MATCHES(Portal1)) { // this is a stupid, stupid policy that doesn't make any sense, but I've // tried arguing about it already and with how long it takes to convince @@ -116,7 +115,7 @@ INIT { sv_cheats = con_findvar("sv_cheats"); sst_autojump->cb = cheatcb; } - return true; + return FEAT_OK; } END { @@ -60,9 +60,9 @@ INIT { con_cmdcb cb = con_getcmdcb(cmd_key_listboundkeys); if_cold (!find_keyinfo(cb)) { errmsg_warnx("couldn't find key binding list"); - return false; + return FEAT_INCOMPAT; } - return true; + return FEAT_OK; } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/cmeta.c b/src/build/cmeta.c index 8a2416d..1903e84 100644 --- a/src/build/cmeta.c +++ b/src/build/cmeta.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -15,22 +15,12 @@ */ #include <stdio.h> -#include <string.h> +#include <stdlib.h> #include "../intdefs.h" +#include "../langext.h" #include "../os.h" #include "cmeta.h" -#include "vec.h" - -/* - * This file does C metadata parsing/scraping for the build system. This - * facilitates tasks ranging from determining header dependencies to searching - * for certain magic macros (for example cvar/command declarations) to generate - * other code. - * - * It's a bit of a mess since it's kind of just hacked together for use at build - * time. Don't worry about it too much. - */ // lazy inlined 3rd party stuff {{{ // too lazy to write a C tokenizer at the moment, or indeed probably ever, so @@ -56,350 +46,218 @@ Type *ty_double = &(Type){TY_DOUBLE, 8, 8}; Type *ty_ldouble = &(Type){TY_LDOUBLE, 16, 16}; // inline just a couple more things, super lazy, but whatever static Type *new_type(TypeKind kind, int size, int align) { - Type *ty = calloc(1, sizeof(Type)); - ty->kind = kind; - ty->size = size; - ty->align = align; - return ty; + Type *ty = calloc(1, sizeof(Type)); + ty->kind = kind; + ty->size = size; + ty->align = align; + return ty; } Type *array_of(Type *base, int len) { - Type *ty = new_type(TY_ARRAY, base->size * len, base->align); - ty->base = base; - ty->array_len = len; - return ty; + Type *ty = new_type(TY_ARRAY, base->size * len, base->align); + ty->base = base; + ty->array_len = len; + return ty; } #include "../3p/chibicc/hashmap.c" #include "../3p/chibicc/strings.c" #include "../3p/chibicc/tokenize.c" -// one more copypaste from preprocess.c for #include <filename> and then I'm -// done I promise -static char *join_tokens(const Token *tok, const Token *end) { - int len = 1; - for (const Token *t = tok; t != end && t->kind != TK_EOF; t = t->next) { - if (t != tok && t->has_space) - len++; - len += t->len; - } - char *buf = calloc(1, len); - int pos = 0; - for (const Token *t = tok; t != end && t->kind != TK_EOF; t = t->next) { - if (t != tok && t->has_space) - buf[pos++] = ' '; - strncpy(buf + pos, t->loc, t->len); - pos += t->len; - } - buf[pos] = '\0'; - return buf; -} // }}} #ifdef _WIN32 #include "../3p/openbsd/asprintf.c" // missing from libc; plonked here for now #endif -static void die(const char *s) { +static noreturn die(int status, const char *s) { fprintf(stderr, "cmeta: fatal: %s\n", s); - exit(100); + exit(status); } -static char *readsource(const os_char *path) { +struct cmeta cmeta_loadfile(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 = os_read(f, buf + off, bufsz - off)) > 0) { - off += nread; - if (off == bufsz) { - bufsz *= 2; - // somewhat arbitrary cutoff - if (bufsz == 1 << 30) die("input file is too large"); - buf = realloc(buf, bufsz); - if (!buf) die("couldn't reallocate memory"); - } - } - if (nread == -1) die("couldn't read file"); - buf[off] = 0; + if (f == -1) die(100, "couldn't open file"); + vlong len = os_fsize(f); + if (len > 1u << 30 - 1) die(2, "input file is far too large"); + struct cmeta ret; + ret.sbase = malloc(len + 1); + ret.sbase[len] = '\0'; // chibicc needs a null terminator + if (!ret.sbase) die(100, "couldn't allocate memory"); + if (os_read(f, ret.sbase, len) != len) die(100, "couldn't read file"); + int maxitems = len / 4; // shortest word is "END" + ret.nitems = 0; + // eventual overall memory requirement: file size * 6. seems fine to me. + // current memory requirement: file size * 10, + all the chibicc linked list + // crap. not as good but we'll continue tolerating it... probably for years! + //ret.itemoffs = malloc(maxitems * sizeof(*ret.itemoffs)); + //if (!ret.itemoffs) die(100, "couldn't allocate memory"); + ret.itemtoks = malloc(maxitems * sizeof(*ret.itemtoks)); + if (!ret.itemtoks) die(100, "couldn't allocate memory"); + ret.itemtypes = malloc(maxitems * sizeof(*ret.itemtypes)); + if (!ret.itemtypes) die(100, "couldn't allocate memory"); 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 *path) { - char *buf = readsource(path); - if (!buf) return 0; #ifdef _WIN32 char *realname = malloc(wcslen(path) + 1); - if (!realname) die("couldn't allocate memory"); + if (!realname) die(100, "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. + // though, so we don't really care as much. this code still sucks though. *realname = *path; for (const ushort *p = path + 1; p[-1]; ++p) realname[p - path] = *p; #else const char *realname = f; #endif - return (const struct cmeta *)tokenize_buf(realname, buf); -} - -// NOTE: we don't care about conditional includes, nor do we expand macros. We -// just parse the minimum info to get what we need for SST. Also, there's not -// too much in the way of syntax checking; if an error gets ignored the compiler -// picks it up anyway, and gives far better diagnostics. -void cmeta_includes(const struct cmeta *cm, - void (*cb)(const char *f, bool issys, void *ctxt), void *ctxt) { - const Token *tp = (const Token *)cm; - if (!tp || !tp->next || !tp->next->next) return; // #, include, "string" - while (tp) { - if (!tp->at_bol || !equal(tp, "#")) { tp = tp->next; continue; } - if (!equal(tp->next, "include")) { tp = tp->next->next; continue; } - tp = tp->next->next; - if (!tp) break; - if (tp->at_bol) tp = tp->next; - if (!tp) break; - if (tp->kind == TK_STR) { - // include strings are a special case; they don't have \escapes. - char *copy = malloc(tp->len - 1); - if (!copy) die("couldn't allocate memory"); - memcpy(copy, tp->loc + 1, tp->len - 2); - copy[tp->len - 2] = '\0'; - cb(copy, false, ctxt); - //free(copy); // ?????? + struct Token *t = tokenize_buf(realname, ret.sbase); + // everything is THING() or THING {} so we need at least 3 tokens ahead - if + // we have fewer tokens left in the file we can bail + if (t && t->next) while (t->next->next) { + if (!t->at_bol) { + t = t->next; + continue; } - else if (equal(tp, "<")) { - tp = tp->next; - if (!tp) break; - const Token *end = tp; - while (!equal(end, ">")) { - end = end->next; - if (!end) return; // shouldn't happen in valid source obviously - if (end->at_bol) break; // ?????? - } - char *joined = join_tokens(tp, end); // just use func from chibicc - cb(joined, true, ctxt); - //free(joined); // ?????? + int type; + if ((equal(t, "DEF_CVAR") || equal(t, "DEF_CVAR_MIN") || + equal(t, "DEF_CVAR_MAX") || equal(t, "DEF_CVAR_MINMAX") || + equal(t, "DEF_CVAR_UNREG") || equal(t, "DEF_CVAR_MIN_UNREG") || + equal(t, "DEF_CVAR_MAX_UNREG") || + equal(t, "DEF_CVAR_MINMAX_UNREG") || + equal(t, "DEF_FEAT_CVAR") || equal(t, "DEF_FEAT_CVAR_MIN") || + equal(t, "DEF_FEAT_CVAR_MAX") || + equal(t, "DEF_FEAT_CVAR_MINMAX")) && equal(t->next, "(")) { + type = CMETA_ITEM_DEF_CVAR; } - // get to the next line (standard allows extra tokens because) - while (!tp->at_bol) { - tp = tp->next; - if (!tp) return; + else if ((equal(t, "DEF_CCMD") || equal(t, "DEF_CCMD_HERE") || + equal(t, "DEF_CCMD_UNREG") || equal(t, "DEF_CCMD_HERE_UNREG") || + equal(t, "DEF_CCMD_PLUSMINUS") || + equal(t, "DEF_CCMD_PLUSMINUS_UNREG") || + equal(t, "DEF_FEAT_CCMD") || equal(t, "DEF_FEAT_CCMD_HERE") || + equal(t, "DEF_FEAT_CCMD_PLUSMINUS")) && equal(t->next, "(")) { + type = CMETA_ITEM_DEF_CCMD; } - } -} - -// AGAIN, NOTE: this doesn't *perfectly* match top level decls only in the event -// that someone writes something weird, but we just don't really care because -// we're not writing something weird. Don't write something weird! -void cmeta_conmacros(const struct cmeta *cm, - void (*cb)(const char *, bool, bool)) { - const Token *tp = (const Token *)cm; - if (!tp || !tp->next || !tp->next->next) return; // DEF_xyz, (, name - while (tp) { - bool isplusminus = false, isvar = false; - bool unreg = false; - // this is like the worst thing ever, but oh well it's just build time - // XXX: tidy this up some day, though, probably - if (equal(tp, "DEF_CCMD_PLUSMINUS")) { - isplusminus = true; + else if ((equal(t, "DEF_EVENT") || equal(t, "DEF_PREDICATE")) && + equal(t->next, "(")) { + type = CMETA_ITEM_DEF_EVENT; + } + else if (equal(t, "HANDLE_EVENT") && equal(t->next, "(")) { + type = CMETA_ITEM_HANDLE_EVENT; } - else if (equal(tp, "DEF_CCMD_PLUSMINUS_UNREG")) { - isplusminus = true; - unreg = true; + else if (equal(t, "FEATURE") && equal(t->next, "(")) { + type = CMETA_ITEM_FEATURE; } - else if (equal(tp, "DEF_CVAR") || equal(tp, "DEF_CVAR_MIN") || - equal(tp, "DEF_CVAR_MAX") || equal(tp, "DEF_CVAR_MINMAX")) { - isvar = true; + else if ((equal(t, "REQUIRE") || equal(t, "REQUIRE_GAMEDATA") || + equal(t, "REQUIRE_GLOBAL") || equal(t, "REQUEST")) && + equal(t->next, "(")) { + type = CMETA_ITEM_REQUIRE; } - else if (equal(tp, "DEF_CVAR_UNREG") || - equal(tp, "DEF_CVAR_MIN_UNREG") || - equal(tp, "DEF_CVAR_MAX_UNREG") || - equal(tp, "DEF_CVAR_MINMAX_UNREG")) { - isvar = true; - unreg = true; + else if (equal(t, "GAMESPECIFIC") && equal(t->next, "(")) { + type = CMETA_ITEM_GAMESPECIFIC; } - else if (equal(tp, "DEF_CCMD_UNREG") || - equal(tp, "DEF_CCMD_HERE_UNREG")) { - unreg = true; + else if (equal(t, "PREINIT") && equal(t->next, "{")) { + type = CMETA_ITEM_PREINIT; } - else if (!equal(tp, "DEF_CCMD") && !equal(tp, "DEF_CCMD_HERE")) { - tp = tp->next; continue; + else if (equal(t, "INIT") && equal(t->next, "{")) { + type = CMETA_ITEM_INIT; } - if (!equal(tp->next, "(")) { tp = tp->next->next; continue; } - tp = tp->next->next; - if (isplusminus) { - // XXX: this is stupid but whatever - char *plusname = malloc(sizeof("PLUS_") + tp->len); - if (!plusname) die("couldn't allocate memory"); - memcpy(plusname, "PLUS_", 5); - memcpy(plusname + sizeof("PLUS_") - 1, tp->loc, tp->len); - plusname[sizeof("PLUS_") - 1 + tp->len] = '\0'; - cb(plusname, false, unreg); - char *minusname = malloc(sizeof("MINUS_") + tp->len); - if (!minusname) die("couldn't allocate memory"); - memcpy(minusname, "MINUS_", 5); - memcpy(minusname + sizeof("MINUS_") - 1, tp->loc, tp->len); - minusname[sizeof("MINUS_") - 1 + tp->len] = '\0'; - cb(minusname, false, unreg); + else if (equal(t, "END") && equal(t->next, "{")) { + type = CMETA_ITEM_END; } else { - char *name = malloc(tp->len + 1); - if (!name) die("couldn't allocate memory"); - memcpy(name, tp->loc, tp->len); - name[tp->len] = '\0'; - cb(name, isvar, unreg); + t = t->next; + continue; } - tp = tp->next; + ret.itemtoks[ret.nitems] = t; + ret.itemtypes[ret.nitems] = type; + ++ret.nitems; + // this is kind of inefficient; in most cases we can skip more stuff, + // but then also, we're always scanning for something specific, so who + // cares actually, this will do for now. + t = t->next->next; } + return ret; } -const char *cmeta_findfeatmacro(const struct cmeta *cm) { - const Token *tp = (const Token *)cm; - if (!tp || !tp->next) return 0; // FEATURE, ( - while (tp) { - if (equal(tp, "FEATURE") && equal(tp->next, "(")) { - if (equal(tp->next->next, ")")) return ""; // no arg = no desc - if (!tp->next->next || tp->next->next->kind != TK_STR) { - return 0; // it's invalid, whatever, just return... - } - return tp->next->next->str; - } - tp = tp->next; +int cmeta_flags_cvar(const struct cmeta *cm, u32 i) { + struct Token *t = cm->itemtoks[i]; + switch_exhaust (t->len) { + // It JUST so happens all of the possible tokens here have a unique + // length. I swear this wasn't planned. But it IS convenient! + case 8: case 12: case 15: return 0; + case 14: case 18: case 21: return CMETA_CVAR_UNREG; + case 13: case 17: case 20: return CMETA_CVAR_FEAT; } - return 0; } -void cmeta_featinfomacros(const struct cmeta *cm, void (*cb)( - enum cmeta_featmacro type, const char *param, void *ctxt), void *ctxt) { - const Token *tp = (const Token *)cm; - if (!tp || !tp->next) return; - while (tp) { - int type = -1; - if (equal(tp, "PREINIT")) { - type = CMETA_FEAT_PREINIT; - } - else if (equal(tp, "INIT")) { - type = CMETA_FEAT_INIT; - } - else if (equal(tp, "END")) { - type = CMETA_FEAT_END; - } - if (type != - 1) { - if (equal(tp->next, "{")) { - cb(type, 0, ctxt); - tp = tp->next; - } - tp = tp->next; - continue; - } - if (equal(tp, "REQUIRE")) { - type = CMETA_FEAT_REQUIRE; - } - else if (equal(tp, "REQUIRE_GAMEDATA")) { - type = CMETA_FEAT_REQUIREGD; - } - else if (equal(tp, "REQUIRE_GLOBAL")) { - type = CMETA_FEAT_REQUIREGLOBAL; - } - else if (equal(tp, "REQUEST")) { - type = CMETA_FEAT_REQUEST; - } - if (type != -1) { - if (equal(tp->next, "(") && tp->next->next) { - tp = tp->next->next; - char *param = malloc(tp->len + 1); - if (!param) die("couldn't allocate memory"); - memcpy(param, tp->loc, tp->len); - param[tp->len] = '\0'; - cb(type, param, ctxt); - tp = tp->next; - } - } - tp = tp->next; +int cmeta_flags_ccmd(const struct cmeta *cm, u32 i) { + struct Token *t = cm->itemtoks[i]; + switch_exhaust (t->len) { + case 13: if (t->loc[4] == 'F') return CMETA_CCMD_FEAT; + case 8: return 0; + case 18: if (t->loc[4] == 'F') return CMETA_CCMD_FEAT; + return CMETA_CCMD_PLUSMINUS; + case 14: case 19: return CMETA_CCMD_UNREG; + case 23: return CMETA_CCMD_FEAT | CMETA_CCMD_PLUSMINUS; + case 24: return CMETA_CCMD_UNREG | CMETA_CCMD_PLUSMINUS; } } -struct vec_str VEC(const char *); +int cmeta_flags_event(const struct cmeta *cm, u32 i) { + // assuming CMETA_EVENT_ISPREDICATE remains 1, the ternary should + // optimise out + return cm->itemtoks[i]->len == 13 ? CMETA_EVENT_ISPREDICATE : 0; +} -static void pushmacroarg(const Token *last, const char *start, - struct vec_str *list) { - int len = last->loc - start + last->len; - char *dup = malloc(len + 1); - if (!dup) die("couldn't allocate memory"); - memcpy(dup, start, len); - dup[len] = '\0'; - if (!vec_push(list, dup)) die("couldn't append to array"); +int cmeta_flags_require(const struct cmeta *cm, u32 i) { + struct Token *t = cm->itemtoks[i]; + // NOTE: this is somewhat more flexible to enable REQUEST_GAMEDATA or + // something in future, although that's kind of useless currently + int optflag = t->loc[4] == 'E'; // REQU[E]ST + switch_exhaust (t->len) { + case 7: return optflag; + case 16: return optflag | CMETA_REQUIRE_GAMEDATA; + case 14: return optflag | CMETA_REQUIRE_GLOBAL; + }; } -// XXX: maybe this should be used for the other functions too. it'd be less ugly -// and handle closing parentheses better, but alloc for tokens we don't care -// about. probably a worthy tradeoff? -static const Token *macroargs(const Token *t, struct vec_str *list) { - int paren = 1; - const Token *last; // avoids copying extra ws/comments in - for (const char *start = t->loc; t; last = t, t = t->next) { - if (equal(t, "(")) { - ++paren; - } - else if (equal(t, ")")) { - if (!--paren) { - pushmacroarg(last, start, list); - return t->next; - } - } - else if (paren == 1 && equal(t, ",")) { - pushmacroarg(last, start, list); - t = t->next; - if (t) start = t->loc; // slightly annoying... - } +int cmeta_nparams(const struct cmeta *cm, u32 i) { + int argc = 1, nest = 0; + struct Token *t = cm->itemtoks[i]->next->next; + if (equal(t, ")")) return 0; // XXX: stupid special case, surely improvable? + for (; t; t = t->next) { + if (equal(t, "(")) { ++nest; continue; } + if (!nest && equal(t, ",")) ++argc; + else if (equal(t, ")") && !nest--) break; } - // I guess we handle this here. - fprintf(stderr, "cmeta: fatal: unexpected EOF in %s\n", t->filename); - exit(2); + if (nest != -1) return 0; // XXX: any need to do anything better here? + return argc; +} + +struct cmeta_param_iter cmeta_param_iter_init(const struct cmeta *cm, u32 i) { + return (struct cmeta_param_iter){cm->itemtoks[i]->next->next}; } -void cmeta_evdefmacros(const struct cmeta *cm, void (*cb)(const char *name, - const char *const *params, int nparams, bool predicate)) { - const Token *tp = (const Token *)cm; - if (!tp || !tp->next || !tp->next->next) return; // DEF_EVENT, (, name - while (tp) { - bool predicate = true; - if (equal(tp, "DEF_EVENT") && equal(tp->next, "(")) { - predicate = false; +struct cmeta_slice cmeta_param_iter(struct cmeta_param_iter *it) { + int nest = 0; + const char *start = it->cur->loc; + for (struct Token *last = 0; it->cur; + last = it->cur, it->cur = it->cur->next) { + if (equal(it->cur, "(")) { ++nest; continue; } + if (!nest && equal(it->cur, ",")) { + if (!last) { // , immediately after (, for some reason. treat as "" + return (struct cmeta_slice){start, 0}; + } + it->cur = it->cur->next; } - else if (!equal(tp, "DEF_PREDICATE") || !equal(tp->next, "(")) { - tp = tp->next; - continue; + else if (equal(it->cur, ")") && !nest--) { + if (!last) break; } - tp = tp->next->next; - struct vec_str args = {0}; - tp = macroargs(tp, &args); - if (args.sz == 0) { - fprintf(stderr, "cmeta: fatal: missing event parameters in %s\n", - tp->filename); - exit(2); + else { + continue; } - cb(args.data[0], args.data + 1, args.sz - 1, predicate); + return (struct cmeta_slice){start, last->loc - start + last->len}; } + return (struct cmeta_slice){0, 0}; } -void cmeta_evhandlermacros(const struct cmeta *cm, const char *modname, - void (*cb_handler)(const char *evname, const char *modname)) { - const Token *tp = (const Token *)cm; - while (tp) { - if (equal(tp, "HANDLE_EVENT") && equal(tp->next, "(")) { - tp = tp->next->next; - char *name = malloc(tp->len + 1); - if (!name) die("couldn't allocate memory"); - memcpy(name, tp->loc, tp->len); - name[tp->len] = '\0'; - cb_handler(name, modname); - } - tp = tp->next; - } +u32 cmeta_line(const struct cmeta *cm, u32 i) { + return cm->itemtoks[i]->line_no; } // vi: sw=4 ts=4 noet tw=80 cc=80 fdm=marker diff --git a/src/build/cmeta.h b/src/build/cmeta.h index 86ae0ec..2a3f741 100644 --- a/src/build/cmeta.h +++ b/src/build/cmeta.h @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,71 +17,72 @@ #ifndef INC_CMETA_H #define INC_CMETA_H +#include "../intdefs.h" #include "../os.h" -struct cmeta; +// XXX: leaking chibicc internals. won't matter after we do away with that +typedef struct Token Token; -const struct cmeta *cmeta_loadfile(const os_char *path); +enum cmeta_item { + CMETA_ITEM_DEF_CVAR, // includes all min/max/unreg variants + CMETA_ITEM_DEF_CCMD, // includes plusminus/unreg variants + CMETA_ITEM_DEF_EVENT, // includes predicates + CMETA_ITEM_HANDLE_EVENT, + CMETA_ITEM_FEATURE, + CMETA_ITEM_REQUIRE, // includes all REQUIRE_*/REQUEST variants + CMETA_ITEM_GAMESPECIFIC, + CMETA_ITEM_PREINIT, + CMETA_ITEM_INIT, + CMETA_ITEM_END +}; -/* - * Iterates through all the #include directives in a file, passing each one in - * turn to the callback cb. - */ -void cmeta_includes(const struct cmeta *cm, - void (*cb)(const char *f, bool issys, void *ctxt), void *ctxt); +struct cmeta { + char *sbase; + u32 nitems; // number of interesting macros + //u32 *itemoffs; // file offsets of interesting macros (ONE DAY!) + Token **itemtoks; // crappy linked token structures, for the time being + u8 *itemtypes; // CMETA_ITEM_* enum values +}; -/* - * Iterates through all commands and variables declared using the macros in - * con_.h, passing each one in turn to the callback cb. - */ -void cmeta_conmacros(const struct cmeta *cm, - void (*cb)(const char *name, bool isvar, bool unreg)); +enum cmeta_flag_cvar { + CMETA_CVAR_UNREG = 1, + CMETA_CVAR_FEAT = 2, +}; +enum cmeta_flag_ccmd { + CMETA_CCMD_UNREG = 1, + CMETA_CCMD_FEAT = 2, + CMETA_CCMD_PLUSMINUS = 4 +}; +enum cmeta_flag_event { + CMETA_EVENT_ISPREDICATE = 1 +}; +enum cmeta_flag_require { + CMETA_REQUIRE_OPTIONAL = 1, // i.e. REQUEST() macro, could be extended + CMETA_REQUIRE_GAMEDATA = 2, + CMETA_REQUIRE_GLOBAL = 4 +}; -/* - * Looks for a feature description macro in file, returning the description - * string if it exists, an empty string if the feature is defined without a - * user-facing description, and null if source file does not define a feature. - */ -const char *cmeta_findfeatmacro(const struct cmeta *cm); +struct cmeta_slice { const char *s; int len; }; -/* - * the various kinds of feature specficiation macros, besides the feature - * declaration macro itself - */ -enum cmeta_featmacro { - CMETA_FEAT_REQUIRE, - CMETA_FEAT_REQUIREGD, - CMETA_FEAT_REQUIREGLOBAL, - CMETA_FEAT_REQUEST, - CMETA_FEAT_PREINIT, - CMETA_FEAT_INIT, - CMETA_FEAT_END -}; +struct cmeta cmeta_loadfile(const os_char *path); +int cmeta_flags_cvar(const struct cmeta *cm, u32 i); +int cmeta_flags_ccmd(const struct cmeta *cm, u32 i); +int cmeta_flags_event(const struct cmeta *cm, u32 i); +int cmeta_flags_require(const struct cmeta *cm, u32 i); -/* - * Iterates through all feature dependency macros and init/end/preinit - * indicators, passing each bit of information to the callback cb. - * - * PREINT, INIT and END macros don't pass anything to param. - * - * This one takes a context pointer, while the others don't, because this is all - * cobbled together without much consistent abstraction. - */ -void cmeta_featinfomacros(const struct cmeta *cm, void (*cb)( - enum cmeta_featmacro type, const char *param, void *ctxt), void *ctxt); +int cmeta_nparams(const struct cmeta *cm, u32 i); +struct cmeta_param_iter { Token *cur; }; +struct cmeta_param_iter cmeta_param_iter_init(const struct cmeta *cm, u32 i); +struct cmeta_slice cmeta_param_iter(struct cmeta_param_iter *it); + +#define cmeta_param_foreach(varname, cm, u32) \ + switch (0) for (struct cmeta_slice varname; 0;) default: \ + for (struct cmeta_param_iter _it = cmeta_param_iter_init(cm, i); \ + varname = cmeta_param_iter(&_it), varname.s;) \ + /* {...} */ + +u32 cmeta_line(const struct cmeta *cm, u32 i); -/* - * Iterates through all event-related macros and takes note of which events are - * defined, giving a callback for each. - */ -void cmeta_evdefmacros(const struct cmeta *cm, void (*cb)(const char *name, - const char *const *params, int nparams, bool predicate)); -/* - * Iterates through all event-related macros and gives a callback for each event - * that is handled by the given module. - */ -void cmeta_evhandlermacros(const struct cmeta *cm, const char *modname, - void (*cb)(const char *evname, const char *modname)); #endif // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/codegen.c b/src/build/codegen.c deleted file mode 100644 index 70b5e12..0000000 --- a/src/build/codegen.c +++ /dev/null @@ -1,537 +0,0 @@ -/* - * Copyright © 2024 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 - * 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 <ctype.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -#include "../intdefs.h" -#include "../langext.h" -#include "../os.h" -#include "cmeta.h" -#include "skiplist.h" -#include "vec.h" - -#ifdef _WIN32 -#define fS "S" -#else -#define fS "s" -#endif - -static void die(const char *s) { - fprintf(stderr, "codegen: fatal: %s\n", s); - exit(100); -} - -#define MAXENT 65536 // arbitrary limit! -static struct conent { - const char *name; - bool unreg; - bool isvar; // false for cmd -} conents[MAXENT]; -static int nconents; - -#define PUT(name_, isvar_, unreg_) do { \ - if (nconents == countof(conents)) { \ - fprintf(stderr, "codegen: out of space; make conents bigger!\n"); \ - exit(1); \ - } \ - conents[nconents].name = name_; \ - conents[nconents].isvar = isvar_; conents[nconents++].unreg = unreg_; \ -} while (0) - -static void oncondef(const char *name, bool isvar, bool unreg) { - PUT(name, isvar, unreg); -} - -struct vec_str VEC(const char *); -struct vec_usize VEC(usize); -struct vec_featp VEC(struct feature *); - -enum { UNSEEN, SEEING, SEEN }; - -DECL_SKIPLIST(static, feature, struct feature, const char *, 4) -DECL_SKIPLIST(static, feature_bydesc, struct feature, const char *, 4) -struct feature { - const char *modname; - const char *desc; - const struct cmeta *cm; // backref for subsequent options pass - struct vec_featp needs; - // keep optionals in a separate array mainly so we have separate counts - struct vec_featp wants; - uint dfsstate : 2; // used for sorting and cycle checking - bool has_preinit : 1, /*has_init : 1, <- required anyway! */ has_end : 1; - bool has_evhandlers : 1; - bool is_requested : 1; // determines if has_ variable needs to be extern - //char pad : 2; - //char pad[3]; - struct vec_str need_gamedata; - struct vec_str need_globals; - struct skiplist_hdr_feature hdr; // by id/modname - struct skiplist_hdr_feature_bydesc hdr_bydesc; -}; -static inline int cmp_feature(struct feature *e, const char *s) { - return strcmp(e->modname, s); -} -static inline int cmp_feature_bydesc(struct feature *e, const char *s) { - for (const char *p = e->desc; ; ++p, ++s) { - // shortest string first - if (!*p) return !!*s; if (!*s) return -1; - // case insensitive sort where possible - if (tolower((uchar)*p) > tolower((uchar)*s)) return 1; - if (tolower((uchar)*p) < tolower((uchar)*s)) return -1; - // prioritise upper-case if same letter - if (isupper((uchar)*p) && islower((uchar)*s)) return 1; - if (islower((uchar)*p) && isupper((uchar)*s)) return -1; - } - return 0; -} -static inline struct skiplist_hdr_feature *hdr_feature(struct feature *e) { - return &e->hdr; -} -static inline struct skiplist_hdr_feature_bydesc *hdr_feature_bydesc( - struct feature *e) { - return &e->hdr_bydesc; -} -DEF_SKIPLIST(static, feature, cmp_feature, hdr_feature) -DEF_SKIPLIST(static, feature_bydesc, cmp_feature_bydesc, hdr_feature_bydesc) -static struct skiplist_hdr_feature features = {0}; -// sort in two different ways, so we can alphabetise the user-facing display -// NOTE: not all features will show up in this second list! -static struct skiplist_hdr_feature_bydesc features_bydesc = {0}; - -static void onfeatinfo(enum cmeta_featmacro type, const char *param, - void *ctxt) { - struct feature *f = ctxt; - switch_exhaust_enum (cmeta_featmacro, type) { - case CMETA_FEAT_REQUIRE:; bool optional = false; goto dep; - case CMETA_FEAT_REQUEST: optional = true; -dep:; struct feature *dep = skiplist_get_feature(&features, param); - if (optional) dep->is_requested = true; - if (!dep) { - fprintf(stderr, "codegen: error: feature `%s` tried to depend " - "on non-existent feature `%s`\n", f->modname, param); - exit(1); \ - } - if (!vec_push(optional ? &f->wants : &f->needs, dep)) { - die("couldn't allocate memory"); - } - break; - case CMETA_FEAT_REQUIREGD:; - struct vec_str *vecp = &f->need_gamedata; - goto push; - case CMETA_FEAT_REQUIREGLOBAL: - vecp = &f->need_globals; -push: if (!vec_push(vecp, param)) die("couldn't allocate memory"); - break; - case CMETA_FEAT_PREINIT: f->has_preinit = true; break; - case CMETA_FEAT_END: f->has_end = true; break; - case CMETA_FEAT_INIT:; // nop for now, I guess - } -} - -DECL_SKIPLIST(static, event, struct event, const char *, 4) - struct event { - usize name; // string, but tagged pointer - see below - const char *const *params; - int nparams; - //char pad[4]; - struct vec_usize handlers; // strings, but with tagged pointers - see below - struct skiplist_hdr_event hdr; -}; -static inline int cmp_event(struct event *e, const char *s) { - return strcmp((const char *)(e->name & ~1ull), s); -} -static inline struct skiplist_hdr_event *hdr_event(struct event *e) { - return &e->hdr; -} -DEF_SKIPLIST(static, event, cmp_event, hdr_event) -static struct skiplist_hdr_event events = {0}; - -static void onevdef(const char *name, const char *const *params, int nparams, - bool predicate) { - struct event *e = skiplist_get_event(&events, name); - if (!e) { - struct event *e = malloc(sizeof(*e)); - if (!e) die("couldn't allocate memory"); - // hack: using unused pointer bit to distinguish the two types of event - e->name = (usize)name | predicate; - e->params = params; e->nparams = nparams; - e->handlers = (struct vec_usize){0}; - e->hdr = (struct skiplist_hdr_event){0}; - skiplist_insert_event(&events, name, e); - } - else { - fprintf(stderr, "codegen: error: duplicate event definition `%s`\n", - name); - exit(2); - } -} - -static void onevhandler(const char *evname, const char *modname) { - struct event *e = skiplist_get_event(&events, evname); - if (!e) { - fprintf(stderr, "codegen: error: module `%s` trying to handle " - "non-existent event `%s`\n", modname, evname); - exit(2); - } - usize taggedptr = (usize)modname; - struct feature *f = skiplist_get_feature(&features, modname); - f->has_evhandlers = true; - // hack: using unused pointer bit to determine whether a handler is tied to - // a feature and thus conditional. relies on malloc alignment! - if (f) taggedptr |= 1ull; - // NOTE: not bothering to check for more than one handler in a file. - // compiler will get that anyway. - if (!vec_push(&e->handlers, taggedptr)) die("couldn't allocate memory"); -} - -struct passinfo { - const struct cmeta *cm; - const os_char *path; -}; -static struct vec_passinfo VEC(struct passinfo) pass2 = {0}; - -#define _(x) \ - if (fprintf(out, "%s\n", x) < 0) die("couldn't write to file"); -#define F(f, ...) \ - if (fprintf(out, f "\n", __VA_ARGS__) < 0) die("couldn't write to file"); -#define H_() \ - _( "/* This file is autogenerated by "__FILE__". DO NOT EDIT! */") -#define H() H_() _( "") - -static struct vec_featp endstack = {0}; // stack for reversing order - -static void featdfs(FILE *out, struct feature *f) { - if (f->dfsstate == SEEN) return; - if (f->dfsstate == SEEING) { - // XXX: could unwind for full cycle listing like in build. - // purely being lazy by not doing that here, and assuming there won't - // actually be cycles anyway, because this is not a general purpose tool - // and people working on this codebase are very smart. - fprintf(stderr, "codegen: error: dependency cycle found at feature `%s`\n", - f->modname); - exit(2); - } - f->dfsstate = SEEING; - // easier to do wants first, then we can do the conditional counter nonsense - // without worrying about how that fits in... - for (struct feature *const *pp = f->wants.data; - pp - f->wants.data < f->wants.sz; ++pp) { - featdfs(out, *pp); - } -F( " char status_%s = FEAT_OK;", f->modname); - const char *else_ = ""; - if (f->needs.sz == 1) { - featdfs(out, f->needs.data[0]); -F( " if (status_%s != FEAT_OK) status_%s = FEAT_REQFAIL;", - f->needs.data[0]->modname, f->modname) - else_ = "else "; - } - else if (f->needs.sz > 1) { - for (struct feature *const *pp = f->needs.data; - pp - f->needs.data < f->needs.sz; ++pp) { - featdfs(out, *pp); - } -F( " bool metdeps_%s =", f->modname) - for (struct feature *const *pp = f->needs.data; - pp - f->needs.data < f->needs.sz; ++pp) { -F( " status_%s == FEAT_OK%s", (*pp)->modname, - pp - f->needs.data == f->needs.sz - 1 ? ";" : " &&") // dumb but oh well - } -F( " if (!metdeps_%s) status_%s = FEAT_REQFAIL;", f->modname, f->modname) - else_ = "else "; - } - if (f->has_preinit) { -F( " %sif (!_feature_preinit_%s()) status_%s = FEAT_PREFAIL;", else_, - f->modname, f->modname); - else_ = "else "; - } - for (const char **pp = f->need_gamedata.data; - pp - f->need_gamedata.data < f->need_gamedata.sz; ++pp) { -F( " %sif (!has_%s) status_%s = FEAT_NOGD;", else_, *pp, f->modname) - else_ = "else "; // blegh - } - for (const char **pp = f->need_globals.data; - pp - f->need_globals.data < f->need_globals.sz; ++pp) { -F( " %sif (!%s) status_%s = FEAT_NOGLOBAL;", else_, *pp, f->modname) - else_ = "else "; // blegh 2 - } -F( " %sif (!_feature_init_%s()) status_%s = FEAT_FAIL;", else_, f->modname, - f->modname) - if (f->has_end || f->has_evhandlers || f->is_requested) { -F( " has_%s = status_%s == FEAT_OK;", f->modname, f->modname) - } - if (!vec_push(&endstack, f)) die("couldn't allocate memory"); - f->dfsstate = SEEN; -} - -int OS_MAIN(int argc, os_char *argv[]) { - for (++argv; *argv; ++argv) { - const struct cmeta *cm = cmeta_loadfile(*argv); - if (!cm) { - fprintf(stderr, "codegen: fatal: couldn't load file %" fS "\n", - *argv); - exit(100); - } - cmeta_conmacros(cm, &oncondef); - cmeta_evdefmacros(cm, &onevdef); - if (!vec_push(&pass2, ((struct passinfo){cm, *argv}))) { - die("couldn't allocate memory"); - } - } - - // we have to do a second pass for features and event handlers. also, - // there's a bunch of terrible garbage here. don't stare for too long... - for (struct passinfo *pi = pass2.data; pi - pass2.data < pass2.sz; ++pi) { - // XXX: I guess we should cache these by name or something! - const struct cmeta *cm = pi->cm; -#ifdef _WIN32 - int arglen = wcslen(pi->path); - char *p = malloc(arglen + 1); - if (!p) die("couldn't allocate string"); - // XXX: Unicode isn't real, it can't hurt you. - for (const ushort *q = pi->path; q - pi->path < arglen; ++q) { - p[q - pi->path] = *q; // ugh this is stupid - } - p[arglen] = '\0'; -#else - const char *p = pi->path; -#endif - const char *lastslash = p - 1; - for (; *p; ++p) { -#ifdef _WIN32 - if (*p == '/' || *p == '\\') { -#else - if (*p == '/') { -#endif - lastslash = p; - } - } - int len = strlen(lastslash + 1); - if (len <= 3 || lastslash[len - 1] != '.' || lastslash[len] != 'c') { - fprintf(stderr, "filenames should end in .c probably\n"); - exit(2); - } - char *modname = malloc(len - 1); - if (!modname) die("couldn't allocate string"); - memcpy(modname, lastslash + 1, len - 2); - modname[len - 2] = '\0'; - // ugh. same dumb hacks from compile scripts - if (!strcmp(modname, "con_")) { - free(modname); // might as well - modname = "con"; - } - else if (!strcmp(modname, "sst")) { - continue; // I guess??? - } - const char *featdesc = cmeta_findfeatmacro(cm); - if (featdesc) { - struct feature *f = malloc(sizeof(*f)); - if (!f) die("couldn't allocate memory"); - *f = (struct feature){ - .modname = modname, - .desc = featdesc[0] ? featdesc : 0, - .cm = cm - }; - skiplist_insert_feature(&features, modname, f); - if (f->desc) { - skiplist_insert_feature_bydesc(&features_bydesc, f->desc, f); - } - } - cmeta_evhandlermacros(cm, modname, &onevhandler); - } - // yet another pass because I am stupid and don't want to think harder :) - for (struct feature *f = features.x[0]; f; f = f->hdr.x[0]) { - cmeta_featinfomacros(f->cm, &onfeatinfo, f); - } - - FILE *out = fopen(".build/include/cmdinit.gen.h", "wb"); - if (!out) die("couldn't open cmdinit.gen.h"); -H(); - for (const struct conent *p = conents; p - conents < nconents; ++p) { -F( "extern struct con_%s *%s;", p->isvar ? "var" : "cmd", p->name) - } -_( "") -_( "static void regcmds(void) {") - for (const struct conent *p = conents; p - conents < nconents; ++p) { - if (p->isvar) { -F( " initval(%s);", p->name) - } - if (!p->unreg) { -F( " con_reg(%s);", p->name) - } - } -_( "}") -_( "") -_( "static void freevars(void) {") - for (const struct conent *p = conents; p - conents < nconents; ++p) { - if (p->isvar) { -F( " extfree(%s->strval);", p->name) - } - } -_( "}") - if (fclose(out) == EOF) die("couldn't fully write cmdinit.gen.h"); - - out = fopen(".build/include/featureinit.gen.h", "wb"); - if (!out) die("couldn't open featureinit.gen.h"); - H() - // XXX: I dunno whether this should just be defined in sst.c. It's sort of - // internal to the generated stuff hence tucking it away here, but that's at - // the cost of extra string-spaghettiness -_( "enum {") -_( " FEAT_OK,") -_( " FEAT_REQFAIL,") -_( " FEAT_PREFAIL,") -_( " FEAT_NOGD,") -_( " FEAT_NOGLOBAL,") -_( " FEAT_FAIL") -_( "};") -_( "") -_( "static const char *const featmsgs[] = {") -_( " \" [ OK! ] %s\\n\",") -_( " \" [ skipped ] %s (requires another feature)\\n\",") -_( " \" [ skipped ] %s (not applicable or useful)\\n\",") -_( " \" [ unsupported ] %s (missing gamedata)\\n\",") -_( " \" [ FAILED! ] %s (failed to access engine)\\n\",") -_( " \" [ FAILED! ] %s (error in initialisation)\\n\"") -_( "};") -_( "") - for (struct feature *f = features.x[0]; f; f = f->hdr.x[0]) { - if (f->has_preinit) { -F( "extern bool _feature_preinit_%s(void);", f->modname) - } -F( "extern bool _feature_init_%s(void);", f->modname) - if (f->has_end) { -F( "extern bool _feature_end_%s(void);", f->modname) - } - if (f->is_requested) { -F( "bool has_%s = false;", f->modname) - } - else if (f->has_end || f->has_evhandlers) { -F( "static bool has_%s = false;", f->modname) - } - } -_( "") -_( "static void initfeatures(void) {") - for (struct feature *f = features.x[0]; f; f = f->hdr.x[0]) featdfs(out, f); -_( "") - // note: old success message is moved in here, to get the ordering right -_( " con_colourmsg(&(struct rgba){64, 255, 64, 255},") -_( " LONGNAME \" v\" VERSION \" successfully loaded\");") -_( " con_colourmsg(&(struct rgba){255, 255, 255, 255}, \" for game \");") -_( " con_colourmsg(&(struct rgba){0, 255, 255, 255}, \"%s\\n\", ") -_( " gameinfo_title);") -_( " struct rgba white = {255, 255, 255, 255};") -_( " struct rgba green = {128, 255, 128, 255};") -_( " struct rgba red = {255, 128, 128, 255};") -_( " con_colourmsg(&white, \"---- List of plugin features ---\\n\");"); - for (const struct feature *f = features_bydesc.x[0]; f; - f = f->hdr_bydesc.x[0]) { -F( " con_colourmsg(status_%s == FEAT_OK ? &green : &red,", f->modname) -F( " featmsgs[(int)status_%s], \"%s\");", f->modname, f->desc) - } -_( "}") -_( "") -_( "static void endfeatures(void) {") - for (struct feature **pp = endstack.data + endstack.sz - 1; - pp - endstack.data >= 0; --pp) { - if ((*pp)->has_end) { -F( " if (has_%s) _feature_end_%s();", (*pp)->modname, (*pp)->modname) - } - } -_( "}") -_( "") - if (fclose(out) == EOF) die("couldn't fully write featureinit.gen.h"); - - out = fopen(".build/include/evglue.gen.h", "wb"); - if (!out) die("couldn't open evglue.gen.h"); - H_() - for (const struct event *e = events.x[0]; e; e = e->hdr.x[0]) { -_( "") - // gotta break from the string emit macros for a sec in order to do the - // somewhat more complicated task sometimes referred to as a "for loop" - fprintf(out, "%s_%s(", e->name & 1 ? "bool CHECK" : "void EMIT", - (const char *)(e->name & ~1ull)); - for (int n = 0; n < (int)e->nparams - 1; ++n) { - fprintf(out, "typeof(%s) a%d, ", e->params[n], n + 1); - } - if (e->nparams && strcmp(e->params[0], "void")) { - fprintf(out, "typeof(%s) a%d", e->params[e->nparams - 1], - e->nparams); - } - else { - // just unilaterally doing void for now. when we're fully on C23 - // eventually we can unilaterally do nothing instead - fputs("void", out); - } -_( ") {") - for (usize *pp = e->handlers.data; - pp - e->handlers.data < e->handlers.sz; ++pp) { - const char *modname = (const char *)(*pp & ~1ull); - fprintf(out, "\t%s _evhandler_%s_%s(", e->name & 1 ? "bool" : "void", - modname, (const char *)(e->name & ~1ull)); - for (int n = 0; n < (int)e->nparams - 1; ++n) { - fprintf(out, "typeof(%s) a%d, ", e->params[n], n + 1); - } - if (e->nparams && strcmp(e->params[0], "void")) { - fprintf(out, "typeof(%s) a%d", e->params[e->nparams - 1], - e->nparams); - } - else { - fputs("void", out); - } - fputs(");\n\t", out); - // conditional and non-conditional cases - in theory could be - // unified a bit but this is easier to make output relatively pretty - // note: has_* variables are already included by this point (above) - if (e->name & 1) { - if (*pp & 1) fprintf(out, "if (has_%s && !", modname); - else fprintf(out, "if (!"); - fprintf(out, "_evhandler_%s_%s(", modname, - (const char *)(e->name & ~1ull)); - // XXX: much repetitive drivel here - for (int n = 0; n < (int)e->nparams - 1; ++n) { - fprintf(out, "a%d,", n + 1); - } - if (e->nparams && strcmp(e->params[0], "void")) { - fprintf(out, "a%d", e->nparams); - } - fputs(")) return false;\n", out); - } - else { - if (*pp & 1) fprintf(out, "if (has_%s) ", modname); - fprintf(out, "_evhandler_%s_%s(", modname, - (const char *)(e->name & ~1ull)); - for (int n = 0; n < (int)e->nparams - 1; ++n) { - fprintf(out, "a%d,", n + 1); - } - if (e->nparams && strcmp(e->params[0], "void")) { - fprintf(out, "a%d", e->nparams); - } - fputs(");\n", out); - } - } - if (e->name & 1) fputs("\treturn true;\n", out); -_( "}") - } - if (fclose(out) == EOF) die("couldn't fully write evglue.gen.h"); - - return 0; -} - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/gluegen.c b/src/build/gluegen.c new file mode 100644 index 0000000..4a0f4a1 --- /dev/null +++ b/src/build/gluegen.c @@ -0,0 +1,963 @@ +/* + * Copyright © 2025 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 + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../intdefs.h" +#include "../langext.h" +#include "../os.h" +#include "cmeta.h" + +#ifdef _WIN32 +#define fS "S" +#else +#define fS "s" +#endif + +static inline noreturn die(int status, const char *s) { + fprintf(stderr, "gluegen: fatal: %s\n", s); + exit(status); +} + +static inline noreturn diefile(int status, const os_char *f, int line, + const char *s) { + if (line) { + fprintf(stderr, "gluegen: fatal: %" fS ":%d: %s\n", f, line, s); + } + else { + fprintf(stderr, "gluegen: fatal: %" fS ": %s\n", f, s); + } + exit(status); +} + +#define ARENASZ (1024 * 1024) +static _Alignas(64) char _arena[ARENASZ] = {0}, *const arena = _arena - 64; +static int arena_last = 0; +static int arena_used = 64; // using 0 indices as null; reserve and stay aligned + +static inline void _arena_align(void) { + enum { ALIGN = ssizeof(void *) }; + if (arena_used & ALIGN - 1) arena_used = (arena_used + ALIGN) & ~(ALIGN - 1); +} + +static inline int arena_bump(int amt) { + if_cold (arena_used + amt >= ARENASZ) die(2, "out of arena memory"); + int ret = arena_used; + arena_used += amt; + return ret; +} + +static int arena_new(int sz) { + _arena_align(); + arena_last = arena_used; + int off = arena_bump(sz); + return off; +} + +#define LIST_MINALLOC 64 +#define LIST_MINSPACE (LIST_MINALLOC - ssizeof(struct list_chunkhdr)) + +struct list_chunkhdr { int next, sz; }; +struct list_chunk { struct list_chunkhdr hdr; char space[LIST_MINSPACE]; }; + +static struct list_grow_ret { + struct list_chunkhdr *tail; + void *data; +} list_grow(struct list_chunkhdr *tail, int amt) { + if_hot ((char *)tail == arena + arena_last) { + arena_bump(amt); + } + else if (tail->sz + amt > LIST_MINSPACE) { + int allocsz = ssizeof(struct list_chunkhdr) + amt; + int new = arena_new(allocsz > LIST_MINALLOC ? allocsz : LIST_MINALLOC); + struct list_chunkhdr *newptr = (struct list_chunkhdr *)(arena + new); + newptr->next = 0; newptr->sz = 0; + tail->next = new; + return (struct list_grow_ret){newptr, (char *)(newptr + 1)}; + } + struct list_grow_ret r = {tail, (char *)(tail + 1) + tail->sz}; + tail->sz += amt; + return r; +} + +static inline void *list_grow_p(struct list_chunkhdr **tailp, int amt) { + struct list_grow_ret r = list_grow(*tailp, amt); + *tailp = r.tail; + return r.data; +} + +#define list_append_p(T, tailp) ((T *)list_grow_p((tailp), ssizeof(T))) +#define list_append(tailp, x) ((void)(*list_append_p(typeof(x), (tailp)) = (x))) + +#define _list_foreach(T, varname, firstchunkp, extra) \ + for (struct list_chunkhdr *_h = &(firstchunkp)->hdr; _h; \ + _h = _h->next ? (struct list_chunkhdr *)(arena + _h->next) : 0) \ + for (typeof(T) *varname = (typeof(T) *)(_h + 1); \ + (char *)varname < (char *)(_h + 1) + _h->sz && (extra); \ + ++varname) \ + /* ... */ + +#define list_foreach_p(T, varname, firstchunkp) \ + _list_foreach(T, varname, firstchunkp, 1) + /* {...} */ + +#define list_foreach(T, varname, firstchunkp) \ + /* N.B. this crazy looking construct avoids unused warnings on varname + in case we just want to count up the entries or something */ \ + switch (0) for (typeof(T) varname; 0;) if ((void)varname, 0) case 0: \ + _list_foreach(T, _foreachp, firstchunkp, (varname = *_foreachp, 1)) \ + /* ... */ + +#define DEF_NEW(type, func, nvar, maxvar, desc) \ + static inline type func(void) { \ + if_cold (nvar == maxvar) { \ + die(2, "out of " desc " - increase " #maxvar " in gluegen.c!"); \ + } \ + return nvar++; \ + } + +// trickery to enable 1-based indexing (and thus 0-as-null) for various arrays +#define SHUNT(T, x) typeof(T) _array_##x[], *const x = _array_##x - 1, _array_##x + +#define MAX_MODULES 512 +// note that by 1-indexing these, we enable the radix stuff above to use the +// default initialisation as null and we *also* allow argc to be used as a +// direct index into each array when looping through files passed to main()! +// XXX: padded 16-byte structs. could eventually switch to u32 indices. for now, +// locality is favoured over packing. no clue whether that's the right call! +static SHUNT(struct cmeta_slice, mod_names)[MAX_MODULES] = {0}; +static SHUNT(struct cmeta_slice, mod_featdescs)[MAX_MODULES] = {0}; +static SHUNT(struct cmeta_slice, mod_gamespecific)[MAX_MODULES] = {0}; +enum { + HAS_INIT = 1, // really, this means this *is a feature*! + HAS_PREINIT = 2, + HAS_END = 4, + HAS_EVENTS = 8, + HAS_OPTDEPS = 16, // something else depends on *us* with REQUEST() + DFS_SEEING = 64, // for REQUIRE() cycle detection + DFS_SEEN = 128 +}; +static u8 mod_flags[MAX_MODULES] = {0}; +static SHUNT(struct list_chunk, mod_needs)[MAX_MODULES] = {0}; +static SHUNT(struct list_chunk, mod_wants)[MAX_MODULES] = {0}; +static SHUNT(struct list_chunk, mod_gamedata)[MAX_MODULES] = {0}; +static SHUNT(struct list_chunk, mod_globals)[MAX_MODULES] = {0}; +static int nmods = 1; + +static s16 feat_initorder[MAX_MODULES]; +static int nfeatures = 0; + +#define MAX_CVARS 8192 +#define MAX_CCMDS MAX_CVARS +static SHUNT(struct cmeta_slice, cvar_names)[MAX_CVARS]; +static SHUNT(struct cmeta_slice, ccmd_names)[MAX_CCMDS]; +static SHUNT(s16, cvar_feats)[MAX_CVARS]; +static SHUNT(s16, ccmd_feats)[MAX_CVARS]; +static SHUNT(u8, cvar_flags)[MAX_CVARS]; +static SHUNT(u8, ccmd_flags)[MAX_CVARS]; +static int ncvars = 1, nccmds = 1; +DEF_NEW(s16, cvar_new, ncvars, MAX_CVARS, "cvar entries") +DEF_NEW(s16, ccmd_new, nccmds, MAX_CCMDS, "ccmd entries") + +#define MAX_EVENTS 512 +static SHUNT(struct cmeta_slice, event_names)[MAX_EVENTS]; +static SHUNT(s16, event_owners)[MAX_EVENTS]; +static SHUNT(struct list_chunk, event_handlers)[MAX_EVENTS] = {0}; +static SHUNT(struct list_chunkhdr *, event_handlers_tails)[MAX_EVENTS] = {0}; +static SHUNT(struct list_chunk, event_params)[MAX_EVENTS] = {0}; +// XXX: would simply things a little if we could segregate the regular event and +// predicate arrays, but because of how the event.h API is currently set up, +// HANDLE_EVENT doesn't give context for what kind of event something is, so we +// can't do that in a single pass (right now we create placeholder event entries +// and check at the end to make sure we didn't miss one). fixing this is tricky! +static SHUNT(bool, event_predicateflags)[MAX_EVENTS] = {0}; +static int nevents = 1; +DEF_NEW(s16, event_new, nevents, MAX_EVENTS, "event entries") + +// a "crit-nybble tree" (see also: djb crit-bit trees) +struct radix { s16 children[16], critpos; }; +static SHUNT(struct radix, radices)[MAX_MODULES * 2 + MAX_EVENTS]; +static int nradices = 1; // also reserve a null value + +// NOTE: this will never fail, as node count is bounded by modules * 2 + events +static inline s16 radix_new(void) { return nradices++; } + +static int matchlen(const char *s1, const char *s2, int len, bool ignorecase) { + uchar c1, c2; + for (int i = 0; i < len; ++i) { + c1 = s1[i]; c2 = s2[i]; + if (ignorecase) { + c1 |= (c1 >= 'A' && c1 <= 'Z') << 5; // if A-Z, |= 32 -> a-z + c2 |= (c2 >= 'A' && c2 <= 'Z') << 5; // " + } + int diff = c1 ^ c2; + if (diff) return (i << 1) | (diff < 16); + } + return (len << 1); +} +static inline int radixbranch(const char *s, int i, bool ignorecase) { + uchar c = s[i >> 1]; + if (ignorecase) c |= (c >= 'A' && c <= 'Z') << 5; + return c >> ((~i & 1) << 2) & 0xF; +} + +/* + * Tries to insert a string index into a radix/crit-nybble/whatever trie. + * Does not actually put the index in; that way an item can be allocated only + * after determining that it doesn't already exist. Callers have to check + * ret.isnew and act accordingly. + */ +static struct radix_insert_ret { + bool isnew; + union { + s16 *strp; /* If isnew, caller must do `*ret.strp = -stridx;`. */ + s16 stridx; /* If !isnew, `stridx` points at the existing string. */ + }; +} radix_insert(s16 *nodep, const struct cmeta_slice *strlist, + const char *s, int len, bool ignorecase) { + // special case: an empty tree is just null; replace it with a leaf node + if (!*nodep) return (struct radix_insert_ret){true, nodep}; + s16 *insp = nodep; + for (s16 cur;;) { + cur = *nodep; + assume(cur); + if (cur < 0) { + // once we find an existing leaf node, we have to compare our string + // against it to find the critical position (i.e. the point where + // the strings differ) and then insert a new node at that point. + const struct cmeta_slice existing = strlist[-cur]; + int ml; + if (existing.len == len) { + ml = matchlen(existing.s, s, len, ignorecase); + if (ml == len << 1) { + return (struct radix_insert_ret){false, .stridx = -cur}; + } + } + else { + // ugh this is a little inelegant, can we think of a better way? + ml = matchlen(existing.s, s, + len < existing.len ? len : existing.len, ignorecase); + } + int oldbranch = radixbranch(existing.s, ml, ignorecase); + int newbranch = radixbranch(s, ml, ignorecase); + assume(oldbranch != newbranch); + for (;;) { + // splice in front of an existing string *or* an empty slot. + if (*insp <= 0) break; + struct radix *r = radices + *insp; + if (r->critpos > ml) break; + insp = r->children + radixbranch(s, r->critpos, ignorecase); + } + s16 new = radix_new(); + radices[new].critpos = ml; + radices[new].children[oldbranch] = *insp; + s16 *strp = radices[new].children + newbranch; + *insp = new; + return (struct radix_insert_ret){true, strp}; + } + // try to take the exact path to match common prefixes, but otherwise + // just pick any path that lets us find a string to compare with. + // we always have to compare against an existing string to determine the + // exact correct point at which to insert a new node. + int branch = 0; + if_hot (radices[cur].critpos <= len << 1) { + branch = radixbranch(s, radices[cur].critpos, ignorecase); + } + while (!radices[cur].children[branch]) branch = (branch + 1) & 15; + nodep = radices[cur].children + branch; + } +} + +/* + * Inserts an entry that's already in a string list. Can be used for + * dupe-checking after appending a new entry. Returns false on duplicate + * entries. + */ +static bool radix_insertidx(s16 *nodep, const struct cmeta_slice *strlist, + s16 stridx, bool ignorecase) { + const char *s = strlist[stridx].s; int len = strlist[stridx].len; + struct radix_insert_ret r = radix_insert(nodep, strlist, s, len, ignorecase); + if (r.isnew) *r.strp = -stridx; + return r.isnew; +} + +/* + * Returns the string index of an existing entry matching `s` and `len`. + * Returns 0 (the reserved null index) if an entry does not exist. + */ +static s16 radix_lookup(s16 node, const struct cmeta_slice *strlist, + const char *s, int len, bool ignorecase) { + while (node) { + if (node < 0) { + const struct cmeta_slice matched = strlist[-node]; + if (matched.len != len) return 0; + if (matchlen(matched.s, s, len, ignorecase) == len << 1) { + return -node; + } + return 0; + } + if (radices[node].critpos >= len << 1) return 0; + int branch = radixbranch(s, radices[node].critpos, ignorecase); + node = radices[node].children[branch]; + } + return 0; +} + +static inline void handle(s16 mod, s16 mods, s16 *featdescs, s16 *events, + const os_char *file, const struct cmeta *cm) { + bool isfeat = false; + const char *needfeat = 0; + bool canpreinit = true, haspreinit = false, hasinit = false; + struct list_chunkhdr *tail_needs = &mod_needs[mod].hdr; + struct list_chunkhdr *tail_wants = &mod_wants[mod].hdr; + struct list_chunkhdr *tail_gamedata = &mod_gamedata[mod].hdr; + struct list_chunkhdr *tail_globals = &mod_globals[mod].hdr; + for (u32 i = 0; i != cm->nitems; ++i) { + switch_exhaust_enum(cmeta_item, cm->itemtypes[i]) { + case CMETA_ITEM_DEF_CVAR: + if (!cmeta_nparams(cm, i)) { + diefile(2, file, cmeta_line(cm, i), + "cvar macro missing required parameter"); + } + int flags = cmeta_flags_cvar(cm, i); + s16 idx = cvar_new(); + cvar_flags[idx] = flags; + // NOTE: always hooking cvar/ccmd up to a feat entry for + // GAMESPECIFIC checks, even if it's not a DEF_FEAT_*. + // If a module doesn't declare FEATURE, its cvars/ccmds will + // end up getting initialised/registered unconditionally. + cvar_feats[idx] = mod; + if (flags & CMETA_CVAR_FEAT) needfeat = "feature cvar defined"; + cmeta_param_foreach (name, cm, i) { + cvar_names[idx] = name; + break; // ignore subsequent args + } + break; + case CMETA_ITEM_DEF_CCMD: + if (!cmeta_nparams(cm, i)) { + diefile(2, file, cmeta_line(cm, i), + "ccmd macro missing required parameter"); + } + flags = cmeta_flags_ccmd(cm, i); + if (flags & CMETA_CCMD_FEAT) { + needfeat = "feature ccmd defined"; + } + if (flags & CMETA_CCMD_PLUSMINUS) { + // split PLUSMINUS entries in two; makes stuff easier later. + flags &= ~CMETA_CCMD_PLUSMINUS; + idx = ccmd_new(); + ccmd_flags[idx] = flags; + ccmd_feats[idx] = mod; + cmeta_param_foreach (name, cm, i) { + char *s = arena + arena_new(5 + name.len); + memcpy(s, "PLUS_", 5); + memcpy(s + 5, name.s, name.len); + ccmd_names[idx] = (struct cmeta_slice){s, 5 + name.len}; + break; + } + idx = ccmd_new(); + ccmd_flags[idx] = flags; + ccmd_feats[idx] = mod; + cmeta_param_foreach (name, cm, i) { + char *s = arena + arena_new(6 + name.len); + memcpy(s, "MINUS_", 6); + memcpy(s + 6, name.s, name.len); + ccmd_names[idx] = (struct cmeta_slice){s, 6 + name.len}; + break; + } + } + else { + idx = ccmd_new(); + ccmd_flags[idx] = flags; + ccmd_feats[idx] = mod; + cmeta_param_foreach (name, cm, i) { + ccmd_names[idx] = name; + break; + } + } + break; + case CMETA_ITEM_DEF_EVENT: + if (!cmeta_nparams(cm, i)) { + diefile(2, file, cmeta_line(cm, i), + "event macro missing required parameter"); + } + flags = cmeta_flags_event(cm, i); + struct cmeta_param_iter it = cmeta_param_iter_init(cm, i); + struct cmeta_slice evname = cmeta_param_iter(&it); + struct radix_insert_ret r = radix_insert(events, event_names, + evname.s, evname.len, false); + int e; + if (r.isnew) { + e = event_new(); + *r.strp = -e; + event_names[e] = evname; + event_handlers_tails[e] = &event_handlers[e].hdr; + } + else { + e = r.stridx; + if (event_owners[e]) { + diefile(2, file, cmeta_line(cm, i), + "conflicting event definition"); + } + } + event_owners[e] = mod; + event_predicateflags[e] = !!(flags & CMETA_EVENT_ISPREDICATE); + struct list_chunkhdr *tail = &event_params[e].hdr; + for (struct cmeta_slice param; param = cmeta_param_iter(&it), + param.s;) { + list_append(&tail, param); + } + break; + case CMETA_ITEM_HANDLE_EVENT: + int nparams = cmeta_nparams(cm, i); + if (!nparams) { + diefile(2, file, cmeta_line(cm, i), + "event handler macro missing required parameter"); + } + it = cmeta_param_iter_init(cm, i); + evname = cmeta_param_iter(&it); + r = radix_insert(events, event_names, evname.s, evname.len, + false); + if (r.isnew) { + e = event_new(); + *r.strp = -e; + event_names[e] = evname; + event_handlers_tails[e] = &event_handlers[e].hdr; + } + else { + e = r.stridx; + } + list_append(event_handlers_tails + e, mod); + mod_flags[mod] |= HAS_EVENTS; + break; + case CMETA_ITEM_FEATURE: + isfeat = true; + // note: if no param given, featdesc will still be null + cmeta_param_foreach (param, cm, i) { + mod_featdescs[mod] = param; + if (!radix_insertidx(featdescs, mod_featdescs, mod, true)) { + diefile(2, file, cmeta_line(cm, i), + "duplicate feature description text"); + } + break; + } + break; + case CMETA_ITEM_REQUIRE: + if (!cmeta_nparams(cm, i)) { + diefile(2, file, cmeta_line(cm, i), + "dependency macro missing required parameter"); + } + flags = cmeta_flags_require(cm, i); + struct list_chunkhdr **tailp; + switch_exhaust(flags) { + case 0: tailp = &tail_needs; break; + case CMETA_REQUIRE_OPTIONAL: tailp = &tail_wants; break; + case CMETA_REQUIRE_GAMEDATA: tailp = &tail_gamedata; break; + case CMETA_REQUIRE_GLOBAL: tailp = &tail_globals; + } + cmeta_param_foreach(param, cm, i) { + int modflags = 0; + switch_exhaust(flags) { + case CMETA_REQUIRE_OPTIONAL: + modflags = HAS_OPTDEPS; + case 0: + canpreinit = false; + s16 depmod = radix_lookup(mods, mod_names, param.s, + param.len, false); + if (!depmod) { + fprintf(stderr, "cmeta_fatal: %" fS ":%d: " + "feature `%.*s` does not exist\n", + file, cmeta_line(cm, i), + param.len, param.s); + exit(2); + } + mod_flags[depmod] |= modflags; + list_append(tailp, depmod); + break; + case CMETA_REQUIRE_GAMEDATA: case CMETA_REQUIRE_GLOBAL: + list_append(tailp, param); + } + } + break; + case CMETA_ITEM_GAMESPECIFIC: + canpreinit = false; + if_cold (!cmeta_nparams(cm, i)) { + diefile(2, file, cmeta_line(cm, i), + "GAMESPECIFIC macro missing required parameter"); + } + needfeat = "GAMESPECIFIC specified"; + if_cold (mod_gamespecific[mod].s) { + diefile(2, file, cmeta_line(cm, i), + "conflicting GAMESPECIFIC macros"); + } + cmeta_param_foreach(param, cm, i) { + mod_gamespecific[mod] = param; + break; + } + break; + case CMETA_ITEM_INIT: + if (hasinit) { + diefile(2, file, cmeta_line(cm, i), "multiple INIT blocks"); + } + hasinit = true; + mod_flags[mod] |= HAS_INIT; + needfeat = "INIT block defined"; + break; + case CMETA_ITEM_PREINIT: + if (haspreinit) { + diefile(2, file, cmeta_line(cm, i), + "multiple PREINIT blocks"); + } + haspreinit = true; + mod_flags[mod] |= HAS_PREINIT; + needfeat = "PREINIT block defined"; + break; + case CMETA_ITEM_END: + if (mod_flags[mod] & HAS_END) { + diefile(2, file, cmeta_line(cm, i), "multiple END blocks"); + } + mod_flags[mod] |= HAS_END; + needfeat = "END block defined"; + } + } + if (needfeat && !isfeat) { + fprintf(stderr, "gluegen: fatal: %" fS ": %s without FEATURE()", file, + needfeat); + exit(2); + } + if (isfeat && !hasinit) { + diefile(2, file, 0, "feature is missing INIT block"); + } + if (!canpreinit && haspreinit) { + diefile(2, file, 0, "cannot use dependencies along with PREINIT"); + } +} + +static int dfs(s16 mod, bool first); +static int dfs_inner(s16 mod, s16 dep, bool first) { + if (!(mod_flags[dep] & HAS_INIT)) { + fprintf(stderr, "gluegen: fatal: feature `%.*s` tried to depend on " + "non-feature module `%.*s`\n", + mod_names[mod].len, mod_names[mod].s, + mod_names[dep].len, mod_names[dep].s); + exit(2); + } + switch (dfs(dep, false)) { + // unwind the call stack by printing each node in the dependency cycle. + // ASCII arrows are kind of ugly here but windows' CRT handles unicode + // *so* horrendously and I'm not in the mood to replace stdio for this + // (maybe another time!) + case 1: + fprintf(stderr, ".-> %.*s\n", + mod_names[mod].len, mod_names[mod].s); + return 2; + case 2: + fprintf(stderr, first ? "'-- %.*s\n" : "| %.*s\n", + mod_names[mod].len, mod_names[mod].s); + return 2; + } + return 0; +} +static int dfs(s16 mod, bool first) { + if (mod_flags[mod] & DFS_SEEN) return 0; + if (mod_flags[mod] & DFS_SEEING) { + fprintf(stderr, "gluegen: fatal: feature dependency cycle:\n"); + return 1; + } + mod_flags[mod] |= DFS_SEEING; + list_foreach(s16, dep, mod_needs + mod) { + if (dfs_inner(mod, dep, first)) return 2; + } + list_foreach(s16, dep, mod_wants + mod) { + if (dfs_inner(mod, dep, first)) return 2; + } + feat_initorder[nfeatures++] = mod; + mod_flags[mod] |= DFS_SEEN; // note: no need to bother unsetting SEEING. + return 0; +} + +static inline void sortfeatures(void) { + for (int i = 1; i < nmods; ++i) { + if ((mod_flags[i] & HAS_INIT) && dfs(i, true)) exit(2); + } +} + +static inline noreturn diewrite(void) { die(100, "couldn't write to file"); } +#define _(x) \ + if (fprintf(out, "%s\n", x) < 0) diewrite(); +#define F(f, ...) \ + if (fprintf(out, f "\n", __VA_ARGS__) < 0) diewrite(); +#define H_() \ + _("/* This file is autogenerated by src/build/gluegen.c. DO NOT EDIT! */") +#define H() H_() _("") + +static void recursefeatdescs(FILE *out, s16 node) { + if (node < 0) { + if (mod_featdescs[-node].s) { +F( " if (status_%.*s != FEAT_SKIP) {", + mod_names[-node].len, mod_names[-node].s) +F( " con_colourmsg(status_%.*s == FEAT_OK ? &green : &red,", + mod_names[-node].len, mod_names[-node].s) +F( " featmsgs[status_%.*s], %.*s);", + mod_names[-node].len, mod_names[-node].s, + mod_featdescs[-node].len, mod_featdescs[-node].s) +_( " }") + } + } + else if (node > 0) { + for (int i = 0; i < 16; ++i) { + recursefeatdescs(out, radices[node].children[i]); + } + } +} + +static int evargs(FILE *out, s16 i, const char *suffix) { + int j = 1; + if (fprintf(out, "(") < 0) diewrite(); + list_foreach (struct cmeta_slice, param, event_params + i) { + if (param.len == 4 && !memcmp(param.s, "void", 4)) { + // UGH, crappy special case for (void). with C23 this could be + // redundant, but I think we still want to avoid blowing up gluegen + // if someone (me?) does it the old way out of habit. + // also if someone does void, int something_else this will create + // nonsense, but that's just garbage-in-garbage-out I guess. + break; + } + else if (fprintf(out, "%stypeof(%.*s) a%d", j == 1 ? "" : ", ", + param.len, param.s, j) < 0) { + diewrite(); + } + ++j; + } + if (fputs(suffix, out) < 0) diewrite(); + return j; +} + +static int evargs_notype(FILE *out, s16 i, const char *suffix) { + int j = 1; + if (fprintf(out, "(") < 0) diewrite(); + list_foreach(struct cmeta_slice, param, event_params + i) { + if (param.len == 4 && !memcmp(param.s, "void", 4)) { + break; + } + if (fprintf(out, "%sa%d", j == 1 ? "" : ", ", j) < 0) { + diewrite(); + } + ++j; + } + if (fputs(suffix, out) < 0) diewrite(); + return j; +} + +static inline void gencode(FILE *out, s16 featdescs) { + for (int i = 1; i < nmods; ++i) { + if (mod_flags[i] & HAS_INIT) { +F( "extern int _feat_init_%.*s(void);", mod_names[i].len, mod_names[i].s) + } + if (mod_flags[i] & HAS_PREINIT) { +F( "extern int _feat_preinit_%.*s(void);", mod_names[i].len, mod_names[i].s) + } + if (mod_flags[i] & HAS_END) { +F( "extern void _feat_end_%.*s(void);", mod_names[i].len, mod_names[i].s) + } + } +_( "") +_( "static struct {") + for (int i = 1; i < nmods; ++i) { + if (mod_flags[i] & HAS_OPTDEPS) continue; + if ((mod_flags[i] & HAS_INIT) && + (mod_flags[i] & (HAS_END | HAS_EVENTS))) { +F( " bool _has_%.*s : 1;", mod_names[i].len, mod_names[i].s) + } + } + for (int i = 1; i < nmods; ++i) { + if (mod_flags[i] & HAS_PREINIT) { +F( " int preinit_%.*s : 2;", mod_names[i].len, mod_names[i].s) + } + } +_( "} feats = {0};") +_( "") + for (int i = 1; i < nmods; ++i) { + if (!(mod_flags[i] & HAS_INIT)) continue; + if (mod_flags[i] & HAS_OPTDEPS) { + // If something REQUESTS a feature, it needs to be able to query + // has_*, so make it extern. XXX: this could be bitpacked as well + // potentially however the whole struct would then have to be + // included in all the places that use feature macros, which would + // probably screw up the potential for fast incremental builds... +F( "bool has_%.*s = false;", mod_names[i].len, mod_names[i].s) + } + else if (mod_flags[i] & (HAS_END | HAS_EVENTS)) { + // mildly stupid, but easier really. paper over the difference so we + // can generate the same has_ checks elsewhere :^) +F( "#define has_%.*s (feats._has_%.*s)", + mod_names[i].len, mod_names[i].s, mod_names[i].len, mod_names[i].s) + } + } +_( "") + for (int i = 1; i < ncvars; ++i) { +F( "extern struct con_var *%.*s;", cvar_names[i].len, cvar_names[i].s); + } + for (int i = 1; i < nccmds; ++i) { +F( "extern struct con_cmd *%.*s;", ccmd_names[i].len, ccmd_names[i].s); + } +_( "") +_( "static inline void preinitfeatures(void) {") + for (int i = 1; i < nmods; ++i) { + if (mod_flags[i] & HAS_PREINIT) { +F( " feats.preinit_%.*s = _feat_preinit_%.*s();", + mod_names[i].len, mod_names[i].s, mod_names[i].len, mod_names[i].s) + } + } +_( "}") +_( "") +_( "static inline void initfeatures(void) {") + for (int i = 0; i < nfeatures; ++i) { // N.B.: this *should* be 0-indexed! + const char *else_ = ""; + s16 mod = feat_initorder[i]; + if (mod_flags[mod] & HAS_PREINIT) { +F( " s8 status_%.*s = feats.preinit_%.*s;", + mod_names[mod].len, mod_names[mod].s, + mod_names[mod].len, mod_names[mod].s) + } + else { +F( " s8 status_%.*s;", mod_names[mod].len, mod_names[mod].s) + } + if (mod_gamespecific[mod].s) { +F( " %sif (!GAMETYPE_MATCHES(%.*s)) status_%.*s = FEAT_SKIP;", else_, + mod_gamespecific[mod].len, mod_gamespecific[mod].s, + mod_names[mod].len, mod_names[mod].s) + else_ = "else "; + } + list_foreach (struct cmeta_slice, gamedata, mod_gamedata + mod) { + // this is not a *totally* ideal way of doing this, but it's easy. + // if we had some info about what gamedata was doing, we could avoid + // having to ifdef these cases and could just directly generate the + // right thing. but that'd be quite a bit of work, so... we don't! + if (mod_gamespecific[mod].s) { +F( "#ifdef _GAMES_WITH_%.*s", gamedata.len, gamedata.s) +F( " %sif (!(_gametype_tag_%.*s & _GAMES_WITH_%.*s) && !has_%.*s) {", else_, + mod_gamespecific[mod].len, mod_gamespecific[mod].s, + gamedata.len, gamedata.s, gamedata.len, gamedata.s) +F( " status_%.*s = NOGD;", mod_names[mod].len, mod_names[mod].s) +_( " }") +_( "#else") + } +F( " %sif (!has_%.*s) status_%.*s = NOGD;", else_, + gamedata.len, gamedata.s, mod_names[mod].len, mod_names[mod].s) + if (mod_gamespecific[mod].s) { +_( "#endif") + } + else_ = "else "; + } + list_foreach (struct cmeta_slice, global, mod_globals + mod) { +F( " %sif (!(%.*s)) status_%.*s = NOGLOBAL;", else_, + global.len, global.s, mod_names[mod].len, mod_names[mod].s) + else_ = "else "; + } + list_foreach (s16, dep, mod_needs + mod) { +F( " %sif (status_%.*s != FEAT_OK) status_%.*s = REQFAIL;", else_, + mod_names[dep].len, mod_names[dep].s, + mod_names[mod].len, mod_names[mod].s) + else_ = "else "; + } + if (mod_flags[mod] & (HAS_END | HAS_EVENTS | HAS_OPTDEPS)) { +F( " %sif ((status_%.*s = _feat_init_%.*s()) == FEAT_OK) has_%.*s = true;", + else_, + mod_names[mod].len, mod_names[mod].s, + mod_names[mod].len, mod_names[mod].s, + mod_names[mod].len, mod_names[mod].s) + } + else { +F( " %sstatus_%.*s = _feat_init_%.*s();", else_, + mod_names[mod].len, mod_names[mod].s, + mod_names[mod].len, mod_names[mod].s) + } + } +_( "") + for (int i = 1; i < ncvars; ++i) { + if (!(cvar_flags[i] & CMETA_CVAR_UNREG)) { + if (cvar_flags[i] & CMETA_CVAR_FEAT) { + struct cmeta_slice modname = mod_names[cvar_feats[i]]; +F( " if (status_%.*s != FEAT_SKIP) con_regvar(%.*s);", + modname.len, modname.s, cvar_names[i].len, cvar_names[i].s) +F( " else if (status_%.*s != FEAT_OK) %.*s->base.flags |= CON_HIDDEN;", + modname.len, modname.s, cvar_names[i].len, cvar_names[i].s) + } + else { +F( " con_regvar(%.*s);", cvar_names[i].len, cvar_names[i].s) + } + } + } + for (int i = 1; i < nccmds; ++i) { + if (!(ccmd_flags[i] & CMETA_CCMD_UNREG)) { + if (ccmd_flags[i] & CMETA_CCMD_FEAT) { + struct cmeta_slice modname = mod_names[ccmd_feats[i]]; +F( " if (status_%.*s == FEAT_OK) con_regcmd(%.*s);", + modname.len, modname.s, ccmd_names[i].len, ccmd_names[i].s) + } + else { +F( " con_regcmd(%.*s);", ccmd_names[i].len, ccmd_names[i].s) + } + } + } +_( "") +_( " successbanner();") +_( " struct rgba white = {255, 255, 255, 255};") +_( " struct rgba green = {128, 255, 128, 255};") +_( " struct rgba red = {255, 128, 128, 255};") +_( " con_colourmsg(&white, \"---- List of plugin features ---\\n\");"); + recursefeatdescs(out, featdescs); +_( "}") +_( "") +_( "static inline void endfeatures(void) {") + for (int i = nfeatures - 1; i >= 0; --i) { + s16 mod = feat_initorder[i]; + if (mod_flags[mod] & HAS_END) { +F( " if (has_%.*s) _feat_end_%.*s();", + mod_names[mod].len, mod_names[mod].s, + mod_names[mod].len, mod_names[mod].s) + } + } +_( "}") +_( "") +_( "static inline void freevars(void) {") + for (int i = 1; i < ncvars; ++i) { +F( " extfree(%.*s->strval);", cvar_names[i].len, cvar_names[i].s) + } +_( "}") + for (int i = 1; i < nevents; ++i) { + const char *prefix = event_predicateflags[i] ? + "bool CHECK_" : "void EMIT_"; + if (fprintf(out, "\n%s%.*s", prefix, + event_names[i].len, event_names[i].s) < 0) { + diewrite(); + } + evargs(out, i, ") {\n"); + list_foreach(s16, mod, event_handlers + i) { + const char *type = event_predicateflags[i] ? "bool" : "void"; + if (fprintf(out, "\t%s _evhandler_%.*s_%.*s", type, + mod_names[mod].len, mod_names[mod].s, + event_names[i].len, event_names[i].s) < 0) { + diewrite(); + } + evargs(out, i, ");\n"); + if (event_predicateflags[i]) { + if (mod_flags[mod] & HAS_INIT) { + if (fprintf(out, "\tif (has_%.*s && !", + mod_names[mod].len, mod_names[mod].s) < 0) { + diewrite(); + } + } + else if (fputs("\tif (!", out) < 0) { + diewrite(); + } + if (fprintf(out, "_evhandler_%.*s_%.*s", + mod_names[mod].len, mod_names[mod].s, + event_names[i].len, event_names[i].s) < 0) { + diewrite(); + } + evargs_notype(out, i, ")) return false;\n"); + } + else { + if (fputc('\t', out) < 0) diewrite(); + if ((mod_flags[mod] & HAS_INIT) && fprintf(out, "if (has_%.*s) ", + mod_names[mod].len, mod_names[mod].s) < 0) { + diewrite(); + } + if (fprintf(out, "_evhandler_%.*s_%.*s", + mod_names[mod].len, mod_names[mod].s, + event_names[i].len, event_names[i].s) < 0) { + diewrite(); + } + evargs_notype(out, i, ");\n"); + } + } + if (event_predicateflags[i]) { +_( " return true;") + } +_( "}") + } +} + +int OS_MAIN(int argc, os_char *argv[]) { + s16 modlookup = 0, featdesclookup = 0, eventlookup = 0; + if (argc > MAX_MODULES) { + die(2, "too many files passed - increase MAX_MODULES in gluegen.c!"); + } + nmods = argc; + for (int i = 1; i < nmods; ++i) { + const os_char *f = argv[i]; + int len = 0; + int lastpart = 0; + for (; f[len]; ++len) { +#ifdef _WIN32 + if (f[len] == '/' || f[len] == '\\') lastpart = len + 1; +#else + if (f[len] == '/') lastpart = len + 1; +#endif + } + if_cold (!len) die(1, "empty file path given"); + if_cold (len < lastpart) diefile(1, f, 0, "invalid file path"); + if_cold (len < 3 || memcmp(f + len - 2, OS_LIT(".c"), + 2 * ssizeof(os_char))) { + diefile(1, f, 0, "not a C source file (.c)"); + } + struct cmeta_slice modname; + // ugh. same dumb hack from compile scripts + if_cold (len - lastpart == 6 && !memcmp(f + lastpart, OS_LIT("con_.c"), + 6 * ssizeof(os_char))) { + modname.s = "con"; modname.len = 3; + } + else { + char *p = arena + arena_new(len - lastpart - 2); + // XXX: Unicode isn't real, it can't hurt you. + for (int i = lastpart, j = 0; i < len - 2; ++i, ++j) p[j] = f[i]; + modname.s = p; modname.len = len - lastpart - 2; + } + mod_names[i] = modname; + if (!radix_insertidx(&modlookup, mod_names, i, false)) { + // XXX: might have to fix this some day to handle subdirs and such. + // for now we rely on it happening not to be a problem basically lol + diefile(2, f, 0, "duplicate module name"); + } + } + for (int i = 1; i < nmods; ++i) { + struct cmeta cm = cmeta_loadfile(argv[i]); + handle(i, modlookup, &featdesclookup, &eventlookup, argv[i], &cm); + } + // double check that events are defined. the compiler would also catch this, + // but we can do it faster and with arguably more helpful error information. + for (int i = 1; i < nevents; ++i) { + if (!event_owners[i]) { + fprintf(stderr, "gluegen: fatal: undefined event %.*s\n", + event_names[i].len, event_names[i].s); + exit(2); + } + } + sortfeatures(); + + FILE *out = fopen(".build/include/glue.gen.h", "wb"); + if (!out) die(100, "couldn't open .build/include/glue.gen.h"); + H() + gencode(out, featdesclookup); + if (fflush(out)) die(100, "couldn't finish writing output"); + return 0; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/mkentprops.c b/src/build/mkentprops.c index cda1c02..3df21ed 100644 --- a/src/build/mkentprops.c +++ b/src/build/mkentprops.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -30,7 +30,7 @@ #endif static noreturn die(int status, const char *s) { - fprintf(stderr, "mkentprops: %s\n", s); + fprintf(stderr, "mkentprops: fatal: %s\n", s); exit(status); } static noreturn dieparse(const os_char *file, int line, const char *s) { @@ -194,7 +194,7 @@ static inline void handleentry(char *k, char *v, int vlen, } } -static void parse(const os_char *file, int len) { +static inline void parse(const os_char *file, int len) { char *s = sbase; // for convenience if (s[len - 1] != '\n') dieparse(file, 0, "invalid text file (missing EOL)"); enum { BOL = 0, KEY = 4, KWS = 8, VAL = 12, COM = 16, ERR = -1 }; @@ -279,14 +279,12 @@ Fi(" %s = off;", sbase + art_leaves[idx].varstr); _i(" if (mem_loads32(mem_offset(sp, off_SP_type)) == DPT_DataTable) {") _i(" int baseoff = off;") _i(" const struct SendTable *st = mem_loadptr(mem_offset(sp, off_SP_subtable));") -_i(" // BEGIN SUBTABLE") Fi(" for (int i = 0, need = %d; i < st->nprops && need; ++i) {", art_leaves[idx].nsubs + (art_leaves[idx].varstr != -1)) _i(" const struct SendProp *sp = mem_offset(st->props, sz_SendProp * i);") _i(" const char *p = mem_loadptr(mem_offset(sp, off_SP_varname));") dosendtables(out, art_leaves[idx].subtree, indent + 4); _i(" }") -_i(" // END SUBTABLE") _i(" }") } Fi(" --need;") @@ -338,7 +336,7 @@ _i(" } break;") _i("}") } -static void dodecls(FILE *out) { +static inline void dodecls(FILE *out) { for (int i = 0; i < ndecls; ++i) { const char *s = sbase + decls[i]; F( "extern int %s;", s); @@ -346,7 +344,7 @@ F( "#define has_%s (!!%s)", s, s); // offsets will NEVER be 0, due to vtable! } } -static void doinit(FILE *out) { +static inline void doinit(FILE *out) { for (int i = 0; i < ndecls; ++i) { const char *s = sbase + decls[i]; F( "int %s = 0;", s); diff --git a/src/build/mkgamedata.c b/src/build/mkgamedata.c index 1fce1cf..ed8cf97 100644 --- a/src/build/mkgamedata.c +++ b/src/build/mkgamedata.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -29,7 +29,7 @@ #endif static noreturn die(int status, const char *s) { - fprintf(stderr, "mkentprops: %s\n", s); + fprintf(stderr, "mkentprops: fatal: %s\n", s); exit(status); } @@ -156,7 +156,37 @@ static inline noreturn diewrite(void) { die(100, "couldn't write to file"); } _( "/* This file is autogenerated by src/build/mkgamedata.c. DO NOT EDIT! */") \ _( "") -static void decls(FILE *out) { +static inline void knowngames(FILE *out) { + // kind of tricky optimisation: if a gamedata entry has no default but + // does have game-specific values which match a feature's GAMESPECIFIC() + // macro, load-time conditional checks resulting from REQUIRE_GAMEDATA() can + // be elided at compile-time. + for (int i = 0, j; i < nents - 1; i = j) { + while (exprs[i]) { // if there's a default value, we don't need this + // skip to next unindented thing, return if there isn't one with at + // least one indented thing under it. + for (++i; indents[i] != 0; ++i) if (i == nents - 1) return; + } +F( "#line %d \"%" fS "\"", srclines[i], srcnames[srcfiles[i]]) + if (fprintf(out, "#define _GAMES_WITH_%s (", sbase + tags[i]) < 0) { + diewrite(); + } + const char *pipe = ""; + for (j = i + 1; j < nents && indents[j] != 0; ++j) { + // don't attempt to optimise for nested conditionals because that's + // way more complicated and also basically defeats the purpose. + if (indents[j] != 1) continue; + if (fprintf(out, "%s \\\n\t _gametype_tag_%s", pipe, + sbase + tags[j]) < 0) { + diewrite(); + } + pipe = " |"; + } + fputs(" \\\n)\n", out); + } +} + +static inline void decls(FILE *out) { for (int i = 0; i < nents; ++i) { if (indents[i] != 0) continue; F( "#line %d \"%" fS "\"", srclines[i], srcnames[srcfiles[i]]) @@ -175,13 +205,13 @@ F( "#line %d \"%" fS "\"", srclines[i], srcnames[srcfiles[i]]) if_cold (i == nents - 1 || !indents[i + 1]) { // no tags - it's constant F( "enum { %s = (%s) };", sbase + tags[i], sbase + exprs[i]) } - else { // global variable intialised by gamedata_init() call + else { // global variable intialised by initgamedata() call F( "extern int %s;", sbase + tags[i]); } } } -static void defs(FILE *out) { +static inline void defs(FILE *out) { for (int i = 0; i < nents; ++i) { if (indents[i] != 0) continue; if_hot (i < nents - 1 && indents[i + 1]) { @@ -196,8 +226,8 @@ F( "int %s = -2147483648;", sbase + tags[i]) } } -static void init(FILE *out) { -_( "void gamedata_init(void) {") +static inline void init(FILE *out) { +_( "static void initgamedata(void) {") int varidx; int indent = 0; for (int i = 0; i < nents; ++i) { @@ -261,6 +291,7 @@ int OS_MAIN(int argc, os_char *argv[]) { FILE *out = fopen(".build/include/gamedata.gen.h", "wb"); if (!out) die(100, "couldn't open gamedata.gen.h"); H(); + knowngames(out); decls(out); out = fopen(".build/include/gamedatainit.gen.h", "wb"); diff --git a/src/build/skiplist.h b/src/build/skiplist.h deleted file mode 100644 index ab6a920..0000000 --- a/src/build/skiplist.h +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright © 2024 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 - * 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. - */ - -#ifndef INC_SKIPLIST_H -#define INC_SKIPLIST_H - -#include <stdlib.h> - -#include "../intdefs.h" -#include "../os.h" - -#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; -} -#else -#include <strings.h> -#define _skiplist_ffs ffs -#endif - -// WARNING: this is a really hacked-up version of the skiplist.h from cbits in -// order to support windows. It probably isn't a good idea to plop straight into -// your own use case. - -#if defined(__GNUC__) || defined(__clang__) -#define _skiplist_unused __attribute__((unused)) // heck off gcc -#else -#define _skiplist_unused -#endif - -// NOTE: using xoroshiro128++, a comparatively bad (i.e. non-cryptographic) prng -// for the sake of simplicity; original cbits skiplist.h relies on libcpoly to -// get arc4random() everywhere but since we're only using this at build time -// that seemed like a silly dependency to bother with. -//#define _skiplist_rng arc4random - -// ALSO NOTE: the PRNG code here is *decidedly not* thread safe. again, this -// isn't a problem for our use case. just keep it in mind if reusing this header -// for something else. or ideally, don't reuse this header for something else... -static inline uvlong _skiplist_rotl(const uvlong x, int k) { - return (x << k) | (x >> (64 - k)); -} -_skiplist_unused static uvlong _skiplist_rng(void) { - static uvlong s[2]; - static bool init = false; - if (!init) { os_randombytes(s, sizeof(s)); init = true; } - uvlong s0 = s[0], s1 = s[1]; - uvlong ret = _skiplist_rotl(s0 * 5, 7) * 9; - s1 ^= s0; - s[0] = _skiplist_rotl(s0, 24) ^ s1 ^ (s1 << 16); - s[1] = _skiplist_rotl(s1, 37); - return ret; -} - -/* - * Declares the skiplist header struct skiplist_hdr##name, but none of the - * associated functions. Use when the structure needs to be passed around in - * some way but actual operations on the list are a private implementation - * detail. Otherwise, see DECL_SKIPLIST below. - */ -#define DECL_SKIPLIST_TYPE(name, dtype, ktype, levels) \ -typedef dtype _skiplist_dt_##name; \ -typedef ktype _skiplist_kt_##name; \ -enum { skiplist_lvls_##name = (levels) }; \ -struct skiplist_hdr_##name { dtype *x[levels]; }; - -/* - * Declares the skiplist header struct skiplist_hdr_##name, with functions - * skiplist_{get,del,pop,insert}_##name for operating on the list. A single - * occurrence of DEF_SKIPLIST is required to actually implement the - * functions. - * - * This macro implies DECL_SKIPLIST_TYPE (both should not be used). - * - * mod should be either static or extern. - * - * dtype should be the struct type that the skiplist header will be embedded in, - * forming the linked structure. - * - * ktype should be the type of the struct member used for comparisons, for - * example int or char *. - * - * levels should be the number of levels in each node. 4 is probably a - * reasonable number, depending on the size of the structure and how many - * entries need to be stored and looked up. - * - * The resulting get, del, pop and insert functions are hopefully self- - * explanatory - get and del return the relevant node or a null pointer if - * no such node is found. - */ -#define DECL_SKIPLIST(mod, name, dtype, ktype, levels) \ -DECL_SKIPLIST_TYPE(name, dtype, ktype, levels) \ -\ -_skiplist_unused mod dtype *skiplist_get_##name(struct skiplist_hdr_##name *l, \ - ktype k); \ -_skiplist_unused mod dtype *skiplist_del_##name(struct skiplist_hdr_##name *l, \ - ktype k); \ -_skiplist_unused mod dtype *skiplist_pop_##name(struct skiplist_hdr_##name *l); \ -_skiplist_unused mod void skiplist_insert_##name(struct skiplist_hdr_##name *l, \ - ktype k, dtype *node); - -/* - * Implements the functions corresponding to a skiplist - must come after - * DECL_SKIPLIST with the same modifier and name. - * - * compfunc should be a function declared as follows (or an equivalent macro): - * int cf(dtype *x, ktype y); - * - * hdrfunc should be a function declared as follows (or an equivalent macro): - * struct skiplist_hdr_##name *hf(dtype *l); - */ -#define DEF_SKIPLIST(mod, name, compfunc, hdrfunc) \ -static inline int _skiplist_lvl_##name(void) { \ - int i; \ - /* for 2 levels we get 1 50% of the time, 2 25% of the time, 0 25% of the - time. loop if 0 to distribute this evenly (this gets less likely the more - levels there are: at 4 levels, only loops 6% of the time) */ \ - while (!(i = _skiplist_ffs(_skiplist_rng() & \ - ((1 << skiplist_lvls_##name) - 1)))); \ - /* ffs gives bit positions as 1-N but we actually want an array index */ \ - return i - 1; \ -} \ -\ -_skiplist_unused \ -mod _skiplist_dt_##name *skiplist_get_##name(struct skiplist_hdr_##name *l, \ - _skiplist_kt_##name k) { \ - for (int cmp, lvl = skiplist_lvls_##name - 1; lvl > -1; --lvl) { \ - while (l->x[lvl] && (cmp = compfunc(l->x[lvl], k)) < 0) { \ - l = hdrfunc(l->x[lvl]); \ - } \ - /* NOTE: cmp can be uninitialised here, but only if the list is - completely empty, in which case we'd return 0 anyway - so it doesn't - actually matter! */ \ - if (cmp == 0) return l->x[lvl]; \ - } \ - /* reached the end, no match */ \ - return 0; \ -} \ -\ -_skiplist_unused \ -_skiplist_dt_##name *skiplist_del_##name(struct skiplist_hdr_##name *l, \ - _skiplist_kt_##name k) { \ - _skiplist_dt_##name *ret = 0; \ - /* ALSO NOTE: in *this* case, cmp DOES need to be initialised to prevent a - possible null-deref via hdrfunc(l->x[lvl])->x */ \ - for (int cmp = 1, lvl = skiplist_lvls_##name - 1; lvl > -1; --lvl) { \ - while (l->x[lvl] && (cmp = compfunc(l->x[lvl], k)) < 0) { \ - l = hdrfunc(l->x[lvl]); \ - } \ - if (cmp == 0) { \ - ret = l->x[lvl]; \ - /* just shift each link by 1 */ \ - l->x[lvl] = hdrfunc(l->x[lvl])->x[0]; \ - /* ... and update every level of links via loop */ \ - } \ - } \ - /* reached the end, return whatever was found */ \ - return ret; \ -} \ -\ -_skiplist_unused \ -mod _skiplist_dt_##name *skiplist_pop_##name(struct skiplist_hdr_##name *l) { \ - _skiplist_dt_##name *cur = l->x[0]; \ - if (!cur) return 0; \ - l->x[0] = hdrfunc(cur)->x[0]; \ - for (int lvl = 1; lvl < skiplist_lvls_##name; ++lvl) { \ - if (l->x[lvl]) l->x[lvl] = hdrfunc(l->x[lvl])->x[lvl]; \ - } \ - return cur; \ -} \ -\ -_skiplist_unused \ -mod void skiplist_insert_##name(struct skiplist_hdr_##name *l, \ - _skiplist_kt_##name k, _skiplist_dt_##name *node) { \ - /* note: higher levels are unset but also skipped in other searches */ \ - int inslvl = _skiplist_lvl_##name(); \ - for (int lvl = skiplist_lvls_##name - 1; lvl > -1; --lvl) { \ - while (l->x[lvl] && compfunc(l->x[lvl], k) < 0) { \ - l = hdrfunc(l->x[lvl]); \ - } \ - if (lvl <= inslvl) { \ - hdrfunc(node)->x[lvl] = l->x[lvl]; \ - l->x[lvl] = node; \ - } \ - } \ -} \ - -#endif - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/vec.h b/src/build/vec.h deleted file mode 100644 index 6dfa645..0000000 --- a/src/build/vec.h +++ /dev/null @@ -1,95 +0,0 @@ -/* This file is dedicated to the public domain. */ - -#ifndef INC_VEC_H -#define INC_VEC_H - -#include <errno.h> -#include <stdlib.h> - -#include "../intdefs.h" - -struct _vec { - uint sz; - uint max; - void *data; -}; - -/* - * A dynamic array with push, pop and concatenate operations. - * - * Usage: struct VEC(my_type) myvec = {0}; - * Or: struct myvec VEC(my_type); - * Or: typedef struct VEC(my_type) myvec; - */ -#define VEC(type) { \ - uint sz; \ - uint max; \ - type *data; \ -} - -#if defined(__GNUC__) || defined(__clang__) -__attribute__((unused)) // heck off gcc -#endif -static bool _vec_ensure(struct _vec *v, uint tsize, uint newmax) { - // FIXME: potential overflow at least on 32-bit hosts (if any!?). - // should use reallocarray or something but didn't feel like porting right - // now. consider doing later. - void *new = realloc(v->data, tsize * newmax); - if (new) { v->data = new; v->max = newmax; } - return !!new; -} - -#if defined(__GNUC__) || defined(__clang__) -__attribute__((unused)) // heck off gcc 2 -#endif -static bool _vec_make_room(struct _vec *v, uint tsize, uint addcnt) { - // this overflow check is probably unnecessary, but just in case - u64 chk = v->max + addcnt; - if (chk > 1u << 30) { errno = ENOMEM; return false; } - u32 x = chk; - if (x < 16) { - x = 16; - } - else { - // round up to next 2*n - --x; - x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; - x++; - } - return _vec_ensure(v, tsize, x); -} - -// internal: for reuse by vec0 -#define _vec_push(v, val, slack) ( \ - ((v)->sz + (slack) < (v)->max || \ - _vec_make_room((struct _vec *)(v), sizeof(val), 1)) && /*NOLINT*/ \ - ((v)->data[(v)->sz++ - slack] = (val), true) \ -) - -#define _vec_pushall(v, vals, n, slack) ( \ - ((v)->sz + (n) + (slack) <= (v)->max || \ - _vec_make_room((struct _vec *)(v), sizeof(*(vals)), (n))) && \ - (memcpy((v)->data + (v)->sz - (slack), (vals), (n) * sizeof(*(vals))), \ - (v)->sz += (n), true) \ -) - -/* - * Appends an item to the end of a vector. Gives true on success and false if - * memory allocation fails. - */ -#define vec_push(v, val) _vec_push(v, val, 0) - -/* - * Appends n items from an array to the end of a vector. Gives true on success - * and false if memory allocation fails. - */ -#define vec_pushall(v, vals, n) _vec_pushall(v, vals, n, 0) - -/* - * Removes an item from the end of a vector and gives that item. - */ -#define vec_pop(v) ((v)->data[--(v)->sz]) - -#endif - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/clientcon.c b/src/clientcon.c index 29a4208..9673848 100644 --- a/src/clientcon.c +++ b/src/clientcon.c @@ -20,7 +20,7 @@ #include "feature.h" #include "gamedata.h" -FEATURE("") +FEATURE() REQUIRE(ent) REQUIRE_GAMEDATA(vtidx_ClientPrintf) REQUIRE_GLOBAL(engserver) @@ -36,6 +36,6 @@ void clientcon_reply(const char *s) { if (e) { clientcon_msg(e, s); return; } } -INIT { return true; } +INIT { return FEAT_OK; } // vi: sw=4 ts=4 noet tw=80 cc=80 @@ -1,6 +1,6 @@ /* THIS FILE SHOULD BE CALLED `con.c` BUT WINDOWS IS STUPID */ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -69,8 +69,6 @@ static inline void initval(struct con_var *v) { memcpy(v->strval, v->defaultval, v->strlen); } -#include <cmdinit.gen.h> // generated by build/codegen.c - // to try and match the engine even though it's probably not strictly required, // we call the Internal* virtual functions via the actual vtable. since vtables // are built dynamically (below), we store this index; other indices are just @@ -303,8 +301,13 @@ struct _con_vtab_iconvar_wrap _con_vtab_iconvar_wrap = { #endif }; -void con_reg(void *cmd_or_var) { - RegisterConCommand(_con_iface, cmd_or_var); +void con_regvar(struct con_var *v) { + initval(v); + RegisterConCommand(_con_iface, v); +} + +void con_regcmd(struct con_cmd *c) { + RegisterConCommand(_con_iface, c); } // XXX: these should use vcall/gamedata stuff as they're only used for the @@ -384,8 +387,6 @@ void con_init(void) { *pi++ = (void *)&IsFlagSet_thunk; // last one: not in 004, but doesn't matter. one less branch! *pi++ = (void *)&GetSplitScreenPlayerSlot; - - regcmds(); } static void helpuserhelpus(int pluginver, char ifaceverchar) { @@ -460,7 +461,6 @@ bool con_detect(int pluginver) { void con_disconnect(void) { UnregisterConCommands(_con_iface, dllid); - freevars(); } struct con_var *con_findvar(const char *name) { @@ -1,6 +1,6 @@ /* THIS FILE SHOULD BE CALLED `con.h` BUT WINDOWS IS STUPID */ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -74,7 +74,7 @@ typedef void (*con_cmdcbv1)(void); /* * This is an autocompletion callback for suggesting arguments to a command. - * XXX: Autocompletion isn't really totally figured out or implemented yet. + * TODO(autocomplete): Autocompletion isn't really implemented yet. */ typedef int (*con_complcb)(const char *part, char cmds[CON_CMD_MAXCOMPLETE][CON_CMD_MAXCOMPLLEN]); @@ -286,7 +286,8 @@ extern struct _con_vtab_iconvar_wrap { /* * Defines a console command with the handler function body immediately - * following the macro (like in Source itself). + * following the macro (like in Source itself). The function takes the argument + * `struct con_cmdargs *cmd` for command arguments. */ #define DEF_CCMD_HERE(name, desc, flags) \ static void _cmdf_##name(const struct con_cmdargs *cmd); \ @@ -295,8 +296,35 @@ extern struct _con_vtab_iconvar_wrap { /* { body here } */ /* + * These are exactly the same as the above macros, but instead of + * unconditionally registering things, they have the following conditions: + * + * - Variables are always registered, but get hidden if a feature fails to + * initialise. + * - If a feature specifies GAMESPECIFIC(), its cvars will remain unregistered + * unless the game matches. + * - Commands are only registered if the feature successfully initialises. + * + * In situations where exact control over initialisation is not required, these + * macros ought to make life a lot easier and are generally recommended. + * + * Obviously, these should only be used inside of a feature (see feature.h). The + * code generator will produce an error otherwise. + */ +#define DEF_FEAT_CVAR DEF_CVAR +#define DEF_FEAT_CVAR_MIN DEF_CVAR_MIN +#define DEF_FEAT_CVAR_MAX DEF_CVAR_MAX +#define DEF_FEAT_CVAR_MINMAX DEF_CVAR_MINMAX +#define DEF_FEAT_CCMD DEF_CCMD +#define DEF_FEAT_CCMD_HERE DEF_CCMD_HERE +#define DEF_FEAT_CCMD_PLUSMINUS DEF_CCMD_PLUSMINUS + +/* * These are exactly the same as the above macros, but they don't cause the - * commands or variables to be registered on plugin load. + * commands or variables to be registered on plugin load or feature + * initialisation. Registration must be done manually. These are generally not + * recommended but may be needed in specific cases such as conditionally + * reimplementing a built-in engine feature. */ #define DEF_CVAR_UNREG DEF_CVAR #define DEF_CVAR_MIN_UNREG DEF_CVAR_MIN @@ -307,10 +335,14 @@ extern struct _con_vtab_iconvar_wrap { #define DEF_CCMD_PLUSMINUS_UNREG DEF_CCMD_PLUSMINUS /* - * Registers a command or variable defined with the _UNREG variants of the above - * macros. Can be used to conditionally register things. + * These functions register a command or variable, respectively, defined with + * the _UNREG variants of the above macros. These can be used to conditionally + * register things. Wherever possible, it is advised to use the DEF_FEAT_* + * macros instead for conditional registration, as they handle the common cases + * automatically. */ -void con_reg(void *cmd_or_var); +void con_regvar(struct con_var *v); +void con_regcmd(struct con_cmd *c); #endif diff --git a/src/democustom.c b/src/democustom.c index 30fc4ee..749415b 100644 --- a/src/democustom.c +++ b/src/democustom.c @@ -120,7 +120,8 @@ INIT { if (buildnum >= 2042) nbits_datalen = 11; else nbits_datalen = 12; //} - return find_WriteMessages(); + if (!find_WriteMessages()) return FEAT_INCOMPAT; + return FEAT_OK; } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/demorec.c b/src/demorec.c index 4a8efb5..66ffa1d 100644 --- a/src/demorec.c +++ b/src/demorec.c @@ -1,6 +1,6 @@ /* * Copyright © 2021 Willian Henrique <wsimanbrazil@yahoo.com.br> - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -38,8 +38,8 @@ FEATURE("improved demo recording") REQUIRE_GAMEDATA(vtidx_StopRecording) -DEF_CVAR(sst_autorecord, "Continuously record demos even after reconnecting", 1, - CON_ARCHIVE | CON_HIDDEN) +DEF_FEAT_CVAR(sst_autorecord, + "Continuously record demos even after reconnecting", 1, CON_ARCHIVE) void *demorecorder; static int *demonum; @@ -261,21 +261,21 @@ INIT { orig_stop_cb = con_getcmdcb(cmd_stop); if_cold (!find_demorecorder()) { errmsg_errorx("couldn't find demo recorder instance"); - return false; + return FEAT_INCOMPAT; } void **vtable = mem_loadptr(demorecorder); // XXX: 16 is totally arbitrary here! figure out proper bounds later if_cold (!os_mprot(vtable, 16 * sizeof(void *), PAGE_READWRITE)) { errmsg_errorsys("couldn't make virtual table writable"); - return false; + return FEAT_FAIL; } if_cold (!find_recmembers(vtable[vtidx_StopRecording])) { errmsg_errorx("couldn't find recording state variables"); - return false; + return FEAT_INCOMPAT; } if_cold (!find_demoname(vtable[vtidx_StartRecording])) { errmsg_errorx("couldn't find demo basename variable"); - return false; + return FEAT_INCOMPAT; } orig_SetSignonState = (SetSignonState_func)hook_vtable(vtable, @@ -286,8 +286,7 @@ INIT { cmd_record->cb = &hook_record_cb; cmd_stop->cb = &hook_stop_cb; - sst_autorecord->base.flags &= ~CON_HIDDEN; - return true; + return FEAT_OK; } END { diff --git a/src/engineapi.c b/src/engineapi.c index 5a78a92..62a4eb1 100644 --- a/src/engineapi.c +++ b/src/engineapi.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> * Copyright © 2023 Willian Henrique <wsimanbrazil@yahoo.com.br> * * Permission to use, copy, modify, and/or distribute this software for any @@ -18,6 +18,7 @@ #include <stdlib.h> // used in generated code #include <string.h> // " +#include "abi.h" // for NVDTOR use in gamedata generated code #include "con_.h" #include "engineapi.h" #include "gamedata.h" @@ -30,7 +31,7 @@ #include "vcall.h" #include "x86.h" -u64 _gametype_tag = 0; // declared in gametype.h but seems sensible enough here +u32 _gametype_tag = 0; // declared in gametype.h but seems sensible enough here ifacefactory factory_client = 0, factory_server = 0, factory_engine = 0, factory_inputsystem = 0; @@ -49,6 +50,7 @@ struct CServerPlugin *pluginhandler; DECL_VFUNC_DYN(void *, GetAllServerClasses) #include <entpropsinit.gen.h> // generated by build/mkentprops.c +#include <gamedatainit.gen.h> // generated by build/mkgamedata.c bool engineapi_init(int pluginver) { if_cold (!con_detect(pluginver)) return false; @@ -110,8 +112,7 @@ bool engineapi_init(int pluginver) { _gametype_tag |= _gametype_tag_TheLastStand; } } - - gamedata_init(); + initgamedata(); con_init(); if_cold (!gameinfo_init()) { con_disconnect(); return false; } return true; @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -127,7 +127,7 @@ static inline ctor_func findctor(const struct CEntityFactory *factory, seencall = 0; continue; } - return false; + return 0; } } // duping NEXT_INSN macro here in the name of a nicer message @@ -180,13 +180,12 @@ INIT { // for PEntityOfEntIndex we don't really have to do any more init, we // can just call the function later. - if (has_vtidx_PEntityOfEntIndex) return true; + if (has_vtidx_PEntityOfEntIndex) return FEAT_OK; if (globalvars && has_off_edicts) { edicts = getptr_edicts(globalvars); - return true; + return FEAT_OK; } - errmsg_warnx("not implemented for this engine"); - return false; + return FEAT_INCOMPAT; } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/event.h b/src/event.h index 86a443e..7e173d4 100644 --- a/src/event.h +++ b/src/event.h @@ -1,5 +1,5 @@ /* - * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -29,7 +29,7 @@ #define DECL_EVENT(evname, ...) typedef void _must_declare_event_##evname; /* - * Declares a predicate - a special type of even returning bool. Predicates are + * Declares a predicate - a special type of event returning bool. Predicates are * used to determine whether some other action should be performed, and * generally should not have side effects, since they get short-circuited and * thus won't always fire when a check is being performed. diff --git a/src/fastfwd.c b/src/fastfwd.c index e287770..7378b04 100644 --- a/src/fastfwd.c +++ b/src/fastfwd.c @@ -1,6 +1,6 @@ /* * Copyright © 2023 Matthew Wozniak <sirtomato999@gmail.com> - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> * Copyright © 2023 Willian Henrique <wsimanbrazil@yahoo.com.br> * * Permission to use, copy, modify, and/or distribute this software for any @@ -35,6 +35,10 @@ #include "x86util.h" FEATURE() +// currently only used for l4d quick reset stuff, and conflicts with SPT's +// tas_pause hook. so, disable for non-L4D games for now, to be polite. +// TODO(compat): come up with a real solution for this if/when required +GAMESPECIFIC(L4Dbased) REQUIRE_GAMEDATA(vtidx_RunFrame) REQUIRE_GAMEDATA(vtidx_Frame) REQUIRE_GAMEDATA(vtidx_GetRealTime) @@ -174,79 +178,73 @@ static void *find_floatcall(void *func, int fldcnt, const char *name) { } NEXT_INSN(p, name); } - return false; + return 0; } next: NEXT_INSN(p, name); } return 0; } -PREINIT { - // currently only used for l4d quick reset stuff, and conflicts with SPT's - // tas_pause hook. so, disable for non-L4D games for now, to be polite. - // TODO(compat): come up with a real solution for this if/when required - return GAMETYPE_MATCHES(L4Dbased); -} - INIT { void *hldsapi = factory_engine("VENGINE_HLDS_API_VERSION002", 0); if_cold (!hldsapi) { errmsg_errorx("couldn't find HLDS API interface"); - return false; + return FEAT_INCOMPAT; } void *enginetool = factory_engine("VENGINETOOL003", 0); if_cold (!enginetool) { errmsg_errorx("missing engine tool interface"); - return false; + return FEAT_INCOMPAT; } // behold: the greatest pointer chase of all time realtime = find_float((*(void ***)enginetool)[vtidx_GetRealTime]); if_cold (!realtime) { errmsg_errorx("couldn't find realtime variable"); - return false; + return FEAT_INCOMPAT; } host_frametime = find_float((*(void ***)enginetool)[vtidx_HostFrameTime]); if_cold (!host_frametime) { errmsg_errorx("couldn't find host_frametime variable"); - return false; + return FEAT_INCOMPAT; } void *eng = find_eng((*(void ***)hldsapi)[vtidx_RunFrame]); if_cold (!eng) { errmsg_errorx("couldn't find eng global object"); - return false; + return FEAT_INCOMPAT; } void *func; if_cold (!(func = find_HostState_Frame((*(void ***)eng)[vtidx_Frame]))) { errmsg_errorx("couldn't find HostState_Frame function"); - return false; + return FEAT_INCOMPAT; } if_cold (!(func = find_FrameUpdate(func))) { errmsg_errorx("couldn't find FrameUpdate function"); - return false; + return FEAT_INCOMPAT; } if_cold (!(func = find_floatcall(func, GAMETYPE_MATCHES(L4D2_2147plus) ? 2 : 1, "CHostState::State_Run"))) { errmsg_errorx("couldn't find State_Run function"); - return false; + return FEAT_INCOMPAT; } if_cold (!(func = find_floatcall(func, 1, "Host_RunFrame"))) { errmsg_errorx("couldn't find Host_RunFrame function"); - return false; + return FEAT_INCOMPAT; } if_cold (!(func = find_floatcall(func, 1, "_Host_RunFrame"))) { errmsg_errorx("couldn't find _Host_RunFrame"); - return false; + return FEAT_INCOMPAT; } if_cold (!find_Host_AccumulateTime(func)) { errmsg_errorx("couldn't find Host_AccumulateTime"); - return false; + return FEAT_INCOMPAT; } orig_Host_AccumulateTime = (Host_AccumulateTime_func)hook_inline( (void *)orig_Host_AccumulateTime, (void *)hook_Host_AccumulateTime); if_cold (!orig_Host_AccumulateTime) { errmsg_errorsys("couldn't hook Host_AccumulateTime function"); + return FEAT_FAIL; } - return true; + return FEAT_OK; } END { diff --git a/src/feature.h b/src/feature.h index e1e4688..81370b1 100644 --- a/src/feature.h +++ b/src/feature.h @@ -1,5 +1,5 @@ /* - * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -21,18 +21,42 @@ #define _FEATURE_CAT(a, b) _FEATURE_CAT1(a, b) /* - * Declares that this translation unit implements a "feature" - a unit of - * plugin functionality. + * Declares that this translation unit implements a "feature" - a unit of plugin + * functionality. * - * desc specifies a string to be displayed to the user. Omit this to declare an - * internal feature, which won't be advertised, but will be available to other - * features. + * At build time, the code generator automatically creates the glue code + * required to make features load and unload in the correct order, subject to + * compatibility with each supported game and engine version. As such, there is + * no need to manually plug a new feature into SST's initialisation code as long + * as dependency information is properly declared (see the macros below). + * + * desc specifies a string to be displayed to the user in the console printout + * that gets displayed when the plugin finishes loading. Omit this to declare an + * internal feature, which won't be displayed to users but will still be + * available for other features to call into and build functionality on top of. */ #define FEATURE(... /*desc*/) /* - * Indicates that the specified feature is required for this feature to function. - * If that feature fails to initialise, this feature will not be enabled. + * Declares that this feature should only be loaded for games matching the given + * gametype tag (see gametype.h). Console variables and commands created using + * DEF_FEAT_* macros will not be registered if SST is loaded by some other game. + * + * As an optimisation, REQUIRE_GAMEDATA() checks (see below) can also be elided + * in cases where gamedata is always present for this particular game. As such, + * it is wise to still specify gamedata dependencies correctly, so that the + * definitions can be changed in the data files without breaking code. + */ +#define GAMESPECIFIC(tag) + +/* + * Indicates that the specified feature is required for this feature to + * function. If that feature fails to initialise, this feature will not be + * enabled. + * + * By convention, the name of the feature is the name of its implementation + * source file, minus the .c extension. For instance, foo.c would be referred to + * by REQUIRE(foo). */ #define REQUIRE(feature) @@ -40,9 +64,9 @@ * Indicates that the specified feature should be initialised before this one, * but is not a hard requirement. * - * Presence of a feature can be tested for using has_<featurename>. + * Presence of a feature can in turn be tested for using has_<featname>. */ -#define REQUEST(featname) extern bool has_##featname; +#define REQUEST(feature) extern bool has_##feature; /* * Indicates that the specified gamedata entry is required for this feature to @@ -52,40 +76,85 @@ * effect on whether this feature is loaded. It can simply be tested for using * has_<entryname>. */ -#define REQUIRE_GAMEDATA(feature) +#define REQUIRE_GAMEDATA(entry) /* * Indicates that this feature requires a global variable (such as a factory or * globally-exposed engine interface) to be non-null in order to function. If * the variable has a null/zero value prior to feature initialisation, this * feature will not be enabled. + * + * Note that this really only works for variables known to engineapi.c which is + * kind of a bad abstraction, but it's currently just necessary in practice. + * + * Correct usage of this macro will generally be very similar to other usages + * found elsewhere in the codebase already, so use those as a reference. */ #define REQUIRE_GLOBAL(varname) +/* status values for INIT and PREINIT below */ +enum { + FEAT_SKIP = -1, /* feature isn't useful here, pretend it doesn't exist */ + FEAT_OK, /* feature successfully initialised/enabled */ + FEAT_FAIL, /* error in starting up feature */ + FEAT_INCOMPAT, /* feature is incompatible with this game/engine version */ + _FEAT_INTERNAL_STATUSES // internal detail, do not use +}; + /* - * Defines the special feature init function which is unique to this translation - * unit. This should return true to indicate success, or false to indicate - * failure. Features which start to load will cause dependent features not to be - * started. + * Defines the special feature initialisation function which is unique to this + * translation unit. All features are required to specify this function. + * + * The defined function must return FEAT_OK on success, FEAT_FAIL on failure due + * to some transient error, FEAT_INCOMPAT to indicate incompatibility with the + * current game/engine version, or FEAT_SKIP to indicate that the feature is + * useless or unnecessary. + * + * If a value other than FEAT_OK is returned, END (see below) will not be called + * later and other features that depend on this feature will be disabled. If + * this feature provides other functions as API, they can be assumed not to get + * called unless initialisation is successful. + * + * For features with a description (see FEATURE() above), all return values with + * the exception of FEAT_SKIP will cause a corresponding status message to be + * displayed in the listing after the plugin finishes loading, while FEAT_SKIP + * will simply hide the feature from the listing. Features with no description + * will not be displayed anyway. * - * Features are required to specify this function. + * Features which either fail to initialise or elect to skip loading will cause + * dependent features not to be enabled. */ -#define INIT bool _FEATURE_CAT(_feature_init_, MODULE_NAME)(void) // { code... } +#define INIT int _FEATURE_CAT(_feat_init_, MODULE_NAME)(void) // { code... } /* * Defines the special, optional feature shutdown function which is unique to * this translation unit. This does not return a value, and may be either * specified once, or left out if no cleanup is required for this feature. */ -#define END void _FEATURE_CAT(_feature_end_, MODULE_NAME)(void) // { code... } +#define END void _FEATURE_CAT(_feat_end_, MODULE_NAME)(void) // { code... } /* - * Defines a conditional check to run prior to checking other requirements for - * this feature. This can be used to match a certain game type or conditionally - * register console variables, and should return true or false to indicate - * whether the feature should continue to initialise. + * Defines a special feature pre-init function which performs early feature + * initialisation, the moment the plugin is loaded. If the plugin is autoloaded + * via VDF, this will be called long before the deferred initialisation that + * usually happens after the client and VGUI have spun up. Since most of the + * rest of SST is also deferred, care must be taken not to call anything else + * that is not yet initialised. + * + * When in doubt, do not use this; it exists only to serve a couple of fringe + * cases. + * + * Like INIT above, the function created by this macro is expected to return one + * of FEAT_OK, FEAT_FAIL, FEAT_INCOMPAT, or FEAT_SKIP. If a value other than + * FEAT_OK is returned, the INIT block won't be run afterwards. + * + * Features that use this macro are currently disallowed from using REQUIRE() + * and REQUEST(), as well as GAMESPECIFIC(), because it's not clear how the + * semantics of doing so should work. It is still possible to use + * REQUIRE_GAMEDATA and REQUIRE_GLOBAL, however these only apply to the INIT + * block, *NOT* the PREINIT. */ -#define PREINIT bool _FEATURE_CAT(_feature_preinit_, MODULE_NAME)(void) // {...} +#define PREINIT int _FEATURE_CAT(_feat_preinit_, MODULE_NAME)(void) // {...} #endif diff --git a/src/fixes.c b/src/fixes.c index 84b3482..dbb60f5 100644 --- a/src/fixes.c +++ b/src/fixes.c @@ -148,7 +148,7 @@ static void l4d2specific(void) { if (ident.VendorId == 0x8086) con_setvari(v, 1); // neat vendor id, btw! } IDirect3D9_Release(d3d9); -e:; +e: #endif // There's a rare, inexplicable issue where the game will drop to an @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> * Copyright © 2022 Willian Henrique <wsimanbrazil@yahoo.com.br> * * Permission to use, copy, modify, and/or distribute this software for any @@ -35,6 +35,9 @@ #include "x86util.h" FEATURE("extended FOV range") +// could work for other games, but generally only portal 1 people want this (the +// rest of us consider this cheating and a problem for runs...) +GAMESPECIFIC(Portal1) REQUEST(ent) DEF_CVAR_MINMAX_UNREG(fov_desired, @@ -80,34 +83,28 @@ HANDLE_EVENT(ClientActive, struct edict *e) { static struct con_cmd *cmd_fov; -PREINIT { - // could work for other games, but generally only portal 1 people want this - // (the rest of us consider this cheating and a problem for runs...) - return GAMETYPE_MATCHES(Portal1); -} - INIT { cmd_fov = con_findcmd("fov"); - if_cold (!cmd_fov) return false; // shouldn't happen but just in case! + if_cold (!cmd_fov) return FEAT_INCOMPAT; // shouldn't happen, but who knows! if (real_fov_desired = con_findvar("fov_desired")) { // latest steampipe already goes up to 120 fov - if (real_fov_desired->parent->maxval == 120) return false; + if (real_fov_desired->parent->maxval == 120) return FEAT_SKIP; real_fov_desired->parent->maxval = 120; } else { - if (!has_ent) return false; - con_reg(fov_desired); + if (!has_ent) return FEAT_INCOMPAT; + con_regvar(fov_desired); real_fov_desired = fov_desired; } if_cold (!find_SetDefaultFOV(cmd_fov)) { errmsg_errorx("couldn't find SetDefaultFOV function"); - return false; + return FEAT_INCOMPAT; } orig_SetDefaultFOV = (SetDefaultFOV_func)hook_inline( (void *)orig_SetDefaultFOV, (void *)&hook_SetDefaultFOV); if_cold (!orig_SetDefaultFOV) { errmsg_errorsys("couldn't hook SetDefaultFOV function"); - return false; + return FEAT_FAIL; } // we might not be using our cvar but simpler to do this unconditionally @@ -115,7 +112,7 @@ INIT { fov_desired->parent->base.flags &= ~CON_HIDDEN; // hide the original fov command since we've effectively broken it anyway :) cmd_fov->base.flags |= CON_DEVONLY; - return true; + return FEAT_OK; } END { diff --git a/src/gamedata.c b/src/gamedata.c deleted file mode 100644 index 247bf25..0000000 --- a/src/gamedata.c +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright © 2024 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 - * 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 "abi.h" // purely for NVDTOR -#include "gametype.h" - -#include <gamedatainit.gen.h> // generated by build/mkgamedata.c - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/gamedata.h b/src/gamedata.h index 050d3b8..9f4932a 100644 --- a/src/gamedata.h +++ b/src/gamedata.h @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -32,9 +32,6 @@ #undef NVDTOR #endif -/* Called as part of plugin init to set up various metadata about the game. */ -void gamedata_init(void); - #endif // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/gameserver.c b/src/gameserver.c index 9e046ee..c2df33f 100644 --- a/src/gameserver.c +++ b/src/gameserver.c @@ -68,10 +68,10 @@ static bool find_sv(con_cmdcb pause_cb) { INIT { struct con_cmd *pause = con_findcmd("pause"); if_cold (!find_sv(pause->cb)) { - errmsg_errorx("couldn't find game server object\n"); - return false; + errmsg_errorx("couldn't find game server object"); + return FEAT_INCOMPAT; } - return true; + return FEAT_OK; } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/gametype.h b/src/gametype.h index 35a43be..a6d6b0c 100644 --- a/src/gametype.h +++ b/src/gametype.h @@ -1,5 +1,5 @@ /* - * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> * Copyright © 2023 Willian Henrique <wsimanbrazil@yahoo.com.br> * * Permission to use, copy, modify, and/or distribute this software for any @@ -20,7 +20,7 @@ #include "intdefs.h" -extern u64 _gametype_tag; +extern u32 _gametype_tag; /* general engine branches used in a bunch of stuff */ #define _gametype_tag_OE 1 @@ -58,7 +58,7 @@ extern u64 _gametype_tag; /* Matches for any multiple possible tags */ #define _gametype_tag_L4D (_gametype_tag_L4D1 | _gametype_tag_L4D2) -// XXX: *stupid* naming, refactor later (damn Survivors ruining everything) +// XXX: *stupid* naming, refactor one day (damn Survivors ruining everything) #define _gametype_tag_L4D2x (_gametype_tag_L4D2 | _gametype_tag_L4DS) #define _gametype_tag_L4Dx (_gametype_tag_L4D1 | _gametype_tag_L4D2x) #define _gametype_tag_L4Dbased (_gametype_tag_L4Dx | _gametype_tag_Portal2) @@ -169,29 +169,29 @@ INIT { matsurf = factory_engine("MatSystemSurface006", 0); if_cold (!matsurf) { errmsg_errorx("couldn't get MatSystemSurface006 interface"); - return false; + return FEAT_INCOMPAT; } void *schememgr = factory_engine("VGUI_Scheme010", 0); if_cold (!schememgr) { errmsg_errorx("couldn't get VGUI_Scheme010 interface"); - return false; + return FEAT_INCOMPAT; } if_cold (!find_toolspanel(vgui)) { errmsg_errorx("couldn't find engine tools panel"); - return false; + return FEAT_INCOMPAT; } void **vtable = *(void ***)toolspanel; if_cold (!os_mprot(vtable + vtidx_Paint, sizeof(void *), PAGE_READWRITE)) { errmsg_errorsys("couldn't make virtual table writable"); - return false; + return FEAT_FAIL; } orig_Paint = (Paint_func)hook_vtable(vtable, vtidx_Paint, (void *)&hook_Paint); SetPaintEnabled(toolspanel, true); // 1 is the default, first loaded scheme. should always be sourcescheme.res scheme = GetIScheme(schememgr, (struct handlewrap){1}); - return true; + return FEAT_OK; } END { diff --git a/src/inputhud.c b/src/inputhud.c index af3bb59..da96d5e 100644 --- a/src/inputhud.c +++ b/src/inputhud.c @@ -1,7 +1,7 @@ /* * Copyright © 2022 Matthew Wozniak <sirtomato999@gmail.com> * Copyright © 2022 Willian Henrique <wsimanbrazil@yahoo.com.br> - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com + * Copyright © 2025 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 @@ -43,23 +43,23 @@ REQUIRE_GAMEDATA(vtidx_VClient_DecodeUserCmdFromBuffer) REQUIRE_GLOBAL(factory_client) REQUIRE(hud) -DEF_CVAR(sst_inputhud, "Enable button input HUD", 0, CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR(sst_inputhud_bgcolour_normal, +DEF_FEAT_CVAR(sst_inputhud, "Enable button input HUD", 0, CON_ARCHIVE) +DEF_FEAT_CVAR(sst_inputhud_bgcolour_normal, "Input HUD default key background colour (RGBA hex)", "4040408C", - CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR(sst_inputhud_bgcolour_pressed, + CON_ARCHIVE) +DEF_FEAT_CVAR(sst_inputhud_bgcolour_pressed, "Input HUD pressed key background colour (RGBA hex)", "202020C8", - CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR(sst_inputhud_fgcolour, "Input HUD text colour (RGBA hex)", "F0F0F0FF", - CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR_MINMAX(sst_inputhud_scale, "Input HUD size (multiple of minimum)", - 1.5, 1, 4, CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR_MINMAX(sst_inputhud_x, + CON_ARCHIVE) +DEF_FEAT_CVAR(sst_inputhud_fgcolour, "Input HUD text colour (RGBA hex)", + "F0F0F0FF", CON_ARCHIVE) +DEF_FEAT_CVAR_MINMAX(sst_inputhud_scale, "Input HUD size (multiple of minimum)", + 1.5, 1, 4, CON_ARCHIVE) +DEF_FEAT_CVAR_MINMAX(sst_inputhud_x, "Input HUD x position (fraction between screen left and right)", - 0.02, 0, 1, CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR_MINMAX(sst_inputhud_y, + 0.02, 0, 1, CON_ARCHIVE) +DEF_FEAT_CVAR_MINMAX(sst_inputhud_y, "Input HUD y position (fraction between screen top and bottom)", - 0.95, 0, 1, CON_ARCHIVE | CON_HIDDEN) + 0.95, 0, 1, CON_ARCHIVE) static void *input; static int heldbuttons = 0, tappedbuttons = 0; @@ -379,11 +379,11 @@ INIT { !(vclient = factory_client("VClient016", 0)) && !(vclient = factory_client("VClient017", 0))) { errmsg_errorx("couldn't get client interface"); - return false; + return FEAT_INCOMPAT; } if (!find_input(vclient)) { errmsg_errorx("couldn't find input global"); - return false; + return FEAT_INCOMPAT; } for (int i = 0; i < countof(fontnames); ++i) { if (fonts[i].h = hud_getfont(fontnames[i], true)) { @@ -396,7 +396,7 @@ INIT { // just unprotect the first few pointers (GetUserCmd is 8) if_cold (!os_mprot(vtable, sizeof(void *) * 8, PAGE_READWRITE)) { errmsg_errorsys("couldn't make virtual table writable"); - return false; + return FEAT_FAIL; } if (GAMETYPE_MATCHES(L4Dbased)) { orig_CreateMove = (CreateMove_func)hook_vtable(vtable, vtidx_CreateMove, @@ -418,31 +418,25 @@ INIT { else if (GAMETYPE_MATCHES(L4D)) layout = &layout_l4d; // TODO(compat): more game-specific layouts! - sst_inputhud->base.flags &= ~CON_HIDDEN; - sst_inputhud_scale->base.flags &= ~CON_HIDDEN; - sst_inputhud_bgcolour_normal->base.flags &= ~CON_HIDDEN; sst_inputhud_bgcolour_normal->cb = &colourcb; - sst_inputhud_bgcolour_pressed->base.flags &= ~CON_HIDDEN; sst_inputhud_bgcolour_pressed->cb = &colourcb; - sst_inputhud_fgcolour->base.flags &= ~CON_HIDDEN; sst_inputhud_fgcolour->cb = &colourcb; - sst_inputhud_x->base.flags &= ~CON_HIDDEN; - sst_inputhud_y->base.flags &= ~CON_HIDDEN; - // HACK: default HUD position would clash with L4D player health HUDs and - // HL2 sprint HUD, so move it up. this currently has to be done in a super - // crappy, nasty way to get the defaults to display right in the console... - // TODO(opt): move PREINIT stuff to before cvar init and avoid this nonsense + // Default HUD position would clash with L4D player health HUDs and + // HL2 sprint HUD, so move it up. This is a bit yucky, but at least we don't + // have to go through all the virtual setter crap twice... if (GAMETYPE_MATCHES(L4D)) { sst_inputhud_y->defaultval = "0.82"; - con_setvarstr(sst_inputhud_y, "0.82"); + sst_inputhud_y->fval = 0.82f; + sst_inputhud_y->ival = 0; } else if (GAMETYPE_MATCHES(HL2series)) { sst_inputhud_y->defaultval = "0.75"; - con_setvarstr(sst_inputhud_y, "0.75"); + sst_inputhud_y->fval = 0.75f; + sst_inputhud_y->ival = 0; } - return true; + return FEAT_OK; } END { diff --git a/src/kvsys.c b/src/kvsys.c index 31652f3..bc9230e 100644 --- a/src/kvsys.c +++ b/src/kvsys.c @@ -113,7 +113,7 @@ INIT { (void *)hook_GetStringForSymbol); } } - return true; + return FEAT_OK; } END { diff --git a/src/l4dmm.c b/src/l4dmm.c index 05a03a7..40e6fd5 100644 --- a/src/l4dmm.c +++ b/src/l4dmm.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -28,6 +28,7 @@ #include "vcall.h" FEATURE() +GAMESPECIFIC(L4D) REQUIRE(kvsys) REQUIRE_GAMEDATA(vtidx_GetMatchNetworkMsgController) REQUIRE_GAMEDATA(vtidx_GetActiveGameServerDetails) @@ -125,12 +126,12 @@ INIT { ifacefactory factory = (ifacefactory)os_dlsym(mmlib, "CreateInterface"); if_cold (!factory) { errmsg_errordl("couldn't get matchmaking interface factory"); - return false; + return FEAT_INCOMPAT; } matchfwk = factory("MATCHFRAMEWORK_001", 0); if_cold (!matchfwk) { errmsg_errorx("couldn't get IMatchFramework interface"); - return false; + return FEAT_INCOMPAT; } sym_game = kvsys_strtosym("game"); sym_campaign = kvsys_strtosym("campaign"); @@ -141,11 +142,11 @@ INIT { oldmmiface = factory_engine("VENGINE_MATCHMAKING_VERSION001", 0); if_cold (!oldmmiface) { errmsg_errorx("couldn't get IMatchmaking interface"); - return false; + return FEAT_INCOMPAT; } #endif } - return true; + return FEAT_OK; } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/l4dreset.c b/src/l4dreset.c index 9e72f04..8333839 100644 --- a/src/l4dreset.c +++ b/src/l4dreset.c @@ -1,6 +1,6 @@ /* * Copyright © 2023 Willian Henrique <wsimanbrazil@yahoo.com.br> - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> * Copyright © 2024 Hayden K <imaciidz@gmail.com> * * Permission to use, copy, modify, and/or distribute this software for any @@ -45,6 +45,7 @@ #endif FEATURE("Left 4 Dead quick resetting") +GAMESPECIFIC(L4D) REQUIRE(ent) REQUIRE(fastfwd) REQUIRE(gameserver) @@ -196,11 +197,11 @@ static short ffdelay = 0; static float ffadj = 0; static int nextmapnum = 0; -DEF_CVAR_MINMAX_UNREG(sst_l4d_quickreset_peektime, +DEF_FEAT_CVAR_MINMAX(sst_l4d_quickreset_peektime, "Number of seconds to show each relevant item spot during fast-forward", - 1.5, 0, 3, CON_ARCHIVE | CON_HIDDEN) + 1.5, 0, 3, CON_ARCHIVE) -DEF_CCMD_HERE_UNREG(sst_l4d_quickreset_continue, +DEF_FEAT_CCMD_HERE(sst_l4d_quickreset_continue, "Get to the end of the current cutscene without further slowdowns", 0) { if (!ffdelay) { con_warn("not currently fast-forwarding a cutscene\n"); @@ -340,11 +341,10 @@ static int getffidx(const char *campaign) { return -1; // if unknown, just don't fast-forward, I guess. } -DEF_CVAR_UNREG(sst_l4d_quickreset_fastfwd, - "Fast-forward through cutscenes when quick-resetting", 1, - CON_ARCHIVE | CON_HIDDEN) +DEF_FEAT_CVAR(sst_l4d_quickreset_fastfwd, + "Fast-forward through cutscenes when quick-resetting", 1, CON_ARCHIVE) -DEF_CCMD_HERE_UNREG(sst_l4d_quickreset, +DEF_FEAT_CCMD_HERE(sst_l4d_quickreset, "Reset (or switch) campaign and clear all vote cooldowns", 0) { if (cmd->argc > 2) { con_warn("usage: sst_l4d_quickreset [campaignid]\n"); @@ -376,13 +376,6 @@ DEF_CCMD_HERE_UNREG(sst_l4d_quickreset, } } -PREINIT { - if (!GAMETYPE_MATCHES(L4D)) return false; - con_reg(sst_l4d_quickreset_fastfwd); - con_reg(sst_l4d_quickreset_peektime); - return true; -} - // Note: this returns a pointer to subsequent bytes for find_voteissues() below static inline const uchar *find_votecontroller(con_cmdcbv1 listissues_cb) { const uchar *insns = (const uchar *)listissues_cb; @@ -490,7 +483,7 @@ static inline bool find_UnfreezeTeam(void *GameFrame) { // note: L4D1 only NEXT_INSN(p, "Director::Update call"); } return false; -ok: // Director::Update calls UnfreezeTeam after the first jmp instruction +ok: // Director::Update calls UnfreezeTeam after the first jmp instruction while (p - insns < 96) { // jz XXX; mov ecx, <reg>; call Director::UnfreezeTeam if (p[0] == X86_JZ && p[2] == X86_MOVRMW && (p[3] & 0xF8) == 0xC8 && @@ -508,28 +501,28 @@ INIT { struct con_cmd *cmd_listissues = con_findcmd("listissues"); if_cold (!cmd_listissues) { errmsg_errorx("couldn't find \"listissues\" command"); - return false; + return FEAT_INCOMPAT; } con_cmdcbv1 listissues_cb = con_getcmdcbv1(cmd_listissues); const uchar *nextinsns = find_votecontroller(listissues_cb); if_cold (!nextinsns) { errmsg_errorx("couldn't find vote controller variable"); - return false; + return FEAT_INCOMPAT; } if_cold (!find_voteissues(nextinsns)) { errmsg_errorx("couldn't find vote issues list offset\n"); - return false; + return FEAT_INCOMPAT; } void **vtable; #ifdef _WIN32 void *GameShutdown = (*(void ***)srvdll)[vtidx_GameShutdown]; if_cold (!find_TheDirector(GameShutdown)) { errmsg_errorx("couldn't find TheDirector variable"); - return false; + return FEAT_INCOMPAT; } #else #warning TODO(linux): should be able to just dlsym(server, "TheDirector") - return false; + return FEAT_INCOMPAT; #endif #ifdef _WIN32 // L4D1 has no Linux build, no need to check whether L4D2 if (GAMETYPE_MATCHES(L4D2)) { @@ -538,7 +531,7 @@ INIT { if_cold (!os_mprot(vtable + vtidx_OnGameplayStart, sizeof(*vtable), PAGE_READWRITE)) { errmsg_errorsys("couldn't make virtual table writable"); - return false; + return FEAT_FAIL; } orig_OnGameplayStart = (OnGameplayStart_func)hook_vtable(vtable, vtidx_OnGameplayStart, (void *)&hook_OnGameplayStart); @@ -548,7 +541,7 @@ INIT { void *GameFrame = (*(void ***)srvdll)[vtidx_GameFrame]; if_cold (!find_UnfreezeTeam(GameFrame)) { errmsg_errorx("couldn't find UnfreezeTeam function"); - return false; + return FEAT_INCOMPAT; } orig_UnfreezeTeam = (UnfreezeTeam_func)hook_inline( (void *)orig_UnfreezeTeam, (void *)&hook_UnfreezeTeam); @@ -573,11 +566,7 @@ INIT { nocd: errmsg_note("resetting a first map will not clear vote cooldowns"); } } - con_reg(sst_l4d_quickreset); - con_reg(sst_l4d_quickreset_continue); - sst_l4d_quickreset_fastfwd->base.flags &= ~CON_HIDDEN; - sst_l4d_quickreset_peektime->base.flags &= ~CON_HIDDEN; - return true; + return FEAT_OK; } END { diff --git a/src/l4dwarp.c b/src/l4dwarp.c index b9e019d..eb638e4 100644 --- a/src/l4dwarp.c +++ b/src/l4dwarp.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> * Copyright © 2023 Willian Henrique <wsimanbrazil@yahoo.com.br> * * Permission to use, copy, modify, and/or distribute this software for any @@ -36,12 +36,15 @@ #include "x86util.h" FEATURE("Left 4 Dead warp testing") +GAMESPECIFIC(L4D) REQUIRE(clientcon) REQUIRE(ent) REQUIRE(trace) REQUIRE_GAMEDATA(off_entpos) REQUIRE_GAMEDATA(off_eyeang) REQUIRE_GAMEDATA(off_teamnum) +REQUIRE_GAMEDATA(vtidx_AddBoxOverlay2) +REQUIRE_GAMEDATA(vtidx_AddLineOverlay) REQUIRE_GAMEDATA(vtidx_Teleport) DECL_VFUNC_DYN(void, Teleport, const struct vec3f */*pos*/, @@ -99,7 +102,7 @@ static struct vec3f warptarget(void *ent) { }; } -DEF_CCMD_HERE_UNREG(sst_l4d_testwarp, "Simulate a bot warping to you " +DEF_FEAT_CCMD_HERE(sst_l4d_testwarp, "Simulate a bot warping to you " "(specify \"staystuck\" to skip take-control simulation)", CON_SERVERSIDE | CON_CHEAT) { bool staystuck = false; @@ -167,9 +170,9 @@ static bool draw_testpos(struct vec3f start, struct vec3f testpos, return false; } +// note: UNREG because testwarp can still work without this DEF_CCMD_HERE_UNREG(sst_l4d_previewwarp, "Visualise bot warp unstuck logic " - "(use clear_debug_overlays to remove)", - CON_SERVERSIDE | CON_CHEAT) { + "(use clear_debug_overlays to remove)", CON_SERVERSIDE | CON_CHEAT) { struct edict *ed = ent_getedict(con_cmdclient + 1); if_cold (!ed || !ed->ent_unknown) { errmsg_errorx("couldn't access player entity"); @@ -306,26 +309,16 @@ static bool init_filter(void) { return false; } -PREINIT { - return GAMETYPE_MATCHES(L4D); -} - INIT { struct con_cmd *z_add = con_findcmd("z_add"); if (!z_add || !find_EntityPlacementTest(z_add->cb)) { errmsg_errorx("couldn't find EntityPlacementTest function"); - return false; + return FEAT_INCOMPAT; } if (!init_filter()) { - errmsg_errorx("couldn't init trace filter for EntityPlacementTest"); - return false; + errmsg_errorx("couldn't find trace filter ctor for EntityPlacementTest"); + return FEAT_INCOMPAT; } - con_reg(sst_l4d_testwarp); - // NOTE: assuming has_vtidx_AddLineOverlay && has_vtidx_AddBoxOverlay2 - // since those are specified for L4D. - // TODO(opt): add some zero-cost/compile-time way to make sure gamedata - // exists in a game-specific scenario? (probably requires declarative - // game-specific features in codegen, which hasn't been high-priority) if_cold (!has_off_collision) { errmsg_warnx("missing m_Collision gamedata - warp preview unavailable"); } @@ -334,9 +327,9 @@ INIT { "warp preview unavailable"); } else { - con_reg(sst_l4d_previewwarp); + con_regcmd(sst_l4d_previewwarp); } - return true; + return FEAT_OK; } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/langext.h b/src/langext.h index ef0f18d..8d3ee6e 100644 --- a/src/langext.h +++ b/src/langext.h @@ -8,6 +8,8 @@ #define ssizeof(x) ((ssize)sizeof(x)) #define countof(x) (ssizeof(x) / ssizeof(*x)) +#undef unreachable // C23 stddef.h; prefer the non-function-like look of ours. + #if defined(__GNUC__) || defined(__clang__) #define if_hot(x) if (__builtin_expect(!!(x), 1)) #define if_cold(x) if (__builtin_expect(!!(x), 0)) @@ -44,6 +46,7 @@ #define switch_exhaust_enum(E, x) switch_exhaust ((enum E)(x)) #endif +// could do [[noreturn]] in future, _Noreturn probably supports more compilers. #define noreturn _Noreturn void #ifdef _WIN32 diff --git a/src/nomute.c b/src/nomute.c index 4e5bcc4..a72f067 100644 --- a/src/nomute.c +++ b/src/nomute.c @@ -1,6 +1,6 @@ /* * Copyright © 2023 Willian Henrique <wsimanbrazil@yahoo.com.br> - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -58,9 +58,9 @@ static long __stdcall hook_CreateSoundBuffer(IDirectSound *this, } PREINIT { - if (con_findvar("snd_mute_losefocus")) return false; - con_reg(snd_mute_losefocus); - return true; + if (con_findvar("snd_mute_losefocus")) return FEAT_SKIP; + con_regvar(snd_mute_losefocus); + return FEAT_OK; } INIT { @@ -69,14 +69,14 @@ INIT { if_cold (DirectSoundCreate(0, &ds_obj, 0) != DS_OK) { // XXX: can this error be usefully stringified? errmsg_errorx("couldn't create IDirectSound instance"); - return false; + return FEAT_OK; } ds_vt = ds_obj->lpVtbl; ds_obj->lpVtbl->Release(ds_obj); if_cold (!os_mprot(&ds_vt->CreateSoundBuffer, sizeof(void *), PAGE_READWRITE)) { errmsg_errorsys("couldn't make virtual table writable"); - return false; + return FEAT_OK; } orig_CreateSoundBuffer = ds_vt->CreateSoundBuffer; ds_vt->CreateSoundBuffer = &hook_CreateSoundBuffer; @@ -93,7 +93,7 @@ INIT { "audio settings or restarting the game with SST autoloaded in " "order to have an effect"); } - return true; + return FEAT_OK; } END { diff --git a/src/nosleep.c b/src/nosleep.c index 52c88a9..170b4d6 100644 --- a/src/nosleep.c +++ b/src/nosleep.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -43,7 +43,7 @@ static void VCALLCONV hook_SleepUntilInput(void *this, int timeout) { PREINIT { if (con_findvar("engine_no_focus_sleep")) return false; - con_reg(engine_no_focus_sleep); + con_regvar(engine_no_focus_sleep); return true; } @@ -52,12 +52,12 @@ INIT { if_cold (!os_mprot(vtable + vtidx_SleepUntilInput, sizeof(void *), PAGE_READWRITE)) { errmsg_errorx("couldn't make virtual table writable"); - return false; + return FEAT_FAIL; } orig_SleepUntilInput = (SleepUntilInput_func)hook_vtable(vtable, vtidx_SleepUntilInput, (void *)&hook_SleepUntilInput); engine_no_focus_sleep->base.flags &= ~CON_HIDDEN; - return true; + return FEAT_OK; } END { @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 Michael Smith <mikesmiffy128@gmail.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,6 +17,10 @@ #ifndef INC_OS_H #define INC_OS_H +#ifdef INC_LANGEXT_H // we can't rely on include order! +#undef noreturn // ugh, this breaks Windows headers. how annoying. temp-undef it +#endif + #include <string.h> #include <sys/stat.h> // XXX: try abstracting stat() and avoiding ucrt dep here @@ -239,6 +243,10 @@ bool os_mprot(void *addr, int len, int mode); */ void os_randombytes(void *buf, int sz); +#ifdef INC_LANGEXT_H +#define noreturn _Noreturn void // HACK: put this back if undef'd above +#endif + #endif // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/portalcolours.c b/src/portalcolours.c index 61d9cc7..89d1d17 100644 --- a/src/portalcolours.c +++ b/src/portalcolours.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -32,16 +32,17 @@ #include "vcall.h" FEATURE("portal gun colour customisation") +GAMESPECIFIC(Portal1) REQUIRE_GLOBAL(clientlib) // It's like the thing Portal Tools does, but at runtime! -DEF_CVAR_UNREG(sst_portal_colour0, "Crosshair colour for gravity beam (RGB hex)", - "F2CAA7", CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR_UNREG(sst_portal_colour1, "Crosshair colour for left portal (RGB hex)", - "40A0FF", CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR_UNREG(sst_portal_colour2, "Crosshair colour for right portal (RGB hex)", - "FFA020", CON_ARCHIVE | CON_HIDDEN) +DEF_FEAT_CVAR(sst_portal_colour0, "Crosshair colour for gravity beam (RGB hex)", + "F2CAA7", CON_ARCHIVE) +DEF_FEAT_CVAR(sst_portal_colour1, "Crosshair colour for left portal (RGB hex)", + "40A0FF", CON_ARCHIVE) +DEF_FEAT_CVAR(sst_portal_colour2, "Crosshair colour for right portal (RGB hex)", + "FFA020", CON_ARCHIVE) static struct rgba colours[3] = { {242, 202, 167, 255}, {64, 160, 255, 255}, {255, 160, 32, 255}}; @@ -93,36 +94,25 @@ static bool find_UTIL_Portal_Color(void *base) { return false; } -PREINIT { - if (!GAMETYPE_MATCHES(Portal1)) return false; - con_reg(sst_portal_colour0); - con_reg(sst_portal_colour1); - con_reg(sst_portal_colour2); - return true; -} - INIT { #ifdef _WIN32 if_cold (!find_UTIL_Portal_Color(clientlib)) { errmsg_errorx("couldn't find UTIL_Portal_Color"); - return false; + return FEAT_INCOMPAT; } orig_UTIL_Portal_Color = (UTIL_Portal_Color_func)hook_inline( (void *)orig_UTIL_Portal_Color, (void *)&hook_UTIL_Portal_Color); if_cold (!orig_UTIL_Portal_Color) { errmsg_errorsys("couldn't hook UTIL_Portal_Color"); - return false; + return FEAT_INCOMPAT; } - sst_portal_colour0->base.flags &= ~CON_HIDDEN; sst_portal_colour0->cb = &colourcb; - sst_portal_colour1->base.flags &= ~CON_HIDDEN; sst_portal_colour1->cb = &colourcb; - sst_portal_colour2->base.flags &= ~CON_HIDDEN; sst_portal_colour2->cb = &colourcb; - return true; + return FEAT_OK; #else #warning TODO(linux): yet more stuff! - return false; + return FEAT_INCOMPAT; #endif } diff --git a/src/rinput.c b/src/rinput.c index 1187e15..6d568f3 100644 --- a/src/rinput.c +++ b/src/rinput.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -68,7 +68,7 @@ DEF_CVAR_MINMAX(sst_mouse_factor, "Number of hardware mouse counts per step", static ssize __stdcall inproc(void *wnd, uint msg, usize wp, ssize lp) { switch (msg) { - case WM_INPUT:; + case WM_INPUT: char buf[ssizeof(RAWINPUTHEADER) + ssizeof(RAWMOUSE) /* = 40 */]; uint sz = sizeof(buf); if_hot (GetRawInputData((void *)lp, RID_INPUT, buf, &sz, @@ -133,7 +133,7 @@ INIT { if_cold (!os_mprot(vtable_insys + vtidx_GetRawMouseAccumulators, ssizeof(void *), PAGE_READWRITE)) { errmsg_errorx("couldn't make virtual table writable"); - return false; + return FEAT_FAIL; } orig_GetRawMouseAccumulators = (GetRawMouseAccumulators_func)hook_vtable( vtable_insys, vtidx_GetRawMouseAccumulators, @@ -141,7 +141,7 @@ INIT { } else { // create cvar hidden so config is still preserved if we fail to init - con_reg(m_rawinput); + con_regvar(m_rawinput); } WNDCLASSEXW wc = { .cbSize = sizeof(wc), @@ -172,7 +172,7 @@ INIT { con_colourmsg(&blue, ", you can scale down the sensor input with "); con_colourmsg(&gold, "sst_mouse_factor"); con_colourmsg(&blue, "!\n"); - return false; + return FEAT_INCOMPAT; } if (has_rawinput) { // no real reason to keep this around receiving useless window messages @@ -209,13 +209,13 @@ INIT { ok: m_rawinput->base.flags &= ~CON_HIDDEN; sst_mouse_factor->base.flags &= ~CON_HIDDEN; - return true; + return FEAT_OK; e3: DestroyWindow(inwin); e2: unhook_inline((void *)orig_SetCursorPos); e1: unhook_inline((void *)orig_GetCursorPos); e0: UnregisterClassW(L"RInput", 0); - return false; + return FEAT_FAIL; } END { @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -28,6 +28,8 @@ #include "engineapi.h" #include "errmsg.h" #include "event.h" +#include "extmalloc.h" // for freevars() in generated code +#include "feature.h" #include "fixes.h" #include "gamedata.h" #include "gameinfo.h" @@ -239,7 +241,7 @@ DEF_CCMD_HERE(sst_printversion, "Display plugin version information", 0) { // cvar to the top of the demo. this will be removed later, once there's a less // stupid way of achieving the same goal. #if VERSION_MAJOR != 0 || VERSION_MINOR != 9 -#error Need to change this manually, since codegen requires it to be spelled \ +#error Need to change this manually, since gluegen requires it to be spelled \ out in DEF_CVAR - better yet, can we get rid of this yet? #endif DEF_CVAR(__sst_0_9_beta, "", 0, CON_HIDDEN | CON_DEMO) @@ -271,7 +273,28 @@ static const char *updatenotes = "\ * More behind-the-scenes changes, as always\n\ "; -#include <featureinit.gen.h> // generated by build/codegen.c +enum { // used in generated code, must line up with + REQFAIL = _FEAT_INTERNAL_STATUSES, + NOGD, + NOGLOBAL +}; +static const char *const featmsgs[] = { // " + " [ OK! ] %s\n", + " [ FAILED! ] %s (error in initialisation)\n", + " [ unsupported ] %s (incompatible with this game or engine)\n", + " [ skipped ] %s (requires another feature)\n", + " [ unsupported ] %s (missing required gamedata entry)\n", + " [ FAILED! ] %s (failed to access engine)\n" +}; + +static inline void successbanner(void) { // called by generated code + con_colourmsg(&(struct rgba){64, 255, 64, 255}, + LONGNAME " v" VERSION " successfully loaded"); + con_colourmsg(&(struct rgba){255, 255, 255, 255}, " for game "); + con_colourmsg(&(struct rgba){0, 255, 255, 255}, "%s\n", gameinfo_title); +} + +#include <glue.gen.h> // generated by build/gluegen.c static void do_featureinit(void) { engineapi_lateinit(); @@ -297,7 +320,7 @@ static void do_featureinit(void) { "InputSystemVersion001", 0))) { errmsg_warnx("missing input system interface"); } - // ... and now for the real magic! + // ... and now for the real magic! (n.b. this also registers feature cvars) initfeatures(); // if we're autoloaded and the external autoupdate script downloaded a new @@ -374,7 +397,7 @@ static con_cmdcb orig_plugin_load_cb, orig_plugin_unload_cb; static int ownidx; // XXX: super hacky way of getting this to do_unload() static bool ispluginv1(const struct CPlugin *plugin) { - // basename string is set with strncmp(), so if there's null bytes with more + // basename string is set with strncpy(), so if there's null bytes with more // stuff after, we can't be looking at a v2 struct. and we expect null bytes // in ifacever, since it's a small int value return (plugin->v2.basename[0] == 0 || plugin->v2.basename[0] == 1) && @@ -437,6 +460,7 @@ 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 + preinitfeatures(); if (!deferinit()) { do_featureinit(); fixes_apply(); } if_hot (pluginhandler) { cmd_plugin_load = con_findcmd("plugin_load"); @@ -468,6 +492,7 @@ static void do_unload(void) { } endfeatures(); con_disconnect(); + freevars(); } static bool VCALLCONV Load(void *this, ifacefactory enginef, @@ -547,7 +572,4 @@ export const void *CreateInterface(const char *name, int *ret) { return 0; } -// no better place to put this lol -#include <evglue.gen.h> // generated by src/build/codegen.c - // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/trace.c b/src/trace.c index 0a301a7..c3e25ec 100644 --- a/src/trace.c +++ b/src/trace.c @@ -1,6 +1,6 @@ /* * Copyright © 2023 Willian Henrique <wsimanbrazil@yahoo.com.br> - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -23,12 +23,14 @@ #include "trace.h" FEATURE() +// TODO(compat): limiting to tested branches for now; support others as needed +GAMESPECIFIC(L4D) struct ray { // these have type VectorAligned in the engine, which occupies 16 bytes struct vec3f _Alignas(16) start, delta, startoff, extents; // align to 16 since "extents" is supposed to occupy 16 bytes. - // TODO(compat): this member isn't in every engine branch + // TODO(compat): this member isn't in every engine branch const float _Alignas(16) (*worldaxistransform)[3][4]; bool isray, isswept; }; @@ -85,17 +87,12 @@ struct CGameTrace trace_hull(struct vec3f start, struct vec3f end, return t; } -PREINIT { - // TODO(compat): restricting this to tested branches for now - return GAMETYPE_MATCHES(L4D); -} - INIT { - if (!(srvtrace = factory_engine("EngineTraceServer003", 0))) { + if (!(srvtrace = factory_engine("EngineTraceServer003", 0))) { errmsg_errorx("couldn't get server-side tracing interface"); - return false; + return FEAT_INCOMPAT; } - return true; + return FEAT_OK; } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/x86util.h b/src/x86util.h index 9fcdfff..214f30e 100644 --- a/src/x86util.h +++ b/src/x86util.h @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -28,7 +28,7 @@ int _len = x86_len(p); \ if_cold (_len == -1) { \ errmsg_errorx("unknown or invalid instruction looking for %s", tgt); \ - return false; \ + return 0; \ } \ (p) += _len; \ } while (0) diff --git a/src/xhair.c b/src/xhair.c index dddcdab..30fc69b 100644 --- a/src/xhair.c +++ b/src/xhair.c @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Michael Smith <mikesmiffy128@gmail.com> + * Copyright © 2025 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 @@ -14,8 +14,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ -#include <string.h> - #include "con_.h" #include "engineapi.h" #include "feature.h" @@ -25,24 +23,24 @@ #include "intdefs.h" #include "vcall.h" -FEATURE("crosshair drawing") +FEATURE("custom crosshair drawing") REQUIRE(hud) DECL_VFUNC_DYN(bool, IsInGame) -DEF_CVAR(sst_xhair, "Enable custom crosshair", 0, CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR(sst_xhair_colour, "Colour for alternative crosshair (RGBA hex)", - "FFFFFF", CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR_MIN(sst_xhair_thickness, "Thickness of custom crosshair in pixels", 2, - 1, CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR_MIN(sst_xhair_size, "Length of lines in custom crosshair in pixels", 8, - 0, CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR_MIN(sst_xhair_gap, "Gap between lines in custom crosshair in pixels", - 16, 0, CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR(sst_xhair_dot, "Whether to draw dot in middle of custom crosshair", - 1, CON_ARCHIVE | CON_HIDDEN) -DEF_CVAR(sst_xhair_outline, "Whether to draw outline around custom crosshair", - 0, CON_ARCHIVE | CON_HIDDEN) +DEF_FEAT_CVAR(sst_xhair, "Enable custom crosshair", 0, CON_ARCHIVE) +DEF_FEAT_CVAR(sst_xhair_colour, + "Colour for custom crosshair (RGBA hex)", "FFFFFF", CON_ARCHIVE) +DEF_FEAT_CVAR_MIN(sst_xhair_thickness, + "Thickness of custom crosshair in pixels", 2, 1, CON_ARCHIVE) +DEF_FEAT_CVAR_MIN(sst_xhair_size, + "Length of lines in custom crosshair in pixels", 8, 0, CON_ARCHIVE) +DEF_FEAT_CVAR_MIN(sst_xhair_gap, + "Gap between lines in custom crosshair in pixels", 16, 0, CON_ARCHIVE) +DEF_FEAT_CVAR(sst_xhair_dot, + "Whether to draw dot in middle of custom crosshair", 1, CON_ARCHIVE) +DEF_FEAT_CVAR(sst_xhair_outline, + "Whether to draw outline around custom crosshair", 0, CON_ARCHIVE) static struct rgba colour = {255, 255, 255, 255}; @@ -80,15 +78,8 @@ HANDLE_EVENT(HudPaint, void) { } INIT { - sst_xhair->base.flags &= ~CON_HIDDEN; - sst_xhair_colour->base.flags &= ~CON_HIDDEN; sst_xhair_colour->cb = &colourcb; - sst_xhair_thickness->base.flags &= ~CON_HIDDEN; - sst_xhair_size->base.flags &= ~CON_HIDDEN; - sst_xhair_gap->base.flags &= ~CON_HIDDEN; - sst_xhair_dot->base.flags &= ~CON_HIDDEN; - sst_xhair_outline->base.flags &= ~CON_HIDDEN; - return true; + return FEAT_OK; } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/test/hook.test.c b/test/hook.test.c index a2447cc..9e7cfa9 100644 --- a/test/hook.test.c +++ b/test/hook.test.c @@ -12,7 +12,7 @@ #include <stdio.h> #include <string.h> -// stubs +// stub void con_warn(const char *msg, ...) { va_list l; va_start(l, msg); @@ -20,43 +20,38 @@ void con_warn(const char *msg, ...) { va_end(l); } -__attribute__((noinline)) -static int some_function(int a, int b) { return a + b; } -static int (*orig_some_function)(int, int); -static int some_hook(int a, int b) { - return orig_some_function(a, b) + 5; -} -__attribute__((noinline)) -static int other_function(int a, int b) { return a - b; } -static int (*orig_other_function)(int, int); -static int other_hook(int a, int b) { - return orig_other_function(a, b) + 5; -} +typedef int (*testfunc)(int, int); + +__attribute__((noinline)) static int func1(int a, int b) { return a + b; } +static int (*orig_func1)(int, int); +static int hook1(int a, int b) { return orig_func1(a, b) + 5; } + +__attribute__((noinline)) static int func2(int a, int b) { return a - b; } +static int (*orig_func2)(int, int); +static int hook2(int a, int b) { return orig_func2(a, b) + 5; } TEST("Inline hooks should be able to wrap the original function") { if (!hook_init()) return false; - orig_some_function = hook_inline(&some_function, &some_hook); - if (!orig_some_function) return false; - return some_function(5, 5) == 15; + orig_func1 = (testfunc)hook_inline((void *)&func1, (void *)&hook1); + if (!orig_func1) return false; + return func1(5, 5) == 15; } TEST("Inline hooks should be removable again") { if (!hook_init()) return false; - orig_some_function = hook_inline(&some_function, &some_hook); - if (!orig_some_function) return false; - unhook_inline(orig_some_function); - return some_function(5, 5) == 10; + orig_func1 = (testfunc)hook_inline((void *)&func1, (void *)&hook1); + if (!orig_func1) return false; + unhook_inline((void *)orig_func1); + return func1(5, 5) == 10; } -TEST("Multiple functions should be able to be inline hooked at once") { +TEST("Multiple functions should be able to be inline-hooked at once") { if (!hook_init()) return false; - orig_some_function = hook_inline(&some_function, &some_hook); - if (!orig_some_function) return false; - - orig_other_function = hook_inline(&other_function, &other_hook); - if (!orig_other_function) return false; - - return other_function(5, 5) == 5; + orig_func1 = (testfunc)hook_inline((void *)&func1, (void *)&hook1); + if (!orig_func1) return false; + orig_func2 = (testfunc)hook_inline((void *)&func2, (void *)&hook2); + if (!orig_func2) return false; + return func2(5, 5) == 5; } #endif |