1 #include "stdafx.h"
2 
3 #define DIRECTINPUT_VERSION 0x0800
4 #include <thread>
5 #include <windows.h>
6 #include <dinput.h>
7 #include <dinputd.h>
8 #include <wbemidl.h>
9 #include <oleauto.h>
10 #include "DirectInputManager.h"
11 #include <algorithm>
12 #include "../Core/MessageManager.h"
13 #include "../Core/Console.h"
14 #include "../Core/EmulationSettings.h"
15 
16 LPDIRECTINPUT8 DirectInputManager::_directInput = nullptr;
17 vector<DirectInputData> DirectInputManager::_joysticks;
18 vector<DirectInputData> DirectInputManager::_joysticksToAdd;
19 std::vector<GUID> DirectInputManager::_processedGuids;
20 std::vector<GUID> DirectInputManager::_xinputDeviceGuids;
21 HWND DirectInputManager::_hWnd = nullptr;
22 
Initialize()23 void DirectInputManager::Initialize()
24 {
25 	HRESULT hr;
26 
27 	// Register with the DirectInput subsystem and get a pointer to a IDirectInput interface we can use.
28 	// Create a DInput object
29 	if(FAILED(hr = DirectInput8Create(GetModuleHandle(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8, (VOID**)&_directInput, nullptr))) {
30 		MessageManager::Log("[DInput] DirectInput8Create failed: " + std::to_string(hr));
31 		return;
32 	}
33 
34 	IDirectInputJoyConfig8* pJoyConfig = nullptr;
35 	if(FAILED(hr = _directInput->QueryInterface(IID_IDirectInputJoyConfig8, (void**)&pJoyConfig))) {
36 		MessageManager::Log("[DInput] QueryInterface failed: " + std::to_string(hr));
37 		return;
38 	}
39 
40 	if(pJoyConfig) {
41 		pJoyConfig->Release();
42 	}
43 
44 	UpdateDeviceList();
45 }
46 
ProcessDevice(const DIDEVICEINSTANCE * pdidInstance)47 bool DirectInputManager::ProcessDevice(const DIDEVICEINSTANCE* pdidInstance)
48 {
49 	const GUID* deviceGuid = &pdidInstance->guidInstance;
50 
51 	auto comp = [=](GUID guid) {
52 		return guid.Data1 == deviceGuid->Data1 &&
53 			guid.Data2 == deviceGuid->Data2 &&
54 			guid.Data3 == deviceGuid->Data3 &&
55 			memcmp(guid.Data4, deviceGuid->Data4, sizeof(guid.Data4)) == 0;
56 	};
57 
58 	bool wasProcessedBefore = std::find_if(_processedGuids.begin(), _processedGuids.end(), comp) != _processedGuids.end();
59 	if(wasProcessedBefore) {
60 		return false;
61 	} else {
62 		bool isXInput = IsXInputDevice(&pdidInstance->guidProduct);
63 		if(isXInput) {
64 			_xinputDeviceGuids.push_back(*deviceGuid);
65 			_processedGuids.push_back(*deviceGuid);
66 		}
67 		return !isXInput;
68 	}
69 }
70 
71 //-----------------------------------------------------------------------------
72 // Enum each PNP device using WMI and check each device ID to see if it contains
73 // "IG_" (ex. "VID_045E&PID_028E&IG_00").  If it does, then it's an XInput device
74 // Unfortunately this information can not be found by just using DirectInput
75 //-----------------------------------------------------------------------------
IsXInputDevice(const GUID * pGuidProductFromDirectInput)76 bool DirectInputManager::IsXInputDevice(const GUID* pGuidProductFromDirectInput)
77 {
78 	IWbemLocator*           pIWbemLocator = NULL;
79 	IEnumWbemClassObject*   pEnumDevices = NULL;
80 	IWbemClassObject*       pDevices[20] = { 0 };
81 	IWbemServices*          pIWbemServices = NULL;
82 	BSTR                    bstrNamespace = NULL;
83 	BSTR                    bstrDeviceID = NULL;
84 	BSTR                    bstrClassName = NULL;
85 	DWORD                   uReturned = 0;
86 	bool                    bIsXinputDevice = false;
87 	UINT                    iDevice = 0;
88 	VARIANT                 var;
89 	HRESULT                 hr;
90 
91 	// CoInit if needed
92 	hr = CoInitialize(NULL);
93 	bool bCleanupCOM = SUCCEEDED(hr);
94 
95 	// Create WMI
96 	hr = CoCreateInstance(__uuidof(WbemLocator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IWbemLocator), (LPVOID*)&pIWbemLocator);
97 	if(FAILED(hr) || pIWbemLocator == NULL) {
98 		goto LCleanup;
99 	}
100 
101 	bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2");
102 	bstrClassName = SysAllocString(L"Win32_PNPEntity");
103 	bstrDeviceID = SysAllocString(L"DeviceID");
104 
105 	// Connect to WMI
106 	hr = pIWbemLocator->ConnectServer(bstrNamespace, NULL, NULL, 0L, 0L, NULL, NULL, &pIWbemServices);
107 	if(FAILED(hr) || pIWbemServices == NULL) {
108 		goto LCleanup;
109 	}
110 
111 	// Switch security level to IMPERSONATE.
112 	CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
113 
114 	hr = pIWbemServices->CreateInstanceEnum(bstrClassName, 0, NULL, &pEnumDevices);
115 	if(FAILED(hr) || pEnumDevices == NULL) {
116 		goto LCleanup;
117 	}
118 
119 	// Loop over all devices
120 	for(;; ) {
121 		// Get 20 at a time
122 		hr = pEnumDevices->Next(10000, 20, pDevices, &uReturned);
123 		if(FAILED(hr) || uReturned == 0 || bIsXinputDevice) {
124 			break;
125 		}
126 
127 		for(iDevice = 0; iDevice < uReturned; iDevice++) {
128 			// For each device, get its device ID
129 			hr = pDevices[iDevice]->Get(bstrDeviceID, 0L, &var, NULL, NULL);
130 			if(SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != NULL) {
131 				// Check if the device ID contains "IG_".  If it does, then it's an XInput device
132 				// This information can not be found from DirectInput
133 				if(wcsstr(var.bstrVal, L"IG_")) {
134 					// If it does, then get the VID/PID from var.bstrVal
135 					DWORD dwPid = 0, dwVid = 0;
136 					WCHAR* strVid = wcsstr(var.bstrVal, L"VID_");
137 					if(strVid && swscanf_s(strVid, L"VID_%4X", &dwVid) != 1) {
138 						dwVid = 0;
139 					}
140 					WCHAR* strPid = wcsstr(var.bstrVal, L"PID_");
141 					if(strPid && swscanf_s(strPid, L"PID_%4X", &dwPid) != 1) {
142 						dwPid = 0;
143 					}
144 
145 					// Compare the VID/PID to the DInput device
146 					DWORD dwVidPid = MAKELONG(dwVid, dwPid);
147 					if(dwVidPid == pGuidProductFromDirectInput->Data1) {
148 						bIsXinputDevice = true;
149 						pDevices[iDevice]->Release();
150 						pDevices[iDevice] = nullptr;
151 						break;
152 					}
153 				}
154 			}
155 			VariantClear(&var);
156 			pDevices[iDevice]->Release();
157 			pDevices[iDevice] = nullptr;
158 		}
159 	}
160 
161 LCleanup:
162 	if(bstrNamespace) {
163 		SysFreeString(bstrNamespace);
164 	}
165 	if(bstrDeviceID) {
166 		SysFreeString(bstrDeviceID);
167 	}
168 	if(bstrClassName) {
169 		SysFreeString(bstrClassName);
170 	}
171 	for(iDevice = 0; iDevice < 20; iDevice++) {
172 		if(pDevices[iDevice]) {
173 			pDevices[iDevice]->Release();
174 		}
175 	}
176 	if(pEnumDevices) {
177 		pEnumDevices->Release();
178 	}
179 	if(pIWbemLocator) {
180 		pIWbemLocator->Release();
181 	}
182 	if(pIWbemServices) {
183 		pIWbemServices->Release();
184 	}
185 
186 	if(bCleanupCOM) {
187 		CoUninitialize();
188 	}
189 
190 	return bIsXinputDevice;
191 }
192 
UpdateDeviceList()193 void DirectInputManager::UpdateDeviceList()
194 {
195 	if(_needToUpdate) {
196 		//An update is already pending, skip
197 		return;
198 	}
199 
200 	HRESULT hr;
201 
202 	// Enumerate devices
203 	if(SUCCEEDED(hr = _directInput->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumJoysticksCallback, nullptr, DIEDFL_ALLDEVICES))) {
204 		if(!_joysticksToAdd.empty()) {
205 			//Sleeping apparently lets us read accurate "default" values, otherwise a PS4 controller returns all 0s, despite not doing so normally
206 			for(DirectInputData &joystick : _joysticksToAdd) {
207 				UpdateInputState(joystick);
208 			}
209 			std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(100));
210 
211 			for(DirectInputData &joystick : _joysticksToAdd) {
212 				UpdateInputState(joystick);
213 				joystick.defaultState = joystick.state;
214 			}
215 			_needToUpdate = true;
216 		}
217 	}
218 
219 	if(_requestUpdate) {
220 		_requestUpdate = false;
221 		_needToUpdate = true;
222 	}
223 }
224 
225 //-----------------------------------------------------------------------------
226 // Name: EnumJoysticksCallback()
227 // Desc: Called once for each enumerated joystick. If we find one, create a
228 //       device interface on it so we can play with it.
229 //-----------------------------------------------------------------------------
EnumJoysticksCallback(const DIDEVICEINSTANCE * pdidInstance,void * pContext)230 int DirectInputManager::EnumJoysticksCallback(const DIDEVICEINSTANCE* pdidInstance, void* pContext)
231 {
232 	HRESULT hr;
233 
234 	if(ProcessDevice(pdidInstance)) {
235 		_processedGuids.push_back(pdidInstance->guidInstance);
236 
237 		// Obtain an interface to the enumerated joystick.
238 		LPDIRECTINPUTDEVICE8 pJoystick = nullptr;
239 		hr = _directInput->CreateDevice(pdidInstance->guidInstance, &pJoystick, nullptr);
240 
241 		if(SUCCEEDED(hr)) {
242 			DIJOYSTATE2 state;
243 			memset(&state, 0, sizeof(state));
244 			DirectInputData data{ pJoystick, state, state, false };
245 			memcpy(&data.instanceInfo, pdidInstance, sizeof(DIDEVICEINSTANCE));
246 
247 			// Set the data format to "simple joystick" - a predefined data format
248 			// A data format specifies which controls on a device we are interested in, and how they should be reported.
249 			// This tells DInput that we will be passing a DIJOYSTATE2 structure to IDirectInputDevice::GetDeviceState().
250 			if(SUCCEEDED(hr = data.joystick->SetDataFormat(&c_dfDIJoystick2))) {
251 				// Set the cooperative level to let DInput know how this device should interact with the system and with other DInput applications.
252 				if(SUCCEEDED(hr = data.joystick->SetCooperativeLevel(_hWnd, DISCL_NONEXCLUSIVE | DISCL_BACKGROUND))) {
253 					// Enumerate the joystick objects. The callback function enabled user interface elements for objects that are found, and sets the min/max values property for discovered axes.
254 					if(SUCCEEDED(hr = data.joystick->EnumObjects(EnumObjectsCallback, data.joystick, DIDFT_ALL))) {
255 						_joysticksToAdd.push_back(data);
256 					} else {
257 						MessageManager::Log("[DInput] Failed to enumerate objects: " + std::to_string(hr));
258 					}
259 				} else {
260 					MessageManager::Log("[DInput] Failed to set cooperative level: " + std::to_string(hr));
261 				}
262 			} else {
263 				MessageManager::Log("[DInput] Failed to set data format: " + std::to_string(hr));
264 			}
265 		} else {
266 			MessageManager::Log("[DInput] Failed to create directinput device" + std::to_string(hr));
267 		}
268 	}
269 	return DIENUM_CONTINUE;
270 }
271 
272 //-----------------------------------------------------------------------------
273 // Name: EnumObjectsCallback()
274 // Desc: Callback function for enumerating objects (axes, buttons, POVs) on a
275 //       joystick. This function enables user interface elements for objects
276 //       that are found to exist, and scales axes min/max values.
277 //-----------------------------------------------------------------------------
EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE * pdidoi,void * pContext)278 int DirectInputManager::EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, void* pContext)
279 {
280 	LPDIRECTINPUTDEVICE8 joystick = (LPDIRECTINPUTDEVICE8)pContext;
281 
282 	// For axes that are returned, set the DIPROP_RANGE property for the enumerated axis in order to scale min/max values.
283 	if(pdidoi->dwType & DIDFT_AXIS) {
284 		DIPROPRANGE diprg;
285 		diprg.diph.dwSize = sizeof(DIPROPRANGE);
286 		diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
287 		diprg.diph.dwHow = DIPH_BYID;
288 		diprg.diph.dwObj = pdidoi->dwType; // Specify the enumerated axis
289 		diprg.lMin = -1000;
290 		diprg.lMax = +1000;
291 
292 		// Set the range for the axis
293 		if(FAILED(joystick->SetProperty(DIPROP_RANGE, &diprg.diph))) {
294 			return DIENUM_STOP;
295 		}
296 	}
297 
298 	return DIENUM_CONTINUE;
299 }
300 
301 
RefreshState()302 void DirectInputManager::RefreshState()
303 {
304 	if(_needToUpdate) {
305 		vector<DirectInputData> joysticks;
306 		//Keep exisiting joysticks, if they still work, otherwise remove them from the list
307 		for(DirectInputData &joystick : _joysticks) {
308 			if(joystick.stateValid) {
309 				joysticks.push_back(joystick);
310 			} else {
311 				MessageManager::Log("[DInput] Device lost, trying to reacquire...");
312 
313 				//Release the joystick, we'll try to initialize it again if it still exists
314 				const GUID* deviceGuid = &joystick.instanceInfo.guidInstance;
315 
316 				auto comp = [=](GUID guid) {
317 					return guid.Data1 == deviceGuid->Data1 &&
318 						guid.Data2 == deviceGuid->Data2 &&
319 						guid.Data3 == deviceGuid->Data3 &&
320 						memcmp(guid.Data4, deviceGuid->Data4, sizeof(guid.Data4)) == 0;
321 				};
322 				_processedGuids.erase(std::remove_if(_processedGuids.begin(), _processedGuids.end(), comp), _processedGuids.end());
323 
324 				joystick.joystick->Unacquire();
325 				joystick.joystick->Release();
326 			}
327 		}
328 
329 		//Add the newly-found joysticks
330 		for(DirectInputData &joystick : _joysticksToAdd) {
331 			joysticks.push_back(joystick);
332 		}
333 
334 		_joysticks = joysticks;
335 		_joysticksToAdd.clear();
336 		_needToUpdate = false;
337 	}
338 
339 	for(DirectInputData &joystick : _joysticks) {
340 		UpdateInputState(joystick);
341 	}
342 }
343 
GetJoystickCount()344 int DirectInputManager::GetJoystickCount()
345 {
346 	return (int)_joysticks.size();
347 }
348 
IsPressed(int port,int button)349 bool DirectInputManager::IsPressed(int port, int button)
350 {
351 	if(port >= (int)_joysticks.size() || !_joysticks[port].stateValid) {
352 		return false;
353 	}
354 
355 	DIJOYSTATE2& state = _joysticks[port].state;
356 	DIJOYSTATE2& defaultState = _joysticks[port].defaultState;
357 	int deadRange = (int)(500 * _console->GetSettings()->GetControllerDeadzoneRatio());
358 
359 	int povDirection = state.rgdwPOV[0] / 4500;
360 	bool povCentered = (LOWORD(state.rgdwPOV[0]) == 0xFFFF) || povDirection >= 8;
361 
362 	switch(button) {
363 		case 0x00: return state.lY - defaultState.lY < -deadRange;
364 		case 0x01: return state.lY - defaultState.lY > deadRange;
365 		case 0x02: return state.lX - defaultState.lX < -deadRange;
366 		case 0x03: return state.lX - defaultState.lX > deadRange;
367 		case 0x04: return state.lRy - defaultState.lRy < -deadRange;
368 		case 0x05: return state.lRy - defaultState.lRy > deadRange;
369 		case 0x06: return state.lRx - defaultState.lRx < -deadRange;
370 		case 0x07: return state.lRx - defaultState.lRx > deadRange;
371 		case 0x08: return state.lZ - defaultState.lZ < -deadRange;
372 		case 0x09: return state.lZ - defaultState.lZ > deadRange;
373 		case 0x0A: return state.lRz - defaultState.lRz < -deadRange;
374 		case 0x0B: return state.lRz - defaultState.lRz > deadRange;
375 		case 0x0C: return !povCentered && (povDirection == 7 || povDirection == 0 || povDirection == 1);
376 		case 0x0D: return !povCentered && (povDirection >= 3 && povDirection <= 5);
377 		case 0x0E: return !povCentered && (povDirection >= 1 && povDirection <= 3);
378 		case 0x0F: return !povCentered && (povDirection >= 5 && povDirection <= 7);
379 		default: return state.rgbButtons[button - 0x10] != 0;
380 	}
381 
382 	return false;
383 }
384 
UpdateInputState(DirectInputData & data)385 void DirectInputManager::UpdateInputState(DirectInputData &data)
386 {
387 	DIJOYSTATE2 newState;
388 	HRESULT hr;
389 
390 	// Poll the device to read the current state
391 	hr = data.joystick->Poll();
392 	if(FAILED(hr)) {
393 		// DInput is telling us that the input stream has been interrupted. We aren't tracking any state between polls, so
394 		// we don't have any special reset that needs to be done. We just re-acquire and try again.
395 		hr = data.joystick->Acquire();
396 		while(hr == DIERR_INPUTLOST) {
397 			hr = data.joystick->Acquire();
398 		}
399 
400 		// hr may be DIERR_OTHERAPPHASPRIO or other errors.  This may occur when the app is minimized or in the process of
401 		// switching, so just try again later
402 		if(FAILED(hr)) {
403 			data.stateValid = false;
404 			_requestUpdate = true;
405 			return;
406 		}
407 	}
408 
409 	// Get the input's device state
410 	if(FAILED(hr = data.joystick->GetDeviceState(sizeof(DIJOYSTATE2), &newState))) {
411 		MessageManager::Log("[DInput] Failed to get device state: " + std::to_string(hr));
412 		data.stateValid = false;
413 		_requestUpdate = true;
414 		return; // The device should have been acquired during the Poll()
415 	}
416 
417 	data.state = newState;
418 	data.stateValid = true;
419 }
420 
421 
DirectInputManager(shared_ptr<Console> console,HWND hWnd)422 DirectInputManager::DirectInputManager(shared_ptr<Console> console, HWND hWnd)
423 {
424 	_console = console;
425 	_hWnd = hWnd;
426 	Initialize();
427 }
428 
~DirectInputManager()429 DirectInputManager::~DirectInputManager()
430 {
431 	for(DirectInputData &data: _joysticks) {
432 		data.joystick->Unacquire();
433 		data.joystick->Release();
434 	}
435 
436 	_needToUpdate = false;
437 	_joysticks.clear();
438 	_joysticksToAdd.clear();
439 	_processedGuids.clear();
440 	_xinputDeviceGuids.clear();
441 
442 	if(_directInput) {
443 		_directInput->Release();
444 		_directInput = nullptr;
445 	}
446 }
447