summaryrefslogtreecommitdiffhomepage
path: root/src/democustom.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/democustom.c')
-rw-r--r--src/democustom.c138
1 files changed, 138 insertions, 0 deletions
diff --git a/src/democustom.c b/src/democustom.c
new file mode 100644
index 0000000..0a1174f
--- /dev/null
+++ b/src/democustom.c
@@ -0,0 +1,138 @@
+/*
+ * 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 "bitbuf.h"
+#include "con_.h"
+#include "democustom.h"
+#include "demorec.h"
+#include "engineapi.h"
+#include "errmsg.h"
+#include "gamedata.h"
+#include "intdefs.h"
+#include "mem.h"
+#include "ppmagic.h"
+#include "vcall.h"
+
+static int nbits_msgtype, nbits_datalen;
+
+// The engine allows usermessages up to 255 bytes, we add 2 bytes of overhead,
+// and then there's the leading bits before that too (see create_message)
+static char bb_buf[DEMOCUSTOM_MSG_MAX + 4];
+static struct bitbuf bb = {
+ bb_buf, sizeof(bb_buf), sizeof(bb_buf) * 8, 0, false, false, "SST"
+};
+
+static void create_message(struct bitbuf *msg, const void *buf, int len) {
+ // The way we pack our custom demo data is via a user message packet with
+ // type "HudText" - this causes the client to do a text lookup which will
+ // simply silently fail on invalid keys. By making the first byte null
+ // (creating an empty string), we get the rest of the packet to stick in
+ // whatever other data we want.
+ //
+ // Notes from Uncrafted:
+ // > But yeah the data you want to append is as follows:
+ // > - 6 bits (5 bits in older versions) for the message type - should be 23
+ // > for user message
+ bitbuf_appendbits(msg, 23, nbits_msgtype);
+ // > - 1 byte for the user message type - should be 2 for HudText
+ bitbuf_appendbyte(msg, 2);
+ // > - ~~an int~~ 11 or 12 bits for the length of your data in bits,
+ bitbuf_appendbits(msg, len * 8, nbits_datalen); // NOTE: assuming len <= 254
+ // > - your data
+ // [first the aforementioned null byte, plus an arbitrary marker byte to
+ // avoid confusion when parsing the demo later...
+ bitbuf_appendbyte(msg, 0);
+ bitbuf_appendbyte(msg, 0xAC);
+ // ... and then just the data itself]
+ bitbuf_appendbuf(msg, buf, len);
+ // Thanks Uncrafted, very cool!
+}
+
+typedef void (*VCALLCONV WriteMessages_func)(void *this, struct bitbuf *msg);
+static WriteMessages_func WriteMessages = 0;
+
+void democustom_write(const void *buf, int len) {
+ create_message(&bb, buf, len);
+ WriteMessages(demorecorder, &bb);
+ bitbuf_reset(&bb);
+}
+
+// This finds the CDemoRecorder::WriteMessages() function, which takes a raw
+// network packet, wraps it up in the appropriate demo framing format and writes
+// it out to the demo file being recorded.
+static bool find_WriteMessages(void) {
+ // TODO(compat): probably rewrite this to just scan for a call instruction!
+ const uchar *insns = (*(uchar ***)demorecorder)[vtidx_RecordPacket];
+ // RecordPacket calls WriteMessages pretty much right away:
+ // 56 push esi
+ // 57 push edi
+ // 8B F1 mov esi,ecx
+ // 8D BE lea edi,[esi + 0x68c]
+ // 8C 06 00 00
+ // 57 push edi
+ // E8 call CDemoRecorder_WriteMessages
+ // B0 EF FF FF
+ // So we just double check the byte pattern...
+ static const uchar bytes[] =
+#ifdef _WIN32
+ HEXBYTES(56, 57, 8B, F1, 8D, BE, 8C, 06, 00, 00, 57, E8);
+#else
+#warning This is possibly different on Linux too, have a look!
+ {-1, -1, -1, -1, -1, -1};
+#endif
+ if (!memcmp(insns, bytes, sizeof(bytes))) {
+ ssize off = mem_loadoffset(insns + sizeof(bytes));
+ // ... and then offset is relative to the address of whatever is _after_
+ // the call instruction... because x86.
+ WriteMessages = (WriteMessages_func)(insns + sizeof(bytes) + 4 + off);
+ return true;
+ }
+ return false;
+}
+
+DECL_VFUNC_DYN(int, GetEngineBuildNumber)
+
+bool democustom_init(void) {
+ if (!has_vtidx_GetEngineBuildNumber || !has_vtidx_RecordPacket) {
+ errmsg_errorx("missing gamedata entries for this engine");
+ return false;
+ }
+
+ // More UncraftedkNowledge:
+ // > yeah okay so [the usermessage length is] 11 bits if the demo protocol
+ // > is 11 or if the game is l4d2 and the network protocol is 2042.
+ // > otherwise it's 12 bits
+ // > there might be some other l4d2 versions where it's 11 but idk
+ // So here we have to figure out the network protocol version!
+ // NOTE: assuming engclient != null as GEBN index relies on client version
+ int buildnum = VCALL(engclient, GetEngineBuildNumber);
+ // condition is redundant until other GetEngineBuildNumber offsets are added
+ // if (GAMETYPE_MATCHES(L4D2)) {
+ nbits_msgtype = 6;
+ // based on Some Code I Read, buildnum *should* be the protocol version,
+ // however L4D2 returns the actual game version instead, because sure
+ // why not. The only practical difference though is that the network
+ // protocol froze after 2042, so we just have to do a >=. No big deal
+ // really.
+ if (buildnum >= 2042) nbits_datalen = 11; else nbits_datalen = 12;
+ // }
+
+ return find_WriteMessages();
+}
+
+// vi: sw=4 ts=4 noet tw=80 cc=80