summaryrefslogtreecommitdiffhomepage
path: root/src/ent.c
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 /src/ent.c
parentec85df6d2cbb25211613e550bcc21422ee5eb78e (diff)
Add some server entity code-crawling machinery
Diffstat (limited to 'src/ent.c')
-rw-r--r--src/ent.c105
1 files changed, 105 insertions, 0 deletions
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;