From 4721f53cb21dfa18dc787581fac0d2a25f0a9690 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Wed, 20 Jul 2022 18:12:00 +0100 Subject: Split custom demo data into its own file/feature Copyright note: the stuff Bill wrote is all still in the other file. --- src/democustom.c | 138 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/democustom.h | 35 ++++++++++++++ src/demorec.c | 114 +-------------------------------------------- src/demorec.h | 14 ++---- src/sst.c | 6 +-- 5 files changed, 180 insertions(+), 127 deletions(-) create mode 100644 src/democustom.c create mode 100644 src/democustom.h (limited to 'src') 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 + * + * 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 "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 diff --git a/src/democustom.h b/src/democustom.h new file mode 100644 index 0000000..9f9bbe9 --- /dev/null +++ b/src/democustom.h @@ -0,0 +1,35 @@ +/* + * 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_DEMOCUSTOM_H +#define INC_DEMOCUSTOM_H + +#include + +/* maximum length of a custom demo message, in bytes */ +#define DEMOCUSTOM_MSG_MAX 253 + +/* + * Write a block of up to DEMOWRITER_MSG_MAX bytes into the currently recording + * demo - NOT bounds checked, caller MUST ensure length is okay! + */ +void democustom_write(const void *buf, int len); + +bool democustom_init(void); + +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/demorec.c b/src/demorec.c index b4a674d..1fb5e8e 100644 --- a/src/demorec.c +++ b/src/demorec.c @@ -18,9 +18,7 @@ #include #include -#include "bitbuf.h" #include "con_.h" -#include "demorec.h" #include "engineapi.h" #include "errmsg.h" #include "gamedata.h" @@ -37,7 +35,7 @@ DEF_CVAR(sst_autorecord, "Continuously record demos even after reconnecting", 1, CON_ARCHIVE | CON_HIDDEN) -static void *demorecorder; +void *demorecorder; static int *demonum; static bool *recording; static bool wantstop = false; @@ -252,114 +250,4 @@ void demorec_end(void) { cmd_stop->cb = orig_stop_cb; } -// custom data writing stuff is a separate feature, defined below. it we can't -// find WriteMessage, we can still probably do the auto recording stuff above - -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[DEMOREC_CUSTOM_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 demorec_writecustom(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 demorec_custom_init(void) { - if (!has_vtidx_GetEngineBuildNumber || !has_vtidx_RecordPacket) { - errmsg_errorx("custom: 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 diff --git a/src/demorec.h b/src/demorec.h index a18bea4..7306c0a 100644 --- a/src/demorec.h +++ b/src/demorec.h @@ -20,20 +20,12 @@ #include +/* For internal use by democustom */ +extern void *demorecorder; + bool demorec_init(void); void demorec_end(void); -bool demorec_custom_init(void); - -/* maximum length of a custom demo message, in bytes */ -#define DEMOREC_CUSTOM_MSG_MAX 253 - -/* - * Write a block of up to DEMOWRITER_MSG_MAX bytes into the currently recording - * demo - NOT bounds checked, caller MUST ensure length is okay! - */ -void demorec_writecustom(void *buf, int len); - #endif // vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/sst.c b/src/sst.c index 7246e89..4182b09 100644 --- a/src/sst.c +++ b/src/sst.c @@ -26,6 +26,7 @@ #include "alias.h" #include "autojump.h" #include "con_.h" +#include "democustom.h" #include "demorec.h" #include "engineapi.h" #include "errmsg.h" @@ -209,11 +210,10 @@ static const char *updatenotes = "\ static void do_featureinit(void) { bool has_bind = bind_init(); if (has_bind) has_ac = ac_init(); - bool has_alias = alias_init(); + alias_init(); has_autojump = autojump_init(); has_demorec = demorec_init(); - // not enabling demorec_custom yet - kind of incomplete and currently unused - //if (has_demorec) demorec_custom_init(); + if (has_demorec) democustom_init(); bool has_ent = ent_init(); has_fov = fov_init(has_ent); if (has_ent) l4dwarp_init(); -- cgit v1.2.3