summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMichael Smith <mikesmiffy128@gmail.com>2022-12-25 11:03:50 +0000
committerMichael Smith <mikesmiffy128@gmail.com>2022-12-25 11:03:50 +0000
commit9a09c605402e6ff74f93f7fe7afc50ccc785acc3 (patch)
tree0a098cecec7e0787023df9ad4c2f6d915817bdea
parentf6fb10a7d3bfb59a729ee4b7a9368632ab52077a (diff)
Add basic mouse input scaling
-rw-r--r--gamedata/inputsystem.kv8
-rw-r--r--src/engineapi.c4
-rw-r--r--src/engineapi.h1
-rw-r--r--src/nosleep.c10
-rw-r--r--src/rinput.c135
-rw-r--r--src/sst.c63
6 files changed, 153 insertions, 68 deletions
diff --git a/gamedata/inputsystem.kv b/gamedata/inputsystem.kv
index 1e5ab88..95fa51f 100644
--- a/gamedata/inputsystem.kv
+++ b/gamedata/inputsystem.kv
@@ -5,4 +5,12 @@ vtidx_SleepUntilInput {
Portal2 34 // IAppSystem changes
}
+// XXX: This function won't always exist even when has_ is true.
+// It's only used by rinput.c after checking that m_rawinput exists.
+vtidx_GetRawMouseAccumulators {
+ L4D2 37
+ 2013 39
+ Portal2 50
+}
+
// vi: sw=4 ts=4 noet tw=80 cc=80 ft=plain
diff --git a/src/engineapi.c b/src/engineapi.c
index 8cc3dd6..b2272f9 100644
--- a/src/engineapi.c
+++ b/src/engineapi.c
@@ -40,6 +40,8 @@ struct VEngineServer *engserver;
DECL_VFUNC(void *, GetGlobalVars, 1)
void *globalvars;
+void *inputsystem;
+
DECL_VFUNC_DYN(void *, GetAllServerClasses)
DECL_VFUNC(int, GetEngineBuildNumber_newl4d2, 99) // duping gamedata entry, yuck
@@ -71,6 +73,8 @@ bool engineapi_init(int pluginver) {
void *pim = factory_server("PlayerInfoManager002", 0);
if (pim) globalvars = GetGlobalVars(pim);
+ inputsystem = factory_inputsystem("InputSystemVersion001", 0);
+
void *srvdll;
// TODO(compat): add this back when there's gamedata for 009 (no point atm)
/*if (srvdll = factory_engine("ServerGameDLL009", 0)) {
diff --git a/src/engineapi.h b/src/engineapi.h
index 0f4fa8f..6394ec2 100644
--- a/src/engineapi.h
+++ b/src/engineapi.h
@@ -124,6 +124,7 @@ struct ServerClass {
extern struct VEngineClient *engclient;
extern struct VEngineServer *engserver;
extern void *globalvars;
+extern void *inputsystem;
/*
* Called on plugin init to attempt to initialise various core interfaces.
diff --git a/src/nosleep.c b/src/nosleep.c
index 07a5500..e75c6e6 100644
--- a/src/nosleep.c
+++ b/src/nosleep.c
@@ -20,12 +20,13 @@
#include "feature.h"
#include "gamedata.h"
#include "hook.h"
+#include "mem.h"
#include "os.h"
#include "vcall.h"
FEATURE("inactive window sleep adjustment")
REQUIRE_GAMEDATA(vtidx_SleepUntilInput)
-REQUIRE_GLOBAL(factory_inputsystem)
+REQUIRE_GLOBAL(inputsystem)
DEF_CVAR_UNREG(engine_no_focus_sleep,
"Delay while tabbed out (SST reimplementation)", 50,
@@ -46,12 +47,7 @@ PREINIT {
}
INIT {
- void *insys = factory_inputsystem("InputSystemVersion001", 0);
- if (!insys) {
- errmsg_errorx("couldn't get input system interface");
- return false;
- }
- vtable = *(void ***)insys;
+ vtable = mem_loadptr(inputsystem);
if (!os_mprot(vtable + vtidx_SleepUntilInput, sizeof(void *),
PAGE_READWRITE)) {
errmsg_errorx("couldn't make virtual table writable");
diff --git a/src/rinput.c b/src/rinput.c
index a7ad8d8..e24fc1c 100644
--- a/src/rinput.c
+++ b/src/rinput.c
@@ -16,16 +16,22 @@
// NOTE: compiled on Windows only. All Linux Source releases are new enough to
// have raw input already.
+// TODO(linux): actually, we DO want the scaling on Linux, so we need offsets
+// for GetRawMouseAccumulators, etc.
#include <Windows.h>
#include "con_.h"
+#include "gamedata.h"
#include "hook.h"
+#include "engineapi.h"
#include "errmsg.h"
#include "feature.h"
#include "intdefs.h"
+#include "mem.h"
+#include "vcall.h"
-FEATURE("raw mouse input")
+FEATURE("scalable raw mouse input")
// 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
@@ -33,16 +39,31 @@ FEATURE("raw mouse input")
// 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.
+//
+// As an *additional* feature, we also implement hardware input scaling, meaning
+// that some number of counts are required from the mouse in order to move the
+// cursor a single unit in game. This is useful for doing "minisnaps" (imprecise
+// but accurate mouse movements at high sensitivity) on mice which don't allow
+// their CPI to be lowered very far. It's implemented either in our own raw
+// input functionality, or by hooking the game's, as required.
#define USAGEPAGE_MOUSE 1
#define USAGE_MOUSE 2
-static long dx = 0, dy = 0;
-static void *inwin;
+static int cx, cy, rx = 0, ry = 0; // cursor xy, remainder xy
+static union { // cheeky space saving
+ void *inwin;
+ void **vtable_insys;
+} u1;
+#define inwin u1.inwin
+#define vtable_insys u1.vtable_insys
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)
+
static ssize __stdcall inproc(void *wnd, uint msg, ssize wp, ssize lp) {
switch (msg) {
case WM_INPUT:;
@@ -52,8 +73,10 @@ static ssize __stdcall inproc(void *wnd, uint msg, ssize wp, ssize lp) {
sizeof(RAWINPUTHEADER)) != -1) {
RAWINPUT *ri = (RAWINPUT *)buf;
if (ri->header.dwType == RIM_TYPEMOUSE) {
- dx += ri->data.mouse.lLastX;
- dy += ri->data.mouse.lLastY;
+ int d = con_getvari(sst_mouse_factor);
+ int dx = rx + ri->data.mouse.lLastX;
+ int dy = ry + ri->data.mouse.lLastY;
+ cx += dx / d; cy += dy / d; rx = dx % d; ry = dy % d;
}
}
return 0;
@@ -65,27 +88,59 @@ static ssize __stdcall inproc(void *wnd, uint msg, ssize wp, ssize lp) {
}
typedef int (*__stdcall GetCursorPos_func)(POINT *p);
-static GetCursorPos_func orig_GetCursorPos;
+typedef uint (*VCALLCONV GetRawMouseAccumulators_func)(void *, int *, int *);
+static union { // more cheeky space saving
+ GetCursorPos_func orig_GetCursorPos;
+ GetRawMouseAccumulators_func orig_GetRawMouseAccumulators;
+} u2;
+#define orig_GetCursorPos u2.orig_GetCursorPos
+#define orig_GetRawMouseAccumulators u2.orig_GetRawMouseAccumulators
+
static int __stdcall hook_GetCursorPos(POINT *p) {
if (!con_getvari(m_rawinput)) return orig_GetCursorPos(p);
- p->x = dx; p->y = dy;
+ p->x = cx; p->y = cy;
return 1;
}
+
typedef int (*__stdcall SetCursorPos_func)(int x, int y);
-static SetCursorPos_func orig_SetCursorPos;
+static SetCursorPos_func orig_SetCursorPos = 0;
static int __stdcall hook_SetCursorPos(int x, int y) {
- dx = x; dy = y;
+ cx = x; cy = y;
return orig_SetCursorPos(x, y);
}
-PREINIT {
- 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);
- return true;
+static uint VCALLCONV hook_GetRawMouseAccumulators(void *this, int *x, int *y) {
+ int dx, dy;
+ uint ret = orig_GetRawMouseAccumulators(this, &dx, &dy);
+ int d = con_getvari(sst_mouse_factor);
+ dx += rx; dy += ry;
+ *x = dx / d; *y = dy / d; rx = dx % d; ry = dy % d;
+ // NOTE! This is usually void, but apparently returns a bool in the 2013
+ // SDK, for reasons I didn't bother researching. In any case, we can just
+ // unconditionally preserve EAX and it won't do any harm.
+ return ret;
}
INIT {
+ bool has_rawinput = !!con_findvar("m_rawinput");
+ if (has_rawinput) {
+ if (!has_vtidx_GetRawMouseAccumulators) return false;
+ if (!inputsystem) return false;
+ vtable_insys = mem_loadptr(inputsystem);
+ // XXX: this is kind of duping nosleep, but that won't always init...
+ if (!os_mprot(vtable_insys + vtidx_GetRawMouseAccumulators,
+ sizeof(void *), PAGE_READWRITE)) {
+ errmsg_errorx("couldn't make virtual table writable");
+ return false;
+ }
+ orig_GetRawMouseAccumulators = (GetRawMouseAccumulators_func)hook_vtable(
+ vtable_insys, vtidx_GetRawMouseAccumulators,
+ (void *)&hook_GetRawMouseAccumulators);
+ }
+ else {
+ // create cvar hidden so config is still preserved if we fail to init
+ con_reg(m_rawinput);
+ }
WNDCLASSEXW wc = {
.cbSize = sizeof(wc),
// cast because inproc is binary-compatible but doesn't use stupid
@@ -102,10 +157,26 @@ INIT {
"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");
+ if (has_rawinput) {
+ con_colourmsg(&white, "This is built into this version of game, and"
+ " will also get provided by SST in older versions. ");
+ }
+ else {
+ con_colourmsg(&white, "This option carries over to newer game "
+ "versions that have it built-in. ");
+ }
+ con_colourmsg(&white, "No need for external programs :)\n");
+ con_colourmsg(&gold, "Additionally");
+ con_colourmsg(&blue, ", you can scale down the sensor input with ");
+ con_colourmsg(&gold, "sst_mouse_factor");
+ con_colourmsg(&blue, "!\n");
return false;
}
+ if (has_rawinput) {
+ // no real reason to keep this around receiving useless window messages
+ UnregisterClassW(L"RInput", 0);
+ goto ok;
+ }
orig_GetCursorPos = (GetCursorPos_func)hook_inline((void *)&GetCursorPos,
(void *)&hook_GetCursorPos);
@@ -134,7 +205,8 @@ INIT {
goto e3;
}
- m_rawinput->base.flags &= ~CON_HIDDEN;
+ok: m_rawinput->base.flags &= ~CON_HIDDEN;
+ sst_mouse_factor->base.flags &= ~CON_HIDDEN;
return true;
e3: DestroyWindow(inwin);
@@ -145,18 +217,23 @@ e0: UnregisterClassW(L"RInput", 0);
}
END {
- 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);
+ if (orig_SetCursorPos) { // if null, we didn't init our own implementation
+ 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);
+ }
+ else {
+ unhook_vtable(vtable_insys, vtidx_GetRawMouseAccumulators,
+ (void *)orig_GetRawMouseAccumulators);
+ }
}
// vi: sw=4 ts=4 noet tw=80 cc=80
diff --git a/src/sst.c b/src/sst.c
index a7eb331..d159c8e 100644
--- a/src/sst.c
+++ b/src/sst.c
@@ -266,22 +266,23 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) {
}
factory_engine = enginef; factory_server = serverf;
+#ifdef _WIN32
+ void *inputsystemlib = GetModuleHandleW(L"inputsystem.dll");
+#else
+ // TODO(linux): assuming the above doesn't apply to this; check if it does!
+ // ... actually, there's a good chance this assumption is now wrong!
+ void *inputsystemlib = dlopen("bin/libinputsystem.so",
+ RTLD_NOW | RLTD_NOLOAD);
+ if (inputsystemlib) dlclose(inputsystemlib); // blegh
+#endif
+ if (!inputsystemlib) {
+ errmsg_warndl("couldn't get the input system library");
+ }
+ else if (!(factory_inputsystem = (ifacefactory)os_dlsym(inputsystemlib,
+ "CreateInterface"))) {
+ errmsg_warndl("couldn't get input system's CreateInterface");
+ }
if (!engineapi_init(ifacever)) return false;
-
- const void **p = vtable_firstdiff;
- if (GAMETYPE_MATCHES(Portal2)) *p++ = (void *)&nop_p_v; // ClientFullyConnect
- *p++ = (void *)&nop_p_v; // ClientDisconnect
- *p++ = (void *)&nop_pp_v; // ClientPutInServer
- *p++ = (void *)&SetCommandClient; // SetCommandClient
- *p++ = (void *)&nop_p_v; // ClientSettingsChanged
- *p++ = (void *)&nop_5pi_i; // ClientConnect
- *p++ = ifacever > 1 ? (void *)&nop_pp_i : (void *)&nop_p_i; // ClientCommand
- // remaining stuff here is backwards compatible, so added unconditionally
- *p++ = (void *)&nop_pp_i; // NetworkIDValidated
- *p++ = (void *)&nop_ipipp_v; // OnQueryCvarValueFinished (002+)
- *p++ = (void *)&nop_p_v; // OnEdictAllocated
- *p = (void *)&nop_p_v; // OnEdictFreed
-
#ifdef _WIN32
clientlib = GetModuleHandleW(gameinfo_clientlib);
#else
@@ -298,22 +299,20 @@ static bool do_load(ifacefactory enginef, ifacefactory serverf) {
"CreateInterface"))) {
errmsg_warndl("couldn't get client's CreateInterface");
}
-#ifdef _WIN32
- void *inputsystemlib = GetModuleHandleW(L"inputsystem.dll");
-#else
- // TODO(linux): assuming the above doesn't apply to this; check if it does!
- // ... actually, there's a good chance this assumption is now wrong!
- void *inputsystemlib = dlopen("bin/libinputsystem.so",
- RTLD_NOW | RLTD_NOLOAD);
- if (inputsystemlib) dlclose(inputsystemlib); // blegh
-#endif
- if (!inputsystemlib) {
- errmsg_warndl("couldn't get the input system library");
- }
- else if (!(factory_inputsystem = (ifacefactory)os_dlsym(inputsystemlib,
- "CreateInterface"))) {
- errmsg_warndl("couldn't get input system's CreateInterface");
- }
+
+ const void **p = vtable_firstdiff;
+ if (GAMETYPE_MATCHES(Portal2)) *p++ = (void *)&nop_p_v; // ClientFullyConnect
+ *p++ = (void *)&nop_p_v; // ClientDisconnect
+ *p++ = (void *)&nop_pp_v; // ClientPutInServer
+ *p++ = (void *)&SetCommandClient; // SetCommandClient
+ *p++ = (void *)&nop_p_v; // ClientSettingsChanged
+ *p++ = (void *)&nop_5pi_i; // ClientConnect
+ *p++ = ifacever > 1 ? (void *)&nop_pp_i : (void *)&nop_p_i; // ClientCommand
+ // remaining stuff here is backwards compatible, so added unconditionally
+ *p++ = (void *)&nop_pp_i; // NetworkIDValidated
+ *p++ = (void *)&nop_ipipp_v; // OnQueryCvarValueFinished (002+)
+ *p++ = (void *)&nop_p_v; // OnEdictAllocated
+ *p = (void *)&nop_p_v; // OnEdictFreed
// NOTE: this is technically redundant for early versions but I CBA writing
// a version check; it's easier to just do this unilaterally.
@@ -467,7 +466,7 @@ static const void *vtable[MAX_VTABLE_FUNCS] = {
(void *)&GetPluginDescription,
(void *)&nop_p_v, // LevelInit
(void *)&nop_pii_v, // ServerActivate
- (void *)&GameFrame, // GameFrame
+ (void *)&GameFrame,
(void *)&nop_v_v, // LevelShutdown
(void *)&ClientActive
// At this point, Alien Swarm and Portal 2 add ClientFullyConnect, so we