diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/autojump.c | 1 | ||||
-rw-r--r-- | src/engineapi.c | 13 | ||||
-rw-r--r-- | src/engineapi.h | 27 | ||||
-rw-r--r-- | src/gametype.h | 21 | ||||
-rw-r--r-- | src/hud.c | 12 | ||||
-rw-r--r-- | src/hud.h | 17 | ||||
-rw-r--r-- | src/inputhud.c | 443 | ||||
-rw-r--r-- | src/l4dwarp.c | 16 | ||||
-rw-r--r-- | src/os.c | 1 | ||||
-rw-r--r-- | src/rinput.c | 2 | ||||
-rw-r--r-- | src/sst.c | 1 | ||||
-rw-r--r-- | src/x86.c | 4 | ||||
-rw-r--r-- | src/x86.h | 17 |
13 files changed, 540 insertions, 35 deletions
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) @@ -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;) { @@ -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; } @@ -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) { @@ -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" @@ -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; @@ -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) \ |