summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMichael Smith <mikesmiffy128@gmail.com>2023-05-17 23:27:05 +0100
committerMichael Smith <mikesmiffy128@gmail.com>2023-05-17 23:33:33 +0100
commitd03e4138f637027908b52764a2ce3669097947c6 (patch)
treec6596f4169a11410679bbd3add4415b4cc6cc002
parentec85df6d2cbb25211613e550bcc21422ee5eb78e (diff)
Add some server entity code-crawling machinery
-rw-r--r--src/dictmaptree.h79
-rw-r--r--src/ent.c105
-rw-r--r--src/ent.h23
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
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