diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/build/cmeta.c | 2 | ||||
| -rw-r--r-- | src/build/cmeta.h | 2 | ||||
| -rw-r--r-- | src/build/codegen.c | 2 | ||||
| -rw-r--r-- | src/build/mkentprops.c | 208 | ||||
| -rw-r--r-- | src/build/mkgamedata.c | 2 | ||||
| -rw-r--r-- | src/build/skiplist.h | 205 | ||||
| -rw-r--r-- | src/demorec.c | 2 | ||||
| -rw-r--r-- | src/engineapi.c | 62 | ||||
| -rw-r--r-- | src/engineapi.h | 47 | ||||
| -rw-r--r-- | src/ent.c | 34 | ||||
| -rw-r--r-- | src/ent.h | 3 | ||||
| -rw-r--r-- | src/gamedata.h | 4 | ||||
| -rw-r--r-- | src/gametype.h | 31 | ||||
| -rw-r--r-- | src/l4dwarp.c | 60 | ||||
| -rw-r--r-- | src/l4dwarp.h | 26 | ||||
| -rw-r--r-- | src/sst.c | 5 | 
16 files changed, 661 insertions, 34 deletions
diff --git a/src/build/cmeta.c b/src/build/cmeta.c index 3d9281a..bf18903 100644 --- a/src/build/cmeta.c +++ b/src/build/cmeta.c @@ -5,7 +5,7 @@   * 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 + * 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 diff --git a/src/build/cmeta.h b/src/build/cmeta.h index 20672f4..4757654 100644 --- a/src/build/cmeta.h +++ b/src/build/cmeta.h @@ -5,7 +5,7 @@   * 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 + * 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 diff --git a/src/build/codegen.c b/src/build/codegen.c index 6d5fc99..d96059d 100644 --- a/src/build/codegen.c +++ b/src/build/codegen.c @@ -5,7 +5,7 @@   * 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 + * 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 diff --git a/src/build/mkentprops.c b/src/build/mkentprops.c new file mode 100644 index 0000000..5dd2fea --- /dev/null +++ b/src/build/mkentprops.c @@ -0,0 +1,208 @@ +/* + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../intdefs.h" +#include "../kv.h" +#include "../noreturn.h" +#include "../os.h" +#include "skiplist.h" +#include "vec.h" + +#ifdef _WIN32 +#define fS "S" +#else +#define fS "s" +#endif + +static noreturn die(const char *s) { +	fprintf(stderr, "mkentprops: %s\n", s); +	exit(100); +} + +struct prop { +	const char *varname; /* the C global name */ +	const char *propname; /* the entity property name */ +	struct prop *next; +}; +struct vec_prop VEC(struct prop *); + +DECL_SKIPLIST(static, class, struct class, const char *, 4) +struct class { +	const char *name; /* the entity class name */ +	struct vec_prop props; +	struct skiplist_hdr_class hdr; +}; +static inline int cmp_class(struct class *c, const char *s) { +	return strcmp(c->name, s); +} +static inline struct skiplist_hdr_class *hdr_class(struct class *c) { +	return &c->hdr; +} +DEF_SKIPLIST(static, class, cmp_class, hdr_class) +static struct skiplist_hdr_class classes = {0}; +static int nclasses = 0; + +struct parsestate { +	const os_char *filename; +	struct kv_parser *parser; +	char *lastvar; +}; + +static noreturn badparse(struct parsestate *state, const char *e) { +	fprintf(stderr, "mkentprops: %" fS ":%d:%d: parse error: %s", +			state->filename, state->parser->line, state->parser->col, e); +	exit(1); +} + +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: +			state->lastvar = malloc(len + 1); +			if (!state->lastvar) die("couldn't allocate memory"); +			memcpy(state->lastvar, p, len); state->lastvar[len] = '\0'; +			break; +		case KV_NEST_START: badparse(state, "unexpected nested block"); +		case KV_NEST_END: badparse(state, "unexpected closing brace"); +		case KV_VAL: case KV_VAL_QUOTED:; +			struct prop *prop = malloc(sizeof(*prop)); +			if (!p) die("couldn't allocate memory"); +			prop->varname = state->lastvar; +			char *classname = malloc(len + 1); +			if (!classname) die("couldn't allocate memory"); +			memcpy(classname, p, len); classname[len] = '\0'; +			char *propname = strchr(classname, '/'); +			if (!propname) { +				badparse(state, "network name not in class/prop format"); +			} +			*propname = '\0'; ++propname; // split! +			prop->propname = propname; +			struct class *class = skiplist_get_class(&classes, classname); +			if (!class) { +				class = malloc(sizeof(*class)); +				if (!class) die("couldn't allocate memory"); +				*class = (struct class){.name = classname}; +				skiplist_insert_class(&classes, classname, class); +				++nclasses; +			} +			// (if class is already there just leak classname, no point freeing) +			if (!vec_push(&class->props, prop)) die("couldn't append to array"); +			break; +		case KV_COND_PREFIX: case KV_COND_SUFFIX: +			badparse(state, "unexpected conditional"); +	} +} + +static inline noreturn diewrite(void) { die("couldn't write to file"); } + +#define _(x) \ +	if (fprintf(out, "%s\n", x) < 0) diewrite(); +#define F(f, ...) \ +	if (fprintf(out, f "\n", __VA_ARGS__) < 0) diewrite(); +#define H() \ +_( "/* This file is autogenerated by src/build/mkentprops.c. DO NOT EDIT! */") \ +_( "") + +static void decls(FILE *out) { +	for (struct class *c = classes.x[0]; c; c = c->hdr.x[0]) { +		for (struct prop **pp = c->props.data; +				pp - c->props.data < c->props.sz; ++pp) { +F( "extern bool has_%s;", (*pp)->varname) +F( "extern int %s;", (*pp)->varname) +		} +	} +} + +static void defs(FILE *out) { +	for (struct class *c = classes.x[0]; c; c = c->hdr.x[0]) { +		for (struct prop **pp = c->props.data; +				pp - c->props.data < c->props.sz; ++pp) { +F( "bool has_%s = false;", (*pp)->varname) +F( "int %s;", (*pp)->varname) +		} +	} +_( "") +_( "static void initentprops(struct ServerClass *class) {") +F( "	for (int needclasses = %d; class; class = class->next) {", nclasses) +	char *else1 = ""; +	for (struct class *c = classes.x[0]; c; c = c->hdr.x[0]) { +		// TODO(opt): some sort of PHF instead of chained strcmp, if we ever +		// have more than a few classes/properties? +F( "		%sif (!strcmp(class->name, \"%s\")) {", else1, c->name) +_( "			struct SendTable *st = class->table;") +				// christ this is awful :( +F( "			int needprops = %d;", c->props.sz) +_( "			for (struct SendProp *p = st->props; (char *)p -") +_( "					(char *)st->props < st->nprops * sz_SendProp;") +_( "					p = mem_offset(p, sz_SendProp)) {") +		char *else2 = ""; +		for (struct prop **pp = c->props.data; +				pp - c->props.data < c->props.sz; ++pp) { +F( "				%sif (!strcmp(*(const char **)mem_offset(p, off_SP_varname), \"%s\")) {", +		else2, (*pp)->propname) // ugh +F( "					has_%s = true;", (*pp)->varname) +F( "					%s = *(int *)mem_offset(p, off_SP_offset);", (*pp)->varname) +_( "					if (!--needprops) break;") +_( "				}") +			else2 = "else "; +		} +_( "			}") +_( "			if (!--needclasses) break;") +_( "		}") +		else1 = "else "; +	} +_( "	}") +_( "}") +} + +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}; +		char buf[1024]; +		int nread; +		while (nread = read(fd, buf, sizeof(buf))) { +			if (nread == -1) die("couldn't read file"); +			if (!kv_parser_feed(&kv, buf, nread, &kv_cb, &state)) goto ep; +		} +		if (!kv_parser_done(&kv)) { +ep:			fprintf(stderr, "mkentprops: %" fS ":%d:%d: bad syntax: %s\n", +					*argv, kv.line, kv.col, kv.errmsg); +			exit(1); +		} +		close(fd); +	} + +	FILE *out = fopen(".build/include/entprops.gen.h", "wb"); +	if (!out) die("couldn't open entprops.gen.h"); +	H(); +	decls(out); + +	out = fopen(".build/include/entpropsinit.gen.h", "wb"); +	if (!out) die("couldn't open entpropsinit.gen.h"); +	H(); +	defs(out); +	return 0; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/mkgamedata.c b/src/build/mkgamedata.c index d17d7df..ce6490e 100644 --- a/src/build/mkgamedata.c +++ b/src/build/mkgamedata.c @@ -5,7 +5,7 @@   * 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 + * 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 diff --git a/src/build/skiplist.h b/src/build/skiplist.h new file mode 100644 index 0000000..b747d89 --- /dev/null +++ b/src/build/skiplist.h @@ -0,0 +1,205 @@ +/* + * 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_SKIPLIST_H +#define INC_SKIPLIST_H + +#include <stdlib.h> + +#include "../intdefs.h" +#include "../os.h" + +#ifdef _WIN32 +static inline int _skiplist_ffs(uint x) { +	uint ret; +	// on Windows, sizeof(ulong) == sizeof(uint) +	if (_BitScanForward((ulong *)&ret, x)) return ret + 1; else return 0; +} +#else +#include <strings.h> +#define _skiplist_ffs ffs +#endif + +// WARNING: this is a really hacked-up version of the skiplist.h from cbits in +// order to support windows. It probably isn't a good idea to plop straight into +// your own use case. + +#if defined(__GNUC__) || defined(__clang__) +#define _skiplist_unused __attribute__((unused)) // heck off gcc +#else +#define _skiplist_unused +#endif + +// NOTE: using xoroshiro128++, a comparatively bad (i.e. non-cryptographic) prng +// for the sake of simplicity; original cbits skiplist.h relies on libcpoly to +// get arc4random() everywhere but since we're only using this at build time +// that seemed like a silly dependency to bother with. +//#define _skiplist_rng arc4random + +// ALSO NOTE: the PRNG code here is *decidedly not* thread safe. again, this +// isn't a problem for our use case. just keep it in mind if reusing this header +// for something else. or ideally, don't reuse this header for something else... +static inline uvlong _skiplist_rotl(const uvlong x, int k) { +	return (x << k) | (x >> (64 - k)); +} +_skiplist_unused static uvlong _skiplist_rng(void) { +	static uvlong s[2]; +	static bool init = false; +	if (!init) { os_randombytes(s, sizeof(s)); init = true; } +	uvlong s0 = s[0], s1 = s[1]; +	uvlong ret = _skiplist_rotl(s0 * 5, 7) * 9; +	s1 ^= s0; +	s[0] = _skiplist_rotl(s0, 24) ^ s1 ^ (s1 << 16); +	s[1] = _skiplist_rotl(s1, 37); +	return ret; +} + +/* + * Declares the skiplist header struct skiplist_hdr##name, but none of the + * associated functions. Use when the structure needs to be passed around in + * some way but actual operations on the list are a private implementation + * detail. Otherwise, see DECL_SKIPLIST below. + */ +#define DECL_SKIPLIST_TYPE(name, dtype, ktype, levels) \ +typedef dtype _skiplist_dt_##name; \ +typedef ktype _skiplist_kt_##name; \ +enum { skiplist_lvls_##name = (levels) }; \ +struct skiplist_hdr_##name { dtype *x[levels]; }; + +/* + * Declares the skiplist header struct skiplist_hdr_##name, with functions + * skiplist_{get,del,pop,insert}_##name for operating on the list. A single + * occurrence of DEF_SKIPLIST is required to actually implement the + * functions. + * + * This macro implies DECL_SKIPLIST_TYPE (both should not be used). + * + * mod should be either static or extern. + * + * dtype should be the struct type that the skiplist header will be embedded in, + * forming the linked structure. + * + * ktype should be the type of the struct member used for comparisons, for + * example int or char *. + * + * levels should be the number of levels in each node. 4 is probably a + * reasonable number, depending on the size of the structure and how many + * entries need to be stored and looked up. + * + * The resulting get, del, pop and insert functions are hopefully self- + * explanatory - get and del return the relevant node or a null pointer if + * no such node is found. + */ +#define DECL_SKIPLIST(mod, name, dtype, ktype, levels) \ +DECL_SKIPLIST_TYPE(name, dtype, ktype, levels) \ +\ +_skiplist_unused mod dtype *skiplist_get_##name(struct skiplist_hdr_##name *l, \ +		ktype k); \ +_skiplist_unused mod dtype *skiplist_del_##name(struct skiplist_hdr_##name *l, \ +		ktype k); \ +_skiplist_unused mod dtype *skiplist_pop_##name(struct skiplist_hdr_##name *l); \ +_skiplist_unused mod void skiplist_insert_##name(struct skiplist_hdr_##name *l, \ +		ktype k, dtype *node); + +/* + * Implements the functions corresponding to a skiplist - must come after + * DECL_SKIPLIST with the same modifier and name. + * + * compfunc should be a function declared as follows (or an equivalent macro): + * int cf(dtype *x, ktype y); + * + * hdrfunc should be a function declared as follows (or an equivalent macro): + * struct skiplist_hdr_##name *hf(dtype *l); + */ +#define DEF_SKIPLIST(mod, name, compfunc, hdrfunc) \ +static inline int _skiplist_lvl_##name(void) { \ +	int i; \ +	/* for 2 levels we get 1 50% of the time, 2 25% of the time, 0 25% of the +	   time. loop if 0 to distribute this evenly (this gets less likely the more +	   levels there are: at 4 levels, only loops 6% of the time) */ \ +	while (!(i = _skiplist_ffs(_skiplist_rng() & \ +			((1 << skiplist_lvls_##name) - 1)))); \ +	/* ffs gives bit positions as 1-N but we actually want an array index */ \ +	return i - 1; \ +} \ +\ +_skiplist_unused \ +mod _skiplist_dt_##name *skiplist_get_##name(struct skiplist_hdr_##name *l, \ +		_skiplist_kt_##name k) { \ +	for (int cmp, lvl = skiplist_lvls_##name - 1; lvl > -1; --lvl) { \ +		while (l->x[lvl] && (cmp = compfunc(l->x[lvl], k)) < 0) { \ +			l = hdrfunc(l->x[lvl]); \ +		} \ +		/* NOTE: cmp can be uninitialised here, but only if the list is +		   completely empty, in which case we'd return 0 anyway - so it doesn't +		   actually matter! */ \ +		if (cmp == 0) return l->x[lvl]; \ +	} \ +	/* reached the end, no match */ \ +	return 0; \ +} \ +\ +_skiplist_unused \ +_skiplist_dt_##name *skiplist_del_##name(struct skiplist_hdr_##name *l, \ +		_skiplist_kt_##name k) { \ +	_skiplist_dt_##name *ret = 0; \ +	/* ALSO NOTE: in *this* case, cmp DOES need to be initialised to prevent a +	   possible null-deref via hdrfunc(l->x[lvl])->x */ \ +	for (int cmp = 1, lvl = skiplist_lvls_##name - 1; lvl > -1; --lvl) { \ +		while (l->x[lvl] && (cmp = compfunc(l->x[lvl], k)) < 0) { \ +			l = hdrfunc(l->x[lvl]); \ +		} \ +		if (cmp == 0) { \ +			ret = l->x[lvl]; \ +			/* just shift each link by 1 */ \ +			l->x[lvl] = hdrfunc(l->x[lvl])->x[0]; \ +			/* ... and update every level of links via loop */ \ +		} \ +	} \ +	/* reached the end, return whatever was found */ \ +	return ret; \ +} \ +\ +_skiplist_unused \ +mod _skiplist_dt_##name *skiplist_pop_##name(struct skiplist_hdr_##name *l) { \ +	_skiplist_dt_##name *cur = l->x[0]; \ +	if (!cur) return 0; \ +	l->x[0] = hdrfunc(cur)->x[0]; \ +	for (int lvl = 1; lvl < skiplist_lvls_##name; ++lvl) { \ +		if (l->x[lvl]) l->x[lvl] = hdrfunc(l->x[lvl])->x[lvl]; \ +	} \ +	return cur; \ +} \ +\ +_skiplist_unused \ +mod void skiplist_insert_##name(struct skiplist_hdr_##name *l, \ +		_skiplist_kt_##name k, _skiplist_dt_##name *node) { \ +	/* note: higher levels are unset but also skipped in other searches */ \ +	int inslvl = _skiplist_lvl_##name(); \ +	for (int lvl = skiplist_lvls_##name - 1; lvl > -1; --lvl) { \ +		while (l->x[lvl] && compfunc(l->x[lvl], k) < 0) { \ +			l = hdrfunc(l->x[lvl]); \ +		} \ +		if (lvl <= inslvl) { \ +			hdrfunc(node)->x[lvl] = l->x[lvl]; \ +			l->x[lvl] = node; \ +		} \ +	} \ +} \ + +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/demorec.c b/src/demorec.c index 9624db9..7875e30 100644 --- a/src/demorec.c +++ b/src/demorec.c @@ -363,7 +363,7 @@ bool demorec_custom_init(void) {  		// really.  		if (buildnum >= 2042) nbits_datalen = 11; else nbits_datalen = 12;  	// } -	 +  	return find_WriteMessages();  } diff --git a/src/engineapi.c b/src/engineapi.c index 54671a8..54b9128 100644 --- a/src/engineapi.c +++ b/src/engineapi.c @@ -14,9 +14,15 @@   * PERFORMANCE OF THIS SOFTWARE.   */ +#include <stdbool.h> // used in generated code +#include <string.h> // " +  #include "engineapi.h" +#include "gamedata.h"  #include "gametype.h"  #include "intdefs.h" +#include "mem.h" // " +#include "vcall.h"  u64 _gametype_tag = 0; // declared in gametype.h but seems sensible enough here @@ -26,6 +32,15 @@ ifacefactory factory_client = 0, factory_server = 0, factory_engine = 0,  struct VEngineClient *engclient;  struct VEngineServer *engserver; +// this seems to be very stable, thank goodness +DECL_VFUNC(void *, GetGlobalVars, 1) +void *globalvars; + +DECL_VFUNC_DYN(void *, GetAllServerClasses) +DECL_VFUNC_DYN(int, GetEngineBuildNumber) + +#include <entpropsinit.gen.h> +  void engineapi_init(void) {  	if (engclient = factory_engine("VEngineClient015", 0)) {  		_gametype_tag |= _gametype_tag_Client015; @@ -45,6 +60,53 @@ void engineapi_init(void) {  	}  	// else if (engserver = others as needed...) {  	// } + +	void *pim = factory_server("PlayerInfoManager002", 0); +	if (pim) globalvars = VCALL(pim, GetGlobalVars); + +	void *srvdll; +	// TODO(compat): add this back when there's gamedata for 009 (no point atm) +	/*if (srvdll = factory_engine("ServerGameDLL009", 0)) { +		_gametype_tag |= _gametype_tag_SrvDLL009; +	}*/ +	if (srvdll = factory_server("ServerGameDLL005", 0)) { +		_gametype_tag |= _gametype_tag_SrvDLL005; +	} + +	// need to do this now; ServerClass network table iteration requires +	// SendProp offsets +	gamedata_init(); + +	// TODO(compat): we need this terrible hack for now because TLS somehow +	// changed the entity vtable layout and I've yet to think of a way to make +	// gamedata more flexible to handle that properly. I blame JAiZ. +	if (engclient && has_vtidx_GetEngineBuildNumber && +			VCALL(engclient, GetEngineBuildNumber) >= 2200) { +		++vtidx_Teleport; +	} + +	if (has_vtidx_GetAllServerClasses && has_sz_SendProp && +			has_off_SP_varname && has_off_SP_offset) { +		struct ServerClass *svclass = VCALL(srvdll, GetAllServerClasses); +		initentprops(svclass); +#if 0 // just keeping a note of this testing code for now, might delete later +		for (; svclass; svclass = svclass->next) { +			struct SendTable *st = svclass->table; +			for (struct SendProp *p = st->props; (char *)p - +					(char *)st->props < st->nprops * sz_SendProp; +					p = mem_offset(p, sz_SendProp)) { +				if (!strcmp(*(const char **)mem_offset(p, off_SP_varname), +						"m_angEyeAngles[0]")) { +					con_msg("%s\n", svclass->name); +					con_msg("  %s\n", st->tablename); +					con_msg("    %s = %d\n", *(const char **)mem_offset(p, +							off_SP_varname), *(int *)mem_offset(p, off_SP_offset)); +				return; +				} +			} +		} +#endif +	}  }  // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/engineapi.h b/src/engineapi.h index dcf3bbe..6def65b 100644 --- a/src/engineapi.h +++ b/src/engineapi.h @@ -42,13 +42,11 @@ struct VEngineClient {  	void **vtable;  	/* opaque fields */  }; -extern struct VEngineClient *engclient;  struct VEngineServer {  	void **vtable;  	/* opaque fields */  }; -extern struct VEngineServer *engserver;  struct CUtlMemory {  	void *mem; @@ -61,10 +59,14 @@ struct CUtlVector {  };  struct edict { +	// CBaseEdict  	int stateflags;  	int netserial;  	void *ent_networkable;  	void *ent_unknown; +	// edict_t +	// NOTE! *REMOVED* in l4d-based branches. don't iterate over edict pointers! +	float freetime;  };  struct vec3f { float x, y, z; }; @@ -84,15 +86,52 @@ struct CMoveData {  	struct vec3f origin;  }; +#define SENDPROP_INT 0 +#define SENDPROP_FLOAT 1 +#define SENDPROP_VEC 2 +#define SENDPROP_VECXY 3 +#define SENDPROP_STR 4 +#define SENDPROP_ARRAY 5 +#define SENDPROP_DTABLE 6 +#define SENDPROP_INT64 7 + +// these have to be opaque because, naturally, they're unstable between +// branches - access stuff using gamedata offsets as usual +struct RecvProp; +struct SendProp; + +// these two things seem to be stable enough for our purposes +struct SendTable { +	struct SendProp *props; +	int nprops; +	char *tablename; +	void *precalc; +	bool inited : 1; +	bool waswritten : 1; +	/* "has props encoded against current tick count" ??? */ +	bool haspropsenccurtickcnt : 1;  +}; +struct ServerClass { +	char *name; +	struct SendTable *table; +	struct ServerClass *next; +	int id; +	int instbaselineidx; +}; +  /// }}} +extern struct VEngineClient *engclient; +extern struct VEngineServer *engserver; +extern void *globalvars; +  /*   * 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(). + * Also performs additional gametype detection after con_init(), and calls + * gamedata_init() to setup offsets and such.   */  void engineapi_init(void); @@ -21,29 +21,39 @@  #include "gamedata.h"  #include "gametype.h"  #include "intdefs.h" +#include "mem.h"  #include "vcall.h"  DECL_VFUNC_DYN(void *, PEntityOfEntIndex, int) +static struct edict **edicts = 0; -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. +void *ent_getedict(int idx) { +	if (edicts) { +		// globalvars->edicts seems to be null when disconnected +		if (!*edicts) return 0; +		return mem_offset(*edicts, sz_edict * idx); +	} +	else { +		return VCALL(engserver, PEntityOfEntIndex, idx); +	} +} -	struct edict *e = VCALL(engserver, PEntityOfEntIndex, idx); +void *ent_get(int idx) { +	struct edict *e = ent_getedict(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. +	if (has_vtidx_PEntityOfEntIndex) return true; +	if (globalvars && has_off_edicts) { +		edicts = mem_offset(globalvars, off_edicts); +		return true;  	} -	// for PEntityOfEntIndex we don't really have to do any more init, we can -	// just call the function later. -	return true; +	con_warn("ent: not implemented for this engine\n"); +	return false;  }  // vi: sw=4 ts=4 noet tw=80 cc=80 @@ -19,6 +19,9 @@  #include <stdbool.h> +#include "engineapi.h" + +struct edict *ent_getedict(int idx);  void *ent_get(int idx);  bool ent_init(void); diff --git a/src/gamedata.h b/src/gamedata.h index b5e4ed7..678d572 100644 --- a/src/gamedata.h +++ b/src/gamedata.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 @@ -25,6 +25,8 @@  #define NVDTOR 2  #endif  #include <gamedata.gen.h> +// entprops are built by a different tool, in a different header for simplicity +#include <entprops.gen.h>  #undef NVDTOR  void gamedata_init(void); diff --git a/src/gametype.h b/src/gametype.h index 7c8fa3f..d7d0b3e 100644 --- a/src/gametype.h +++ b/src/gametype.h @@ -21,29 +21,40 @@  extern u64 _gametype_tag; +/* general engine branches used in a bunch of stuff */  #define _gametype_tag_OE		1 -// TODO(compat): detect in con_init, even if just to fail (VEngineServer broke) +#define _gametype_tag_OrangeBox	(1 << 1) +#define _gametype_tag_2013		(1 << 2) + +/* specific games with dedicated branches / engine changes */ +// TODO(compat): detect dmomm, even if only just to fail (VEngineServer broke)  // TODO(compat): buy dmomm in a steam sale to implement and test the above, lol -#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_DMoMM		(1 << 3) +#define _gametype_tag_L4D1		(1 << 4) +#define _gametype_tag_L4D2		(1 << 5) +#define _gametype_tag_L4DS		(1 << 6) /* Survivors (weird arcade port) */  #define _gametype_tag_Portal2	(1 << 7) -#define _gametype_tag_2013		(1 << 8) + +/* games needing game-specific stuff, but not tied to a singular branch */ +#define _gametype_tag_Portal1	(1 << 8) + +/* VEngineClient versions */  #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) +/* ServerGameDLL versions */ +#define _gametype_tag_SrvDLL009	(1 << 14) // 2013-ish +#define _gametype_tag_SrvDLL005	(1 << 15) // mostly everything else, it seems + +/* Matches for any multiple possible tags */  #define _gametype_tag_L4D		(_gametype_tag_L4D1 | _gametype_tag_L4D2)  // XXX: *stupid* naming, refactor later (damn Survivors ruining everything)  #define _gametype_tag_L4D2x		(_gametype_tag_L4D2 | _gametype_tag_L4DS)  #define _gametype_tag_L4Dx		(_gametype_tag_L4D1 | _gametype_tag_L4D2x) -#define _gametype_tag_L4Dbased \ -	(_gametype_tag_L4D1 | _gametype_tag_L4D2x | _gametype_tag_Portal2) +#define _gametype_tag_L4Dbased	(_gametype_tag_L4Dx | _gametype_tag_Portal2)  #define _gametype_tag_OrangeBoxbased \  	(_gametype_tag_OrangeBox | _gametype_tag_2013) diff --git a/src/l4dwarp.c b/src/l4dwarp.c new file mode 100644 index 0000000..94b7911 --- /dev/null +++ b/src/l4dwarp.c @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#define _USE_MATH_DEFINES // ... windows. +#include <math.h> +#include <stdbool.h> + +#include "con_.h" +#include "engineapi.h" +#include "ent.h" +#include "gamedata.h" +#include "gametype.h" +#include "intdefs.h" +#include "mem.h" +#include "vcall.h" + +DECL_VFUNC_DYN(void *, GetBaseEntity) +DECL_VFUNC_DYN(void, Teleport, const struct vec3f *pos, const struct vec3f *ang, +		const struct vec3f *vel) + +DEF_CCMD_HERE_UNREG(sst_l4d_testwarp, "Simulate a bot warping to you", +		CON_SERVERSIDE) { +	struct edict *ed = ent_getedict(con_cmdclient + 1); +	if (!ed) { con_warn("error: couldn't access player entity\n"); return; } +	void *e = VCALL(ed->ent_unknown, GetBaseEntity); // is this call required? +	struct vec3f *org = mem_offset(e, off_entpos); +	struct vec3f *ang = mem_offset(e, off_eyeang); +	// L4D idle warps go up to 10 units behind relative to whatever angle the +	// player is facing, lessening the distance based on pitch angle but never +	// displacing vertically +	float pitch = ang->x * M_PI / 180, yaw = ang->y * M_PI / 180; +	float shift = -10 * cos(pitch); +	VCALL(e, Teleport, &(struct vec3f){org->x + shift * cos(yaw), +			org->y + shift * sin(yaw), org->z}, 0, &(struct vec3f){0, 0, 0}); +} + +bool l4dwarp_init(void) { +	if (!GAMETYPE_MATCHES(L4Dx)) return false; +	if (!has_off_entpos || !has_off_eyeang || !has_vtidx_Teleport) { +		con_warn("l4dwarp: missing gamedata entries for this engine\n"); +		return false; +	} +	con_reg(sst_l4d_testwarp); +	return true; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/l4dwarp.h b/src/l4dwarp.h new file mode 100644 index 0000000..5328fcf --- /dev/null +++ b/src/l4dwarp.h @@ -0,0 +1,26 @@ +/* + * 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_L4DWARP_H +#define INC_L4DWARP_H + +#include <stdbool.h> + +bool l4dwarp_init(void); + +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 @@ -32,6 +32,7 @@  #include "gameinfo.h"  #include "gametype.h"  #include "hook.h" +#include "l4dwarp.h"  #include "nosleep.h"  #include "os.h"  #include "rinput.h" @@ -201,12 +202,11 @@ static bool already_loaded = false, skip_unload = false;  static bool do_load(ifacefactory enginef, ifacefactory serverf) {  	factory_engine = enginef; factory_server = serverf;  	if (!con_init(enginef, ifacever)) return false; -	engineapi_init(); // load some other interfaces +	engineapi_init(); // load some other interfaces. also calls gamedata_init()  	// 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; @@ -263,6 +263,7 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) {  	//if (has_demorec) demorec_custom_init();  	bool has_ent = ent_init();  	has_fov = fov_init(has_ent); +	if (has_ent) l4dwarp_init();  	has_nosleep = nosleep_init();  #ifdef _WIN32  	has_rinput = rinput_init();  | 
