From bce769d9179ffbcbc634e73ad124528e28c5c7a8 Mon Sep 17 00:00:00 2001
From: Jetro Jormalainen <jje-wine@jv.jetro.fi>
Date: Tue, 30 Apr 2019 09:20:54 +1000
Subject: [PATCH] dinput: Allow empty Joystick mappings.

---
 dlls/dinput/device.c        | 82 ++++++++++++++++++++++++++++++-------
 dlls/dinput/joystick.c      |  4 +-
 dlls/dinput/keyboard.c      |  2 +
 dlls/dinput/mouse.c         |  2 +
 dlls/dinput8/tests/device.c | 50 ++++++++++++++++++++++
 5 files changed, 125 insertions(+), 15 deletions(-)

diff --git a/dlls/dinput/device.c b/dlls/dinput/device.c
index d1bf934a827..6107ab26538 100644
--- a/dlls/dinput/device.c
+++ b/dlls/dinput/device.c
@@ -30,6 +30,7 @@
 #include <string.h>
 #include "wine/debug.h"
 #include "wine/unicode.h"
+#include "wine/heap.h"
 #include "windef.h"
 #include "winbase.h"
 #include "winreg.h"
@@ -643,12 +644,29 @@ static DWORD semantic_to_obj_id(IDirectInputDeviceImpl* This, DWORD dwSemantic)
     return type | (0x0000ff00 & (obj_instance << 8));
 }
 
+static void del_mapping_key(const WCHAR *device, const WCHAR *username, const WCHAR *guid) {
+    static const WCHAR subkey[] = {
+        'S','o','f','t','w','a','r','e','\\',
+        'W','i','n','e','\\',
+        'D','i','r','e','c','t','I','n','p','u','t','\\',
+        'M','a','p','p','i','n','g','s','\\','%','s','\\','%','s','\\','%','s','\0'};
+    WCHAR *keyname;
+
+    keyname = heap_alloc(sizeof(WCHAR) * (lstrlenW(subkey) + strlenW(username) + strlenW(device) + strlenW(guid)));
+    sprintfW(keyname, subkey, username, device, guid);
+
+    /* Remove old key mappings so there will be no overlapping mappings */
+    RegDeleteKeyW(HKEY_CURRENT_USER, keyname);
+
+    heap_free(keyname);
+}
+
 /*
  * get_mapping_key
  * Retrieves an open registry key to save the mapping, parametrized for an username,
  * specific device and specific action mapping guid.
  */
