diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/autojump.c | 47 | ||||
| -rw-r--r-- | src/build/mkgamedata.c | 258 | ||||
| -rw-r--r-- | src/build/vec.h | 96 | ||||
| -rw-r--r-- | src/con_.c | 38 | ||||
| -rw-r--r-- | src/con_.h | 8 | ||||
| -rw-r--r-- | src/demorec.c | 28 | ||||
| -rw-r--r-- | src/engineapi.c | 50 | ||||
| -rw-r--r-- | src/engineapi.h | 101 | ||||
| -rw-r--r-- | src/ent.c | 49 | ||||
| -rw-r--r-- | src/ent.h | 29 | ||||
| -rw-r--r-- | src/factory.h | 14 | ||||
| -rw-r--r-- | src/fixes.c | 1 | ||||
| -rw-r--r-- | src/fixes.h | 2 | ||||
| -rw-r--r-- | src/fov.c | 136 | ||||
| -rw-r--r-- | src/fov.h | 33 | ||||
| -rw-r--r-- | src/gameinfo.c | 37 | ||||
| -rw-r--r-- | src/gameinfo.h | 2 | ||||
| -rw-r--r-- | src/gametype.h | 23 | ||||
| -rw-r--r-- | src/nosleep.c | 19 | ||||
| -rw-r--r-- | src/sst.c | 68 | ||||
| -rw-r--r-- | src/vcall.h | 36 | 
21 files changed, 761 insertions, 314 deletions
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 @@ -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) @@ -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 @@ -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  | 
