1 /*************************************************************************/
2 /*  joystick.cpp                                                         */
3 /*************************************************************************/
4 /*                       This file is part of:                           */
5 /*                           GODOT ENGINE                                */
6 /*                      https://godotengine.org                          */
7 /*************************************************************************/
8 /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
9 /* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */
10 /*                                                                       */
11 /* Permission is hereby granted, free of charge, to any person obtaining */
12 /* a copy of this software and associated documentation files (the       */
13 /* "Software"), to deal in the Software without restriction, including   */
14 /* without limitation the rights to use, copy, modify, merge, publish,   */
15 /* distribute, sublicense, and/or sell copies of the Software, and to    */
16 /* permit persons to whom the Software is furnished to do so, subject to */
17 /* the following conditions:                                             */
18 /*                                                                       */
19 /* The above copyright notice and this permission notice shall be        */
20 /* included in all copies or substantial portions of the Software.       */
21 /*                                                                       */
22 /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
23 /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
24 /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
25 /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
26 /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
27 /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
28 /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
29 /*************************************************************************/
30 //author: Andreas Haas <hondres,  liugam3@gmail.com>
31 #include "joystick.h"
32 #include <oleauto.h>
33 #include <wbemidl.h>
34 #include <iostream>
35 
36 #ifndef __GNUC__
37 #define __builtin_bswap32 _byteswap_ulong
38 #endif
39 
_xinput_get_state(DWORD dwUserIndex,XINPUT_STATE * pState)40 DWORD WINAPI _xinput_get_state(DWORD dwUserIndex, XINPUT_STATE *pState) {
41 	return ERROR_DEVICE_NOT_CONNECTED;
42 }
_xinput_set_state(DWORD dwUserIndex,XINPUT_VIBRATION * pVibration)43 DWORD WINAPI _xinput_set_state(DWORD dwUserIndex, XINPUT_VIBRATION *pVibration) {
44 	return ERROR_DEVICE_NOT_CONNECTED;
45 }
46 
joystick_windows()47 joystick_windows::joystick_windows() {
48 }
49 
joystick_windows(InputDefault * _input,HWND * hwnd)50 joystick_windows::joystick_windows(InputDefault *_input, HWND *hwnd) {
51 
52 	input = _input;
53 	hWnd = hwnd;
54 	joystick_count = 0;
55 	dinput = NULL;
56 	xinput_dll = NULL;
57 	xinput_get_state = NULL;
58 	xinput_set_state = NULL;
59 
60 	load_xinput();
61 
62 	for (int i = 0; i < JOYSTICKS_MAX; i++)
63 		attached_joysticks[i] = false;
64 
65 	HRESULT result;
66 	result = DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void **)&dinput, NULL);
67 	if (FAILED(result)) {
68 		printf("failed init DINPUT: %ld\n", result);
69 	}
70 	probe_joysticks();
71 }
72 
~joystick_windows()73 joystick_windows::~joystick_windows() {
74 
75 	close_joystick();
76 	dinput->Release();
77 	unload_xinput();
78 }
79 
have_device(const GUID & p_guid)80 bool joystick_windows::have_device(const GUID &p_guid) {
81 
82 	for (int i = 0; i < JOYSTICKS_MAX; i++) {
83 
84 		if (d_joysticks[i].guid == p_guid) {
85 
86 			d_joysticks[i].confirmed = true;
87 			return true;
88 		}
89 	}
90 	return false;
91 }
92 
93 // adapted from SDL2, works a lot better than the MSDN version
is_xinput_device(const GUID * p_guid)94 bool joystick_windows::is_xinput_device(const GUID *p_guid) {
95 
96 	static GUID IID_ValveStreamingGamepad = { MAKELONG(0x28DE, 0x11FF), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
97 	static GUID IID_X360WiredGamepad = { MAKELONG(0x045E, 0x02A1), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
98 	static GUID IID_X360WirelessGamepad = { MAKELONG(0x045E, 0x028E), 0x0000, 0x0000, { 0x00, 0x00, 0x50, 0x49, 0x44, 0x56, 0x49, 0x44 } };
99 
100 	if (p_guid == &IID_ValveStreamingGamepad || p_guid == &IID_X360WiredGamepad || p_guid == &IID_X360WirelessGamepad)
101 		return true;
102 
103 	PRAWINPUTDEVICELIST dev_list = NULL;
104 	unsigned int dev_list_count = 0;
105 
106 	if (GetRawInputDeviceList(NULL, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == -1) {
107 		return false;
108 	}
109 	dev_list = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * dev_list_count);
110 	if (!dev_list) return false;
111 
112 	if (GetRawInputDeviceList(dev_list, &dev_list_count, sizeof(RAWINPUTDEVICELIST)) == -1) {
113 		free(dev_list);
114 		return false;
115 	}
116 	for (int i = 0; i < dev_list_count; i++) {
117 
118 		RID_DEVICE_INFO rdi;
119 		char dev_name[128];
120 		UINT rdiSize = sizeof(rdi);
121 		UINT nameSize = sizeof(dev_name);
122 
123 		rdi.cbSize = rdiSize;
124 		if ((dev_list[i].dwType == RIM_TYPEHID) &&
125 				(GetRawInputDeviceInfoA(dev_list[i].hDevice, RIDI_DEVICEINFO, &rdi, &rdiSize) != (UINT)-1) &&
126 				(MAKELONG(rdi.hid.dwVendorId, rdi.hid.dwProductId) == (LONG)p_guid->Data1) &&
127 				(GetRawInputDeviceInfoA(dev_list[i].hDevice, RIDI_DEVICENAME, &dev_name, &nameSize) != (UINT)-1) &&
128 				(strstr(dev_name, "IG_") != NULL)) {
129 
130 			free(dev_list);
131 			return true;
132 		}
133 	}
134 	free(dev_list);
135 	return false;
136 }
137 
setup_dinput_joystick(const DIDEVICEINSTANCE * instance)138 bool joystick_windows::setup_dinput_joystick(const DIDEVICEINSTANCE *instance) {
139 
140 	HRESULT hr;
141 	int num = input->get_unused_joy_id();
142 
143 	if (have_device(instance->guidInstance) || num == -1)
144 		return false;
145 
146 	d_joysticks[joystick_count] = dinput_gamepad();
147 	dinput_gamepad *joy = &d_joysticks[joystick_count];
148 
149 	const DWORD devtype = (instance->dwDevType & 0xFF);
150 
151 	if ((devtype != DI8DEVTYPE_JOYSTICK) && (devtype != DI8DEVTYPE_GAMEPAD) && (devtype != DI8DEVTYPE_1STPERSON)) {
152 		//printf("ignore device %s, type %x\n", instance->tszProductName, devtype);
153 		return false;
154 	}
155 
156 	hr = dinput->CreateDevice(instance->guidInstance, &joy->di_joy, NULL);
157 
158 	if (FAILED(hr)) {
159 
160 		//std::wcout << "failed to create device: " << instance->tszProductName << std::endl;
161 		return false;
162 	}
163 
164 	const GUID &guid = instance->guidProduct;
165 	char uid[128];
166 	sprintf(uid, "%08lx%04hx%04hx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx",
167 			__builtin_bswap32(guid.Data1), guid.Data2, guid.Data3,
168 			guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
169 			guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
170 
171 	id_to_change = joystick_count;
172 
173 	joy->di_joy->SetDataFormat(&c_dfDIJoystick2);
174 	joy->di_joy->SetCooperativeLevel(*hWnd, DISCL_FOREGROUND);
175 	joy->di_joy->EnumObjects(objectsCallback, this, NULL);
176 	joy->joy_axis.sort();
177 
178 	joy->guid = instance->guidInstance;
179 	input->joy_connection_changed(num, true, instance->tszProductName, uid);
180 	joy->attached = true;
181 	joy->id = num;
182 	attached_joysticks[num] = true;
183 	joy->confirmed = true;
184 	joystick_count++;
185 	return true;
186 }
187 
setup_joystick_object(const DIDEVICEOBJECTINSTANCE * ob,int p_joy_id)188 void joystick_windows::setup_joystick_object(const DIDEVICEOBJECTINSTANCE *ob, int p_joy_id) {
189 
190 	if (ob->dwType & DIDFT_AXIS) {
191 
192 		HRESULT res;
193 		DIPROPRANGE prop_range;
194 		DIPROPDWORD dilong;
195 		DWORD ofs;
196 		if (ob->guidType == GUID_XAxis)
197 			ofs = DIJOFS_X;
198 		else if (ob->guidType == GUID_YAxis)
199 			ofs = DIJOFS_Y;
200 		else if (ob->guidType == GUID_ZAxis)
201 			ofs = DIJOFS_Z;
202 		else if (ob->guidType == GUID_RxAxis)
203 			ofs = DIJOFS_RX;
204 		else if (ob->guidType == GUID_RyAxis)
205 			ofs = DIJOFS_RY;
206 		else if (ob->guidType == GUID_RzAxis)
207 			ofs = DIJOFS_RZ;
208 		else if (ob->guidType == GUID_Slider)
209 			ofs = DIJOFS_SLIDER(0);
210 		else
211 			return;
212 		prop_range.diph.dwSize = sizeof(DIPROPRANGE);
213 		prop_range.diph.dwHeaderSize = sizeof(DIPROPHEADER);
214 		prop_range.diph.dwObj = ob->dwType;
215 		prop_range.diph.dwHow = DIPH_BYID;
216 		prop_range.lMin = -MAX_JOY_AXIS;
217 		prop_range.lMax = +MAX_JOY_AXIS;
218 
219 		dinput_gamepad &joy = d_joysticks[p_joy_id];
220 
221 		res = IDirectInputDevice8_SetProperty(joy.di_joy, DIPROP_RANGE, &prop_range.diph);
222 		if (FAILED(res))
223 			return;
224 
225 		dilong.diph.dwSize = sizeof(dilong);
226 		dilong.diph.dwHeaderSize = sizeof(dilong.diph);
227 		dilong.diph.dwObj = ob->dwType;
228 		dilong.diph.dwHow = DIPH_BYID;
229 		dilong.dwData = 0;
230 
231 		res = IDirectInputDevice8_SetProperty(joy.di_joy, DIPROP_DEADZONE, &dilong.diph);
232 		if (FAILED(res))
233 			return;
234 
235 		joy.joy_axis.push_back(ofs);
236 	}
237 }
238 
enumCallback(const DIDEVICEINSTANCE * instance,void * pContext)239 BOOL CALLBACK joystick_windows::enumCallback(const DIDEVICEINSTANCE *instance, void *pContext) {
240 
241 	joystick_windows *self = (joystick_windows *)pContext;
242 	if (self->is_xinput_device(&instance->guidProduct)) {
243 		return DIENUM_CONTINUE;
244 	}
245 	self->setup_dinput_joystick(instance);
246 	return DIENUM_CONTINUE;
247 }
248 
objectsCallback(const DIDEVICEOBJECTINSTANCE * instance,void * context)249 BOOL CALLBACK joystick_windows::objectsCallback(const DIDEVICEOBJECTINSTANCE *instance, void *context) {
250 
251 	joystick_windows *self = (joystick_windows *)context;
252 	self->setup_joystick_object(instance, self->id_to_change);
253 
254 	return DIENUM_CONTINUE;
255 }
256 
close_joystick(int id)257 void joystick_windows::close_joystick(int id) {
258 
259 	if (id == -1) {
260 
261 		for (int i = 0; i < JOYSTICKS_MAX; i++) {
262 
263 			close_joystick(i);
264 		}
265 		return;
266 	}
267 
268 	if (!d_joysticks[id].attached) return;
269 
270 	d_joysticks[id].di_joy->Unacquire();
271 	d_joysticks[id].di_joy->Release();
272 	d_joysticks[id].attached = false;
273 	attached_joysticks[d_joysticks[id].id] = false;
274 	d_joysticks[id].guid.Data1 = d_joysticks[id].guid.Data2 = d_joysticks[id].guid.Data3 = 0;
275 	input->joy_connection_changed(d_joysticks[id].id, false, "");
276 	joystick_count--;
277 }
278 
probe_joysticks()279 void joystick_windows::probe_joysticks() {
280 
281 	DWORD dwResult;
282 	for (DWORD i = 0; i < XUSER_MAX_COUNT; i++) {
283 
284 		ZeroMemory(&x_joysticks[i].state, sizeof(XINPUT_STATE));
285 
286 		dwResult = xinput_get_state(i, &x_joysticks[i].state);
287 		if (dwResult == ERROR_SUCCESS) {
288 
289 			int id = input->get_unused_joy_id();
290 			if (id != -1 && !x_joysticks[i].attached) {
291 
292 				x_joysticks[i].attached = true;
293 				x_joysticks[i].id = id;
294 				x_joysticks[i].ff_timestamp = 0;
295 				x_joysticks[i].ff_end_timestamp = 0;
296 				x_joysticks[i].vibrating = false;
297 				attached_joysticks[id] = true;
298 				input->joy_connection_changed(id, true, "XInput Gamepad", "__XINPUT_DEVICE__");
299 			}
300 		} else if (x_joysticks[i].attached) {
301 
302 			x_joysticks[i].attached = false;
303 			attached_joysticks[x_joysticks[i].id] = false;
304 			input->joy_connection_changed(x_joysticks[i].id, false, "");
305 		}
306 	}
307 
308 	for (int i = 0; i < joystick_count; i++) {
309 
310 		d_joysticks[i].confirmed = false;
311 	}
312 
313 	dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, enumCallback, this, DIEDFL_ATTACHEDONLY);
314 
315 	for (int i = 0; i < joystick_count; i++) {
316 
317 		if (!d_joysticks[i].confirmed) {
318 
319 			close_joystick(i);
320 		}
321 	}
322 }
323 
process_joysticks(unsigned int p_last_id)324 unsigned int joystick_windows::process_joysticks(unsigned int p_last_id) {
325 
326 	HRESULT hr;
327 
328 	for (int i = 0; i < XUSER_MAX_COUNT; i++) {
329 
330 		xinput_gamepad &joy = x_joysticks[i];
331 		if (!joy.attached) {
332 			continue;
333 		}
334 		ZeroMemory(&joy.state, sizeof(XINPUT_STATE));
335 
336 		xinput_get_state(i, &joy.state);
337 		if (joy.state.dwPacketNumber != joy.last_packet) {
338 
339 			int button_mask = XINPUT_GAMEPAD_DPAD_UP;
340 			for (int i = 0; i <= 16; i++) {
341 
342 				p_last_id = input->joy_button(p_last_id, joy.id, i, joy.state.Gamepad.wButtons & button_mask);
343 				button_mask = button_mask * 2;
344 			}
345 
346 			p_last_id = input->joy_axis(p_last_id, joy.id, JOY_AXIS_0, axis_correct(joy.state.Gamepad.sThumbLX, true));
347 			p_last_id = input->joy_axis(p_last_id, joy.id, JOY_AXIS_1, axis_correct(joy.state.Gamepad.sThumbLY, true, false, true));
348 			p_last_id = input->joy_axis(p_last_id, joy.id, JOY_AXIS_2, axis_correct(joy.state.Gamepad.sThumbRX, true));
349 			p_last_id = input->joy_axis(p_last_id, joy.id, JOY_AXIS_3, axis_correct(joy.state.Gamepad.sThumbRY, true, false, true));
350 			p_last_id = input->joy_axis(p_last_id, joy.id, JOY_AXIS_4, axis_correct(joy.state.Gamepad.bLeftTrigger, true, true));
351 			p_last_id = input->joy_axis(p_last_id, joy.id, JOY_AXIS_5, axis_correct(joy.state.Gamepad.bRightTrigger, true, true));
352 			joy.last_packet = joy.state.dwPacketNumber;
353 		}
354 		uint64_t timestamp = input->get_joy_vibration_timestamp(joy.id);
355 		if (timestamp > joy.ff_timestamp) {
356 			Vector2 strength = input->get_joy_vibration_strength(joy.id);
357 			float duration = input->get_joy_vibration_duration(joy.id);
358 			if (strength.x == 0 && strength.y == 0) {
359 				joystick_vibration_stop_xinput(i, timestamp);
360 			} else {
361 				joystick_vibration_start_xinput(i, strength.x, strength.y, duration, timestamp);
362 			}
363 		} else if (joy.vibrating && joy.ff_end_timestamp != 0) {
364 			uint64_t current_time = OS::get_singleton()->get_ticks_usec();
365 			if (current_time >= joy.ff_end_timestamp)
366 				joystick_vibration_stop_xinput(i, current_time);
367 		}
368 	}
369 
370 	for (int i = 0; i < JOYSTICKS_MAX; i++) {
371 
372 		dinput_gamepad *joy = &d_joysticks[i];
373 
374 		if (!joy->attached)
375 			continue;
376 
377 		DIJOYSTATE2 js;
378 		hr = joy->di_joy->Poll();
379 		if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) {
380 			IDirectInputDevice8_Acquire(joy->di_joy);
381 			joy->di_joy->Poll();
382 		}
383 		if (FAILED(hr = joy->di_joy->GetDeviceState(sizeof(DIJOYSTATE2), &js))) {
384 
385 			//printf("failed to read joy #%d\n", i);
386 			continue;
387 		}
388 
389 		p_last_id = post_hat(p_last_id, joy->id, js.rgdwPOV[0]);
390 
391 		for (int j = 0; j < 128; j++) {
392 
393 			if (js.rgbButtons[j] & 0x80) {
394 
395 				if (!joy->last_buttons[j]) {
396 
397 					p_last_id = input->joy_button(p_last_id, joy->id, j, true);
398 					joy->last_buttons[j] = true;
399 				}
400 			} else {
401 
402 				if (joy->last_buttons[j]) {
403 
404 					p_last_id = input->joy_button(p_last_id, joy->id, j, false);
405 					joy->last_buttons[j] = false;
406 				}
407 			}
408 		}
409 
410 		// on mingw, these constants are not constants
411 		int count = 6;
412 		int axes[] = { DIJOFS_X, DIJOFS_Y, DIJOFS_Z, DIJOFS_RX, DIJOFS_RY, DIJOFS_RZ };
413 		int values[] = { js.lX, js.lY, js.lZ, js.lRx, js.lRy, js.lRz };
414 
415 		for (int j = 0; j < joy->joy_axis.size(); j++) {
416 
417 			for (int k = 0; k < count; k++) {
418 				if (joy->joy_axis[j] == axes[k]) {
419 					p_last_id = input->joy_axis(p_last_id, joy->id, j, axis_correct(values[k]));
420 					break;
421 				};
422 			};
423 		};
424 	}
425 	return p_last_id;
426 }
427 
post_hat(unsigned int p_last_id,int p_device,DWORD p_dpad)428 unsigned int joystick_windows::post_hat(unsigned int p_last_id, int p_device, DWORD p_dpad) {
429 
430 	int dpad_val = 0;
431 
432 	if (p_dpad == -1) {
433 		dpad_val = InputDefault::HAT_MASK_CENTER;
434 	}
435 	if (p_dpad == 0) {
436 
437 		dpad_val = InputDefault::HAT_MASK_UP;
438 
439 	} else if (p_dpad == 4500) {
440 
441 		dpad_val = (InputDefault::HAT_MASK_UP | InputDefault::HAT_MASK_RIGHT);
442 
443 	} else if (p_dpad == 9000) {
444 
445 		dpad_val = InputDefault::HAT_MASK_RIGHT;
446 
447 	} else if (p_dpad == 13500) {
448 
449 		dpad_val = (InputDefault::HAT_MASK_RIGHT | InputDefault::HAT_MASK_DOWN);
450 
451 	} else if (p_dpad == 18000) {
452 
453 		dpad_val = InputDefault::HAT_MASK_DOWN;
454 
455 	} else if (p_dpad == 22500) {
456 
457 		dpad_val = (InputDefault::HAT_MASK_DOWN | InputDefault::HAT_MASK_LEFT);
458 
459 	} else if (p_dpad == 27000) {
460 
461 		dpad_val = InputDefault::HAT_MASK_LEFT;
462 
463 	} else if (p_dpad == 31500) {
464 
465 		dpad_val = (InputDefault::HAT_MASK_LEFT | InputDefault::HAT_MASK_UP);
466 	}
467 	return input->joy_hat(p_last_id, p_device, dpad_val);
468 };
469 
axis_correct(int p_val,bool p_xinput,bool p_trigger,bool p_negate) const470 InputDefault::JoyAxis joystick_windows::axis_correct(int p_val, bool p_xinput, bool p_trigger, bool p_negate) const {
471 
472 	InputDefault::JoyAxis jx;
473 	if (Math::abs(p_val) < MIN_JOY_AXIS) {
474 		jx.min = p_trigger ? 0 : -1;
475 		jx.value = 0.0f;
476 		return jx;
477 	}
478 	if (p_xinput) {
479 
480 		if (p_trigger) {
481 			jx.min = 0;
482 			jx.value = (float)p_val / MAX_TRIGGER;
483 			return jx;
484 		}
485 		jx.min = -1;
486 		if (p_val < 0) {
487 			jx.value = (float)p_val / MAX_JOY_AXIS;
488 		} else {
489 			jx.value = (float)p_val / (MAX_JOY_AXIS - 1);
490 		}
491 		if (p_negate) {
492 			jx.value = -jx.value;
493 		}
494 		return jx;
495 	}
496 	jx.min = -1;
497 	jx.value = (float)p_val / MAX_JOY_AXIS;
498 	return jx;
499 }
500 
joystick_vibration_start_xinput(int p_device,float p_weak_magnitude,float p_strong_magnitude,float p_duration,uint64_t p_timestamp)501 void joystick_windows::joystick_vibration_start_xinput(int p_device, float p_weak_magnitude, float p_strong_magnitude, float p_duration, uint64_t p_timestamp) {
502 	xinput_gamepad &joy = x_joysticks[p_device];
503 	if (joy.attached) {
504 		XINPUT_VIBRATION effect;
505 		effect.wLeftMotorSpeed = (65535 * p_strong_magnitude);
506 		effect.wRightMotorSpeed = (65535 * p_weak_magnitude);
507 		if (xinput_set_state(p_device, &effect) == ERROR_SUCCESS) {
508 			joy.ff_timestamp = p_timestamp;
509 			joy.ff_end_timestamp = p_duration == 0 ? 0 : p_timestamp + (uint64_t)(p_duration * 1000000.0);
510 			joy.vibrating = true;
511 		}
512 	}
513 }
514 
joystick_vibration_stop_xinput(int p_device,uint64_t p_timestamp)515 void joystick_windows::joystick_vibration_stop_xinput(int p_device, uint64_t p_timestamp) {
516 	xinput_gamepad &joy = x_joysticks[p_device];
517 	if (joy.attached) {
518 		XINPUT_VIBRATION effect;
519 		effect.wLeftMotorSpeed = 0;
520 		effect.wRightMotorSpeed = 0;
521 		if (xinput_set_state(p_device, &effect) == ERROR_SUCCESS) {
522 			joy.ff_timestamp = p_timestamp;
523 			joy.vibrating = false;
524 		}
525 	}
526 }
527 
load_xinput()528 void joystick_windows::load_xinput() {
529 
530 	xinput_get_state = &_xinput_get_state;
531 	xinput_set_state = &_xinput_set_state;
532 	xinput_dll = LoadLibrary("XInput1_4.dll");
533 	if (!xinput_dll) {
534 		xinput_dll = LoadLibrary("XInput1_3.dll");
535 		if (!xinput_dll) {
536 			xinput_dll = LoadLibrary("XInput9_1_0.dll");
537 		}
538 	}
539 
540 	if (!xinput_dll) {
541 		if (OS::get_singleton()->is_stdout_verbose()) {
542 			print_line("Could not find XInput, using DirectInput only");
543 		}
544 		return;
545 	}
546 
547 	XInputGetState_t func = (XInputGetState_t)GetProcAddress((HMODULE)xinput_dll, "XInputGetState");
548 	XInputSetState_t set_func = (XInputSetState_t)GetProcAddress((HMODULE)xinput_dll, "XInputSetState");
549 	if (!func || !set_func) {
550 		unload_xinput();
551 		return;
552 	}
553 	xinput_get_state = func;
554 	xinput_set_state = set_func;
555 }
556 
unload_xinput()557 void joystick_windows::unload_xinput() {
558 
559 	if (xinput_dll) {
560 
561 		FreeLibrary((HMODULE)xinput_dll);
562 	}
563 }
564