summaryrefslogtreecommitdiffhomepage
path: root/src/democustom.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/democustom.c')
-rw-r--r--src/democustom.c113
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();
}