diff options
author | Michael Smith <mikesmiffy128@gmail.com> | 2023-05-17 23:27:05 +0100 |
---|---|---|
committer | Michael Smith <mikesmiffy128@gmail.com> | 2023-05-17 23:33:33 +0100 |
commit | d03e4138f637027908b52764a2ce3669097947c6 (patch) | |
tree | c6596f4169a11410679bbd3add4415b4cc6cc002 | |
parent | ec85df6d2cbb25211613e550bcc21422ee5eb78e (diff) |
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 |