diff options
author | Michael Smith <mikesmiffy128@gmail.com> | 2023-07-29 14:32:06 +0100 |
---|---|---|
committer | Michael Smith <mikesmiffy128@gmail.com> | 2023-08-02 21:02:31 +0100 |
commit | 9a0d8730fa977f666b5c12e4c5901e7d0391e245 (patch) | |
tree | 87eebcdcef04ae1e7348ef80e972c08aa4783649 /src/democustom.c | |
parent | d337b09936ecd90bad07b28b48b7103395d97ce5 (diff) |
Make various preparations for upcoming features
A lot of this is random WIP from a while back, at least a month ago, and
is being committed now to get it out of the way so that other patches
can be brought in and integrated against it without causing headaches.
Also rolled into this commit is a way to distinguish plugin_unload from
exiting the game. This is required for another soon-to-be-integrated
feature to avoid crashing on exit, and could in theory also be used to
speed up unloading on exit in future. While we're at it, this also
avoids the need to linearly scan through the plugin list to do the
old branch unloading fix, because we can.
Rough summary of the other smaller stuff I can remember doing:
- Rework bitbuf a bit
- Add some cryptographic nonsense in ac.c (not final at all)
- Introduce the first couple of "chunklets" libraries as a sort-of
subproject of this one
- Tidy up random small bits and bobs
- Add source for a small keypair generation tool
- Rework democustom to be very marginally more useful
Diffstat (limited to 'src/democustom.c')
-rw-r--r-- | src/democustom.c | 113 |
1 files changed, 53 insertions, 60 deletions
diff --git a/src/democustom.c b/src/democustom.c index 0ecbaa3..5dcbe01 100644 --- a/src/democustom.c +++ b/src/democustom.c @@ -14,9 +14,10 @@ * PERFORMANCE OF THIS SOFTWARE. */ +#include <string.h> + #include "bitbuf.h" #include "con_.h" -#include "democustom.h" #include "demorec.h" #include "engineapi.h" #include "errmsg.h" @@ -26,6 +27,8 @@ #include "mem.h" #include "ppmagic.h" #include "vcall.h" +#include "x86.h" +#include "x86util.h" FEATURE() REQUIRE(demorec) @@ -34,72 +37,64 @@ REQUIRE_GAMEDATA(vtidx_RecordPacket) 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]; +// engine limit is 255, we use 2 bytes for header + round the bitstream to the +// next whole byte, which gives 3 bytes overhead hence 252 here. +#define CHUNKSZ 252 + +static union { + char x[CHUNKSZ + /*7*/ 8]; // needs to be multiple of of 4! + bitbuf_cell _align; // just in case... +} bb_buf; static struct bitbuf bb = { - bb_buf, sizeof(bb_buf), sizeof(bb_buf) * 8, 0, false, false, "SST" + {bb_buf.x}, 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. +static const void *createhdr(struct bitbuf *msg, int len, bool last) { + // We pack custom data into user message packets of type "HudText," with a + // leading null byte which the engine treats as an empty string. On demo + // playback, the client does a text lookup which fails silently on invalid + // keys, giving us the rest of the packet to stick in whatever 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! + // Big thanks to our resident demo expert, Uncrafted, for explaining what to + // do here way back when this was first being figured out! + bitbuf_appendbits(msg, 23, nbits_msgtype); // type: 23 is user message + bitbuf_appendbyte(msg, 2); // user message type: 2 is HudText + bitbuf_appendbits(msg, len * 8, nbits_datalen); // our data length in bits + bitbuf_appendbyte(msg, 0); // aforementionied null byte + bitbuf_appendbyte(msg, 0xAC + last); // arbitrary marker byte to aid parsing + // store the data itself byte-aligned so there's no need to bitshift the + // universe (which would be both slower and more annoying to do) + bitbuf_roundup(msg); + return msg->buf + (msg->nbits >> 3); } 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); + for (; len > CHUNKSZ; len -= CHUNKSZ) { + createhdr(&bb, CHUNKSZ, false); + memcpy(bb.buf + (bb.nbits >> 3), buf, CHUNKSZ); + bb.nbits += CHUNKSZ << 3; + WriteMessages(demorecorder, &bb); + bitbuf_reset(&bb); + } + createhdr(&bb, len, true); + memcpy(bb.buf + (bb.nbits >> 3), buf, len); + bb.nbits += len << 3; WriteMessages(demorecorder, &bb); bitbuf_reset(&bb); } static bool find_WriteMessages(void) { - // TODO(compat): 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)); - WriteMessages = (WriteMessages_func)(insns + sizeof(bytes) + 4 + off); - return true; + // RecordPacket calls WriteMessages right away, so just look for a call + for (const uchar *p = insns; p - insns < 32;) { + if (*p == X86_CALL) { + WriteMessages = (WriteMessages_func)(p + 5 + mem_loadoffset(p + 1)); + return true; + } + NEXT_INSN(p, "WriteMessages function"); } return false; } @@ -108,23 +103,21 @@ DECL_VFUNC_DYN(int, GetEngineBuildNumber) INIT { // 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 + // - usermessage length is: + // - 11 bits in protocol 11, or l4d2 protocol 2042 + // - otherwise 12 bits // So here we have to figure out the network protocol version! // NOTE: assuming engclient != null as GEBN index relies on client version int buildnum = GetEngineBuildNumber(engclient); - // condition is redundant until other GetEngineBuildNumber offsets are added - // if (GAMETYPE_MATCHES(L4D2)) { + //if (GAMETYPE_MATCHES(L4D2)) { // redundant until we add more GEBN offsets! 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. + // protocol froze after 2042, so we just have to do a >=. Fair enough! + // TODO(compat): how does TLS affect this? no idea yet if (buildnum >= 2042) nbits_datalen = 11; else nbits_datalen = 12; - // } + //} return find_WriteMessages(); } |