[PATCH 2/5] xinput.sys: Introduce new xinput.sys driver.

Zebediah Figura (she/her) zfigura at codeweavers.com
Mon Aug 23 11:40:29 CDT 2021



On 8/18/21 2:31 AM, Rémi Bernon wrote:
> 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 at codeweavers.com>

Can we find something else to name this, so it's less ambiguous what 
"xinput" refers to?

Maybe "winexinput"; this is a wine-specific DLL after all.

> ---
>   configure.ac                    |   1 +
>   dlls/winebus.sys/main.c         |  27 +--
>   dlls/xinput.sys/Makefile.in     |   8 +
>   dlls/xinput.sys/main.c          | 393 ++++++++++++++++++++++++++++++++
>   dlls/xinput.sys/xinput.inf      |  22 ++
>   dlls/xinput.sys/xinput.rc       |  20 ++
>   dlls/xinput.sys/xinput.sys.spec |   1 +
>   loader/wine.inf.in              |   1 +
>   8 files changed, 457 insertions(+), 16 deletions(-)
>   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/winebus.sys/main.c b/dlls/winebus.sys/main.c
> index e360416b5dd..17720bffbc5 100644
> --- a/dlls/winebus.sys/main.c
> +++ b/dlls/winebus.sys/main.c
> @@ -152,8 +152,6 @@ static CRITICAL_SECTION device_list_cs = { &critsect_debug, -1, 0, 0, 0, 0 };
>   static struct list pnp_devset = LIST_INIT(pnp_devset);
>   
>   static const WCHAR zero_serialW[]= {'0','0','0','0',0};
> -static const WCHAR miW[] = {'M','I',0};
> -static const WCHAR igW[] = {'I','G',0};
>   
>   static inline WCHAR *strdupW(const WCHAR *src)
>   {
> @@ -201,25 +199,17 @@ static WCHAR *get_instance_id(DEVICE_OBJECT *device)
>   
>   static WCHAR *get_device_id(DEVICE_OBJECT *device)
>   {
> +    static const WCHAR input_formatW[] = {'&','M','I','_','%','0','2','u',0};
>       static const WCHAR formatW[] = {'%','s','\\','v','i','d','_','%','0','4','x',
>               '&','p','i','d','_','%','0','4','x',0};
> -    static const WCHAR format_inputW[] = {'%','s','\\','v','i','d','_','%','0','4','x',
> -            '&','p','i','d','_','%','0','4','x','&','%','s','_','%','0','2','i',0};
>       struct device_extension *ext = (struct device_extension *)device->DeviceExtension;
>       DWORD len = strlenW(ext->busid) + 34;
> -    WCHAR *dst;
> +    WCHAR *dst, *tmp;
>   
>       if ((dst = ExAllocatePool(PagedPool, len * sizeof(WCHAR))))
>       {
> -        if (ext->input == (WORD)-1)
> -        {
> -            sprintfW(dst, formatW, ext->busid, ext->vid, ext->pid);
> -        }
> -        else
> -        {
> -            sprintfW(dst, format_inputW, ext->busid, ext->vid, ext->pid,
> -                    ext->is_gamepad ? igW : miW, ext->input);
> -        }
> +        tmp = dst + sprintfW(dst, formatW, ext->busid, ext->vid, ext->pid);
> +        if (ext->input != (WORD)-1) sprintfW(tmp, input_formatW, ext->input);
>       }
>   
>       return dst;
> @@ -245,12 +235,17 @@ static WCHAR *get_compatible_ids(DEVICE_OBJECT *device)
>       {
>           'W','I','N','E','B','U','S','\\','W','I','N','E','_','C','O','M','P','_','H','I','D',0
>       };
> -    DWORD len = strlenW(hid_compat);
> +    static const WCHAR xinput_compat[] =
> +    {
> +        'W','I','N','E','B','U','S','\\','W','I','N','E','_','C','O','M','P','_','X','I','N','P','U','T',0
> +    };
> +    struct device_extension *ext = (struct device_extension *)device->DeviceExtension;
> +    DWORD len = strlenW(ext->is_gamepad ? xinput_compat : hid_compat);
>       WCHAR *dst;
>   
>       if ((dst = ExAllocatePool(PagedPool, (len + 2) * sizeof(WCHAR))))
>       {
> -        strcpyW(dst, hid_compat);
> +        strcpyW(dst, ext->is_gamepad ? xinput_compat : hid_compat);
>           dst[len + 1] = 0;
>       }
>   
> 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..b97ec49b325
> --- /dev/null
> +++ b/dlls/xinput.sys/main.c
> @@ -0,0 +1,393 @@
> +/*
> + * 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

This is a weird thing to call this, because it's not dynamic at all.

Also, maybe a slightly more idiomatic structure would be to hold a 
pointer to the parent device as a struct device_extension, and then 
access its members through that pointer?

> +{
> +    DEVICE_OBJECT *bus_pdo;
> +    DEVICE_OBJECT *xinput_pdo;

This is ambiguous, how about "child_pdo"?

> +
> +    WCHAR bus_id[MAX_DEVICE_ID_LEN];
> +    WCHAR instance_id[MAX_DEVICE_ID_LEN];
> +};
> +
> +struct device_extension

Can we please not call this "device_extension"? I know we use that in 
other DLLs, but I think it's rather unclear. "struct device" seems 
better to me.

> +{
> +    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);
> +    }
> +
> +    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");

Nitpick, but it strikes me as odd to define "len" instead of defining 
the string constant itself.

> +    WCHAR *dst;
> +
> +    if ((dst = ExAllocatePool(PagedPool, (len + 2) * sizeof(WCHAR))))
> +    {
> +        wcscpy(dst, L"WINEBUS\\WINE_COMP_HID");

Also, you've just used "len", so this should be memcmp. Same for the others.

> +        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 *xinput_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, &xinput_pdo)))
> +    {
> +        ERR( "failed to create xinput PDO, status %#x.\n", status );
> +        return;
> +    }
> +
> +    pdo_ext = xinput_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");

I hate to nitpick, but can we please add some newlines here? This is not 
easy to read.

> +
> +    state->xinput_pdo = xinput_pdo;
> +
> +    TRACE("fdo %p, xinput PDO %p.\n", fdo, xinput_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->xinput_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;
> +}

Missing a DriverUnload; that'll prevent the driver from being unloaded.

> 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
> 



More information about the wine-devel mailing list