summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rwxr-xr-xcompile4
-rw-r--r--compile.bat10
-rw-r--r--gamedata/engine.kv42
-rw-r--r--gamedata/entprops.kv9
-rw-r--r--gamedata/gamelib.kv26
-rw-r--r--src/build/cmeta.c2
-rw-r--r--src/build/cmeta.h2
-rw-r--r--src/build/codegen.c2
-rw-r--r--src/build/mkentprops.c208
-rw-r--r--src/build/mkgamedata.c2
-rw-r--r--src/build/skiplist.h205
-rw-r--r--src/demorec.c2
-rw-r--r--src/engineapi.c62
-rw-r--r--src/engineapi.h47
-rw-r--r--src/ent.c34
-rw-r--r--src/ent.h3
-rw-r--r--src/gamedata.h4
-rw-r--r--src/gametype.h31
-rw-r--r--src/l4dwarp.c60
-rw-r--r--src/l4dwarp.h26
-rw-r--r--src/sst.c5
21 files changed, 747 insertions, 39 deletions
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 <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.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <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_SKIPLIST_H
+#define INC_SKIPLIST_H
+
+#include <stdlib.h>
+
+#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 <strings.h>
+#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 <stdbool.h> // used in generated code
+#include <string.h> // "
+
#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 <entpropsinit.gen.h>
+
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 <stdbool.h>
+#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 <mikesmiffy128@gmail.com>
+ * Copyright © 2022 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
@@ -25,6 +25,8 @@
#define NVDTOR 2
#endif
#include <gamedata.gen.h>
+// entprops are built by a different tool, in a different header for simplicity
+#include <entprops.gen.h>
#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 <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.
+ */
+
+#define _USE_MATH_DEFINES // ... windows.
+#include <math.h>
+#include <stdbool.h>
+
+#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 <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_L4DWARP_H
+#define INC_L4DWARP_H
+
+#include <stdbool.h>
+
+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();