Yet another xinput implementation for the xbox360 controller

Bruno Jesus 00cpxxx at gmail.com
Sat Aug 27 01:05:06 CDT 2016


Hi to everybody.

In order to exercise dinput (which is overly complex) I have
implemented xinput using dinput. Xinput is as easy as winmm joystick
support, I guess MS wanted to go back to the basics.

I know that this is not really useful specially when HID kicks in but
as I said it was an exercise and now I can play Broforce properly with
the Xbox controller and force feedback.

If you know a game that is xinput only and it does not work with this
patch tests are appreciated (+xinput,+dinput). Most of the code can
still be reused for HID IMO, just change the dinput functions.

If you want force feedback remember to disable the (js) joystick in
the control panel. Also if you have other controller brands it must
have at least 6 axis, 11 buttons and 1 POV to work, but the mapping
has to be redone. If it has less buttons you can relax the checks in
dinput_is_good.

Last but not least I know I should have used dinput to map the axes
values for me but I was tired of reading dinput docs and it was easy
enough to convert manually.

Best wishes,
Bruno
-------------- next part --------------
diff --git a/dlls/xinput1_3/Makefile.in b/dlls/xinput1_3/Makefile.in
index cf8f730..37621fa 100644
--- a/dlls/xinput1_3/Makefile.in
+++ b/dlls/xinput1_3/Makefile.in
@@ -1,5 +1,6 @@
 MODULE    = xinput1_3.dll
 IMPORTLIB = xinput
+IMPORTS   = uuid dxguid dinput dinput8 ole32
 
 C_SRCS = \
 	xinput1_3_main.c
diff --git a/dlls/xinput1_3/xinput1_3_main.c b/dlls/xinput1_3/xinput1_3_main.c
index 63f725b..965b32d 100644
--- a/dlls/xinput1_3/xinput1_3_main.c
+++ b/dlls/xinput1_3/xinput1_3_main.c
@@ -1,6 +1,8 @@
 /*
  * The Wine project - Xinput Joystick Library
+ *
  * Copyright 2008 Andrew Fenn
+ * Copyright 2016 Bruno Jesus
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -17,6 +19,7 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  */
 
+#define COBJMACROS
 #include "config.h"
 #include <assert.h>
 #include <stdarg.h>
@@ -27,10 +30,292 @@
 #include "winbase.h"
 #include "winerror.h"
 
+#include "initguid.h"
 #include "xinput.h"
+#include "dinput.h"
+#include "dinputd.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(xinput);
 
