diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/rinput.c | 181 | ||||
| -rw-r--r-- | src/rinput.h | 13 | ||||
| -rw-r--r-- | src/sst.c | 12 | 
3 files changed, 205 insertions, 1 deletions
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 <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. + */ + +// 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 <stdbool.h> +#include <Windows.h> + +#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 <stdbool.h> + +bool rinput_init(void); +void rinput_end(void); + +#endif +#endif + +// vi: sw=4 ts=4 noet tw=80 cc=80 @@ -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);  | 
