summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rwxr-xr-xcompile5
-rw-r--r--compile.bat11
-rw-r--r--dist/LICENCE.linux28
-rw-r--r--dist/LICENCE.windows28
-rw-r--r--src/3p/README3
-rw-r--r--src/autojump.c31
-rw-r--r--src/build/cmeta.c2
-rw-r--r--src/build/cmeta.h2
-rw-r--r--src/build/codegen.c2
-rw-r--r--src/con_.c2
-rw-r--r--src/con_.h10
-rw-r--r--src/dbg.c21
-rw-r--r--src/dbg.h17
-rw-r--r--src/demorec.c112
-rw-r--r--src/hook.c47
-rw-r--r--src/os-unix.h2
-rw-r--r--src/udis86.c2
-rw-r--r--src/x86.c87
-rw-r--r--src/x86.h451
-rw-r--r--test/bitbuf.test.c2
-rw-r--r--test/hook.test.c8
-rw-r--r--test/kv.test.c2
-rw-r--r--tools/mkbindist.bat5
24 files changed, 686 insertions, 195 deletions
diff --git a/.gitignore b/.gitignore
index 5cfdbc5..d880120 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
/sst.pdb
/sst.so
/compile_commands.json
+/compile_flags.txt
# a place to plonk crap you don't wanna commit yet
/junk/
diff --git a/compile b/compile
index 072a1eb..fdeb843 100755
--- a/compile
+++ b/compile
@@ -40,7 +40,6 @@ ld() {
src="\
autojump.c
con_.c
- dbg.c
demorec.c
extmalloc.c
fixes.c
@@ -49,7 +48,11 @@ src="\
hook.c
kv.c
sst.c
+ x86.c"
+if [ "$dbg" = 1 ]; then src="$src \
+ dbg.c
udis86.c"
+fi
$HOSTCC -O2 -fuse-ld=lld $warnings -D_FILE_OFFSET_BITS=64 -o .build/codegen \
src/build/codegen.c src/build/cmeta.c
diff --git a/compile.bat b/compile.bat
index f363eef..faef34f 100644
--- a/compile.bat
+++ b/compile.bat
@@ -39,15 +39,14 @@ goto :eof
-o .build/codegen.exe src/build/codegen.c src/build/cmeta.c || exit /b
%HOSTCC% -municode -O2 %warnings% -D_CRT_SECURE_NO_WARNINGS -ladvapi32 ^
-o .build/mkgamedata.exe src/build/mkgamedata.c src/kv.c || exit /b
-.build\codegen.exe src/autojump.c src/con_.c src/dbg.c src/demorec.c src/extmalloc.c ^
-src/fixes.c src/gamedata.c src/gameinfo.c src/hook.c src/kv.c src/rinput.c src/sst.c src/udis86.c || exit /b
+.build\codegen.exe src/autojump.c src/con_.c src/demorec.c src/extmalloc.c src/fixes.c ^
+src/gamedata.c src/gameinfo.c src/hook.c src/kv.c src/rinput.c src/sst.c src/x86.c || exit /b
.build\mkgamedata.exe gamedata/engine.kv gamedata/gamelib.kv || exit /b
llvm-rc /FO .build\dll.res src\dll.rc || exit /b
%CC% -shared -O0 -w -o .build/tier0.dll src/stubs/tier0.c
%CC% -shared -O0 -w -o .build/vstdlib.dll src/stubs/vstdlib.c
call :cc src/autojump.c || exit /b
call :cc src/con_.c || exit /b
-call :cc src/dbg.c || exit /b
call :cc src/demorec.c || exit /b
call :cc src/extmalloc.c || exit /b
call :cc src/fixes.c || exit /b
@@ -57,7 +56,11 @@ call :cc src/hook.c || exit /b
call :cc src/kv.c || exit /b
call :cc src/rinput.c || exit /b
call :cc src/sst.c || exit /b
-call :cc src/udis86.c || exit /b
+call :cc src/x86.c || exit /b
+if "%dbg%"=="1" (
+ call :cc src/dbg.c || exit /b
+ call :cc src/udis86.c || exit /b
+)
%CC% -shared -flto %ldflags% -Wl,/IMPLIB:.build/sst.lib,/Brepro ^
-L.build -luser32 -ladvapi32 -lshlwapi -ltier0 -lvstdlib -o sst.dll%objs% .build/dll.res || exit /b
:: get rid of another useless file (can we just not create this???)
diff --git a/dist/LICENCE.linux b/dist/LICENCE.linux
index 13cd6b3..535f40f 100644
--- a/dist/LICENCE.linux
+++ b/dist/LICENCE.linux
@@ -16,33 +16,5 @@ 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.
════════════════════════════════════════════════════════════════════════════════
-
-sst.so also includes code from the udis86 project, licensed as follows:
-════════════════════════════════════════════════════════════════════════════════
-Copyright © 2002–2009 Vivek Thampi
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
-• Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-
-• Redistributions in binary form must reproduce the above copyright notice, this
- list of conditions and the following disclaimer in the documentation and/or
- other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-════════════════════════════════════════════════════════════════════════════════
-
Please respect these terms when distributing copies of sst.so — doing so is as
simple as keeping this LICENCE file in place. Thanks, and have fun! :^)
diff --git a/dist/LICENCE.windows b/dist/LICENCE.windows
index 13f282f..7ebf1f1 100644
--- a/dist/LICENCE.windows
+++ b/dist/LICENCE.windows
@@ -16,33 +16,5 @@ 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.
════════════════════════════════════════════════════════════════════════════════
-
-sst.dll also includes code from the udis86 project, licensed as follows:
-════════════════════════════════════════════════════════════════════════════════
-Copyright © 2002–2009 Vivek Thampi
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without modification,
-are permitted provided that the following conditions are met:
-
-• Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-
-• Redistributions in binary form must reproduce the above copyright notice, this
- list of conditions and the following disclaimer in the documentation and/or
- other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-════════════════════════════════════════════════════════════════════════════════
-
Please respect these terms when distributing copies of sst.dll — doing so is as
simple as keeping this LICENCE file in place. Thanks, and have fun! :^)
diff --git a/src/3p/README b/src/3p/README
index e0c40a6..1617ff0 100644
--- a/src/3p/README
+++ b/src/3p/README
@@ -2,6 +2,9 @@ These are imported 3rd party library sources, wrangled for ease of plonking into
the build (e.g. relative #includes, etc.).
Used in SST itself:
+ [none currently, stuff will probably be added later]
+
+Used in debug builds, but not compiled into releases:
- udis86
Used at build time:
diff --git a/src/autojump.c b/src/autojump.c
index e91c854..6c30df9 100644
--- a/src/autojump.c
+++ b/src/autojump.c
@@ -1,5 +1,5 @@
/*
- * 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
@@ -30,29 +30,18 @@ DEF_CVAR(sst_autojump, "Jump upon hitting the ground while holding space", 0,
struct vec3f { float x, y, z; };
struct CMoveData {
- bool firstrun : 1;
- bool gamecodemoved : 1;
+ bool firstrun : 1, gamecodemoved : 1;
ulong playerhandle;
int impulse;
- struct vec3f viewangles;
- struct vec3f absviewangles;
- int buttons;
- int oldbuttons;
- float mv_forward;
- float mv_side;
- float mv_up;
- float maxspeed;
- float clientmaxspeed;
- struct vec3f vel;
- struct vec3f angles;
- struct vec3f oldangles;
+ struct vec3f viewangles, absviewangles;
+ int buttons, oldbuttons;
+ float mv_forward, mv_side, mv_up;
+ float maxspeed, clmaxspeed;
+ struct vec3f vel, angles, oldangles;
float out_stepheight;
- struct vec3f out_wishvel;
- struct vec3f out_jumpvel;
- struct vec3f constraint_center;
- float constraint_radius;
- float constraint_width;
- float constraint_speedfactor;
+ struct vec3f out_wishvel, out_jumpvel;
+ struct vec3f constraint_centre;
+ float constraint_radius, constraint_width, constraint_speedfactor;
struct vec3f origin;
};
diff --git a/src/build/cmeta.c b/src/build/cmeta.c
index 4e1eb4a..3d9281a 100644
--- a/src/build/cmeta.c
+++ b/src/build/cmeta.c
@@ -1,5 +1,5 @@
/*
- * 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
diff --git a/src/build/cmeta.h b/src/build/cmeta.h
index 18ff62c..20672f4 100644
--- a/src/build/cmeta.h
+++ b/src/build/cmeta.h
@@ -1,5 +1,5 @@
/*
- * 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
diff --git a/src/build/codegen.c b/src/build/codegen.c
index 38645e5..6d5fc99 100644
--- a/src/build/codegen.c
+++ b/src/build/codegen.c
@@ -1,5 +1,5 @@
/*
- * 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
diff --git a/src/con_.c b/src/con_.c
index 925eafb..b69b1a6 100644
--- a/src/con_.c
+++ b/src/con_.c
@@ -1,4 +1,4 @@
-/* XXX: THIS FILE SHOULD BE CALLED `con.c` BUT WINDOWS IS STUPID */
+/* THIS FILE SHOULD BE CALLED `con.c` BUT WINDOWS IS STUPID */
/*
* Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com>
*
diff --git a/src/con_.h b/src/con_.h
index c0c37f1..a5d0c87 100644
--- a/src/con_.h
+++ b/src/con_.h
@@ -1,4 +1,4 @@
-/* XXX: THIS FILE SHOULD BE CALLED `con.h` BUT WINDOWS IS STUPID */
+/* THIS FILE SHOULD BE CALLED `con.h` BUT WINDOWS IS STUPID */
/*
* Copyright © 2022 Michael Smith <mikesmiffy128@gmail.com>
*
@@ -40,7 +40,7 @@ struct con_cmdargs {
const char *argv[CON_CMD_MAX_ARGC];
};
-/* an ARGB colour, passed to con_colourmsg */
+/* an RGBA colour, passed to con_colourmsg */
struct con_colour {
union {
struct { u8 r, g, b, a; };
@@ -236,7 +236,11 @@ extern void *_con_vtab_iconvar[];
.parent = &_cvar_##name_, /* bizarre, but how the engine does it */ \
.defaultval = _Generic(value, char *: value, int: #value, \
float: #value), \
- .strlen = _Generic(value, char *: sizeof(value), \
+ /* N.B. the NOLINT comment below isn't for you, the reader, it's for the
+ computer, because clangd decided the only way to turn off a bogus
+ warning is to write a bogus comment. Also note, this comment you're
+ reading now isn't very useful either, I'm just angry. */ \
+ .strlen = _Generic(value, char *: sizeof(value), /*NOLINT*/ \
default: sizeof(#value)), \
.fval = _Generic(value, char *: 0, int: value, float: value), \
.ival = _Generic(value, char *: 0, int: value, float: (int)value), \
diff --git a/src/dbg.c b/src/dbg.c
index 20f0271..c7af49a 100644
--- a/src/dbg.c
+++ b/src/dbg.c
@@ -1,5 +1,5 @@
/*
- * 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
@@ -14,6 +14,12 @@
* PERFORMANCE OF THIS SOFTWARE.
*/
+#ifdef _WIN32
+#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX
+#include <Windows.h>
+#endif
+
#include "con_.h"
#include "intdefs.h"
#include "ppmagic.h"
@@ -46,4 +52,17 @@ void dbg_asmdump(char *name, const void *p, int len) {
}
}
+#ifdef _WIN32
+usize dbg_toghidra(void *addr) {
+ void *mod;
+ if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (ushort *)addr,
+ (HMODULE *)&mod/*please leave me alone*/)) {
+ con_warn("dbg_toghidra: couldn't get base address\n");
+ return 0;
+ }
+ return (char *)addr - (char *)mod + 0x10000000;
+}
+#endif
+
// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/dbg.h b/src/dbg.h
index 12d7e0c..e3625e0 100644
--- a/src/dbg.h
+++ b/src/dbg.h
@@ -1,5 +1,5 @@
/*
- * 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
@@ -17,12 +17,23 @@
#ifndef INC_DBG_H
#define INC_DBG_H
-/* prints out a basic hexadecimal listing of a byte range */
+/*
+ * These functions can all be used for development and debugging but aren't
+ * available to release builds; this header shouldn't even be #included in real
+ * code that's committed to a repo.
+ */
+
+/* Prints out a basic hexadecimal listing of a byte range. */
void dbg_hexdump(char *name, const void *p, int len);
-/* prints out a disassembly of some instructions in memory */
+/* Prints out a disassembly of some instructions in memory. */
void dbg_asmdump(char *name, const void *p, int len);
+#ifdef _WIN32 // at least for now
+/* Returns a function's Ghidra address, assuming default project offsets. */
+void *dbg_toghidra(void *addr);
+#endif
+
#endif
// vi: sw=4 ts=4 noet tw=80 cc=80
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
diff --git a/src/hook.c b/src/hook.c
index 6b9036c..56eaf7d 100644
--- a/src/hook.c
+++ b/src/hook.c
@@ -21,7 +21,7 @@
#include "intdefs.h"
#include "mem.h"
#include "os.h"
-#include "udis86.h"
+#include "x86.h"
// Warning: half-arsed hacky implementation (because that's all we really need)
// Almost certainly breaks in some weird cases. Oh well! Most of the time,
@@ -39,50 +39,47 @@ static void setrwx(void) {
os_mprot(trampolines, sizeof(trampolines), PAGE_EXECUTE_READWRITE);
}
-#define RELJMP 0xE9 // the first byte of a 5-byte jmp
-
void *hook_inline(void *func_, void *target) {
uchar *func = func_;
// dumb hack: rather than correcting jmp offsets and having to painstakingly
// track them all, just look for the underlying thing being jmp-ed to and
// hook _that_.
- while (*func == RELJMP) func += mem_loadoffset(func + 1) + 5;
+ while (*func == X86_JMPIW) func += mem_loadoffset(func + 1) + 5;
if (!os_mprot(func, 5, PAGE_EXECUTE_READWRITE)) return false;
- struct ud udis;
- ud_init(&udis);
- ud_set_mode(&udis, 32);
- // max insn length is 15, we overwrite 5, so max to copy is 4 + 15 = 19
- ud_set_input_buffer(&udis, func, 19);
int len = 0;
- do {
- ud_disassemble(&udis); // assume we don't run out of bytes
- // TODO(opt): this check should probably be gone, keeping it to make dev
- // life easier/less crashy. Really, we should just refrain from hooking
- // things that immediately call or branch (als, immediate jump is
- // already handled above).
- if (ud_insn_mnemonic(&udis) == UD_Ijmp ||
- ud_insn_mnemonic(&udis) == UD_Icall) {
- con_warn("hook_inline: jmp/call adjustment NYI\n");
- return 0;
+ for (;;) {
+ if (func[len] == X86_CALL) {
+ con_warn("hook_inline: can't trampoline call instructions\n");
+ return false;
+ }
+ int ilen = x86_len(func + len);
+ if (ilen == -1) {
+ con_warn("hook_inline: unknown or invalid instruction\n");
+ return false;
}
- len += ud_insn_len(&udis);
- } while (len < 5);
+ len += ilen;
+ if (len >= 5) break;
+ if (func[len] == X86_JMPIW) {
+ con_warn("hook_inline: can't trampoline jmp instructions\n");
+ return false;
+ }
+ }
// for simplicity, just bump alloc the trampoline. no need to free anyway
- if (nexttrampoline - trampolines > sizeof(trampolines) - len - 6) goto nospc;
+ if (nexttrampoline - trampolines > sizeof(trampolines) - len - 6) goto nosp;
uchar *trampoline = (uchar *)InterlockedExchangeAdd(
(volatile long *)&nexttrampoline, len + 6);
// avoid TOCTOU
if (trampoline - trampolines > sizeof(trampolines) - len - 6) {
-nospc: con_warn("hook_inline: out of trampoline space\n");
+nosp: con_warn("hook_inline: out of trampoline space\n");
return 0;
}
*trampoline++ = len; // stick length in front for quicker unhooking
memcpy(trampoline, func, len);
- trampoline[len] = RELJMP;
+ trampoline[len] = X86_JMPIW;
uint diff = func - (trampoline + 5); // goto the continuation
memcpy(trampoline + len + 1, &diff, 4);
uchar jmp[8];
- jmp[0] = RELJMP;
+ jmp[0] = X86_JMPIW;
diff = (uchar *)target - (func + 5); // goto the hook target
memcpy(jmp + 1, &diff, 4);
// pad with original bytes so we can do an 8-byte atomic write
diff --git a/src/os-unix.h b/src/os-unix.h
index 8b7bf27..5c3c604 100644
--- a/src/os-unix.h
+++ b/src/os-unix.h
@@ -45,6 +45,7 @@ typedef char os_char;
#define os_dlsym dlsym
+#ifdef __linux__
static inline bool os_dlfile(void *m, char *buf, int sz) {
// NOTE: this might be linux/glibc-specific (I haven't checked every
// implementation). this is fine as we don't use it in any build-time code,
@@ -54,6 +55,7 @@ static inline bool os_dlfile(void *m, char *buf, int sz) {
if (ssz > sz) { errno = ENAMETOOLONG; return false; }
memcpy(buf, lm->l_name, ssz); return true;
}
+#endif
// unix mprot flags are much nicer but cannot be defined in terms of the windows
// ones, so we use the windows ones and define them in terms of the unix ones.
diff --git a/src/udis86.c b/src/udis86.c
index fcb3656..754ff83 100644
--- a/src/udis86.c
+++ b/src/udis86.c
@@ -3,8 +3,6 @@
#include "3p/udis86/udis86.c"
#include "3p/udis86/decode.c"
#include "3p/udis86/itab.c"
-// this stuff is optional but llvm is smart enough to remove it if it's unused,
-// so we keep it in here to be able to use it conveniently for debugging etc.
#include "3p/udis86/syn.c"
#include "3p/udis86/syn-intel.c"
diff --git a/src/x86.c b/src/x86.c
new file mode 100644
index 0000000..e33efbb
--- /dev/null
+++ b/src/x86.c
@@ -0,0 +1,87 @@
+/*
+ * 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 "intdefs.h"
+#include "x86.h"
+
+static int mrm(uchar b, int addrlen) {
+ // I won't lie: I don't *entirely* understand this particular logic. I
+ // largely based it on some public domain code I found on the internet
+ if (addrlen == 4 || b & 0xC0) {
+ int sib = addrlen == 4 && b < 0xC0 && (b & 7) == 4;
+ switch (b & 0xC0) {
+ // disp8
+ case 0x40: return 2 + sib;
+ // disp16/32
+ case 0: if ((b & 7) == 5) case 0x80: return 1 + addrlen + sib;
+ }
+ // disp8/32
+ if (sib && (b & 7) == 5) return b & 0x40 ? 3 : 6;
+ }
+ if (addrlen == 2 && b == 0x26) return 3;
+ return 1; // NOTE: include the mrm itself in the byte count
+}
+
+int x86_len(const void *insn_) {
+#define CASES(name, _) case name:
+ const uchar *insn = insn_;
+ int pfxlen = 0, addrlen = 4, operandlen = 4;
+
+p: switch (*insn) {
+ case X86_PFX_ADSZ: addrlen = 2; goto P; // bit dumb sorry
+ case X86_PFX_OPSZ: operandlen = 2;
+P: X86_SEG_PREFIXES(CASES)
+ case X86_PFX_LOCK: case X86_PFX_REPN: case X86_PFX_REP:
+ // instruction can only be 15 bytes. this could go over, oh well,
+ // just don't want to loop for 8 million years
+ if (++pfxlen == 14) return -1;
+ ++insn;
+ goto p;
+ }
+
+ switch (*insn) {
+ X86_OPS_1BYTE_NO(CASES) return pfxlen + 1;
+ X86_OPS_1BYTE_I8(CASES) operandlen = 1;
+ X86_OPS_1BYTE_IW(CASES) return pfxlen + 1 + operandlen;
+ X86_OPS_1BYTE_I16(CASES) return pfxlen + 3;
+ X86_OPS_1BYTE_MRM(CASES) return pfxlen + 1 + mrm(insn[1], addrlen);
+ X86_OPS_1BYTE_MRM_I8(CASES) operandlen = 1;
+ X86_OPS_1BYTE_MRM_IW(CASES)
+ return pfxlen + 1 + operandlen + mrm(insn[1], addrlen);
+ case X86_ENTER: return pfxlen + 4;
+ case X86_CRAZY8: operandlen = 1;
+ case X86_CRAZYW:
+ if ((insn[1] & 0x38) >= 0x10) operandlen = 0;
+ return pfxlen + 2 + operandlen + mrm(insn[1], addrlen);
+ case X86_2BYTE: ++insn; goto b2;
+ }
+ return -1;
+
+b2: switch (*insn) {
+ // we don't support any 3 byte ops for now, implement if ever needed...
+ case X86_3BYTE1: case X86_3BYTE2: case X86_3DNOW: return -1;
+ X86_OPS_2BYTE_NO(CASES) return pfxlen + 2;
+ X86_OPS_2BYTE_IW(CASES) return pfxlen + 2 + operandlen;
+ X86_OPS_2BYTE_MRM(CASES) return pfxlen + 2 + mrm(insn[1], addrlen);
+ X86_OPS_2BYTE_MRM_I8(CASES) operandlen = 1;
+ return pfxlen + 2 + operandlen + mrm(insn[1], addrlen);
+ }
+
+ return -1;
+#undef CASES
+}
+
+// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/x86.h b/src/x86.h
new file mode 100644
index 0000000..46a34b2
--- /dev/null
+++ b/src/x86.h
@@ -0,0 +1,451 @@
+/*
+ * 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.
+ */
+
+#ifndef INC_X86_H
+#define INC_X86_H
+
+/*
+ * Opcode-based X86 instruction analysis. In other words, *NOT* a disassembler.
+ * Only cares about the instructions we expect to see in basic 32-bit userspace
+ * functions; there's no kernel-mode instructions, no MMX/3DNow!/SSE/AVX, no
+ * REX, EVEX, yadda yadda.
+ */
+
+// XXX: no BOUND (0x62): ambiguous with EVEX prefix - can't be arsed!
+
+/* Instruction prefixes: segments */
+#define X86_SEG_PREFIXES(X) \
+ X(X86_PFX_ES, 0x26) \
+ X(X86_PFX_CS, 0x2E) \
+ X(X86_PFX_SS, 0x36) \
+ X(X86_PFX_DS, 0x3E) \
+ X(X86_PFX_FS, 0x64) \
+ X(X86_PFX_GS, 0x65)
+
+/* Instruction prefixes: operations */
+#define X86_OP_PREFIXES(X) \
+ X(X86_PFX_OPSZ, 0x66) \
+ X(X86_PFX_ADSZ, 0x67) \
+ X(X86_PFX_LOCK, 0xF0) \
+ X(X86_PFX_REPN, 0xF2) \
+ X(X86_PFX_REP, 0xF3)
+
+/* All instruction prefixes */
+#define X86_PREFIXES(X) X86_SEG_PREFIXES(X) X86_OP_PREFIXES(X)
+
+/* Single-byte opcodes with no operands */
+#define X86_OPS_1BYTE_NO(X) \
+ X(X86_PUSHES, 0x06) \
+ X(X86_POPES, 0x07) \
+ X(X86_PUSHCS, 0x0E) \
+ X(X86_PUSHSS, 0x16) \
+ X(X86_POPSS, 0x17) \
+ X(X86_PUSHDS, 0x1E) \
+ X(X86_POPDS, 0x1F) \
+ X(X86_DAA, 0x27) \
+ X(X86_DAS, 0x2F) \
+ X(X86_AAA, 0x37) \
+ X(X86_AAS, 0x3F) \
+ X(X86_INCEAX, 0x40) \
+ X(X86_INCECX, 0x41) \
+ X(X86_INCEDX, 0x42) \
+ X(X86_INCEBX, 0x43) \
+ X(X86_INCESP, 0x44) \
+ X(X86_INCEBP, 0x45) \
+ X(X86_INCESI, 0x46) \
+ X(X86_INCEDI, 0x47) \
+ X(X86_DECEAX, 0x48) \
+ X(X86_DECECX, 0x49) \
+ X(X86_DECEDX, 0x4A) \
+ X(X86_DECEBX, 0x4B) \
+ X(X86_DECESP, 0x4C) \
+ X(X86_DECEBP, 0x4D) \
+ X(X86_DECESI, 0x4E) \
+ X(X86_DECEDI, 0x4F) \
+ X(X86_PUSHEAX, 0x50) \
+ X(X86_PUSHECX, 0x51) \
+ X(X86_PUSHEDX, 0x52) \
+ X(X86_PUSHEBX, 0x53) \
+ X(X86_PUSHESP, 0x54) \
+ X(X86_PUSHEBP, 0x55) \
+ X(X86_PUSHESI, 0x56) \
+ X(X86_PUSHEDI, 0x57) \
+ X(X86_POPEAX, 0x58) \
+ X(X86_POPECX, 0x59) \
+ X(X86_POPEDX, 0x5A) \
+ X(X86_POPEBX, 0x5B) \
+ X(X86_POPESP, 0x5C) \
+ X(X86_POPEBP, 0x5D) \
+ X(X86_POPESI, 0x5E) \
+ X(X86_POPEDI, 0x5F) \
+ X(X86_PUSHA, 0x60) \
+ X(X86_POPA, 0x61) \
+ X(X86_NOP, 0x90) \
+ X(X86_XCHGECXEAX, 0x91) \
+ X(X86_XCHGEDXEAX, 0x92) \
+ X(X86_XCHGEBXEAX, 0x93) \
+ X(X86_XCHGESPEAX, 0x94) \
+ X(X86_XCHGEBPEAX, 0x95) \
+ X(X86_XCHGESIEAX, 0x96) \
+ X(X86_XCHGEDIEAX, 0x97) \
+ X(X86_CWDE, 0x98) \
+ X(X86_CDQ, 0x99) \
+ X(X86_WAIT, 0x9B) \
+ X(X86_PUSHF, 0x9C) \
+ X(X86_POPF, 0x9D) \
+ X(X86_SAHF, 0x9E) \
+ X(X86_LAHF, 0x9F) \
+ X(X86_MOVS8, 0xA4) \
+ X(X86_MOVSW, 0xA5) \
+ X(X86_CMPS8, 0xA6) \
+ X(X86_CMPSW, 0xA7) \
+ X(X86_STOS8, 0xAA) \
+ X(X86_STOSD, 0xAB) \
+ X(X86_LODS8, 0xAC) \
+ X(X86_LODSD, 0xAD) \
+ X(X86_SCAS8, 0xAE) \
+ X(X86_SCASD, 0xAF) \
+ X(X86_RET, 0xC3) \
+ X(X86_LEAVE, 0xC9) \
+ X(X86_RETF, 0xCB) \
+ X(X86_INT3, 0xCC) \
+ X(X86_INTO, 0xCE) \
+ X(X86_XLAT, 0xD7) \
+ X(X86_JMPI8, 0xEB) \
+ X(X86_CMC, 0xF5) \
+ X(X86_CLC, 0xF8) \
+ X(X86_STC, 0xF9) \
+ X(X86_CLI, 0xFA) \
+ X(X86_STI, 0xFB) \
+ X(X86_CLD, 0xFC) \
+ X(X86_STD, 0xFD)
+
+/* Single-byte opcodes with a 1-byte immediate operand */
+#define X86_OPS_1BYTE_I8(X) \
+ X(X86_ADDALI, 0x04) \
+ X(X86_ORALI, 0x0C) \
+ X(X86_ADCALI, 0x14) \
+ X(X86_SBBALI, 0x1C) \
+ X(X86_ANDALI, 0x24) \
+ X(X86_SUBALI, 0x2C) \
+ X(X86_XORALI, 0x34) \
+ X(X86_CMPALI, 0x3C) \
+ X(X86_PUSHI8, 0x6A) \
+ X(X86_MOVALII, 0xA0) /* From offset (indirect) */ \
+ X(X86_MOVIIAL, 0xA2) /* To offset (indirect) */ \
+ X(X86_TESTALI, 0xA8) \
+ X(X86_JO, 0x70) \
+ X(X86_JNO, 0x71) \
+ X(X86_JB, 0x72) /* AKA JC */ \
+ X(X86_JNB, 0x73) /* AKA JNC */ \
+ X(X86_JZ, 0x74) /* AKA JE */ \
+ X(X86_JNZ, 0x75) /* AKA JNZ */ \
+ X(X86_JNA, 0x76) /* AKA JBE */ \
+ X(X86_JA, 0x77) /* AKA JNBE */ \
+ X(X86_JS, 0x78) \
+ X(X86_JNS, 0x79) \
+ X(X86_JP, 0x7A) \
+ X(X86_JNP, 0x7B) \
+ X(X86_JL, 0x7C) /* AKA JNGE */ \
+ X(X86_JNL, 0x7D) /* AKA JGE */ \
+ X(X86_JNG, 0x7E) /* AKA JLE */ \
+ X(X86_JG, 0x7F) /* AKA JNLE */ \
+ X(X86_MOVALI, 0xB0) \
+ X(X86_MOVCLI, 0xB1) \
+ X(X86_MOVDLI, 0xB2) \
+ X(X86_MOVBLI, 0xB3) \
+ X(X86_MOVAHI, 0xB4) \
+ X(X86_MOVCHI, 0xB5) \
+ X(X86_MOVDHI, 0xB6) \
+ X(X86_MOVBHI, 0xB7) \
+ X(X86_INT, 0xCD) \
+ X(X86_AMX, 0xD4) /* Note: D4 0A is referred to as AAM */ \
+ X(X86_ADX, 0xD5) /* Note: D4 0A is referred to as AAD */ \
+ X(X86_LOOPNZ, 0xE0) /* AKA LOOPNE */ \
+ X(X86_LOOPZ, 0xE1) /* AKA LOOPE */ \
+ X(X86_LOOP, 0xE2) \
+ X(X86_JCXZ, 0xE3)
+
+/* Single-byte opcodes with a word-sized immediate operand */
+#define X86_OPS_1BYTE_IW(X) \
+ X(X86_ADDEAXI, 0x05) \
+ X(X86_OREAXI, 0x0D) \
+ X(X86_ADCEAXI, 0x15) \
+ X(X86_SBBEAXI, 0x1D) \
+ X(X86_ANDEAXI, 0x25) \
+ X(X86_SUBEAXI, 0x2D) \
+ X(X86_XOREAXI, 0x35) \
+ X(X86_CMPEAXI, 0x3D) \
+ X(X86_PUSHIW, 0x68) \
+ X(X86_MOVEAXII, 0xA1) /* From offset (indirect) */ \
+ X(X86_MOVIIEAX, 0xA3) /* To offset (indirect) */ \
+ X(X86_TESTEAXI, 0xA9) \
+ X(X86_MOVEAXI, 0xB8) \
+ X(X86_MOVECXI, 0xB9) \
+ X(X86_MOVEDXI, 0xBA) \
+ X(X86_MOVEBXI, 0xBB) \
+ X(X86_MOVESPI, 0xBC) \
+ X(X86_MOVEBPI, 0xBD) \
+ X(X86_MOVESII, 0xBE) \
+ X(X86_MOVEDII, 0xBF) \
+ X(X86_CALL, 0xE8) \
+ X(X86_JMPIW, 0xE9)
+
+/* Single-byte opcodes with 16-bit immediate operands, regardless of prefixes */
+#define X86_OPS_1BYTE_I16(X) \
+ X(X86_RETI16, 0xC2) \
+ X(X86_RETFI16, 0xCA)
+
+/*
+ * Single-byte opcodes with a ModRM. `MR` in a name means the ModRM is the
+ * destination, `RM` means it's the source.
+ */
+#define X86_OPS_1BYTE_MRM(X) \
+ X(X86_ADDMR8, 0x00) \
+ X(X86_ADDMRW, 0x01) \
+ X(X86_ADDRM8, 0x02) \
+ X(X86_ADDRMW, 0x03) \
+ X(X86_ORMR8, 0x08) \
+ X(X86_ORMRW, 0x09) \
+ X(X86_ORRM8, 0x0A) \
+ X(X86_ORRMW, 0x0B) \
+ X(X86_ADCMR8, 0x10) \
+ X(X86_ADCMRW, 0x11) \
+ X(X86_ADCRM8, 0x12) \
+ X(X86_ADCRMW, 0x13) \
+ X(X86_SBBMR8, 0x18) \
+ X(X86_SBBMRW, 0x19) \
+ X(X86_SBBRM8, 0x1A) \
+ X(X86_SBBRMW, 0x1B) \
+ X(X86_ANDMR8, 0x20) \
+ X(X86_ANDMRW, 0x21) \
+ X(X86_ANDRM8, 0x22) \
+ X(X86_ANDRMW, 0x23) \
+ X(X86_SUBMR8, 0x28) \
+ X(X86_SUBMRW, 0x29) \
+ X(X86_SUBRM8, 0x2A) \
+ X(X86_SUBRMW, 0x2B) \
+ X(X86_XORMR8, 0x30) \
+ X(X86_XORMRW, 0x31) \
+ X(X86_XORRM8, 0x32) \
+ X(X86_XORRMW, 0x33) \
+ X(X86_CMPMR8, 0x38) \
+ X(X86_CMPMRW, 0x39) \
+ X(X86_CMPRM8, 0x3A) \
+ X(X86_CMPRMW, 0x3B) \
+ X(X86_ARPL, 0x63) \
+ X(X86_TESTMR8, 0x84) \
+ X(X86_TESTMRW, 0x85) \
+ X(X86_XCHGMR8, 0x86) \
+ X(X86_XCHGMRW, 0x87) \
+ X(X86_MOVMR8, 0x88) \
+ X(X86_MOVMRW, 0x89) \
+ X(X86_MOVRM8, 0x8A) \
+ X(X86_MOVRMW, 0x8B) \
+ X(X86_MOVMS, 0x8C) /* Load 4 bytes from segment register */ \
+ X(X86_LEA, 0x8D) \
+ X(X86_MOVSM, 0x8E) /* Store 4 bytes to segment register */ \
+ X(X86_POPM, 0x8F) \
+ X(X86_LES, 0xC4) \
+ X(X86_LDS, 0xC5) \
+ X(X86_SHIFTM18, 0xD0) /* Shift/roll by 1 place */ \
+ X(X86_SHIFTM1W, 0xD1) /* Shift/roll by 1 place */ \
+ X(X86_SHIFTMCL8, 0xD2) /* Shift/roll by CL places */ \
+ X(X86_SHIFTMCLW, 0xD3) /* Shift/roll by CL places */ \
+ X(X86_FLTBLK1, 0xD8) /* Various float ops (1/8) */ \
+ X(X86_FLTBLK2, 0xD9) /* Various float ops (2/8) */ \
+ X(X86_FLTBLK3, 0xDA) /* Various float ops (3/8) */ \
+ X(X86_FLTBLK4, 0xDB) /* Various float ops (4/8) */ \
+ X(X86_FLTBLK5, 0xDC) /* Various float ops (5/8) */ \
+ X(X86_FLTBLK6, 0xDD) /* Various float ops (6/8) */ \
+ X(X86_FLTBLK7, 0xDE) /* Various float ops (7/8) */ \
+ X(X86_FLTBLK8, 0xDF) /* Various float ops (8/8) */ \
+ X(X86_MISCM8, 0xFE) /* Only documented for MRM.reg in {0, 1} */ \
+ X(X86_MISCMW, 0xFF)
+
+/* Single-byte opcodes with a ModRM and a 1-byte immediate operand */
+#define X86_OPS_1BYTE_MRM_I8(X) \
+ X(X86_IMULMI8, 0x6B) /* 3-operand multiply */ \
+ X(X86_ALUMI8, 0x80) /* ALU op in MRM.reg, from immediate */ \
+ X(X86_ALUMI8X, 0x82) /* ALU op in MRM.reg, from immediate, redundant?? */ \
+ X(X86_ALUMI8S, 0x83) /* ALU op in MRM.reg, from immediate, sign-extend */ \
+ X(X86_SHIFTMI8, 0xC0) /* Shift/roll by imm8 places */ \
+ X(X86_SHIFTMIW, 0xC1) /* Shift/roll by imm8 places */ \
+ X(X86_MOVMI8, 0xC6) /* Note: RM.reg must be 0 */
+
+/* Single-byte opcodes with a ModRM and a word-sized immediate operand */
+#define X86_OPS_1BYTE_MRM_IW(X) \
+ X(X86_IMULMIW, 0x69) /* 3-operand multiply */ \
+ X(X86_ALUMIW, 0x81) /* ALU op in MRM.reg, from immediate */ \
+ X(X86_MOVMIW, 0xC7) /* Note: MRM.reg must be 0 */
+
+/* All single-byte x86 instructions */
+#define X86_OPS_1BYTE(X) \
+ X86_OPS_1BYTE_NO(X) \
+ X86_OPS_1BYTE_I8(X) \
+ X86_OPS_1BYTE_IW(X) \
+ X86_OPS_1BYTE_I16(X) \
+ X86_OPS_1BYTE_MRM(X) \
+ X86_OPS_1BYTE_MRM_I8(X) \
+ X86_OPS_1BYTE_MRM_IW(X) \
+ X(X86_ENTER, 0xC8) /* Dumb special case insn: imm16 followed by imm8 */ \
+ X(X86_CRAZY8, 0xF6) /* CRAZY reg-encoded block, has imm8 IFF reg < 2 */ \
+ X(X86_CRAZYW, 0xF7) /* CRAZY reg-encoded block, has imm32/16 IFF reg < 2 */
+
+/* Second bytes of opcodes with no operands */
+#define X86_OPS_2BYTE_NO(X) \
+ X(X86_2B_RDTSC, 0x31) \
+ X(X86_2B_RDPMD, 0x33) \
+ X(X86_2B_SYSENTER, 0x34) \
+ X(X86_2B_PUSHFS, 0xA0) \
+ X(X86_2B_POPFS, 0xA1) \
+ X(X86_2B_CPUID, 0xA2) \
+ X(X86_2B_PUSHGS, 0xA8) \
+ X(X86_2B_POPGS, 0xA9) \
+ X(X86_2B_RSM, 0xAA) \
+ X(X86_2B_BSWAPEAX, 0xC8) \
+ X(X86_2B_BSWAPECX, 0xC9) \
+ X(X86_2B_BSWAPEDX, 0xCA) \
+ X(X86_2B_BSWAPEBX, 0xCB) \
+ X(X86_2B_BSWAPESP, 0xCC) \
+ X(X86_2B_BSWAPEBP, 0xCD) \
+ X(X86_2B_BSWAPESI, 0xCE) \
+ X(X86_2B_BSWAPEDI, 0xCF)
+
+/* Second bytes of opcodes with a word-sized immediate operand */
+#define X86_OPS_2BYTE_IW(X) \
+ X(X86_2B_JOII, 0x80) /* From offset (indirect) */ \
+ X(X86_2B_JNOII, 0x81) /* From offset (indirect) */ \
+ X(X86_2B_JBII, 0x82) /* AKA JC; from offset (indirect) */ \
+ X(X86_2B_JNBII, 0x83) /* AKA JNC; from offset (indirect) */ \
+ X(X86_2B_JZII, 0x84) /* AKA JE; from offset (indirect) */ \
+ X(X86_2B_JNZII, 0x85) /* AKA JNZ; from offset (indirect) */ \
+ X(X86_2B_JNAII, 0x86) /* AKA JBE; from offset (indirect) */ \
+ X(X86_2B_JAII, 0x87) /* AKA JNBE; from offset (indirect) */ \
+ X(X86_2B_JSII, 0x88) /* From offset (indirect) */ \
+ X(X86_2B_JNSII, 0x89) /* From offset (indirect) */ \
+ X(X86_2B_JPII, 0x8A) /* From offset (indirect) */ \
+ X(X86_2B_JNPII, 0x8B) /* From offset (indirect) */ \
+ X(X86_2B_JLII, 0x8C) /* AKA JNGE; from offset (indirect) */ \
+ X(X86_2B_JNLII, 0x8D) /* AKA JGE; from offset (indirect) */ \
+ X(X86_2B_JNGII, 0x8E) /* AKA JLE; from offset (indirect) */ \
+ X(X86_2B_JGII, 0x8F) /* AKA JNLE; from offset (indirect) */
+
+/* Second bytes of opcodes with a ModRM */
+#define X86_OPS_2BYTE_MRM(X) \
+ X(X86_2B_NOP, 0x0D) /* Variable length NOP (3-9 with prefix) */ \
+ X(X86_2B_HINTS1, 0x18) /* Prefetch and hint-nop block 1/8 */ \
+ X(X86_2B_HINTS2, 0x19) /* Prefetch and hint-nop block 2/8 */ \
+ X(X86_2B_HINTS3, 0x1A) /* Prefetch and hint-nop block 3/8 */ \
+ X(X86_2B_HINTS4, 0x1B) /* Prefetch and hint-nop block 4/8 */ \
+ X(X86_2B_HINTS5, 0x1C) /* Prefetch and hint-nop block 5/8 */ \
+ X(X86_2B_HINTS6, 0x1D) /* Prefetch and hint-nop block 6/8 */ \
+ X(X86_2B_HINTS7, 0x1E) /* Prefetch and hint-nop block 7/8 */ \
+ X(X86_2B_HINTS8, 0x1F) /* Prefetch and hint-nop block 8/8 */ \
+ X(X86_2B_CMOVO, 0x40) \
+ X(X86_2B_CMOVNO, 0x41) \
+ X(X86_2B_CMOVB, 0x42) /* AKA CMOVC */ \
+ X(X86_2B_CMOVNB, 0x43) /* AKA CMOVNC */ \
+ X(X86_2B_CMOVZ, 0x44) /* AKA CMOVE */ \
+ X(X86_2B_CMOVNZ, 0x45) /* AKA CMOVNE */ \
+ X(X86_2B_CMOVNA, 0x46) /* AKA CMOVBE */ \
+ X(X86_2B_CMOVA, 0x47) /* AKA CMOVNBE */ \
+ X(X86_2B_CMOVS, 0x48) \
+ X(X86_2B_CMOVNS, 0x49) \
+ X(X86_2B_CMOVP, 0x4A) \
+ X(X86_2B_CMOVNP, 0x4B) \
+ X(X86_2B_CMOVL, 0x4C) /* AKA CMOVNGE */ \
+ X(X86_2B_CMOVNL, 0x4D) /* AKA CMOVGE */ \
+ X(X86_2B_CMOVNG, 0x4E) /* AKA CMOVLE */ \
+ X(X86_2B_CMOVG, 0x4F) /* AKA CMOVNLE */ \
+ X(X86_2B_SETO, 0x90) \
+ X(X86_2B_SETNO, 0x91) \
+ X(X86_2B_SETB, 0x92) /* AKA SETC */ \
+ X(X86_2B_SETNB, 0x93) /* AKA SETNC */ \
+ X(X86_2B_SETZ, 0x94) /* AKA SETE */ \
+ X(X86_2B_SETNZ, 0x95) /* AKA SETNZ */ \
+ X(X86_2B_SETNA, 0x96) /* AKA SETBE */ \
+ X(X86_2B_SETA, 0x97) /* AKA SETNBE */ \
+ X(X86_2B_SETS, 0x98) \
+ X(X86_2B_SETNS, 0x99) \
+ X(X86_2B_SETP, 0x9A) \
+ X(X86_2B_SETNP, 0x9B) \
+ X(X86_2B_SETL, 0x9C) /* AKA SETNGE */ \
+ X(X86_2B_SETNL, 0x9D) /* AKA SETGE */ \
+ X(X86_2B_SETNG, 0x9E) /* AKA SETLE */ \
+ X(X86_2B_SETG, 0x9F) /* AKA SETNLE */ \
+ X(X86_2B_BTMR, 0xA3) \
+ X(X86_2B_SHLDMRCL, 0xA5) \
+ X(X86_2B_BTS, 0xAB) \
+ X(X86_2B_SHRDMRCL, 0xAD) \
+ X(X86_2B_MISC, 0xAE) /* Float env stuff, memory fences */ \
+ X(X86_2B_IMUL, 0xAF) \
+ X(X86_2B_CMPXCHG8, 0xB0) \
+ X(X86_2B_CMPXCHGW, 0xB1) \
+ X(X86_2B_MOVZX8, 0xB6) \
+ X(X86_2B_MOVZXW, 0xB7) \
+ X(X86_2B_POPCNT, 0xB8) \
+ X(X86_2B_BTCRM, 0xBB) \
+ X(X86_2B_BSF, 0xBC) \
+ X(X86_2B_BSR, 0xBD) \
+ X(X86_2B_MOVSX8, 0xBE) \
+ X(X86_2B_MOVSXW, 0xBF) \
+ X(X86_2B_XADDRM8, 0xC0) \
+ X(X86_2B_XADDRMW, 0xC1) \
+ /* NOTE: this one is actually a block with some VMX stuff too; it's only
+ CMPXCHG64 (CMPXCHG8B if you prefer) if MRM.reg = 1, but naming it this
+ way seemed more useful since it's what you'll see in normal userspace
+ programs, which is what we're interested in. */ \
+ X(X86_2B_CMPXCHG64, 0xC7)
+
+/* Second bytes of opcodes with a ModRM and a 1-byte immediate operand */
+#define X86_OPS_2BYTE_MRM_I8(X) \
+ X(X86_2B_SHLDMRI, 0xA4) \
+ X(X86_2B_SHRDMRI, 0xAC) \
+ X(X86_2B_BTXMI, 0xBA) /* BT/BTS/BTR/BTC depending on MRM.reg (4-7) */
+
+#define X86_OPS_2BYTE(X) \
+ X86_OPS_2BYTE_NO(X) \
+ X86_OPS_2BYTE_IW(X) \
+ X86_OPS_2BYTE_MRM(X) \
+ X86_OPS_2BYTE_MRM_I8(X)
+
+#define _X86_ENUM(name, value) name = value,
+enum {
+ X86_PREFIXES(_X86_ENUM)
+ X86_OPS_1BYTE(_X86_ENUM)
+ X86_2BYTE = 0x0F, /* First byte of a 2- or 3-byte opcode */
+ X86_OPS_2BYTE(_X86_ENUM)
+ X86_3BYTE1 = 0x38, /* One of the two second bytes of a three-byte opcode */
+ X86_3BYTE2 = 0x3A, /* The other second byte of a three-byte opcode */
+ X86_3DNOW = 0x0F /* The second byte of a three-byte 3DNow! opcode */
+};
+#undef _X86_ENUM
+
+/*
+ * Returns the length of an instruction, or -1 if it's a "known unknown" or
+ * invalid instruction. Doesn't handle unknown unknowns: may explode or hang on
+ * arbitrary untrusted data. Also doesn't handle, among other things, 3DNow!,
+ * SSE, MMX, AVX, and such. Aims to be small and fast rather than comprehensive.
+ */
+int x86_len(const void *insn);
+
+/* Constructs a ModRM byte, assuming the parameters are all in range. */
+#define X86_MODRM(mod, reg, rm) (unsigned char)((mod) << 6 | (reg) << 3 | rm)
+
+#endif
+
+// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/test/bitbuf.test.c b/test/bitbuf.test.c
index 58d1c4d..324a7f6 100644
--- a/test/bitbuf.test.c
+++ b/test/bitbuf.test.c
@@ -14,7 +14,7 @@ static union {
} bb_buf;
static struct bitbuf bb = {bb_buf.buf, 512, 512 * 8, 0, false, false, "test"};
-TEST("The possible UB in bitbuf_appendbuf shouldn't trigger horrible bugs", 0) {
+TEST("The possible UB in bitbuf_appendbuf shouldn't trigger horrible bugs") {
char unalign[3] = {'X', 'X', 'X'};
char _buf[32 + sizeof(bitbuf_cell)];
char *buf = _buf;
diff --git a/test/hook.test.c b/test/hook.test.c
index 50e07a8..b2e841c 100644
--- a/test/hook.test.c
+++ b/test/hook.test.c
@@ -4,7 +4,7 @@
#ifdef _WIN32
-#include "../src/udis86.c"
+#include "../src/x86.c"
#include "../src/hook.c"
#include <stdarg.h>
@@ -32,20 +32,20 @@ static int other_hook(int a, int b) {
return orig_other_function(a, b) + 5;
}
-TEST("Inline hooks should be able to wrap the original function", 0) {
+TEST("Inline hooks should be able to wrap the original function") {
orig_some_function = hook_inline(&some_function, &some_hook);
if (!orig_some_function) return false;
return some_function(5, 5) == 15;
}
-TEST("Inline hooks should be removable again", 0) {
+TEST("Inline hooks should be removable again") {
orig_some_function = hook_inline(&some_function, &some_hook);
if (!orig_some_function) return false;
unhook_inline(orig_some_function);
return some_function(5, 5) == 10;
}
-TEST("Multiple functions should be able to be inline hooked at once", 0) {
+TEST("Multiple functions should be able to be inline hooked at once") {
orig_some_function = hook_inline(&some_function, &some_hook);
if (!orig_some_function) return false;
diff --git a/test/kv.test.c b/test/kv.test.c
index 12f2801..7f82b8a 100644
--- a/test/kv.test.c
+++ b/test/kv.test.c
@@ -33,7 +33,7 @@ Val2// comment\n\
";
static const int sz = sizeof(data) - 1;
-TEST("parsing should work with any buffer size", 0) {
+TEST("parsing should work with any buffer size") {
for (int chunksz = 3; chunksz <= sz; ++chunksz) {
struct kv_parser kvp = {0};
// sending data in chunks to test reentrancy
diff --git a/tools/mkbindist.bat b/tools/mkbindist.bat
index 687761d..75d4ea1 100644
--- a/tools/mkbindist.bat
+++ b/tools/mkbindist.bat
@@ -20,8 +20,11 @@ set name=sst-v%major%.%minor%-BETA-win32
md TEMP-%name% || exit /B
copy sst.dll TEMP-%name%\sst.dll || exit /B
copy dist\LICENCE.windows TEMP-%name%\LICENCE || exit /B
+:: arbitrary dates to make zip deterministic! consider changing on next actual release date!
+powershell (Get-Item TEMP-%name%\sst.dll).LastWriteTime = new-object DateTime 2022, 1, 1, 0, 0, 0
+powershell (Get-Item TEMP-%name%\LICENCE).LastWriteTime = new-object DateTime 2022, 1, 1, 0, 0, 0
pushd TEMP-%name%
-"%SEVENZIP%" a %name%.zip sst.dll LICENCE || exit /B
+"%SEVENZIP%" a -mtc=off %name%.zip sst.dll LICENCE || exit /B
move %name%.zip ..\release\%name%.zip
popd
rd /s /q TEMP-%name%\ || exit /B