From d03e4138f637027908b52764a2ce3669097947c6 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 17 May 2023 23:27:05 +0100 Subject: Add some server entity code-crawling machinery --- src/dictmaptree.h | 79 ++++++++++++++++++++++++++++++++++++++++ src/ent.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ent.h | 23 ++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 src/dictmaptree.h 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 + * + * 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 diff --git a/src/ent.c b/src/ent.c index 4ab0379..8dcb1f1 100644 --- a/src/ent.c +++ b/src/ent.c @@ -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; diff --git a/src/ent.h b/src/ent.h index 0273d86..4d2ddd9 100644 --- a/src/ent.h +++ b/src/ent.h @@ -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 -- cgit v1.2.3