Currently only acting as a pass-through driver, matching any device with a WINEBUS\WINE_COMP_XINPUT compatible id.
This creates new PDO on the bus, adding the &IG_ device ID suffix to the original device ID (replacing an eventual &MI_ suffix), and removes the need to set the suffix on winebus.sys side.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- configure.ac | 1 + dlls/xinput.sys/Makefile.in | 8 + dlls/xinput.sys/main.c | 394 ++++++++++++++++++++++++++++++++ dlls/xinput.sys/xinput.inf | 22 ++ dlls/xinput.sys/xinput.rc | 20 ++ dlls/xinput.sys/xinput.sys.spec | 1 + loader/wine.inf.in | 1 + 7 files changed, 447 insertions(+) create mode 100644 dlls/xinput.sys/Makefile.in create mode 100644 dlls/xinput.sys/main.c create mode 100644 dlls/xinput.sys/xinput.inf create mode 100644 dlls/xinput.sys/xinput.rc create mode 100644 dlls/xinput.sys/xinput.sys.spec
diff --git a/configure.ac b/configure.ac index 7237289a2ad..7ff7869b37e 100644 --- a/configure.ac +++ b/configure.ac @@ -3895,6 +3895,7 @@ WINE_CONFIG_MAKEFILE(dlls/xaudio2_7) WINE_CONFIG_MAKEFILE(dlls/xaudio2_7/tests) WINE_CONFIG_MAKEFILE(dlls/xaudio2_8) WINE_CONFIG_MAKEFILE(dlls/xaudio2_9) +WINE_CONFIG_MAKEFILE(dlls/xinput.sys) WINE_CONFIG_MAKEFILE(dlls/xinput1_1) WINE_CONFIG_MAKEFILE(dlls/xinput1_2) WINE_CONFIG_MAKEFILE(dlls/xinput1_3) diff --git a/dlls/xinput.sys/Makefile.in b/dlls/xinput.sys/Makefile.in new file mode 100644 index 00000000000..52b00ef0528 --- /dev/null +++ b/dlls/xinput.sys/Makefile.in @@ -0,0 +1,8 @@ +MODULE = xinput.sys +IMPORTS = ntoskrnl +EXTRADLLFLAGS = -mno-cygwin -Wl,--subsystem,native + +C_SRCS = \ + main.c + +RC_SRCS = xinput.rc diff --git a/dlls/xinput.sys/main.c b/dlls/xinput.sys/main.c new file mode 100644 index 00000000000..d1c547f0572 --- /dev/null +++ b/dlls/xinput.sys/main.c @@ -0,0 +1,394 @@ +/* + * WINE XInput device driver + * + * Copyright 2021 Rémi Bernon for CodeWeavers + * + * This library 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 Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <stdarg.h> +#include <stdlib.h> + +#include "ntstatus.h" +#define WIN32_NO_STATUS +#include "windef.h" +#include "winbase.h" +#include "winternl.h" +#include "winioctl.h" + +#include "cfgmgr32.h" +#include "ddk/wdm.h" +#include "ddk/hidport.h" + +#include "wine/debug.h" + +WINE_DEFAULT_DEBUG_CHANNEL(xinput); + +#if defined(__i386__) && !defined(_WIN32) +extern void *WINAPI wrap_fastcall_func1(void *func, const void *a); +__ASM_STDCALL_FUNC(wrap_fastcall_func1, 8, + "popl %ecx\n\t" + "popl %eax\n\t" + "xchgl (%esp),%ecx\n\t" + "jmp *%eax"); +#define call_fastcall_func1(func,a) wrap_fastcall_func1(func,a) +#else +#define call_fastcall_func1(func,a) func(a) +#endif + +struct device_state +{ + DEVICE_OBJECT *bus_pdo; + DEVICE_OBJECT *gamepad_pdo; + + WCHAR bus_id[MAX_DEVICE_ID_LEN]; + WCHAR instance_id[MAX_DEVICE_ID_LEN]; +}; + +struct device_extension +{ + BOOL is_fdo; + BOOL removed; + + WCHAR device_id[MAX_DEVICE_ID_LEN]; + struct device_state *state; +}; + +static inline struct device_extension *ext_from_DEVICE_OBJECT(DEVICE_OBJECT *device) +{ + return (struct device_extension *)device->DeviceExtension; +} + +static NTSTATUS WINAPI internal_ioctl(DEVICE_OBJECT *device, IRP *irp) +{ + struct device_extension *ext = ext_from_DEVICE_OBJECT(device); + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp); + ULONG code = stack->Parameters.DeviceIoControl.IoControlCode; + struct device_state *state = ext->state; + + if (InterlockedOr(&ext->removed, FALSE)) + { + irp->IoStatus.Status = STATUS_DELETE_PENDING; + irp->IoStatus.Information = 0; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_DELETE_PENDING; + } + + TRACE("device %p, irp %p, code %#x, bus_pdo %p.\n", device, irp, code, state->bus_pdo); + + IoSkipCurrentIrpStackLocation(irp); + return IoCallDriver(state->bus_pdo, irp); +} + +static WCHAR *query_instance_id(DEVICE_OBJECT *device) +{ + struct device_extension *ext = ext_from_DEVICE_OBJECT(device); + struct device_state *state = ext->state; + DWORD len = wcslen(state->instance_id); + WCHAR *dst; + + if ((dst = ExAllocatePool(PagedPool, len * sizeof(WCHAR)))) + wcscpy(dst, state->instance_id); + + return dst; +} + +static WCHAR *query_device_id(DEVICE_OBJECT *device) +{ + struct device_extension *ext = ext_from_DEVICE_OBJECT(device); + DWORD len = wcslen(ext->device_id); + WCHAR *dst; + + if ((dst = ExAllocatePool(PagedPool, len * sizeof(WCHAR)))) + wcscpy(dst, ext->device_id); + + return dst; +} + +static WCHAR *query_hardware_ids(DEVICE_OBJECT *device) +{ + struct device_extension *ext = (struct device_extension *)device->DeviceExtension; + DWORD len = wcslen(ext->device_id); + WCHAR *dst; + + if ((dst = ExAllocatePool(PagedPool, (len + 2) * sizeof(WCHAR)))) + { + wcscpy(dst, ext->device_id); + dst[len + 1] = 0; + } + + return dst; +} + +static WCHAR *query_compatible_ids(DEVICE_OBJECT *device) +{ + DWORD len = wcslen(L"WINEBUS\WINE_COMP_HID"); + WCHAR *dst; + + if ((dst = ExAllocatePool(PagedPool, (len + 2) * sizeof(WCHAR)))) + { + wcscpy(dst, L"WINEBUS\WINE_COMP_HID"); + dst[len + 1] = 0; + } + + return dst; +} + +static NTSTATUS WINAPI pdo_pnp(DEVICE_OBJECT *device, IRP *irp) +{ + struct device_extension *ext = ext_from_DEVICE_OBJECT(device); + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp); + struct device_state *state = ext->state; + ULONG code = stack->MinorFunction; + NTSTATUS status; + + TRACE("device %p, irp %p, code %#x, bus_pdo %p.\n", device, irp, code, state->bus_pdo); + + switch (code) + { + case IRP_MN_START_DEVICE: + status = STATUS_SUCCESS; + break; + + case IRP_MN_SURPRISE_REMOVAL: + status = STATUS_SUCCESS; + if (InterlockedExchange(&ext->removed, TRUE)) break; + break; + + case IRP_MN_REMOVE_DEVICE: + irp->IoStatus.Status = STATUS_SUCCESS; + IoCompleteRequest(irp, IO_NO_INCREMENT); + IoDeleteDevice(device); + return STATUS_SUCCESS; + + case IRP_MN_QUERY_ID: + switch (stack->Parameters.QueryId.IdType) + { + case BusQueryHardwareIDs: + irp->IoStatus.Information = (ULONG_PTR)query_hardware_ids(device); + break; + case BusQueryCompatibleIDs: + irp->IoStatus.Information = (ULONG_PTR)query_compatible_ids(device); + break; + case BusQueryDeviceID: + irp->IoStatus.Information = (ULONG_PTR)query_device_id(device); + break; + case BusQueryInstanceID: + irp->IoStatus.Information = (ULONG_PTR)query_instance_id(device); + break; + default: + IoSkipCurrentIrpStackLocation(irp); + return IoCallDriver(state->bus_pdo, irp); + } + + if (!irp->IoStatus.Information) status = STATUS_NO_MEMORY; + else status = STATUS_SUCCESS; + break; + + default: + IoSkipCurrentIrpStackLocation(irp); + return IoCallDriver(state->bus_pdo, irp); + } + + irp->IoStatus.Status = status; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return status; +} + +static void create_child_pdos(DEVICE_OBJECT *fdo) +{ + struct device_extension *fdo_ext = fdo->DeviceExtension, *pdo_ext; + struct device_state *state = fdo_ext->state; + DEVICE_OBJECT *gamepad_pdo; + UNICODE_STRING string; + WCHAR *tmp, pdo_name[255]; + NTSTATUS status; + + swprintf(pdo_name, ARRAY_SIZE(pdo_name), L"\Device\%s#%p&%p", state->bus_id, fdo->DriverObject, state->bus_pdo); + RtlInitUnicodeString(&string, pdo_name); + if ((status = IoCreateDevice(fdo->DriverObject, sizeof(*pdo_ext), &string, 0, 0, FALSE, &gamepad_pdo))) + { + ERR( "failed to create gamepad PDO, status %#x.\n", status ); + return; + } + + pdo_ext = gamepad_pdo->DeviceExtension; + pdo_ext->is_fdo = FALSE; + pdo_ext->state = state; + wcscpy(pdo_ext->device_id, fdo_ext->device_id); + if ((tmp = wcsstr(pdo_ext->device_id, L"&MI_"))) memcpy(tmp, L"&IG", 6); + else wcscat(pdo_ext->device_id, L"&IG_00"); + + state->gamepad_pdo = gamepad_pdo; + + TRACE("fdo %p, gamepad PDO %p.\n", fdo, gamepad_pdo); + + IoInvalidateDeviceRelations(state->bus_pdo, BusRelations); +} + +static NTSTATUS WINAPI fdo_pnp(DEVICE_OBJECT *device, IRP *irp) +{ + struct device_extension *ext = ext_from_DEVICE_OBJECT(device); + IO_STACK_LOCATION *stack = IoGetCurrentIrpStackLocation(irp); + struct device_state *state = ext->state; + ULONG code = stack->MinorFunction; + DEVICE_RELATIONS *devices; + DEVICE_OBJECT *child; + NTSTATUS status; + + TRACE("device %p, irp %p, code %#x, bus_pdo %p.\n", device, irp, code, state->bus_pdo); + + switch (stack->MinorFunction) + { + case IRP_MN_QUERY_DEVICE_RELATIONS: + if (stack->Parameters.QueryDeviceRelations.Type == BusRelations) + { + if (!(devices = ExAllocatePool(PagedPool, offsetof(DEVICE_RELATIONS, Objects[2])))) + { + irp->IoStatus.Status = STATUS_NO_MEMORY; + IoCompleteRequest(irp, IO_NO_INCREMENT); + return STATUS_NO_MEMORY; + } + + devices->Count = 0; + if ((child = state->gamepad_pdo)) + { + devices->Objects[devices->Count] = child; + call_fastcall_func1(ObfReferenceObject, child); + devices->Count++; + } + + irp->IoStatus.Information = (ULONG_PTR)devices; + irp->IoStatus.Status = STATUS_SUCCESS; + } + + IoSkipCurrentIrpStackLocation(irp); + return IoCallDriver(state->bus_pdo, irp); + + case IRP_MN_START_DEVICE: + IoSkipCurrentIrpStackLocation(irp); + if (!(status = IoCallDriver(state->bus_pdo, irp))) + create_child_pdos(device); + return status; + + case IRP_MN_REMOVE_DEVICE: + IoSkipCurrentIrpStackLocation(irp); + status = IoCallDriver(state->bus_pdo, irp); + IoDetachDevice(state->bus_pdo); + IoDeleteDevice(device); + return status; + + default: + IoSkipCurrentIrpStackLocation(irp); + return IoCallDriver(state->bus_pdo, irp); + } + + return STATUS_SUCCESS; +} + +static NTSTATUS WINAPI driver_pnp(DEVICE_OBJECT *device, IRP *irp) +{ + struct device_extension *ext = ext_from_DEVICE_OBJECT(device); + + if (ext->is_fdo) return fdo_pnp(device, irp); + return pdo_pnp(device, irp); +} + +static NTSTATUS get_device_id(DEVICE_OBJECT *device, BUS_QUERY_ID_TYPE type, WCHAR *id) +{ + IO_STACK_LOCATION *stack; + IO_STATUS_BLOCK io; + KEVENT event; + IRP *irp; + + KeInitializeEvent(&event, NotificationEvent, FALSE); + irp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP, device, NULL, 0, NULL, &event, &io); + if (irp == NULL) return STATUS_NO_MEMORY; + + stack = IoGetNextIrpStackLocation(irp); + stack->MinorFunction = IRP_MN_QUERY_ID; + stack->Parameters.QueryId.IdType = type; + + if (IoCallDriver(device, irp) == STATUS_PENDING) + KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); + + wcscpy(id, (WCHAR *)io.Information); + ExFreePool((WCHAR *)io.Information); + return io.Status; +} + +static NTSTATUS WINAPI add_device(DRIVER_OBJECT *driver, DEVICE_OBJECT *bus_pdo) +{ + WCHAR bus_id[MAX_DEVICE_ID_LEN], *device_id, instance_id[MAX_DEVICE_ID_LEN]; + struct device_extension *ext; + struct device_state *state; + DEVICE_OBJECT *fdo; + NTSTATUS status; + + TRACE("driver %p, bus_pdo %p.\n", driver, bus_pdo); + + if ((status = get_device_id(bus_pdo, BusQueryDeviceID, bus_id))) + { + ERR("failed to get PDO device id, status %#x.\n", status); + return status; + } + + if ((device_id = wcsrchr(bus_id, '\'))) *device_id++ = 0; + else + { + ERR("unexpected device id %s\n", debugstr_w(bus_id)); + return STATUS_UNSUCCESSFUL; + } + + if ((status = get_device_id(bus_pdo, BusQueryInstanceID, instance_id))) + { + ERR("failed to get PDO instance id, status %#x.\n", status); + return status; + } + + if ((status = IoCreateDevice(driver, sizeof(*ext) + sizeof(struct device_state), NULL, + FILE_DEVICE_BUS_EXTENDER, 0, FALSE, &fdo))) + { + ERR("failed to create bus FDO, status %#x.\n", status); + return status; + } + + ext = fdo->DeviceExtension; + ext->is_fdo = TRUE; + ext->state = (struct device_state *)(ext + 1); + + state = ext->state; + state->bus_pdo = bus_pdo; + wcscpy(state->bus_id, bus_id); + swprintf(ext->device_id, MAX_DEVICE_ID_LEN, L"%s\%s", bus_id, device_id); + wcscpy(state->instance_id, instance_id); + + TRACE("fdo %p, bus %s, device %s, instance %s.\n", fdo, debugstr_w(state->bus_id), debugstr_w(ext->device_id), debugstr_w(state->instance_id)); + + IoAttachDeviceToDeviceStack(fdo, bus_pdo); + fdo->Flags &= ~DO_DEVICE_INITIALIZING; + return STATUS_SUCCESS; +} + +NTSTATUS WINAPI DriverEntry(DRIVER_OBJECT *driver, UNICODE_STRING *path) +{ + TRACE("driver %p, path %s.\n", driver, debugstr_w(path->Buffer)); + + driver->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = internal_ioctl; + driver->MajorFunction[IRP_MJ_PNP] = driver_pnp; + driver->DriverExtension->AddDevice = add_device; + + return STATUS_SUCCESS; +} diff --git a/dlls/xinput.sys/xinput.inf b/dlls/xinput.sys/xinput.inf new file mode 100644 index 00000000000..dcc5c87166f --- /dev/null +++ b/dlls/xinput.sys/xinput.inf @@ -0,0 +1,22 @@ +[Version] +Signature="$CHICAGO$" +ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318} +Class=System + +[Manufacturer] +Wine=mfg_section + +[mfg_section] +Wine XInput compatible device=device_section,WINEBUS\WINE_COMP_XINPUT + +[device_section.Services] +AddService = xinput,0x2,svc_section + +[svc_section] +Description="Wine XInput device driver" +DisplayName="Wine XInput" +ServiceBinary="%12%\xinput.sys" +LoadOrderGroup="WinePlugPlay" +ServiceType=1 +StartType=3 +ErrorControl=1 diff --git a/dlls/xinput.sys/xinput.rc b/dlls/xinput.sys/xinput.rc new file mode 100644 index 00000000000..791dca8fccb --- /dev/null +++ b/dlls/xinput.sys/xinput.rc @@ -0,0 +1,20 @@ +/* + * Copyright 2021 Rémi Bernon for CodeWeavers + * + * This library 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 Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/* @makedep: xinput.inf */ +1 WINE_DATA_FILE xinput.inf diff --git a/dlls/xinput.sys/xinput.sys.spec b/dlls/xinput.sys/xinput.sys.spec new file mode 100644 index 00000000000..76421d7e35b --- /dev/null +++ b/dlls/xinput.sys/xinput.sys.spec @@ -0,0 +1 @@ +# nothing to export diff --git a/loader/wine.inf.in b/loader/wine.inf.in index 49e4f45da27..9b8ec938c2f 100644 --- a/loader/wine.inf.in +++ b/loader/wine.inf.in @@ -4060,6 +4060,7 @@ services,"@%11%\ws2_32.dll,-4" winebus.inf,"@%12%\winebus.sys,-1" winehid.inf,"@%12%\winehid.sys,-1" wineusb.inf,"@%12%\wineusb.sys,-1" +xinput.inf,"@%12%\xinput.sys,-1"
[NlsFiles] c_037.nls