-static HKEY get_mapping_key(const WCHAR *device, const WCHAR *username, const WCHAR *guid)
+static HKEY get_mapping_key(const WCHAR *device, const WCHAR *username, const WCHAR *guid, BOOL create)
 {
     static const WCHAR subkey[] = {
         'S','o','f','t','w','a','r','e','\\',
@@ -663,8 +681,11 @@ static HKEY get_mapping_key(const WCHAR *device, const WCHAR *username, const WC
     sprintfW(keyname, subkey, username, device, guid);
 
     /* The key used is HKCU\Software\Wine\DirectInput\Mappings\[username]\[device]\[mapping_guid] */
-    if (RegCreateKeyW(HKEY_CURRENT_USER, keyname, &hkey))
-        hkey = 0;
+    if (create) {
+        if (RegCreateKeyW(HKEY_CURRENT_USER, keyname, &hkey))
+            hkey = 0;
+    } else if (RegOpenKeyW(HKEY_CURRENT_USER, keyname, &hkey))
+            hkey = 0;
 
     HeapFree(GetProcessHeap(), 0, keyname);
 
@@ -684,7 +705,9 @@ static HRESULT save_mapping_settings(IDirectInputDevice8W *iface, LPDIACTIONFORM
     if (StringFromCLSID(&lpdiaf->guidActionMap, &guid_str) != S_OK)
         return DI_SETTINGSNOTSAVED;
 
-    hkey = get_mapping_key(didev.tszInstanceName, lpszUsername, guid_str);
+    del_mapping_key(didev.tszInstanceName, lpszUsername, guid_str);
+
+    hkey = get_mapping_key(didev.tszInstanceName, lpszUsername, guid_str, TRUE);
 
     if (!hkey)
     {
@@ -719,7 +742,7 @@ BOOL load_mapping_settings(IDirectInputDeviceImpl *This, LPDIACTIONFORMATW lpdia
     HKEY hkey;
     WCHAR *guid_str;
     DIDEVICEINSTANCEW didev;
-    int i, mapped = 0;
+    int i;
 
     didev.dwSize = sizeof(didev);
     IDirectInputDevice8_GetDeviceInfo(&This->IDirectInputDevice8W_iface, &didev);
@@ -727,7 +750,7 @@ BOOL load_mapping_settings(IDirectInputDeviceImpl *This, LPDIACTIONFORMATW lpdia
     if (StringFromCLSID(&lpdiaf->guidActionMap, &guid_str) != S_OK)
         return FALSE;
 
-    hkey = get_mapping_key(didev.tszInstanceName, username, guid_str);
+    hkey = get_mapping_key(didev.tszInstanceName, username, guid_str, FALSE);
 
     if (!hkey)
     {
@@ -748,15 +771,21 @@ BOOL load_mapping_settings(IDirectInputDeviceImpl *This, LPDIACTIONFORMATW lpdia
         {
             lpdiaf->rgoAction[i].dwObjID = id;
             lpdiaf->rgoAction[i].guidInstance = didev.guidInstance;
-            lpdiaf->rgoAction[i].dwHow = DIAH_DEFAULT;
-            mapped += 1;
+            lpdiaf->rgoAction[i].dwHow = DIAH_USERCONFIG;
+        }
+        else
+        {
+            memset(&lpdiaf->rgoAction[i].guidInstance, 0, sizeof(GUID));
+            lpdiaf->rgoAction[i].dwHow = DIAH_UNMAPPED;
         }
+
     }
 
     RegCloseKey(hkey);
     CoTaskMemFree(guid_str);
 
-    return mapped > 0;
+    /* On Windows BuildActionMap can open empty mapping, so always return TRUE if get_mapping_key is success */
+    return TRUE;
 }
 
 HRESULT _build_action_map(LPDIRECTINPUTDEVICE8W iface, LPDIACTIONFORMATW lpdiaf, LPCWSTR lpszUserName, DWORD dwFlags, DWORD devMask, LPCDIDATAFORMAT df)
@@ -779,13 +808,18 @@ HRESULT _build_action_map(LPDIRECTINPUTDEVICE8W iface, LPDIACTIONFORMATW lpdiaf,
         load_success = load_mapping_settings(This, lpdiaf, username);
     }
 
-    if (load_success) return DI_OK;
+    if (load_success) {
+        /* Update dwCRC to track if action format has changed */
+        for (i=0; i < lpdiaf->dwNumActions; i++)
+        {
+            lpdiaf->dwCRC ^= (lpdiaf->rgoAction[i].dwObjID << i * 2) | (lpdiaf->rgoAction[i].dwObjID >> (sizeof(lpdiaf->dwCRC) * 8 - i * 2));
+            lpdiaf->dwCRC ^= (lpdiaf->rgoAction[i].dwSemantic << (i * 2 + 5)) | (lpdiaf->rgoAction[i].dwSemantic >> (sizeof(lpdiaf->dwCRC) * 8 - (i * 2 + 5)));
+        }
+        return DI_OK;
+    }
 
     for (i=0; i < lpdiaf->dwNumActions; i++)
     {
-        /* Don't touch a user configured action */
-        if (lpdiaf->rgoAction[i].dwHow == DIAH_USERCONFIG) continue;
-
         if ((lpdiaf->rgoAction[i].dwSemantic & devMask) == devMask)
         {
             DWORD obj_id = semantic_to_obj_id(This, lpdiaf->rgoAction[i].dwSemantic);
@@ -816,6 +850,14 @@ HRESULT _build_action_map(LPDIRECTINPUTDEVICE8W iface, LPDIACTIONFORMATW lpdiaf,
         }
     }
 
+    /* Update dwCRC to track if action format has changed */
+    lpdiaf->dwCRC = 0;
+    for (i=0; i < lpdiaf->dwNumActions; i++)
+    {
+        lpdiaf->dwCRC ^= (lpdiaf->rgoAction[i].dwObjID << i * 2) | (lpdiaf->rgoAction[i].dwObjID >> (sizeof(lpdiaf->dwCRC) * 8 - i * 2));
+        lpdiaf->dwCRC ^= (lpdiaf->rgoAction[i].dwSemantic << (i * 2 + 5)) | (lpdiaf->rgoAction[i].dwSemantic >> (sizeof(lpdiaf->dwCRC) * 8 - (i * 2 + 5)));
+    }
+
     if (!has_actions) return DI_NOEFFECT;
 
     return  IDirectInputDevice8WImpl_BuildActionMap(iface, lpdiaf, lpszUserName, dwFlags);
@@ -831,6 +873,7 @@ HRESULT _set_action_map(LPDIRECTINPUTDEVICE8W iface, LPDIACTIONFORMATW lpdiaf, L
     DIPROPSTRING dps;
     WCHAR username[MAX_PATH];
     DWORD username_size = MAX_PATH;
+    DWORD new_crc = 0;
     int i, action = 0, num_actions = 0;
     unsigned int offset = 0;
 
@@ -841,12 +884,23 @@ HRESULT _set_action_map(LPDIRECTINPUTDEVICE8W iface, LPDIACTIONFORMATW lpdiaf, L
     data_format.dwFlags = DIDF_RELAXIS;
     data_format.dwDataSize = lpdiaf->dwDataSize;
 
+    /* Calculate checksum for actionformat */
+    for (i=0; i < lpdiaf->dwNumActions; i++)
+    {
+        new_crc ^= (lpdiaf->rgoAction[i].dwObjID << i * 2) | (lpdiaf->rgoAction[i].dwObjID >> (sizeof(lpdiaf->dwCRC) * 8 - i * 2));
+        new_crc ^= (lpdiaf->rgoAction[i].dwSemantic << (i * 2 + 5)) | (lpdiaf->rgoAction[i].dwSemantic >> (sizeof(lpdiaf->dwCRC) * 8 - (i * 2 + 5)));
+    }
+
     /* Count the actions */
     for (i=0; i < lpdiaf->dwNumActions; i++)
         if (IsEqualGUID(&This->guid, &lpdiaf->rgoAction[i].guidInstance))
             num_actions++;
 
-    if (num_actions == 0) return DI_NOEFFECT;
+    /* Should return DI_NOEFFECT if we dont have any actions and actionformat has not changed */
+    if (num_actions == 0 && lpdiaf->dwCRC == new_crc && !(dwFlags & DIDSAM_FORCESAVE)) return DI_NOEFFECT;
+
+    /* update dwCRC to track if action format has changed */
+    lpdiaf->dwCRC = new_crc;
 
     This->num_actions = num_actions;
 
diff --git a/dlls/dinput/joystick.c b/dlls/dinput/joystick.c
index 66d6cfdffb9..73215dac0d5 100644
--- a/dlls/dinput/joystick.c
+++ b/dlls/dinput/joystick.c
@@ -847,7 +847,7 @@ HRESULT WINAPI JoystickWGenericImpl_BuildActionMap(LPDIRECTINPUTDEVICE8W iface,
         else
             lstrcpynW(username, lpszUserName, size);
 
-        load_success = load_mapping_settings((IDirectInputDeviceImpl *) This, lpdiaf, username);
+        load_success = load_mapping_settings(&This->base, lpdiaf, username);
         heap_free(username);
     }
 
@@ -957,6 +957,8 @@ HRESULT WINAPI JoystickAGenericImpl_SetActionMap(LPDIRECTINPUTDEVICE8A iface,
 
     hr = JoystickWGenericImpl_SetActionMap(&This->base.IDirectInputDevice8W_iface, &diafW, lpszUserNameW, dwFlags);
 
+    lpdiaf->dwCRC = diafW.dwCRC;
+
     HeapFree(GetProcessHeap(), 0, diafW.rgoAction);
     HeapFree(GetProcessHeap(), 0, lpszUserNameW);
 
diff --git a/dlls/dinput/keyboard.c b/dlls/dinput/keyboard.c
index b5e665933ec..0747f5d8c8b 100644
--- a/dlls/dinput/keyboard.c
+++ b/dlls/dinput/keyboard.c
@@ -690,6 +690,8 @@ static HRESULT WINAPI SysKeyboardAImpl_SetActionMap(LPDIRECTINPUTDEVICE8A iface,
 
     hr = SysKeyboardWImpl_SetActionMap(&This->base.IDirectInputDevice8W_iface, &diafW, lpszUserNameW, dwFlags);
 
+    lpdiaf->dwCRC = diafW.dwCRC;
+
     HeapFree(GetProcessHeap(), 0, diafW.rgoAction);
     HeapFree(GetProcessHeap(), 0, lpszUserNameW);
 
diff --git a/dlls/dinput/mouse.c b/dlls/dinput/mouse.c
index 52a766b2a1a..796f1fa2977 100644
--- a/dlls/dinput/mouse.c
+++ b/dlls/dinput/mouse.c
@@ -862,6 +862,8 @@ static HRESULT WINAPI SysMouseAImpl_SetActionMap(LPDIRECTINPUTDEVICE8A iface,
 
     hr = SysMouseWImpl_SetActionMap(&This->base.IDirectInputDevice8W_iface, &diafW, lpszUserNameW, dwFlags);
 
+    lpdiaf->dwCRC = diafW.dwCRC;
+
     HeapFree(GetProcessHeap(), 0, diafW.rgoAction);
     HeapFree(GetProcessHeap(), 0, lpszUserNameW);
 
diff --git a/dlls/dinput8/tests/device.c b/dlls/dinput8/tests/device.c
index f3e7b542355..bec2a6b863c 100644
--- a/dlls/dinput8/tests/device.c
+++ b/dlls/dinput8/tests/device.c
@@ -38,6 +38,8 @@ struct enum_data {
 /* Dummy GUID */
 static const GUID ACTION_MAPPING_GUID = { 0x1, 0x2, 0x3, { 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb } };
 
+static const GUID NULL_GUID = { 0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 } };
+
 enum {
     DITEST_AXIS,
     DITEST_BUTTON,
@@ -365,6 +367,17 @@ static void test_action_mapping(void)
         hr = IDirectInputDevice8_SetActionMap(data.keyboard, data.lpdiaf, NULL, 0);
         ok (hr == DI_NOEFFECT, "SetActionMap should have no effect with no actions to map hr=%08x\n", hr);
 
+        /* Test that after changing actionformat SetActionMap has effect and that second
+         * SetActionMap call with same empty actionformat has no effect */
+        af.dwDataSize = 4 * 1;
+        af.dwNumActions = 1;
+
+        hr = IDirectInputDevice8_SetActionMap(data.keyboard, data.lpdiaf, NULL, 0);
+        ok (hr != DI_NOEFFECT, "SetActionMap should have effect as actionformat has changed hr=%08x\n", hr);
+
+        hr = IDirectInputDevice8_SetActionMap(data.keyboard, data.lpdiaf, NULL, 0);
+        ok (hr == DI_NOEFFECT, "SetActionMap should have no effect with no actions to map hr=%08x\n", hr);
+
         af.dwDataSize = 4 * ARRAY_SIZE(actionMapping);
         af.dwNumActions = ARRAY_SIZE(actionMapping);
 
@@ -556,6 +569,43 @@ static void test_save_settings(void)
         "Mapped incorrectly expected: 0x%08x got: 0x%08x\n", other_results[1], af.rgoAction[1].dwObjID);
     ok (IsEqualGUID(&GUID_SysKeyboard, &af.rgoAction[1].guidInstance), "Action should be mapped to keyboard\n");
 
+    /* Save and load empty mapping */
+    af.rgoAction[0].dwObjID = 0;
+    af.rgoAction[0].dwHow = 0;
+    memset(&af.rgoAction[0].guidInstance, 0, sizeof(GUID));
+    af.rgoAction[1].dwObjID = 0;
+    af.rgoAction[1].dwHow = 0;
+    memset(&af.rgoAction[1].guidInstance, 0, sizeof(GUID));
+
+    hr = IDirectInputDevice8_SetActionMap(pKey, &af, NULL, DIDSAM_FORCESAVE);
+    ok (SUCCEEDED(hr), "SetActionMap failed hr=%08x\n", hr);
+
+    if (hr == DI_SETTINGSNOTSAVED)
+    {
+        skip ("Can't test saving settings if SetActionMap returns DI_SETTINGSNOTSAVED\n");
+        return;
+    }
+
+    af.rgoAction[0].dwObjID = other_results[0];
+    af.rgoAction[0].dwHow = DIAH_USERCONFIG;
+    af.rgoAction[0].guidInstance = GUID_SysKeyboard;
+    af.rgoAction[1].dwObjID = other_results[1];
+    af.rgoAction[1].dwHow = DIAH_USERCONFIG;
+    af.rgoAction[1].guidInstance = GUID_SysKeyboard;
+
+    hr = IDirectInputDevice8_BuildActionMap(pKey, &af, NULL, 0);
+    ok (SUCCEEDED(hr), "BuildActionMap failed hr=%08x\n", hr);
+
+    ok (other_results[0] == af.rgoAction[0].dwObjID,
+        "Mapped incorrectly expected: 0x%08x got: 0x%08x\n", other_results[0], af.rgoAction[0].dwObjID);
+    ok (af.rgoAction[0].dwHow == DIAH_UNMAPPED, "dwHow should have been DIAH_UNMAPPED\n");
+    ok (IsEqualGUID(&NULL_GUID, &af.rgoAction[0].guidInstance), "Action should not be mapped\n");
+
+    ok (other_results[1] == af.rgoAction[1].dwObjID,
+        "Mapped incorrectly expected: 0x%08x got: 0x%08x\n", other_results[1], af.rgoAction[1].dwObjID);
+    ok (af.rgoAction[1].dwHow == DIAH_UNMAPPED, "dwHow should have been DIAH_UNMAPPED\n");
+    ok (IsEqualGUID(&NULL_GUID, &af.rgoAction[1].guidInstance), "Action should not be mapped\n");
+
     IDirectInputDevice_Release(pKey);
     IDirectInput_Release(pDI);
 }
-- 
2.17.1

