summaryrefslogtreecommitdiffhomepage
path: root/src/rinput.c
blob: 1929aee4bc3a68fe8e28897c57145f6afafc86ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
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