diff -urN wine-5.15-orig/dlls/dinput/joystick.c wine-5.15/dlls/dinput/joystick.c
--- wine-5.15-orig/dlls/dinput/joystick.c	2020-08-15 06:37:58 +0900
+++ wine-5.15/dlls/dinput/joystick.c	2020-08-27 15:04:24 +0900
@@ -1029,6 +1029,45 @@
         TRACE("setting default deadzone to: \"%s\" %d\n", buffer, This->deadzone);
     }
 
+    if (!get_config_key(hkey, appkey, "ButtonMap", buffer, sizeof(buffer)))
+    {
+        const char *delim = ",";
+        int button = 0;
+        char *token;
+
+        TRACE("ButtonMap = \"%s\"\n", buffer);
+        for (token = strtok(buffer, delim);
+             token != NULL && button < MAX_MAP_BUTTONS;
+             token = strtok(NULL, delim), button++)
+        {
+            char *s;
+            int value = strtol(token, &s, 10);
+            if (value < 0 || *s != '\0')
+            {
+                ERR("invalid button number: \"%s\"", token);
+                value = -1;
+            }
+            else
+            {
+                TRACE("mapping physical button %d to DInput button %d",
+                      value, button);
+            }
+            This->button_map[button] = value;
+        }
+        for (; button < MAX_MAP_BUTTONS; button++)
+        {
+            This->button_map[button] = button;
+        }
+    }
+    else
+    {
+        int button;
+        for (button = 0; button < MAX_MAP_BUTTONS; button++)
+        {
+            This->button_map[button] = button;
+        }
+    }
+
     This->axis_map = HeapAlloc(GetProcessHeap(), 0, This->device_axis_count * sizeof(int));
     if (!This->axis_map) return DIERR_OUTOFMEMORY;
 
diff -urN wine-5.15-orig/dlls/dinput/joystick_linux.c wine-5.15/dlls/dinput/joystick_linux.c
--- wine-5.15-orig/dlls/dinput/joystick_linux.c	2020-08-15 06:37:58 +0900
+++ wine-5.15/dlls/dinput/joystick_linux.c	2020-08-27 15:04:24 +0900
@@ -869,10 +869,20 @@
               jse.type,jse.number,jse.value);
         if (jse.type & JS_EVENT_BUTTON)
         {
-            if (jse.number >= This->generic.devcaps.dwButtons) return;
+            int button = jse.number;
+            int i;
+            if (button >= This->generic.devcaps.dwButtons) return;
 
-            inst_id = DIDFT_MAKEINSTANCE(jse.number) | DIDFT_PSHBUTTON;
-            This->generic.js.rgbButtons[jse.number] = value = jse.value ? 0x80 : 0x00;
+            for (i = 0; i < MAX_MAP_BUTTONS; i++)
+            {
+                if (This->generic.button_map[i] == button)
+                {
+                    button = i;
+                    break;
+                }
+            }
+            inst_id = DIDFT_MAKEINSTANCE(button) | DIDFT_PSHBUTTON;
+            This->generic.js.rgbButtons[button] = value = jse.value ? 0x80 : 0x00;
         }
         else if (jse.type & JS_EVENT_AXIS)
         {
diff -urN wine-5.15-orig/dlls/dinput/joystick_linuxinput.c wine-5.15/dlls/dinput/joystick_linuxinput.c
--- wine-5.15-orig/dlls/dinput/joystick_linuxinput.c	2020-08-15 06:37:58 +0900
+++ wine-5.15/dlls/dinput/joystick_linuxinput.c	2020-08-27 15:04:24 +0900
@@ -842,7 +842,18 @@
             TRACE("(%p) %d -> %d\n", This, ie.code, btn);
             if (btn & 0x80)
             {
+                int i;
+
                 btn &= 0x7F;
+                for (i = 0; i < MAX_MAP_BUTTONS; i++)
+                {
+                    if (This->generic.button_map[i] == btn)
+                    {
+                        TRACE("(%p) remap: %d -> %d\n", This, btn, i);
+                        btn = i;
+                        break;
+                    }
+                }
                 inst_id = DIDFT_MAKEINSTANCE(btn) | DIDFT_PSHBUTTON;
                 This->generic.js.rgbButtons[btn] = value = ie.value ? 0x80 : 0x00;
             }
diff -urN wine-5.15-orig/dlls/dinput/joystick_osx.c wine-5.15/dlls/dinput/joystick_osx.c
--- wine-5.15-orig/dlls/dinput/joystick_osx.c	2020-08-15 06:37:58 +0900
+++ wine-5.15/dlls/dinput/joystick_osx.c	2020-08-27 15:04:24 +0900
@@ -819,6 +819,16 @@
                         TRACE("valueRef %s val %d oldVal %d newVal %d\n", debugstr_cf(valueRef), val, oldVal, newVal);
                         if (oldVal != newVal)
                         {
+                            int i;
+
+                            for (i = 0; i < MAX_MAP_BUTTONS; i++)
+                            {
+                                if (device->generic.button_map[i] == button_idx)
+                                {
+                                    button_idx = i;
+                                    break;
+                                }
+                            }
                             inst_id = DIDFT_MAKEINSTANCE(button_idx) | DIDFT_PSHBUTTON;
                             queue_event(iface,inst_id,newVal,GetCurrentTime(),device->generic.base.dinput->evsequence++);
                         }
diff -urN wine-5.15-orig/dlls/dinput/joystick_private.h wine-5.15/dlls/dinput/joystick_private.h
--- wine-5.15-orig/dlls/dinput/joystick_private.h	2020-08-15 06:37:58 +0900
+++ wine-5.15/dlls/dinput/joystick_private.h	2020-08-27 15:04:24 +0900
@@ -33,6 +33,9 @@
 #define MAX_PROPS 164
 struct JoystickGenericImpl;
 
+/* Number of buttons for which to allow remapping */
+#define MAX_MAP_BUTTONS 16
+
 typedef void joy_polldev_handler(LPDIRECTINPUTDEVICE8A iface);
 
 typedef struct JoystickGenericImpl
@@ -47,6 +50,7 @@
     char        *name;
     int         device_axis_count;      /* Total number of axes in the device */
     int        *axis_map;               /* User axes remapping */
+    int         button_map[MAX_MAP_BUTTONS]; /* User button remapping */
     LONG        deadzone;               /* Default dead-zone */
 
     joy_polldev_handler *joy_polldev;
