From 1a5c55eb89c22e8822ec057a3731a6d753f13859 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sat, 30 Apr 2022 00:23:31 +0100 Subject: Centralise engine access, add Portal FOV changer - A bunch of stuff is now defined in one header, engineapi.h - engineapi.c is responsible for setting up any interfaces/stuff that's used in more than one place - mkgamedata is pretty much rewritten and now supports nested conditionals - gamedata variables no longer have the gamedata_ prefix because it was just annoyingly long all the time - vcall macros are somewhat revamped and support dynamic (gamedata) indices - Portal 1 FOV can be set anywhere from 75-120 using fov_desired - tested in both the main versions currently used by runners - A few typos were also fixed ("intput," "writeable," "indexes") --- compile | 6 +- compile.bat | 10 +- gamedata/engine.kv | 10 ++ src/autojump.c | 47 +++------ src/build/mkgamedata.c | 258 ++++++++++++++++++++++++------------------------- src/build/vec.h | 96 ++++++++++++++++++ src/con_.c | 38 ++++---- src/con_.h | 8 +- src/demorec.c | 28 +++--- src/engineapi.c | 50 ++++++++++ src/engineapi.h | 101 +++++++++++++++++++ src/ent.c | 49 ++++++++++ src/ent.h | 29 ++++++ src/factory.h | 14 --- src/fixes.c | 1 - src/fixes.h | 2 +- src/fov.c | 136 ++++++++++++++++++++++++++ src/fov.h | 33 +++++++ src/gameinfo.c | 37 ++----- src/gameinfo.h | 2 +- src/gametype.h | 23 +++-- src/nosleep.c | 19 ++-- src/sst.c | 68 ++++++------- src/vcall.h | 36 ++++--- 24 files changed, 783 insertions(+), 318 deletions(-) create mode 100644 src/build/vec.h create mode 100644 src/engineapi.c create mode 100644 src/engineapi.h create mode 100644 src/ent.c create mode 100644 src/ent.h delete mode 100644 src/factory.h create mode 100644 src/fov.c create mode 100644 src/fov.h diff --git a/compile b/compile index 76dc638..64523b3 100755 --- a/compile +++ b/compile @@ -13,7 +13,8 @@ mkdir -p .build/include : "${CC:=clang --target=-i686-pc-linux-gnu -fuse-ld=lld}" : "${HOSTCC:=clang -fuse-ld=lld}" -warnings="-Wall -pedantic -Wno-parentheses -Wno-missing-braces" +warnings="-Wall -pedantic -Wno-parentheses -Wno-missing-braces \ +-Wno-gnu-zero-variadic-macro-arguments" dbg=0 if [ "$dbg" = 1 ]; then @@ -41,8 +42,11 @@ src="\ autojump.c con_.c demorec.c + engineapi.c + ent.c extmalloc.c fixes.c + fov.c gamedata.c gameinfo.c hook.c diff --git a/compile.bat b/compile.bat index 1364b01..005ce63 100644 --- a/compile.bat +++ b/compile.bat @@ -13,7 +13,8 @@ if not exist .build\include\ md .build\include if "%CC%"=="" set CC=clang --target=i686-pc-windows-msvc -fuse-ld=lld if "%HOSTCC%"=="" set HOSTCC=clang -fuse-ld=lld -set warnings=-Wall -pedantic -Wno-parentheses -Wno-missing-braces +set warnings=-Wall -pedantic -Wno-parentheses -Wno-missing-braces ^ +-Wno-gnu-zero-variadic-macro-arguments set dbg=0 if "%dbg%"=="1" ( @@ -39,8 +40,8 @@ goto :eof -o .build/codegen.exe src/build/codegen.c src/build/cmeta.c || exit /b %HOSTCC% -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -ladvapi32 ^ -o .build/mkgamedata.exe src/build/mkgamedata.c src/kv.c || exit /b -.build\codegen.exe src/autojump.c src/con_.c src/demorec.c src/extmalloc.c src/fixes.c src/gamedata.c ^ -src/gameinfo.c src/hook.c src/kv.c src/nosleep.c src/rinput.c src/sst.c src/x86.c || exit /b +.build\codegen.exe src/autojump.c src/con_.c src/demorec.c src/engineapi.c src/ent.c src/extmalloc.c src/fixes.c ^ +src/fov.c src/gamedata.c src/gameinfo.c src/hook.c src/kv.c src/nosleep.c src/rinput.c src/sst.c src/x86.c || exit /b .build\mkgamedata.exe gamedata/engine.kv gamedata/gamelib.kv gamedata/inputsystem.kv || exit /b llvm-rc /FO .build\dll.res src\dll.rc || exit /b %CC% -shared -O0 -w -o .build/tier0.dll src/stubs/tier0.c @@ -48,8 +49,11 @@ llvm-rc /FO .build\dll.res src\dll.rc || exit /b call :cc src/autojump.c || exit /b call :cc src/con_.c || exit /b call :cc src/demorec.c || exit /b +call :cc src/engineapi.c || exit /b +call :cc src/ent.c || exit /b call :cc src/extmalloc.c || exit /b call :cc src/fixes.c || exit /b +call :cc src/fov.c || exit /b call :cc src/gamedata.c || exit /b call :cc src/gameinfo.c || exit /b call :cc src/hook.c || exit /b diff --git a/gamedata/engine.kv b/gamedata/engine.kv index 81a0545..a43d25c 100644 --- a/gamedata/engine.kv +++ b/gamedata/engine.kv @@ -6,6 +6,16 @@ vtidx_StopRecording 7 vtidx_RecordPacket 11 // VEngineClient +vtidx_GetGameDirectory { + Client015 35 // current portal 2 + Client014 { L4D2 73 } // YES IT'S SEVENTY THREE ALL OF A SUDDEN. + Client013 { + L4Dx 36 // AND THEN THEY CHANGED IT BACK LATER! + default 35 // <- most things have this! + } + Client012 37 // dmomm, ep1, ... +} vtidx_GetEngineBuildNumber { L4D2 99 } +vtidx_PEntityOfEntIndex { OrangeBox 19 } // probably OE too but??? // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/autojump.c b/src/autojump.c index a8d7ee2..9d4e513 100644 --- a/src/autojump.c +++ b/src/autojump.c @@ -17,7 +17,7 @@ #include #include "con_.h" -#include "factory.h" +#include "engineapi.h" #include "gamedata.h" #include "intdefs.h" #include "hook.h" @@ -28,34 +28,17 @@ DEF_CVAR(sst_autojump, "Jump upon hitting the ground while holding space", 0, CON_REPLICATE | CON_DEMO | CON_HIDDEN) -struct vec3f { float x, y, z; }; -struct CMoveData { - bool firstrun : 1, gamecodemoved : 1; - ulong playerhandle; - int impulse; - struct vec3f viewangles, absviewangles; - int buttons, oldbuttons; - float mv_forward, mv_side, mv_up; - float maxspeed, clmaxspeed; - struct vec3f vel, angles, oldangles; - float out_stepheight; - struct vec3f out_wishvel, out_jumpvel; - struct vec3f constraint_centre; - float constraint_radius, constraint_width, constraint_speedfactor; - struct vec3f origin; -}; - #define IN_JUMP 2 #define NIDX 256 // *completely* arbitrary lol static bool justjumped[NIDX] = {0}; static inline int handleidx(ulong h) { return h & (1 << 11) - 1; } static void *gmsv = 0, *gmcl = 0; -typedef bool (VCALLCONV *CheckJumpButton_f)(void *); -static CheckJumpButton_f origsv, origcl; +typedef bool (*VCALLCONV CheckJumpButton_func)(void *); +static CheckJumpButton_func origsv, origcl; static bool VCALLCONV hook(void *this) { - struct CMoveData **mvp = mem_offset(this, gamedata_off_mv), *mv = *mvp; + struct CMoveData **mvp = mem_offset(this, off_mv), *mv = *mvp; // use 0 idx for client side, as server indices start at 1 // FIXME: does this account for splitscreen??? int i = this == gmsv ? handleidx(mv->playerhandle) : 0; @@ -65,9 +48,9 @@ static bool VCALLCONV hook(void *this) { static bool unprot(void *gm) { void **vtable = *(void ***)gm; - bool ret = os_mprot(vtable + gamedata_vtidx_CheckJumpButton, - sizeof(void *), PAGE_EXECUTE_READWRITE); - if (!ret) con_warn("autojump: couldn't make memory writeable\n"); + bool ret = os_mprot(vtable + vtidx_CheckJumpButton, sizeof(void *), + PAGE_EXECUTE_READWRITE); + if (!ret) con_warn("autojump: couldn't make memory writable\n"); return ret; } @@ -77,7 +60,7 @@ bool autojump_init(void) { con_warn("autojump: missing required factories\n"); return false; } - if (!gamedata_has_vtidx_CheckJumpButton || !gamedata_has_off_mv) { + if (!has_vtidx_CheckJumpButton || !has_off_mv) { con_warn("autojump: missing gamedata entries for this engine\n"); return false; } @@ -94,20 +77,18 @@ bool autojump_init(void) { return false; } if (!unprot(gmcl)) return false; - origsv = (CheckJumpButton_f)hook_vtable(*(void ***)gmsv, - gamedata_vtidx_CheckJumpButton, (void *)&hook); - origcl = (CheckJumpButton_f)hook_vtable(*(void ***)gmcl, - gamedata_vtidx_CheckJumpButton, (void *)&hook); + origsv = (CheckJumpButton_func)hook_vtable(*(void ***)gmsv, + vtidx_CheckJumpButton, (void *)&hook); + origcl = (CheckJumpButton_func)hook_vtable(*(void ***)gmcl, + vtidx_CheckJumpButton, (void *)&hook); sst_autojump->base.flags &= ~CON_HIDDEN; return true; } void autojump_end(void) { - unhook_vtable(*(void ***)gmsv, gamedata_vtidx_CheckJumpButton, - (void *)origsv); - unhook_vtable(*(void ***)gmcl, gamedata_vtidx_CheckJumpButton, - (void *)origcl); + unhook_vtable(*(void ***)gmsv, vtidx_CheckJumpButton, (void *)origsv); + unhook_vtable(*(void ***)gmcl, vtidx_CheckJumpButton, (void *)origcl); } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/mkgamedata.c b/src/build/mkgamedata.c index c5f6820..d17d7df 100644 --- a/src/build/mkgamedata.c +++ b/src/build/mkgamedata.c @@ -14,6 +14,7 @@ * PERFORMANCE OF THIS SOFTWARE. */ +#include #include #include #include @@ -22,6 +23,7 @@ #include "../kv.h" #include "../noreturn.h" #include "../os.h" +#include "vec.h" #ifdef _WIN32 #define fS "S" @@ -35,10 +37,18 @@ static noreturn die(const char *s) { } /* - * We keep the gamedata KV format as simple and flat as possible: + * We keep the gamedata KV format as simple as possible. Default values are + * specified as direct key-value pairs: * * + * + * Game- or engine-specific values are set using blocks: + * * { ... [default ] } + * + * The most complicated it can get is if conditionals are nested, which + * basically translates directly into nested ifs: + * { { } } * [however many entries...] * * If that doesn't make sense, just look at one of the existing data files and @@ -47,35 +57,21 @@ static noreturn die(const char *s) { * Note: if `default` isn't given in a conditional block, that piece of gamedata * is considered unavailable and modules that use it won't get initialised/used. */ +struct vec_ent VEC(struct ent *); struct ent { - const char *name; - // normally I'd be inclined to do some pointer bitpacking meme but that's - // annoying and doesn't matter here so here's an bool and 7 bytes of padding - bool iscond; - union { - struct { - struct ent_cond { - const char *name; - // note: can be any old C expression; just plopped in - const char *expr; - struct ent_cond *next; - } *cond; - // store user-specified defaults in a special place to make the - // actual codegen logic easier - const char *defval; - }; - const char *expr; - }; - struct ent *next; -} *ents_head, **ents_tail = &ents_head; -const char **curdefval; // dumb hacky afterthought, woopsy + const char *name; // (or condition tag, in a child node) + const char *defexpr; + struct vec_ent subents; + struct ent *parent; // to back up a level during parse +}; +// root only contains subents list but it's easier to use the same struct +static struct ent root = {0}; struct parsestate { const os_char *filename; struct kv_parser *parser; - const char *lastkey; - struct ent_cond **nextcond; - bool incond; + struct ent *curent; // current ent lol + bool haddefault; // blegh; }; static noreturn badparse(struct parsestate *state, const char *e) { @@ -88,67 +84,52 @@ static void kv_cb(enum kv_token type, const char *p, uint len, void *ctxt) { struct parsestate *state = ctxt; switch (type) { case KV_IDENT: case KV_IDENT_QUOTED:; + if (len == 7 && !memcmp(p, "default", 7)) { // special case! + if (state->curent == &root) { + badparse(state, "unexpected default keyword at top level"); + } + struct ent *e = state->curent; + if (e->defexpr) { + badparse(state, "multiple default keywords"); + } + state->haddefault = true; + break; + } + state->haddefault = false; char *k = malloc(len + 1); if (!k) die("couldn't allocate key string"); - memcpy(k, p, len); - k[len] = '\0'; - state->lastkey = k; - break; - case KV_NEST_START: - if (state->incond) badparse(state, "unexpected nested object"); - state->incond = true; + // FIXME(?): should check and prevent duplicate keys probably! + // need table.h or something to avoid O(n^2) :) + memcpy(k, p, len); k[len] = '\0'; struct ent *e = malloc(sizeof(*e)); if (!e) die("couldn't allocate memory"); - e->name = state->lastkey; - e->iscond = true; - e->cond = 0; - e->defval = 0; - state->nextcond = &e->cond; - e->next = 0; - *ents_tail = e; - ents_tail = &e->next; - curdefval = &e->defval; // dumb hacky afterthought part 2 + e->name = k; + e->defexpr = 0; + e->subents = (struct vec_ent){0}; + if (!vec_push(&state->curent->subents, e)) { + die("couldn't append to array"); + } + e->parent = state->curent; + state->curent = e; + break; + case KV_NEST_START: + if (state->haddefault) badparse(state, "default cannot be a block"); break; case KV_NEST_END: - state->incond = false; + if (!state->curent->parent) { + badparse(state, "unexpected closing brace"); + } + state->curent = state->curent->parent; break; case KV_VAL: case KV_VAL_QUOTED: - if (state->incond) { - // continuation of dumb hackiness - if (!strcmp(state->lastkey, "default")) { - char *s = malloc(len + 1); - if (!s) die("couldn't allocate default value string"); - memcpy(s, p, len); - s[len] = '\0'; - *curdefval = s; - break; - } - struct ent_cond *c = malloc(sizeof(*c)); - if (!c) die("couldn't allocate memory"); - c->name = state->lastkey; - char *expr = malloc(len + 1); - if (!expr) die("couldn't allocate value/expression string"); - memcpy(expr, p, len); - expr[len] = '\0'; - c->expr = expr; - c->next = 0; - *(state->nextcond) = c; - state->nextcond = &c->next; - } - else { - // also kind of dumb but whatever - struct ent *e = malloc(sizeof(*e)); - if (!e) die("couldn't allocate memory"); - e->name = state->lastkey; - e->iscond = false; - char *expr = malloc(len + 1); - if (!expr) die("couldn't allocate value/expression string"); - memcpy(expr, p, len); - expr[len] = '\0'; - e->expr = expr; - e->next = 0; - *ents_tail = e; - ents_tail = &e->next; + char *s = malloc(len + 1); + if (!s) die("couldn't allocate value string"); + memcpy(s, p, len); s[len] = '\0'; + state->curent->defexpr = s; + if (!state->haddefault) { + // a non-default value is just a node that itself only has a + // default value. + state->curent = state->curent->parent; } break; case KV_COND_PREFIX: case KV_COND_SUFFIX: @@ -156,20 +137,85 @@ static void kv_cb(enum kv_token type, const char *p, uint len, void *ctxt) { } } +static inline noreturn diewrite(void) { die("couldn't write to file"); } + +#define _doindent \ + for (int _indent = 0; _indent < indent; ++_indent) { \ + if (fputs("\t", out) == -1) diewrite(); \ + } #define _(x) \ - if (fprintf(out, "%s\n", x) < 0) die("couldn't write to file"); + if (fprintf(out, "%s\n", x) < 0) diewrite(); +#define _i(x) _doindent _(x) #define F(f, ...) \ - if (fprintf(out, f "\n", __VA_ARGS__) < 0) die("couldn't write to file"); + if (fprintf(out, f "\n", __VA_ARGS__) < 0) diewrite(); +#define Fi(...) _doindent F(__VA_ARGS__) #define H() \ _( "/* This file is autogenerated by src/build/mkgamedata.c. DO NOT EDIT! */") \ _( "") +static void decls(FILE *out) { + for (struct ent *const *pp = root.subents.data; + pp - root.subents.data < root.subents.sz; ++pp) { + if ((*pp)->defexpr) { +F( "#define has_%s true", (*pp)->name) + if ((*pp)->subents.sz) { +F( "extern int %s;", (*pp)->name) + } + else { +F( "enum { %s = %s };", (*pp)->name, (*pp)->defexpr) + } + } + else { +F( "extern bool has_%s;", (*pp)->name) +F( "extern int %s;", (*pp)->name) + } + } +} + +static void inits(FILE *out, const char *var, struct vec_ent *v, bool needhas, + int indent) { + for (struct ent *const *pp = v->data; pp - v->data < v->sz; ++pp) { +Fi("if (GAMETYPE_MATCHES(%s)) {", (*pp)->name) + bool has = needhas && (*pp)->defexpr; + if (has) { +Fi(" has_%s = true;", var); + } + if ((*pp)->defexpr) { +Fi(" %s = %s;", var, (*pp)->defexpr); + } + inits(out, var, &(*pp)->subents, !has, indent + 1); +_i("}") + } +} + +static void defs(FILE *out) { + for (struct ent *const *pp = root.subents.data; + pp - root.subents.data < root.subents.sz; ++pp) { + if ((*pp)->defexpr) { + if ((*pp)->subents.sz) { +F( "int %s = %s;", (*pp)->name, (*pp)->defexpr); + } + } + else { +F( "int %s;", (*pp)->name); +F( "bool has_%s = false;", (*pp)->name); + } + } +_( "") +_( "void gamedata_init(void) {") + for (struct ent *const *pp = root.subents.data; + pp - root.subents.data < root.subents.sz; ++pp) { + inits(out, (*pp)->name, &(*pp)->subents, !(*pp)->defexpr, 1); + } +_( "}") +} + int OS_MAIN(int argc, os_char *argv[]) { for (++argv; *argv; ++argv) { int fd = os_open(*argv, O_RDONLY); if (fd == -1) die("couldn't open file"); struct kv_parser kv = {0}; - struct parsestate state = {*argv, &kv}; + struct parsestate state = {*argv, &kv, &root}; char buf[1024]; int nread; while (nread = read(fd, buf, sizeof(buf))) { @@ -187,58 +233,12 @@ ep: fprintf(stderr, "mkgamedata: %" fS ":%d:%d: bad syntax: %s\n", FILE *out = fopen(".build/include/gamedata.gen.h", "wb"); if (!out) die("couldn't open gamedata.gen.h"); H(); - for (struct ent *e = ents_head; e; e = e->next) { - if (e->iscond) { -F( "extern int gamedata_%s;", e->name) - if (e->defval) { -F( "#define gamedata_has_%s true", e->name) - } - else { -F( "extern bool gamedata_has_%s;", e->name) - } - } - else { -F( "enum { gamedata_%s = %s };", e->name, e->expr) -F( "#define gamedata_has_%s true", e->name) - } - } + decls(out); out = fopen(".build/include/gamedatainit.gen.h", "wb"); if (!out) die("couldn't open gamedatainit.gen.h"); H(); - for (struct ent *e = ents_head; e; e = e->next) { - if (e->iscond) { - if (e->defval) { -F( "int gamedata_%s = %s;", e->name, e->defval); - } - else { -F( "int gamedata_%s;", e->name); -F( "bool gamedata_has_%s = false;", e->name); - } - } - } -_( "") -_( "void gamedata_init(void) {") - for (struct ent *e = ents_head; e; e = e->next) { - if (e->iscond) { - for (struct ent_cond *c = e->cond; c; c = c->next) { - if (e->defval) { -F( " if (GAMETYPE_MATCHES(%s)) gamedata_%s = %s;", c->name, e->name, c->expr) - } - else { - // XXX: not bothering to generate `else`s. technically this - // has different semantics; we hope that the compiler can - // just do the right thing either way. -F( " if (GAMETYPE_MATCHES(%s)) {", c->name) -F( " gamedata_%s = %s;", e->name, c->expr) -F( " gamedata_has_%s = true;", e->name) -_( " }") - } - } - } - } -_( "}") - + defs(out); return 0; } diff --git a/src/build/vec.h b/src/build/vec.h new file mode 100644 index 0000000..50b0a3b --- /dev/null +++ b/src/build/vec.h @@ -0,0 +1,96 @@ +/* This file is dedicated to the public domain. */ + +#ifndef INC_VEC_H +#define INC_VEC_H + +#include +#include +#include + +#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)) && \ + ((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/con_.c b/src/con_.c index 59f5ac4..fd5db62 100644 --- a/src/con_.c +++ b/src/con_.c @@ -52,8 +52,8 @@ void (*_con_colourmsgf)(void *this, const struct con_colour *c, const char *fmt, // XXX: the const and non-const entries might actually be flipped on windows, // not 100% sure, but dunno if it's worth essentially duping most of these when // the actual executed machine code is probably identical anyway. -DECL_VFUNC0(int, AllocateDLLIdentifier, 5) -DECL_VFUNC0(int, AllocateDLLIdentifier_p2, 8) +DECL_VFUNC(int, AllocateDLLIdentifier, 5) +DECL_VFUNC(int, AllocateDLLIdentifier_p2, 8) DECL_VFUNC(void, RegisterConCommand, 6, /*ConCommandBase*/ void *) DECL_VFUNC(void, RegisterConCommand_p2, 9, /*ConCommandBase*/ void *) DECL_VFUNC(void, UnregisterConCommands, 8, int) @@ -61,7 +61,7 @@ DECL_VFUNC(void, UnregisterConCommands_p2, 11, int) // DECL_VFUNC(void *, FindCommandBase, 10, const char *) DECL_VFUNC(void *, FindCommandBase_p2, 13, const char *) DECL_VFUNC(struct con_var *, FindVar, 12, const char *) -// DECL_VFUNC0(const struct con_var *, FindVar_const, 13, const char *) +// DECL_VFUNC(const struct con_var *, FindVar_const, 13, const char *) DECL_VFUNC(struct con_var *, FindVar_p2, 15, const char *) DECL_VFUNC(struct con_cmd *, FindCommand, 14, const char *) DECL_VFUNC(struct con_cmd *, FindCommand_p2, 17, const char *) @@ -91,7 +91,7 @@ static inline void initval(struct con_var *v) { // to try and be like the engine even though it's probably not actually // required, we call the Internal* virtual functions by actual virtual lookup. // since the vtables are filled dynamically (below), we store this index; other -// indexes are just offset from this one since the 3-or-4 functions are all +// indices are just offset from this one since the 3-or-4 functions are all // right next to each other. static int vtidx_InternalSetValue; @@ -182,10 +182,11 @@ static void VCALLCONV ChangeStringValue(struct con_var *this, const char *s, this->strlen = len; } memcpy(this->strval, s, len); - //if (cb) {...} // not bothering - // also note: portal2 has a *list* of callbacks, although that part of ABI - // doesn't matter as far as plugin compat goes, so still not bothering - // we do however bother to call global callbacks, as is polite. + // callbacks don't matter as far as ABI compat goes (and thank goodness + // because e.g. portal2 randomly adds a *list* of callbacks!?). however we + // do need callbacks for at least one feature, so do our own minimal thing + if (this->cb) this->cb(this); + // also call global callbacks, as is polite. if (GAMETYPE_MATCHES(Portal2)) { VCALL(_con_iface, CallGlobalChangeCallbacks_p2, this, old, oldf); } @@ -415,12 +416,12 @@ bool con_init(void *(*f)(const char *, int *), int plugin_ver) { // the actual ABI to use to avoid spectacular crashes. if (VCALL(_con_iface, FindCommandBase_p2, "portal2_square_portals")) { _con_colourmsgf = VFUNC(_con_iface, ConsoleColorPrintf_p2); - dllid = VCALL0(_con_iface, AllocateDLLIdentifier_p2); + dllid = VCALL(_con_iface, AllocateDLLIdentifier_p2); _gametype_tag |= _gametype_tag_Portal2; } else if (VCALL(_con_iface, FindCommand, "l4d2_snd_adrenaline")) { _con_colourmsgf = VFUNC(_con_iface, ConsoleColorPrintf_l4d); - dllid = VCALL0(_con_iface, AllocateDLLIdentifier); + dllid = VCALL(_con_iface, AllocateDLLIdentifier); // while we're here, also distinguish Survivors, the stupid Japanese // arcade game a few people seem to care about for some reason // (which for some other reason also has some vtable changes) @@ -433,7 +434,7 @@ bool con_init(void *(*f)(const char *, int *), int plugin_ver) { } else if (VCALL(_con_iface, FindVar, "z_difficulty")) { _con_colourmsgf = VFUNC(_con_iface, ConsoleColorPrintf_l4d); - dllid = VCALL0(_con_iface, AllocateDLLIdentifier); + dllid = VCALL(_con_iface, AllocateDLLIdentifier); _gametype_tag |= _gametype_tag_L4D1; } else { @@ -449,7 +450,7 @@ bool con_init(void *(*f)(const char *, int *), int plugin_ver) { // TODO(compat): are there any cases where 004 is incompatible? could // this crash? find out! _con_colourmsgf = VFUNC(_con_iface, ConsoleColorPrintf_004); - dllid = VCALL0(_con_iface, AllocateDLLIdentifier); + dllid = VCALL(_con_iface, AllocateDLLIdentifier); // even more spaghetti! we need the plugin interface version to // accurately distinguish 2007/2013 branches if (plugin_ver == 3) _gametype_tag |= _gametype_tag_2013; @@ -489,12 +490,8 @@ void con_disconnect(void) { } struct con_var *con_findvar(const char *name) { - if (GAMETYPE_MATCHES(Portal2)) { - return VCALL(_con_iface, FindVar_p2, name); - } - else { - return VCALL(_con_iface, FindVar, name); - } + if (GAMETYPE_MATCHES(Portal2)) return VCALL(_con_iface, FindVar_p2, name); + else return VCALL(_con_iface, FindVar, name); } struct con_cmd *con_findcmd(const char *name) { @@ -506,8 +503,7 @@ struct con_cmd *con_findcmd(const char *name) { } } -#define GETTER(T, N, M) \ - T N(const struct con_var *v) { return v->parent->M; } +#define GETTER(T, N, M) T N(const struct con_var *v) { return v->parent->M; } GETTER(const char *, con_getvarstr, strval) GETTER(float, con_getvarf, fval) GETTER(int, con_getvari, ival) @@ -518,7 +514,7 @@ GETTER(int, con_getvari, ival) ((void (*VCALLCONV)(void *, T))(v->vtable_iconvar[I]))( \ &v->vtable_iconvar, x); \ } -// vtable indexes for str/int/float are consistently at the start, hooray. +// vtable indices for str/int/float are consistently at the start, hooray. // unfortunately the windows overload ordering meme still applies... #ifdef _WIN32 SETTER(const char *, 2, con_setvarstr) diff --git a/src/con_.h b/src/con_.h index ed93bad..93306ad 100644 --- a/src/con_.h +++ b/src/con_.h @@ -144,7 +144,13 @@ struct con_var { // ConVar in engine float minval; bool hasmax; // just sticking to sdk position for now float maxval; - //void *cb; // we don't currently bother with callback support. add if needed! + /* + * Our quickly-chucked in optional callback - doesn't match the engine!! + * Also has to be manually set in code, although that's probably fine anyway + * as it's common to only want a cvar to do something if the feature + * succesfully init-ed. + */ + void (*cb)(struct con_var *this); }; /* The change callback used in most branches of Source. Takes an IConVar :) */ diff --git a/src/demorec.c b/src/demorec.c index 5d17452..f8f2829 100644 --- a/src/demorec.c +++ b/src/demorec.c @@ -21,10 +21,10 @@ #include "bitbuf.h" #include "con_.h" #include "demorec.h" -#include "hook.h" -#include "factory.h" +#include "engineapi.h" #include "gamedata.h" #include "gameinfo.h" +#include "hook.h" #include "intdefs.h" #include "mem.h" #include "os.h" @@ -185,7 +185,7 @@ static inline bool find_recmembers(void *stoprecording) { } bool demorec_init(void) { - if (!gamedata_has_vtidx_StopRecording) { + if (!has_vtidx_StopRecording) { con_warn("demorec: missing gamedata entries for this engine\n"); return false; } @@ -218,15 +218,15 @@ bool demorec_init(void) { con_warn("demorec: couldn't unprotect CDemoRecorder vtable: %s\n", err); return false; } - if (!find_recmembers(vtable[gamedata_vtidx_StopRecording])) { + if (!find_recmembers(vtable[vtidx_StopRecording])) { con_warn("demorec: couldn't find m_bRecording and m_nDemoNumber\n"); return false; } orig_SetSignonState = (SetSignonState_func)hook_vtable(vtable, - gamedata_vtidx_SetSignonState, (void *)&hook_SetSignonState); + vtidx_SetSignonState, (void *)&hook_SetSignonState); orig_StopRecording = (StopRecording_func)hook_vtable(vtable, - gamedata_vtidx_StopRecording, (void *)&hook_StopRecording); + vtidx_StopRecording, (void *)&hook_StopRecording); orig_record_cb = cmd_record->cb; cmd_record->cb = &hook_record_cb; orig_stop_cb = cmd_stop->cb; cmd_stop->cb = &hook_stop_cb; @@ -239,10 +239,8 @@ void demorec_end(void) { // avoid dumb edge case if someone somehow records and immediately unloads if (*recording && *demonum == 0) *demonum = 1; void **vtable = *(void ***)demorecorder; - unhook_vtable(vtable, gamedata_vtidx_SetSignonState, - (void *)orig_SetSignonState); - unhook_vtable(vtable, gamedata_vtidx_StopRecording, - (void *)orig_StopRecording); + unhook_vtable(vtable, vtidx_SetSignonState, (void *)orig_SetSignonState); + unhook_vtable(vtable, vtidx_StopRecording, (void *)orig_StopRecording); cmd_record->cb = orig_record_cb; cmd_stop->cb = orig_stop_cb; } @@ -250,8 +248,7 @@ void demorec_end(void) { // custom data writing stuff is a separate feature, defined below. it we can't // find WriteMessage, we can still probably do the auto recording stuff above -static int nbits_msgtype; -static int nbits_datalen; +static int nbits_msgtype, nbits_datalen; // The engine allows usermessages up to 255 bytes, we add 2 bytes of overhead, // and then there's the leading bits before that too (see create_message) @@ -300,7 +297,7 @@ void demorec_writecustom(void *buf, int len) { // it out to the demo file being recorded. static bool find_WriteMessages(void) { // TODO(compat): probably rewrite this to just scan for a call instruction! - const uchar *insns = (*(uchar ***)demorecorder)[gamedata_vtidx_RecordPacket]; + const uchar *insns = (*(uchar ***)demorecorder)[vtidx_RecordPacket]; // RecordPacket calls WriteMessages pretty much right away: // 56 push esi // 57 push edi @@ -329,8 +326,7 @@ static bool find_WriteMessages(void) { } bool demorec_custom_init(void) { - if (!gamedata_has_vtidx_GetEngineBuildNumber || - !gamedata_has_vtidx_RecordPacket) { + if (!has_vtidx_GetEngineBuildNumber || !has_vtidx_RecordPacket) { con_warn("demorec: custom: missing gamedata entries for this engine\n"); return false; } @@ -348,7 +344,7 @@ bool demorec_custom_init(void) { if (clientiface = factory_engine("VEngineClient013", 0)) { typedef uint (*VCALLCONV GetEngineBuildNumber_func)(void *this); buildnum = (*(GetEngineBuildNumber_func **)clientiface)[ - gamedata_vtidx_GetEngineBuildNumber](clientiface); + vtidx_GetEngineBuildNumber](clientiface); } // add support for other interfaces here: // else if (clientiface = factory_engine("VEngineClient0XX", 0)) { diff --git a/src/engineapi.c b/src/engineapi.c new file mode 100644 index 0000000..54671a8 --- /dev/null +++ b/src/engineapi.c @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#include "engineapi.h" +#include "gametype.h" +#include "intdefs.h" + +u64 _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; + +struct VEngineClient *engclient; +struct VEngineServer *engserver; + +void engineapi_init(void) { + if (engclient = factory_engine("VEngineClient015", 0)) { + _gametype_tag |= _gametype_tag_Client015; + } + else if (engclient = factory_engine("VEngineClient014", 0)) { + _gametype_tag |= _gametype_tag_Client014; + } + else if (engclient = factory_engine("VEngineClient013", 0)) { + _gametype_tag |= _gametype_tag_Client013; + } + else if (engclient = factory_engine("VEngineClient012", 0)) { + _gametype_tag |= _gametype_tag_Client012; + } + + if (engserver = factory_engine("VEngineServer021", 0)) { + _gametype_tag |= _gametype_tag_Server021; + } + // else if (engserver = others as needed...) { + // } +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/engineapi.h b/src/engineapi.h new file mode 100644 index 0000000..dcf3bbe --- /dev/null +++ b/src/engineapi.h @@ -0,0 +1,101 @@ +/* + * 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. + */ + +/* + * This header was named by mlugg. Please direct all filename-related + * bikeshedding to . + */ + +#ifndef INC_ENGINEAPI_H +#define INC_ENGINEAPI_H + +#include "intdefs.h" +#include "vcall.h" + +/* + * Here, we define a bunch of random data types as well as interfaces that don't + * have abstractions elsewhere but nonetheless need to be used in a few + * different places. + */ + +/* Access to game and engine factories obtained on plugin load */ +typedef void *(*ifacefactory)(const char *name, int *ret); +extern ifacefactory factory_client, factory_server, factory_engine, + factory_inputsystem; + +// various engine types {{{ + +struct VEngineClient { + void **vtable; + /* opaque fields */ +}; +extern struct VEngineClient *engclient; + +struct VEngineServer { + void **vtable; + /* opaque fields */ +}; +extern struct VEngineServer *engserver; + +struct CUtlMemory { + void *mem; + int alloccnt, growsz; +}; +struct CUtlVector { + struct CUtlMemory m; + int sz; + void *mem_again_for_some_reason; +}; + +struct edict { + int stateflags; + int netserial; + void *ent_networkable; + void *ent_unknown; +}; + +struct vec3f { float x, y, z; }; +struct CMoveData { + bool firstrun : 1, gamecodemoved : 1; + ulong playerhandle; + int impulse; + struct vec3f viewangles, absviewangles; + int buttons, oldbuttons; + float mv_forward, mv_side, mv_up; + float maxspeed, clmaxspeed; + struct vec3f vel, angles, oldangles; + float out_stepheight; + struct vec3f out_wishvel, out_jumpvel; + struct vec3f constraint_centre; + float constraint_radius, constraint_width, constraint_speedfactor; + struct vec3f origin; +}; + +/// }}} + +/* + * Called on plugin init to attempt to initialise various core interfaces. + * Doesn't return an error result, because the plugin can still load even if + * this stuff is missing. + * + * Also performs additional gametype detection after con_init(), before + * gamedata_init(). + */ +void engineapi_init(void); + +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 fdm=marker diff --git a/src/ent.c b/src/ent.c new file mode 100644 index 0000000..b621e0b --- /dev/null +++ b/src/ent.c @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#include + +#include "con_.h" +#include "engineapi.h" +#include "gamedata.h" +#include "gametype.h" +#include "intdefs.h" +#include "vcall.h" + +DECL_VFUNC_DYN(void *, PEntityOfEntIndex, int) + +void *ent_get(int idx) { + // TODO(compat): Based on previous attempts at this, for L4D2, we need + // factory_server("PlayerInfoManager002")->GetGlobalVars()->edicts + // (offset 22 or so). Then get edicts from that. For now, we only need this + // for Portal FOV stuff, so just doing this eiface stuff. + + struct edict *e = VCALL(engserver, PEntityOfEntIndex, idx); + if (!e) return 0; + return e->ent_unknown; +} + +bool ent_init(void) { + if (!has_vtidx_PEntityOfEntIndex) { + con_warn("ent: missing gamedata entries for this engine\n"); + return false; + } + // for PEntityOfEntIndex we don't really have to do any more init, we can + // just call the function later. + return true; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/ent.h b/src/ent.h new file mode 100644 index 0000000..c781259 --- /dev/null +++ b/src/ent.h @@ -0,0 +1,29 @@ +/* + * 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_ENT_H +#define INC_ENT_H + +#include + +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/factory.h b/src/factory.h deleted file mode 100644 index 18bf069..0000000 --- a/src/factory.h +++ /dev/null @@ -1,14 +0,0 @@ -/* This file is dedicated to the public domain. */ - -#ifndef INC_FACTORY_H -#define INC_FACTORY_H - -/* Access to game and engine factories obtained on plugin load */ - -typedef void *(*ifacefactory)(const char *name, int *ret); -extern ifacefactory factory_client, factory_server, factory_engine, - factory_inputsystem; - -#endif - -// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/fixes.c b/src/fixes.c index ca5aaf0..0a503f8 100644 --- a/src/fixes.c +++ b/src/fixes.c @@ -23,7 +23,6 @@ #endif #include "con_.h" -#include "factory.h" #include "gametype.h" static void chflags(const char *name, int unset, int set) { diff --git a/src/fixes.h b/src/fixes.h index 4733957..ae863a8 100644 --- a/src/fixes.h +++ b/src/fixes.h @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Michael Smith + * Copyright © 2022 Michael Smith * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above diff --git a/src/fov.c b/src/fov.c new file mode 100644 index 0000000..f60f067 --- /dev/null +++ b/src/fov.c @@ -0,0 +1,136 @@ +/* + * 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. + */ + +// TODO(linux): theoretically, probably ifdef out the cvar-replacement stuff; we +// expect any game that's been ported to linux to already have fov_desired + +#include + +#include "con_.h" +#include "engineapi.h" +#include "ent.h" +#include "gametype.h" +#include "hook.h" +#include "intdefs.h" +#include "mem.h" +#include "vcall.h" +#include "x86.h" + +bool changedmax = false; +DEF_CVAR_MINMAX_UNREG(fov_desired, + "Set the base field of view (SST reimplementation)", 75, 75, 120, + CON_HIDDEN | CON_ARCHIVE) +static struct con_var *real_fov_desired; // engine's if it has it, or ours + +typedef void (*VCALLCONV SetDefaultFOV_func)(void *, int); +static SetDefaultFOV_func orig_SetDefaultFOV; +static void VCALLCONV hook_SetDefaultFOV(void *this, int fov) { + // the game normally clamps fov_desired on the server side, disregard + // whatever it tries to set and force our own value instead + orig_SetDefaultFOV(this, con_getvari(real_fov_desired)); +} + +static bool find_SetDefaultFOV(struct con_cmd *fov) { + uchar *fovcb = (uchar *)fov->cb; + int callcnt = 0; + for (uchar *p = fovcb; p - fovcb < 96;) { + // fov command source, and consequent asm, calls 4 functions, one of + // them virtual (i.e. via register). of the 3 direct calls, + // SetDefaultFOV is the third. + if (p[0] == X86_CALL && ++callcnt == 3) { + orig_SetDefaultFOV = (SetDefaultFOV_func)(p + 5 + + mem_loadoffset(p + 1)); + return true; + } + int len = x86_len(p); + if (len == -1) { + con_warn("fov: find_SetDefaultFOV: unknown or invalid instruction\n"); + return false; + } + p += len; + } + return true; +} + +// replacement cvar needs to actively set player fov if in a map +static void fovcb(struct con_var *v) { + void *player = ent_get(1); // NOTE: singleplayer only! + if (player) orig_SetDefaultFOV(player, con_getvari(v)); +} + +// called by sst.c in ClientActive to ensure fov is applied on load +void fov_onload(void) { + if (real_fov_desired == fov_desired) { + void *player = ent_get(1); // " + if (player) orig_SetDefaultFOV(player, con_getvari(fov_desired)); + } +} + +static struct con_cmd *cmd_fov; + +bool fov_init(bool has_ent) { + // 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; } + + 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; + real_fov_desired->parent->maxval = 120; + } + else { + if (!has_ent) return false; + con_reg(fov_desired); + real_fov_desired = fov_desired; + } + if (!find_SetDefaultFOV(cmd_fov)) { + con_warn("fov: couldn't find SetDefaultFOV function\n"); + return false; + } + orig_SetDefaultFOV = (SetDefaultFOV_func)hook_inline( + (void *)orig_SetDefaultFOV, (void *)&hook_SetDefaultFOV); + if (!orig_SetDefaultFOV) { + con_warn("fov: couldn't hook SetDefaultFOV function\n"); + return false; + } + + // we might not be using our cvar but simpler to do this unconditionally + fov_desired->cb = &fovcb; + 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; +} + +void fov_end(void) { + if (real_fov_desired && real_fov_desired != fov_desired) { + real_fov_desired->parent->maxval = 90; + if (con_getvarf(real_fov_desired) > 90) { + con_setvarf(real_fov_desired, 90); // blegh. + } + } + else { + void *player = ent_get(1); // also singleplayer only + if (player) orig_SetDefaultFOV(player, 75); + } + unhook_inline((void *)orig_SetDefaultFOV); + cmd_fov->base.flags &= ~CON_DEVONLY; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/fov.h b/src/fov.h new file mode 100644 index 0000000..a59bbc0 --- /dev/null +++ b/src/fov.h @@ -0,0 +1,33 @@ +/* + * 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_FOV_H +#define INC_FOV_H + +#include + +#include "engineapi.h" + +bool fov_init(bool has_ent); +void fov_end(void); + +// annoying spaghetti, from sst.c. maybe one day there could be some proper +// event system... +void fov_onload(void); + +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/gameinfo.c b/src/gameinfo.c index 95cce97..36c9402 100644 --- a/src/gameinfo.c +++ b/src/gameinfo.c @@ -20,6 +20,7 @@ #endif #include "con_.h" +#include "engineapi.h" #include "gametype.h" #include "intdefs.h" #include "kv.h" @@ -173,13 +174,7 @@ static void kv_cb(enum kv_token type, const char *p, uint len, void *_ctxt) { }; // values for ctxt->matchtype - enum { - mt_none, - mt_title, - mt_nest, - mt_game, - mt_gamebin - }; + enum { mt_none, mt_title, mt_nest, mt_game, mt_gamebin }; #define MATCH(s) (len == sizeof(s) - 1 && matchtok(p, s, sizeof(s) - 1)) switch (type) { @@ -238,29 +233,13 @@ static void kv_cb(enum kv_token type, const char *p, uint len, void *_ctxt) { #undef MATCH } -bool gameinfo_init(void *(*ifacef)(const char *, int *)) { - typedef char *(*VCALLCONV GetGameDirectory_func)(void *this); - GetGameDirectory_func **engclient; - int off; - if (engclient = ifacef("VEngineClient015", 0)) { // portal 2 (post-release?) - off = 35; - } - else if (engclient = ifacef("VEngineClient014", 0)) { // l4d2000-~2027, bms? - if (!GAMETYPE_MATCHES(L4D2x)) goto unsup; - off = 73; // YES, THIS IS SEVENTY THREE ALL OF A SUDDEN. I KNOW. CRAZY. - } - else if (engclient = ifacef("VEngineClient013", 0)) { // ...most things? - if (GAMETYPE_MATCHES(L4Dx)) off = 36; // THEY CHANGED IT BACK LATER!? - else off = 35; - } - else if (engclient = ifacef("VEngineClient012", 0)) { // dmomm, ep1, ... - off = 37; - } - else { -unsup: con_warn("gameinfo: unsupported VEngineClient interface\n"); +DECL_VFUNC_DYN(const char *, GetGameDirectory) + +bool gameinfo_init(void) { + if (!has_vtidx_GetGameDirectory) { + con_warn("gameinfo: unsupported VEngineClient interface\n"); return false; } - GetGameDirectory_func GetGameDirectory = (*engclient)[off]; // engine always calls chdir() with its own base path on startup, so engine // base dir is just cwd @@ -280,7 +259,7 @@ unsup: con_warn("gameinfo: unsupported VEngineClient interface\n"); #ifdef _WIN32 int gamedirlen = _snwprintf(gamedir, sizeof(gamedir) / sizeof(*gamedir), - L"%S", GetGameDirectory(engclient)); + L"%S", VCALL(engclient, GetGameDirectory)); if (gamedirlen < 0) { // encoding error??? ugh... con_warn("gameinfo: invalid game directory path!\n"); return false; diff --git a/src/gameinfo.h b/src/gameinfo.h index eac5009..4948268 100644 --- a/src/gameinfo.h +++ b/src/gameinfo.h @@ -34,7 +34,7 @@ extern const os_char *gameinfo_serverlib; /* Absolute path to the server lib */ * spaghetti magic to figure out which game/engine we're in and where its * libraries (which we want to hook) are located. */ -bool gameinfo_init(void *(*ifacef)(const char *, int *)); +bool gameinfo_init(void); #endif diff --git a/src/gametype.h b/src/gametype.h index ff39785..7c8fa3f 100644 --- a/src/gametype.h +++ b/src/gametype.h @@ -19,19 +19,24 @@ #include "intdefs.h" -extern u32 _gametype_tag; +extern u64 _gametype_tag; #define _gametype_tag_OE 1 // TODO(compat): detect in con_init, even if just to fail (VEngineServer broke) // TODO(compat): buy dmomm in a steam sale to implement and test the above, lol -#define _gametype_tag_DMoMM 2 -#define _gametype_tag_OrangeBox 4 -#define _gametype_tag_L4D1 8 -#define _gametype_tag_L4D2 16 -#define _gametype_tag_L4DS 32 -#define _gametype_tag_Portal1 64 -#define _gametype_tag_Portal2 128 -#define _gametype_tag_2013 256 +#define _gametype_tag_DMoMM (1 << 1) +#define _gametype_tag_OrangeBox (1 << 2) +#define _gametype_tag_L4D1 (1 << 3) +#define _gametype_tag_L4D2 (1 << 4) +#define _gametype_tag_L4DS (1 << 5) +#define _gametype_tag_Portal1 (1 << 6) +#define _gametype_tag_Portal2 (1 << 7) +#define _gametype_tag_2013 (1 << 8) +#define _gametype_tag_Client015 (1 << 9) +#define _gametype_tag_Client014 (1 << 10) +#define _gametype_tag_Client013 (1 << 11) +#define _gametype_tag_Client012 (1 << 12) +#define _gametype_tag_Server021 (1 << 13) #define _gametype_tag_L4D (_gametype_tag_L4D1 | _gametype_tag_L4D2) // XXX: *stupid* naming, refactor later (damn Survivors ruining everything) diff --git a/src/nosleep.c b/src/nosleep.c index db206e0..4ad02df 100644 --- a/src/nosleep.c +++ b/src/nosleep.c @@ -17,9 +17,9 @@ #include #include "con_.h" -#include "hook.h" -#include "factory.h" +#include "engineapi.h" #include "gamedata.h" +#include "hook.h" #include "os.h" #include "vcall.h" @@ -44,30 +44,29 @@ bool nosleep_init(void) { con_warn("nosleep: missing required factories\n"); return false; } - if (!gamedata_has_vtidx_SleepUntilInput) { + if (!has_vtidx_SleepUntilInput) { con_warn("nosleep: missing gamedata entries for this engine\n"); return false; } void *insys = factory_inputsystem("InputSystemVersion001", 0); if (!insys) { - con_warn("nosleep: couldn't get intput system interface\n"); + con_warn("nosleep: couldn't get input system interface\n"); return false; } vtable = *(void ***)insys; - if (!os_mprot(vtable + gamedata_vtidx_SleepUntilInput, - sizeof(void *), PAGE_EXECUTE_READWRITE)) { - con_warn("nosleep: couldn't make memory writeable\n"); + if (!os_mprot(vtable + vtidx_SleepUntilInput, sizeof(void *), + PAGE_EXECUTE_READWRITE)) { + con_warn("nosleep: couldn't make memory writable\n"); return false; } orig_SleepUntilInput = (SleepUntilInput_func)hook_vtable(vtable, - gamedata_vtidx_SleepUntilInput, (void *)&hook_SleepUntilInput); + vtidx_SleepUntilInput, (void *)&hook_SleepUntilInput); engine_no_focus_sleep->base.flags &= ~CON_HIDDEN; return true; } void nosleep_end(void) { - unhook_vtable(vtable, gamedata_vtidx_SleepUntilInput, - (void *)orig_SleepUntilInput); + unhook_vtable(vtable, vtidx_SleepUntilInput, (void *)orig_SleepUntilInput); } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/sst.c b/src/sst.c index 2ba0f90..e75c9d8 100644 --- a/src/sst.c +++ b/src/sst.c @@ -24,7 +24,9 @@ #include "autojump.h" #include "con_.h" #include "demorec.h" -#include "factory.h" +#include "engineapi.h" +#include "ent.h" +#include "fov.h" #include "fixes.h" #include "gamedata.h" #include "gameinfo.h" @@ -42,11 +44,6 @@ #define fS "s" #endif -u32 _gametype_tag = 0; // spaghetti: no point making a .c file for 1 variable - -ifacefactory factory_client = 0, factory_server = 0, factory_engine = 0, - factory_inputsystem = 0; - static int ifacever; #ifdef __linux__ @@ -191,23 +188,27 @@ static const void *const *const plugin_obj; // 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_autojump = false, has_demorec = false, - has_demorec_custom = false, has_nosleep = false; +static bool has_autojump = false, has_demorec = false, has_fov = false, + has_nosleep = false; #ifdef _WIN32 static bool has_rinput = false; #endif -// since this is static/global, it only becomes false again when the plugin SO -// is unloaded/reloaded -static bool already_loaded = false; -static bool skip_unload = false; +static bool already_loaded = false, skip_unload = false; #define RGBA(r, g, b, a) (&(struct con_colour){(r), (g), (b), (a)}) static bool do_load(ifacefactory enginef, ifacefactory serverf) { factory_engine = enginef; factory_server = serverf; if (!con_init(enginef, ifacever)) return false; - if (!gameinfo_init(enginef)) { con_disconnect(); return false; } + engineapi_init(); // load some other interfaces + // detect p1 for the benefit of specific features + if (!GAMETYPE_MATCHES(Portal2) && con_findcmd("upgrade_portalgun")) { + _gametype_tag |= _gametype_tag_Portal1; + } + gamedata_init(); + if (!gameinfo_init()) { con_disconnect(); return false; } + const void **p = vtable_firstdiff; if (GAMETYPE_MATCHES(Portal2)) *p++ = (void *)&nop_p_v; // ClientFullyConnect *p++ = (void *)&nop_p_v; // ClientDisconnect @@ -256,18 +257,16 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) { con_warn("sst: warning: couldn't get input system's CreateInterface\n"); } - // detect p1 for the benefit of specific features - if (!GAMETYPE_MATCHES(Portal2) && con_findcmd("upgrade_portalgun")) { - _gametype_tag |= _gametype_tag_Portal1; - } - gamedata_init(); has_autojump = autojump_init(); has_demorec = demorec_init(); + // not enabling demorec_custom yet - kind of incomplete and currently unused + //if (has_demorec) demorec_custom_init(); + bool has_ent = ent_init(); + has_fov = fov_init(has_ent); has_nosleep = nosleep_init(); #ifdef _WIN32 has_rinput = rinput_init(); #endif - if (has_demorec) has_demorec_custom = demorec_custom_init(); fixes_apply(); // NOTE: this is technically redundant for early versions but I CBA writing @@ -280,8 +279,8 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) { "vtable; won't be able to prevent nag message\n"); goto e; } - orig_GetStringForSymbol = (GetStringForSymbol_func)hook_vtable(kvsvt, - 4, (void *)GetStringForSymbol_hook); + orig_GetStringForSymbol = (GetStringForSymbol_func)hook_vtable(kvsvt, 4, + (void *)GetStringForSymbol_hook); } e: con_colourmsg(RGBA(64, 255, 64, 255), @@ -291,24 +290,11 @@ e: con_colourmsg(RGBA(64, 255, 64, 255), return true; } -// XXX: not sure if all this stuff should, like, go somewhere? - -struct CUtlMemory { - void *mem; - int alloccnt, growsz; -}; -struct CUtlVector { - struct CUtlMemory m; - int sz; - void *mem_again_for_some_reason; -}; - struct CServerPlugin /* : IServerPluginHelpers */ { void **vtable; struct CUtlVector plugins; /*IPluginHelpersCheck*/ void *pluginhlpchk; }; - struct CPlugin { char description[128]; bool paused; @@ -320,6 +306,7 @@ struct CPlugin { }; static void do_unload(void) { +#ifdef _WIN32 // this is only relevant in builds that predate linux support struct CServerPlugin *pluginhandler = factory_engine("ISERVERPLUGINHELPERS001", 0); if (pluginhandler) { // if not, oh well too bad we tried :^) @@ -338,9 +325,11 @@ static void do_unload(void) { } } } +#endif if (has_autojump) autojump_end(); if (has_demorec) demorec_end(); + if (has_fov) fov_end(); // dep on ent if (has_nosleep) nosleep_end(); #ifdef _WIN32 if (has_rinput) rinput_end(); @@ -386,6 +375,13 @@ static const char *VCALLCONV GetPluginDescription(void *this) { return LONGNAME " v" VERSION; } +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 + if (has_fov) fov_onload(); +} + #define MAX_VTABLE_FUNCS 21 static const void *vtable[MAX_VTABLE_FUNCS] = { // start off with the members which (thankfully...) are totally stable @@ -397,10 +393,10 @@ static const void *vtable[MAX_VTABLE_FUNCS] = { (void *)&UnPause, (void *)&GetPluginDescription, (void *)&nop_p_v, // LevelInit - (void *)&nop_pii_v, // ServerActivate + (void *)&nop_pii_v, // ServerActivate (void *)&nop_b_v, // GameFrame (void *)&nop_v_v, // LevelShutdown - (void *)&nop_p_v // ClientActive + (void *)&ClientActive // At this point, Alien Swarm and Portal 2 add ClientFullyConnect, so we // can't hardcode any more of the layout! }; diff --git a/src/vcall.h b/src/vcall.h index 654cafb..caee70a 100644 --- a/src/vcall.h +++ b/src/vcall.h @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Michael Smith + * Copyright © 2022 Michael Smith * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,6 +17,8 @@ #ifndef INC_VCALL_H #define INC_VCALL_H +#include "gamedata.h" + /* * Convenient facilities for calling simple (single-table) virtual functions on * possibly-opaque pointers to C++ objects. @@ -33,24 +35,32 @@ #define VCALLCONV #endif -#define DECL_VFUNC0(ret, name, idx) \ - enum { _VTIDX_##name = (idx) }; \ - typedef ret (*VCALLCONV _VFUNC_##name)(void *this); +#define _DECL_VFUNC_DYN(ret, conv, name, ...) \ + /* XXX: GCC extension, seems worthwhile vs having two macros for one thing. + Replace with __VA_OPT__(,) whenever that gets fully standardised. */ \ + typedef ret (*conv name##_func)(void *this, ##__VA_ARGS__); +#define _DECL_VFUNC(ret, conv, name, idx, ...) \ + enum { vtidx_##name = (idx) }; \ + _DECL_VFUNC_DYN(ret, conv, name, ##__VA_ARGS__) +/* Define a virtual function with a known index */ #define DECL_VFUNC(ret, name, idx, ...) \ - enum { _VTIDX_##name = (idx) }; \ - typedef ret (*VCALLCONV _VFUNC_##name)(void *this, __VA_ARGS__); + _DECL_VFUNC(ret, VCALLCONV, name, idx, ##__VA_ARGS__) -// not bothering to provide a zero-argument version because the main use of -// this is vararg functions, which error if __thiscall +/* Define a virtual function with a known index, without thiscall convention */ #define DECL_VFUNC_CDECL(ret, name, idx, ...) \ - enum { _VTIDX_##name = (idx) }; \ - typedef ret (*_VFUNC_##name)(void *this, __VA_ARGS__); + _DECL_VFUNC(ret, , name, idx, ##__VA_ARGS__) + +/* Define a virtual function with an index defined elsewhere */ +#define DECL_VFUNC_DYN(ret, name, ...) \ + _DECL_VFUNC_DYN(ret, VCALLCONV, name, ##__VA_ARGS__) -#define VFUNC(x, name) ((*(_VFUNC_##name **)(x))[_VTIDX_##name]) +/* Define a virtual function with an index defined elsewhere, without thiscall */ +#define DECL_VFUNC_CDECLDYN(ret, name, ...) \ + _DECL_VFUNC_DYN(ret, , name, ##__VA_ARGS__) -#define VCALL0(x, name) (VFUNC(x, name)(x)) -#define VCALL(x, name, ...) VFUNC(x, name)(x, __VA_ARGS__) +#define VFUNC(x, name) ((*(name##_func **)(x))[vtidx_##name]) +#define VCALL(x, name, ...) VFUNC(x, name)(x, ##__VA_ARGS__) #endif -- cgit v1.2.3