summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMichael Smith <mikesmiffy128@gmail.com>2022-04-30 00:23:31 +0100
committerMichael Smith <mikesmiffy128@gmail.com>2022-04-30 00:34:47 +0100
commit1a5c55eb89c22e8822ec057a3731a6d753f13859 (patch)
treefa09dd757a1966649119f70717bd11d679c0f179
parent1aaedffd8c68614936c59d4681e6dc111cb32691 (diff)
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")
-rwxr-xr-xcompile6
-rw-r--r--compile.bat10
-rw-r--r--gamedata/engine.kv10
-rw-r--r--src/autojump.c47
-rw-r--r--src/build/mkgamedata.c258
-rw-r--r--src/build/vec.h96
-rw-r--r--src/con_.c38
-rw-r--r--src/con_.h8
-rw-r--r--src/demorec.c28
-rw-r--r--src/engineapi.c50
-rw-r--r--src/engineapi.h101
-rw-r--r--src/ent.c49
-rw-r--r--src/ent.h29
-rw-r--r--src/factory.h14
-rw-r--r--src/fixes.c1
-rw-r--r--src/fixes.h2
-rw-r--r--src/fov.c136
-rw-r--r--src/fov.h33
-rw-r--r--src/gameinfo.c37
-rw-r--r--src/gameinfo.h2
-rw-r--r--src/gametype.h23
-rw-r--r--src/nosleep.c19
-rw-r--r--src/sst.c68
-rw-r--r--src/vcall.h36
24 files changed, 783 insertions, 318 deletions
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 <stdbool.h>
#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 <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -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:
*
* <varname> <expr>
+ *
+ * Game- or engine-specific values are set using blocks:
+ *
* <varname> { <gametype> <expr> <gametype> <expr> ... [default <expr>] }
+ *
+ * The most complicated it can get is if conditionals are nested, which
+ * basically translates directly into nested ifs:
+ * <varname> { <gametype> { <gametype> <expr> <gametype> <expr> } }
* [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 <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include "../intdefs.h"
+
+struct _vec {
+ uint sz;
+ uint max;
+ void *data;
+};
+
+/*
+ * A dynamic array with push, pop and concatenate operations.
+ *
+ * Usage: struct VEC(my_type) myvec = {0};
+ * Or: struct myvec VEC(my_type);
+ * Or: typedef struct VEC(my_type) myvec;
+ */
+#define VEC(type) { \
+ uint sz; \
+ uint max; \
+ type *data; \
+}
+
+#if defined(__GNUC__) || defined(__clang__)
+__attribute__((unused)) // heck off gcc
+#endif
+static bool _vec_ensure(struct _vec *v, uint tsize, uint newmax) {
+ // FIXME: potential overflow at least on 32-bit hosts (if any!?).
+ // should use reallocarray or something but didn't feel like porting right
+ // now. consider doing later.
+ void *new = realloc(v->data, tsize * newmax);
+ if (new) { v->data = new; v->max = newmax; }
+ return !!new;
+}
+
+#if defined(__GNUC__) || defined(__clang__)
+__attribute__((unused)) // heck off gcc 2
+#endif
+static bool _vec_make_room(struct _vec *v, uint tsize, uint addcnt) {
+ // this overflow check is probably unnecessary, but just in case
+ u64 chk = v->max + addcnt;
+ if (chk > 1u << 30) { errno = ENOMEM; return false; }
+ u32 x = chk;
+ if (x < 16) {
+ x = 16;
+ }
+ else {
+ // round up to next 2*n
+ --x;
+ x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16;
+ x++;
+ }
+ return _vec_ensure(v, tsize, x);
+}
+
+// internal: for reuse by vec0
+#define _vec_push(v, val, slack) ( \
+ ((v)->sz + (slack) < (v)->max || \
+ _vec_make_room((struct _vec *)(v), sizeof(val), 1)) && \
+ ((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 <mikesmiffy128@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "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 <mikesmiffy128@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * This header was named by mlugg. Please direct all filename-related
+ * bikeshedding to <mlugg@mlugg.co.uk>.
+ */
+
+#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 <mikesmiffy128@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdbool.h>
+
+#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 <mikesmiffy128@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef INC_ENT_H
+#define INC_ENT_H
+
+#include <stdbool.h>
+
+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 <mikesmiffy128@gmail.com>
+ * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
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 <mikesmiffy128@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+// 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 <stdbool.h>
+
+#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 <mikesmiffy128@gmail.com>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+ * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef INC_FOV_H
+#define INC_FOV_H
+
+#include <stdbool.h>
+
+#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 <stdbool.h>
#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 <mikesmiffy128@gmail.com>
+ * Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -17,6 +17,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