pcsx2/plugins/LilyPad/DirectInput.cpp
FlatOutPS2 5d90afe648 LilyPad: Add separate bindings for each pad type (#1609)
Adds separate bindings for each of the pad types (DualShock2,
Guitar,Pop'n Music). This allows the user to change the button
configuration to better suit the Guitar and Pop'n Music pads without
messing up the bindings already setup for the DS2.

Close #1576.
2016-11-06 21:59:19 +00:00

672 lines
22 KiB
C++

/* LilyPad - Pad plugin for PS2 Emulator
* Copyright (C) 2002-2014 PCSX2 Dev Team/ChickenLiver
*
* PCSX2 is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Found- ation, either version 3 of the License, or (at your option)
* any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with PCSX2. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Global.h"
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>
#include "InputManager.h"
#include "Config.h"
#include "VKey.h"
#include "DirectInput.h"
#include "DeviceEnumerator.h"
#include "PS2Etypes.h"
// All for getting GUIDs of XInput devices....
#include <wbemidl.h>
#include <oleauto.h>
// MS's code imports wmsstd.h, thus requiring the entire windows
// media SDK also be installed for a simple macro. This is
// simpler and less silly.
#ifndef SAFE_RELEASE
#define SAFE_RELEASE(p) \
{ \
if (p) { \
(p)->Release(); \
(p) = NULL; \
} \
}
#endif
// Aka htons, without the winsock dependency.
inline static u16 flipShort(u16 s)
{
return (s >> 8) | (s << 8);
}
// Aka htonl, without the winsock dependency.
inline static u32 flipLong(u32 l)
{
return (((u32)flipShort((u16)l)) << 16) | flipShort((u16)(l >> 16));
}
static void GUIDtoString(wchar_t *data, const GUID *pg)
{
wsprintfW(data, L"%08X-%04X-%04X-%04X-%04X%08X",
pg->Data1, (u32)pg->Data2, (u32)pg->Data3,
flipShort(((u16 *)pg->Data4)[0]),
flipShort(((u16 *)pg->Data4)[1]),
flipLong(((u32 *)pg->Data4)[1]));
}
struct DirectInput8Data
{
IDirectInput8 *lpDI8;
int refCount;
int deviceCount;
};
DirectInput8Data di8d = {0, 0, 0};
IDirectInput8 *GetDirectInput()
{
if (!di8d.lpDI8) {
if (FAILED(DirectInput8Create(hInst, 0x800, IID_IDirectInput8, (void **)&di8d.lpDI8, 0)))
return 0;
}
di8d.refCount++;
return di8d.lpDI8;
}
void ReleaseDirectInput()
{
if (di8d.refCount) {
di8d.refCount--;
if (!di8d.refCount) {
di8d.lpDI8->Release();
di8d.lpDI8 = 0;
}
}
}
static int StringToGUID(GUID *pg, wchar_t *dataw)
{
char data[100];
if (wcslen(dataw) > 50)
return 0;
int w = 0;
while (dataw[w]) {
data[w] = (char)dataw[w];
w++;
}
data[w] = 0;
u32 temp[5];
sscanf(data, "%08X-%04X-%04X-%04X-%04X%08X",
&pg->Data1, temp, temp + 1,
temp + 2, temp + 3, temp + 4);
pg->Data2 = (u16)temp[0];
pg->Data3 = (u16)temp[1];
((u16 *)pg->Data4)[0] = flipShort((u16)temp[2]);
((u16 *)pg->Data4)[1] = flipShort((u16)temp[3]);
((u32 *)pg->Data4)[1] = flipLong(temp[4]);
return 1;
}
struct DI8Effect
{
IDirectInputEffect *die;
int scale;
};
BOOL CALLBACK EnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef);
BOOL CALLBACK EnumEffectsCallback(LPCDIEFFECTINFOW pdei, LPVOID pvRef);
class DirectInputDevice : public Device
{
public:
DI8Effect *diEffects;
IDirectInputDevice8 *did;
GUID guidInstance;
DirectInputDevice(DeviceType type, IDirectInputDevice8 *did, wchar_t *displayName, wchar_t *instanceID, wchar_t *productID, GUID guid)
: Device(DI, type, displayName, instanceID, productID)
{
diEffects = 0;
guidInstance = guid;
this->did = 0;
did->EnumEffects(EnumEffectsCallback, this, DIEFT_ALL);
did->EnumObjects(EnumDeviceObjectsCallback, this, DIDFT_ALL);
did->Release();
}
void SetEffect(ForceFeedbackBinding *binding, unsigned char force)
{
int index = 0;
if (!diEffects) {
return;
}
for (int port = 0; port < 2; port++) {
for (int slot = 0; slot < 4; slot++) {
int padtype = config.padConfigs[port][slot].type;
unsigned int diff = binding - pads[port][slot][padtype].ffBindings;
if (diff < (unsigned int)pads[port][slot][padtype].numFFBindings) {
index += diff;
port = 2;
break;
}
index += pads[port][slot][padtype].numFFBindings;
}
}
IDirectInputEffect *die = diEffects[index].die;
if (die) {
DIEFFECT dieffect;
memset(&dieffect, 0, sizeof(dieffect));
union
{
DIPERIODIC periodic;
DIRAMPFORCE ramp;
DICONSTANTFORCE constant;
};
dieffect.dwSize = sizeof(dieffect);
dieffect.lpvTypeSpecificParams = &periodic;
int magnitude = abs((int)((force * 10000 * (__int64)diEffects[index].scale) / BASE_SENSITIVITY / 255));
if (magnitude > 10000)
magnitude = 10000;
int type = ffEffectTypes[binding->effectIndex].type;
if (type == EFFECT_CONSTANT) {
dieffect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
constant.lMagnitude = magnitude;
} else if (type == EFFECT_PERIODIC) {
dieffect.cbTypeSpecificParams = sizeof(DIPERIODIC);
periodic.dwMagnitude = 0;
periodic.lOffset = magnitude;
periodic.dwPhase = 0;
periodic.dwPeriod = 2000000;
} else if (type == EFFECT_RAMP) {
dieffect.cbTypeSpecificParams = sizeof(DIRAMPFORCE);
ramp.lEnd = ramp.lStart = magnitude;
}
dieffect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTIDS;
dieffect.dwDuration = 2000000;
die->SetParameters(&dieffect, DIEP_TYPESPECIFICPARAMS | DIEP_START);
}
}
int Activate(InitInfo *initInfo)
{
int i;
IDirectInput8 *di8 = GetDirectInput();
Deactivate();
if (!di8)
return 0;
if (DI_OK != di8->CreateDevice(guidInstance, &did, 0)) {
ReleaseDirectInput();
did = 0;
return 0;
}
{
DIOBJECTDATAFORMAT *formats = (DIOBJECTDATAFORMAT *)calloc(numPhysicalControls, sizeof(DIOBJECTDATAFORMAT));
for (i = 0; i < numPhysicalControls; i++) {
formats[i].dwType = physicalControls[i].type | DIDFT_MAKEINSTANCE(physicalControls[i].id);
formats[i].dwOfs = 4 * i;
}
DIDATAFORMAT format;
format.dwSize = sizeof(format);
format.dwDataSize = 4 * numPhysicalControls;
format.dwObjSize = sizeof(DIOBJECTDATAFORMAT);
format.dwFlags = 0;
format.dwNumObjs = numPhysicalControls;
format.rgodf = formats;
int res = did->SetDataFormat(&format);
for (i = 0; i < numPhysicalControls; i++) {
if (physicalControls[i].type == ABSAXIS) {
DIPROPRANGE prop;
prop.diph.dwHeaderSize = sizeof(DIPROPHEADER);
prop.diph.dwSize = sizeof(DIPROPRANGE);
prop.diph.dwObj = formats[i].dwType;
prop.diph.dwHow = DIPH_BYID;
prop.lMin = -FULLY_DOWN;
prop.lMax = FULLY_DOWN;
did->SetProperty(DIPROP_RANGE, &prop.diph);
// May do something like this again, if there's any need.
/*
if (FAILED(DI->did->SetProperty(DIPROP_RANGE, &prop.diph))) {
if (FAILED(DI->did->GetProperty(DIPROP_RANGE, &prop.diph))) {
// ????
DI->objects[i].min = prop.lMin;
DI->objects[i].max = prop.lMax;
continue;
}
}
DI->objects[i].min = prop.lMin;
DI->objects[i].max = prop.lMax;
//*/
}
}
free(formats);
}
// Note: Have to use hWndTop to properly hide cursor for mouse device.
if (type == OTHER) {
did->SetCooperativeLevel(initInfo->hWndTop, DISCL_BACKGROUND | DISCL_EXCLUSIVE);
} else if (type == KEYBOARD) {
did->SetCooperativeLevel(initInfo->hWndTop, DISCL_FOREGROUND);
} else {
did->SetCooperativeLevel(initInfo->hWndTop, DISCL_FOREGROUND | DISCL_EXCLUSIVE);
}
if (did->Acquire() != DI_OK) {
did->Release();
did = 0;
ReleaseDirectInput();
return 0;
}
AllocState();
int count = GetFFBindingCount();
diEffects = (DI8Effect *)calloc(count, sizeof(DI8Effect));
i = 0;
for (int port = 0; port < 2; port++) {
for (int slot = 0; slot < 4; slot++) {
int padtype = config.padConfigs[port][slot].type;
int subIndex = i;
for (int j = 0; j < pads[port][slot][padtype].numFFBindings; j++) {
ForceFeedbackBinding *b = 0;
b = &pads[port][slot][padtype].ffBindings[i - subIndex];
ForceFeedbackEffectType *eff = ffEffectTypes + b->effectIndex;
GUID guid;
if (!StringToGUID(&guid, eff->effectID))
continue;
DIEFFECT dieffect;
memset(&dieffect, 0, sizeof(dieffect));
dieffect.dwSize = sizeof(dieffect);
dieffect.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTIDS;
dieffect.dwDuration = 1000000;
dieffect.dwGain = 10000;
dieffect.dwTriggerButton = DIEB_NOTRIGGER;
union
{
DIPERIODIC pediodic;
DIRAMPFORCE ramp;
DICONSTANTFORCE constant;
} stuff = {0, 0, 0, 0};
if (eff->type == EFFECT_CONSTANT) {
dieffect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
} else if (eff->type == EFFECT_PERIODIC) {
dieffect.cbTypeSpecificParams = sizeof(DIPERIODIC);
} else if (eff->type == EFFECT_RAMP) {
dieffect.cbTypeSpecificParams = sizeof(DIRAMPFORCE);
}
dieffect.lpvTypeSpecificParams = &stuff;
int maxForce = 0;
int numAxes = 0;
int *axes = (int *)malloc(sizeof(int) * 3 * numFFAxes);
DWORD *axisIDs = (DWORD *)(axes + numFFAxes);
LONG *dirList = (LONG *)(axisIDs + numFFAxes);
dieffect.rgdwAxes = axisIDs;
dieffect.rglDirection = dirList;
for (int k = 0; k < numFFAxes; k++) {
if (b->axes[k].force) {
int force = abs(b->axes[k].force);
if (force > maxForce) {
maxForce = force;
}
axes[numAxes] = k;
axisIDs[numAxes] = ffAxes[k].id;
dirList[numAxes] = b->axes[k].force;
numAxes++;
}
}
if (!numAxes) {
free(axes);
continue;
}
dieffect.cAxes = numAxes;
diEffects[i].scale = maxForce;
if (!SUCCEEDED(did->CreateEffect(guid, &dieffect, &diEffects[i].die, 0))) {
diEffects[i].die = 0;
diEffects[i].scale = 0;
}
free(axes);
axes = 0;
i++;
}
}
}
active = 1;
return 1;
}
int Update()
{
if (!active)
return 0;
if (numPhysicalControls) {
HRESULT res = did->Poll();
// ??
if ((res != DI_OK && res != DI_NOEFFECT) ||
DI_OK != did->GetDeviceState(4 * numPhysicalControls, physicalControlState)) {
Deactivate();
return 0;
}
for (int i = 0; i < numPhysicalControls; i++) {
if (physicalControls[i].type & RELAXIS) {
physicalControlState[i] *= (FULLY_DOWN / 3);
} else if (physicalControls[i].type & BUTTON) {
physicalControlState[i] = (physicalControlState[i] & 0x80) * FULLY_DOWN / 128;
}
}
}
return 1;
}
int GetFFBindingCount()
{
int count = 0;
for (int port = 0; port < 2; port++) {
for (int slot = 0; slot < 4; slot++) {
int padtype = config.padConfigs[port][slot].type;
count += pads[port][slot][padtype].numFFBindings;
}
}
return count;
}
void Deactivate()
{
FreeState();
if (diEffects) {
int count = GetFFBindingCount();
for (int i = 0; i < count; i++) {
if (diEffects[i].die) {
diEffects[i].die->Stop();
diEffects[i].die->Release();
}
}
free(diEffects);
diEffects = 0;
}
if (active) {
did->Unacquire();
did->Release();
ReleaseDirectInput();
did = 0;
active = 0;
}
}
~DirectInputDevice()
{
}
};
BOOL CALLBACK EnumEffectsCallback(LPCDIEFFECTINFOW pdei, LPVOID pvRef)
{
DirectInputDevice *did = (DirectInputDevice *)pvRef;
EffectType type;
int diType = DIEFT_GETTYPE(pdei->dwEffType);
if (diType == DIEFT_CONSTANTFORCE) {
type = EFFECT_CONSTANT;
} else if (diType == DIEFT_RAMPFORCE) {
type = EFFECT_RAMP;
} else if (diType == DIEFT_PERIODIC) {
type = EFFECT_PERIODIC;
} else {
return DIENUM_CONTINUE;
}
wchar_t guidString[50];
GUIDtoString(guidString, &pdei->guid);
did->AddFFEffectType(pdei->tszName, guidString, type);
return DIENUM_CONTINUE;
}
BOOL CALLBACK EnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, LPVOID pvRef)
{
DirectInputDevice *did = (DirectInputDevice *)pvRef;
if (lpddoi->dwType & DIDFT_FFACTUATOR) {
did->AddFFAxis(lpddoi->tszName, lpddoi->dwType);
}
ControlType type;
if (lpddoi->dwType & DIDFT_POV)
type = POV;
else if (lpddoi->dwType & DIDFT_ABSAXIS)
type = ABSAXIS;
else if (lpddoi->dwType & DIDFT_RELAXIS)
type = RELAXIS;
else if (lpddoi->dwType & DIDFT_PSHBUTTON)
type = PSHBTN;
else if (lpddoi->dwType & DIDFT_TGLBUTTON)
type = TGLBTN;
else
return DIENUM_CONTINUE;
// If too many objects, ignore extra buttons.
if ((did->numPhysicalControls > 255 && DIDFT_GETINSTANCE(lpddoi->dwType) > 255) && (type & (DIDFT_PSHBUTTON | DIDFT_TGLBUTTON))) {
int i;
for (i = did->numPhysicalControls - 1; i > did->numPhysicalControls - 4; i--) {
if (!lpddoi->tszName[0])
break;
const wchar_t *s1 = lpddoi->tszName;
const wchar_t *s2 = did->physicalControls[i].name;
while (*s1 && *s1 == *s2) {
s1++;
s2++;
}
// If perfect match with one of last 4 names, break.
if (!*s1 && !*s2)
break;
while (s1 != lpddoi->tszName && (s1[-1] >= '0' && s1[-1] <= '9'))
s1--;
int check = 0;
while (*s1 >= '0' && *s1 <= '9') {
check = check * 10 + *s1 - '0';
s1++;
}
while (*s2 >= '0' && *s2 <= '9') {
s2++;
}
// If perfect match other than final number > 30, then break.
// takes care of "button xx" case without causing issues with F keys.
if (!*s1 && !*s2 && check > 30)
break;
}
if (i != did->numPhysicalControls - 4) {
return DIENUM_CONTINUE;
}
}
int vkey = 0;
if (lpddoi->tszName[0] && did->type == KEYBOARD) {
for (u32 i = 0; i < 256; i++) {
wchar_t *t = GetVKStringW((u8)i);
if (!wcsicmp(lpddoi->tszName, t)) {
vkey = i;
break;
}
}
}
did->AddPhysicalControl(type, DIDFT_GETINSTANCE(lpddoi->dwType), vkey, lpddoi->tszName);
return DIENUM_CONTINUE;
}
// Evil code from MS's site. If only they'd just made a way to get
// an XInput device's GUID directly in the first place...
BOOL IsXInputDevice(const GUID *pGuidProductFromDirectInput)
{
IWbemLocator *pIWbemLocator = NULL;
IEnumWbemClassObject *pEnumDevices = NULL;
IWbemClassObject *pDevices[20] = {0};
IWbemServices *pIWbemServices = NULL;
BSTR bstrNamespace = NULL;
BSTR bstrDeviceID = NULL;
BSTR bstrClassName = NULL;
DWORD uReturned = 0;
bool bIsXinputDevice = false;
UINT iDevice = 0;
VARIANT var;
HRESULT hr;
// CoInit if needed
hr = CoInitialize(NULL);
bool bCleanupCOM = SUCCEEDED(hr);
// Create WMI
hr = CoCreateInstance(__uuidof(WbemLocator),
NULL,
CLSCTX_INPROC_SERVER,
__uuidof(IWbemLocator),
(LPVOID *)&pIWbemLocator);
if (FAILED(hr) || pIWbemLocator == NULL)
goto LCleanup;
bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2");
if (bstrNamespace == NULL)
goto LCleanup;
bstrClassName = SysAllocString(L"Win32_PNPEntity");
if (bstrClassName == NULL)
goto LCleanup;
bstrDeviceID = SysAllocString(L"DeviceID");
if (bstrDeviceID == NULL)
goto LCleanup;
// Connect to WMI
hr = pIWbemLocator->ConnectServer(bstrNamespace, NULL, NULL, 0L,
0L, NULL, NULL, &pIWbemServices);
if (FAILED(hr) || pIWbemServices == NULL)
goto LCleanup;
// Switch security level to IMPERSONATE.
CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL,
RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);
hr = pIWbemServices->CreateInstanceEnum(bstrClassName, 0, NULL, &pEnumDevices);
if (FAILED(hr) || pEnumDevices == NULL)
goto LCleanup;
// Loop over all devices
for (;;) {
// Get 20 at a time
hr = pEnumDevices->Next(10000, 20, pDevices, &uReturned);
if (FAILED(hr))
goto LCleanup;
if (uReturned == 0)
break;
for (iDevice = 0; iDevice < uReturned; iDevice++) {
// For each device, get its device ID
hr = pDevices[iDevice]->Get(bstrDeviceID, 0L, &var, NULL, NULL);
if (SUCCEEDED(hr) && var.vt == VT_BSTR && var.bstrVal != NULL) {
// Check if the device ID contains "IG_". If it does, then it's an XInput device
// This information can not be found from DirectInput
if (wcsstr(var.bstrVal, L"IG_")) {
// If it does, then get the VID/PID from var.bstrVal
DWORD dwPid = 0, dwVid = 0;
WCHAR *strVid = wcsstr(var.bstrVal, L"VID_");
if (strVid) {
dwVid = wcstoul(strVid + 4, 0, 16);
}
WCHAR *strPid = wcsstr(var.bstrVal, L"PID_");
if (strPid) {
dwPid = wcstoul(strPid + 4, 0, 16);
}
// Compare the VID/PID to the DInput device
DWORD dwVidPid = MAKELONG(dwVid, dwPid);
if (dwVidPid == pGuidProductFromDirectInput->Data1) {
bIsXinputDevice = true;
goto LCleanup;
}
}
}
SAFE_RELEASE(pDevices[iDevice]);
}
}
LCleanup:
if (bstrNamespace)
SysFreeString(bstrNamespace);
if (bstrDeviceID)
SysFreeString(bstrDeviceID);
if (bstrClassName)
SysFreeString(bstrClassName);
for (iDevice = 0; iDevice < 20; iDevice++)
SAFE_RELEASE(pDevices[iDevice]);
SAFE_RELEASE(pEnumDevices);
SAFE_RELEASE(pIWbemLocator);
SAFE_RELEASE(pIWbemServices);
if (bCleanupCOM)
CoUninitialize();
return bIsXinputDevice;
}
struct DeviceEnumInfo
{
IDirectInput8 *di8;
int ignoreXInput;
};
BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCE lpddi, LPVOID pvRef)
{
IDirectInput8 *di8 = ((DeviceEnumInfo *)pvRef)->di8;
const wchar_t *name;
wchar_t temp[40];
//if (((DeviceEnumInfo*)pvRef)->ignoreXInput && lpddi->
if (lpddi->tszInstanceName[0]) {
name = lpddi->tszInstanceName;
} else if (lpddi->tszProductName[0]) {
name = lpddi->tszProductName;
} else {
wsprintfW(temp, L"Device %i", di8d.deviceCount);
name = temp;
}
di8d.deviceCount++;
wchar_t *fullName = (wchar_t *)malloc((wcslen(name) + 4) * sizeof(wchar_t));
wsprintf(fullName, L"DX %s", name);
wchar_t instanceID[100];
wchar_t productID[100];
GUIDtoString(instanceID, &lpddi->guidInstance);
GUIDtoString(productID, &lpddi->guidProduct);
DeviceType type = OTHER;
if ((lpddi->dwDevType & 0xFF) == DI8DEVTYPE_KEYBOARD) {
type = KEYBOARD;
} else if ((lpddi->dwDevType & 0xFF) == DI8DEVTYPE_MOUSE) {
type = MOUSE;
}
IDirectInputDevice8 *did;
if (DI_OK == di8->CreateDevice(lpddi->guidInstance, &did, 0)) {
DirectInputDevice *dev = new DirectInputDevice(type, did, fullName, instanceID, productID, lpddi->guidInstance);
if (dev->numPhysicalControls || dev->numFFAxes) {
dm->AddDevice(dev);
} else {
delete dev;
}
}
free(fullName);
return DIENUM_CONTINUE;
}
void EnumDirectInputDevices(int ignoreXInput)
{
DeviceEnumInfo enumInfo;
enumInfo.di8 = GetDirectInput();
if (!enumInfo.di8)
return;
enumInfo.ignoreXInput = ignoreXInput;
di8d.deviceCount = 0;
enumInfo.di8->EnumDevices(DI8DEVCLASS_ALL, EnumCallback, &enumInfo, DIEDFL_ATTACHEDONLY);
ReleaseDirectInput();
}