diff options
Diffstat (limited to 'src/ent.c')
-rw-r--r-- | src/ent.c | 105 |
1 files changed, 105 insertions, 0 deletions
@@ -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; |