+static struct ControllerMap
+{
+    LPDIRECTINPUTDEVICE8A device;
+    BOOL connected, wired, acquired, jedi;
+    XINPUT_STATE state;
+    XINPUT_VIBRATION vibration;
+    BOOL vibration_dirty;
+
+    DIEFFECT effect_data;
+    LPDIRECTINPUTEFFECT effect_instance;
+} controllers[XUSER_MAX_COUNT];
+
+static struct
+{
+    LPDIRECTINPUT8A iface;
+    BOOL enabled;
+    int mapped;
+} dinput;
+
+#define STARTUP_DINPUT if (!dinput.iface) dinput_start();
+
+/* ========================= Internal functions ============================= */
+
+static BOOL dinput_is_good(const LPDIRECTINPUTDEVICE8A device, BOOL *wired, BOOL *jedi)
+{
+    HRESULT hr;
+    DIPROPDWORD property;
+    DIDEVCAPS caps;
+    static const struct
+    {
+        unsigned long vidpid;
+        BOOL wired;
+    } data_list[] = {
+        /* Microsoft known controllers for Xbox 360 */
+        { MAKELONG(0x045e, 0x028e), 1 /* wired controller*/ },
+        { MAKELONG(0x045e, 0x0291), 0 /* wireless receiver */},
+        { MAKELONG(0x045e, 0x0719), 0 /* wireless controller */}
+    };
+    int i;
+
+    property.diph.dwSize = sizeof(property);
+    property.diph.dwHeaderSize = sizeof(property.diph);
+    property.diph.dwObj = 0;
+    property.diph.dwHow = DIPH_DEVICE;
+
+    hr = IDirectInputDevice_GetProperty(device, DIPROP_VIDPID, &property.diph);
+    if (FAILED(hr))
+        return FALSE;
+
+    caps.dwSize = sizeof(caps);
+    hr = IDirectInputDevice_GetCapabilities(device, &caps);
+    if (FAILED(hr))
+        return FALSE;
+
+    if (caps.dwAxes < 6 || caps.dwButtons < 11  || caps.dwPOVs < 1)
+        return FALSE;
+
+    *jedi = !!(caps.dwFlags & DIDC_FORCEFEEDBACK);
+    for (i = 0; i < sizeof(data_list) / sizeof(data_list[0]); i++)
+        /* check if has force feedback to ignore (js) driver joysticks */
+        if (*jedi && property.dwData == data_list[i].vidpid)
+        {
+            *wired = data_list[i].wired;
+            return TRUE;
+        }
+
+    if (caps.dwAxes == 6 && caps.dwButtons == 11  && caps.dwPOVs == 1)
+        FIXME("This controller has the same number of buttons/axes from xbox 360, may work...\n");
+    else
+        FIXME("This is not a known xbox controller, using anyway. Expect problems!\n");
+
+    *wired = TRUE;
+    return TRUE;
+}
+
+static void dinput_joystate_to_xinput(DIJOYSTATE2 *js, XINPUT_GAMEPAD *gamepad)
+{
+    int buttons[] = {
+        XINPUT_GAMEPAD_A,
+        XINPUT_GAMEPAD_B,
+        XINPUT_GAMEPAD_X,
+        XINPUT_GAMEPAD_Y,
+        XINPUT_GAMEPAD_LEFT_SHOULDER,
+        XINPUT_GAMEPAD_RIGHT_SHOULDER,
+        XINPUT_GAMEPAD_BACK,
+        XINPUT_GAMEPAD_START,
+        0, /* xbox key not used */
+        XINPUT_GAMEPAD_LEFT_THUMB,
+        XINPUT_GAMEPAD_RIGHT_THUMB
+    }, i;
+
+    /* First the D-Pad which is recognized as a POV */
+    gamepad->wButtons = 0x0000;
+    switch (js->rgdwPOV[0])
+    {
+        case 0    : gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_UP; break;
+        case 4500 : gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_UP; /* fall through */
+        case 9000 : gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT; break;
+        case 13500: gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_RIGHT; /* fall through */
+        case 18000: gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_DOWN; break;
+        case 22500: gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_DOWN; /* fall through */
+        case 27000: gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_LEFT; break;
+        case 31500: gamepad->wButtons |= XINPUT_GAMEPAD_DPAD_LEFT | XINPUT_GAMEPAD_DPAD_UP;
+    }
+    /* Buttons */
+    for (i = 0; i < sizeof(buttons) / sizeof(*buttons); i++)
+        if (js->rgbButtons[i] & 0x80)
+            gamepad->wButtons |= buttons[i];
+
+    /* Both triggers */
+    gamepad->bLeftTrigger = (255 * js->lZ) / 65535;
+    gamepad->bRightTrigger = (255 * js->lRz) / 65535;
+
+    /* Axes */
+    gamepad->sThumbLX = js->lX - 0x7FFF;
+    gamepad->sThumbLY = -(js->lY - 0x7FFF);
+
+    gamepad->sThumbRX = js->lRx - 0x7FFF;
+    gamepad->sThumbRY = -(js->lRy - 0x7FFF);
+}
+
+static void dinput_fill_effect(DIEFFECT *effect)
+{
+    static DWORD axes[2] = {DIJOFS_X, DIJOFS_Y};
+    static LONG direction[2] = {0, 0};
+
+    effect->dwSize = sizeof(effect);
+    effect->dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
+    effect->dwDuration = INFINITE;
+    effect->dwGain = 0;
+    effect->dwTriggerButton = DIEB_NOTRIGGER;
+    effect->cAxes = sizeof(axes) / sizeof(axes[0]);
+    effect->rgdwAxes = axes;
+    effect->rglDirection = direction;
+}
+
+static void dinput_send_effect(int index, int power)
+{
+    HRESULT hr;
+    DIPERIODIC periodic;
+    DIEFFECT *effect = &controllers[index].effect_data;
+    LPDIRECTINPUTEFFECT *instance = &controllers[index].effect_instance;
+
+    if (!*instance)
+        dinput_fill_effect(effect);
+
+    effect->cbTypeSpecificParams  = sizeof(periodic);
+    effect->lpvTypeSpecificParams = &periodic;
+
+    periodic.dwMagnitude = power;
+    periodic.dwPeriod = DI_SECONDS; /* 1 second */
+    periodic.lOffset = 0;
+    periodic.dwPhase = 0;
+
+    if (!*instance)
+    {
+        hr = IDirectInputDevice8_CreateEffect(controllers[index].device, &GUID_Square,
+                                              effect, instance, NULL);
+        if (FAILED(hr))
+        {
+            WARN("Failed to create effect (0x%x)\n", hr);
+            return;
+        }
+        if (!*instance)
+        {
+            WARN("Effect not returned???\n");
+            return;
+        }
+
+        hr = IDirectInputEffect_SetParameters(*instance, effect, DIEP_AXES | DIEP_DIRECTION | DIEP_NODOWNLOAD);
+        if (FAILED(hr))
+        {
+            IUnknown_Release(*instance);
+            *instance = NULL;
+            WARN("Failed to configure effect (0x%x)\n", hr);
+            return;
+        }
+    }
+
+    hr = IDirectInputEffect_SetParameters(*instance, effect, DIEP_TYPESPECIFICPARAMS | DIEP_START);
+    if (FAILED(hr))
+    {
+        WARN("Failed to play effect (0x%x)\n", hr);
+        return;
+    }
+}
+
+static BOOL CALLBACK dinput_enum_callback(const DIDEVICEINSTANCEA *instance, void *context)
+{
+    LPDIRECTINPUTDEVICE8A device;
+    BOOL wired, jedi;
+    HRESULT hr;
+
+    if (dinput.mapped == sizeof(controllers) / sizeof(*controllers))
+        return DIENUM_STOP;
+
+    hr = IDirectInput_CreateDevice(dinput.iface, &instance->guidInstance, &device, NULL);
+    if (FAILED(hr))
+        return DIENUM_CONTINUE;
+
+    if (!dinput_is_good(device, &wired, &jedi))
+    {
+        IDirectInput_Release(device);
+        return DIENUM_CONTINUE;
+    }
+
+    controllers[dinput.mapped].connected = TRUE;
+    controllers[dinput.mapped].wired = wired;
+    controllers[dinput.mapped].jedi = jedi;
+    controllers[dinput.mapped].device = device;
+    dinput.mapped++;
+
+    return DIENUM_CONTINUE;
+}
+
+static void dinput_start(void)
+{
+    HRESULT hr;
+
+    if (!dinput.iface)
+    {
+        hr = DirectInput8Create(GetModuleHandleA(NULL), 0x0800, &IID_IDirectInput8A,
+                                (void **)&dinput.iface, NULL);
+        if (FAILED(hr))
+        {
+            ERR("Failed to create dinput8 interface, no xinput controller support (0x%x)\n", hr);
+            return;
+        }
+    }
+
+    hr = IDirectInput8_EnumDevices(dinput.iface, DI8DEVCLASS_GAMECTRL,
+                                   dinput_enum_callback, NULL, DIEDFL_ATTACHEDONLY);
+    if (FAILED(hr))
+    {
+        ERR("Failed to enumerate dinput8 devices, no xinput controller support (0x%x)\n", hr);
+        return;
+    }
+
+    dinput.enabled = TRUE;
+}
+
+static void dinput_update(int index)
+{
+    HRESULT hr;
+    DIJOYSTATE2 data;
+    XINPUT_GAMEPAD gamepad;
+
+    if (!controllers[index].acquired)
+    {
+        IDirectInputDevice8_SetDataFormat(controllers[index].device, &c_dfDIJoystick2);
+        hr = IDirectInputDevice8_Acquire(controllers[index].device);
+        if (FAILED(hr))
+        {
+            WARN("Failed to acquire game controller (0x%x)\n", hr);
+            return;
+        }
+        controllers[index].acquired = TRUE;
+    }
+
+    IDirectInputDevice8_Poll(controllers[index].device);
+    hr = IDirectInputDevice_GetDeviceState(controllers[index].device, sizeof(data), &data);
+    if (FAILED(hr))
+    {
+        if (hr == DIERR_INPUTLOST)
+            controllers[index].acquired = FALSE;
+        ERR("Failed to get game controller state (0x%x)\n", hr);
+        return;
+    }
+
+    dinput_joystate_to_xinput(&data, &gamepad);
+    if (memcmp(&controllers[index].state.Gamepad, &gamepad, sizeof(gamepad)))
+    {
+        controllers[index].state.Gamepad = gamepad;
+        controllers[index].state.dwPacketNumber++;
+    }
+}
+
+/* ============================ Dll Functions =============================== */
+
 BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved)
 {
     switch(reason)
@@ -46,87 +331,160 @@ BOOL WINAPI DllMain(HINSTANCE inst, DWORD reason, LPVOID reserved)
 
 void WINAPI XInputEnable(BOOL enable)
 {
-    /* Setting to false will stop messages from XInputSetState being sent
-    to the controllers. Setting to true will send the last vibration
-    value (sent to XInputSetState) to the controller and allow messages to
-    be sent */
-    FIXME("(%d) Stub!\n", enable);
+    TRACE("(%d)\n", enable);
+
+    STARTUP_DINPUT
+
+    dinput.enabled = enable;
+    if(dinput.enabled)
+    {
+        int i;
+        /* Apply the last vibration status that was sent to the controller 
+         * while xinput was disabled. */
+        for (i = 0; i < sizeof(controllers) / sizeof(*controllers); i++)
+        {
+            if (controllers[i].connected && controllers[i].vibration_dirty)
+                XInputSetState(i, &controllers[i].vibration);
+        }
+    }
 }
 
-DWORD WINAPI XInputSetState(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration)
+DWORD WINAPI XInputSetState(DWORD index, XINPUT_VIBRATION* vibration)
 {
-    FIXME("(%d %p) Stub!\n", dwUserIndex, pVibration);
+    TRACE("(%u %p)\n", index, vibration);
 
-    if (dwUserIndex < XUSER_MAX_COUNT)
-    {
+    STARTUP_DINPUT
+
+    if (index >= XUSER_MAX_COUNT)
+        return ERROR_BAD_ARGUMENTS;
+    if (!controllers[index].connected)
         return ERROR_DEVICE_NOT_CONNECTED;
-        /* If controller exists then return ERROR_SUCCESS */
+
+    /* Check if we really have to do all the process */
+    if (!controllers[index].vibration_dirty &&
+        !memcmp(&controllers[index].vibration, vibration, sizeof(*vibration)))
+        return ERROR_SUCCESS;
+
+    controllers[index].vibration = *vibration;
+    controllers[index].vibration_dirty = !dinput.enabled;
+
+    if (dinput.enabled && controllers[index].jedi)
+    {
+        int power;
+        /* FIXME: we can't set the speed of each motor so do an average */
+        power = DI_FFNOMINALMAX * (vibration->wLeftMotorSpeed + vibration->wRightMotorSpeed) / 2 / 0xFFFF;
+
+        TRACE("Vibration left/right speed %d/%d translated to %d\n\n",
+              vibration->wLeftMotorSpeed, vibration->wRightMotorSpeed, power);
+        dinput_send_effect(index, power);
     }
-    return ERROR_BAD_ARGUMENTS;
+
+    return ERROR_SUCCESS;
 }
 
-DWORD WINAPI DECLSPEC_HOTPATCH XInputGetState(DWORD dwUserIndex, XINPUT_STATE* pState)
+DWORD WINAPI DECLSPEC_HOTPATCH XInputGetState(DWORD index, XINPUT_STATE* state)
 {
-    static int warn_once;
+    TRACE("(%u %p)\n", index, state);
 
-    if (!warn_once++)
-        FIXME("(%u %p)\n", dwUserIndex, pState);
+    STARTUP_DINPUT
 
-    if (dwUserIndex < XUSER_MAX_COUNT)
-    {
+    if (index >= XUSER_MAX_COUNT)
+        return ERROR_BAD_ARGUMENTS;
+    if (!controllers[index].connected)
         return ERROR_DEVICE_NOT_CONNECTED;
-        /* If controller exists then return ERROR_SUCCESS */
-    }
-    return ERROR_BAD_ARGUMENTS;
+
+    dinput_update(index);
+    *state = controllers[index].state;
+
+    return ERROR_SUCCESS;
 }
 
-DWORD WINAPI XInputGetKeystroke(DWORD dwUserIndex, DWORD dwReserve, PXINPUT_KEYSTROKE pKeystroke)
+DWORD WINAPI XInputGetKeystroke(DWORD index, DWORD reserved, PXINPUT_KEYSTROKE key)
 {
-    FIXME("(%d %d %p) Stub!\n", dwUserIndex, dwReserve, pKeystroke);
+    TRACE("(%u %d %p) stub!\n", index, reserved, key);
 
-    if (dwUserIndex < XUSER_MAX_COUNT)
-    {
+    STARTUP_DINPUT
+
+    if (index >= XUSER_MAX_COUNT)
+        return ERROR_BAD_ARGUMENTS;
+    if (!controllers[index].connected)
         return ERROR_DEVICE_NOT_CONNECTED;
-        /* If controller exists then return ERROR_SUCCESS */
-    }
-    return ERROR_BAD_ARGUMENTS;
+
+    return ERROR_NOT_SUPPORTED;
 }
 
-DWORD WINAPI XInputGetCapabilities(DWORD dwUserIndex, DWORD dwFlags, XINPUT_CAPABILITIES* pCapabilities)
+/* Not defined anywhere ??? */
+#define XINPUT_CAPS_FFB_SUPPORTED 1
+#define XINPUT_CAPS_WIRELESS 2
+
+DWORD WINAPI XInputGetCapabilities(DWORD index, DWORD flags, XINPUT_CAPABILITIES* capabilities)
 {
-    static int warn_once;
+    TRACE("(%u %d %p)\n", index, flags, capabilities);
 
-    if (!warn_once++)
-        FIXME("(%d %d %p)\n", dwUserIndex, dwFlags, pCapabilities);
+    STARTUP_DINPUT
 
-    if (dwUserIndex < XUSER_MAX_COUNT)
-    {
+    if (index >= XUSER_MAX_COUNT || (flags && (flags & ~XINPUT_FLAG_GAMEPAD)))
+        return ERROR_BAD_ARGUMENTS;
+    if (!controllers[index].connected)
         return ERROR_DEVICE_NOT_CONNECTED;
-        /* If controller exists then return ERROR_SUCCESS */
-    }
-    return ERROR_BAD_ARGUMENTS;
+
+    memset(capabilities, 0, sizeof(*capabilities));
+    capabilities->Type = XINPUT_DEVTYPE_GAMEPAD;
+    capabilities->SubType = XINPUT_DEVSUBTYPE_GAMEPAD; 
+
+    capabilities->Flags = XINPUT_CAPS_FFB_SUPPORTED;
+    if (!controllers[index].wired)
+        capabilities->Flags |= XINPUT_CAPS_WIRELESS;
+
+    dinput_update(index);
+
+    capabilities->Vibration = controllers[index].vibration;
+    capabilities->Gamepad = controllers[index].state.Gamepad;
+
+    return ERROR_SUCCESS;
 }
 
-DWORD WINAPI XInputGetDSoundAudioDeviceGuids(DWORD dwUserIndex, GUID* pDSoundRenderGuid, GUID* pDSoundCaptureGuid)
+DWORD WINAPI XInputGetDSoundAudioDeviceGuids(DWORD index, GUID* dsound_render_guid, GUID* dsound_capture_guid)
 {
-    FIXME("(%d %p %p) Stub!\n", dwUserIndex, pDSoundRenderGuid, pDSoundCaptureGuid);
+    TRACE("(%u %p %p) Stub!\n", index, dsound_render_guid, dsound_capture_guid);
 
-    if (dwUserIndex < XUSER_MAX_COUNT)
-    {
+    STARTUP_DINPUT
+
+    if (index >= XUSER_MAX_COUNT)
+        return ERROR_BAD_ARGUMENTS;
+    if (!controllers[index].connected)
         return ERROR_DEVICE_NOT_CONNECTED;
-        /* If controller exists then return ERROR_SUCCESS */
-    }
-    return ERROR_BAD_ARGUMENTS;
+
+    return ERROR_NOT_SUPPORTED;
 }
 
-DWORD WINAPI XInputGetBatteryInformation(DWORD dwUserIndex, BYTE deviceType, XINPUT_BATTERY_INFORMATION* pBatteryInfo)
+DWORD WINAPI XInputGetBatteryInformation(DWORD index, BYTE type, XINPUT_BATTERY_INFORMATION* battery)
 {
-    FIXME("(%d %u %p) Stub!\n", dwUserIndex, deviceType, pBatteryInfo);
+    TRACE("(%u %u %p) Stub!\n", index, type, battery);
 
-    if (dwUserIndex < XUSER_MAX_COUNT)
-    {
+    STARTUP_DINPUT
+
+    if (index >= XUSER_MAX_COUNT)
+        return ERROR_BAD_ARGUMENTS;
+    if (!controllers[index].connected)
         return ERROR_DEVICE_NOT_CONNECTED;
-        /* If controller exists then return ERROR_SUCCESS */
+    if (type != BATTERY_DEVTYPE_GAMEPAD && type != BATTERY_DEVTYPE_HEADSET)
+        return ERROR_BAD_ARGUMENTS;
+
+    if (controllers[index].wired)
+    {
+        battery->BatteryType = BATTERY_TYPE_WIRED;
+        battery->BatteryLevel = BATTERY_LEVEL_FULL;
     }
-    return ERROR_BAD_ARGUMENTS;
+    else
+    {
+        static int once;
+        if (!once++)
+            FIXME("Reporting fake battery values\n");
+
+        battery->BatteryType = BATTERY_TYPE_NIMH;
+        battery->BatteryLevel = BATTERY_LEVEL_MEDIUM;
+    }
+
+    return ERROR_SUCCESS;
 }


More information about the wine-devel mailing list