diff options
author | Michael Smith <mikesmiffy128@gmail.com> | 2022-04-24 03:27:35 +0100 |
---|---|---|
committer | Michael Smith <mikesmiffy128@gmail.com> | 2022-04-24 03:43:26 +0100 |
commit | 7b12eb811ff62d9d14ccb7c152a9821796efe9a5 (patch) | |
tree | de95be73de40e732d8bbd002b721b4683fbf12c0 /src/demorec.c | |
parent | 99e9a6765a9a358987c062ec4a251f8254581933 (diff) |
Replace udis86 with a very small x86 decoder
hook_inline() uses the new x86_len() function to get instruction lengths
instead of doing full-blown disassembly, which should be a tiny bit
quicker, and also removes the next for about 90KiB of lookup tables and
such in the final binary. The code-digging logic in demorecord is also
rewritten to be opcode-based rather than mnenmonic based. In general,
going forward the plan is to always rely on opcodes and thus avoid a
bunch of disassembly work every plugin load.
udis86 is still in the tree for now to provide dbg_asmdump(), but it's
only compiled into debug builds and left out of releases completely. As
such, the whole BSD licence statement is also gone from the distribution
LICENCE files. There's now also a dbg_toghidra() which spits out a
rebased address to look stuff up for proper reverse engineering, which
might be more useful than dbg_asmdump() anyway. If nobody ends up using
the latter ever again, udis86 could get chucked completely. We'll see.
Also shoehorned into this commit are a couple more forgotten copyright
year bumps and some general minor cleanup here and there, because I
couldn't be bothered wading through all the diff hunks. Oh, and
makebindist.bat now makes an effort to make the zip file timestamps
predictable/reproducible. That should be a different commit for sure,
but oh well too bad.
Diffstat (limited to 'src/demorec.c')
-rw-r--r-- | src/demorec.c | 112 |
1 files changed, 44 insertions, 68 deletions
diff --git a/src/demorec.c b/src/demorec.c index bcc7367..a384e55 100644 --- a/src/demorec.c +++ b/src/demorec.c @@ -1,6 +1,6 @@ /* * Copyright © 2021 Willian Henrique <wsimanbrazil@yahoo.com.br> - * Copyright © 2021 Michael Smith <mikesmiffy128@gmail.com> + * 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 @@ -27,8 +27,9 @@ #include "intdefs.h" #include "mem.h" #include "os.h" -#include "udis86.h" +#include "ppmagic.h" #include "vcall.h" +#include "x86.h" #define SIGNONSTATE_SPAWN 5 // ready to receive entity packets #define SIGNONSTATE_FULL 6 // fully connected, first non-delta packet received @@ -95,7 +96,6 @@ static void hook_stop_callback(const struct con_cmdargs *args) { orig_stop_callback(args); } - // 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]; @@ -139,75 +139,59 @@ void demorec_writecustom(void *buf, int len) { bitbuf_reset(&bb); } +// XXX: probably want some general foreach-instruction macro once we start doing +// this kind of hackery in multiple different places +#define NEXT_INSN(p) do { \ + int _len = x86_len(p); \ + if (_len == -1) { \ + con_warn("demorec: %s: unknown or invalid instruction\n", __func__); \ + return false; \ + } \ + (p) += _len; \ +} while (0) // This finds the "demorecorder" global variable (the engine-wide CDemoRecorder // instance). -static inline void *find_demorecorder(struct con_cmd *cmd_stop) { - // The "stop" command calls the virtual function demorecorder.IsRecording(), - // so just look for the load of the "this" pointer - struct ud udis; - ud_init(&udis); - ud_set_mode(&udis, 32); - ud_set_input_buffer(&udis, (uchar *)con_getcmdcb(cmd_stop), 32); - while (ud_disassemble(&udis)) { +static inline bool find_demorecorder(struct con_cmd *cmd_stop) { #ifdef _WIN32 - if (ud_insn_mnemonic(&udis) == UD_Imov) { - const struct ud_operand *dest = ud_insn_opr(&udis, 0); - const struct ud_operand *src = ud_insn_opr(&udis, 1); - // looking for a mov from an address into ECX, as per thiscall - if (dest->type == UD_OP_REG && dest->base == UD_R_ECX && - src->type == UD_OP_MEM) { - return *(void **)src->lval.udword; - } + uchar *stopcb = (uchar *)con_getcmdcb(cmd_stop); + // The "stop" command calls the virtual function demorecorder.IsRecording(), + // so just look for the load of the "this" pointer into ECX + for (uchar *p = stopcb; p - stopcb < 32;) { + if (p[0] == X86_MOVRMW && p[1] == X86_MODRM(0, 1, 5)) { + void **indirect = mem_loadptr(p + 2); + demorecorder = *indirect; + return true; } + NEXT_INSN(p); + } #else #warning TODO(linux): implement linux equivalent (cdecl!) - return 0; #endif - } - return 0; + return false; } // This finds "m_bRecording" and "m_nDemoNumber" using the pointer to the // original "StopRecording" demorecorder function. -static inline bool find_recmembers(void *stop_recording_func) { - struct ud udis; - ud_init(&udis); - ud_set_mode(&udis, 32); - // TODO(opt): consider the below: is it really needed? does it matter? - // way overshooting the size of the function in bytes to make sure it - // accomodates for possible differences in different games. we make sure - // to stop early if we find a RET so should be fine - ud_set_input_buffer(&udis, (uchar *)stop_recording_func, 200); - while (ud_disassemble(&udis)) { +static inline bool find_recmembers(void *stoprecording) { #ifdef _WIN32 - enum ud_mnemonic_code code = ud_insn_mnemonic(&udis); - if (code == UD_Imov) { - const struct ud_operand *dest = ud_insn_opr(&udis, 0); - const struct ud_operand *src = ud_insn_opr(&udis, 1); - // m_nDemoNumber and m_bRecording are both set to 0 - // looking for movs with immediates equal to 0 - // the byte immediate refers to m_bRecording - if (src->type == UD_OP_IMM && src->lval.ubyte == 0) { - if (src->size == 8) { - recording = (bool *)mem_offset(demorecorder, - dest->lval.udword); - } - else { - demonum = (int *)mem_offset(demorecorder, - dest->lval.udword); - } - if (recording && demonum) return true; // blegh - } + for (uchar *p = (uchar *)stoprecording; p - (uchar *)stoprecording < 128;) { + // m_nDemoNumber = 0 -> mov dword ptr [<reg> + off], 0 + // XXX: might end up wanting constants for the MRM field masks? + if (p[0] == X86_MOVMIW && (p[1] & 0xC0) == 0x80 && + mem_load32(p + 6) == 0) { + demonum = mem_offset(demorecorder, mem_load32(p + 2)); } - else if (code == UD_Iret) { - return false; + // m_bRecording = false -> mov byte ptr [<reg> + off], 0 + else if (p[0] == X86_MOVMI8 && (p[1] & 0xC0) == 0x80 && p[6] == 0) { + recording = mem_offset(demorecorder, mem_load32(p + 2)); } + if (recording && demonum) return true; // blegh + NEXT_INSN(p); + } #else // linux is probably different here idk -#warning TODO(linux): implement linux equivalent - return false; +#warning TODO(linux): implement linux equivalent (???) #endif - } return false; } @@ -215,6 +199,7 @@ static inline bool find_recmembers(void *stop_recording_func) { // 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)[gamedata_vtidx_RecordPacket]; // RecordPacket calls WriteMessages pretty much right away: // 56 push esi @@ -228,10 +213,10 @@ static bool find_WriteMessages(void) { // So we just double check the byte pattern... static const uchar bytes[] = #ifdef _WIN32 -{0x56, 0x57, 0x8B, 0xF1, 0x8D, 0xBE, 0x8C, 0x06, 0x00, 0x00, 0x57, 0xE8}; + 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}; + {-1, -1, -1, -1, -1, -1}; #endif if (!memcmp(insns, bytes, sizeof(bytes))) { ssize off = mem_loadoffset(insns + sizeof(bytes)); @@ -249,21 +234,17 @@ bool demorec_init(void) { con_warn("demorec: missing gamedata entries for this engine\n"); return false; } - cmd_stop = con_findcmd("stop"); if (!cmd_stop) { // can *this* even happen? I hope not! con_warn("demorec: couldn't find \"stop\" command\n"); return false; } - - demorecorder = find_demorecorder(cmd_stop); - if (!demorecorder) { + if (!find_demorecorder(cmd_stop)) { con_warn("demorec: couldn't find demo recorder instance\n"); return false; } void **vtable = *(void ***)demorecorder; - // XXX: 16 is totally arbitrary here! figure out proper bounds later if (!os_mprot(vtable, 16 * sizeof(void *), PAGE_EXECUTE_READWRITE)) { #ifdef _WIN32 @@ -276,7 +257,7 @@ bool demorec_init(void) { return false; } - if (!find_recmembers(vtable[7])) { + if (!find_recmembers(vtable[7])) { // XXX: stop hardcoding this!? con_warn("demorec: couldn't find m_bRecording and m_nDemoNumber\n"); return false; } @@ -301,11 +282,6 @@ bool demorec_custom_init(void) { con_warn("demorec: custom: missing gamedata entries for this engine\n"); return false; } - // TODO(featgen): auto-check this factory - if (!factory_engine) { - con_warn("demorec: missing required interfaces\n"); - return false; - } // More UncraftedkNowledge: // > yeah okay so [the usermessage length is] 11 bits if the demo protocol |