diff options
| author | 2023-05-17 23:27:05 +0100 | |
|---|---|---|
| committer | 2023-05-17 23:33:33 +0100 | |
| commit | d03e4138f637027908b52764a2ce3669097947c6 (patch) | |
| tree | c6596f4169a11410679bbd3add4415b4cc6cc002 | |
| parent | ec85df6d2cbb25211613e550bcc21422ee5eb78e (diff) | |
| download | sst-d03e4138f637027908b52764a2ce3669097947c6.tar.gz sst-d03e4138f637027908b52764a2ce3669097947c6.zip | |
Add some server entity code-crawling machinery
| -rw-r--r-- | src/dictmaptree.h | 79 | ||||
| -rw-r--r-- | src/ent.c | 105 | ||||
| -rw-r--r-- | src/ent.h | 23 | 
3 files changed, 207 insertions, 0 deletions
| diff --git a/src/dictmaptree.h b/src/dictmaptree.h new file mode 100644 index 0000000..8a354fe --- /dev/null +++ b/src/dictmaptree.h @@ -0,0 +1,79 @@ +/* + * Copyright © 2023 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_DICTMAPTREE_H +#define INC_DICTMAPTREE_H + +#include "engineapi.h" +#include "intdefs.h" + +/* + * Valve's dict/map/tree structures come in various shapes and sizes, so here we + * do the generic macro thing for future proofing. For now we just define a + * CUtlDict (map with string keys) of pointers, with ushort indices, which is + * sufficient for server entity factory lookup, and probably some other stuff. + * Functions for actually modifying the dicts/maps/trees aren't implemented. + */ + +#define DECL_CUTLRBTREE_NODE(name, ktype, vtype, idxtype) \ +typedef typeof(ktype) _cutlrbtree_##name##_key; \ +typedef typeof(vtype) _cutlrbtree_##name##_val; \ +typedef typeof(idxtype) _cutlrbtree_##name##_idx; \ +struct name { \ +	struct { \ +		_cutlrbtree_##name##_idx l, r, p, tags; \ +	} links; \ +	_cutlrbtree_##name##_key k; \ +	_cutlrbtree_##name##_val v; \ +}; + +#define DEF_CUTLRBTREE(name, nodetype) \ +struct name { \ +	bool (*cmp)(const _cutlrbtree_##nodetype##_key *, \ +			const _cutlrbtree_##nodetype##_key *); \ +	struct CUtlMemory elems; \ +	_cutlrbtree_##nodetype##_idx root, count, firstfree, lastalloc; \ +	struct nodetype *nodes; \ +}; \ +\ +static _cutlrbtree_##nodetype##_idx name##_find(const struct name *rb, \ +		const _cutlrbtree_##nodetype##_key k) { \ +	_cutlrbtree_##nodetype##_idx idx = rb->root; \ +	while (idx != (_cutlrbtree_##nodetype##_idx)-1) { \ +		struct nodetype *nodes = rb->elems.mem; \ +		if (rb->cmp(&k, &nodes[idx].k)) idx = nodes[idx].links.l; \ +		else if (rb->cmp(&nodes[idx].k, &k)) idx = nodes[idx].links.r; \ +		else break; \ +	} \ +	return idx; \ +} \ +\ +static inline _cutlrbtree_##nodetype##_val name##_findval(const struct name *rb, \ +		const _cutlrbtree_##nodetype##_key k) { \ +	const _cutlrbtree_##nodetype##_idx idx = name##_find(rb, k); \ +	if (idx == (_cutlrbtree_##nodetype##_idx)-1) { \ +		return (_cutlrbtree_CUtlDict_node_p_ushort_val){0}; \ +	} \ +	struct nodetype *nodes = rb->elems.mem; \ +	return nodes[idx].v; \ +} + +DECL_CUTLRBTREE_NODE(CUtlDict_node_p_ushort, const char *, void *, ushort) +DEF_CUTLRBTREE(CUtlDict_p_ushort, CUtlDict_node_p_ushort) + +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 @@ -14,6 +14,8 @@   * PERFORMANCE OF THIS SOFTWARE.   */ +#include "con_.h" +#include "dictmaptree.h"  #include "engineapi.h"  #include "errmsg.h"  #include "feature.h" @@ -22,6 +24,8 @@  #include "intdefs.h"  #include "mem.h"  #include "vcall.h" +#include "x86.h" +#include "x86util.h"  FEATURE() @@ -45,7 +49,108 @@ void *ent_get(int idx) {  	return e->ent_unknown;  } +struct CEntityFactory { +	struct CEntityFactory_vtable { +		void /*IServerNetworkable*/ *(*VCALLCONV Create)( +				struct CEntityFactory *this, const char *name); +		void (*VCALLCONV Destroy)(struct CEntityFactory *this, +				void /*IServerNetworkable*/ *networkable); +		usize (*VCALLCONV GetEntitySize)(struct CEntityFactory *this); +	} *vtable; +}; + +struct CEntityFactoryDictionary { +	void **vtable; +	struct CUtlDict_p_ushort dict; +}; + +#ifdef _WIN32 // TODO(linux): this'll be different too, leaving out for now +static struct CEntityFactoryDictionary *entfactorydict = 0; +static inline bool find_entfactorydict(con_cmdcb dumpentityfactories_cb) { +	const uchar *insns = (const uchar *)dumpentityfactories_cb; +	for (const uchar *p = insns; p - insns < 64;) { +		// the call to EntityFactoryDictionary() is inlined. that returns a +		// static, which is lazy-inited (trivia: this was old MSVC, so it's not +		// threadsafe like C++ requires nowadays). for some reason the init flag +		// is set using OR, and then the instance is put in ECX to call the ctor +		if (p[0] == X86_ORMRW && p[6] == X86_MOVECXI && p[11] == X86_CALL) { +			entfactorydict = mem_loadptr(p + 7); +			return true; +		} +		NEXT_INSN(p, "entity factory dictionary"); +	} +	return false; +} +#endif + +const struct CEntityFactory *ent_getfactory(const char *name) { +#ifdef _WIN32 +	if (entfactorydict) { +		return CUtlDict_p_ushort_findval(&entfactorydict->dict, name); +	} +#endif +	return 0; +} + +typedef void (*VCALLCONV ctor_func)(void *); +static inline ctor_func findctor(const struct CEntityFactory *factory, +		const char *classname) { +#ifdef _WIN32 +	const uchar *insns = (const uchar *)factory->vtable->Create; +	// every Create() method follows the same pattern. after calling what is +	// presumably operator new(), it copies the return value from EAX into ECX +	// and then calls the constructor. +	for (const uchar *p = insns; p - insns < 32;) { +		if (p[0] == X86_MOVRMW && p[1] == 0xC8 && p[2] == X86_CALL) { +			return (ctor_func)(p + 7 + mem_loadoffset(p + 3)); +		} +		// duping NEXT_INSN macro here in the name of a nicer message +		int len = x86_len(p); +		if (len == -1) { +			errmsg_errorx("unknown or invalid instruction looking for %s " +					"constructor", classname); +			return 0; +		} +		p += len; +	} +#else +#warning TODO(linux): this will be different of course +#endif +	return 0; +} + +void **ent_findvtable(const struct CEntityFactory *factory, +		const char *classname) { +#ifdef _WIN32 +	ctor_func ctor = findctor(factory, classname); +	if (!ctor) return 0; +	const uchar *insns = (const uchar *)ctor; +	// the constructor itself should do *(void**)this = &vtable; almost right +	// away, so look for the first immediate load into indirect register +	for (const uchar *p = insns; p - insns < 24;) { +		if (p[0] == X86_MOVMIW && (p[1] & 0xF8) == 0) return mem_loadptr(p + 2); +		int len = x86_len(p); +		if (len == -1) { +			errmsg_errorx("unknown or invalid instruction looking for %s " +					"vtable pointer", classname); +			return 0; +		} +		p += len; +	} +#else +#warning TODO(linux): this will be different of course +#endif +	return 0; +} +  INIT { +#ifdef _WIN32 // TODO(linux): above +	struct con_cmd *dumpentityfactories = con_findcmd("dumpentityfactories"); +	if (!dumpentityfactories || !find_entfactorydict(dumpentityfactories->cb)) { +		errmsg_warnx("server entity factories unavailable"); +	} +#endif +  	// 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; @@ -18,6 +18,7 @@  #define INC_ENT_H  #include "engineapi.h" +#include "vcall.h"  /* Returns a server-side edict pointer, or null if not found. */  struct edict *ent_getedict(int idx); @@ -25,6 +26,28 @@ struct edict *ent_getedict(int idx);  /* Returns an opaque pointer to a server-side entity, or null if not found. */  void *ent_get(int idx); +struct CEntityFactory; // opaque for now, can move out of ent.c if needed later + +/* + * Returns the CEntityFactory for a given entity name, or null if not found or + * unavailable. This provides a means to create and destroy entities of that + * type on the server, as well as opportunities to dig through instructions to + * find useful stuff. + */ +const struct CEntityFactory *ent_getfactory(const char *name); + +/* + * Attempts to find the virtual table of an entity class given only its factory. + * Returns null if that couldn't be done. Can be used to hook things without + * having an instance of the class up-front, or perform further snooping to get + * even deeper into an entity's code. + * + * The classname parameter is used for error messages on failed instruction + * searches and isn't used for the search itself. + */ +void **ent_findvtable(const struct CEntityFactory *factory, +		const char *classname); +  #endif  // vi: sw=4 ts=4 noet tw=80 cc=80 | 
