mirror of
https://github.com/nefarius/ViGEmBus.git
synced 2025-08-10 00:52:17 +00:00
407 lines
14 KiB
C
407 lines
14 KiB
C
/*
|
|
MIT License
|
|
|
|
Copyright (c) 2016 Benjamin "Nefarius" Höglinger
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
*/
|
|
|
|
|
|
#include "busenum.h"
|
|
#include "xgip.tmh"
|
|
|
|
NTSTATUS Xgip_PreparePdo(PWDFDEVICE_INIT DeviceInit, PUNICODE_STRING DeviceId, PUNICODE_STRING DeviceDescription)
|
|
{
|
|
NTSTATUS status;
|
|
UNICODE_STRING buffer;
|
|
|
|
// prepare device description
|
|
status = RtlUnicodeStringInit(DeviceDescription, L"Virtual Xbox One Controller");
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
// Set hardware IDs
|
|
RtlUnicodeStringInit(&buffer, L"USB\\VID_0E6F&PID_0139&REV_0650");
|
|
|
|
status = WdfPdoInitAddHardwareID(DeviceInit, &buffer);
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
RtlUnicodeStringCopy(DeviceId, &buffer);
|
|
|
|
RtlUnicodeStringInit(&buffer, L"USB\\VID_0E6F&PID_0139");
|
|
|
|
status = WdfPdoInitAddHardwareID(DeviceInit, &buffer);
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
// Set compatible IDs
|
|
RtlUnicodeStringInit(&buffer, L"USB\\MS_COMP_XGIP10");
|
|
|
|
status = WdfPdoInitAddCompatibleID(DeviceInit, &buffer);
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
RtlUnicodeStringInit(&buffer, L"USB\\Class_FF&SubClass_47&Prot_D0");
|
|
|
|
status = WdfPdoInitAddCompatibleID(DeviceInit, &buffer);
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
RtlUnicodeStringInit(&buffer, L"USB\\Class_FF&SubClass_47");
|
|
|
|
status = WdfPdoInitAddCompatibleID(DeviceInit, &buffer);
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
RtlUnicodeStringInit(&buffer, L"USB\\Class_FF");
|
|
|
|
status = WdfPdoInitAddCompatibleID(DeviceInit, &buffer);
|
|
if (!NT_SUCCESS(status))
|
|
return status;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS Xgip_PrepareHardware(WDFDEVICE Device)
|
|
{
|
|
NTSTATUS status;
|
|
WDF_QUERY_INTERFACE_CONFIG ifaceCfg;
|
|
|
|
// Expose USB_BUS_INTERFACE_USBDI_GUID
|
|
USB_BUS_INTERFACE_USBDI_V1 xgipInterface;
|
|
|
|
xgipInterface.Size = sizeof(USB_BUS_INTERFACE_USBDI_V1);
|
|
xgipInterface.Version = USB_BUSIF_USBDI_VERSION_1;
|
|
xgipInterface.BusContext = (PVOID)Device;
|
|
|
|
xgipInterface.InterfaceReference = WdfDeviceInterfaceReferenceNoOp;
|
|
xgipInterface.InterfaceDereference = WdfDeviceInterfaceDereferenceNoOp;
|
|
|
|
xgipInterface.SubmitIsoOutUrb = UsbPdo_SubmitIsoOutUrb;
|
|
xgipInterface.GetUSBDIVersion = UsbPdo_GetUSBDIVersion;
|
|
xgipInterface.QueryBusTime = UsbPdo_QueryBusTime;
|
|
xgipInterface.QueryBusInformation = UsbPdo_QueryBusInformation;
|
|
xgipInterface.IsDeviceHighSpeed = UsbPdo_IsDeviceHighSpeed;
|
|
|
|
WDF_QUERY_INTERFACE_CONFIG_INIT(&ifaceCfg, (PINTERFACE)&xgipInterface, &USB_BUS_INTERFACE_USBDI_GUID, NULL);
|
|
|
|
status = WdfDeviceAddQueryInterface(Device, &ifaceCfg);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR, TRACE_XGIP, "WdfDeviceAddQueryInterface failed with status %!STATUS!", status);
|
|
return status;
|
|
}
|
|
|
|
// Default button states
|
|
UCHAR DefaultReport[XGIP_REPORT_SIZE] =
|
|
{
|
|
0x20, 0x00, 0x10, 0x0e, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0xa3, 0xfd, 0xed, 0x05, 0x5b, 0x03,
|
|
0x6f, 0x02
|
|
};
|
|
|
|
RtlCopyBytes(XgipGetData(Device)->Report, DefaultReport, XGIP_REPORT_SIZE);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS Xgip_AssignPdoContext(WDFDEVICE Device)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
PXGIP_DEVICE_DATA xgip = XgipGetData(Device);
|
|
|
|
TraceEvents(TRACE_LEVEL_VERBOSE, TRACE_XGIP, "Initializing XGIP context...");
|
|
|
|
RtlZeroMemory(xgip, sizeof(XGIP_DEVICE_DATA));
|
|
|
|
// Set fixed report id
|
|
xgip->Report[0] = 0x20;
|
|
xgip->Report[3] = 0x0E;
|
|
|
|
// I/O Queue for pending IRPs
|
|
WDF_IO_QUEUE_CONFIG pendingUsbQueueConfig, notificationsQueueConfig;
|
|
|
|
// Create and assign queue for incoming interrupt transfer
|
|
WDF_IO_QUEUE_CONFIG_INIT(&pendingUsbQueueConfig, WdfIoQueueDispatchManual);
|
|
|
|
status = WdfIoQueueCreate(Device, &pendingUsbQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, &xgip->PendingUsbInRequests);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR, TRACE_XGIP, "WdfIoQueueCreate failed with status %!STATUS!", status);
|
|
return status;
|
|
}
|
|
|
|
// Create and assign queue for user-land notification requests
|
|
WDF_IO_QUEUE_CONFIG_INIT(¬ificationsQueueConfig, WdfIoQueueDispatchManual);
|
|
|
|
status = WdfIoQueueCreate(Device, ¬ificationsQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, &xgip->PendingNotificationRequests);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR, TRACE_XGIP, "WdfIoQueueCreate failed with status %!STATUS!", status);
|
|
return status;
|
|
}
|
|
|
|
WDF_OBJECT_ATTRIBUTES collectionAttribs;
|
|
WDF_OBJECT_ATTRIBUTES_INIT(&collectionAttribs);
|
|
|
|
collectionAttribs.ParentObject = Device;
|
|
|
|
status = WdfCollectionCreate(&collectionAttribs, &xgip->XboxgipSysInitCollection);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR, TRACE_XGIP, "WdfCollectionCreate failed with status %!STATUS!", status);
|
|
return status;
|
|
}
|
|
|
|
// Initialize periodic timer
|
|
WDF_TIMER_CONFIG timerConfig;
|
|
WDF_TIMER_CONFIG_INIT_PERIODIC(&timerConfig, Xgip_SysInitTimerFunc, XGIP_SYS_INIT_PERIOD);
|
|
|
|
// Timer object attributes
|
|
WDF_OBJECT_ATTRIBUTES timerAttribs;
|
|
WDF_OBJECT_ATTRIBUTES_INIT(&timerAttribs);
|
|
|
|
// PDO is parent
|
|
timerAttribs.ParentObject = Device;
|
|
|
|
// Create timer
|
|
status = WdfTimerCreate(&timerConfig, &timerAttribs, &xgip->XboxgipSysInitTimer);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_ERROR, TRACE_XGIP, "WdfTimerCreate failed with status %!STATUS!", status);
|
|
return status;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
VOID Xgip_GetConfigurationDescriptorType(PUCHAR Buffer, ULONG Length)
|
|
{
|
|
UCHAR XgipDescriptorData[XGIP_DESCRIPTOR_SIZE] =
|
|
{
|
|
0x09, // bLength
|
|
0x02, // bDescriptorType (Configuration)
|
|
0x40, 0x00, // wTotalLength 64
|
|
0x02, // bNumInterfaces 2
|
|
0x01, // bConfigurationValue
|
|
0x00, // iConfiguration (String Index)
|
|
0xC0, // bmAttributes
|
|
0xFA, // bMaxPower 500mA
|
|
|
|
0x09, // bLength
|
|
0x04, // bDescriptorType (Interface)
|
|
0x00, // bInterfaceNumber 0
|
|
0x00, // bAlternateSetting
|
|
0x02, // bNumEndpoints 2
|
|
0xFF, // bInterfaceClass
|
|
0x47, // bInterfaceSubClass
|
|
0xD0, // bInterfaceProtocol
|
|
0x00, // iInterface (String Index)
|
|
|
|
0x07, // bLength
|
|
0x05, // bDescriptorType (Endpoint)
|
|
0x81, // bEndpointAddress (IN/D2H)
|
|
0x03, // bmAttributes (Interrupt)
|
|
0x40, 0x00, // wMaxPacketSize 64
|
|
0x04, // bInterval 4 (unit depends on device speed)
|
|
|
|
0x07, // bLength
|
|
0x05, // bDescriptorType (Endpoint)
|
|
0x01, // bEndpointAddress (OUT/H2D)
|
|
0x03, // bmAttributes (Interrupt)
|
|
0x40, 0x00, // wMaxPacketSize 64
|
|
0x04, // bInterval 4 (unit depends on device speed)
|
|
|
|
0x09, // bLength
|
|
0x04, // bDescriptorType (Interface)
|
|
0x01, // bInterfaceNumber 1
|
|
0x00, // bAlternateSetting
|
|
0x00, // bNumEndpoints 0
|
|
0xFF, // bInterfaceClass
|
|
0x47, // bInterfaceSubClass
|
|
0xD0, // bInterfaceProtocol
|
|
0x00, // iInterface (String Index)
|
|
|
|
0x09, // bLength
|
|
0x04, // bDescriptorType (Interface)
|
|
0x01, // bInterfaceNumber 1
|
|
0x01, // bAlternateSetting
|
|
0x02, // bNumEndpoints 2
|
|
0xFF, // bInterfaceClass
|
|
0x47, // bInterfaceSubClass
|
|
0xD0, // bInterfaceProtocol
|
|
0x00, // iInterface (String Index)
|
|
|
|
0x07, // bLength
|
|
0x05, // bDescriptorType (Endpoint)
|
|
0x02, // bEndpointAddress (OUT/H2D)
|
|
0x01, // bmAttributes (Isochronous, No Sync, Data EP)
|
|
0xE0, 0x00, // wMaxPacketSize 224
|
|
0x01, // bInterval 1 (unit depends on device speed)
|
|
|
|
0x07, // bLength
|
|
0x05, // bDescriptorType (Endpoint)
|
|
0x83, // bEndpointAddress (IN/D2H)
|
|
0x01, // bmAttributes (Isochronous, No Sync, Data EP)
|
|
0x80, 0x00, // wMaxPacketSize 128
|
|
0x01, // bInterval 1 (unit depends on device speed)
|
|
|
|
// 64 bytes
|
|
|
|
// best guess: USB Standard Descriptor
|
|
};
|
|
|
|
RtlCopyBytes(Buffer, XgipDescriptorData, Length);
|
|
}
|
|
|
|
VOID Xgip_GetDeviceDescriptorType(PUSB_DEVICE_DESCRIPTOR pDescriptor, PPDO_DEVICE_DATA pCommon)
|
|
{
|
|
pDescriptor->bLength = 0x12;
|
|
pDescriptor->bDescriptorType = USB_DEVICE_DESCRIPTOR_TYPE;
|
|
pDescriptor->bcdUSB = 0x0200; // USB v2.0
|
|
pDescriptor->bDeviceClass = 0xFF;
|
|
pDescriptor->bDeviceSubClass = 0x47;
|
|
pDescriptor->bDeviceProtocol = 0xD0;
|
|
pDescriptor->bMaxPacketSize0 = 0x40;
|
|
pDescriptor->idVendor = pCommon->VendorId;
|
|
pDescriptor->idProduct = pCommon->ProductId;
|
|
pDescriptor->bcdDevice = 0x0650;
|
|
pDescriptor->iManufacturer = 0x01;
|
|
pDescriptor->iProduct = 0x02;
|
|
pDescriptor->iSerialNumber = 0x03;
|
|
pDescriptor->bNumConfigurations = 0x01;
|
|
}
|
|
|
|
VOID Xgip_SelectConfiguration(PUSBD_INTERFACE_INFORMATION pInfo)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_XGIP, ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: Length %d, Interface %d, Alternate %d, Pipes %d",
|
|
(int)pInfo->Length,
|
|
(int)pInfo->InterfaceNumber,
|
|
(int)pInfo->AlternateSetting,
|
|
pInfo->NumberOfPipes);
|
|
|
|
pInfo->Class = 0xFF;
|
|
pInfo->SubClass = 0x47;
|
|
pInfo->Protocol = 0xD0;
|
|
|
|
pInfo->InterfaceHandle = (USBD_INTERFACE_HANDLE)0xFFFF0000;
|
|
|
|
pInfo->Pipes[0].MaximumTransferSize = 0x00400000;
|
|
pInfo->Pipes[0].MaximumPacketSize = 0x40;
|
|
pInfo->Pipes[0].EndpointAddress = 0x81;
|
|
pInfo->Pipes[0].Interval = 0x04;
|
|
pInfo->Pipes[0].PipeType = UsbdPipeTypeInterrupt;
|
|
pInfo->Pipes[0].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0081;
|
|
pInfo->Pipes[0].PipeFlags = 0x00;
|
|
|
|
pInfo->Pipes[1].MaximumTransferSize = 0x00400000;
|
|
pInfo->Pipes[1].MaximumPacketSize = 0x40;
|
|
pInfo->Pipes[1].EndpointAddress = 0x01;
|
|
pInfo->Pipes[1].Interval = 0x04;
|
|
pInfo->Pipes[1].PipeType = UsbdPipeTypeInterrupt;
|
|
pInfo->Pipes[1].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0001;
|
|
pInfo->Pipes[1].PipeFlags = 0x00;
|
|
|
|
pInfo = (PUSBD_INTERFACE_INFORMATION)((PCHAR)pInfo + pInfo->Length);
|
|
|
|
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_XGIP, ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: Length %d, Interface %d, Alternate %d, Pipes %d",
|
|
(int)pInfo->Length,
|
|
(int)pInfo->InterfaceNumber,
|
|
(int)pInfo->AlternateSetting,
|
|
pInfo->NumberOfPipes);
|
|
|
|
pInfo->Class = 0xFF;
|
|
pInfo->SubClass = 0x47;
|
|
pInfo->Protocol = 0xD0;
|
|
|
|
pInfo->InterfaceHandle = (USBD_INTERFACE_HANDLE)0xFFFF0000;
|
|
}
|
|
|
|
VOID Xgip_SysInitTimerFunc(
|
|
_In_ WDFTIMER Timer
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
WDFDEVICE hChild;
|
|
PXGIP_DEVICE_DATA xgip;
|
|
WDFREQUEST usbRequest;
|
|
PIRP pendingIrp;
|
|
PIO_STACK_LOCATION irpStack;
|
|
WDFMEMORY mem;
|
|
|
|
hChild = WdfTimerGetParentObject(Timer);
|
|
xgip = XgipGetData(hChild);
|
|
|
|
if (xgip == NULL) return;
|
|
|
|
// Is TRUE when collection is filled up
|
|
if (xgip->XboxgipSysInitReady)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_VERBOSE, TRACE_XGIP, "XBOXGIP ready, completing requests...");
|
|
|
|
// Get pending IN request
|
|
status = WdfIoQueueRetrieveNextRequest(xgip->PendingUsbInRequests, &usbRequest);
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
TraceEvents(TRACE_LEVEL_VERBOSE, TRACE_XGIP, "Request found");
|
|
|
|
// Get top memory object
|
|
mem = (WDFMEMORY)WdfCollectionGetFirstItem(xgip->XboxgipSysInitCollection);
|
|
|
|
// Get pending IRP
|
|
pendingIrp = WdfRequestWdmGetIrp(usbRequest);
|
|
irpStack = IoGetCurrentIrpStackLocation(pendingIrp);
|
|
|
|
// Get USB request block
|
|
PURB urb = (PURB)irpStack->Parameters.Others.Argument1;
|
|
|
|
// Get buffer size and content
|
|
size_t size;
|
|
PUCHAR Buffer = WdfMemoryGetBuffer(mem, &size);
|
|
|
|
// Assign buffer size and content to URB
|
|
urb->UrbBulkOrInterruptTransfer.TransferBufferLength = (ULONG)size;
|
|
RtlCopyBytes(urb->UrbBulkOrInterruptTransfer.TransferBuffer, Buffer, size);
|
|
|
|
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_XGIP, "[%X] Buffer length: %d",
|
|
((PUCHAR)urb->UrbBulkOrInterruptTransfer.TransferBuffer)[0],
|
|
urb->UrbBulkOrInterruptTransfer.TransferBufferLength);
|
|
|
|
// Complete pending request
|
|
WdfRequestComplete(usbRequest, status);
|
|
|
|
// Free memory from collection
|
|
WdfCollectionRemoveItem(xgip->XboxgipSysInitCollection, 0);
|
|
WdfObjectDelete(mem);
|
|
}
|
|
|
|
// Stop timer when collection is purged
|
|
if (WdfCollectionGetCount(xgip->XboxgipSysInitCollection) == 0)
|
|
{
|
|
TraceEvents(TRACE_LEVEL_VERBOSE, TRACE_XGIP, "Collection finished");
|
|
|
|
WdfTimerStop(xgip->XboxgipSysInitTimer, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|