summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rwxr-xr-xcompile1
-rw-r--r--compile.bat1
-rw-r--r--gamedata/gamelib.txt12
-rw-r--r--gamedata/vguimatsurface.txt3
-rw-r--r--src/autojump.c1
-rw-r--r--src/engineapi.c13
-rw-r--r--src/engineapi.h27
-rw-r--r--src/gametype.h21
-rw-r--r--src/hud.c12
-rw-r--r--src/hud.h17
-rw-r--r--src/inputhud.c443
-rw-r--r--src/l4dwarp.c16
-rw-r--r--src/os.c1
-rw-r--r--src/rinput.c2
-rw-r--r--src/sst.c1
-rw-r--r--src/x86.c4
-rw-r--r--src/x86.h17
-rw-r--r--test/x86.test.c9
-rw-r--r--tools/x86test.c43
19 files changed, 607 insertions, 37 deletions
diff --git a/compile b/compile
index b61bd3d..9045f53 100755
--- a/compile
+++ b/compile
@@ -74,6 +74,7 @@ src="\
hexcolour.c
hook.c
hud.c
+ inputhud.c
kvsys.c
l4dmm.c
l4dreset.c
diff --git a/compile.bat b/compile.bat
index fe96d29..11d0aac 100644
--- a/compile.bat
+++ b/compile.bat
@@ -86,6 +86,7 @@ setlocal DisableDelayedExpansion
:+ hexcolour.c
:+ hook.c
:+ hud.c
+:+ inputhud.c
:+ kvsys.c
:+ l4dmm.c
:+ l4dreset.c
diff --git a/gamedata/gamelib.txt b/gamedata/gamelib.txt
index 65e250b..615ecf4 100644
--- a/gamedata/gamelib.txt
+++ b/gamedata/gamelib.txt
@@ -43,4 +43,16 @@ vtidx_OnGameplayStart
L4D2 11 # note: just happens to be the same on linux!
L4D1 11
+# CInput
+vtidx_CreateMove 3
+vtidx_DecodeUserCmdFromBuffer 7
+vtidx_GetUserCmd 8
+
+# VClient
+vtidx_VClient_DecodeUserCmdFromBuffer 22
+ Client014
+ L4D2 43 # shoutouts to 2000
+ Client013
+ L4Dbased 23
+
# vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/gamedata/vguimatsurface.txt b/gamedata/vguimatsurface.txt
index a9dc3f2..e35c125 100644
--- a/gamedata/vguimatsurface.txt
+++ b/gamedata/vguimatsurface.txt
@@ -45,5 +45,8 @@ vtidx_GetFontTall
vtidx_GetCharacterWidth
OrangeBoxbased 71
L4D 71
+vtidx_GetTextSize
+ OrangeBoxbased 72
+ L4D 72
# vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/autojump.c b/src/autojump.c
index cc44573..64ed436 100644
--- a/src/autojump.c
+++ b/src/autojump.c
@@ -35,7 +35,6 @@ REQUIRE_GLOBAL(factory_client) // note: server will never be null
DEF_CVAR(sst_autojump, "Jump upon hitting the ground while holding space", 0,
CON_REPLICATE | CON_DEMO | CON_HIDDEN)
-#define IN_JUMP 2
#define NIDX 256 // *completely* arbitrary lol
static bool justjumped[NIDX] = {0};
static inline int handleidx(ulong h) { return h & (1 << 11) - 1; }
diff --git a/src/engineapi.c b/src/engineapi.c
index 785245d..5a78a92 100644
--- a/src/engineapi.c
+++ b/src/engineapi.c
@@ -90,10 +90,15 @@ bool engineapi_init(int pluginver) {
}
// detect p1 for the benefit of specific features
- if (!GAMETYPE_MATCHES(Portal2) && con_findcmd("upgrade_portalgun")) {
- _gametype_tag |= _gametype_tag_Portal1;
- if (!con_findvar("tf_escort_score_rate")) {
- _gametype_tag |= _gametype_tag_Portal1_3420;
+ if (!GAMETYPE_MATCHES(Portal2)) {
+ if (con_findcmd("upgrade_portalgun")) {
+ _gametype_tag |= _gametype_tag_Portal1;
+ if (!con_findvar("tf_escort_score_rate")) {
+ _gametype_tag |= _gametype_tag_Portal1_3420;
+ }
+ }
+ else if (con_findcmd("phys_swap")) {
+ _gametype_tag |= _gametype_tag_HL2series;
}
}
diff --git a/src/engineapi.h b/src/engineapi.h
index 4f96b73..308e34e 100644
--- a/src/engineapi.h
+++ b/src/engineapi.h
@@ -154,6 +154,33 @@ struct CServerPlugin /* : IServerPluginHelpers */ {
};
extern struct CServerPlugin *pluginhandler;
+// input button bits
+#define IN_ATTACK (1 << 0)
+#define IN_JUMP (1 << 1)
+#define IN_DUCK (1 << 2)
+#define IN_FORWARD (1 << 3)
+#define IN_BACK (1 << 4)
+#define IN_USE (1 << 5)
+#define IN_CANCEL (1 << 6)
+#define IN_LEFT (1 << 7)
+#define IN_RIGHT (1 << 8)
+#define IN_MOVELEFT (1 << 9)
+#define IN_MOVERIGHT (1 << 10)
+#define IN_ATTACK2 (1 << 11)
+#define IN_RUN (1 << 12)
+#define IN_RELOAD (1 << 13)
+#define IN_ALT1 (1 << 14)
+#define IN_ALT2 (1 << 15)
+#define IN_SCORE (1 << 16)
+#define IN_SPEED (1 << 17)
+#define IN_WALK (1 << 18)
+#define IN_ZOOM (1 << 19)
+#define IN_WEAPON1 (1 << 20)
+#define IN_WEAPON2 (1 << 21)
+#define IN_BULLRUSH (1 << 22)
+#define IN_GRENADE1 (1 << 23)
+#define IN_GRENADE2 (1 << 24)
+
/*
* Called on plugin init to attempt to initialise various core interfaces.
* This includes console/cvar initialisation and populating gametype and
diff --git a/src/gametype.h b/src/gametype.h
index 81b860a..35a43be 100644
--- a/src/gametype.h
+++ b/src/gametype.h
@@ -38,22 +38,23 @@ extern u64 _gametype_tag;
/* games needing game-specific stuff, but not tied to a singular branch */
#define _gametype_tag_Portal1 (1 << 8)
+#define _gametype_tag_HL2series (1 << 9) /* HL2, episodes, and mods */
/* VEngineClient versions */
-#define _gametype_tag_Client015 (1 << 9)
-#define _gametype_tag_Client014 (1 << 10)
-#define _gametype_tag_Client013 (1 << 11)
-#define _gametype_tag_Client012 (1 << 12)
-#define _gametype_tag_Server021 (1 << 13)
+#define _gametype_tag_Client015 (1 << 10)
+#define _gametype_tag_Client014 (1 << 11)
+#define _gametype_tag_Client013 (1 << 12)
+#define _gametype_tag_Client012 (1 << 13)
+#define _gametype_tag_Server021 (1 << 14)
/* ServerGameDLL versions */
-#define _gametype_tag_SrvDLL009 (1 << 14) // 2013-ish
-#define _gametype_tag_SrvDLL005 (1 << 15) // mostly everything else, it seems
+#define _gametype_tag_SrvDLL009 (1 << 15) // 2013-ish
+#define _gametype_tag_SrvDLL005 (1 << 16) // mostly everything else, it seems
/* games needing version-specific stuff */
-#define _gametype_tag_Portal1_3420 (1 << 16)
-#define _gametype_tag_L4D2_2147plus (1 << 17)
-#define _gametype_tag_TheLastStand (1 << 18) /* The JAiZ update */
+#define _gametype_tag_Portal1_3420 (1 << 17)
+#define _gametype_tag_L4D2_2147plus (1 << 18)
+#define _gametype_tag_TheLastStand (1 << 19) /* The JAiZ update */
/* Matches for any multiple possible tags */
#define _gametype_tag_L4D (_gametype_tag_L4D1 | _gametype_tag_L4D2)
diff --git a/src/hud.c b/src/hud.c
index b1cbb56..2334bb8 100644
--- a/src/hud.c
+++ b/src/hud.c
@@ -81,10 +81,12 @@ DECL_VFUNC_DYN(void, DrawPolyLine, int *, int *, int)
DECL_VFUNC_DYN(void, DrawSetTextFont, struct handlewrap)
DECL_VFUNC_DYN(void, DrawSetTextColor, struct rgba)
DECL_VFUNC_DYN(void, DrawSetTextPos, int, int)
-DECL_VFUNC_DYN(void, DrawPrintText, ushort *, int, int)
+DECL_VFUNC_DYN(void, DrawPrintText, hud_wchar *, int, int)
DECL_VFUNC_DYN(void, GetScreenSize, int *, int *)
DECL_VFUNC_DYN(int, GetFontTall, struct handlewrap)
DECL_VFUNC_DYN(int, GetCharacterWidth, struct handlewrap, int)
+DECL_VFUNC_DYN(int, GetTextSize, struct handlewrap, const hud_wchar *,
+ int *, int *)
// vgui::Panel
DECL_VFUNC_DYN(void, SetPaintEnabled, bool)
@@ -122,7 +124,7 @@ void hud_drawpolyline(int *x, int *y, int npoints, struct rgba colour) {
DrawPolyLine(matsurf, x, y, npoints);
}
-void hud_drawtext(ulong font, int x, int y, struct rgba colour, ushort *str,
+void hud_drawtext(ulong font, int x, int y, struct rgba colour, hud_wchar *str,
int len) {
DrawSetTextFont(matsurf, (struct handlewrap){font});
DrawSetTextPos(matsurf, x, y);
@@ -138,10 +140,14 @@ int hud_fontheight(ulong font) {
return GetFontTall(matsurf, (struct handlewrap){font});
}
-int hud_charwidth(ulong font, int ch) {
+int hud_charwidth(ulong font, hud_wchar ch) {
return GetCharacterWidth(matsurf, (struct handlewrap){font}, ch);
}
+void hud_textsize(ulong font, const ushort *s, int *width, int *height) {
+ GetTextSize(matsurf, (struct handlewrap){font}, s, width, height);
+}
+
static bool find_toolspanel(void *enginevgui) {
const uchar *insns = (const uchar *)VFUNC(enginevgui, GetPanel);
for (const uchar *p = insns; p - insns < 16;) {
diff --git a/src/hud.h b/src/hud.h
index fed152f..e3aea1d 100644
--- a/src/hud.h
+++ b/src/hud.h
@@ -1,5 +1,6 @@
/*
* Copyright © 2022 Matthew Wozniak <sirtomato999@gmail.com>
+ * Copyright © 2024 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
@@ -21,6 +22,13 @@
#include "engineapi.h"
#include "intdefs.h"
+// ugh!
+#ifdef _WIN32
+typedef ushort hud_wchar;
+#else
+typedef int hud_wchar;
+#endif
+
/*
* Emitted when the game HUD is being drawn. Allows features to draw their own
* additional overlays atop the game's standard HUD.
@@ -57,16 +65,19 @@ void hud_drawline(int x0, int y0, int x1, int y1, struct rgba colour);
void hud_drawpolyline(int *xs, int *ys, int npoints, struct rgba colour);
/* Draws text using a given font handle. */
-void hud_drawtext(ulong font, int x, int y, struct rgba colour, ushort *str,
+void hud_drawtext(ulong font, int x, int y, struct rgba colour, hud_wchar *str,
int len);
-/* Returns the width and height of the game window in pixels. */
+/* Gets the width and height of the game window in pixels. */
void hud_screensize(int *width, int *height);
/* Returns the height of a font, in pixels. */
int hud_fontheight(ulong font);
/* Returns the width of a font character, in pixels. */
-int hud_charwidth(ulong font, int ch);
+int hud_charwidth(ulong font, hud_wchar ch);
+
+/* Gets the width and height of string s, in pixels, using the given font. */
+void hud_textsize(ulong font, const ushort *s, int *width, int *height);
#endif
diff --git a/src/inputhud.c b/src/inputhud.c
new file mode 100644
index 0000000..c4f7342
--- /dev/null
+++ b/src/inputhud.c
@@ -0,0 +1,443 @@
+/*
+ * Copyright © 2022 Matthew Wozniak <sirtomato999@gmail.com>
+ * Copyright © 2022 Willian Henrique <wsimanbrazil@yahoo.com.br>
+ * Copyright © 2024 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 <math.h>
+
+#include "con_.h"
+#include "engineapi.h"
+#include "event.h"
+#include "errmsg.h"
+#include "gamedata.h"
+#include "gametype.h"
+#include "hexcolour.h"
+#include "hook.h"
+#include "hud.h"
+#include "intdefs.h"
+#include "feature.h"
+#include "mem.h"
+#include "vcall.h"
+#include "x86.h"
+#include "x86util.h"
+
+FEATURE("button input HUD")
+REQUIRE_GAMEDATA(vtidx_CreateMove)
+REQUIRE_GAMEDATA(vtidx_DecodeUserCmdFromBuffer)
+REQUIRE_GAMEDATA(vtidx_GetUserCmd)
+REQUIRE_GAMEDATA(vtidx_VClient_DecodeUserCmdFromBuffer)
+REQUIRE_GLOBAL(factory_client)
+REQUIRE(hud)
+
+DEF_CVAR(sst_inputhud, "Enable button input HUD", 0, CON_ARCHIVE | CON_HIDDEN)
+DEF_CVAR(sst_inputhud_bgcolour_normal,
+ "Input HUD default key background colour (RGBA hex)", "4040408C",
+ CON_ARCHIVE | CON_HIDDEN)
+DEF_CVAR(sst_inputhud_bgcolour_pressed,
+ "Input HUD pressed key background colour (RGBA hex)", "202020C8",
+ CON_ARCHIVE | CON_HIDDEN)
+DEF_CVAR(sst_inputhud_fgcolour, "Input HUD text colour (RGBA hex)", "F0F0F0FF",
+ CON_ARCHIVE | CON_HIDDEN)
+DEF_CVAR_MINMAX(sst_inputhud_scale, "Input HUD size (multiple of minimum)",
+ 1.5, 1, 4, CON_ARCHIVE | CON_HIDDEN)
+DEF_CVAR_MINMAX(sst_inputhud_x,
+ "Input HUD x position (fraction between screen left and right)",
+ 0.02, 0, 1, CON_ARCHIVE | CON_HIDDEN)
+DEF_CVAR_MINMAX(sst_inputhud_y,
+ "Input HUD y position (fraction between screen top and bottom)",
+ 0.95, 0, 1, CON_ARCHIVE | CON_HIDDEN)
+
+static void *input;
+static int heldbuttons = 0, tappedbuttons = 0;
+
+static struct rgba colours[3] = {
+ {64, 64, 64, 140},
+ {16, 16, 16, 200},
+ {240, 240, 240, 255}
+};
+
+static void colourcb(struct con_var *v) {
+ if (v == sst_inputhud_bgcolour_normal) {
+ hexcolour_rgba(colours[0].bytes, con_getvarstr(v));
+ }
+ else if (v == sst_inputhud_bgcolour_pressed) {
+ hexcolour_rgba(colours[1].bytes, con_getvarstr(v));
+ }
+ else /* v == sst_inputhud_fg */ {
+ hexcolour_rgba(colours[2].bytes, con_getvarstr(v));
+ }
+}
+
+struct CUserCmd {
+ void **vtable;
+ int cmd, tick;
+ struct vec3f angles;
+ float fmove, smove, umove;
+ int buttons;
+ char impulse;
+ int weaponselect, weaponsubtype;
+ int rngseed;
+ short mousedx, mousedy;
+ // client only:
+ bool predicted;
+ struct CUtlVector *entgroundcontact;
+};
+
+#define vtidx_GetUserCmd_l4dbased vtidx_GetUserCmd
+DECL_VFUNC_DYN(struct CUserCmd *, GetUserCmd, int)
+DECL_VFUNC_DYN(struct CUserCmd *, GetUserCmd_l4dbased, int, int)
+
+typedef void (*VCALLCONV CreateMove_func)(void *, int, float, bool);
+static CreateMove_func orig_CreateMove;
+static void VCALLCONV hook_CreateMove(void *this, int seq, float ft,
+ bool active) {
+ orig_CreateMove(this, seq, ft, active);
+ struct CUserCmd *cmd = GetUserCmd(this, seq);
+ // trick: to ensure every input (including scroll wheel) is displayed for at
+ // least a frame, even at sub-tickrate framerates, we accumulate tapped
+ // buttons with bitwise or. once these are drawn, tappedbuttons is cleared,
+ // but heldbuttons maintains its state, so stuff doesn't flicker constantly
+ if (cmd) { heldbuttons = cmd->buttons; tappedbuttons |= cmd->buttons; }
+}
+// basically a dupe, but calling the other version of GetUserCmd
+static void VCALLCONV hook_CreateMove_l4dbased(void *this, int seq, float ft,
+ bool active) {
+ orig_CreateMove(this, seq, ft, active);
+ struct CUserCmd *cmd = GetUserCmd_l4dbased(this, -1, seq);
+ if (cmd) { heldbuttons = cmd->buttons; tappedbuttons |= cmd->buttons; }
+}
+
+typedef void (*VCALLCONV DecodeUserCmdFromBuffer_func)(void *, void *, int);
+typedef void (*VCALLCONV DecodeUserCmdFromBuffer_l4dbased_func)(void *, int,
+ void *, int);
+static union {
+ DecodeUserCmdFromBuffer_func prel4d;
+ DecodeUserCmdFromBuffer_l4dbased_func l4dbased;
+} _orig_DecodeUserCmdFromBuffer;
+#define orig_DecodeUserCmdFromBuffer _orig_DecodeUserCmdFromBuffer.prel4d
+#define orig_DecodeUserCmdFromBuffer_l4dbased \
+ _orig_DecodeUserCmdFromBuffer.l4dbased
+static void VCALLCONV hook_DecodeUserCmdFromBuffer(void *this, void *reader,
+ int seq) {
+ orig_DecodeUserCmdFromBuffer(this, reader, seq);
+ struct CUserCmd *cmd = GetUserCmd(this, seq);
+ if (cmd) { heldbuttons = cmd->buttons; tappedbuttons |= cmd->buttons; }
+}
+static void VCALLCONV hook_DecodeUserCmdFromBuffer_l4dbased(void *this,
+ int slot, void *reader, int seq) {
+ orig_DecodeUserCmdFromBuffer_l4dbased(this, slot, reader, seq);
+ struct CUserCmd *cmd = GetUserCmd_l4dbased(this, slot, seq);
+ if (cmd) { heldbuttons = cmd->buttons; tappedbuttons |= cmd->buttons; }
+}
+
+static inline int bsf(uint x) {
+ // this should generate xor <ret>, <ret>; bsfl <ret>, <x>.
+ // doing a straight bsf (e.g. via BitScanForward or __builtin_ctz) creates
+ // a false dependency on many CPUs, which compilers don't understand somehow
+ int ret = 0;
+#if defined(__GNUC__) || defined(__clang__)
+ __asm__ volatile (
+ "bsfl %1, %0\n"
+ : "+r" (ret)
+ : "r" (x)
+ );
+ return ret;
+#else
+#error need some sort of inline asm, or a non-broken(!) bitscan intrinsic
+#endif
+}
+
+// IMPORTANT: these things must all match the button order in engineapi.h
+static const struct {
+ hud_wchar *s;
+ int len;
+} text[] = {
+ /* IN_ATTACK */ {L"Pri", 3},
+ /* IN_JUMP */ {L"Jump", 4},
+ /* IN_DUCK */ {L"Duck", 4},
+ /* IN_FORWARD */ {L"Fwd", 3},
+ /* IN_BACK */ {L"Back", 4},
+ /* IN_USE */ {L"Use", 3},
+ /* IN_CANCEL */ {0},
+ /* IN_LEFT */ {L"LTurn", 5},
+ /* IN_RIGHT */ {L"RTurn", 5},
+ /* IN_MOVELEFT */ {L"Left", 4},
+ /* IN_MOVERIGHT */ {L"Right", 5},
+ /* IN_ATTACK2 */ {L"Sec", 3},
+ /* IN_RUN */ {0},
+ /* IN_RELOAD */ {L"Rld", 3},
+ /* IN_ALT1 */ {0},
+ /* IN_ALT2 */ {0},
+ /* IN_SCORE */ {0},
+ /* IN_SPEED */ {L"Speed", 5},
+ /* IN_WALK */ {L"Walk", 4},
+ /* IN_ZOOM */ {L"Zoom", 4}
+ // ignoring the rest
+};
+
+struct layout {
+ int mask;
+ schar w, h;
+ struct { schar x, y; } pos[20]; // XXX: should make flexible???
+};
+
+// input layouts (since some games don't use all input bits) {{{
+
+static const struct layout layout_hl2 = {
+ IN_ATTACK | IN_JUMP | IN_DUCK | IN_FORWARD | IN_BACK | IN_USE | IN_LEFT |
+ IN_RIGHT | IN_MOVELEFT | IN_MOVERIGHT | IN_ATTACK2 | IN_RELOAD | IN_SPEED |
+ IN_WALK | IN_ZOOM,
+ 15, 6,
+ {
+ // F 1 2
+ // L B R U R Z
+ // W S D J l r
+ /* IN_ATTACK */ {10, 0}, /* IN_JUMP */ { 6, 4},
+ /* IN_DUCK */ { 4, 4}, /* IN_FORWARD */ { 3, 0},
+ /* IN_BACK */ { 3, 2}, /* IN_USE */ { 9, 2},
+ /* IN_CANCEL */ {0}, /* IN_LEFT */ {10, 4},
+ /* IN_RIGHT */ {12, 4}, /* IN_MOVELEFT */ { 1, 2},
+ /* IN_MOVERIGHT */ { 5, 2}, /* IN_ATTACK2 */ {12, 0},
+ /* IN_RUN */ {0}, /* IN_RELOAD */ {11, 2},
+ /* IN_ALT1 */ {0}, /* IN_ALT2 */ {0},
+ /* IN_SCORE */ {0}, /* IN_SPEED */ { 2, 4},
+ /* IN_WALK */ { 0, 4}, /* IN_ZOOM */ {13, 2}
+ }
+};
+
+static const struct layout layout_portal1 = {
+ IN_ATTACK | IN_JUMP | IN_DUCK | IN_FORWARD | IN_BACK | IN_USE | IN_LEFT |
+ IN_RIGHT | IN_MOVELEFT | IN_MOVERIGHT | IN_ATTACK2,
+ 11, 6,
+ {
+ // F 1 2
+ // L B R U
+ // D J l r
+ /* IN_ATTACK */ {7, 0}, /* IN_JUMP */ {3, 4},
+ /* IN_DUCK */ {1, 4}, /* IN_FORWARD */ {2, 0},
+ /* IN_BACK */ {2, 2}, /* IN_USE */ {8, 2},
+ /* IN_CANCEL */ {0}, /* IN_LEFT */ {7, 4},
+ /* IN_RIGHT */ {9, 4}, /* IN_MOVELEFT */ {0, 2},
+ /* IN_MOVERIGHT */ {4, 2}, /* IN_ATTACK2 */ {9, 0}
+ }
+};
+
+// TODO(compat): add portal2 layout once there's hud gamedata for portal 2
+//static const struct layout layout_portal2 = {
+// IN_ATTACK | IN_JUMP | IN_DUCK | IN_FORWARD | IN_BACK | IN_USE | IN_LEFT |
+// IN_RIGHT | IN_MOVELEFT | IN_MOVERIGHT | IN_ATTACK2 | IN_ZOOM,
+// 11, 6,
+// {
+// // F 1 2
+// // L B R U Z
+// // D J l r
+// /* IN_ATTACK */ {7, 0}, /* IN_JUMP */ {3, 4},
+// /* IN_DUCK */ {1, 4}, /* IN_FORWARD */ {2, 0},
+// /* IN_BACK */ {2, 2}, /* IN_USE */ {7, 2},
+// /* IN_CANCEL */ {0}, /* IN_LEFT */ {7, 4},
+// /* IN_RIGHT */ {9, 4}, /* IN_MOVELEFT */ {0, 2},
+// /* IN_MOVERIGHT */ {4, 2}, /* IN_ATTACK2 */ {9, 0},
+// /* IN_RUN */ {0}, /* IN_RELOAD */ {0},
+// /* IN_ALT1 */ {0}, /* IN_ALT2 */ {0},
+// /* IN_SCORE */ {0}, /* IN_SPEED */ {0},
+// /* IN_WALK */ {0}, /* IN_ZOOM */ {9, 2}
+// }
+//};
+
+static const struct layout layout_l4d = {
+ IN_ATTACK | IN_JUMP | IN_DUCK | IN_FORWARD | IN_BACK | IN_USE | IN_LEFT |
+ IN_RIGHT | IN_MOVELEFT | IN_MOVERIGHT | IN_ATTACK2 | IN_SPEED | IN_ZOOM,
+ 11, 6,
+ {
+ // F 1 2
+ // L B R U Z
+ // W D J l r
+ /* IN_ATTACK */ {7, 0}, /* IN_JUMP */ {4, 4},
+ /* IN_DUCK */ {2, 4}, /* IN_FORWARD */ {2, 0},
+ /* IN_BACK */ {2, 2}, /* IN_USE */ {7, 2},
+ /* IN_CANCEL */ {0}, /* IN_LEFT */ {7, 4},
+ /* IN_RIGHT */ {9, 4}, /* IN_MOVELEFT */ {0, 2},
+ /* IN_MOVERIGHT */ {4, 2}, /* IN_ATTACK2 */ {9, 0},
+ /* IN_RUN */ {0}, /* IN_RELOAD */ {0},
+ /* IN_ALT1 */ {0}, /* IN_ALT2 */ {0},
+ /* IN_SCORE */ {0}, /* IN_SPEED */ {0, 4},
+ /* IN_WALK */ {0}, /* IN_ZOOM */ {9, 2}
+ }
+};
+
+// }}}
+
+static const struct layout *layout = &layout_hl2;
+
+static const char *const fontnames[] = {
+ "DebugFixedSmall",
+ "HudSelectionText",
+ "CommentaryDefault",
+ "DefaultVerySmall",
+ "DefaultSmall",
+ "Default"
+};
+static struct { ulong h; int sz; } fonts[countof(fontnames)];
+
+HANDLE_EVENT(HudPaint, void) {
+ if (!con_getvari(sst_inputhud)) return;
+ int screenw, screenh;
+ hud_screensize(&screenw, &screenh);
+ int basesz = screenw > screenh ? screenw : screenh;
+ int boxsz = ceilf(basesz * 0.025f);
+ if (boxsz < 24) boxsz = 24;
+ boxsz *= con_getvarf(sst_inputhud_scale);
+ int idealfontsz = boxsz - 8; // NOTE: this is overall text width, see INIT
+ ulong font = 0; int fontsz = 0;
+ // get the biggest font that'll fit the box
+ // XXX: can/should we avoid doing this every frame?
+ for (int i = 0; i < countof(fonts); ++i) {
+ // XXX: fonts aren't sorted... should we bother?
+ if_cold (!fonts[i].h) continue;
+ if (fonts[i].sz < fontsz) continue;
+ if (fonts[i].sz <= idealfontsz) font = fonts[i].h;
+ //else break; // not sorted
+ }
+ int gap = (boxsz | 32) >> 5; // minimum 1 pixel gap
+ int w = (boxsz + gap) * layout->w / 2 - gap;
+ int h = (boxsz + gap) * layout->h / 2 - gap;
+ int basex = roundf(con_getvarf(sst_inputhud_x) * (screenw - w));
+ int basey = roundf(con_getvarf(sst_inputhud_y) * (screenh - h));
+ int buttons = heldbuttons | tappedbuttons;
+ for (int mask = layout->mask, bitidx, bit; mask; mask ^= bit) {
+ bitidx = bsf(mask); bit = 1 << bitidx;
+ // divide sizes by 2 here to allow in-between positioning
+ int x = basex + layout->pos[bitidx].x * (boxsz + gap) / 2;
+ int y = basey + layout->pos[bitidx].y * (boxsz + gap) / 2;
+ hud_drawrect(x, y, x + boxsz, y + boxsz,
+ colours[!!(buttons & bit)], true);
+ if_hot (font) {
+ int tw, th;
+ hud_textsize(font, text[bitidx].s, &tw, &th);
+ hud_drawtext(font, x + (boxsz - tw) / 2, y + (boxsz - th) / 2,
+ colours[2], text[bitidx].s, text[bitidx].len);
+ }
+ }
+ tappedbuttons = 0;
+}
+
+// find the CInput "input" global
+static inline bool find_input(void* vclient) {
+#ifdef _WIN32
+ // the only CHLClient::DecodeUserCmdFromBuffer() does is call a virtual
+ // function, so find its thisptr being loaded into ECX
+ void* decodeusercmd =
+ (*(void***)vclient)[vtidx_VClient_DecodeUserCmdFromBuffer];
+ for (uchar *p = (uchar *)decodeusercmd; p - (uchar *)decodeusercmd < 32;) {
+ if (p[0] == X86_MOVRMW && p[1] == X86_MODRM(0, 1, 5)) {
+ void **indirect = mem_loadptr(p + 2);
+ input = *indirect;
+ return true;
+ }
+ NEXT_INSN(p, "input object");
+ }
+#else
+#warning TODO(linux): implement linux equivalent (see demorec.c)
+#endif
+ return false;
+}
+
+INIT {
+ void *vclient;
+ if (!(vclient = factory_client("VClient015", 0)) &&
+ !(vclient = factory_client("VClient016", 0)) &&
+ !(vclient = factory_client("VClient017", 0))) {
+ errmsg_errorx("couldn't get client interface");
+ return false;
+ }
+ if (!find_input(vclient)) {
+ errmsg_errorx("couldn't find input global");
+ return false;
+ }
+ for (int i = 0; i < countof(fontnames); ++i) {
+ fonts[i].h = hud_getfont(fontnames[i], true);
+ if (!fonts[i].h) {
+ errmsg_warnx("couldn't get \"%s\" font", fontnames[i]);
+ }
+ else {
+ int dummy;
+ // use (roughly) the widest string as a reference for what will fit
+ hud_textsize(fonts[i].h, L"Speed", &fonts[i].sz, &dummy);
+ }
+ }
+ void **vtable = mem_loadptr(input);
+ // just unprotect the first few pointers (GetUserCmd is 8)
+ if (!os_mprot(vtable, sizeof(void *) * 8, PAGE_READWRITE)) {
+ errmsg_errorsys("couldn't make virtual table writable");
+ return false;
+ }
+ if (GAMETYPE_MATCHES(L4Dbased)) {
+ orig_CreateMove = (CreateMove_func)hook_vtable(vtable, vtidx_CreateMove,
+ (void *)&hook_CreateMove_l4dbased);
+ orig_DecodeUserCmdFromBuffer = (DecodeUserCmdFromBuffer_func)hook_vtable(
+ vtable, vtidx_DecodeUserCmdFromBuffer,
+ (void *)&hook_DecodeUserCmdFromBuffer_l4dbased);
+ }
+ else {
+ orig_CreateMove = (CreateMove_func)hook_vtable(vtable, vtidx_CreateMove,
+ (void *)&hook_CreateMove);
+ orig_DecodeUserCmdFromBuffer = (DecodeUserCmdFromBuffer_func)hook_vtable(
+ vtable, vtidx_DecodeUserCmdFromBuffer,
+ (void *)&hook_DecodeUserCmdFromBuffer);
+ }
+
+ if (GAMETYPE_MATCHES(Portal1)) layout = &layout_portal1;
+ //else if (GAMETYPE_MATCHES(Portal2)) layout = &layout_portal2;
+ else if (GAMETYPE_MATCHES(L4D)) layout = &layout_l4d;
+ // TODO(compat): more game-specific layouts!
+
+ sst_inputhud->base.flags &= ~CON_HIDDEN;
+ sst_inputhud_scale->base.flags &= ~CON_HIDDEN;
+ sst_inputhud_bgcolour_normal->base.flags &= ~CON_HIDDEN;
+ sst_inputhud_bgcolour_normal->cb = &colourcb;
+ sst_inputhud_bgcolour_pressed->base.flags &= ~CON_HIDDEN;
+ sst_inputhud_bgcolour_pressed->cb = &colourcb;
+ sst_inputhud_fgcolour->base.flags &= ~CON_HIDDEN;
+ sst_inputhud_fgcolour->cb = &colourcb;
+ sst_inputhud_x->base.flags &= ~CON_HIDDEN;
+ sst_inputhud_y->base.flags &= ~CON_HIDDEN;
+
+ // HACK: default HUD position would clash with L4D player health HUDs and
+ // HL2 sprint HUD, so move it up. this currently has to be done in a super
+ // crappy, nasty way to get the defaults to display right in the console...
+ // TODO(opt): move PREINIT stuff to before cvar init and avoid this nonsense
+ if (GAMETYPE_MATCHES(L4D)) {
+ sst_inputhud_y->defaultval = "0.82";
+ con_setvarstr(sst_inputhud_y, "0.82");
+ }
+ else if (GAMETYPE_MATCHES(HL2series)) {
+ sst_inputhud_y->defaultval = "0.75";
+ con_setvarstr(sst_inputhud_y, "0.75");
+ }
+
+ return true;
+}
+
+END {
+ void **vtable = mem_loadptr(input);
+ unhook_vtable(vtable, vtidx_CreateMove, (void *)orig_CreateMove);
+ // N.B.: since the orig_ function is in a union, we don't have to worry
+ // about which version we're unhooking
+ unhook_vtable(vtable, vtidx_DecodeUserCmdFromBuffer,
+ (void *)orig_DecodeUserCmdFromBuffer);
+}
+
+// vi: sw=4 ts=4 noet tw=80 cc=80 fdm=marker
diff --git a/src/l4dwarp.c b/src/l4dwarp.c
index 50a5d46..f508460 100644
--- a/src/l4dwarp.c
+++ b/src/l4dwarp.c
@@ -112,6 +112,10 @@ DEF_CCMD_HERE_UNREG(sst_l4d_testwarp, "Simulate a bot warping to you "
return;
}
void *e = ed->ent_unknown;
+ if_cold (mem_loadu32(mem_offset(e, off_teamnum)) != 2) {
+ clientcon_msg(ed, "error: must be in the Survivor team");
+ return;
+ }
filter.pass_ent = e;
struct vec3f stuckpos = warptarget(e);
struct vec3f finalpos;
@@ -171,6 +175,10 @@ DEF_CCMD_HERE_UNREG(sst_l4d_previewwarp, "Visualise bot warp unstuck logic "
return;
}
void *e = ed->ent_unknown;
+ if_cold (mem_loadu32(mem_offset(e, off_teamnum)) != 2) {
+ clientcon_msg(ed, "error: must be in the Survivor team");
+ return;
+ }
filter.pass_ent = e;
struct vec3f stuckpos = warptarget(e);
struct vec3f finalpos;
@@ -182,13 +190,9 @@ DEF_CCMD_HERE_UNREG(sst_l4d_previewwarp, "Visualise bot warp unstuck logic "
struct vec3f maxs = *OBBMaxs(mem_offset(ed->ent_unknown, off_collision));
struct vec3f step = {maxs.x - mins.x, maxs.y - mins.y, maxs.z - mins.z};
struct failranges { struct { int neg, pos; } x, y, z; } ranges;
- AddBoxOverlay2(dbgoverlay, &stuckpos, &mins, &maxs, &zerovec,
- &red_face, &red_edge, 1000.0);
if (success) {
AddBoxOverlay2(dbgoverlay, &finalpos, &mins, &maxs, &zerovec,
&green_face, &green_edge, 1000.0);
- AddLineOverlay(dbgoverlay, &stuckpos, &finalpos,
- cyan_line.r, cyan_line.g, cyan_line.b, true, 1000.0);
if (finalpos.x != stuckpos.x) {
float iters = roundf((finalpos.x - stuckpos.x) / step.x);
int isneg = iters < 0;
@@ -223,12 +227,16 @@ DEF_CCMD_HERE_UNREG(sst_l4d_previewwarp, "Visualise bot warp unstuck logic "
// we were never actually stuck - no need to draw all the boxes
return;
}
+ AddLineOverlay(dbgoverlay, &stuckpos, &finalpos,
+ cyan_line.r, cyan_line.g, cyan_line.b, true, 1000.0);
}
else {
finalpos = stuckpos;
// searched the entire 15 iteration range, found nowhere to go
ranges = (struct failranges){{-15, 15}, {-15, 15}, {-15, 15}};
}
+ AddBoxOverlay2(dbgoverlay, &stuckpos, &mins, &maxs, &zerovec,
+ &red_face, &red_edge, 1000.0);
bool needline = true;
for (int i = ranges.x.neg; i <= ranges.x.pos; ++i) {
if (i == 0) { needline = true; continue; }
diff --git a/src/os.c b/src/os.c
index cf5c262..e6d32e8 100644
--- a/src/os.c
+++ b/src/os.c
@@ -166,7 +166,6 @@ struct link_map {
static struct link_map *lmbase = 0;
void *os_dlhandle(const char *name) {
- extern struct link_map *lmbase; // note: defined in sst.c for now
if_cold (!lmbase) { // IMPORTANT: not thread safe. don't forget later!
lmbase = (struct link_map *)dlopen("libc.so.6", RTLD_LAZY | RTLD_NOLOAD);
dlclose(lmbase); // assume success
diff --git a/src/rinput.c b/src/rinput.c
index 5464957..3baa67c 100644
--- a/src/rinput.c
+++ b/src/rinput.c
@@ -64,7 +64,7 @@ DEF_CVAR_UNREG(m_rawinput, "Use Raw Input for mouse input (SST reimplementation)
0, CON_ARCHIVE | CON_HIDDEN)
DEF_CVAR_MINMAX(sst_mouse_factor, "Number of hardware mouse counts per step",
- 1, 1, 20, /*CON_ARCHIVE |*/ CON_HIDDEN)
+ 1, 1, 100, /*CON_ARCHIVE |*/ CON_HIDDEN)
static ssize __stdcall inproc(void *wnd, uint msg, usize wp, ssize lp) {
switch (msg) {
diff --git a/src/sst.c b/src/sst.c
index ba0ba1f..5f09991 100644
--- a/src/sst.c
+++ b/src/sst.c
@@ -33,6 +33,7 @@
#include "gameinfo.h"
#include "gametype.h"
#include "hook.h"
+#include "intdefs.h"
#include "langext.h"
#include "os.h"
#include "sst.h"
diff --git a/src/x86.c b/src/x86.c
index e0431d6..5399af8 100644
--- a/src/x86.c
+++ b/src/x86.c
@@ -25,7 +25,6 @@ static int mrmsib(const uchar *p, int addrlen) {
// But it's confusingly-written enough that the code I wrote before didn't
// work, so with any luck nobody will need to refer to it again and this is
// actually correct now. Fingers crossed.
- if ((*p & 0xC6) == 0x06) return 3; // special case for disp16
if (addrlen == 4 || *p & 0xC0) {
int sib = addrlen == 4 && *p < 0xC0 && (*p & 7) == 4;
switch (*p & 0xC0) {
@@ -41,7 +40,7 @@ static int mrmsib(const uchar *p, int addrlen) {
case 0x80: return 1 + addrlen + sib;
}
}
- if (addrlen == 2 && *p == 0x26) return 3;
+ if (addrlen == 2 && (*p & 0xC7) == 0x06) return 3;
return 1; // note: include the mrm itself in the byte count
}
@@ -66,6 +65,7 @@ P: X86_SEG_PREFIXES(CASES)
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_IWI(CASES) return pfxlen + 1 + addrlen;
X86_OPS_1BYTE_I16(CASES) return pfxlen + 3;
X86_OPS_1BYTE_MRM(CASES) return pfxlen + 1 + mrmsib(insn + 1, addrlen);
X86_OPS_1BYTE_MRM_I8(CASES) operandlen = 1;
diff --git a/src/x86.h b/src/x86.h
index 52e4f9b..b4df9c8 100644
--- a/src/x86.h
+++ b/src/x86.h
@@ -25,6 +25,9 @@
*/
// XXX: no BOUND (0x62): ambiguous with EVEX prefix - can't be arsed!
+// XXX: no LES (0xC4) or DES (0xC5) either for similar reasons. better to report
+// an unknown instruction than to potentially misinterpret an AVX thing.
+// these are all legacy instructions that won't really be used much anyway.
/* Instruction prefixes: segments */
#define X86_SEG_PREFIXES(X) \
@@ -188,10 +191,6 @@
X(X86_XOREAXI, 0x35) \
X(X86_CMPEAXI, 0x3D) \
X(X86_PUSHIW, 0x68) \
- X(X86_MOVALII, 0xA0) /* From offset (indirect) */ \
- X(X86_MOVEAXII, 0xA1) /* From offset (indirect) */ \
- X(X86_MOVIIAL, 0xA2) /* To offset (indirect) */ \
- X(X86_MOVIIEAX, 0xA3) /* To offset (indirect) */ \
X(X86_TESTEAXI, 0xA9) \
X(X86_MOVEAXI, 0xB8) \
X(X86_MOVECXI, 0xB9) \
@@ -204,6 +203,13 @@
X(X86_CALL, 0xE8) \
X(X86_JMPIW, 0xE9)
+/* Single-byte opcodes with a word-sized immediate operand (indirect) */
+#define X86_OPS_1BYTE_IWI(X) \
+ X(X86_MOVALII, 0xA0) /* From offset (indirect) */ \
+ X(X86_MOVEAXII, 0xA1) /* From offset (indirect) */ \
+ X(X86_MOVIIAL, 0xA2) /* To offset (indirect) */ \
+ X(X86_MOVIIEAX, 0xA3) /* To offset (indirect) */ \
+
/* Single-byte opcodes with 16-bit immediate operands, regardless of prefixes */
#define X86_OPS_1BYTE_I16(X) \
X(X86_RETI16, 0xC2) \
@@ -259,8 +265,6 @@
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 */ \
@@ -297,6 +301,7 @@
X86_OPS_1BYTE_NO(X) \
X86_OPS_1BYTE_I8(X) \
X86_OPS_1BYTE_IW(X) \
+ X86_OPS_1BYTE_IWI(X) \
X86_OPS_1BYTE_I16(X) \
X86_OPS_1BYTE_MRM(X) \
X86_OPS_1BYTE_MRM_I8(X) \
diff --git a/test/x86.test.c b/test/x86.test.c
index c0c825a..bf6e6e8 100644
--- a/test/x86.test.c
+++ b/test/x86.test.c
@@ -37,9 +37,14 @@ TEST("mov AL, moff8 instructions should be decoded correctly") {
return true;
}
-TEST("fiadd [off16] instructions should be decoded correctly") {
+TEST("16-bit MRM instructions should be decoded correctly") {
const uchar fiadd_off16[] = HEXBYTES(67, DA, 06, DF, 11);
- return x86_len(fiadd_off16) == 5;
+ const uchar fld_tword[] = HEXBYTES(67, DB, 2E, 99, C4);
+ const uchar add_off16_bl[] = HEXBYTES(67, 00, 1E, F5, BB);
+ if (x86_len(fiadd_off16) != 5) return false;
+ if (x86_len(fld_tword) != 5) return false;
+ if (x86_len(add_off16_bl) != 5) return false;
+ return true;
}
// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/tools/x86test.c b/tools/x86test.c
new file mode 100644
index 0000000..18fc72f
--- /dev/null
+++ b/tools/x86test.c
@@ -0,0 +1,43 @@
+/* This file is dedicated to the public domain. */
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include "../src/udis86.h"
+#include "../src/udis86.c"
+#include "../src/intdefs.h"
+#include "../src/x86.h"
+#include "../src/x86.c"
+#include "../src/os.h"
+#include "../src/os.c"
+
+/*
+ * Quick hacked-up test program to more exhaustively test x86.c. This is not run
+ * as part of the build; it is just here for development and reference purposes.
+ */
+
+int main(void) {
+ uchar buf[15];
+ int bad = 0;
+ for (int i = 0; i < 100000000 && bad < 30; ++i) {
+ os_randombytes(buf, sizeof(buf));
+ struct ud u;
+ ud_init(&u);
+ ud_set_mode(&u, 32);
+ ud_set_input_buffer(&u, buf, sizeof(buf));
+ ud_set_syntax(&u, UD_SYN_INTEL);
+ int len = ud_disassemble(&u);
+ if (len && ud_insn_mnemonic(&u) != UD_Iinvalid) {
+ int mylen = x86_len(buf);
+ if (mylen != -1 && mylen != len) {
+ ++bad;
+ fprintf(stderr, "Uh oh! %s\nExp: %d\nGot: %d\nBytes:",
+ ud_insn_asm(&u), len, mylen);
+ for (int i = 0; i < len; ++i) fprintf(stderr, " %02X", buf[i]);
+ fputs("\n\n", stderr);
+ }
+ }
+ }
+ fprintf(stderr, "%d bad cases\n", bad);
+}
+