From 6d0db0d5bee0201b732149616a691827367cfb35 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 3 May 2022 04:20:27 +0100 Subject: Add entity property finding and L4D warp testing This was a lot more code than expected, but it might be finally close to time to release the next beta... We'll see if any more rabbit holes present themselves to jump into, though. --- compile | 4 + compile.bat | 10 ++- gamedata/engine.kv | 42 +++++++++- gamedata/entprops.kv | 9 +++ gamedata/gamelib.kv | 26 ++++++- src/build/cmeta.c | 2 +- src/build/cmeta.h | 2 +- src/build/codegen.c | 2 +- src/build/mkentprops.c | 208 +++++++++++++++++++++++++++++++++++++++++++++++++ src/build/mkgamedata.c | 2 +- src/build/skiplist.h | 205 ++++++++++++++++++++++++++++++++++++++++++++++++ src/demorec.c | 2 +- src/engineapi.c | 62 +++++++++++++++ src/engineapi.h | 47 ++++++++++- src/ent.c | 34 +++++--- src/ent.h | 3 + src/gamedata.h | 4 +- src/gametype.h | 31 +++++--- src/l4dwarp.c | 60 ++++++++++++++ src/l4dwarp.h | 26 +++++++ src/sst.c | 5 +- 21 files changed, 747 insertions(+), 39 deletions(-) create mode 100644 gamedata/entprops.kv create mode 100644 src/build/mkentprops.c create mode 100644 src/build/skiplist.h create mode 100644 src/l4dwarp.c create mode 100644 src/l4dwarp.h diff --git a/compile b/compile index 64523b3..02a80d3 100755 --- a/compile +++ b/compile @@ -51,6 +51,7 @@ src="\ gameinfo.c hook.c kv.c + l4dwarp.c nosleep.c sst.c x86.c" @@ -63,8 +64,11 @@ $HOSTCC -O2 -fuse-ld=lld $warnings -D_FILE_OFFSET_BITS=64 -o .build/codegen \ src/build/codegen.c src/build/cmeta.c $HOSTCC -O2 -fuse-ld=lld $warnings -D_FILE_OFFSET_BITS=64 -o .build/mkgamedata \ src/build/mkgamedata.c src/kv.c +$HOSTCC -O2 -fuse-ld=lld $warnings -D_FILE_OFFSET_BITS=64 -o .build/mkentprops \ + src/build/mkentprops.c src/kv.c .build/codegen `for s in $src; do echo "src/$s"; done` .build/mkgamedata gamedata/engine.kv gamedata/gamelib.kv gamedata/inputsystem.kv +.build/mkentprops gamedata/entprops.kv for s in $src; do cc "$s"; done $CC -shared -fpic -fuse-ld=lld -O0 -w -o .build/libtier0.so src/stubs/tier0.c $CC -shared -fpic -fuse-ld=lld -O0 -w -o .build/libvstdlib.so src/stubs/vstdlib.c diff --git a/compile.bat b/compile.bat index 005ce63..c218816 100644 --- a/compile.bat +++ b/compile.bat @@ -36,13 +36,16 @@ set objs=%objs% .build/%basename%.o goto :eof :main -%HOSTCC% -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -ladvapi32 ^ +%HOSTCC% -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS ^ -o .build/codegen.exe src/build/codegen.c src/build/cmeta.c || exit /b -%HOSTCC% -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -ladvapi32 ^ +%HOSTCC% -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS ^ -o .build/mkgamedata.exe src/build/mkgamedata.c src/kv.c || exit /b +%HOSTCC% -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -ladvapi32 ^ +-o .build/mkentprops.exe src/build/mkentprops.c src/kv.c || exit /b .build\codegen.exe src/autojump.c src/con_.c src/demorec.c src/engineapi.c src/ent.c src/extmalloc.c src/fixes.c ^ -src/fov.c src/gamedata.c src/gameinfo.c src/hook.c src/kv.c src/nosleep.c src/rinput.c src/sst.c src/x86.c || exit /b +src/fov.c src/gamedata.c src/gameinfo.c src/hook.c src/kv.c src/l4dwarp.c src/nosleep.c src/rinput.c src/sst.c src/x86.c || exit /b .build\mkgamedata.exe gamedata/engine.kv gamedata/gamelib.kv gamedata/inputsystem.kv || exit /b +.build\mkentprops.exe gamedata/entprops.kv || exit /b llvm-rc /FO .build\dll.res src\dll.rc || exit /b %CC% -shared -O0 -w -o .build/tier0.dll src/stubs/tier0.c %CC% -shared -O0 -w -o .build/vstdlib.dll src/stubs/vstdlib.c @@ -58,6 +61,7 @@ call :cc src/gamedata.c || exit /b call :cc src/gameinfo.c || exit /b call :cc src/hook.c || exit /b call :cc src/kv.c || exit /b +call :cc src/l4dwarp.c || exit /b call :cc src/nosleep.c || exit /b call :cc src/rinput.c || exit /b call :cc src/sst.c || exit /b diff --git a/gamedata/engine.kv b/gamedata/engine.kv index a43d25c..8ee8161 100644 --- a/gamedata/engine.kv +++ b/gamedata/engine.kv @@ -8,7 +8,10 @@ vtidx_RecordPacket 11 // VEngineClient vtidx_GetGameDirectory { Client015 35 // current portal 2 - Client014 { L4D2 73 } // YES IT'S SEVENTY THREE ALL OF A SUDDEN. + Client014 { + L4D2 73 // YES IT'S SEVENTY THREE ALL OF A SUDDEN. + 2013 35 + } Client013 { L4Dx 36 // AND THEN THEY CHANGED IT BACK LATER! default 35 // <- most things have this! @@ -18,4 +21,41 @@ vtidx_GetGameDirectory { vtidx_GetEngineBuildNumber { L4D2 99 } vtidx_PEntityOfEntIndex { OrangeBox 19 } // probably OE too but??? +sz_edict { + default 20 + L4Dbased 16 // see engineapi.h comment +} + +// SendProp +sz_SendProp { + // wrapping all these in 005 for right now. + // will need at least 009 as well at some point! + SrvDLL005 { + OrangeBox 76 + L4D1 80 + L4D2 84 + Portal2 84 + } + //2013 80 // TODO(compat): not sure about 2013/009 yet +} +off_SP_varname { + SrvDLL005 { + OrangeBox 44 + //L4Dbased 48 // TODO(compat): haven't tested Survivors + // for now do this instead: + L4D 48 + Portal2 48 + } + //2013 48 // TODO(compat): not sure about 2013/009 yet pt2 +} +off_SP_offset { + SrvDLL005 { + OrangeBox 68 + L4D1 72 + L4D2 76 + Portal2 76 + } + //2013 72 // TODO(compat): not sure about 2013/009 yet pt3 +} + // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/gamedata/entprops.kv b/gamedata/entprops.kv new file mode 100644 index 0000000..cd19ae8 --- /dev/null +++ b/gamedata/entprops.kv @@ -0,0 +1,9 @@ +// This follows a different format to the other gamedata files. +// It simply assigns variable names to network table property name strings. +// Network names are classname/propname, e.g. CBasePlayer/m_fWhatever + +off_entpos CBaseEntity/m_vecOrigin +// look angles, currently just for L4D1/2, can add other games as needed +off_eyeang "CCSPlayer/m_angEyeAngles[0]" + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/gamedata/gamelib.kv b/gamedata/gamelib.kv index 7d8978b..e7a4302 100644 --- a/gamedata/gamelib.kv +++ b/gamedata/gamelib.kv @@ -9,7 +9,31 @@ vtidx_CheckJumpButton { } off_mv 8 +// IServerGameDLL +vtidx_GetAllServerClasses { + default 10 + 2013 11 + // TODO(compat): BMS 11 +} + // I(Server|Client)Unknown -vtidx_GetBaseEntity 4 +vtidx_GetBaseEntity "4 + NVDTOR" + +// CBaseEntity or CBasePlayer or something +off_netprop_statechanged { L4D 88 } +off_simtime { L4D 128 } +vtidx_Teleport { + L4D "104 + NVDTOR" + L4D2 { + default "116 + NVDTOR" // TODO(linux): might actually be 119!?!? + // TODO(compat): would like to be able to specify this here. + // see also engineapi.c + //TLS "117 + NVDTOR" + } +} + +// CGlobalVars +off_curtime 12 +off_edicts { L4D 88 } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/cmeta.c b/src/build/cmeta.c index 3d9281a..bf18903 100644 --- a/src/build/cmeta.c +++ b/src/build/cmeta.c @@ -5,7 +5,7 @@ * 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 + * 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 diff --git a/src/build/cmeta.h b/src/build/cmeta.h index 20672f4..4757654 100644 --- a/src/build/cmeta.h +++ b/src/build/cmeta.h @@ -5,7 +5,7 @@ * 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 + * 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 diff --git a/src/build/codegen.c b/src/build/codegen.c index 6d5fc99..d96059d 100644 --- a/src/build/codegen.c +++ b/src/build/codegen.c @@ -5,7 +5,7 @@ * 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 + * 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 diff --git a/src/build/mkentprops.c b/src/build/mkentprops.c new file mode 100644 index 0000000..5dd2fea --- /dev/null +++ b/src/build/mkentprops.c @@ -0,0 +1,208 @@ +/* + * Copyright © 2022 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. + */ + +#include +#include +#include +#include + +#include "../intdefs.h" +#include "../kv.h" +#include "../noreturn.h" +#include "../os.h" +#include "skiplist.h" +#include "vec.h" + +#ifdef _WIN32 +#define fS "S" +#else +#define fS "s" +#endif + +static noreturn die(const char *s) { + fprintf(stderr, "mkentprops: %s\n", s); + exit(100); +} + +struct prop { + const char *varname; /* the C global name */ + const char *propname; /* the entity property name */ + struct prop *next; +}; +struct vec_prop VEC(struct prop *); + +DECL_SKIPLIST(static, class, struct class, const char *, 4) +struct class { + const char *name; /* the entity class name */ + struct vec_prop props; + struct skiplist_hdr_class hdr; +}; +static inline int cmp_class(struct class *c, const char *s) { + return strcmp(c->name, s); +} +static inline struct skiplist_hdr_class *hdr_class(struct class *c) { + return &c->hdr; +} +DEF_SKIPLIST(static, class, cmp_class, hdr_class) +static struct skiplist_hdr_class classes = {0}; +static int nclasses = 0; + +struct parsestate { + const os_char *filename; + struct kv_parser *parser; + char *lastvar; +}; + +static noreturn badparse(struct parsestate *state, const char *e) { + fprintf(stderr, "mkentprops: %" fS ":%d:%d: parse error: %s", + state->filename, state->parser->line, state->parser->col, e); + exit(1); +} + +static void kv_cb(enum kv_token type, const char *p, uint len, void *ctxt) { + struct parsestate *state = ctxt; + switch (type) { + case KV_IDENT: case KV_IDENT_QUOTED: + state->lastvar = malloc(len + 1); + if (!state->lastvar) die("couldn't allocate memory"); + memcpy(state->lastvar, p, len); state->lastvar[len] = '\0'; + break; + case KV_NEST_START: badparse(state, "unexpected nested block"); + case KV_NEST_END: badparse(state, "unexpected closing brace"); + case KV_VAL: case KV_VAL_QUOTED:; + struct prop *prop = malloc(sizeof(*prop)); + if (!p) die("couldn't allocate memory"); + prop->varname = state->lastvar; + char *classname = malloc(len + 1); + if (!classname) die("couldn't allocate memory"); + memcpy(classname, p, len); classname[len] = '\0'; + char *propname = strchr(classname, '/'); + if (!propname) { + badparse(state, "network name not in class/prop format"); + } + *propname = '\0'; ++propname; // split! + prop->propname = propname; + struct class *class = skiplist_get_class(&classes, classname); + if (!class) { + class = malloc(sizeof(*class)); + if (!class) die("couldn't allocate memory"); + *class = (struct class){.name = classname}; + skiplist_insert_class(&classes, classname, class); + ++nclasses; + } + // (if class is already there just leak classname, no point freeing) + if (!vec_push(&class->props, prop)) die("couldn't append to array"); + break; + case KV_COND_PREFIX: case KV_COND_SUFFIX: + badparse(state, "unexpected conditional"); + } +} + +static inline noreturn diewrite(void) { die("couldn't write to file"); } + +#define _(x) \ + if (fprintf(out, "%s\n", x) < 0) diewrite(); +#define F(f, ...) \ + if (fprintf(out, f "\n", __VA_ARGS__) < 0) diewrite(); +#define H() \ +_( "/* This file is autogenerated by src/build/mkentprops.c. DO NOT EDIT! */") \ +_( "") + +static void decls(FILE *out) { + for (struct class *c = classes.x[0]; c; c = c->hdr.x[0]) { + for (struct prop **pp = c->props.data; + pp - c->props.data < c->props.sz; ++pp) { +F( "extern bool has_%s;", (*pp)->varname) +F( "extern int %s;", (*pp)->varname) + } + } +} + +static void defs(FILE *out) { + for (struct class *c = classes.x[0]; c; c = c->hdr.x[0]) { + for (struct prop **pp = c->props.data; + pp - c->props.data < c->props.sz; ++pp) { +F( "bool has_%s = false;", (*pp)->varname) +F( "int %s;", (*pp)->varname) + } + } +_( "") +_( "static void initentprops(struct ServerClass *class) {") +F( " for (int needclasses = %d; class; class = class->next) {", nclasses) + char *else1 = ""; + for (struct class *c = classes.x[0]; c; c = c->hdr.x[0]) { + // TODO(opt): some sort of PHF instead of chained strcmp, if we ever + // have more than a few classes/properties? +F( " %sif (!strcmp(class->name, \"%s\")) {", else1, c->name) +_( " struct SendTable *st = class->table;") + // christ this is awful :( +F( " int needprops = %d;", c->props.sz) +_( " for (struct SendProp *p = st->props; (char *)p -") +_( " (char *)st->props < st->nprops * sz_SendProp;") +_( " p = mem_offset(p, sz_SendProp)) {") + char *else2 = ""; + for (struct prop **pp = c->props.data; + pp - c->props.data < c->props.sz; ++pp) { +F( " %sif (!strcmp(*(const char **)mem_offset(p, off_SP_varname), \"%s\")) {", + else2, (*pp)->propname) // ugh +F( " has_%s = true;", (*pp)->varname) +F( " %s = *(int *)mem_offset(p, off_SP_offset);", (*pp)->varname) +_( " if (!--needprops) break;") +_( " }") + else2 = "else "; + } +_( " }") +_( " if (!--needclasses) break;") +_( " }") + else1 = "else "; + } +_( " }") +_( "}") +} + +int OS_MAIN(int argc, os_char *argv[]) { + for (++argv; *argv; ++argv) { + int fd = os_open(*argv, O_RDONLY); + if (fd == -1) die("couldn't open file"); + struct kv_parser kv = {0}; + struct parsestate state = {*argv, &kv}; + char buf[1024]; + int nread; + while (nread = read(fd, buf, sizeof(buf))) { + if (nread == -1) die("couldn't read file"); + if (!kv_parser_feed(&kv, buf, nread, &kv_cb, &state)) goto ep; + } + if (!kv_parser_done(&kv)) { +ep: fprintf(stderr, "mkentprops: %" fS ":%d:%d: bad syntax: %s\n", + *argv, kv.line, kv.col, kv.errmsg); + exit(1); + } + close(fd); + } + + FILE *out = fopen(".build/include/entprops.gen.h", "wb"); + if (!out) die("couldn't open entprops.gen.h"); + H(); + decls(out); + + out = fopen(".build/include/entpropsinit.gen.h", "wb"); + if (!out) die("couldn't open entpropsinit.gen.h"); + H(); + defs(out); + return 0; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/build/mkgamedata.c b/src/build/mkgamedata.c index d17d7df..ce6490e 100644 --- a/src/build/mkgamedata.c +++ b/src/build/mkgamedata.c @@ -5,7 +5,7 @@ * 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 + * 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 diff --git a/src/build/skiplist.h b/src/build/skiplist.h new file mode 100644 index 0000000..b747d89 --- /dev/null +++ b/src/build/skiplist.h @@ -0,0 +1,205 @@ +/* + * Copyright © 2022 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_SKIPLIST_H +#define INC_SKIPLIST_H + +#include + +#include "../intdefs.h" +#include "../os.h" + +#ifdef _WIN32 +static inline int _skiplist_ffs(uint x) { + uint ret; + // on Windows, sizeof(ulong) == sizeof(uint) + if (_BitScanForward((ulong *)&ret, x)) return ret + 1; else return 0; +} +#else +#include +#define _skiplist_ffs ffs +#endif + +// WARNING: this is a really hacked-up version of the skiplist.h from cbits in +// order to support windows. It probably isn't a good idea to plop straight into +// your own use case. + +#if defined(__GNUC__) || defined(__clang__) +#define _skiplist_unused __attribute__((unused)) // heck off gcc +#else +#define _skiplist_unused +#endif + +// NOTE: using xoroshiro128++, a comparatively bad (i.e. non-cryptographic) prng +// for the sake of simplicity; original cbits skiplist.h relies on libcpoly to +// get arc4random() everywhere but since we're only using this at build time +// that seemed like a silly dependency to bother with. +//#define _skiplist_rng arc4random + +// ALSO NOTE: the PRNG code here is *decidedly not* thread safe. again, this +// isn't a problem for our use case. just keep it in mind if reusing this header +// for something else. or ideally, don't reuse this header for something else... +static inline uvlong _skiplist_rotl(const uvlong x, int k) { + return (x << k) | (x >> (64 - k)); +} +_skiplist_unused static uvlong _skiplist_rng(void) { + static uvlong s[2]; + static bool init = false; + if (!init) { os_randombytes(s, sizeof(s)); init = true; } + uvlong s0 = s[0], s1 = s[1]; + uvlong ret = _skiplist_rotl(s0 * 5, 7) * 9; + s1 ^= s0; + s[0] = _skiplist_rotl(s0, 24) ^ s1 ^ (s1 << 16); + s[1] = _skiplist_rotl(s1, 37); + return ret; +} + +/* + * Declares the skiplist header struct skiplist_hdr##name, but none of the + * associated functions. Use when the structure needs to be passed around in + * some way but actual operations on the list are a private implementation + * detail. Otherwise, see DECL_SKIPLIST below. + */ +#define DECL_SKIPLIST_TYPE(name, dtype, ktype, levels) \ +typedef dtype _skiplist_dt_##name; \ +typedef ktype _skiplist_kt_##name; \ +enum { skiplist_lvls_##name = (levels) }; \ +struct skiplist_hdr_##name { dtype *x[levels]; }; + +/* + * Declares the skiplist header struct skiplist_hdr_##name, with functions + * skiplist_{get,del,pop,insert}_##name for operating on the list. A single + * occurrence of DEF_SKIPLIST is required to actually implement the + * functions. + * + * This macro implies DECL_SKIPLIST_TYPE (both should not be used). + * + * mod should be either static or extern. + * + * dtype should be the struct type that the skiplist header will be embedded in, + * forming the linked structure. + * + * ktype should be the type of the struct member used for comparisons, for + * example int or char *. + * + * levels should be the number of levels in each node. 4 is probably a + * reasonable number, depending on the size of the structure and how many + * entries need to be stored and looked up. + * + * The resulting get, del, pop and insert functions are hopefully self- + * explanatory - get and del return the relevant node or a null pointer if + * no such node is found. + */ +#define DECL_SKIPLIST(mod, name, dtype, ktype, levels) \ +DECL_SKIPLIST_TYPE(name, dtype, ktype, levels) \ +\ +_skiplist_unused mod dtype *skiplist_get_##name(struct skiplist_hdr_##name *l, \ + ktype k); \ +_skiplist_unused mod dtype *skiplist_del_##name(struct skiplist_hdr_##name *l, \ + ktype k); \ +_skiplist_unused mod dtype *skiplist_pop_##name(struct skiplist_hdr_##name *l); \ +_skiplist_unused mod void skiplist_insert_##name(struct skiplist_hdr_##name *l, \ + ktype k, dtype *node); + +/* + * Implements the functions corresponding to a skiplist - must come after + * DECL_SKIPLIST with the same modifier and name. + * + * compfunc should be a function declared as follows (or an equivalent macro): + * int cf(dtype *x, ktype y); + * + * hdrfunc should be a function declared as follows (or an equivalent macro): + * struct skiplist_hdr_##name *hf(dtype *l); + */ +#define DEF_SKIPLIST(mod, name, compfunc, hdrfunc) \ +static inline int _skiplist_lvl_##name(void) { \ + int i; \ + /* for 2 levels we get 1 50% of the time, 2 25% of the time, 0 25% of the + time. loop if 0 to distribute this evenly (this gets less likely the more + levels there are: at 4 levels, only loops 6% of the time) */ \ + while (!(i = _skiplist_ffs(_skiplist_rng() & \ + ((1 << skiplist_lvls_##name) - 1)))); \ + /* ffs gives bit positions as 1-N but we actually want an array index */ \ + return i - 1; \ +} \ +\ +_skiplist_unused \ +mod _skiplist_dt_##name *skiplist_get_##name(struct skiplist_hdr_##name *l, \ + _skiplist_kt_##name k) { \ + for (int cmp, lvl = skiplist_lvls_##name - 1; lvl > -1; --lvl) { \ + while (l->x[lvl] && (cmp = compfunc(l->x[lvl], k)) < 0) { \ + l = hdrfunc(l->x[lvl]); \ + } \ + /* NOTE: cmp can be uninitialised here, but only if the list is + completely empty, in which case we'd return 0 anyway - so it doesn't + actually matter! */ \ + if (cmp == 0) return l->x[lvl]; \ + } \ + /* reached the end, no match */ \ + return 0; \ +} \ +\ +_skiplist_unused \ +_skiplist_dt_##name *skiplist_del_##name(struct skiplist_hdr_##name *l, \ + _skiplist_kt_##name k) { \ + _skiplist_dt_##name *ret = 0; \ + /* ALSO NOTE: in *this* case, cmp DOES need to be initialised to prevent a + possible null-deref via hdrfunc(l->x[lvl])->x */ \ + for (int cmp = 1, lvl = skiplist_lvls_##name - 1; lvl > -1; --lvl) { \ + while (l->x[lvl] && (cmp = compfunc(l->x[lvl], k)) < 0) { \ + l = hdrfunc(l->x[lvl]); \ + } \ + if (cmp == 0) { \ + ret = l->x[lvl]; \ + /* just shift each link by 1 */ \ + l->x[lvl] = hdrfunc(l->x[lvl])->x[0]; \ + /* ... and update every level of links via loop */ \ + } \ + } \ + /* reached the end, return whatever was found */ \ + return ret; \ +} \ +\ +_skiplist_unused \ +mod _skiplist_dt_##name *skiplist_pop_##name(struct skiplist_hdr_##name *l) { \ + _skiplist_dt_##name *cur = l->x[0]; \ + if (!cur) return 0; \ + l->x[0] = hdrfunc(cur)->x[0]; \ + for (int lvl = 1; lvl < skiplist_lvls_##name; ++lvl) { \ + if (l->x[lvl]) l->x[lvl] = hdrfunc(l->x[lvl])->x[lvl]; \ + } \ + return cur; \ +} \ +\ +_skiplist_unused \ +mod void skiplist_insert_##name(struct skiplist_hdr_##name *l, \ + _skiplist_kt_##name k, _skiplist_dt_##name *node) { \ + /* note: higher levels are unset but also skipped in other searches */ \ + int inslvl = _skiplist_lvl_##name(); \ + for (int lvl = skiplist_lvls_##name - 1; lvl > -1; --lvl) { \ + while (l->x[lvl] && compfunc(l->x[lvl], k) < 0) { \ + l = hdrfunc(l->x[lvl]); \ + } \ + if (lvl <= inslvl) { \ + hdrfunc(node)->x[lvl] = l->x[lvl]; \ + l->x[lvl] = node; \ + } \ + } \ +} \ + +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/demorec.c b/src/demorec.c index 9624db9..7875e30 100644 --- a/src/demorec.c +++ b/src/demorec.c @@ -363,7 +363,7 @@ bool demorec_custom_init(void) { // really. if (buildnum >= 2042) nbits_datalen = 11; else nbits_datalen = 12; // } - + return find_WriteMessages(); } diff --git a/src/engineapi.c b/src/engineapi.c index 54671a8..54b9128 100644 --- a/src/engineapi.c +++ b/src/engineapi.c @@ -14,9 +14,15 @@ * PERFORMANCE OF THIS SOFTWARE. */ +#include // used in generated code +#include // " + #include "engineapi.h" +#include "gamedata.h" #include "gametype.h" #include "intdefs.h" +#include "mem.h" // " +#include "vcall.h" u64 _gametype_tag = 0; // declared in gametype.h but seems sensible enough here @@ -26,6 +32,15 @@ ifacefactory factory_client = 0, factory_server = 0, factory_engine = 0, struct VEngineClient *engclient; struct VEngineServer *engserver; +// this seems to be very stable, thank goodness +DECL_VFUNC(void *, GetGlobalVars, 1) +void *globalvars; + +DECL_VFUNC_DYN(void *, GetAllServerClasses) +DECL_VFUNC_DYN(int, GetEngineBuildNumber) + +#include + void engineapi_init(void) { if (engclient = factory_engine("VEngineClient015", 0)) { _gametype_tag |= _gametype_tag_Client015; @@ -45,6 +60,53 @@ void engineapi_init(void) { } // else if (engserver = others as needed...) { // } + + void *pim = factory_server("PlayerInfoManager002", 0); + if (pim) globalvars = VCALL(pim, GetGlobalVars); + + void *srvdll; + // TODO(compat): add this back when there's gamedata for 009 (no point atm) + /*if (srvdll = factory_engine("ServerGameDLL009", 0)) { + _gametype_tag |= _gametype_tag_SrvDLL009; + }*/ + if (srvdll = factory_server("ServerGameDLL005", 0)) { + _gametype_tag |= _gametype_tag_SrvDLL005; + } + + // need to do this now; ServerClass network table iteration requires + // SendProp offsets + gamedata_init(); + + // TODO(compat): we need this terrible hack for now because TLS somehow + // changed the entity vtable layout and I've yet to think of a way to make + // gamedata more flexible to handle that properly. I blame JAiZ. + if (engclient && has_vtidx_GetEngineBuildNumber && + VCALL(engclient, GetEngineBuildNumber) >= 2200) { + ++vtidx_Teleport; + } + + if (has_vtidx_GetAllServerClasses && has_sz_SendProp && + has_off_SP_varname && has_off_SP_offset) { + struct ServerClass *svclass = VCALL(srvdll, GetAllServerClasses); + initentprops(svclass); +#if 0 // just keeping a note of this testing code for now, might delete later + for (; svclass; svclass = svclass->next) { + struct SendTable *st = svclass->table; + for (struct SendProp *p = st->props; (char *)p - + (char *)st->props < st->nprops * sz_SendProp; + p = mem_offset(p, sz_SendProp)) { + if (!strcmp(*(const char **)mem_offset(p, off_SP_varname), + "m_angEyeAngles[0]")) { + con_msg("%s\n", svclass->name); + con_msg(" %s\n", st->tablename); + con_msg(" %s = %d\n", *(const char **)mem_offset(p, + off_SP_varname), *(int *)mem_offset(p, off_SP_offset)); + return; + } + } + } +#endif + } } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/engineapi.h b/src/engineapi.h index dcf3bbe..6def65b 100644 --- a/src/engineapi.h +++ b/src/engineapi.h @@ -42,13 +42,11 @@ struct VEngineClient { void **vtable; /* opaque fields */ }; -extern struct VEngineClient *engclient; struct VEngineServer { void **vtable; /* opaque fields */ }; -extern struct VEngineServer *engserver; struct CUtlMemory { void *mem; @@ -61,10 +59,14 @@ struct CUtlVector { }; struct edict { + // CBaseEdict int stateflags; int netserial; void *ent_networkable; void *ent_unknown; + // edict_t + // NOTE! *REMOVED* in l4d-based branches. don't iterate over edict pointers! + float freetime; }; struct vec3f { float x, y, z; }; @@ -84,15 +86,52 @@ struct CMoveData { struct vec3f origin; }; +#define SENDPROP_INT 0 +#define SENDPROP_FLOAT 1 +#define SENDPROP_VEC 2 +#define SENDPROP_VECXY 3 +#define SENDPROP_STR 4 +#define SENDPROP_ARRAY 5 +#define SENDPROP_DTABLE 6 +#define SENDPROP_INT64 7 + +// these have to be opaque because, naturally, they're unstable between +// branches - access stuff using gamedata offsets as usual +struct RecvProp; +struct SendProp; + +// these two things seem to be stable enough for our purposes +struct SendTable { + struct SendProp *props; + int nprops; + char *tablename; + void *precalc; + bool inited : 1; + bool waswritten : 1; + /* "has props encoded against current tick count" ??? */ + bool haspropsenccurtickcnt : 1; +}; +struct ServerClass { + char *name; + struct SendTable *table; + struct ServerClass *next; + int id; + int instbaselineidx; +}; + /// }}} +extern struct VEngineClient *engclient; +extern struct VEngineServer *engserver; +extern void *globalvars; + /* * Called on plugin init to attempt to initialise various core interfaces. * Doesn't return an error result, because the plugin can still load even if * this stuff is missing. * - * Also performs additional gametype detection after con_init(), before - * gamedata_init(). + * Also performs additional gametype detection after con_init(), and calls + * gamedata_init() to setup offsets and such. */ void engineapi_init(void); diff --git a/src/ent.c b/src/ent.c index b621e0b..2e47208 100644 --- a/src/ent.c +++ b/src/ent.c @@ -21,29 +21,39 @@ #include "gamedata.h" #include "gametype.h" #include "intdefs.h" +#include "mem.h" #include "vcall.h" DECL_VFUNC_DYN(void *, PEntityOfEntIndex, int) +static struct edict **edicts = 0; -void *ent_get(int idx) { - // TODO(compat): Based on previous attempts at this, for L4D2, we need - // factory_server("PlayerInfoManager002")->GetGlobalVars()->edicts - // (offset 22 or so). Then get edicts from that. For now, we only need this - // for Portal FOV stuff, so just doing this eiface stuff. +void *ent_getedict(int idx) { + if (edicts) { + // globalvars->edicts seems to be null when disconnected + if (!*edicts) return 0; + return mem_offset(*edicts, sz_edict * idx); + } + else { + return VCALL(engserver, PEntityOfEntIndex, idx); + } +} - struct edict *e = VCALL(engserver, PEntityOfEntIndex, idx); +void *ent_get(int idx) { + struct edict *e = ent_getedict(idx); if (!e) return 0; return e->ent_unknown; } bool ent_init(void) { - if (!has_vtidx_PEntityOfEntIndex) { - con_warn("ent: missing gamedata entries for this engine\n"); - return false; + // 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; + if (globalvars && has_off_edicts) { + edicts = mem_offset(globalvars, off_edicts); + return true; } - // for PEntityOfEntIndex we don't really have to do any more init, we can - // just call the function later. - return true; + con_warn("ent: not implemented for this engine\n"); + return false; } // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/ent.h b/src/ent.h index c781259..98657ce 100644 --- a/src/ent.h +++ b/src/ent.h @@ -19,6 +19,9 @@ #include +#include "engineapi.h" + +struct edict *ent_getedict(int idx); void *ent_get(int idx); bool ent_init(void); diff --git a/src/gamedata.h b/src/gamedata.h index b5e4ed7..678d572 100644 --- a/src/gamedata.h +++ b/src/gamedata.h @@ -1,5 +1,5 @@ /* - * Copyright © 2021 Michael Smith + * Copyright © 2022 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 @@ -25,6 +25,8 @@ #define NVDTOR 2 #endif #include +// entprops are built by a different tool, in a different header for simplicity +#include #undef NVDTOR void gamedata_init(void); diff --git a/src/gametype.h b/src/gametype.h index 7c8fa3f..d7d0b3e 100644 --- a/src/gametype.h +++ b/src/gametype.h @@ -21,29 +21,40 @@ extern u64 _gametype_tag; +/* general engine branches used in a bunch of stuff */ #define _gametype_tag_OE 1 -// TODO(compat): detect in con_init, even if just to fail (VEngineServer broke) +#define _gametype_tag_OrangeBox (1 << 1) +#define _gametype_tag_2013 (1 << 2) + +/* specific games with dedicated branches / engine changes */ +// TODO(compat): detect dmomm, even if only just to fail (VEngineServer broke) // TODO(compat): buy dmomm in a steam sale to implement and test the above, lol -#define _gametype_tag_DMoMM (1 << 1) -#define _gametype_tag_OrangeBox (1 << 2) -#define _gametype_tag_L4D1 (1 << 3) -#define _gametype_tag_L4D2 (1 << 4) -#define _gametype_tag_L4DS (1 << 5) -#define _gametype_tag_Portal1 (1 << 6) +#define _gametype_tag_DMoMM (1 << 3) +#define _gametype_tag_L4D1 (1 << 4) +#define _gametype_tag_L4D2 (1 << 5) +#define _gametype_tag_L4DS (1 << 6) /* Survivors (weird arcade port) */ #define _gametype_tag_Portal2 (1 << 7) -#define _gametype_tag_2013 (1 << 8) + +/* games needing game-specific stuff, but not tied to a singular branch */ +#define _gametype_tag_Portal1 (1 << 8) + +/* VEngineClient versions */ #define _gametype_tag_Client015 (1 << 9) #define _gametype_tag_Client014 (1 << 10) #define _gametype_tag_Client013 (1 << 11) #define _gametype_tag_Client012 (1 << 12) #define _gametype_tag_Server021 (1 << 13) +/* ServerGameDLL versions */ +#define _gametype_tag_SrvDLL009 (1 << 14) // 2013-ish +#define _gametype_tag_SrvDLL005 (1 << 15) // mostly everything else, it seems + +/* Matches for any multiple possible tags */ #define _gametype_tag_L4D (_gametype_tag_L4D1 | _gametype_tag_L4D2) // XXX: *stupid* naming, refactor later (damn Survivors ruining everything) #define _gametype_tag_L4D2x (_gametype_tag_L4D2 | _gametype_tag_L4DS) #define _gametype_tag_L4Dx (_gametype_tag_L4D1 | _gametype_tag_L4D2x) -#define _gametype_tag_L4Dbased \ - (_gametype_tag_L4D1 | _gametype_tag_L4D2x | _gametype_tag_Portal2) +#define _gametype_tag_L4Dbased (_gametype_tag_L4Dx | _gametype_tag_Portal2) #define _gametype_tag_OrangeBoxbased \ (_gametype_tag_OrangeBox | _gametype_tag_2013) diff --git a/src/l4dwarp.c b/src/l4dwarp.c new file mode 100644 index 0000000..94b7911 --- /dev/null +++ b/src/l4dwarp.c @@ -0,0 +1,60 @@ +/* + * Copyright © 2022 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. + */ + +#define _USE_MATH_DEFINES // ... windows. +#include +#include + +#include "con_.h" +#include "engineapi.h" +#include "ent.h" +#include "gamedata.h" +#include "gametype.h" +#include "intdefs.h" +#include "mem.h" +#include "vcall.h" + +DECL_VFUNC_DYN(void *, GetBaseEntity) +DECL_VFUNC_DYN(void, Teleport, const struct vec3f *pos, const struct vec3f *ang, + const struct vec3f *vel) + +DEF_CCMD_HERE_UNREG(sst_l4d_testwarp, "Simulate a bot warping to you", + CON_SERVERSIDE) { + struct edict *ed = ent_getedict(con_cmdclient + 1); + if (!ed) { con_warn("error: couldn't access player entity\n"); return; } + void *e = VCALL(ed->ent_unknown, GetBaseEntity); // is this call required? + struct vec3f *org = mem_offset(e, off_entpos); + struct vec3f *ang = mem_offset(e, off_eyeang); + // L4D idle warps go up to 10 units behind relative to whatever angle the + // player is facing, lessening the distance based on pitch angle but never + // displacing vertically + float pitch = ang->x * M_PI / 180, yaw = ang->y * M_PI / 180; + float shift = -10 * cos(pitch); + VCALL(e, Teleport, &(struct vec3f){org->x + shift * cos(yaw), + org->y + shift * sin(yaw), org->z}, 0, &(struct vec3f){0, 0, 0}); +} + +bool l4dwarp_init(void) { + if (!GAMETYPE_MATCHES(L4Dx)) return false; + if (!has_off_entpos || !has_off_eyeang || !has_vtidx_Teleport) { + con_warn("l4dwarp: missing gamedata entries for this engine\n"); + return false; + } + con_reg(sst_l4d_testwarp); + return true; +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/l4dwarp.h b/src/l4dwarp.h new file mode 100644 index 0000000..5328fcf --- /dev/null +++ b/src/l4dwarp.h @@ -0,0 +1,26 @@ +/* + * Copyright © 2022 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_L4DWARP_H +#define INC_L4DWARP_H + +#include + +bool l4dwarp_init(void); + +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/sst.c b/src/sst.c index e75c9d8..13e8b19 100644 --- a/src/sst.c +++ b/src/sst.c @@ -32,6 +32,7 @@ #include "gameinfo.h" #include "gametype.h" #include "hook.h" +#include "l4dwarp.h" #include "nosleep.h" #include "os.h" #include "rinput.h" @@ -201,12 +202,11 @@ static bool already_loaded = false, skip_unload = false; static bool do_load(ifacefactory enginef, ifacefactory serverf) { factory_engine = enginef; factory_server = serverf; if (!con_init(enginef, ifacever)) return false; - engineapi_init(); // load some other interfaces + engineapi_init(); // load some other interfaces. also calls gamedata_init() // detect p1 for the benefit of specific features if (!GAMETYPE_MATCHES(Portal2) && con_findcmd("upgrade_portalgun")) { _gametype_tag |= _gametype_tag_Portal1; } - gamedata_init(); if (!gameinfo_init()) { con_disconnect(); return false; } const void **p = vtable_firstdiff; @@ -263,6 +263,7 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) { //if (has_demorec) demorec_custom_init(); bool has_ent = ent_init(); has_fov = fov_init(has_ent); + if (has_ent) l4dwarp_init(); has_nosleep = nosleep_init(); #ifdef _WIN32 has_rinput = rinput_init(); -- cgit v1.2.3