diff --git a/src/win/win_joystick_rawinput.c b/src/win/win_joystick_rawinput.c
new file mode 100644
index 000000000..91ef6989f
--- /dev/null
+++ b/src/win/win_joystick_rawinput.c
@@ -0,0 +1,472 @@
+/*
+ * 86Box A hypervisor and IBM PC system emulator that specializes in
+ * running old operating systems and software designed for IBM
+ * PC systems and compatibles from 1981 through fairly recent
+ * system designs based on the PCI bus.
+ *
+ * This file is part of the 86Box distribution.
+ *
+ * RawInput joystick interface.
+ *
+ * Version: @(#)win_joystick_rawinput.c 1.0.0 2020/1/19
+ *
+ * Authors: Sarah Walker,
+ * Miran Grca,
+ * GH Cao,
+ *
+ * Copyright 2008-2018 Sarah Walker.
+ * Copyright 2016-2018 Miran Grca.
+ * Copyright 2020 GH Cao.
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#define HAVE_STDARG_H
+#include "../86box.h"
+#include "../device.h"
+#include "../plat.h"
+#include "../game/gameport.h"
+#include "win.h"
+
+#ifdef ENABLE_JOYSTICK_LOG
+int joystick_do_log = ENABLE_JOYSTICK_LOG;
+
+
+static void
+joystick_log(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (joystick_do_log) {
+ va_start(ap, fmt);
+ pclog_ex(fmt, ap);
+ va_end(ap);
+ }
+}
+#else
+#define joystick_log(fmt, ...)
+#endif
+
+typedef struct {
+ HANDLE hdevice;
+ PHIDP_PREPARSED_DATA data;
+
+ USAGE usage_button[128];
+
+ struct raw_axis_t {
+ USAGE usage;
+ USHORT link;
+ USHORT bitsize;
+ LONG max;
+ LONG min;
+ } axis[8];
+
+ struct raw_pov_t {
+ USAGE usage;
+ USHORT link;
+ LONG max;
+ LONG min;
+ } pov[4];
+} raw_joystick_t;
+
+plat_joystick_t plat_joystick_state[MAX_PLAT_JOYSTICKS];
+joystick_t joystick_state[MAX_JOYSTICKS];
+int joysticks_present = 0;
+
+raw_joystick_t raw_joystick_state[MAX_PLAT_JOYSTICKS];
+
+/* We only use the first 32 buttons reported, from Usage ID 1-128 */
+void joystick_add_button(raw_joystick_t* rawjoy, plat_joystick_t* joy, USAGE usage) {
+ if (joy->nr_buttons >= 32) return;
+ if (usage < 1 || usage > 128) return;
+
+ rawjoy->usage_button[usage] = joy->nr_buttons;
+ sprintf(joy->button[joy->nr_buttons].name, "Button %d", usage);
+ joy->nr_buttons++;
+}
+
+void joystick_add_axis(raw_joystick_t* rawjoy, plat_joystick_t* joy, PHIDP_VALUE_CAPS prop) {
+ if (joy->nr_axes >= 8) return;
+
+ switch (prop->Range.UsageMin) {
+ case HID_USAGE_GENERIC_X:
+ sprintf(joy->axis[joy->nr_axes].name, "X");
+ break;
+ case HID_USAGE_GENERIC_Y:
+ sprintf(joy->axis[joy->nr_axes].name, "Y");
+ break;
+ case HID_USAGE_GENERIC_Z:
+ sprintf(joy->axis[joy->nr_axes].name, "Z");
+ break;
+ case HID_USAGE_GENERIC_RX:
+ sprintf(joy->axis[joy->nr_axes].name, "RX");
+ break;
+ case HID_USAGE_GENERIC_RY:
+ sprintf(joy->axis[joy->nr_axes].name, "RY");
+ break;
+ case HID_USAGE_GENERIC_RZ:
+ sprintf(joy->axis[joy->nr_axes].name, "RZ");
+ break;
+ default:
+ return;
+ }
+
+ joy->axis[joy->nr_axes].id = joy->nr_axes;
+ rawjoy->axis[joy->nr_axes].usage = prop->Range.UsageMin;
+ rawjoy->axis[joy->nr_axes].link = prop->LinkCollection;
+ rawjoy->axis[joy->nr_axes].bitsize = prop->BitSize;
+
+ /* Assume unsigned when min >= 0 */
+ if (prop->LogicalMin < 0) {
+ rawjoy->axis[joy->nr_axes].max = prop->LogicalMax;
+ } else {
+ /*
+ * Some joysticks will send -1 in LogicalMax, like Xbox Controllers
+ * so we need to mask that to appropriate value (instead of 0xFFFFFFFF)
+ */
+ rawjoy->axis[joy->nr_axes].max = prop->LogicalMax & ((1 << prop->BitSize) - 1);
+ }
+ rawjoy->axis[joy->nr_axes].min = prop->LogicalMin;
+
+ joy->nr_axes++;
+}
+
+void joystick_add_pov(raw_joystick_t* rawjoy, plat_joystick_t* joy, PHIDP_VALUE_CAPS prop) {
+ if (joy->nr_povs >= 4) return;
+
+ sprintf(joy->pov[joy->nr_povs].name, "POV %d", joy->nr_povs+1);
+ rawjoy->pov[joy->nr_povs].usage = prop->Range.UsageMin;
+ rawjoy->pov[joy->nr_povs].link = prop->LinkCollection;
+ rawjoy->pov[joy->nr_povs].min = prop->LogicalMin;
+ rawjoy->pov[joy->nr_povs].max = prop->LogicalMax;
+
+ joy->nr_povs++;
+}
+
+void joystick_get_capabilities(raw_joystick_t* rawjoy, plat_joystick_t* joy) {
+ UINT size = 0;
+ PHIDP_BUTTON_CAPS btn_caps = NULL;
+ PHIDP_VALUE_CAPS val_caps = NULL;
+
+ /* Get preparsed data (HID data format) */
+ GetRawInputDeviceInfoW(rawjoy->hdevice, RIDI_PREPARSEDDATA, NULL, &size);
+ rawjoy->data = malloc(size);
+ if (GetRawInputDeviceInfoW(rawjoy->hdevice, RIDI_PREPARSEDDATA, rawjoy->data, &size) <= 0)
+ fatal("joystick_get_capabilities: Failed to get preparsed data.\n");
+
+ HIDP_CAPS caps;
+ HidP_GetCaps(rawjoy->data, &caps);
+
+ /* Buttons */
+ if (caps.NumberInputButtonCaps > 0) {
+ btn_caps = calloc(caps.NumberInputButtonCaps, sizeof(HIDP_BUTTON_CAPS));
+ if (HidP_GetButtonCaps(HidP_Input, btn_caps, &caps.NumberInputButtonCaps, rawjoy->data) != HIDP_STATUS_SUCCESS) {
+ joystick_log("joystick_get_capabilities: Failed to query input buttons.\n");
+ goto end;
+ }
+ /* We only detect generic stuff */
+ for (int c=0; c 0) {
+ val_caps = calloc(caps.NumberInputValueCaps, sizeof(HIDP_VALUE_CAPS));
+ if (HidP_GetValueCaps(HidP_Input, val_caps, &caps.NumberInputValueCaps, rawjoy->data) != HIDP_STATUS_SUCCESS) {
+ joystick_log("joystick_get_capabilities: Failed to query axes and povs.\n");
+ goto end;
+ }
+ /* We only detect generic stuff */
+ for (int c=0; chdevice, RIDI_DEVICENAME, device_name, &size);
+ device_name = calloc(size, sizeof(char));
+ if (GetRawInputDeviceInfoA(rawjoy->hdevice, RIDI_DEVICENAME, device_name, &size) <= 0)
+ fatal("joystick_get_capabilities: Failed to get device name.\n");
+
+ HANDLE hDevObj = CreateFile(device_name, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
+ if (hDevObj) {
+ HidD_GetProductString(hDevObj, device_desc_wide, sizeof(WCHAR) * 200);
+ CloseHandle(hDevObj);
+ }
+ free(device_name);
+
+ int result = WideCharToMultiByte(CP_ACP, 0, device_desc_wide, 200, joy->name, 260, NULL, NULL);
+ if (result == 0 || strlen(joy->name) == 0)
+ sprintf(joy->name,
+ "RawInput %s, VID:%04lX PID:%04lX",
+ info->hid.usUsage == HID_USAGE_GENERIC_JOYSTICK ? "Joystick" : "Gamepad",
+ info->hid.dwProductId,
+ info->hid.dwVendorId);
+}
+
+void joystick_init()
+{
+ UINT size = 0;
+ atexit(joystick_close);
+
+ joysticks_present = 0;
+ memset(raw_joystick_state, 0, sizeof(raw_joystick_t) * MAX_PLAT_JOYSTICKS);
+
+ /* Get a list of raw input devices from Windows */
+ UINT raw_devices = 0;
+ GetRawInputDeviceList(NULL, &raw_devices, sizeof(RAWINPUTDEVICELIST));
+ PRAWINPUTDEVICELIST deviceList = calloc(raw_devices, sizeof(RAWINPUTDEVICELIST));
+ GetRawInputDeviceList(deviceList, &raw_devices, sizeof(RAWINPUTDEVICELIST));
+
+ for (int i=0; i= MAX_PLAT_JOYSTICKS) break;
+ if (deviceList[i].dwType != RIM_TYPEHID) continue;
+
+ /* Get device info: hardware IDs and usage IDs */
+ GetRawInputDeviceInfoA(deviceList[i].hDevice, RIDI_DEVICEINFO, NULL, &size);
+ info = malloc(size);
+ info->cbSize = sizeof(RID_DEVICE_INFO);
+ if (GetRawInputDeviceInfoA(deviceList[i].hDevice, RIDI_DEVICEINFO, info, &size) <= 0)
+ goto end_loop;
+
+ /* If this is not a joystick/gamepad, skip */
+ if (info->hid.usUsagePage != HID_USAGE_PAGE_GENERIC) goto end_loop;
+ if (info->hid.usUsage != HID_USAGE_GENERIC_JOYSTICK &&
+ info->hid.usUsage != HID_USAGE_GENERIC_GAMEPAD) goto end_loop;
+
+ plat_joystick_t *joy = &plat_joystick_state[joysticks_present];
+ raw_joystick_t *rawjoy = &raw_joystick_state[joysticks_present];
+ rawjoy->hdevice = deviceList[i].hDevice;
+
+ joystick_get_capabilities(rawjoy, joy);
+ joystick_get_device_name(rawjoy, joy, info);
+
+ joystick_log("joystick_init: %s - %d buttons, %d axes, %d POVs\n",
+ joy->name, joy->nr_buttons, joy->nr_axes, joy->nr_povs);
+
+ joysticks_present++;
+
+ end_loop:
+ free(info);
+ }
+
+ joystick_log("joystick_init: joysticks_present=%i\n", joysticks_present);
+
+ /* Initialize the RawInput (joystick and gamepad) module. */
+ RAWINPUTDEVICE ridev[2];
+ ridev[0].dwFlags = 0;
+ ridev[0].hwndTarget = NULL;
+ ridev[0].usUsagePage = HID_USAGE_PAGE_GENERIC;
+ ridev[0].usUsage = HID_USAGE_GENERIC_JOYSTICK;
+
+ ridev[1].dwFlags = 0;
+ ridev[1].hwndTarget = NULL;
+ ridev[1].usUsagePage = HID_USAGE_PAGE_GENERIC;
+ ridev[1].usUsage = HID_USAGE_GENERIC_GAMEPAD;
+
+ if (!RegisterRawInputDevices(ridev, 2, sizeof(RAWINPUTDEVICE)))
+ fatal("plat_joystick_init: RegisterRawInputDevices failed\n");
+}
+
+void joystick_close()
+{
+ RAWINPUTDEVICE ridev[2];
+ ridev[0].dwFlags = RIDEV_REMOVE;
+ ridev[0].hwndTarget = NULL;
+ ridev[0].usUsagePage = HID_USAGE_PAGE_GENERIC;
+ ridev[0].usUsage = HID_USAGE_GENERIC_JOYSTICK;
+
+ ridev[1].dwFlags = RIDEV_REMOVE;
+ ridev[1].hwndTarget = NULL;
+ ridev[1].usUsagePage = HID_USAGE_PAGE_GENERIC;
+ ridev[1].usUsage = HID_USAGE_GENERIC_GAMEPAD;
+
+ RegisterRawInputDevices(ridev, 2, sizeof(RAWINPUTDEVICE));
+}
+
+
+void win_joystick_handle(PRAWINPUT raw)
+{
+ HRESULT r;
+ int j = -1; /* current joystick index, -1 when not found */
+
+ /* If the input is not from a known device, we ignore it */
+ for (int i=0; iheader.hDevice) {
+ j = i;
+ break;
+ }
+ }
+ if (j == -1) return;
+
+ /* Read buttons */
+ USAGE usage_list[128] = {0};
+ ULONG usage_length = plat_joystick_state[j].nr_buttons;
+ memset(plat_joystick_state[j].b, 0, 32 * sizeof(int));
+
+ r = HidP_GetUsages(HidP_Input, HID_USAGE_PAGE_BUTTON, 0, usage_list, &usage_length,
+ raw_joystick_state[j].data, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid);
+
+ if (r == HIDP_STATUS_SUCCESS) {
+ for (int i=0; imax - axis->min + 1) / 2;
+
+ r = HidP_GetUsageValue(HidP_Input, HID_USAGE_PAGE_GENERIC, axis->link, axis->usage, &uvalue,
+ raw_joystick_state[j].data, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid);
+
+ if (r == HIDP_STATUS_SUCCESS) {
+ if (axis->min < 0) {
+ /* extend signed uvalue to LONG */
+ if (uvalue & (1 << (axis->bitsize-1))) {
+ ULONG mask = (1 << axis->bitsize) - 1;
+ value = -1U ^ mask;
+ value |= uvalue;
+ } else {
+ value = uvalue;
+ }
+ } else {
+ /* Assume unsigned when min >= 0, convert to a signed value */
+ value = (LONG)uvalue - center;
+ }
+ if (abs(value) == 1) value = 0;
+ value = value * 32768 / center;
+ }
+
+ plat_joystick_state[j].a[a] = value;
+ //joystick_log("%s %-06d ", plat_joystick_state[j].axis[a].name, plat_joystick_state[j].a[a]);
+ }
+
+ /* read povs */
+ for (int p=0; plink, pov->usage, &uvalue,
+ raw_joystick_state[j].data, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid);
+
+ if (r == HIDP_STATUS_SUCCESS && (uvalue >= pov->min && uvalue <= pov->max)) {
+ value = (uvalue - pov->min) * 36000;
+ value /= (pov->max - pov->min + 1);
+ value %= 36000;
+ }
+
+ plat_joystick_state[j].p[p] = value;
+
+ //joystick_log("%s %-3d ", plat_joystick_state[j].pov[p].name, plat_joystick_state[j].p[p]);
+
+ }
+ //joystick_log("\n");
+}
+
+
+static int joystick_get_axis(int joystick_nr, int mapping)
+{
+ if (mapping & POV_X)
+ {
+ int pov = plat_joystick_state[joystick_nr].p[mapping & 3];
+ if (LOWORD(pov) == 0xFFFF)
+ return 0;
+ else
+ return sin((2*M_PI * (double)pov) / 36000.0) * 32767;
+ }
+ else if (mapping & POV_Y)
+ {
+ int pov = plat_joystick_state[joystick_nr].p[mapping & 3];
+
+ if (LOWORD(pov) == 0xFFFF)
+ return 0;
+ else
+ return -cos((2*M_PI * (double)pov) / 36000.0) * 32767;
+ }
+ else
+ return plat_joystick_state[joystick_nr].a[plat_joystick_state[joystick_nr].axis[mapping].id];
+
+}
+
+
+void joystick_process(void)
+{
+ int c, d;
+
+ if (joystick_type == 7) return;
+
+ for (c = 0; c < joystick_get_max_joysticks(joystick_type); c++)
+ {
+ if (joystick_state[c].plat_joystick_nr)
+ {
+ int joystick_nr = joystick_state[c].plat_joystick_nr - 1;
+
+ for (d = 0; d < joystick_get_axis_count(joystick_type); d++)
+ joystick_state[c].axis[d] = joystick_get_axis(joystick_nr, joystick_state[c].axis_mapping[d]);
+ for (d = 0; d < joystick_get_button_count(joystick_type); d++)
+ joystick_state[c].button[d] = plat_joystick_state[joystick_nr].b[joystick_state[c].button_mapping[d]];
+
+ for (d = 0; d < joystick_get_pov_count(joystick_type); d++)
+ {
+ int x, y;
+ double angle, magnitude;
+
+ x = joystick_get_axis(joystick_nr, joystick_state[c].pov_mapping[d][0]);
+ y = joystick_get_axis(joystick_nr, joystick_state[c].pov_mapping[d][1]);
+
+ angle = (atan2((double)y, (double)x) * 360.0) / (2*M_PI);
+ magnitude = sqrt((double)x*(double)x + (double)y*(double)y);
+
+ if (magnitude < 16384)
+ joystick_state[c].pov[d] = -1;
+ else
+ joystick_state[c].pov[d] = ((int)angle + 90 + 360) % 360;
+ }
+ }
+ else
+ {
+ for (d = 0; d < joystick_get_axis_count(joystick_type); d++)
+ joystick_state[c].axis[d] = 0;
+ for (d = 0; d < joystick_get_button_count(joystick_type); d++)
+ joystick_state[c].button[d] = 0;
+ for (d = 0; d < joystick_get_pov_count(joystick_type); d++)
+ joystick_state[c].pov[d] = -1;
+ }
+ }
+}
+