diff options
Diffstat (limited to 'src/democustom.c')
-rw-r--r-- | src/democustom.c | 138 |
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 |