From 5e921bf59373d79d27c322ff86e8b5a37b151e45 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sun, 31 Jul 2022 16:02:10 +0100 Subject: Add magical feature codegen system, at long last --- TODO/.featgen | 3 + TODO/featgen | 9 -- src/ac.c | 48 +++++--- src/ac.h | 5 +- src/alias.c | 44 +++---- src/alias.h | 6 +- src/autojump.c | 20 ++-- src/autojump.h | 27 ----- src/bind.c | 5 +- src/bind.h | 4 - src/build/cmeta.c | 83 ++++++++++++- src/build/cmeta.h | 33 ++++++ src/build/codegen.c | 326 ++++++++++++++++++++++++++++++++++++++++++++++------ src/democustom.c | 13 ++- src/democustom.h | 4 - src/demorec.c | 14 +-- src/demorec.h | 5 - src/engineapi.c | 4 +- src/ent.c | 5 +- src/ent.h | 5 - src/event.h | 2 +- src/feature.h | 94 +++++++++++++++ src/fov.c | 25 ++-- src/hook.c | 2 +- src/l4dwarp.c | 18 +-- src/l4dwarp.h | 26 ----- src/nosleep.c | 27 ++--- src/nosleep.h | 27 ----- src/portalcolours.c | 14 ++- src/portalcolours.h | 27 ----- src/rinput.c | 11 +- src/rinput.h | 29 ----- src/sst.c | 70 +++-------- src/sst.h | 5 + 34 files changed, 664 insertions(+), 376 deletions(-) create mode 100644 TODO/.featgen delete mode 100644 TODO/featgen delete mode 100644 src/autojump.h create mode 100644 src/feature.h delete mode 100644 src/l4dwarp.h delete mode 100644 src/nosleep.h delete mode 100644 src/portalcolours.h delete mode 100644 src/rinput.h diff --git a/TODO/.featgen b/TODO/.featgen new file mode 100644 index 0000000..bf6dc38 --- /dev/null +++ b/TODO/.featgen @@ -0,0 +1,3 @@ +Feature setup code generation +==== +It's done! See feature.h, event.h and the various stuff in codegen.c. diff --git a/TODO/featgen b/TODO/featgen deleted file mode 100644 index b71f32a..0000000 --- a/TODO/featgen +++ /dev/null @@ -1,9 +0,0 @@ -Feature setup code generation -==== -Once the plugin has enough features across enough C files, setting up and -tearing down everything will get kind of annoying. That's where some more code -generation might be nice. - -There's a few vague ideas of how to do this but really it's pretty open-ended -overall. In the meantime, we're just calling functions manually. Not a huge -deal, really. diff --git a/src/ac.c b/src/ac.c index 2a1a8a4..4d21a48 100644 --- a/src/ac.c +++ b/src/ac.c @@ -23,14 +23,23 @@ #include "hook.h" #include "engineapi.h" #include "errmsg.h" +#include "event.h" +#include "feature.h" #include "intdefs.h" #include "mem.h" #include "os.h" #include "ppmagic.h" +#include "sst.h" #include "vcall.h" #include "x86.h" #include "x86util.h" +FEATURE() +REQUIRE(bind) +REQUIRE(democustom) +REQUIRE_GAMEDATA(vtidx_GetDesktopResolution) +REQUIRE_GAMEDATA(vtidx_DispatchAllStoredGameMessages) + static bool lockdown = false; #ifdef _WIN32 @@ -62,18 +71,10 @@ static ssize __stdcall mproc(int code, UINT_PTR wp, ssize lp) { static ulong __stdcall inhookthrmain(void *unused) { if (!SetWindowsHookExW(WH_KEYBOARD_LL, &kproc, 0, 0) || !SetWindowsHookExW(WH_MOUSE_LL, &mproc, 0, 0)) { - // intentionally vague message - con_warn("sst: RTA mode is unavailable due to an error\n"); return -1; } MSG m; int ret; while ((ret = GetMessageW(&m, inhookwin, 0, 0)) > 0) DispatchMessage(&m); - if (ret == -1) { - // XXX: if this ever happens, it's a disaster! users might not notice - // their run just dying all of a sudden. with any luck it won't matter - // in practice but... this kind of sucks. - con_warn("** sst: ERROR in message loop, abandoning RTA mode! **"); - } return ret; } @@ -92,12 +93,15 @@ static void inhook_start(void) { inhookthr = CreateThread(0, 0, &inhookthrmain, 0, 0, &inhooktid); } -// TODO(rta): run this check every tick (or at least X amount of time) static void inhook_check(void) { if (WaitForSingleObject(inhookthr, 0) == WAIT_OBJECT_0) { ulong status; GetExitCodeThread(inhookthr, &status); - if (status != 0) { + if (status) { + // XXX: if this ever happens, it's a disaster! users might not + // notice their run just dying all of a sudden. with any luck it + // won't matter in practice but... this kind of sucks. + con_warn("** sst: ERROR in message loop, abandoning RTA mode! **"); // TODO(rta): stop demos, and stuff. lockdown = false; } @@ -110,6 +114,13 @@ static void inhook_stop(void) { errmsg_warnsys("couldn't wait for thread, status unknown"); // XXX: now what!? } + // assume WAIT_OBJECT_0 + ulong status; + GetExitCodeThread(inhookthr, &status); + if (status) { + // not much else we can do now! + con_warn("warning: RTA mode message loop had an error during shutdown"); + } } #else @@ -131,6 +142,14 @@ static void startlockdown(void) { // TODO(rta): start demos, etc } +HANDLE_EVENT(Tick) { +#ifdef _WIN32 + static int fewticks = 0; + // just check this every so often (roughly 0.1-0.3s depending on game) + if (lockdown && !(++fewticks & 7)) inhook_check(); +#endif +} + static void endlockdown(void) { if (!lockdown) return; #ifdef _WIN32 @@ -227,12 +246,7 @@ ok: DispatchAllStoredGameMessages_func DispatchAllStoredGameMessages = return false; } -bool ac_init(void) { - if (!has_vtidx_GetDesktopResolution || - !has_vtidx_DispatchAllStoredGameMessages) { - errmsg_errorx("missing gamedata entries for this engine"); - return false; - } +INIT { #if defined(_WIN32) if (!win32_init()) return false; #elif defined(__linux__) @@ -248,7 +262,7 @@ bool ac_init(void) { return true; } -void ac_end(void) { +END { endlockdown(); unhook_inline((void *)orig_DispatchInputEvent); } diff --git a/src/ac.h b/src/ac.h index 638b01f..7b48cd1 100644 --- a/src/ac.h +++ b/src/ac.h @@ -17,10 +17,7 @@ #ifndef INC_AC_H #define INC_AC_H -#include - -bool ac_init(void); -void ac_end(void); +// TODO(rta): keeping this header here as I expect to expose some functions... #endif diff --git a/src/alias.c b/src/alias.c index 3605800..b94a6d5 100644 --- a/src/alias.c +++ b/src/alias.c @@ -19,16 +19,27 @@ #include "alias.h" #include "con_.h" -#include "dbg.h" #include "errmsg.h" #include "extmalloc.h" +#include "feature.h" #include "gametype.h" #include "mem.h" #include "x86.h" #include "x86util.h" +FEATURE("alias management") + struct alias **_alias_head; +void alias_nuke(void) { + for (struct alias *p = alias_head; p;) { + struct alias *next = p->next; + extfree(p->value); extfree(p); + p = next; + } + alias_head = 0; +} + void alias_rm(const char *name) { for (struct alias **p = _alias_head; *p; p = &(*p)->next) { if (!strcmp((*p)->name, name)) { @@ -40,7 +51,15 @@ void alias_rm(const char *name) { } } -DEF_CCMD_HERE(sst_alias_remove, "Remove a command alias", 0) { +DEF_CCMD_HERE_UNREG(sst_alias_clear, "Remove all command aliases", 0) { + if (cmd->argc != 1) { + con_warn("usage: sst_alias_clear"); + return; + } + alias_nuke(); +} + +DEF_CCMD_HERE_UNREG(sst_alias_remove, "Remove a command alias", 0) { if (cmd->argc != 2) { con_warn("usage: sst_alias_remove name"); return; @@ -52,23 +71,6 @@ DEF_CCMD_HERE(sst_alias_remove, "Remove a command alias", 0) { alias_rm(cmd->argv[1]); } -void alias_nuke(void) { - for (struct alias *p = alias_head; p;) { - struct alias *next = p->next; - extfree(p->value); extfree(p); - p = next; - } - alias_head = 0; -} - -DEF_CCMD_HERE(sst_alias_clear, "Remove all command aliases", 0) { - if (cmd->argc != 1) { - con_warn("usage: sst_alias_clear"); - return; - } - alias_nuke(); -} - static bool find_alias_head(con_cmdcb alias_cb) { #ifdef _WIN32 for (uchar *p = (uchar *)alias_cb; p - (uchar *)alias_cb < 64;) { @@ -88,7 +90,7 @@ static bool find_alias_head(con_cmdcb alias_cb) { return false; } -bool alias_init(void) { +INIT { // TODO(compat): no idea why sst_alias_clear crashes in p2, figure out later if (GAMETYPE_MATCHES(Portal2)) return false; @@ -101,6 +103,8 @@ bool alias_init(void) { errmsg_warnx("couldn't find alias list"); return false; }; + con_reg(sst_alias_clear); + con_reg(sst_alias_remove); return true; } diff --git a/src/alias.h b/src/alias.h index 3e417d9..bc125eb 100644 --- a/src/alias.h +++ b/src/alias.h @@ -17,8 +17,6 @@ #ifndef INC_ALIAS_H #define INC_ALIAS_H -#include - struct alias { struct alias *next; char name[32]; // TIL this has a hard limit :^) @@ -27,10 +25,8 @@ struct alias { extern struct alias **_alias_head; #define alias_head (*_alias_head) // act as a global -void alias_rm(const char *name); void alias_nuke(void); - -bool alias_init(void); +void alias_rm(const char *name); #endif diff --git a/src/autojump.c b/src/autojump.c index 284dd85..c4f54f4 100644 --- a/src/autojump.c +++ b/src/autojump.c @@ -19,6 +19,7 @@ #include "con_.h" #include "engineapi.h" #include "errmsg.h" +#include "feature.h" #include "gamedata.h" #include "gametype.h" #include "intdefs.h" @@ -27,6 +28,11 @@ #include "os.h" #include "vcall.h" +FEATURE("autojump") +REQUIRE_GAMEDATA(off_mv) +REQUIRE_GAMEDATA(vtidx_CheckJumpButton) +REQUIRE_GLOBAL(factory_client) // note: server will never be null + DEF_CVAR(sst_autojump, "Jump upon hitting the ground while holding space", 0, CON_REPLICATE | CON_DEMO | CON_HIDDEN) @@ -69,17 +75,7 @@ static bool unprot(void *gm) { return ret; } -bool autojump_init(void) { - // TODO(featgen): auto-check these factories - if (!factory_client || !factory_server) { - errmsg_errorx("missing required factories"); - return false; - } - if (!has_vtidx_CheckJumpButton || !has_off_mv) { - errmsg_errorx("missing gamedata entries for this engine"); - return false; - } - +INIT { gmsv = factory_server("GameMovement001", 0); if (!gmsv) { errmsg_errorx("couldn't get server-side game movement interface"); @@ -111,7 +107,7 @@ bool autojump_init(void) { return true; } -void autojump_end(void) { +END { unhook_vtable(*(void ***)gmsv, vtidx_CheckJumpButton, (void *)origsv); unhook_vtable(*(void ***)gmcl, vtidx_CheckJumpButton, (void *)origcl); } diff --git a/src/autojump.h b/src/autojump.h deleted file mode 100644 index 56377b7..0000000 --- a/src/autojump.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright © 2021 Michael Smith - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH - * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, - * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM - * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef INC_AUTOJUMP_H -#define INC_AUTOJUMP_H - -#include - -bool autojump_init(void); -void autojump_end(void); - -#endif - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/bind.c b/src/bind.c index 62cdec3..762259d 100644 --- a/src/bind.c +++ b/src/bind.c @@ -19,12 +19,15 @@ #include "con_.h" #include "dbg.h" #include "errmsg.h" +#include "feature.h" #include "hook.h" #include "intdefs.h" #include "mem.h" #include "x86.h" #include "x86util.h" +FEATURE() + struct keyinfo { char *binding; uchar keyuptgt : 3; @@ -53,7 +56,7 @@ static bool find_keyinfo(con_cmdcb klbc_cb) { return false; } -bool bind_init(void) { +INIT { struct con_cmd *cmd_key_listboundkeys = con_findcmd("key_listboundkeys"); if (!cmd_key_listboundkeys) { errmsg_errorx("couldn't find key_listboundkeys command"); diff --git a/src/bind.h b/src/bind.h index 4d91a96..daf97da 100644 --- a/src/bind.h +++ b/src/bind.h @@ -17,12 +17,8 @@ #ifndef INC_BIND_H #define INC_BIND_H -#include - const char *bind_get(int keycode); -bool bind_init(void); - #endif // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/cmeta.c b/src/build/cmeta.c index 3d49927..260d33f 100644 --- a/src/build/cmeta.c +++ b/src/build/cmeta.c @@ -20,6 +20,7 @@ #include "../intdefs.h" #include "../os.h" +#include "cmeta.h" /* * This file does C metadata parsing/scraping for the build system. This @@ -31,10 +32,12 @@ * time. Don't worry about it too much. */ -// too lazy to write a C tokenizer at the moment, so let's just yoink some code -// from a hacked-up copy of chibicc, a nice minimal C compiler with code that's -// pretty easy to work with. it does leak memory by design, but build stuff is -// all one-shot so that's fine. +// lazy inlined 3rd party stuff {{{ +// too lazy to write a C tokenizer at the moment, or indeed probably ever, so +// let's just yoink some code from a hacked-up copy of chibicc, a nice minimal C +// compiler with code that's pretty easy to work with. it does leak memory by +// design, but build stuff is all one-shot so that's fine. +#include "../3p/chibicc/chibicc.h" #include "../3p/chibicc/unicode.c" // type sentinels from type.c (don't bring in the rest of type.c because it // circularly depends on other stuff and we really only want tokenize here) @@ -88,6 +91,7 @@ static char *join_tokens(Token *tok, Token *end) { buf[pos] = '\0'; return buf; } +// }}} #ifdef _WIN32 #include "../3p/openbsd/asprintf.c" // missing from libc; plonked here for now @@ -266,7 +270,74 @@ void cmeta_conmacros(const struct cmeta *cm, } } -void cmeta_evdefmacros(const struct cmeta *cm, void (*cb_def)(const char *name)) { +const char *cmeta_findfeatmacro(const struct cmeta *cm) { + Token *tp = (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; + } + return 0; +} + +void cmeta_featinfomacros(const struct cmeta *cm, void (*cb)( + enum cmeta_featmacro type, const char *param, void *ctxt), void *ctxt) { + Token *tp = (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) die1("couldn't allocate memory"); + memcpy(param, tp->loc, tp->len); + param[tp->len] = '\0'; + cb(type, param, ctxt); + tp = tp->next; + } + } + tp = tp->next; + } +} + +void cmeta_evdefmacros(const struct cmeta *cm, + void (*cb_def)(const char *name)) { Token *tp = (Token *)cm; if (!tp || !tp->next || !tp->next->next) return; // DEF_EVENT, (, name while (tp) { @@ -298,4 +369,4 @@ void cmeta_evhandlermacros(const struct cmeta *cm, const char *modname, } } -// vi: sw=4 ts=4 noet tw=80 cc=80 +// 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 125ce2c..40c4ac5 100644 --- a/src/build/cmeta.h +++ b/src/build/cmeta.h @@ -39,6 +39,39 @@ void cmeta_includes(const struct cmeta *cm, void cmeta_conmacros(const struct cmeta *cm, void (*cb)(const char *name, bool isvar, bool unreg)); +/* + * 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); + +/* + * 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 +}; + +/* + * 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); + /* * Iterates through all event-related macros and takes note of which events are * defined, giving a callback for each. diff --git a/src/build/codegen.c b/src/build/codegen.c index 1dee6a1..04a9058 100644 --- a/src/build/codegen.c +++ b/src/build/codegen.c @@ -14,35 +14,37 @@ * PERFORMANCE OF THIS SOFTWARE. */ +#include #include #include #include +#include "../intdefs.h" #include "../os.h" #include "cmeta.h" #include "skiplist.h" #include "vec.h" -#define MAXENT 65536 // arbitrary limit! -static struct ent { - const char *name; - bool unreg; - bool isvar; // false for cmd -} ents[MAXENT]; -static int nents; - static void die(const char *s) { fprintf(stderr, "codegen: %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 (nents == sizeof(ents) / sizeof(*ents)) { \ + if (nconents == sizeof(conents) / sizeof(*conents)) { \ fprintf(stderr, "codegen: out of space; make ents bigger!\n"); \ exit(1); \ } \ - ents[nents].name = name_; \ - ents[nents].isvar = isvar_; ents[nents++].unreg = unreg_; \ + conents[nconents].name = name_; \ + conents[nconents].isvar = isvar_; conents[nconents++].unreg = unreg_; \ } while (0) static void oncondef(const char *name, bool isvar, bool unreg) { @@ -50,10 +52,95 @@ static void oncondef(const char *name, bool isvar, bool 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) { + // longer string first + if (!*s) return !!*p; else if (!*p) return -1; + // case insensitive sort where possible + if (tolower(*p) > tolower(*s)) return 1; + if (tolower(*p) < tolower(*s)) return -1; + // prioritise upper-case if same letter + if (isupper(*p) && islower(*s)) return 1; + if (islower(*p) && isupper(*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 (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 { + struct event { const char *name; - struct vec_str handlers; + 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) { @@ -63,7 +150,7 @@ static inline struct skiplist_hdr_event *hdr_event(struct event *e) { return &e->hdr; } DEF_SKIPLIST(static, event, cmp_event, hdr_event) -struct skiplist_hdr_event events = {0}; +static struct skiplist_hdr_event events = {0}; static void onevdef(const char *name) { struct event *e = skiplist_get_event(&events, name); @@ -71,7 +158,7 @@ static void onevdef(const char *name) { struct event *e = malloc(sizeof(*e)); if (!e) die("couldn't allocate memory"); e->name = name; - e->handlers = (struct vec_str){0}; + e->handlers = (struct vec_usize){0}; e->hdr = (struct skiplist_hdr_event){0}; skiplist_insert_event(&events, name, e); } @@ -89,24 +176,95 @@ static void onevhandler(const char *evname, const char *modname) { "non-existent event `%s`\n", modname, evname); exit(2); } + usize taggedptr = (usize)modname; + struct feature *f = skiplist_get_feature(&features, modname); + // 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, modname)) die("couldn't allocate memory"); + 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 src/build/codegen.c. DO NOT EDIT! */") \ -_( "") +#define H_() \ + _( "/* This file is autogenerated by "__FILE__". DO NOT EDIT! */") +#define H() H_() _( "") -struct passinfo { - const struct cmeta *cm; - const os_char *path; -}; -struct vec_passinfo VEC(struct passinfo) pass2 = {0}; +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) { @@ -118,8 +276,8 @@ int OS_MAIN(int argc, os_char *argv[]) { } } - // we have to a second pass for event handlers. also, there's a bunch of - // terrible garbage here. don't stare for too long... + // 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; @@ -162,18 +320,36 @@ int OS_MAIN(int argc, os_char *argv[]) { 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 ent *p = ents; p - ents < nents; ++p) { +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 ent *p = ents; p - ents < nents; ++p) { + for (const struct conent *p = conents; p - conents < nconents; ++p) { if (p->isvar) { F( " initval(%s);", p->name) } @@ -184,23 +360,103 @@ F( " con_reg(%s);", p->name) _( "}") _( "") _( "static void freevars(void) {") - for (const struct ent *p = ents; p - ents < nents; ++p) { + for (const struct conent *p = conents; p - conents < nconents; ++p) { if (p->isvar) { -F( " extfree(%s->strval);", p->name); +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(RGBA(64, 255, 64, 255),") +_( " LONGNAME \" v\" VERSION \" successfully loaded\");") +_( " con_colourmsg(RGBA(255, 255, 255, 255), \" for game \");") +_( " con_colourmsg(RGBA(0, 255, 255, 255), \"%s\\n\", gameinfo_title);") +_( " struct con_colour white = {255, 255, 255, 255};") +_( " struct con_colour green = {128, 255, 128, 255};") +_( " struct con_colour 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() + H_() for (const struct event *e = events.x[0]; e; e = e->hdr.x[0]) { +_( "") F( "void _evemit_%s(void) {", e->name) - for (const char **pp = e->handlers.data; + for (usize *pp = e->handlers.data; pp - e->handlers.data < e->handlers.sz; ++pp) { -F( " void _evhandler_%s_%s(void);", *pp, e->name); // blegh. -F( " _evhandler_%s_%s();", *pp, e->name); + const char *modname = (const char *)(*pp & ~1ull); +F( " void _evhandler_%s_%s(void);", modname, e->name) // blegh. + if (*pp & 1ull) { + // note: has_* variables are already included by this point (above) +F( " if (has_%s) _evhandler_%s_%s();", modname, modname, e->name) + } + else { +F( " _evhandler_%s_%s();", modname, e->name) + } } _( "}") } diff --git a/src/democustom.c b/src/democustom.c index 0a1174f..c7de77b 100644 --- a/src/democustom.c +++ b/src/democustom.c @@ -22,12 +22,18 @@ #include "demorec.h" #include "engineapi.h" #include "errmsg.h" +#include "feature.h" #include "gamedata.h" #include "intdefs.h" #include "mem.h" #include "ppmagic.h" #include "vcall.h" +FEATURE() +REQUIRE(demorec) +REQUIRE_GAMEDATA(vtidx_GetEngineBuildNumber) +REQUIRE_GAMEDATA(vtidx_RecordPacket) + static int nbits_msgtype, nbits_datalen; // The engine allows usermessages up to 255 bytes, we add 2 bytes of overhead, @@ -107,12 +113,7 @@ static bool find_WriteMessages(void) { DECL_VFUNC_DYN(int, GetEngineBuildNumber) -bool democustom_init(void) { - if (!has_vtidx_GetEngineBuildNumber || !has_vtidx_RecordPacket) { - errmsg_errorx("missing gamedata entries for this engine"); - return false; - } - +INIT { // More UncraftedkNowledge: // > yeah okay so [the usermessage length is] 11 bits if the demo protocol // > is 11 or if the game is l4d2 and the network protocol is 2042. diff --git a/src/democustom.h b/src/democustom.h index 9f9bbe9..a0a28b8 100644 --- a/src/democustom.h +++ b/src/democustom.h @@ -17,8 +17,6 @@ #ifndef INC_DEMOCUSTOM_H #define INC_DEMOCUSTOM_H -#include - /* maximum length of a custom demo message, in bytes */ #define DEMOCUSTOM_MSG_MAX 253 @@ -28,8 +26,6 @@ */ void democustom_write(const void *buf, int len); -bool democustom_init(void); - #endif // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/demorec.c b/src/demorec.c index 1fb5e8e..50128f0 100644 --- a/src/demorec.c +++ b/src/demorec.c @@ -21,6 +21,7 @@ #include "con_.h" #include "engineapi.h" #include "errmsg.h" +#include "feature.h" #include "gamedata.h" #include "gameinfo.h" #include "hook.h" @@ -32,6 +33,9 @@ #include "x86.h" #include "x86util.h" +FEATURE("improved demo recording") +REQUIRE_GAMEDATA(vtidx_StopRecording) + DEF_CVAR(sst_autorecord, "Continuously record demos even after reconnecting", 1, CON_ARCHIVE | CON_HIDDEN) @@ -187,7 +191,7 @@ static inline bool find_recmembers(void *stoprecording) { recording = mem_offset(demorecorder, mem_load32(p + 2)); } if (recording && demonum) return true; // blegh - NEXT_INSN(p, "state variables"); + NEXT_INSN(p, "recording state variables"); } #else // linux is probably different here idk #warning TODO(linux): implement linux equivalent (???) @@ -195,11 +199,7 @@ static inline bool find_recmembers(void *stoprecording) { return false; } -bool demorec_init(void) { - if (!has_vtidx_StopRecording) { - errmsg_errorx("missing gamedata entries for this engine"); - return false; - } +INIT { cmd_record = con_findcmd("record"); if (!cmd_record) { // can *this* even happen? I hope not! errmsg_errorx("couldn't find \"record\" command"); @@ -240,7 +240,7 @@ bool demorec_init(void) { return true; } -void demorec_end(void) { +END { // avoid dumb edge case if someone somehow records and immediately unloads if (*recording && *demonum == 0) *demonum = 1; void **vtable = *(void ***)demorecorder; diff --git a/src/demorec.h b/src/demorec.h index 7306c0a..40f8c38 100644 --- a/src/demorec.h +++ b/src/demorec.h @@ -18,14 +18,9 @@ #ifndef INC_DEMOREC_H #define INC_DEMOREC_H -#include - /* For internal use by democustom */ extern void *demorecorder; -bool demorec_init(void); -void demorec_end(void); - #endif // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/engineapi.c b/src/engineapi.c index 15c780a..cef085b 100644 --- a/src/engineapi.c +++ b/src/engineapi.c @@ -14,8 +14,8 @@ * PERFORMANCE OF THIS SOFTWARE. */ -#include // used in generated code -#include // " +#include +#include // used in generated code #include // " #include "con_.h" diff --git a/src/ent.c b/src/ent.c index 392fceb..9d69bf0 100644 --- a/src/ent.c +++ b/src/ent.c @@ -18,12 +18,15 @@ #include "engineapi.h" #include "errmsg.h" +#include "feature.h" #include "gamedata.h" #include "gametype.h" #include "intdefs.h" #include "mem.h" #include "vcall.h" +FEATURE() + DECL_VFUNC_DYN(void *, PEntityOfEntIndex, int) static struct edict **edicts = 0; @@ -44,7 +47,7 @@ void *ent_get(int idx) { return e->ent_unknown; } -bool ent_init(void) { +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; diff --git a/src/ent.h b/src/ent.h index 98657ce..049f27f 100644 --- a/src/ent.h +++ b/src/ent.h @@ -17,16 +17,11 @@ #ifndef INC_ENT_H #define INC_ENT_H -#include - #include "engineapi.h" struct edict *ent_getedict(int idx); void *ent_get(int idx); -bool ent_init(void); -void ent_end(void); - #endif // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/event.h b/src/event.h index 41965e9..e439201 100644 --- a/src/event.h +++ b/src/event.h @@ -24,7 +24,7 @@ #define DEF_EVENT(evname) \ DECL_EVENT(evname) \ static inline void _evown_##evname(void) { _evemit_##evname(); } -#define EMIT_EVENT(evname) _evown_##evname(); +#define EMIT_EVENT(evname) _evown_##evname() #define HANDLE_EVENT(evname) \ void _EVENT_CAT4(_evhandler_, MODULE_NAME, _, evname)(void) \ diff --git a/src/feature.h b/src/feature.h new file mode 100644 index 0000000..5277bca --- /dev/null +++ b/src/feature.h @@ -0,0 +1,94 @@ +/* + * Copyright © 2022 Michael Smith + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * 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_FEATURE_H +#define INC_FEATURE_H + +#include + +#define _FEATURE_CAT1(a, b) a##b +#define _FEATURE_CAT(a, b) _FEATURE_CAT1(a, b) + +/* + * 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. + */ +#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. + */ +#define REQUIRE(feature) + +/* + * 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_. + */ +#define REQUEST(featname) extern bool has_##featname; + +/* + * Indicates that the specified gamedata entry is required for this feature to + * function. If that entry is missing, this feature will not be enabled. + * + * Note that optional gamedata doesn't need to be specified here as it has no + * effect on whether this feature is loaded. It can simply be tested for using + * has_. + */ +#define REQUIRE_GAMEDATA(feature) + +/* + * 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. + */ +#define REQUIRE_GLOBAL(varname) + +/* + * 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. + * + * Features are required to specify this function. + */ +#define INIT bool _FEATURE_CAT(_feature_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... } + +/* + * 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. + */ +#define PREINIT bool _FEATURE_CAT(_feature_preinit_, MODULE_NAME)(void) // {...} + +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/fov.c b/src/fov.c index 4ea9b2d..c7c72ed 100644 --- a/src/fov.c +++ b/src/fov.c @@ -24,6 +24,7 @@ #include "errmsg.h" #include "ent.h" #include "event.h" +#include "feature.h" #include "gametype.h" #include "hook.h" #include "intdefs.h" @@ -31,8 +32,11 @@ #include "sst.h" #include "vcall.h" #include "x86.h" +#include "x86util.h" + +FEATURE("extended FOV range") +REQUEST(ent) -bool changedmax = false; DEF_CVAR_MINMAX_UNREG(fov_desired, "Set the base field of view (SST reimplementation)", 75, 75, 120, CON_HIDDEN | CON_ARCHIVE) @@ -58,13 +62,7 @@ static bool find_SetDefaultFOV(struct con_cmd *fov) { mem_loadoffset(p + 1)); return true; } - int len = x86_len(p); - if (len == -1) { - errmsg_errorx("unknown or invalid instruction looking for %s", - "SetDefaultFOV"); - return false; - } - p += len; + NEXT_INSN(p, "SetDefaultFOV"); } return false; } @@ -75,7 +73,7 @@ static void fovcb(struct con_var *v) { if (player) orig_SetDefaultFOV(player, con_getvari(v)); } -// called by sst.c in ClientActive to ensure fov is applied on load +// ensure FOV is applied on load, if the engine wouldn't do that itself HANDLE_EVENT(ClientActive) { if (real_fov_desired == fov_desired) { void *player = ent_get(1); // " @@ -85,14 +83,15 @@ HANDLE_EVENT(ClientActive) { static struct con_cmd *cmd_fov; -bool fov_init(bool has_ent) { +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...) - if (!GAMETYPE_MATCHES(Portal1)) { return false; } + return GAMETYPE_MATCHES(Portal1); +} +INIT { cmd_fov = con_findcmd("fov"); if (!cmd_fov) return false; // shouldn't really happen but just in case! - 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; @@ -122,7 +121,7 @@ bool fov_init(bool has_ent) { return true; } -void fov_end(void) { +END { if (real_fov_desired && real_fov_desired != fov_desired) { real_fov_desired->parent->maxval = 90; if (con_getvarf(real_fov_desired) > 90) { diff --git a/src/hook.c b/src/hook.c index 8465fbe..eacac4a 100644 --- a/src/hook.c +++ b/src/hook.c @@ -57,7 +57,7 @@ void *hook_inline(void *func_, void *target) { // FIXME: these cases may result in somewhat dodgy error messaging. They // shouldn't happen anyway though. Maybe if we're confident we just // compile 'em out of release builds some day, but that sounds a little - // scary. For now prefering confusing messages over crashes, I guess. + // scary. For now preferring confusing messages over crashes, I guess. if (func[len] == X86_CALL) { con_warn("hook_inline: can't trampoline call instructions\n"); return 0; diff --git a/src/l4dwarp.c b/src/l4dwarp.c index 76aa230..5412b64 100644 --- a/src/l4dwarp.c +++ b/src/l4dwarp.c @@ -16,18 +16,23 @@ #define _USE_MATH_DEFINES // ... windows. #include -#include #include "con_.h" #include "engineapi.h" #include "errmsg.h" #include "ent.h" +#include "feature.h" #include "gamedata.h" #include "gametype.h" #include "intdefs.h" #include "mem.h" #include "vcall.h" +FEATURE("Left 4 Dead warp testing") +REQUIRE_GAMEDATA(off_entpos) +REQUIRE_GAMEDATA(off_eyeang) +REQUIRE_GAMEDATA(vtidx_Teleport) + DECL_VFUNC_DYN(void *, GetBaseEntity) DECL_VFUNC_DYN(void, Teleport, const struct vec3f *pos, const struct vec3f *ang, const struct vec3f *vel) @@ -48,12 +53,11 @@ DEF_CCMD_HERE_UNREG(sst_l4d_testwarp, "Simulate a bot warping to you", org->y + shift * sin(yaw), org->z}, 0, &(struct vec3f){0, 0, 0}); } -bool l4dwarp_init(void) { - if (!GAMETYPE_MATCHES(L4Dx)) return false; - if (!has_off_entpos || !has_off_eyeang || !has_vtidx_Teleport) { - errmsg_errorx("missing gamedata entries for this engine"); - return false; - } +PREINIT { + return GAMETYPE_MATCHES(L4Dx); +} + +INIT { con_reg(sst_l4d_testwarp); return true; } diff --git a/src/l4dwarp.h b/src/l4dwarp.h deleted file mode 100644 index 5328fcf..0000000 --- a/src/l4dwarp.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright © 2022 Michael Smith - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * 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_L4DWARP_H -#define INC_L4DWARP_H - -#include - -bool l4dwarp_init(void); - -#endif - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/nosleep.c b/src/nosleep.c index 5618ba9..07a5500 100644 --- a/src/nosleep.c +++ b/src/nosleep.c @@ -14,16 +14,19 @@ * PERFORMANCE OF THIS SOFTWARE. */ -#include - #include "con_.h" #include "engineapi.h" #include "errmsg.h" +#include "feature.h" #include "gamedata.h" #include "hook.h" #include "os.h" #include "vcall.h" +FEATURE("inactive window sleep adjustment") +REQUIRE_GAMEDATA(vtidx_SleepUntilInput) +REQUIRE_GLOBAL(factory_inputsystem) + DEF_CVAR_UNREG(engine_no_focus_sleep, "Delay while tabbed out (SST reimplementation)", 50, CON_ARCHIVE | CON_HIDDEN) @@ -36,19 +39,13 @@ static void VCALLCONV hook_SleepUntilInput(void *this, int timeout) { orig_SleepUntilInput(this, con_getvari(engine_no_focus_sleep)); } -bool nosleep_init(void) { - struct con_var *v = con_findvar("engine_no_focus_sleep"); - if (v) return false; // no need! +PREINIT { + if (con_findvar("engine_no_focus_sleep")) return false; con_reg(engine_no_focus_sleep); - // TODO(featgen): auto-check these factories - if (!factory_inputsystem) { - errmsg_errorx("missing required factories"); - return false; - } - if (!has_vtidx_SleepUntilInput) { - errmsg_errorx("missing gamedata entries for this engine"); - return false; - } + return true; +} + +INIT { void *insys = factory_inputsystem("InputSystemVersion001", 0); if (!insys) { errmsg_errorx("couldn't get input system interface"); @@ -66,7 +63,7 @@ bool nosleep_init(void) { return true; } -void nosleep_end(void) { +END { unhook_vtable(vtable, vtidx_SleepUntilInput, (void *)orig_SleepUntilInput); } diff --git a/src/nosleep.h b/src/nosleep.h deleted file mode 100644 index 4d5237f..0000000 --- a/src/nosleep.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright © 2022 Michael Smith - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * 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_NOSLEEP_H -#define INC_NOSLEEP_H - -#include - -bool nosleep_init(void); -void nosleep_end(void); - -#endif - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/portalcolours.c b/src/portalcolours.c index 750ee19..231aa01 100644 --- a/src/portalcolours.c +++ b/src/portalcolours.c @@ -21,13 +21,18 @@ #include "engineapi.h" #include "errmsg.h" #include "gametype.h" +#include "feature.h" #include "hook.h" #include "intdefs.h" #include "mem.h" #include "os.h" #include "ppmagic.h" +#include "sst.h" #include "vcall.h" +FEATURE("portal gun colour customisation") +REQUIRE_GLOBAL(clientlib) + // It's like the thing Portal Tools does, but at runtime! DEF_CVAR(sst_portal_colour0, "Crosshair colour for gravity beam (hex)", @@ -119,8 +124,11 @@ static bool find_UTIL_Portal_Color(void *base) { return false; } -bool portalcolours_init(void *clientlib) { // ... should libs be globals? - if (!GAMETYPE_MATCHES(Portal)) return false; +PREINIT { + return GAMETYPE_MATCHES(Portal1); +} + +INIT { #ifdef _WIN32 if (!find_UTIL_Portal_Color(clientlib)) { errmsg_errorx("couldn't find UTIL_Portal_Color"); @@ -145,7 +153,7 @@ bool portalcolours_init(void *clientlib) { // ... should libs be globals? #endif } -void portalcolours_end(void) { +END { unhook_inline((void *)orig_UTIL_Portal_Color); } diff --git a/src/portalcolours.h b/src/portalcolours.h deleted file mode 100644 index 2dd4bb5..0000000 --- a/src/portalcolours.h +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright © 2022 Michael Smith - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * 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_PORTALCOLOURS_H -#define INC_PORTALCOLOURS_H - -#include - -bool portalcolours_init(void *clientlib); -void portalcolours_end(); - -#endif - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/rinput.c b/src/rinput.c index 1b81cf5..a7ad8d8 100644 --- a/src/rinput.c +++ b/src/rinput.c @@ -17,14 +17,16 @@ // NOTE: compiled on Windows only. All Linux Source releases are new enough to // have raw input already. -#include #include #include "con_.h" #include "hook.h" #include "errmsg.h" +#include "feature.h" #include "intdefs.h" +FEATURE("raw mouse input") + // We reimplement m_rawinput by hooking cursor functions in the same way as // RInput (it's way easier than replacing all the mouse-handling internals of // the actual engine). We also take the same window class it does in order to @@ -76,11 +78,14 @@ static int __stdcall hook_SetCursorPos(int x, int y) { return orig_SetCursorPos(x, y); } -bool rinput_init(void) { +PREINIT { if (con_findvar("m_rawinput")) return false; // no need! // create cvar hidden so if we fail to init, setting can still be preserved con_reg(m_rawinput); + return true; +} +INIT { WNDCLASSEXW wc = { .cbSize = sizeof(wc), // cast because inproc is binary-compatible but doesn't use stupid @@ -139,7 +144,7 @@ e0: UnregisterClassW(L"RInput", 0); return false; } -void rinput_end(void) { +END { RAWINPUTDEVICE rd = { .dwFlags = RIDEV_REMOVE, .hwndTarget = 0, diff --git a/src/rinput.h b/src/rinput.h deleted file mode 100644 index e89a363..0000000 --- a/src/rinput.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright © 2022 Michael Smith - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * 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_RINPUT_H -#define INC_RINPUT_H -#ifdef _WIN32 - -#include - -bool rinput_init(void); -void rinput_end(void); - -#endif -#endif - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/sst.c b/src/sst.c index d2576f6..c959d65 100644 --- a/src/sst.c +++ b/src/sst.c @@ -22,26 +22,15 @@ #endif #include "ac.h" -#include "bind.h" -#include "alias.h" -#include "autojump.h" #include "con_.h" -#include "democustom.h" -#include "demorec.h" #include "engineapi.h" #include "errmsg.h" -#include "ent.h" #include "event.h" -#include "fov.h" #include "fixes.h" #include "gameinfo.h" #include "gametype.h" #include "hook.h" -#include "l4dwarp.h" -#include "nosleep.h" -#include "portalcolours.h" #include "os.h" -#include "rinput.h" #include "vcall.h" #include "version.h" @@ -53,8 +42,9 @@ static int ifacever; -// we need to keep this reference to dlclose() it later - see below -static void *clientlib = 0; +// XXX: exposing this clumsily to portalcolours. we should have a better way of +// exposing lib handles in general, probably. +void *clientlib = 0; #ifdef _WIN32 extern long __ImageBase; // this is actually the PE header struct but don't care @@ -189,16 +179,6 @@ static void VCALLCONV SetCommandClient(void *this, int i) { con_cmdclient = i; } static const void **vtable_firstdiff; static const void *const *const plugin_obj; -// TODO(featgen): I wanted some nice fancy automatic feature system that -// figures out the dependencies at build time and generates all the init glue -// but we want to actually release the plugin this decade so for now I'm just -// plonking some bools here and worrying about it later. :^) -static bool has_ac = false, has_autojump = false, has_demorec = false, - has_fov = false, has_nosleep = false, has_portalcolours = false; -#ifdef _WIN32 -static bool has_rinput = false; -#endif - static bool already_loaded = false, skip_unload = false; #define RGBA(r, g, b, a) (&(struct con_colour){(r), (g), (b), (a)}) @@ -208,28 +188,12 @@ static const char *updatenotes = "\ * various internal cleanup\n\ "; +#include + static void do_featureinit(void) { - bool has_bind = bind_init(); - if (has_bind) has_ac = ac_init(); - alias_init(); - has_autojump = autojump_init(); - has_demorec = demorec_init(); - if (has_demorec) democustom_init(); - bool has_ent = ent_init(); - has_fov = fov_init(has_ent); - if (has_ent) l4dwarp_init(); - has_nosleep = nosleep_init(); - if (clientlib) has_portalcolours = portalcolours_init(clientlib); -#ifdef _WIN32 - has_rinput = rinput_init(); -#endif + initfeatures(); fixes_apply(); - con_colourmsg(RGBA(64, 255, 64, 255), - LONGNAME " v" VERSION " successfully loaded"); - con_colourmsg(RGBA(255, 255, 255, 255), " for game "); - con_colourmsg(RGBA(0, 255, 255, 255), "%s\n", gameinfo_title); - // if we're autoloaded and the external autoupdate script downloaded a new // version, let the user know about the cool new stuff! if (getenv("SST_UPDATED")) { @@ -416,16 +380,7 @@ static void do_unload(void) { } #endif - if (has_ac) ac_end(); - if (has_autojump) autojump_end(); - if (has_demorec) demorec_end(); - if (has_fov) fov_end(); // dep on ent - if (has_nosleep) nosleep_end(); - if (has_portalcolours) portalcolours_end(); -#ifdef _WIN32 - if (has_rinput) rinput_end(); -#endif - + endfeatures(); #ifdef __linux__ if (clientlib) dlclose(clientlib); #endif @@ -475,12 +430,19 @@ DEF_CVAR(_sst_onload_echo, "EXPERIMENTAL! Don't rely on this existing!", "", CON_HIDDEN) DEF_EVENT(ClientActive) +DEF_EVENT(Tick) + +// Quick and easy server tick event. Eventually, we might want a deeper hook +// for anything timing-sensitive, but this will do for our current needs. +static void VCALLCONV GameFrame(void *this, bool simulating) { + EMIT_EVENT(Tick); +} static void VCALLCONV ClientActive(void *this, struct edict *player) { // XXX: it's kind of dumb that we get handed the edict here then go look it // up again in fov.c but I can't be bothered refactoring any further now // that this finally works, do something later lol - EMIT_EVENT(ClientActive) + EMIT_EVENT(ClientActive); // continuing dumb portal hack. didn't even seem worth adding a feature for if (has_vtidx_ServerCommand && con_getvarstr(_sst_onload_echo)[0]) { @@ -510,7 +472,7 @@ static const void *vtable[MAX_VTABLE_FUNCS] = { (void *)&GetPluginDescription, (void *)&nop_p_v, // LevelInit (void *)&nop_pii_v, // ServerActivate - (void *)&nop_b_v, // GameFrame + (void *)&GameFrame, // GameFrame (void *)&nop_v_v, // LevelShutdown (void *)&ClientActive // At this point, Alien Swarm and Portal 2 add ClientFullyConnect, so we diff --git a/src/sst.h b/src/sst.h index 247346b..d432480 100644 --- a/src/sst.h +++ b/src/sst.h @@ -19,7 +19,12 @@ #include "event.h" +/* misc stuff that doesn't belong anywhere else */ + DECL_EVENT(ClientActive) +DECL_EVENT(Tick) + +extern void *clientlib; #endif -- cgit v1.2.3