From 042acdc08f29920d4d46dde7c8a663b20a743f90 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Mon, 21 Mar 2022 04:50:32 +0000 Subject: Add m_rawinput reimplementation to replace RInput It's archive so you can set m_rawinput 1, load SST via VDF and then never think about it again. --- compile.bat | 5 +- src/rinput.c | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/rinput.h | 13 +++++ src/sst.c | 12 +++- 4 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 src/rinput.c create mode 100644 src/rinput.h diff --git a/compile.bat b/compile.bat index 3db7d1a..b2a880a 100644 --- a/compile.bat +++ b/compile.bat @@ -25,7 +25,7 @@ clang -municode -O2 -fuse-ld=lld %warnings% -D_CRT_SECURE_NO_WARNINGS -ladvapi32 clang -municode -O2 -fuse-ld=lld %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/demorec.c src/dbg.c src/fixes.c ^ -src/gamedata.c src/gameinfo.c src/hook.c src/kv.c src/sst.c src/udis86.c || exit /b +src/gamedata.c src/gameinfo.c src/hook.c src/kv.c src/rinput.c src/sst.c src/udis86.c || exit /b .build\mkgamedata.exe gamedata/engine.kv gamedata/gamelib.kv || exit /b :: llvm-rc doesn't preprocess, looks like it might later: :: https://reviews.llvm.org/D100755?id=339141 @@ -46,10 +46,11 @@ call :cc src/gamedata.c || exit /b call :cc src/gameinfo.c || exit /b 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 clang -m32 -shared -O2 -flto -fuse-ld=lld -Wl,/implib:.build/sst.lib,/Brepro ^ --L.build -ladvapi32 -lshlwapi -ltier0 -lvstdlib -o sst.dll%objs% .build/dll.res || exit /b +-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???) del .build\sst.lib diff --git a/src/rinput.c b/src/rinput.c new file mode 100644 index 0000000..1929aee --- /dev/null +++ b/src/rinput.c @@ -0,0 +1,181 @@ +/* + * Copyright © 2022 Michael Smith + * + * 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. + */ + +// NOTE: compiled on Windows only. All Linux Source releases are new enough to +// have raw input already. + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include + +#include "con_.h" +#include "hook.h" +#include "intdefs.h" + +// We reimplement m_rawinput by hooking cursor functions in the same way as +// RInput (it's way easier than replacing all the mouse-handling internals of +// the actual engine). We also take the same window class it does in order to +// either block it from being loaded redundantly, or be blocked if it's already +// loaded. If m_rawinput already exists, we do nothing; people should use the +// game's native raw input instead in that case. + +#define ERR "sst: rinput: error: " + +#define USAGEPAGE_MOUSE 1 +#define USAGE_MOUSE 2 + +static volatile long dx = 0, dy = 0; +static void *inwin; + +DEF_CVAR_UNREG(m_rawinput, "Use Raw Input for mouse input (SST reimplementation)", + 0, CON_ARCHIVE | CON_HIDDEN) + +static ssize __stdcall inproc(void *wnd, uint msg, ssize wp, ssize lp) { + switch (msg) { + case WM_INPUT:; + char buf[sizeof(RAWINPUTHEADER) + sizeof(RAWMOUSE) /* = 40 */]; + uint sz = sizeof(buf); + if (GetRawInputData((void *)lp, RID_INPUT, buf, &sz, + sizeof(RAWINPUTHEADER)) != -1) { + RAWINPUT *ri = (RAWINPUT *)buf; + if (ri->header.dwType == RIM_TYPEMOUSE) { + // NOTE: I can't tell if RInput has been really slightly + // wrong for years or if there's actually a really subtle + // reason why an atomic/memory-fenced add is unnecessary for + // synchronisation but I'd rather err on the side of being + // really pedantic and careful for totally accurate mouse + // input, at *presumably* no noticeable performance cost. + InterlockedAdd(&dx, ri->data.mouse.lLastX); + InterlockedAdd(&dy, ri->data.mouse.lLastY); + } + } + return 0; + case WM_DESTROY: + PostQuitMessage(0); + return 0; + } + return DefWindowProc(wnd, msg, wp, lp); +} + +static ulong __stdcall threadmain(void *unused) { + MSG m; + // XXX: ignoring errors, in theory could spin? in practice rinput does this + // too and it's probably fine lol + while (GetMessageW(&m, inwin, 0, 0)) DispatchMessage(&m); + return 0; +} + +typedef int (*__stdcall GetCursorPos_func)(POINT *p); +static GetCursorPos_func orig_GetCursorPos; +static int __stdcall hook_GetCursorPos(POINT *p) { + if (!con_getvari(m_rawinput)) return orig_GetCursorPos(p); + p->x = dx; p->y = dy; + return 0; +} +typedef int (*__stdcall SetCursorPos_func)(int x, int y); +static SetCursorPos_func orig_SetCursorPos; +static int __stdcall hook_SetCursorPos(int x, int y) { + dx = x; dy = y; + return orig_SetCursorPos(x, y); +} + +bool rinput_init(void) { + if (con_findvar("m_rawinput")) return false; // no need! + // create cvar hidden so if we fail to init, setting can still be preserved + con_reg(m_rawinput); + + WNDCLASSEXW wc = { + .cbSize = sizeof(wc), + // cast because inproc is binary-compatible but doesn't use stupid + // microsoft typedefs + .lpfnWndProc = (WNDPROC)&inproc, + .lpszClassName = L"RInput" + }; + if (!RegisterClassExW(&wc)) { + struct con_colour gold = {255, 210, 0, 255}; + struct con_colour blue = {45, 190, 190, 255}; + struct con_colour white = {200, 200, 200, 255}; + con_colourmsg(&gold, "SST PROTIP! "); + con_colourmsg(&blue, "It appears you're using RInput.exe.\n" + "Consider launching without that and using "); + con_colourmsg(&gold, "m_rawinput 1"); + con_colourmsg(&blue, " instead!\n"); + con_colourmsg(&white, "This option carries over to newer game versions " + "that have it built-in. No need for external programs :)\n"); + return false; + } + + orig_GetCursorPos = (GetCursorPos_func)hook_inline((void *)&GetCursorPos, + (void *)&hook_GetCursorPos); + if (!orig_GetCursorPos) { + con_warn(ERR "couldn't hook GetCursorPos\n"); + goto e0; + } + orig_SetCursorPos = (SetCursorPos_func)hook_inline((void *)&SetCursorPos, + (void *)&hook_SetCursorPos); + if (!orig_SetCursorPos) { + con_warn(ERR "couldn't hook SetCursorPos\n"); + goto e1; + } + + inwin = CreateWindowExW(0, L"RInput", L"RInput", 0, 0, 0, 0, 0, 0, 0, 0, 0); + if (!inwin) { + con_warn(ERR " couldn't create input window\n"); + goto e2; + } + RAWINPUTDEVICE rd = { + .hwndTarget = inwin, + .usUsagePage = USAGEPAGE_MOUSE, + .usUsage = USAGE_MOUSE + }; + if (!RegisterRawInputDevices(&rd, 1, sizeof(rd))) { + con_warn(ERR " couldn't create raw mouse device\n"); + goto e3; + } + if (!CreateThread(0, 8192, &threadmain, 0, 0, 0)) { + con_warn(ERR " couldn't create thread\n"); + goto e4; + } + + m_rawinput->base.flags &= ~CON_HIDDEN; + return true; + +e4: rd.dwFlags |= RIDEV_REMOVE; rd.hwndTarget = 0; + RegisterRawInputDevices(&rd, 1, sizeof(rd)); +e3: DestroyWindow(inwin); +e2: unhook_inline((void *)orig_SetCursorPos); +e1: unhook_inline((void *)orig_GetCursorPos); +e0: UnregisterClassW(L"RInput", 0); + return false; +} + +void rinput_end(void) { + RAWINPUTDEVICE rd = { + .dwFlags = RIDEV_REMOVE, + .hwndTarget = 0, + .usUsagePage = USAGEPAGE_MOUSE, + .usUsage = USAGE_MOUSE + }; + RegisterRawInputDevices(&rd, 1, sizeof(rd)); + DestroyWindow(inwin); + UnregisterClassW(L"RInput", 0); + + unhook_inline((void *)orig_GetCursorPos); + unhook_inline((void *)orig_SetCursorPos); +} + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/rinput.h b/src/rinput.h new file mode 100644 index 0000000..4048a2c --- /dev/null +++ b/src/rinput.h @@ -0,0 +1,13 @@ +#ifndef INC_RINPUT_H +#define INC_RINPUT_H +#ifdef _WIN32 + +#include + +bool rinput_init(void); +void rinput_end(void); + +#endif +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 diff --git a/src/sst.c b/src/sst.c index a7965b5..5c1568d 100644 --- a/src/sst.c +++ b/src/sst.c @@ -27,6 +27,7 @@ #include "gametype.h" #include "hook.h" #include "os.h" +#include "rinput.h" #include "vcall.h" #include "version.h" @@ -65,10 +66,13 @@ ifacefactory factory_client = 0, factory_server = 0, factory_engine = 0; // TODO(featgen): I wanted some nice fancy automatic feature system that // figures out the dependencies at build time and generates all the init glue // but we want to actually release the plugin this decade so for now I'm just -// plonking ~~some bools~~ one bool here and worrying about it later. :^) +// plonking some bools here and worrying about it later. :^) static bool has_autojump = false; static bool has_demorec = false; static bool has_demorec_custom = false; +#ifdef _WIN32 +static bool has_rinput = false; +#endif // HACK: later versions of L4D2 show an annoying dialog on every plugin_load. // We can suppress this by catching the message string that's passed from @@ -130,6 +134,9 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) { nc: gamedata_init(); has_autojump = autojump_init(); has_demorec = demorec_init(); +#ifdef _WIN32 + has_rinput = rinput_init(); +#endif if (has_demorec) has_demorec_custom = demorec_custom_init(); fixes_apply(); @@ -157,6 +164,9 @@ e: con_colourmsg(RGBA(64, 255, 64, 255), static void do_unload(void) { if (has_autojump) autojump_end(); if (has_demorec) demorec_end(); +#ifdef _WIN32 + if (has_rinput) rinput_end(); +#endif #ifdef __linux__ //if (serverlib) dlclose(serverlib); -- cgit v1.2.3