diff --git a/ViGEmBus.sln b/ViGEmBus.sln new file mode 100644 index 0000000..1a83c0f --- /dev/null +++ b/ViGEmBus.sln @@ -0,0 +1,157 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2024 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ViGEmBus", "sys\ViGEmBus.vcxproj", "{040101B0-EE5C-4EF1-99EE-9F81C795C001}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ViGEmClient", "lib\ViGEmClient.vcxproj", "{7DB06674-1F4F-464B-8E1C-172E9587F9DC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug (dynamic)|ARM = Debug (dynamic)|ARM + Debug (dynamic)|ARM64 = Debug (dynamic)|ARM64 + Debug (dynamic)|x64 = Debug (dynamic)|x64 + Debug (dynamic)|x86 = Debug (dynamic)|x86 + Debug (static)|ARM = Debug (static)|ARM + Debug (static)|ARM64 = Debug (static)|ARM64 + Debug (static)|x64 = Debug (static)|x64 + Debug (static)|x86 = Debug (static)|x86 + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release (dynamic)|ARM = Release (dynamic)|ARM + Release (dynamic)|ARM64 = Release (dynamic)|ARM64 + Release (dynamic)|x64 = Release (dynamic)|x64 + Release (dynamic)|x86 = Release (dynamic)|x86 + Release (static)|ARM = Release (static)|ARM + Release (static)|ARM64 = Release (static)|ARM64 + Release (static)|x64 = Release (static)|x64 + Release (static)|x86 = Release (static)|x86 + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (dynamic)|ARM.ActiveCfg = Debug|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (dynamic)|ARM.Build.0 = Debug|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (dynamic)|ARM.Deploy.0 = Debug|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (dynamic)|ARM64.ActiveCfg = Debug|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (dynamic)|ARM64.Build.0 = Debug|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (dynamic)|ARM64.Deploy.0 = Debug|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (dynamic)|x64.ActiveCfg = Debug|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (dynamic)|x64.Build.0 = Debug|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (dynamic)|x64.Deploy.0 = Debug|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (dynamic)|x86.ActiveCfg = Debug|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (dynamic)|x86.Build.0 = Debug|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (dynamic)|x86.Deploy.0 = Debug|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (static)|ARM.ActiveCfg = Debug|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (static)|ARM.Build.0 = Debug|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (static)|ARM.Deploy.0 = Debug|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (static)|ARM64.ActiveCfg = Debug|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (static)|ARM64.Build.0 = Debug|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (static)|ARM64.Deploy.0 = Debug|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (static)|x64.ActiveCfg = Debug|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (static)|x64.Build.0 = Debug|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (static)|x64.Deploy.0 = Debug|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (static)|x86.ActiveCfg = Debug|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (static)|x86.Build.0 = Debug|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug (static)|x86.Deploy.0 = Debug|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug|ARM.ActiveCfg = Debug|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug|ARM.Build.0 = Debug|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug|ARM.Deploy.0 = Debug|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug|ARM64.Build.0 = Debug|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug|x64.ActiveCfg = Debug|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug|x64.Build.0 = Debug|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug|x64.Deploy.0 = Debug|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug|x86.ActiveCfg = Debug|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug|x86.Build.0 = Debug|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Debug|x86.Deploy.0 = Debug|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (dynamic)|ARM.ActiveCfg = Release|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (dynamic)|ARM.Build.0 = Release|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (dynamic)|ARM.Deploy.0 = Release|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (dynamic)|ARM64.ActiveCfg = Release|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (dynamic)|ARM64.Build.0 = Release|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (dynamic)|ARM64.Deploy.0 = Release|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (dynamic)|x64.ActiveCfg = Release|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (dynamic)|x64.Build.0 = Release|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (dynamic)|x64.Deploy.0 = Release|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (dynamic)|x86.ActiveCfg = Release|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (dynamic)|x86.Build.0 = Release|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (dynamic)|x86.Deploy.0 = Release|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (static)|ARM.ActiveCfg = Release|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (static)|ARM.Build.0 = Release|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (static)|ARM.Deploy.0 = Release|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (static)|ARM64.ActiveCfg = Release|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (static)|ARM64.Build.0 = Release|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (static)|ARM64.Deploy.0 = Release|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (static)|x64.ActiveCfg = Release|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (static)|x64.Build.0 = Release|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (static)|x64.Deploy.0 = Release|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (static)|x86.ActiveCfg = Release|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (static)|x86.Build.0 = Release|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release (static)|x86.Deploy.0 = Release|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release|ARM.ActiveCfg = Release|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release|ARM.Build.0 = Release|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release|ARM.Deploy.0 = Release|ARM + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release|ARM64.ActiveCfg = Release|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release|ARM64.Build.0 = Release|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release|ARM64.Deploy.0 = Release|ARM64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release|x64.ActiveCfg = Release|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release|x64.Build.0 = Release|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release|x64.Deploy.0 = Release|x64 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release|x86.ActiveCfg = Release|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release|x86.Build.0 = Release|Win32 + {040101B0-EE5C-4EF1-99EE-9F81C795C001}.Release|x86.Deploy.0 = Release|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug (dynamic)|ARM.ActiveCfg = Debug (dynamic)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug (dynamic)|ARM64.ActiveCfg = Debug (dynamic)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug (dynamic)|x64.ActiveCfg = Debug (dynamic)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug (dynamic)|x64.Build.0 = Debug (dynamic)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug (dynamic)|x86.ActiveCfg = Debug (dynamic)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug (dynamic)|x86.Build.0 = Debug (dynamic)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug (static)|ARM.ActiveCfg = Debug (static)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug (static)|ARM64.ActiveCfg = Debug (static)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug (static)|x64.ActiveCfg = Debug (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug (static)|x64.Build.0 = Debug (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug (static)|x86.ActiveCfg = Debug (static)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug (static)|x86.Build.0 = Debug (static)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|ARM.ActiveCfg = Debug (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|ARM.Build.0 = Debug (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|ARM64.ActiveCfg = Debug (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|ARM64.Build.0 = Debug (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|x64.ActiveCfg = Debug (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|x64.Build.0 = Debug (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|x86.ActiveCfg = Debug (static)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug|x86.Build.0 = Debug (static)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release (dynamic)|ARM.ActiveCfg = Release (dynamic)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release (dynamic)|ARM64.ActiveCfg = Release (dynamic)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release (dynamic)|x64.ActiveCfg = Release (dynamic)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release (dynamic)|x64.Build.0 = Release (dynamic)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release (dynamic)|x86.ActiveCfg = Release (dynamic)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release (dynamic)|x86.Build.0 = Release (dynamic)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release (static)|ARM.ActiveCfg = Release (static)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release (static)|ARM64.ActiveCfg = Release (static)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release (static)|x64.ActiveCfg = Release (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release (static)|x64.Build.0 = Release (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release (static)|x86.ActiveCfg = Release (static)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release (static)|x86.Build.0 = Release (static)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|ARM.ActiveCfg = Debug (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|ARM.Build.0 = Debug (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|ARM64.ActiveCfg = Debug (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|ARM64.Build.0 = Debug (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|x64.ActiveCfg = Release (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|x64.Build.0 = Release (static)|x64 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|x86.ActiveCfg = Release (static)|Win32 + {7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release|x86.Build.0 = Release (static)|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D5CD61FD-80BB-4E0E-840C-BAF66ABB1CF0} + EndGlobalSection +EndGlobal diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 0000000..5f4e113 --- /dev/null +++ b/lib/README.md @@ -0,0 +1,8 @@ +# ViGEm user-mode client library +This static library provides the gateway to the bus drivers functionalities. It offers: + * Searching and connecting to a library-compatible bus device on the system + * Attaching and removing a (artificially limited) number of virtual devices + * Feeding the emulated devices (aka providing input state changes) + +## Dependencies +In addition to this library you'll need to link against `setupapi.lib`. \ No newline at end of file diff --git a/lib/ViGEmClient.cpp b/lib/ViGEmClient.cpp new file mode 100644 index 0000000..8deee3f --- /dev/null +++ b/lib/ViGEmClient.cpp @@ -0,0 +1,636 @@ +/* +MIT License + +Copyright (c) 2017 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. +*/ + + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +#include "ViGEmBusShared.h" +#include "ViGEmClient.h" +#include +#include +#include + +#define VIGEM_TARGETS_MAX USHRT_MAX + +// +// Represents a driver connection object. +// +typedef struct _VIGEM_CLIENT_T +{ + HANDLE hBusDevice; + +} VIGEM_CLIENT; + +// +// Represents the (connection) state of a target device object. +// +typedef enum _VIGEM_TARGET_STATE +{ + VIGEM_TARGET_NEW, + VIGEM_TARGET_INITIALIZED, + VIGEM_TARGET_CONNECTED, + VIGEM_TARGET_DISCONNECTED +} VIGEM_TARGET_STATE, *PVIGEM_TARGET_STATE; + +// +// Represents a virtual gamepad object. +// +typedef struct _VIGEM_TARGET_T +{ + ULONG Size; + ULONG SerialNo; + VIGEM_TARGET_STATE State; + USHORT VendorId; + USHORT ProductId; + VIGEM_TARGET_TYPE Type; + DWORD_PTR Notification; + +} VIGEM_TARGET; + +// +// Initializes a virtual gamepad object. +// +PVIGEM_TARGET FORCEINLINE VIGEM_TARGET_ALLOC_INIT( + _In_ VIGEM_TARGET_TYPE Type +) +{ + auto target = static_cast(malloc(sizeof(VIGEM_TARGET))); + RtlZeroMemory(target, sizeof(VIGEM_TARGET)); + + target->Size = sizeof(VIGEM_TARGET); + target->State = VIGEM_TARGET_INITIALIZED; + target->Type = Type; + + return target; +} + + +PVIGEM_CLIENT vigem_alloc() +{ + auto driver = static_cast(malloc(sizeof(VIGEM_CLIENT))); + + RtlZeroMemory(driver, sizeof(VIGEM_CLIENT)); + driver->hBusDevice = INVALID_HANDLE_VALUE; + + return driver; +} + +void vigem_free(PVIGEM_CLIENT vigem) +{ + if (vigem) + free(vigem); +} + +VIGEM_ERROR vigem_connect(PVIGEM_CLIENT vigem) +{ + SP_DEVICE_INTERFACE_DATA deviceInterfaceData = { 0 }; + deviceInterfaceData.cbSize = sizeof(deviceInterfaceData); + DWORD memberIndex = 0; + DWORD requiredSize = 0; + auto error = VIGEM_ERROR_BUS_NOT_FOUND; + + // check for already open handle as re-opening accidentally would destroy all live targets + if (vigem->hBusDevice != INVALID_HANDLE_VALUE) + { + return VIGEM_ERROR_BUS_ALREADY_CONNECTED; + } + + auto deviceInfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_BUSENUM_VIGEM, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + // enumerate device instances + while (SetupDiEnumDeviceInterfaces(deviceInfoSet, nullptr, &GUID_DEVINTERFACE_BUSENUM_VIGEM, memberIndex++, &deviceInterfaceData)) + { + // get required target buffer size + SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, nullptr, 0, &requiredSize, nullptr); + + // allocate target buffer + auto detailDataBuffer = static_cast(malloc(requiredSize)); + detailDataBuffer->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + // get detail buffer + if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, detailDataBuffer, requiredSize, &requiredSize, nullptr)) + { + SetupDiDestroyDeviceInfoList(deviceInfoSet); + free(detailDataBuffer); + error = VIGEM_ERROR_BUS_NOT_FOUND; + continue; + } + + // bus found, open it + vigem->hBusDevice = CreateFile(detailDataBuffer->DevicePath, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_OVERLAPPED, + nullptr); + + // check bus open result + if (vigem->hBusDevice == INVALID_HANDLE_VALUE) + { + error = VIGEM_ERROR_BUS_ACCESS_FAILED; + free(detailDataBuffer); + continue; + } + + DWORD transfered = 0; + OVERLAPPED lOverlapped = { 0 }; + lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + VIGEM_CHECK_VERSION version; + VIGEM_CHECK_VERSION_INIT(&version, VIGEM_COMMON_VERSION); + + // send compiled library version to driver to check compatibility + DeviceIoControl(vigem->hBusDevice, IOCTL_VIGEM_CHECK_VERSION, &version, version.Size, nullptr, 0, &transfered, &lOverlapped); + + // wait for result + if (GetOverlappedResult(vigem->hBusDevice, &lOverlapped, &transfered, TRUE) != 0) + { + error = VIGEM_ERROR_NONE; + free(detailDataBuffer); + CloseHandle(lOverlapped.hEvent); + break; + } + + error = VIGEM_ERROR_BUS_VERSION_MISMATCH; + + CloseHandle(lOverlapped.hEvent); + free(detailDataBuffer); + } + + SetupDiDestroyDeviceInfoList(deviceInfoSet); + + return error; +} + +void vigem_disconnect(PVIGEM_CLIENT vigem) +{ + if (vigem->hBusDevice != INVALID_HANDLE_VALUE) + { + CloseHandle(vigem->hBusDevice); + + RtlZeroMemory(vigem, sizeof(VIGEM_CLIENT)); + vigem->hBusDevice = INVALID_HANDLE_VALUE; + } +} + +PVIGEM_TARGET vigem_target_x360_alloc(void) +{ + auto target = VIGEM_TARGET_ALLOC_INIT(Xbox360Wired); + + target->VendorId = 0x045E; + target->ProductId = 0x028E; + + return target; +} + +PVIGEM_TARGET vigem_target_ds4_alloc(void) +{ + auto target = VIGEM_TARGET_ALLOC_INIT(DualShock4Wired); + + target->VendorId = 0x054C; + target->ProductId = 0x05C4; + + return target; +} + +void vigem_target_free(PVIGEM_TARGET target) +{ + if (target) + free(target); +} + +VIGEM_ERROR vigem_target_add(PVIGEM_CLIENT vigem, PVIGEM_TARGET target) +{ + if (vigem->hBusDevice == nullptr) + { + return VIGEM_ERROR_BUS_NOT_FOUND; + } + + if (target->State == VIGEM_TARGET_NEW) + { + return VIGEM_ERROR_TARGET_UNINITIALIZED; + } + + if (target->State == VIGEM_TARGET_CONNECTED) + { + return VIGEM_ERROR_ALREADY_CONNECTED; + } + + DWORD transfered = 0; + VIGEM_PLUGIN_TARGET plugin; + OVERLAPPED lOverlapped = { 0 }; + lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + for (target->SerialNo = 1; target->SerialNo <= VIGEM_TARGETS_MAX; target->SerialNo++) + { + VIGEM_PLUGIN_TARGET_INIT(&plugin, target->SerialNo, target->Type); + + plugin.VendorId = target->VendorId; + plugin.ProductId = target->ProductId; + + DeviceIoControl(vigem->hBusDevice, IOCTL_VIGEM_PLUGIN_TARGET, &plugin, plugin.Size, nullptr, 0, &transfered, &lOverlapped); + + if (GetOverlappedResult(vigem->hBusDevice, &lOverlapped, &transfered, TRUE) != 0) + { + target->State = VIGEM_TARGET_CONNECTED; + CloseHandle(lOverlapped.hEvent); + + return VIGEM_ERROR_NONE; + } + } + + CloseHandle(lOverlapped.hEvent); + + return VIGEM_ERROR_NO_FREE_SLOT; +} + +VIGEM_ERROR vigem_target_add_async(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, PVIGEM_TARGET_ADD_RESULT result) +{ + if (vigem->hBusDevice == nullptr) + { + return VIGEM_ERROR_BUS_NOT_FOUND; + } + + if (target->State == VIGEM_TARGET_NEW) + { + return VIGEM_ERROR_TARGET_UNINITIALIZED; + } + + if (target->State == VIGEM_TARGET_CONNECTED) + { + return VIGEM_ERROR_ALREADY_CONNECTED; + } + + std::thread _async{ []( + PVIGEM_TARGET _Target, + PVIGEM_CLIENT _Client, + PVIGEM_TARGET_ADD_RESULT _Result) + { + DWORD transfered = 0; + VIGEM_PLUGIN_TARGET plugin; + OVERLAPPED lOverlapped = { 0 }; + lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + for (_Target->SerialNo = 1; _Target->SerialNo <= VIGEM_TARGETS_MAX; _Target->SerialNo++) + { + VIGEM_PLUGIN_TARGET_INIT(&plugin, _Target->SerialNo, _Target->Type); + + plugin.VendorId = _Target->VendorId; + plugin.ProductId = _Target->ProductId; + + DeviceIoControl(_Client->hBusDevice, IOCTL_VIGEM_PLUGIN_TARGET, &plugin, plugin.Size, nullptr, 0, &transfered, &lOverlapped); + + if (GetOverlappedResult(_Client->hBusDevice, &lOverlapped, &transfered, TRUE) != 0) + { + _Target->State = VIGEM_TARGET_CONNECTED; + CloseHandle(lOverlapped.hEvent); + + if (_Result) + _Result(_Client, _Target, VIGEM_ERROR_NONE); + + return; + } + } + + CloseHandle(lOverlapped.hEvent); + + if (_Result) + _Result(_Client, _Target, VIGEM_ERROR_NO_FREE_SLOT); + + }, target, vigem, result }; + + _async.detach(); + + return VIGEM_ERROR_NONE; +} + +VIGEM_ERROR vigem_target_remove(PVIGEM_CLIENT vigem, PVIGEM_TARGET target) +{ + if (vigem->hBusDevice == nullptr) + { + return VIGEM_ERROR_BUS_NOT_FOUND; + } + + if (target->State == VIGEM_TARGET_NEW) + { + return VIGEM_ERROR_TARGET_UNINITIALIZED; + } + + if (target->State != VIGEM_TARGET_CONNECTED) + { + return VIGEM_ERROR_TARGET_NOT_PLUGGED_IN; + } + + DWORD transfered = 0; + VIGEM_UNPLUG_TARGET unplug; + OVERLAPPED lOverlapped = { 0 }; + lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + VIGEM_UNPLUG_TARGET_INIT(&unplug, target->SerialNo); + + DeviceIoControl(vigem->hBusDevice, IOCTL_VIGEM_UNPLUG_TARGET, &unplug, unplug.Size, nullptr, 0, &transfered, &lOverlapped); + + if (GetOverlappedResult(vigem->hBusDevice, &lOverlapped, &transfered, TRUE) != 0) + { + target->State = VIGEM_TARGET_DISCONNECTED; + CloseHandle(lOverlapped.hEvent); + + //g_xusbCallbacks.erase(Target->SerialNo); + //g_ds4Callbacks.erase(Target->SerialNo); + + return VIGEM_ERROR_NONE; + } + + CloseHandle(lOverlapped.hEvent); + + return VIGEM_ERROR_REMOVAL_FAILED; +} + +VIGEM_ERROR vigem_target_x360_register_notification(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, PVIGEM_X360_NOTIFICATION notification) +{ + if (vigem->hBusDevice == nullptr) + { + return VIGEM_ERROR_BUS_NOT_FOUND; + } + + if (target->SerialNo == 0 || notification == nullptr) + { + return VIGEM_ERROR_INVALID_TARGET; + } + + if (target->Notification == reinterpret_cast(notification)) + { + return VIGEM_ERROR_CALLBACK_ALREADY_REGISTERED; + } + + target->Notification = reinterpret_cast(notification); + + std::thread _async{ []( + PVIGEM_TARGET _Target, + PVIGEM_CLIENT _Client) + { + DWORD error = ERROR_SUCCESS; + DWORD transfered = 0; + OVERLAPPED lOverlapped = { 0 }; + lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + XUSB_REQUEST_NOTIFICATION notify; + XUSB_REQUEST_NOTIFICATION_INIT(¬ify, _Target->SerialNo); + + do + { + DeviceIoControl(_Client->hBusDevice, + IOCTL_XUSB_REQUEST_NOTIFICATION, + ¬ify, + notify.Size, + ¬ify, + notify.Size, + &transfered, + &lOverlapped); + + if (GetOverlappedResult(_Client->hBusDevice, &lOverlapped, &transfered, TRUE) != 0) + { + if (_Target->Notification == NULL) + { + CloseHandle(lOverlapped.hEvent); + return; + } + + reinterpret_cast(_Target->Notification)( + _Client, + _Target, + notify.LargeMotor, + notify.SmallMotor, + notify.LedNumber); + } + else + { + error = GetLastError(); + } + } while (error != ERROR_OPERATION_ABORTED && error != ERROR_ACCESS_DENIED); + + CloseHandle(lOverlapped.hEvent); + + }, target, vigem }; + + _async.detach(); + + return VIGEM_ERROR_NONE; +} + +VIGEM_ERROR vigem_target_ds4_register_notification(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, PVIGEM_DS4_NOTIFICATION notification) +{ + if (vigem->hBusDevice == nullptr) + { + return VIGEM_ERROR_BUS_NOT_FOUND; + } + + if (target->SerialNo == 0 || notification == nullptr) + { + return VIGEM_ERROR_INVALID_TARGET; + } + + if (target->Notification == reinterpret_cast(notification)) + { + return VIGEM_ERROR_CALLBACK_ALREADY_REGISTERED; + } + + target->Notification = reinterpret_cast(notification); + + std::thread _async{ []( + PVIGEM_TARGET _Target, + PVIGEM_CLIENT _Client) + { + DWORD error = ERROR_SUCCESS; + DWORD transfered = 0; + OVERLAPPED lOverlapped = { 0 }; + lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + DS4_REQUEST_NOTIFICATION notify; + DS4_REQUEST_NOTIFICATION_INIT(¬ify, _Target->SerialNo); + + do + { + DeviceIoControl(_Client->hBusDevice, + IOCTL_DS4_REQUEST_NOTIFICATION, + ¬ify, + notify.Size, + ¬ify, + notify.Size, + &transfered, + &lOverlapped); + + if (GetOverlappedResult(_Client->hBusDevice, &lOverlapped, &transfered, TRUE) != 0) + { + if (_Target->Notification == NULL) + { + CloseHandle(lOverlapped.hEvent); + return; + } + + reinterpret_cast(_Target->Notification)( + _Client, + _Target, + notify.Report.LargeMotor, + notify.Report.SmallMotor, + notify.Report.LightbarColor); + } + else + { + error = GetLastError(); + } + } while (error != ERROR_OPERATION_ABORTED && error != ERROR_ACCESS_DENIED); + + CloseHandle(lOverlapped.hEvent); + + }, target, vigem }; + + _async.detach(); + + return VIGEM_ERROR_NONE; +} + +void vigem_target_x360_unregister_notification(PVIGEM_TARGET target) +{ + target->Notification = NULL; +} + +void vigem_target_ds4_unregister_notification(PVIGEM_TARGET target) +{ + target->Notification = NULL; +} + +void vigem_target_set_vid(PVIGEM_TARGET target, USHORT vid) +{ + target->VendorId = vid; +} + +void vigem_target_set_pid(PVIGEM_TARGET target, USHORT pid) +{ + target->ProductId = pid; +} + +USHORT vigem_target_get_vid(PVIGEM_TARGET target) +{ + return target->VendorId; +} + +USHORT vigem_target_get_pid(PVIGEM_TARGET target) +{ + return target->ProductId; +} + +VIGEM_ERROR vigem_target_x360_update(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, XUSB_REPORT report) +{ + if (vigem->hBusDevice == nullptr) + { + return VIGEM_ERROR_BUS_NOT_FOUND; + } + + if (target->SerialNo == 0) + { + return VIGEM_ERROR_INVALID_TARGET; + } + + DWORD transfered = 0; + OVERLAPPED lOverlapped = { 0 }; + lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + XUSB_SUBMIT_REPORT xsr; + XUSB_SUBMIT_REPORT_INIT(&xsr, target->SerialNo); + + xsr.Report = report; + + DeviceIoControl(vigem->hBusDevice, IOCTL_XUSB_SUBMIT_REPORT, &xsr, xsr.Size, nullptr, 0, &transfered, &lOverlapped); + + if (GetOverlappedResult(vigem->hBusDevice, &lOverlapped, &transfered, TRUE) == 0) + { + if (GetLastError() == ERROR_ACCESS_DENIED) + { + CloseHandle(lOverlapped.hEvent); + return VIGEM_ERROR_INVALID_TARGET; + } + } + + CloseHandle(lOverlapped.hEvent); + + return VIGEM_ERROR_NONE; +} + +VIGEM_ERROR vigem_target_ds4_update(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, DS4_REPORT report) +{ + if (vigem->hBusDevice == nullptr) + { + return VIGEM_ERROR_BUS_NOT_FOUND; + } + + if (target->SerialNo == 0) + { + return VIGEM_ERROR_INVALID_TARGET; + } + + DWORD transfered = 0; + OVERLAPPED lOverlapped = { 0 }; + lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); + + DS4_SUBMIT_REPORT dsr; + DS4_SUBMIT_REPORT_INIT(&dsr, target->SerialNo); + + dsr.Report = report; + + DeviceIoControl(vigem->hBusDevice, IOCTL_DS4_SUBMIT_REPORT, &dsr, dsr.Size, nullptr, 0, &transfered, &lOverlapped); + + if (GetOverlappedResult(vigem->hBusDevice, &lOverlapped, &transfered, TRUE) == 0) + { + if (GetLastError() == ERROR_ACCESS_DENIED) + { + CloseHandle(lOverlapped.hEvent); + return VIGEM_ERROR_INVALID_TARGET; + } + } + + CloseHandle(lOverlapped.hEvent); + + return VIGEM_ERROR_NONE; +} + +ULONG vigem_target_get_index(PVIGEM_TARGET target) +{ + return target->SerialNo; +} + +VIGEM_TARGET_TYPE vigem_target_get_type(PVIGEM_TARGET target) +{ + return target->Type; +} + +BOOL vigem_target_is_attached(PVIGEM_TARGET target) +{ + return (target->State == VIGEM_TARGET_CONNECTED); +} diff --git a/lib/ViGEmClient.rc b/lib/ViGEmClient.rc new file mode 100644 index 0000000..7ae36a9 --- /dev/null +++ b/lib/ViGEmClient.rc @@ -0,0 +1,99 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// German (Austria) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DEA) +LANGUAGE LANG_GERMAN, SUBLANG_GERMAN_AUSTRIAN + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,14,3,0 + PRODUCTVERSION 1,14,3,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x7L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "0c0704b0" + BEGIN + VALUE "CompanyName", "Benjamin Höglinger-Stelzer" + VALUE "FileDescription", "Virtual Gamepad Emulation User-Mode Library" +VALUE "FileVersion", "1.14.3.0" + VALUE "InternalName", "Virtual Gamepad Emulation User-Mode Library" + VALUE "LegalCopyright", "Copyright (C) Benjamin Höglinger-Stelzer 2017" + VALUE "OriginalFilename", "ViGEmCli.lib" + VALUE "ProductName", "Virtual Gamepad Emulation User-Mode Library" +VALUE "ProductVersion", "1.14.3.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0xc07, 1200 + END +END + +#endif // German (Austria) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/lib/ViGEmClient.vcxproj b/lib/ViGEmClient.vcxproj new file mode 100644 index 0000000..57fbbe5 --- /dev/null +++ b/lib/ViGEmClient.vcxproj @@ -0,0 +1,290 @@ + + + + + Debug (dynamic) + Win32 + + + Debug (dynamic) + x64 + + + Debug (static) + Win32 + + + Release (dynamic) + Win32 + + + Release (dynamic) + x64 + + + Release (static) + Win32 + + + Debug (static) + x64 + + + Release (static) + x64 + + + + {7DB06674-1F4F-464B-8E1C-172E9587F9DC} + Win32Proj + ViGEmClient + 10.0.16299.0 + + + + StaticLibrary + true + v141 + Unicode + + + DynamicLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + StaticLibrary + true + v141 + Unicode + + + DynamicLibrary + true + v141 + Unicode + + + StaticLibrary + false + v141 + true + Unicode + + + DynamicLibrary + false + v141 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)Include;$(VC_IncludePath);$(WindowsSDK_IncludePath); + + + $(SolutionDir)Include;$(VC_IncludePath);$(WindowsSDK_IncludePath); + + + $(SolutionDir)Include;$(VC_IncludePath);$(WindowsSDK_IncludePath); + + + $(SolutionDir)Include;$(VC_IncludePath);$(WindowsSDK_IncludePath); + + + $(SolutionDir)Include;$(VC_IncludePath);$(WindowsSDK_IncludePath); + + + $(SolutionDir)Include;$(VC_IncludePath);$(WindowsSDK_IncludePath); + + + $(SolutionDir)Include;$(VC_IncludePath);$(WindowsSDK_IncludePath); + + + $(SolutionDir)Include;$(VC_IncludePath);$(WindowsSDK_IncludePath); + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + + + + + + + Level3 + Disabled + VIGEM_DYNAMIC;VIGEM_EXPORTS;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + setupapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + + + + + + + Level3 + Disabled + VIGEM_DYNAMIC;VIGEM_EXPORTS;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + setupapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + true + + + + + Level3 + + + MaxSpeed + true + true + VIGEM_DYNAMIC;VIGEM_EXPORTS;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + true + setupapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + true + + + + + Level3 + + + MaxSpeed + true + true + VIGEM_DYNAMIC;VIGEM_EXPORTS;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + true + setupapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/ViGEmClient.vcxproj.filters b/lib/ViGEmClient.vcxproj.filters new file mode 100644 index 0000000..b783fc4 --- /dev/null +++ b/lib/ViGEmClient.vcxproj.filters @@ -0,0 +1,44 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/lib/index.rst b/lib/index.rst new file mode 100644 index 0000000..1f971d1 --- /dev/null +++ b/lib/index.rst @@ -0,0 +1,3 @@ +====== +Client +====== diff --git a/lib/resource.h b/lib/resource.h new file mode 100644 index 0000000..ab53b9b --- /dev/null +++ b/lib/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ViGEmClient.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/sys/ByteArray.c b/sys/ByteArray.c new file mode 100644 index 0000000..5968db5 --- /dev/null +++ b/sys/ByteArray.c @@ -0,0 +1,189 @@ +#include "ByteArray.h" + +// +// Helpers +// + +ULONG_PTR align_to_page_size(ULONG_PTR val) +{ + return (val + (PAGE_SIZE - 1)) & -PAGE_SIZE; +} + + +// +// Forward decalarations +// + +NTSTATUS IncreaseCapacityByteArray(IN PBYTE_ARRAY Array, IN ULONG NumElements); + + +// +// Implementation +// + +NTSTATUS InitByteArray(IN OUT PBYTE_ARRAY Array) +{ + // + // Initialize size and default capacity + Array->Size = 0; + Array->Capacity = INITIAL_ARRAY_CAPACITY; + + // + // Allocate memory + Array->Data = (UCHAR*)ExAllocatePoolWithTag(PagedPool, Array->Capacity, ARRAY_POOL_TAG); + if (Array->Data == NULL) + return STATUS_INSUFFICIENT_RESOURCES; + + return STATUS_SUCCESS; +} + +NTSTATUS AppendElementByteArray(IN PBYTE_ARRAY Array, IN PVOID Element) +{ + // + // Make sure there is room to expand into + if (((Array->Size + 1) * sizeof(UCHAR)) > Array->Capacity) + { + // + // Increase capacity + NTSTATUS status = IncreaseCapacityByteArray(Array, sizeof(UCHAR)); + if (!NT_SUCCESS(status)) + return status; + } + + // + // Append the element and increment the size + RtlCopyMemory(Array->Data + (Array->Size * sizeof(UCHAR)), Element, sizeof(UCHAR)); + + // + // Increment size + Array->Size += 1; + + return STATUS_SUCCESS; +} + +NTSTATUS AppendElementsByteArray(IN PBYTE_ARRAY Array, IN PVOID Elements, IN ULONG NumElements) +{ + // + // Make sure there is room to expand into + if ((Array->Size + NumElements) * sizeof(UCHAR) > Array->Capacity) + { + // + // Increase capacity + NTSTATUS status = IncreaseCapacityByteArray(Array, NumElements); + if (!NT_SUCCESS(status)) + return status; + } + + // + // Append the elements and increase the size + RtlCopyMemory(Array->Data + (Array->Size * sizeof(UCHAR)), Elements, NumElements * sizeof(UCHAR)); + + // + // Increase size + Array->Size += NumElements; + + return STATUS_SUCCESS; +} + +NTSTATUS IncreaseCapacityByteArray(IN PBYTE_ARRAY Array, IN ULONG NumElements) +{ + UCHAR* NewData = NULL; + + // + // Align new size to the immediate next page boundary + Array->Capacity = align_to_page_size((Array->Size + NumElements) * sizeof(UCHAR)); + + // + // Allocate new data with new capacity + NewData = (UCHAR*)ExAllocatePoolWithTag(PagedPool, Array->Capacity, ARRAY_POOL_TAG); + if (NewData == NULL) + return STATUS_INSUFFICIENT_RESOURCES; + + // + // Copy old data over + RtlCopyMemory(NewData, Array->Data, Array->Size * sizeof(UCHAR)); + + // + // Free old data + ExFreePoolWithTag(Array->Data, ARRAY_POOL_TAG); + + // + // Set data pointer to new allocation + Array->Data = NewData; + + return STATUS_SUCCESS; +} + +NTSTATUS GetElementByteArray(IN PBYTE_ARRAY Array, IN ULONG Index, OUT PVOID Element) +{ + // + // Check array bounds + if (Index >= Array->Size || (LONG)Index < 0) + return STATUS_ARRAY_BOUNDS_EXCEEDED; + + // + // Copy data over + RtlCopyMemory(Element, Array->Data + (Index * sizeof(UCHAR)), sizeof(UCHAR)); + + return STATUS_SUCCESS; +} + +NTSTATUS GetElementsByteArray(IN PBYTE_ARRAY Array, IN ULONG Index, OUT PVOID Elements, IN ULONG NumElements) +{ + // + // Check array bounds + if (Index >= Array->Size || (LONG)Index < 0) + return STATUS_ARRAY_BOUNDS_EXCEEDED; + + // + // Copy data over + RtlCopyMemory(Elements, Array->Data + (Index * sizeof(UCHAR)), NumElements * sizeof(UCHAR)); + + return STATUS_SUCCESS; +} + +NTSTATUS SetElementByteArray(IN PBYTE_ARRAY Array, IN ULONG Index, IN PVOID Element) +{ + // + // Check array bounds + if (Index >= Array->Size || (LONG)Index < 0) + return STATUS_ARRAY_BOUNDS_EXCEEDED; + + // + // Copy data over + RtlCopyMemory(Array->Data + (Index * sizeof(UCHAR)), Element, sizeof(UCHAR)); + + return STATUS_SUCCESS; +} + +NTSTATUS SetElementsByteArray(IN PBYTE_ARRAY Array, IN ULONG Index, IN PVOID Elements, IN ULONG NumElements) +{ + // + // Check array bounds + if (Index >= Array->Size || (LONG)Index < 0) + return STATUS_ARRAY_BOUNDS_EXCEEDED; + + // + // Copy data over + RtlCopyMemory(Array->Data + (Index * sizeof(UCHAR)), Elements, NumElements * sizeof(UCHAR)); + + return STATUS_SUCCESS; +} + +NTSTATUS FreeByteArray(IN PBYTE_ARRAY Array) +{ + if (Array->Data == NULL) + return STATUS_MEMORY_NOT_ALLOCATED; + + // + // Free data + ExFreePoolWithTag(Array->Data, ARRAY_POOL_TAG); + + // + // Null out everything + Array->Data = NULL; + Array->Size = 0; + Array->Capacity = 0; + + return STATUS_SUCCESS; +} diff --git a/sys/ByteArray.h b/sys/ByteArray.h new file mode 100644 index 0000000..8ae3661 --- /dev/null +++ b/sys/ByteArray.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#define INITIAL_ARRAY_CAPACITY PAGE_SIZE +#define ARRAY_POOL_TAG 'arrA' + +typedef struct _BYTE_ARRAY +{ + UCHAR* Data; //> array of data we're storing + ULONG_PTR Size; //> slots used so far + ULONG_PTR Capacity; //> total available memory +} BYTE_ARRAY, *PBYTE_ARRAY; + +NTSTATUS InitByteArray(IN OUT PBYTE_ARRAY Array); + +NTSTATUS AppendElementByteArray(IN PBYTE_ARRAY Array, IN PVOID Element); + +NTSTATUS AppendElementsByteArray(IN PBYTE_ARRAY Array, IN PVOID Elements, IN ULONG NumElements); + +NTSTATUS GetElementByteArray(IN PBYTE_ARRAY Array, IN ULONG Index, OUT PVOID Element); + +NTSTATUS GetElementsByteArray(IN PBYTE_ARRAY Array, IN ULONG Index, OUT PVOID Elements, IN ULONG NumElements); + +NTSTATUS SetElementByteArray(IN PBYTE_ARRAY Array, IN ULONG Index, IN PVOID Element); + +NTSTATUS SetElementsByteArray(IN PBYTE_ARRAY Array, IN ULONG Index, IN PVOID Elements, IN ULONG NumElements); + +NTSTATUS FreeByteArray(IN PBYTE_ARRAY Array); \ No newline at end of file diff --git a/sys/Context.h b/sys/Context.h new file mode 100644 index 0000000..4bf4bbd --- /dev/null +++ b/sys/Context.h @@ -0,0 +1,173 @@ +/* +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. +*/ + + +#pragma once + +// +// Used to identify children in the device list of the bus. +// +typedef struct _PDO_IDENTIFICATION_DESCRIPTION +{ + WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER Header; // should contain this header + + ULONG SerialNo; + + // + // PID of the process creating this PDO + // + DWORD OwnerProcessId; + + // + // Device type this PDO is emulating + // + VIGEM_TARGET_TYPE TargetType; + + // + // If set, the vendor ID the emulated device is reporting + // + USHORT VendorId; + + // + // If set, the product ID the emulated device is reporting + // + USHORT ProductId; + + // + // Is the current device owner another driver? + // + BOOLEAN OwnerIsDriver; + + // + // SessionId associated with file handle. Used to map file handles to emulated gamepad devices + // + LONG SessionId; + +} PDO_IDENTIFICATION_DESCRIPTION, *PPDO_IDENTIFICATION_DESCRIPTION; + +// +// The PDO device-extension (context). +// +typedef struct _PDO_DEVICE_DATA +{ + // + // Unique serial number of the device on the bus + // + ULONG SerialNo; + + // + // PID of the process creating this PDO + // + DWORD OwnerProcessId; + + // + // Device type this PDO is emulating + // + VIGEM_TARGET_TYPE TargetType; + + // + // If set, the vendor ID the emulated device is reporting + // + USHORT VendorId; + + // + // If set, the product ID the emulated device is reporting + // + USHORT ProductId; + + // + // Interface for PDO to FDO communication + // + VIGEM_BUS_INTERFACE BusInterface; + + // + // Queue for incoming data interrupt transfer + // + WDFQUEUE PendingUsbInRequests; + + // + // Queue for inverted calls + // + WDFQUEUE PendingNotificationRequests; + +} PDO_DEVICE_DATA, *PPDO_DEVICE_DATA; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(PDO_DEVICE_DATA, PdoGetData) + +// +// FDO (bus device) context data +// +typedef struct _FDO_DEVICE_DATA +{ + // + // Counter of interface references + // + LONG InterfaceReferenceCounter; + + // + // Next SessionId to assign to a file handle + // + LONG NextSessionId; + + // + // Collection holding pending plugin requests + // + WDFCOLLECTION PendingPluginRequests; + + // + // Sync lock for pending request collection + // + WDFSPINLOCK PendingPluginRequestsLock; + +} FDO_DEVICE_DATA, *PFDO_DEVICE_DATA; + +#define FDO_FIRST_SESSION_ID 100 + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(FDO_DEVICE_DATA, FdoGetData) + +// +// Context data associated with file objects created by user mode applications +// +typedef struct _FDO_FILE_DATA +{ + // + // SessionId associated with file handle. Used to map file handles to emulated gamepad devices + // + LONG SessionId; + +} FDO_FILE_DATA, *PFDO_FILE_DATA; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(FDO_FILE_DATA, FileObjectGetData) + +// +// Context data for plugin requests +// +typedef struct _FDO_PLUGIN_REQUEST_DATA +{ + ULONG Serial; + +} FDO_PLUGIN_REQUEST_DATA, *PFDO_PLUGIN_REQUEST_DATA; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(FDO_PLUGIN_REQUEST_DATA, PluginRequestGetData) + diff --git a/sys/Driver.c b/sys/Driver.c new file mode 100644 index 0000000..493522c --- /dev/null +++ b/sys/Driver.c @@ -0,0 +1,442 @@ +/* +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 + +#ifdef ALLOC_PRAGMA +#pragma alloc_text (INIT, DriverEntry) +#pragma alloc_text (PAGE, Bus_EvtDeviceAdd) +#pragma alloc_text (PAGE, Bus_DeviceFileCreate) +#pragma alloc_text (PAGE, Bus_FileClose) +#pragma alloc_text (PAGE, Bus_PdoStageResult) +#endif + + +// +// Driver entry routine. +// +NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) +{ + WDF_DRIVER_CONFIG config; + NTSTATUS status; + WDFDRIVER driver; + + KdPrint((DRIVERNAME "Virtual Gamepad Emulation Bus Driver [built: %s %s]\n", __DATE__, __TIME__)); + + WDF_DRIVER_CONFIG_INIT(&config, Bus_EvtDeviceAdd); + + status = WdfDriverCreate(DriverObject, RegistryPath, WDF_NO_OBJECT_ATTRIBUTES, &config, &driver); + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfDriverCreate failed with status 0x%x\n", status)); + } + + return status; +} + +// +// Bus-device creation routine. +// +NTSTATUS Bus_EvtDeviceAdd(IN WDFDRIVER Driver, IN PWDFDEVICE_INIT DeviceInit) +{ + WDF_CHILD_LIST_CONFIG config; + NTSTATUS status; + WDFDEVICE device; + WDF_IO_QUEUE_CONFIG queueConfig; + PNP_BUS_INFORMATION busInfo; + WDFQUEUE queue; + WDF_FILEOBJECT_CONFIG foConfig; + WDF_OBJECT_ATTRIBUTES fdoAttributes; + WDF_OBJECT_ATTRIBUTES fileHandleAttributes; + WDF_OBJECT_ATTRIBUTES collectionAttributes; + PFDO_DEVICE_DATA pFDOData; + VIGEM_BUS_INTERFACE busInterface; + PINTERFACE interfaceHeader; + + UNREFERENCED_PARAMETER(Driver); + + PAGED_CODE(); + + KdPrint((DRIVERNAME "Bus_EvtDeviceAdd: 0x%p\n", Driver)); + + WdfDeviceInitSetDeviceType(DeviceInit, FILE_DEVICE_BUS_EXTENDER); + // More than one process may talk to the bus at the same time + WdfDeviceInitSetExclusive(DeviceInit, FALSE); + // Bus is power policy owner over all PDOs + WdfDeviceInitSetPowerPolicyOwnership(DeviceInit, TRUE); + +#pragma region Prepare child list + + WDF_CHILD_LIST_CONFIG_INIT(&config, sizeof(PDO_IDENTIFICATION_DESCRIPTION), Bus_EvtDeviceListCreatePdo); + + config.EvtChildListIdentificationDescriptionCompare = Bus_EvtChildListIdentificationDescriptionCompare; + + WdfFdoInitSetDefaultChildListConfig(DeviceInit, &config, WDF_NO_OBJECT_ATTRIBUTES); + +#pragma endregion + +#pragma region Assign File Object Configuration + + WDF_FILEOBJECT_CONFIG_INIT(&foConfig, Bus_DeviceFileCreate, Bus_FileClose, NULL); + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&fileHandleAttributes, FDO_FILE_DATA); + + WdfDeviceInitSetFileObjectConfig(DeviceInit, &foConfig, &fileHandleAttributes); + +#pragma endregion + +#pragma region Create FDO + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&fdoAttributes, FDO_DEVICE_DATA); + + status = WdfDeviceCreate(&DeviceInit, &fdoAttributes, &device); + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "Error creating device 0x%x\n", status)); + return status; + } + + KdPrint((DRIVERNAME "Created FDO: 0x%X\n", device)); + + pFDOData = FdoGetData(device); + if (pFDOData == NULL) + { + KdPrint((DRIVERNAME "Error creating device context\n")); + return STATUS_UNSUCCESSFUL; + } + + pFDOData->InterfaceReferenceCounter = 0; + pFDOData->NextSessionId = FDO_FIRST_SESSION_ID; + +#pragma endregion + +#pragma region Create pending requests collection & lock + + WDF_OBJECT_ATTRIBUTES_INIT(&collectionAttributes); + collectionAttributes.ParentObject = device; + + status = WdfCollectionCreate(&collectionAttributes, &pFDOData->PendingPluginRequests); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfCollectionCreate failed with status 0x%X\n", status)); + return STATUS_UNSUCCESSFUL; + } + + WDF_OBJECT_ATTRIBUTES_INIT(&collectionAttributes); + collectionAttributes.ParentObject = device; + + status = WdfSpinLockCreate(&collectionAttributes, &pFDOData->PendingPluginRequestsLock); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfSpinLockCreate failed with status 0x%X\n", status)); + return STATUS_UNSUCCESSFUL; + } + +#pragma endregion + +#pragma region Add query interface + + // + // Set up the common interface header + // + interfaceHeader = &busInterface.InterfaceHeader; + + interfaceHeader->Size = sizeof(VIGEM_BUS_INTERFACE); + interfaceHeader->Version = VIGEM_BUS_INTERFACE_VERSION; + interfaceHeader->Context = (PVOID)device; + + // + // We don't pay any particular attention to the reference + // counting of this interface, but we MUST specify routines for + // it. Luckily the framework provides dummy routines + // + interfaceHeader->InterfaceReference = WdfDeviceInterfaceReferenceNoOp; + interfaceHeader->InterfaceDereference = WdfDeviceInterfaceDereferenceNoOp; + + busInterface.BusPdoStageResult = Bus_PdoStageResult; + + WDF_QUERY_INTERFACE_CONFIG queryInterfaceConfig; + + WDF_QUERY_INTERFACE_CONFIG_INIT(&queryInterfaceConfig, + interfaceHeader, + &GUID_VIGEM_INTERFACE_PDO, + WDF_NO_EVENT_CALLBACK); + + status = WdfDeviceAddQueryInterface(device, + &queryInterfaceConfig); + + if (!NT_SUCCESS(status)) { + KdPrint((DRIVERNAME "WdfDeviceAddQueryInterface failed 0x%0x\n", status)); + return(status); + } + +#pragma endregion + +#pragma region Create default I/O queue for FDO + + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchParallel); + + queueConfig.EvtIoDeviceControl = Bus_EvtIoDeviceControl; + queueConfig.EvtIoInternalDeviceControl = Bus_EvtIoInternalDeviceControl; + queueConfig.EvtIoDefault = Bus_EvtIoDefault; + + __analysis_assume(queueConfig.EvtIoStop != 0); + status = WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &queue); + __analysis_assume(queueConfig.EvtIoStop == 0); + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfIoQueueCreate failed status 0x%x\n", status)); + return status; + } + +#pragma endregion + +#pragma region Expose FDO interface + + status = WdfDeviceCreateDeviceInterface(device, &GUID_DEVINTERFACE_BUSENUM_VIGEM, NULL); + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfDeviceCreateDeviceInterface failed status 0x%x\n", status)); + return status; + } + +#pragma endregion + +#pragma region Set bus information + + busInfo.BusTypeGuid = GUID_BUS_TYPE_USB; + busInfo.LegacyBusType = PNPBus; + busInfo.BusNumber = 0; + + WdfDeviceSetBusInformationForChildren(device, &busInfo); + +#pragma endregion + + return status; +} + +// Gets called when the user-land process (or kernel driver) exits or closes the handle, +// and all IO has completed. +// +_Use_decl_annotations_ +VOID +Bus_DeviceFileCreate( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request, + _In_ WDFFILEOBJECT FileObject +) +{ + NTSTATUS status = STATUS_INVALID_PARAMETER; + PFDO_FILE_DATA pFileData = NULL; + PFDO_DEVICE_DATA pFDOData = NULL; + LONG refCount = 0; + LONG sessionId = 0; + + UNREFERENCED_PARAMETER(Request); + + PAGED_CODE(); + + pFileData = FileObjectGetData(FileObject); + if (pFileData == NULL) + { + KdPrint((DRIVERNAME "Bus_DeviceFileCreate: ERROR! File handle context not found\n")); + } + else + { + pFDOData = FdoGetData(Device); + if (pFDOData == NULL) + { + KdPrint((DRIVERNAME "Bus_DeviceFileCreate: ERROR! FDO context not found\n")); + status = STATUS_NO_SUCH_DEVICE; + } + else + { + refCount = InterlockedIncrement(&pFDOData->InterfaceReferenceCounter); + sessionId = InterlockedIncrement(&pFDOData->NextSessionId); + + pFileData->SessionId = sessionId; + status = STATUS_SUCCESS; + + KdPrint((DRIVERNAME "Bus_DeviceFileCreate: File id=%d. Device refcount=%d\n", (int)sessionId, (int)refCount)); + } + } + + WdfRequestComplete(Request, status); +} + +// +// Gets called when the user-land process (or kernel driver) exits or closes the handle. +// +_Use_decl_annotations_ +VOID +Bus_FileClose( + WDFFILEOBJECT FileObject +) +{ + WDFDEVICE device; + WDFDEVICE hChild; + NTSTATUS status; + WDFCHILDLIST list; + WDF_CHILD_LIST_ITERATOR iterator; + WDF_CHILD_RETRIEVE_INFO childInfo; + PDO_IDENTIFICATION_DESCRIPTION description; + PFDO_FILE_DATA pFileData = NULL; + PFDO_DEVICE_DATA pFDOData = NULL; + LONG refCount = 0; + + PAGED_CODE(); + + KdPrint((DRIVERNAME "Bus_FileClose called\n")); + + // Check common context + pFileData = FileObjectGetData(FileObject); + if (pFileData == NULL) + { + KdPrint((DRIVERNAME "Bus_FileClose: ERROR! File handle context not found\n")); + return; + } + + device = WdfFileObjectGetDevice(FileObject); + + pFDOData = FdoGetData(device); + if (pFDOData == NULL) + { + KdPrint((DRIVERNAME "Bus_FileClose: ERROR! FDO context not found\n")); + status = STATUS_NO_SUCH_DEVICE; + } + else + { + refCount = InterlockedDecrement(&pFDOData->InterfaceReferenceCounter); + + KdPrint((DRIVERNAME "Bus_FileClose: Device refcount=%d\n", (int)refCount)); + } + + list = WdfFdoGetDefaultChildList(device); + + WDF_CHILD_LIST_ITERATOR_INIT(&iterator, WdfRetrievePresentChildren); + + WdfChildListBeginIteration(list, &iterator); + + for (;;) + { + WDF_CHILD_RETRIEVE_INFO_INIT(&childInfo, &description.Header); + WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER_INIT(&description.Header, sizeof(description)); + + status = WdfChildListRetrieveNextDevice(list, &iterator, &hChild, &childInfo); + if (!NT_SUCCESS(status) || status == STATUS_NO_MORE_ENTRIES) + { + break; + } + + KdPrint((DRIVERNAME "Bus_FileClose enumerate: status=%d devicePID=%d currentPID=%d fileSessionId=%d deviceSessionId=%d ownerIsDriver=%d\n", + (int)childInfo.Status, + (int)description.OwnerProcessId, + (int)CURRENT_PROCESS_ID(), + (int)pFileData->SessionId, + (int)description.SessionId, + (int)description.OwnerIsDriver)); + + // Only unplug devices with matching session id + if (childInfo.Status == WdfChildListRetrieveDeviceSuccess + && description.SessionId == pFileData->SessionId + && !description.OwnerIsDriver) + { + KdPrint((DRIVERNAME "Bus_FileClose unplugging\n")); + + // "Unplug" child + status = WdfChildListUpdateChildDescriptionAsMissing(list, &description.Header); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfChildListUpdateChildDescriptionAsMissing failed with status 0x%X\n", status)); + } + } + } + + WdfChildListEndIteration(list, &iterator); +} + +// +// Called by PDO when a boot-up stage has been completed +// +_Use_decl_annotations_ +VOID +Bus_PdoStageResult( + _In_ PINTERFACE InterfaceHeader, + _In_ VIGEM_PDO_STAGE Stage, + _In_ ULONG Serial, + _In_ NTSTATUS Status +) +{ + ULONG i; + PFDO_DEVICE_DATA pFdoData; + WDFREQUEST curRequest; + ULONG curSerial; + ULONG items; + + UNREFERENCED_PARAMETER(InterfaceHeader); + + PAGED_CODE(); + + KdPrint((DRIVERNAME "Bus_PdoStageResult: Stage: %d, Serial: %d, status: 0x%X\n", Stage, Serial, Status)); + + pFdoData = FdoGetData(InterfaceHeader->Context); + + // + // If any stage fails or is last stage, get associated request and complete it + // + if (!NT_SUCCESS(Status) || Stage == ViGEmPdoInternalIoControl) + { + WdfSpinLockAcquire(pFdoData->PendingPluginRequestsLock); + + items = WdfCollectionGetCount(pFdoData->PendingPluginRequests); + + KdPrint((DRIVERNAME "Items count: %d\n", items)); + + for (i = 0; i < items; i++) + { + curRequest = WdfCollectionGetItem(pFdoData->PendingPluginRequests, i); + curSerial = PluginRequestGetData(curRequest)->Serial; + + KdPrint((DRIVERNAME "Serial: %d, curSerial: %d\n", Serial, curSerial)); + + if (Serial == curSerial) + { + WdfRequestComplete(curRequest, Status); + + WdfCollectionRemove(pFdoData->PendingPluginRequests, curRequest); + + KdPrint((DRIVERNAME "Removed item with serial: %d\n", curSerial)); + + break; + } + } + WdfSpinLockRelease(pFdoData->PendingPluginRequestsLock); + } +} diff --git a/sys/Ds4.c b/sys/Ds4.c new file mode 100644 index 0000000..027ee2a --- /dev/null +++ b/sys/Ds4.c @@ -0,0 +1,378 @@ +/* +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 + +NTSTATUS Ds4_PreparePdo(PWDFDEVICE_INIT DeviceInit, PUNICODE_STRING DeviceId, PUNICODE_STRING DeviceDescription) +{ + NTSTATUS status; + UNICODE_STRING buffer; + + // prepare device description + status = RtlUnicodeStringInit(DeviceDescription, L"Virtual DualShock 4 Controller"); + if (!NT_SUCCESS(status)) + return status; + + // Set hardware IDs + RtlUnicodeStringInit(&buffer, L"USB\\VID_054C&PID_05C4&REV_0100"); + + status = WdfPdoInitAddHardwareID(DeviceInit, &buffer); + if (!NT_SUCCESS(status)) + return status; + + RtlUnicodeStringCopy(DeviceId, &buffer); + + RtlUnicodeStringInit(&buffer, L"USB\\VID_054C&PID_05C4"); + + status = WdfPdoInitAddHardwareID(DeviceInit, &buffer); + if (!NT_SUCCESS(status)) + return status; + + // Set compatible IDs + RtlUnicodeStringInit(&buffer, L"USB\\Class_03&SubClass_00&Prot_00"); + + status = WdfPdoInitAddCompatibleID(DeviceInit, &buffer); + if (!NT_SUCCESS(status)) + return status; + + RtlUnicodeStringInit(&buffer, L"USB\\Class_03&SubClass_00"); + + status = WdfPdoInitAddCompatibleID(DeviceInit, &buffer); + if (!NT_SUCCESS(status)) + return status; + + RtlUnicodeStringInit(&buffer, L"USB\\Class_03"); + + status = WdfPdoInitAddCompatibleID(DeviceInit, &buffer); + if (!NT_SUCCESS(status)) + return status; + + return STATUS_SUCCESS; +} + +NTSTATUS Ds4_PrepareHardware(WDFDEVICE Device) +{ + NTSTATUS status; + WDF_QUERY_INTERFACE_CONFIG ifaceCfg; + INTERFACE devinterfaceHid; + + devinterfaceHid.Size = sizeof(INTERFACE); + devinterfaceHid.Version = 1; + devinterfaceHid.Context = (PVOID)Device; + + devinterfaceHid.InterfaceReference = WdfDeviceInterfaceReferenceNoOp; + devinterfaceHid.InterfaceDereference = WdfDeviceInterfaceDereferenceNoOp; + + // Expose GUID_DEVINTERFACE_HID so HIDUSB can initialize + WDF_QUERY_INTERFACE_CONFIG_INIT(&ifaceCfg, (PINTERFACE)&devinterfaceHid, &GUID_DEVINTERFACE_HID, NULL); + + status = WdfDeviceAddQueryInterface(Device, &ifaceCfg); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfDeviceAddQueryInterface failed status 0x%x\n", status)); + return status; + } + + PDS4_DEVICE_DATA ds4Data = Ds4GetData(Device); + + // Set default HID input report (everything zero`d) + UCHAR DefaultHidReport[DS4_REPORT_SIZE] = + { + 0x01, 0x82, 0x7F, 0x7E, 0x80, 0x08, 0x00, 0x58, + 0x00, 0x00, 0xFD, 0x63, 0x06, 0x03, 0x00, 0xFE, + 0xFF, 0xFC, 0xFF, 0x79, 0xFD, 0x1B, 0x14, 0xD1, + 0xE9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00 + }; + + // Initialize HID reports to defaults + RtlCopyBytes(ds4Data->Report, DefaultHidReport, DS4_REPORT_SIZE); + RtlZeroMemory(&ds4Data->OutputReport, sizeof(DS4_OUTPUT_REPORT)); + + // Start pending IRP queue flush timer + WdfTimerStart(ds4Data->PendingUsbInRequestsTimer, DS4_QUEUE_FLUSH_PERIOD); + + return STATUS_SUCCESS; +} + +NTSTATUS Ds4_AssignPdoContext(WDFDEVICE Device, PPDO_IDENTIFICATION_DESCRIPTION Description) +{ + NTSTATUS status; + PDS4_DEVICE_DATA ds4 = Ds4GetData(Device); + + KdPrint((DRIVERNAME "Initializing DS4 context...\n")); + + // Initialize periodic timer + WDF_TIMER_CONFIG timerConfig; + WDF_TIMER_CONFIG_INIT_PERIODIC(&timerConfig, Ds4_PendingUsbRequestsTimerFunc, DS4_QUEUE_FLUSH_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, &ds4->PendingUsbInRequestsTimer); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfTimerCreate failed 0x%x\n", status)); + return status; + } + + // Load/generate MAC address + + // TODO: tidy up this region + + WDFKEY keyParams, keyTargets, keyDS, keySerial; + UNICODE_STRING keyName, valueName; + + status = WdfDriverOpenParametersRegistryKey(WdfGetDriver(), STANDARD_RIGHTS_ALL, WDF_NO_OBJECT_ATTRIBUTES, &keyParams); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfDriverOpenParametersRegistryKey failed 0x%x\n", status)); + return status; + } + + RtlUnicodeStringInit(&keyName, L"Targets"); + + status = WdfRegistryCreateKey(keyParams, &keyName, + KEY_ALL_ACCESS, REG_OPTION_NON_VOLATILE, NULL, WDF_NO_OBJECT_ATTRIBUTES, &keyTargets); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfRegistryCreateKey failed 0x%x\n", status)); + return status; + } + + RtlUnicodeStringInit(&keyName, L"DualShock"); + + status = WdfRegistryCreateKey(keyTargets, &keyName, + KEY_ALL_ACCESS, REG_OPTION_NON_VOLATILE, NULL, WDF_NO_OBJECT_ATTRIBUTES, &keyDS); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfRegistryCreateKey failed 0x%x\n", status)); + return status; + } + + DECLARE_UNICODE_STRING_SIZE(serialPath, 4); + RtlUnicodeStringPrintf(&serialPath, L"%04d", Description->SerialNo); + + status = WdfRegistryCreateKey(keyDS, &serialPath, + KEY_ALL_ACCESS, REG_OPTION_NON_VOLATILE, NULL, WDF_NO_OBJECT_ATTRIBUTES, &keySerial); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfRegistryCreateKey failed 0x%x\n", status)); + return status; + } + + RtlUnicodeStringInit(&valueName, L"TargetMacAddress"); + + status = WdfRegistryQueryValue(keySerial, &valueName, sizeof(MAC_ADDRESS), &ds4->TargetMacAddress, NULL, NULL); + + KdPrint((DRIVERNAME "MAC-Address: %02X:%02X:%02X:%02X:%02X:%02X\n", + ds4->TargetMacAddress.Vendor0, + ds4->TargetMacAddress.Vendor1, + ds4->TargetMacAddress.Vendor2, + ds4->TargetMacAddress.Nic0, + ds4->TargetMacAddress.Nic1, + ds4->TargetMacAddress.Nic2)); + + if (status == STATUS_OBJECT_NAME_NOT_FOUND) + { + GenerateRandomMacAddress(&ds4->TargetMacAddress); + + status = WdfRegistryAssignValue(keySerial, &valueName, REG_BINARY, sizeof(MAC_ADDRESS), (PVOID)&ds4->TargetMacAddress); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfRegistryAssignValue failed 0x%x\n", status)); + return status; + } + } + else if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfRegistryQueryValue failed 0x%x\n", status)); + return status; + } + + WdfRegistryClose(keySerial); + WdfRegistryClose(keyDS); + WdfRegistryClose(keyTargets); + WdfRegistryClose(keyParams); + + return STATUS_SUCCESS; +} + +VOID Ds4_GetConfigurationDescriptorType(PUCHAR Buffer, ULONG Length) +{ + UCHAR Ds4DescriptorData[DS4_DESCRIPTOR_SIZE] = + { + 0x09, // bLength + 0x02, // bDescriptorType (Configuration) + 0x29, 0x00, // wTotalLength 41 + 0x01, // bNumInterfaces 1 + 0x01, // bConfigurationValue + 0x00, // iConfiguration (String Index) + 0xC0, // bmAttributes Self Powered + 0xFA, // bMaxPower 500mA + + 0x09, // bLength + 0x04, // bDescriptorType (Interface) + 0x00, // bInterfaceNumber 0 + 0x00, // bAlternateSetting + 0x02, // bNumEndpoints 2 + 0x03, // bInterfaceClass + 0x00, // bInterfaceSubClass + 0x00, // bInterfaceProtocol + 0x00, // iInterface (String Index) + + 0x09, // bLength + 0x21, // bDescriptorType (HID) + 0x11, 0x01, // bcdHID 1.11 + 0x00, // bCountryCode + 0x01, // bNumDescriptors + 0x22, // bDescriptorType[0] (HID) + 0xD3, 0x01, // wDescriptorLength[0] 467 + + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x84, // bEndpointAddress (IN/D2H) + 0x03, // bmAttributes (Interrupt) + 0x40, 0x00, // wMaxPacketSize 64 + 0x05, // bInterval 5 (unit depends on device speed) + + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x03, // bEndpointAddress (OUT/H2D) + 0x03, // bmAttributes (Interrupt) + 0x40, 0x00, // wMaxPacketSize 64 + 0x05, // bInterval 5 (unit depends on device speed) + + // 41 bytes + + // best guess: USB Standard Descriptor + }; + + RtlCopyBytes(Buffer, Ds4DescriptorData, Length); +} + +VOID Ds4_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 = 0x00; // per Interface + pDescriptor->bDeviceSubClass = 0x00; + pDescriptor->bDeviceProtocol = 0x00; + pDescriptor->bMaxPacketSize0 = 0x40; + pDescriptor->idVendor = pCommon->VendorId; + pDescriptor->idProduct = pCommon->ProductId; + pDescriptor->bcdDevice = 0x0100; + pDescriptor->iManufacturer = 0x01; + pDescriptor->iProduct = 0x02; + pDescriptor->iSerialNumber = 0x00; + pDescriptor->bNumConfigurations = 0x01; +} + +VOID Ds4_SelectConfiguration(PUSBD_INTERFACE_INFORMATION pInfo) +{ + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: Length %d, Interface %d, Alternate %d, Pipes %d\n", + (int)pInfo->Length, + (int)pInfo->InterfaceNumber, + (int)pInfo->AlternateSetting, + pInfo->NumberOfPipes)); + + pInfo->Class = 0x03; // HID + pInfo->SubClass = 0x00; + pInfo->Protocol = 0x00; + + pInfo->InterfaceHandle = (USBD_INTERFACE_HANDLE)0xFFFF0000; + + pInfo->Pipes[0].MaximumTransferSize = 0x00400000; + pInfo->Pipes[0].MaximumPacketSize = 0x40; + pInfo->Pipes[0].EndpointAddress = 0x84; + pInfo->Pipes[0].Interval = 0x05; + pInfo->Pipes[0].PipeType = 0x03; + pInfo->Pipes[0].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0084; + pInfo->Pipes[0].PipeFlags = 0x00; + + pInfo->Pipes[1].MaximumTransferSize = 0x00400000; + pInfo->Pipes[1].MaximumPacketSize = 0x40; + pInfo->Pipes[1].EndpointAddress = 0x03; + pInfo->Pipes[1].Interval = 0x05; + pInfo->Pipes[1].PipeType = 0x03; + pInfo->Pipes[1].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0003; + pInfo->Pipes[1].PipeFlags = 0x00; +} + +// +// Completes pending I/O requests if feeder is too slow. +// +VOID Ds4_PendingUsbRequestsTimerFunc( + _In_ WDFTIMER Timer +) +{ + NTSTATUS status; + WDFREQUEST usbRequest; + WDFDEVICE hChild; + PDS4_DEVICE_DATA ds4Data; + PIRP pendingIrp; + PIO_STACK_LOCATION irpStack; + PPDO_DEVICE_DATA pdoData; + + hChild = WdfTimerGetParentObject(Timer); + pdoData = PdoGetData(hChild); + ds4Data = Ds4GetData(hChild); + + // Get pending USB request + status = WdfIoQueueRetrieveNextRequest(pdoData->PendingUsbInRequests, &usbRequest); + + if (NT_SUCCESS(status)) + { + // Get pending IRP + pendingIrp = WdfRequestWdmGetIrp(usbRequest); + irpStack = IoGetCurrentIrpStackLocation(pendingIrp); + + // Get USB request block + PURB urb = (PURB)irpStack->Parameters.Others.Argument1; + + // Get transfer buffer + PUCHAR Buffer = (PUCHAR)urb->UrbBulkOrInterruptTransfer.TransferBuffer; + // Set buffer length to report size + urb->UrbBulkOrInterruptTransfer.TransferBufferLength = DS4_REPORT_SIZE; + + // Copy cached report to transfer buffer + if (Buffer) + RtlCopyBytes(Buffer, ds4Data->Report, DS4_REPORT_SIZE); + + // Complete pending request + WdfRequestComplete(usbRequest, status); + } +} + diff --git a/sys/Ds4.h b/sys/Ds4.h new file mode 100644 index 0000000..b590282 --- /dev/null +++ b/sys/Ds4.h @@ -0,0 +1,112 @@ +/* +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. +*/ + + +#pragma once + +#define HID_GET_FEATURE_REPORT_SIZE_0 0x31 +#define HID_GET_FEATURE_REPORT_SIZE_1 0x25 +#define HID_GET_FEATURE_REPORT_MAC_ADDRESSES_SIZE 0x10 + +#define HID_SET_FEATURE_REPORT_SIZE_0 0x17 +#define HID_SET_FEATURE_REPORT_SIZE_1 0x11 + +#define HID_REPORT_ID_0 0xA3 +#define HID_REPORT_ID_1 0x02 +#define HID_REPORT_MAC_ADDRESSES_ID 0x12 +#define HID_REPORT_ID_3 0x13 +#define HID_REPORT_ID_4 0x14 + +#define DS4_DESCRIPTOR_SIZE 0x0029 +#if defined(_X86_) +#define DS4_CONFIGURATION_SIZE 0x0050 +#else +#define DS4_CONFIGURATION_SIZE 0x0070 +#endif +#define DS4_HID_REPORT_DESCRIPTOR_SIZE 0x01D3 + +#define DS4_MANUFACTURER_NAME_LENGTH 0x38 +#define DS4_PRODUCT_NAME_LENGTH 0x28 +#define DS4_OUTPUT_BUFFER_OFFSET 0x04 +#define DS4_OUTPUT_BUFFER_LENGTH 0x05 + +#define DS4_REPORT_SIZE 0x40 +#define DS4_QUEUE_FLUSH_PERIOD 0x05 + + +// +// DS4-specific device context data. +// +typedef struct _DS4_DEVICE_DATA +{ + // + // HID Input Report buffer + // + UCHAR Report[DS4_REPORT_SIZE]; + + // + // Output report cache + // + DS4_OUTPUT_REPORT OutputReport; + + // + // Timer for dispatching interrupt transfer + // + WDFTIMER PendingUsbInRequestsTimer; + + // + // Auto-generated MAC address of the target device + // + MAC_ADDRESS TargetMacAddress; + + // + // Default MAC address of the host (not used) + // + MAC_ADDRESS HostMacAddress; + +} DS4_DEVICE_DATA, *PDS4_DEVICE_DATA; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DS4_DEVICE_DATA, Ds4GetData) + + +EVT_WDF_TIMER Ds4_PendingUsbRequestsTimerFunc; + +NTSTATUS +Bus_Ds4SubmitReport( + WDFDEVICE Device, + ULONG SerialNo, + PDS4_SUBMIT_REPORT Report, + _In_ BOOLEAN FromInterface +); + +// +// DS4-specific functions +// +NTSTATUS Ds4_PreparePdo(PWDFDEVICE_INIT DeviceInit, PUNICODE_STRING DeviceId, PUNICODE_STRING DeviceDescription); +NTSTATUS Ds4_PrepareHardware(WDFDEVICE Device); +NTSTATUS Ds4_AssignPdoContext(WDFDEVICE Device, PPDO_IDENTIFICATION_DESCRIPTION Description); +VOID Ds4_GetConfigurationDescriptorType(PUCHAR Buffer, ULONG Length); +VOID Ds4_GetDeviceDescriptorType(PUSB_DEVICE_DESCRIPTOR pDescriptor, PPDO_DEVICE_DATA pCommon); +VOID Ds4_SelectConfiguration(PUSBD_INTERFACE_INFORMATION pInfo); + diff --git a/sys/Queue.c b/sys/Queue.c new file mode 100644 index 0000000..08f211e --- /dev/null +++ b/sys/Queue.c @@ -0,0 +1,349 @@ +/* +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" + +#ifdef ALLOC_PRAGMA +#pragma alloc_text (PAGE, Bus_EvtIoDefault) +#endif + +// +// Responds to I/O control requests sent to the FDO. +// +VOID Bus_EvtIoDeviceControl( + IN WDFQUEUE Queue, + IN WDFREQUEST Request, + IN size_t OutputBufferLength, + IN size_t InputBufferLength, + IN ULONG IoControlCode +) +{ + NTSTATUS status = STATUS_INVALID_PARAMETER; + WDFDEVICE Device; + size_t length = 0; + PXUSB_SUBMIT_REPORT xusbSubmit = NULL; + PXUSB_REQUEST_NOTIFICATION xusbNotify = NULL; + PDS4_SUBMIT_REPORT ds4Submit = NULL; + PDS4_REQUEST_NOTIFICATION ds4Notify = NULL; + PXGIP_SUBMIT_REPORT xgipSubmit = NULL; + PXGIP_SUBMIT_INTERRUPT xgipInterrupt = NULL; + PVIGEM_CHECK_VERSION pCheckVersion = NULL; + + Device = WdfIoQueueGetDevice(Queue); + + KdPrint((DRIVERNAME "Bus_EvtIoDeviceControl: 0x%p\n", Device)); + + switch (IoControlCode) + { +#pragma region IOCTL_VIGEM_CHECK_VERSION + case IOCTL_VIGEM_CHECK_VERSION: + + KdPrint((DRIVERNAME "IOCTL_VIGEM_CHECK_VERSION\n")); + + status = WdfRequestRetrieveInputBuffer(Request, sizeof(VIGEM_CHECK_VERSION), (PVOID)&pCheckVersion, &length); + + if (!NT_SUCCESS(status) || length != sizeof(VIGEM_CHECK_VERSION)) + { + status = STATUS_INVALID_PARAMETER; + break; + } + + status = (pCheckVersion->Version == VIGEM_COMMON_VERSION) ? STATUS_SUCCESS : STATUS_NOT_SUPPORTED; + + break; +#pragma endregion + +#pragma region IOCTL_VIGEM_PLUGIN_TARGET + case IOCTL_VIGEM_PLUGIN_TARGET: + + KdPrint((DRIVERNAME "IOCTL_VIGEM_PLUGIN_TARGET\n")); + + status = Bus_PlugInDevice(Device, Request, FALSE, &length); + + break; +#pragma endregion + +#pragma region IOCTL_VIGEM_UNPLUG_TARGET + case IOCTL_VIGEM_UNPLUG_TARGET: + + KdPrint((DRIVERNAME "IOCTL_VIGEM_UNPLUG_TARGET\n")); + + status = Bus_UnPlugDevice(Device, Request, FALSE, &length); + + break; +#pragma endregion + +#pragma region IOCTL_XUSB_SUBMIT_REPORT + case IOCTL_XUSB_SUBMIT_REPORT: + + KdPrint((DRIVERNAME "IOCTL_XUSB_SUBMIT_REPORT\n")); + + status = WdfRequestRetrieveInputBuffer(Request, sizeof(XUSB_SUBMIT_REPORT), (PVOID)&xusbSubmit, &length); + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfRequestRetrieveInputBuffer failed 0x%x\n", status)); + break; + } + + if ((sizeof(XUSB_SUBMIT_REPORT) == xusbSubmit->Size) && (length == InputBufferLength)) + { + // This request only supports a single PDO at a time + if (xusbSubmit->SerialNo == 0) + { + status = STATUS_INVALID_PARAMETER; + break; + } + + status = Bus_XusbSubmitReport(Device, xusbSubmit->SerialNo, xusbSubmit, FALSE); + } + + break; +#pragma endregion + +#pragma region IOCTL_XUSB_REQUEST_NOTIFICATION + case IOCTL_XUSB_REQUEST_NOTIFICATION: + + KdPrint((DRIVERNAME "IOCTL_XUSB_REQUEST_NOTIFICATION\n")); + + // Don't accept the request if the output buffer can't hold the results + if (OutputBufferLength < sizeof(XUSB_REQUEST_NOTIFICATION)) + { + KdPrint((DRIVERNAME "IOCTL_XUSB_REQUEST_NOTIFICATION: output buffer too small: %ul\n", OutputBufferLength)); + break; + } + + status = WdfRequestRetrieveInputBuffer(Request, sizeof(XUSB_REQUEST_NOTIFICATION), (PVOID)&xusbNotify, &length); + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfRequestRetrieveInputBuffer failed 0x%x\n", status)); + break; + } + + if ((sizeof(XUSB_REQUEST_NOTIFICATION) == xusbNotify->Size) && (length == InputBufferLength)) + { + // This request only supports a single PDO at a time + if (xusbNotify->SerialNo == 0) + { + status = STATUS_INVALID_PARAMETER; + break; + } + + status = Bus_QueueNotification(Device, xusbNotify->SerialNo, Request); + } + + break; +#pragma endregion + +#pragma region IOCTL_DS4_SUBMIT_REPORT + case IOCTL_DS4_SUBMIT_REPORT: + + KdPrint((DRIVERNAME "IOCTL_DS4_SUBMIT_REPORT\n")); + + status = WdfRequestRetrieveInputBuffer(Request, sizeof(DS4_SUBMIT_REPORT), (PVOID)&ds4Submit, &length); + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfRequestRetrieveInputBuffer failed 0x%x\n", status)); + break; + } + + if ((sizeof(DS4_SUBMIT_REPORT) == ds4Submit->Size) && (length == InputBufferLength)) + { + // This request only supports a single PDO at a time + if (ds4Submit->SerialNo == 0) + { + status = STATUS_INVALID_PARAMETER; + break; + } + + status = Bus_Ds4SubmitReport(Device, ds4Submit->SerialNo, ds4Submit, FALSE); + } + + break; +#pragma endregion + +#pragma region IOCTL_DS4_REQUEST_NOTIFICATION + case IOCTL_DS4_REQUEST_NOTIFICATION: + + KdPrint((DRIVERNAME "IOCTL_DS4_REQUEST_NOTIFICATION\n")); + + // Don't accept the request if the output buffer can't hold the results + if (OutputBufferLength < sizeof(DS4_REQUEST_NOTIFICATION)) + { + KdPrint((DRIVERNAME "IOCTL_DS4_REQUEST_NOTIFICATION: output buffer too small: %ul\n", OutputBufferLength)); + break; + } + + status = WdfRequestRetrieveInputBuffer(Request, sizeof(DS4_REQUEST_NOTIFICATION), (PVOID)&ds4Notify, &length); + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfRequestRetrieveInputBuffer failed 0x%x\n", status)); + break; + } + + if ((sizeof(DS4_REQUEST_NOTIFICATION) == ds4Notify->Size) && (length == InputBufferLength)) + { + // This request only supports a single PDO at a time + if (ds4Notify->SerialNo == 0) + { + status = STATUS_INVALID_PARAMETER; + break; + } + + status = Bus_QueueNotification(Device, ds4Notify->SerialNo, Request); + } + + break; +#pragma endregion + +#pragma region IOCTL_XGIP_SUBMIT_REPORT + case IOCTL_XGIP_SUBMIT_REPORT: + + KdPrint((DRIVERNAME "IOCTL_XGIP_SUBMIT_REPORT\n")); + + status = WdfRequestRetrieveInputBuffer(Request, sizeof(XGIP_SUBMIT_REPORT), (PVOID)&xgipSubmit, &length); + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfRequestRetrieveInputBuffer failed 0x%x\n", status)); + break; + } + + if ((sizeof(XGIP_SUBMIT_REPORT) == xgipSubmit->Size) && (length == InputBufferLength)) + { + // This request only supports a single PDO at a time + if (xgipSubmit->SerialNo == 0) + { + status = STATUS_INVALID_PARAMETER; + break; + } + + status = Bus_XgipSubmitReport(Device, xgipSubmit->SerialNo, xgipSubmit, FALSE); + } + + break; +#pragma endregion + +#pragma region IOCTL_XGIP_SUBMIT_INTERRUPT + case IOCTL_XGIP_SUBMIT_INTERRUPT: + + KdPrint((DRIVERNAME "IOCTL_XGIP_SUBMIT_INTERRUPT\n")); + + status = WdfRequestRetrieveInputBuffer(Request, sizeof(XGIP_SUBMIT_INTERRUPT), (PVOID)&xgipInterrupt, &length); + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfRequestRetrieveInputBuffer failed 0x%x\n", status)); + break; + } + + if ((sizeof(XGIP_SUBMIT_INTERRUPT) == xgipInterrupt->Size) && (length == InputBufferLength)) + { + // This request only supports a single PDO at a time + if (xgipInterrupt->SerialNo == 0) + { + status = STATUS_INVALID_PARAMETER; + break; + } + + status = Bus_XgipSubmitInterrupt(Device, xgipSubmit->SerialNo, xgipInterrupt, FALSE); + } + + break; +#pragma endregion + + default: + KdPrint((DRIVERNAME "UNKNOWN IOCTL CODE 0x%x\n", IoControlCode)); + break; // default status is STATUS_INVALID_PARAMETER + } + + if (status != STATUS_PENDING) + { + WdfRequestCompleteWithInformation(Request, status, length); + } +} + +VOID Bus_EvtIoInternalDeviceControl( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request, + _In_ size_t OutputBufferLength, + _In_ size_t InputBufferLength, + _In_ ULONG IoControlCode +) +{ + NTSTATUS status = STATUS_INVALID_PARAMETER; + WDFDEVICE Device; + size_t length = 0; + + UNREFERENCED_PARAMETER(OutputBufferLength); + UNREFERENCED_PARAMETER(InputBufferLength); + + Device = WdfIoQueueGetDevice(Queue); + + KdPrint((DRIVERNAME "Bus_EvtIoInternalDeviceControl: 0x%p\n", Device)); + + switch (IoControlCode) + { + case IOCTL_VIGEM_PLUGIN_TARGET: + + KdPrint((DRIVERNAME "IOCTL_VIGEM_PLUGIN_TARGET\n")); + + status = Bus_PlugInDevice(Device, Request, TRUE, &length); + + break; + + case IOCTL_VIGEM_UNPLUG_TARGET: + + KdPrint((DRIVERNAME "IOCTL_VIGEM_UNPLUG_TARGET\n")); + + status = Bus_UnPlugDevice(Device, Request, TRUE, &length); + + break; + } + + if (status != STATUS_PENDING) + { + WdfRequestCompleteWithInformation(Request, status, length); + } +} + +// +// Catches unsupported requests. +// +VOID Bus_EvtIoDefault( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request +) +{ + UNREFERENCED_PARAMETER(Queue); + UNREFERENCED_PARAMETER(Request); + + KdPrint((DRIVERNAME "Bus_EvtIoDefault called\n")); + + WdfRequestComplete(Request, STATUS_INVALID_DEVICE_REQUEST); +} \ No newline at end of file diff --git a/sys/Queue.h b/sys/Queue.h new file mode 100644 index 0000000..d2956f8 --- /dev/null +++ b/sys/Queue.h @@ -0,0 +1,30 @@ +/* +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. +*/ + + +#pragma once + +EVT_WDF_IO_QUEUE_IO_DEFAULT Bus_EvtIoDefault; +EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL Bus_EvtIoDeviceControl; +EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL Bus_EvtIoInternalDeviceControl; diff --git a/sys/README.md b/sys/README.md new file mode 100644 index 0000000..03e6a90 --- /dev/null +++ b/sys/README.md @@ -0,0 +1,17 @@ +# ViGEm Bus Driver + +Currently supports emulation of the following USB gamepads: +- [Microsoft Xbox 360 Controller](https://en.wikipedia.org/wiki/Xbox_360_controller) +- [Sony DualShock 4 Controller](https://en.wikipedia.org/wiki/DualShock#DualShock_4) +- [Microsoft Xbox One Controller](https://en.wikipedia.org/wiki/Xbox_One_Controller) + - Experimental; not ready for stable release yet + +## Necessary preparations for Windows 7 +Before installing the bus driver on Windows 7 (x86 or x64) the following 3rd party software has to be installed: + * [Xbox 360 Accessories Software 1.2](https://www.microsoft.com/accessories/en-us/products/gaming/xbox-360-controller-for-windows/52a-00004#techspecs-connect) (contains the missing device drivers) + * [Microsoft Security Advisory 3033929 Update](https://technet.microsoft.com/en-us/library/security/3033929) has to be installed to support the drivers signature. Download links: + * [Security Update for Windows 7 (KB3033929)](https://www.microsoft.com/en-us/download/details.aspx?id=46078) + * [Security Update for Windows 7 for x64-based Systems (KB3033929)](https://www.microsoft.com/en-us/download/details.aspx?id=46148) + +## Installation +[Follow the installation instructions](https://github.com/nefarius/ViGEm/wiki/Driver-Installation). diff --git a/sys/UsbPdo.h b/sys/UsbPdo.h new file mode 100644 index 0000000..42305cc --- /dev/null +++ b/sys/UsbPdo.h @@ -0,0 +1,51 @@ +/* +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. +*/ + + +#pragma once + +BOOLEAN USB_BUSIFFN UsbPdo_IsDeviceHighSpeed(IN PVOID BusContext); +NTSTATUS USB_BUSIFFN UsbPdo_QueryBusInformation( + IN PVOID BusContext, + IN ULONG Level, + IN OUT PVOID BusInformationBuffer, + IN OUT PULONG BusInformationBufferLength, + OUT PULONG BusInformationActualLength +); +NTSTATUS USB_BUSIFFN UsbPdo_SubmitIsoOutUrb(IN PVOID BusContext, IN PURB Urb); +NTSTATUS USB_BUSIFFN UsbPdo_QueryBusTime(IN PVOID BusContext, IN OUT PULONG CurrentUsbFrame); +VOID USB_BUSIFFN UsbPdo_GetUSBDIVersion( + IN PVOID BusContext, + IN OUT PUSBD_VERSION_INFORMATION VersionInformation, + IN OUT PULONG HcdCapabilities +); +NTSTATUS UsbPdo_GetDeviceDescriptorType(PURB urb, PPDO_DEVICE_DATA pCommon); +NTSTATUS UsbPdo_GetConfigurationDescriptorType(PURB urb, PPDO_DEVICE_DATA pCommon); +NTSTATUS UsbPdo_GetStringDescriptorType(PURB urb, PPDO_DEVICE_DATA pCommon); +NTSTATUS UsbPdo_SelectConfiguration(PURB urb, PPDO_DEVICE_DATA pCommon); +NTSTATUS UsbPdo_SelectInterface(PURB urb, PPDO_DEVICE_DATA pCommon); +NTSTATUS UsbPdo_BulkOrInterruptTransfer(PURB urb, WDFDEVICE Device, WDFREQUEST Request); +NTSTATUS UsbPdo_AbortPipe(WDFDEVICE Device); +NTSTATUS UsbPdo_ClassInterface(PURB urb, WDFDEVICE Device, PPDO_DEVICE_DATA pCommon); +NTSTATUS UsbPdo_GetDescriptorFromInterface(PURB urb, PPDO_DEVICE_DATA pCommon); diff --git a/sys/Util.h b/sys/Util.h new file mode 100644 index 0000000..f91464c --- /dev/null +++ b/sys/Util.h @@ -0,0 +1,50 @@ +/* +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. +*/ + + +#pragma once + +// +// Returns the current caller process id. +// +#define CURRENT_PROCESS_ID() ((DWORD)((DWORD_PTR)PsGetCurrentProcessId() & 0xFFFFFFFF)) + +#define IS_OWNER(_pdo_) (_pdo_->OwnerProcessId == CURRENT_PROCESS_ID()) + +// +// Represents a MAC address. +// +typedef struct _MAC_ADDRESS +{ + UCHAR Vendor0; + UCHAR Vendor1; + UCHAR Vendor2; + UCHAR Nic0; + UCHAR Nic1; + UCHAR Nic2; +} MAC_ADDRESS, *PMAC_ADDRESS; + + +VOID ReverseByteArray(PUCHAR Array, INT Length); +VOID GenerateRandomMacAddress(PMAC_ADDRESS Address); diff --git a/sys/ViGEmBus.inf b/sys/ViGEmBus.inf new file mode 100644 index 0000000..11d620e --- /dev/null +++ b/sys/ViGEmBus.inf @@ -0,0 +1,78 @@ +; +; ViGEmBus.inf +; + +[Version] +Signature="$WINDOWS NT$" +Class=System +ClassGuid={4D36E97D-E325-11CE-BFC1-08002BE10318} +Provider=%ManufacturerName% +CatalogFile=ViGEmBus.cat +DriverVer= ; + +[DestinationDirs] +DefaultDestDir = 12 +ViGEmBus_Device_CoInstaller_CopyFiles = 11 + +; ================= Class section ===================== + +[SourceDisksNames] +1 = %DiskName%,,,"" + +[SourceDisksFiles] +ViGEmBus.sys = 1,, +WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll=1 + +;***************************************** +; Install Section +;***************************************** + +[Manufacturer] +%ManufacturerName%=Standard,NT$ARCH$ + +[Standard.NT$ARCH$] +%ViGEmBus.DeviceDesc%=ViGEmBus_Device, Root\ViGEmBus + +[ViGEmBus_Device.NT] +CopyFiles=Drivers_Dir + +[Drivers_Dir] +ViGEmBus.sys + +;-------------- Service installation +[ViGEmBus_Device.NT.Services] +AddService = ViGEmBus,%SPSVCINST_ASSOCSERVICE%, ViGEmBus_Service_Inst + +; -------------- ViGEmBus driver install sections +[ViGEmBus_Service_Inst] +DisplayName = %ViGEmBus.SVCDESC% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\ViGEmBus.sys + +; +;--- ViGEmBus_Device Coinstaller installation ------ +; + +[ViGEmBus_Device.NT.CoInstallers] +AddReg=ViGEmBus_Device_CoInstaller_AddReg +CopyFiles=ViGEmBus_Device_CoInstaller_CopyFiles + +[ViGEmBus_Device_CoInstaller_AddReg] +HKR,,CoInstallers32,0x00010000, "WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll,WdfCoInstaller" + +[ViGEmBus_Device_CoInstaller_CopyFiles] +WdfCoInstaller$KMDFCOINSTALLERVERSION$.dll + +[ViGEmBus_Device.NT.Wdf] +KmdfService = ViGEmBus, ViGEmBus_wdfsect +[ViGEmBus_wdfsect] +KmdfLibraryVersion = $KMDFVERSION$ + +[Strings] +SPSVCINST_ASSOCSERVICE= 0x00000002 +ManufacturerName="Benjamin Höglinger-Stelzer" +DiskName = "ViGEmBus Installation Disk" +ViGEmBus.DeviceDesc = "Virtual Gamepad Emulation Bus" +ViGEmBus.SVCDESC = "Virtual Gamepad Emulation Service" diff --git a/sys/ViGEmBus.rc b/sys/ViGEmBus.rc new file mode 100644 index 0000000..77cb42b --- /dev/null +++ b/sys/ViGEmBus.rc @@ -0,0 +1,100 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// German (Austria) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DEA) +LANGUAGE LANG_GERMAN, SUBLANG_GERMAN_AUSTRIAN +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,14,3,0 + PRODUCTVERSION 1,14,3,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0xe9L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000904b0" + BEGIN + VALUE "CompanyName", "Benjamin Höglinger-Stelzer" + VALUE "FileDescription", "Virtual Gamepad Emulation Bus Driver" +VALUE "FileVersion", "1.14.3.0" + VALUE "InternalName", "Virtual Gamepad Emulation Bus Driver" + VALUE "LegalCopyright", "Copyright (C) Benjamin Höglinger-Stelzer 2016" + VALUE "OriginalFilename", "vigembus.sys" + VALUE "ProductName", "Virtual Gamepad Emulation Bus Driver" +VALUE "ProductVersion", "1.14.3.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x9, 1200 + END +END + +#endif // German (Austria) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/sys/ViGEmBus.vcxproj b/sys/ViGEmBus.vcxproj new file mode 100644 index 0000000..a76c9c0 --- /dev/null +++ b/sys/ViGEmBus.vcxproj @@ -0,0 +1,286 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM + + + Release + ARM + + + Debug + ARM64 + + + Release + ARM64 + + + + {040101B0-EE5C-4EF1-99EE-9F81C795C001} + {1bc93793-694f-48fe-9372-81e2b05556fd} + v4.5 + 12.0 + Debug + Win32 + ViGEmBus + + + + Windows7 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Desktop + 1 + 9 + 1 + + + Windows7 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Desktop + 1 + 9 + 1 + + + Windows7 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Desktop + 1 + 9 + 1 + + + Windows7 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Desktop + 1 + 9 + 1 + + + Windows7 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Desktop + 1 + 9 + 1 + + + Windows7 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Desktop + 1 + 9 + 1 + + + Windows7 + true + WindowsKernelModeDriver10.0 + Driver + KMDF + Desktop + 1 + 9 + 1 + + + Windows7 + false + WindowsKernelModeDriver10.0 + Driver + KMDF + Desktop + 1 + 9 + 1 + + + + + + + + + + + DbgengKernelDebugger + $(SolutionDir)Include;$(IncludePath) + true + + + DbgengKernelDebugger + $(SolutionDir)Include;$(IncludePath) + true + + + DbgengKernelDebugger + $(SolutionDir)Include;$(IncludePath) + true + + + DbgengKernelDebugger + $(SolutionDir)Include;$(IncludePath) + true + + + DbgengKernelDebugger + $(SolutionDir)Include;$(IncludePath) + true + + + DbgengKernelDebugger + $(SolutionDir)Include;$(IncludePath) + true + + + DbgengKernelDebugger + $(SolutionDir)Include;$(IncludePath) + true + + + DbgengKernelDebugger + $(SolutionDir)Include;$(IncludePath) + true + + + + 1.14.3.0 + + + $(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) + + + + + 1.14.3.0 + + + $(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) + + + + + 1.14.3.0 + + + $(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) + + + + + 1.14.3.0 + + + $(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) + + + + + 1.14.3.0 + + + $(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) + + + + + 1.14.3.0 + + + $(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) + + + + + 1.14.3.0 + + + $(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) + + + + + 1.14.3.0 + + + $(DDK_LIB_PATH)ntstrsafe.lib;$(DDK_LIB_PATH)wdmsec.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sys/ViGEmBus.vcxproj.filters b/sys/ViGEmBus.vcxproj.filters new file mode 100644 index 0000000..b06272a --- /dev/null +++ b/sys/ViGEmBus.vcxproj.filters @@ -0,0 +1,107 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {8E41214B-6785-4CFE-B992-037D68949A14} + inf;inv;inx;mof;mc; + + + {bbf85b1d-5a75-4302-af4e-46627fcf0d78} + + + + + Driver Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files\Common + + + Header Files\Common + + + Header Files\Common + + + + + Resource Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/sys/Xgip.h b/sys/Xgip.h new file mode 100644 index 0000000..1c3d7fd --- /dev/null +++ b/sys/Xgip.h @@ -0,0 +1,106 @@ +/* +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. +*/ + + +// +// For children emulating XGIP devices, the following dummy interfaces +// have to be exposed by the PDO or else the child devices won't start +// + +// {70211B0E-0AFB-47DB-AFC1-410BF842497A} +// ReSharper disable once CppMissingIncludeGuard +DEFINE_GUID(GUID_DEVINTERFACE_XGIP_UNKNOWN_0, + 0x70211B0E, 0x0AFB, 0x47DB, 0xAF, 0xC1, 0x41, 0x0B, 0xF8, 0x42, 0x49, 0x7A); + +// {B38290E5-3CD0-4F9D-9937-F5FE2B44D47A} +DEFINE_GUID(GUID_DEVINTERFACE_XGIP_UNKNOWN_1, + 0xB38290E5, 0x3CD0, 0x4F9D, 0x99, 0x37, 0xF5, 0xFE, 0x2B, 0x44, 0xD4, 0x7A); + +// {2AEB0243-6A6E-486B-82FC-D815F6B97006} +DEFINE_GUID(GUID_DEVINTERFACE_XGIP_UNKNOWN_2, + 0x2AEB0243, 0x6A6E, 0x486B, 0x82, 0xFC, 0xD8, 0x15, 0xF6, 0xB9, 0x70, 0x06); + +// {DC7A8E51-49B3-4A3A-9E81-625205E7D729} +DEFINE_GUID(GUID_DEVINTERFACE_XGIP_UNKNOWN_3, + 0xDC7A8E51, 0x49B3, 0x4A3A, 0x9E, 0x81, 0x62, 0x52, 0x05, 0xE7, 0xD7, 0x29); + +// {DEEE98EA-C0A1-42C3-9738-A04606C84E93} +DEFINE_GUID(GUID_DEVINTERFACE_XGIP_UNKNOWN_4, + 0xDEEE98EA, 0xC0A1, 0x42C3, 0x97, 0x38, 0xA0, 0x46, 0x06, 0xC8, 0x4E, 0x93); + + +#pragma once + +#define XGIP_DESCRIPTOR_SIZE 0x0040 +#define XGIP_CONFIGURATION_SIZE 0x88 +#define XGIP_REPORT_SIZE 0x12 +#define XGIP_SYS_INIT_PACKETS 0x0F +#define XGIP_SYS_INIT_PERIOD 0x32 + +typedef struct _XGIP_DEVICE_DATA +{ + UCHAR Report[XGIP_REPORT_SIZE]; + + // + // Queue for incoming interrupt transfer + // + WDFQUEUE PendingUsbInRequests; + + // + // Queue for inverted calls + // + WDFQUEUE PendingNotificationRequests; + + WDFCOLLECTION XboxgipSysInitCollection; + + BOOLEAN XboxgipSysInitReady; + + WDFTIMER XboxgipSysInitTimer; +} XGIP_DEVICE_DATA, *PXGIP_DEVICE_DATA; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(XGIP_DEVICE_DATA, XgipGetData) + + +NTSTATUS +Bus_XgipSubmitInterrupt( + WDFDEVICE Device, + ULONG SerialNo, + PXGIP_SUBMIT_INTERRUPT Report, + _In_ BOOLEAN FromInterface +); + +// +// XGIP-specific functions +// +NTSTATUS Xgip_PreparePdo( + PWDFDEVICE_INIT DeviceInit, + PUNICODE_STRING DeviceId, + PUNICODE_STRING DeviceDescription +); +NTSTATUS Xgip_PrepareHardware(WDFDEVICE Device); +NTSTATUS Xgip_AssignPdoContext(WDFDEVICE Device); +VOID Xgip_GetConfigurationDescriptorType(PUCHAR Buffer, ULONG Length); +VOID Xgip_GetDeviceDescriptorType(PUSB_DEVICE_DESCRIPTOR pDescriptor, PPDO_DEVICE_DATA pCommon); +VOID Xgip_SelectConfiguration(PUSBD_INTERFACE_INFORMATION pInfo); + diff --git a/sys/Xusb.h b/sys/Xusb.h new file mode 100644 index 0000000..384903c --- /dev/null +++ b/sys/Xusb.h @@ -0,0 +1,126 @@ +/* +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. +*/ + + +// +// For children emulating XUSB devices, the following dummy interfaces +// have to be exposed by the PDO or else the child devices won't start +// + +// {70211B0E-0AFB-47DB-AFC1-410BF842497A} +// ReSharper disable once CppMissingIncludeGuard +DEFINE_GUID(GUID_DEVINTERFACE_XUSB_UNKNOWN_0, + 0x70211B0E, 0x0AFB, 0x47DB, 0xAF, 0xC1, 0x41, 0x0B, 0xF8, 0x42, 0x49, 0x7A); + +// {B38290E5-3CD0-4F9D-9937-F5FE2B44D47A} +DEFINE_GUID(GUID_DEVINTERFACE_XUSB_UNKNOWN_1, + 0xB38290E5, 0x3CD0, 0x4F9D, 0x99, 0x37, 0xF5, 0xFE, 0x2B, 0x44, 0xD4, 0x7A); + +// {2AEB0243-6A6E-486B-82FC-D815F6B97006} +DEFINE_GUID(GUID_DEVINTERFACE_XUSB_UNKNOWN_2, + 0x2AEB0243, 0x6A6E, 0x486B, 0x82, 0xFC, 0xD8, 0x15, 0xF6, 0xB9, 0x70, 0x06); + +#pragma once + +#if defined(_X86_) +#define XUSB_CONFIGURATION_SIZE 0x00E4 +#else +#define XUSB_CONFIGURATION_SIZE 0x0130 +#endif +#define XUSB_DESCRIPTOR_SIZE 0x0099 +#define XUSB_RUMBLE_SIZE 0x08 +#define XUSB_LEDSET_SIZE 0x03 +#define XUSB_LEDNUM_SIZE 0x01 +#define XUSB_INIT_STAGE_SIZE 0x03 + +#define XUSB_IS_DATA_PIPE(_x_) ((BOOLEAN)(_x_->PipeHandle == (USBD_PIPE_HANDLE)0xFFFF0081)) +#define XUSB_IS_CONTROL_PIPE(_x_) ((BOOLEAN)(_x_->PipeHandle == (USBD_PIPE_HANDLE)0xFFFF0083)) + +typedef struct _XUSB_INTERRUPT_IN_PACKET +{ + UCHAR Id; + + UCHAR Size; + + XUSB_REPORT Report; + +} XUSB_INTERRUPT_IN_PACKET, *PXUSB_INTERRUPT_IN_PACKET; + +// +// XUSB-specific device context data. +// +typedef struct _XUSB_DEVICE_DATA +{ + // + // Rumble buffer + // + UCHAR Rumble[XUSB_RUMBLE_SIZE]; + + // + // LED number (represents XInput slot index) + // + UCHAR LedNumber; + + // + // Report packet + // + XUSB_INTERRUPT_IN_PACKET Packet; + + // + // Queue for incoming control interrupt transfer + // + WDFQUEUE HoldingUsbInRequests; + + // + // Required for XInputGetCapabilities to work + // + BOOLEAN ReportedCapabilities; + + // + // Required for XInputGetCapabilities to work + // + ULONG InterruptInitStage; + +} XUSB_DEVICE_DATA, *PXUSB_DEVICE_DATA; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(XUSB_DEVICE_DATA, XusbGetData) + + +NTSTATUS +Bus_XusbSubmitReport( + WDFDEVICE Device, + ULONG SerialNo, + PXUSB_SUBMIT_REPORT Report, + _In_ BOOLEAN FromInterface +); + +// +// XUSB-specific functions +// +NTSTATUS Xusb_PreparePdo(PWDFDEVICE_INIT DeviceInit, USHORT VendorId, USHORT ProductId, PUNICODE_STRING DeviceId, PUNICODE_STRING DeviceDescription); +NTSTATUS Xusb_PrepareHardware(WDFDEVICE Device); +NTSTATUS Xusb_AssignPdoContext(WDFDEVICE Device, PPDO_IDENTIFICATION_DESCRIPTION Description); +VOID Xusb_GetConfigurationDescriptorType(PUCHAR Buffer, ULONG Length); +VOID Xusb_GetDeviceDescriptorType(PUSB_DEVICE_DESCRIPTOR pDescriptor, PPDO_DEVICE_DATA pCommon); +VOID Xusb_SelectConfiguration(PUSBD_INTERFACE_INFORMATION pInfo); diff --git a/sys/busenum.c b/sys/busenum.c new file mode 100644 index 0000000..2894fba --- /dev/null +++ b/sys/busenum.c @@ -0,0 +1,638 @@ +/* +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 +#include + +#ifdef ALLOC_PRAGMA +#pragma alloc_text (PAGE, Bus_PlugInDevice) +#pragma alloc_text (PAGE, Bus_UnPlugDevice) +#endif + + + +// +// Simulates a device plug-in event. +// +NTSTATUS Bus_PlugInDevice( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request, + _In_ BOOLEAN IsInternal, + _Out_ size_t* Transferred) +{ + PDO_IDENTIFICATION_DESCRIPTION description; + NTSTATUS status; + PVIGEM_PLUGIN_TARGET plugIn; + WDFFILEOBJECT fileObject; + PFDO_FILE_DATA pFileData; + size_t length = 0; + WDF_OBJECT_ATTRIBUTES requestAttribs; + PFDO_PLUGIN_REQUEST_DATA pReqData; + PFDO_DEVICE_DATA pFdoData; + + PAGED_CODE(); + + + pFdoData = FdoGetData(Device); + + status = WdfRequestRetrieveInputBuffer(Request, sizeof(VIGEM_PLUGIN_TARGET), (PVOID)&plugIn, &length); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfRequestRetrieveInputBuffer failed 0x%x\n", status)); + return status; + } + + if ((sizeof(VIGEM_PLUGIN_TARGET) != plugIn->Size) || (length != plugIn->Size)) + { + KdPrint((DRIVERNAME "Input buffer size mismatch")); + return STATUS_INVALID_PARAMETER; + } + + if (plugIn->SerialNo == 0) + { + KdPrint((DRIVERNAME "Serial no. 0 not allowed")); + return STATUS_INVALID_PARAMETER; + } + + *Transferred = length; + + fileObject = WdfRequestGetFileObject(Request); + if (fileObject == NULL) + { + KdPrint((DRIVERNAME "File object associated with request is null")); + return STATUS_INVALID_PARAMETER; + } + + pFileData = FileObjectGetData(fileObject); + if (pFileData == NULL) + { + KdPrint((DRIVERNAME "File object context associated with request is null")); + return STATUS_INVALID_PARAMETER; + } + + // + // Initialize the description with the information about the newly + // plugged in device. + // + WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER_INIT(&description.Header, sizeof(description)); + + description.SerialNo = plugIn->SerialNo; + description.TargetType = plugIn->TargetType; + description.OwnerProcessId = CURRENT_PROCESS_ID(); + description.SessionId = pFileData->SessionId; + description.OwnerIsDriver = IsInternal; + + // Set default IDs if supplied values are invalid + if (plugIn->VendorId == 0 || plugIn->ProductId == 0) + { + switch (plugIn->TargetType) + { + case Xbox360Wired: + + description.VendorId = 0x045E; + description.ProductId = 0x028E; + + break; + case DualShock4Wired: + + description.VendorId = 0x054C; + description.ProductId = 0x05C4; + + break; + case XboxOneWired: + + description.VendorId = 0x0E6F; + description.ProductId = 0x0139; + +#if !DBG + // TODO: implement and remove! + return STATUS_NOT_SUPPORTED; +#endif + + break; + } + } + else + { + description.VendorId = plugIn->VendorId; + description.ProductId = plugIn->ProductId; + } + + WdfSpinLockAcquire(pFdoData->PendingPluginRequestsLock); + + KdPrint((DRIVERNAME "Items count: %d\n", WdfCollectionGetCount(pFdoData->PendingPluginRequests))); + + status = WdfChildListAddOrUpdateChildDescriptionAsPresent(WdfFdoGetDefaultChildList(Device), &description.Header, NULL); + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfChildListAddOrUpdateChildDescriptionAsPresent failed with 0x%X\n", status)); + + goto pluginEnd; + } + + if (status == STATUS_OBJECT_NAME_EXISTS) + { + status = STATUS_INVALID_PARAMETER; + goto pluginEnd; + } + + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&requestAttribs, FDO_PLUGIN_REQUEST_DATA); + + // + // Allocate context data to request + // + status = WdfObjectAllocateContext(Request, &requestAttribs, (PVOID)&pReqData); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfCollectionAdd failed with 0x%X\n", status)); + + goto pluginEnd; + } + + // + // Glue current serial to request + // + pReqData->Serial = plugIn->SerialNo; + + // + // Keep track of pending request in collection + // + status = WdfCollectionAdd(pFdoData->PendingPluginRequests, Request); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfCollectionAdd failed with 0x%X\n", status)); + + goto pluginEnd; + } + + KdPrint((DRIVERNAME "Added item with serial: %d\n", plugIn->SerialNo)); + + status = NT_SUCCESS(status) ? STATUS_PENDING : status; + +pluginEnd: + + WdfSpinLockRelease(pFdoData->PendingPluginRequestsLock); + return status; +} + +// +// Simulates a device unplug event. +// +NTSTATUS Bus_UnPlugDevice( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request, + _In_ BOOLEAN IsInternal, + _Out_ size_t* Transferred) +{ + NTSTATUS status; + WDFDEVICE hChild; + WDFCHILDLIST list; + WDF_CHILD_LIST_ITERATOR iterator; + WDF_CHILD_RETRIEVE_INFO childInfo; + PDO_IDENTIFICATION_DESCRIPTION description; + BOOLEAN unplugAll; + PVIGEM_UNPLUG_TARGET unPlug; + WDFFILEOBJECT fileObject; + PFDO_FILE_DATA pFileData = NULL; + size_t length = 0; + + PAGED_CODE(); + + KdPrint((DRIVERNAME "Entered Bus_UnPlugDevice\n")); + + status = WdfRequestRetrieveInputBuffer(Request, sizeof(VIGEM_UNPLUG_TARGET), (PVOID)&unPlug, &length); + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "Bus_UnPlugDevice: WdfRequestRetrieveInputBuffer failed 0x%x\n", status)); + return status; + } + + if ((sizeof(VIGEM_UNPLUG_TARGET) != unPlug->Size) || (length != unPlug->Size)) + { + KdPrint((DRIVERNAME "Bus_UnPlugDevice: Input buffer size mismatch")); + return STATUS_INVALID_PARAMETER; + } + + *Transferred = length; + unplugAll = (unPlug->SerialNo == 0); + + if (!IsInternal) + { + fileObject = WdfRequestGetFileObject(Request); + if (fileObject == NULL) + { + KdPrint((DRIVERNAME "Bus_UnPlugDevice: File object associated with request is null")); + return STATUS_INVALID_PARAMETER; + } + + pFileData = FileObjectGetData(fileObject); + if (pFileData == NULL) + { + KdPrint((DRIVERNAME "Bus_UnPlugDevice: File object context associated with request is null")); + return STATUS_INVALID_PARAMETER; + } + } + + list = WdfFdoGetDefaultChildList(Device); + + WDF_CHILD_LIST_ITERATOR_INIT(&iterator, WdfRetrievePresentChildren); + + WdfChildListBeginIteration(list, &iterator); + + for (;;) + { + WDF_CHILD_RETRIEVE_INFO_INIT(&childInfo, &description.Header); + WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER_INIT(&description.Header, sizeof(description)); + + status = WdfChildListRetrieveNextDevice(list, &iterator, &hChild, &childInfo); + + // Error or no more children, end loop + if (!NT_SUCCESS(status) || status == STATUS_NO_MORE_ENTRIES) + { + break; + } + + // If unable to retrieve device + if (childInfo.Status != WdfChildListRetrieveDeviceSuccess) + { + continue; + } + + // Child isn't the one we looked for, skip + if (!unplugAll && description.SerialNo != unPlug->SerialNo) + { + continue; + } + + // Only unplug owned children + if (IsInternal || description.SessionId == pFileData->SessionId) + { + // Unplug child + status = WdfChildListUpdateChildDescriptionAsMissing(list, &description.Header); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "Bus_UnPlugDevice: WdfChildListUpdateChildDescriptionAsMissing failed with status 0x%X\n", status)); + } + } + } + + WdfChildListEndIteration(list, &iterator); + + return STATUS_SUCCESS; +} + +// +// Sends a report update to an XUSB PDO. +// +NTSTATUS Bus_XusbSubmitReport(WDFDEVICE Device, ULONG SerialNo, PXUSB_SUBMIT_REPORT Report, BOOLEAN FromInterface) +{ + return Bus_SubmitReport(Device, SerialNo, Report, FromInterface); +} + +// +// Queues an inverted call to receive XUSB-specific updates. +// +NTSTATUS Bus_QueueNotification(WDFDEVICE Device, ULONG SerialNo, WDFREQUEST Request) +{ + NTSTATUS status = STATUS_INVALID_PARAMETER; + WDFDEVICE hChild; + PPDO_DEVICE_DATA pdoData; + PXUSB_DEVICE_DATA xusbData; + PDS4_DEVICE_DATA ds4Data; + + + KdPrint((DRIVERNAME "Entered Bus_QueueNotification\n")); + + hChild = Bus_GetPdo(Device, SerialNo); + + // Validate child + if (hChild == NULL) + { + KdPrint((DRIVERNAME "Bus_QueueNotification: PDO with serial %d not found\n", SerialNo)); + return STATUS_NO_SUCH_DEVICE; + } + + // Check common context + pdoData = PdoGetData(hChild); + if (pdoData == NULL) + { + KdPrint((DRIVERNAME "Bus_QueueNotification: PDO context not found\n")); + return STATUS_INVALID_PARAMETER; + } + + // Check if caller owns this PDO + if (!IS_OWNER(pdoData)) + { + KdPrint((DRIVERNAME "Bus_QueueNotification: PID mismatch: %d != %d\n", pdoData->OwnerProcessId, CURRENT_PROCESS_ID())); + return STATUS_ACCESS_DENIED; + } + + // Queue the request for later completion by the PDO and return STATUS_PENDING + switch (pdoData->TargetType) + { + case Xbox360Wired: + + xusbData = XusbGetData(hChild); + + if (xusbData == NULL) break; + + status = WdfRequestForwardToIoQueue(Request, pdoData->PendingNotificationRequests); + + break; + case DualShock4Wired: + + ds4Data = Ds4GetData(hChild); + + if (ds4Data == NULL) break; + + status = WdfRequestForwardToIoQueue(Request, pdoData->PendingNotificationRequests); + + break; + default: + status = STATUS_NOT_SUPPORTED; + break; + } + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfRequestForwardToIoQueue failed with status 0x%X\n", status)); + } + + return (NT_SUCCESS(status)) ? STATUS_PENDING : status; +} + +// +// Sends a report update to a DS4 PDO. +// +NTSTATUS Bus_Ds4SubmitReport(WDFDEVICE Device, ULONG SerialNo, PDS4_SUBMIT_REPORT Report, BOOLEAN FromInterface) +{ + return Bus_SubmitReport(Device, SerialNo, Report, FromInterface); +} + +NTSTATUS Bus_XgipSubmitReport(WDFDEVICE Device, ULONG SerialNo, PXGIP_SUBMIT_REPORT Report, BOOLEAN FromInterface) +{ + return Bus_SubmitReport(Device, SerialNo, Report, FromInterface); +} + +NTSTATUS Bus_XgipSubmitInterrupt(WDFDEVICE Device, ULONG SerialNo, PXGIP_SUBMIT_INTERRUPT Report, BOOLEAN FromInterface) +{ + return Bus_SubmitReport(Device, SerialNo, Report, FromInterface); +} + +WDFDEVICE Bus_GetPdo(IN WDFDEVICE Device, IN ULONG SerialNo) +{ + WDFCHILDLIST list; + WDF_CHILD_RETRIEVE_INFO info; + + list = WdfFdoGetDefaultChildList(Device); + + PDO_IDENTIFICATION_DESCRIPTION description; + + WDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER_INIT(&description.Header, sizeof(description)); + + description.SerialNo = SerialNo; + + WDF_CHILD_RETRIEVE_INFO_INIT(&info, &description.Header); + + return WdfChildListRetrievePdo(list, &info); +} + +NTSTATUS Bus_SubmitReport(WDFDEVICE Device, ULONG SerialNo, PVOID Report, BOOLEAN FromInterface) +{ + NTSTATUS status = STATUS_SUCCESS; + WDFDEVICE hChild; + PPDO_DEVICE_DATA pdoData; + WDFREQUEST usbRequest; + PIRP pendingIrp; + BOOLEAN changed; + + + KdPrint((DRIVERNAME "Entered Bus_SubmitReport\n")); + + hChild = Bus_GetPdo(Device, SerialNo); + + // Validate child + if (hChild == NULL) + { + KdPrint((DRIVERNAME "Bus_SubmitReport: PDO with serial %d not found\n", SerialNo)); + return STATUS_NO_SUCH_DEVICE; + } + + // Check common context + pdoData = PdoGetData(hChild); + if (pdoData == NULL) + { + KdPrint((DRIVERNAME "Bus_SubmitReport: PDO context not found\n")); + return STATUS_INVALID_PARAMETER; + } + + // Check if caller owns this PDO + if (!FromInterface && !IS_OWNER(pdoData)) + { + KdPrint((DRIVERNAME "Bus_SubmitReport: PID mismatch: %d != %d\n", pdoData->OwnerProcessId, CURRENT_PROCESS_ID())); + return STATUS_ACCESS_DENIED; + } + + // Check if input is different from previous value + switch (pdoData->TargetType) + { + case Xbox360Wired: + + changed = (RtlCompareMemory(&XusbGetData(hChild)->Packet.Report, + &((PXUSB_SUBMIT_REPORT)Report)->Report, + sizeof(XUSB_REPORT)) != sizeof(XUSB_REPORT)); + + break; + case DualShock4Wired: + + changed = TRUE; + + break; + case XboxOneWired: + + // TODO: necessary? + changed = TRUE; + + break; + default: + + changed = FALSE; + + break; + } + + // Don't waste pending IRP if input hasn't changed + if (!changed) + return status; + + KdPrint((DRIVERNAME "Bus_SubmitReport: received new report\n")); + + // Get pending USB request + switch (pdoData->TargetType) + { + case Xbox360Wired: + + status = WdfIoQueueRetrieveNextRequest(pdoData->PendingUsbInRequests, &usbRequest); + + break; + case DualShock4Wired: + + status = WdfIoQueueRetrieveNextRequest(pdoData->PendingUsbInRequests, &usbRequest); + + break; + case XboxOneWired: + + // Request is control data + if (((PXGIP_SUBMIT_INTERRUPT)Report)->Size == sizeof(XGIP_SUBMIT_INTERRUPT)) + { + PXGIP_DEVICE_DATA xgip = XgipGetData(hChild); + PXGIP_SUBMIT_INTERRUPT interrupt = (PXGIP_SUBMIT_INTERRUPT)Report; + WDFMEMORY memory; + WDF_OBJECT_ATTRIBUTES memAttribs; + WDF_OBJECT_ATTRIBUTES_INIT(&memAttribs); + + memAttribs.ParentObject = hChild; + + // Allocate kernel memory + status = WdfMemoryCreate(&memAttribs, NonPagedPool, VIGEM_POOL_TAG, + interrupt->InterruptLength, &memory, NULL); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfMemoryCreate failed with status 0x%X\n", status)); + goto endSubmitReport; + } + + // Copy interrupt buffer to memory object + status = WdfMemoryCopyFromBuffer(memory, 0, interrupt->Interrupt, interrupt->InterruptLength); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfMemoryCopyFromBuffer failed with status 0x%X\n", status)); + goto endSubmitReport; + } + + // Add memory object to collection + status = WdfCollectionAdd(xgip->XboxgipSysInitCollection, memory); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfCollectionAdd failed with status 0x%X\n", status)); + goto endSubmitReport; + } + + // Check if all packets have been received + xgip->XboxgipSysInitReady = + WdfCollectionGetCount(xgip->XboxgipSysInitCollection) == XGIP_SYS_INIT_PACKETS; + + // If all packets are cached, start initialization timer + if (xgip->XboxgipSysInitReady) + { + WdfTimerStart(xgip->XboxgipSysInitTimer, XGIP_SYS_INIT_PERIOD); + } + + goto endSubmitReport; + } + + status = WdfIoQueueRetrieveNextRequest(XgipGetData(hChild)->PendingUsbInRequests, &usbRequest); + + break; + default: + + status = STATUS_NOT_SUPPORTED; + goto endSubmitReport; + } + + if (status == STATUS_PENDING) + goto endSubmitReport; + else if (!NT_SUCCESS(status)) + goto endSubmitReport; + + KdPrint((DRIVERNAME "Bus_SubmitReport: pending IRP found\n")); + + // Get pending IRP + pendingIrp = WdfRequestWdmGetIrp(usbRequest); + + // Get USB request block + PURB urb = (PURB)URB_FROM_IRP(pendingIrp); + + // Get transfer buffer + PUCHAR Buffer = (PUCHAR)urb->UrbBulkOrInterruptTransfer.TransferBuffer; + + switch (pdoData->TargetType) + { + case Xbox360Wired: + + urb->UrbBulkOrInterruptTransfer.TransferBufferLength = sizeof(XUSB_INTERRUPT_IN_PACKET); + + // Copy submitted report to cache + RtlCopyBytes(&XusbGetData(hChild)->Packet.Report, &((PXUSB_SUBMIT_REPORT)Report)->Report, sizeof(XUSB_REPORT)); + // Copy cached report to URB transfer buffer + RtlCopyBytes(Buffer, &XusbGetData(hChild)->Packet, sizeof(XUSB_INTERRUPT_IN_PACKET)); + + break; + case DualShock4Wired: + + urb->UrbBulkOrInterruptTransfer.TransferBufferLength = DS4_REPORT_SIZE; + + /* Copy report to cache and transfer buffer + * Skip first byte as it contains the never changing report id */ + RtlCopyBytes(Ds4GetData(hChild)->Report + 1, &((PDS4_SUBMIT_REPORT)Report)->Report, sizeof(DS4_REPORT)); + + if (Buffer) + RtlCopyBytes(Buffer, Ds4GetData(hChild)->Report, DS4_REPORT_SIZE); + + break; + case XboxOneWired: + + // Request is input report + if (((PXGIP_SUBMIT_REPORT)Report)->Size == sizeof(XGIP_SUBMIT_REPORT)) + { + urb->UrbBulkOrInterruptTransfer.TransferBufferLength = XGIP_REPORT_SIZE; + + // Increase event counter on every call (can roll-over) + XgipGetData(hChild)->Report[2]++; + + /* Copy report to cache and transfer buffer + * Skip first four bytes as they are not part of the report */ + RtlCopyBytes(XgipGetData(hChild)->Report + 4, &((PXGIP_SUBMIT_REPORT)Report)->Report, sizeof(XGIP_REPORT)); + RtlCopyBytes(Buffer, XgipGetData(hChild)->Report, XGIP_REPORT_SIZE); + + break; + } + + break; + default: + status = STATUS_INVALID_PARAMETER; + break; + } + + // Complete pending request + WdfRequestComplete(usbRequest, status); + +endSubmitReport: + return status; +} + diff --git a/sys/busenum.h b/sys/busenum.h new file mode 100644 index 0000000..5c5b813 --- /dev/null +++ b/sys/busenum.h @@ -0,0 +1,166 @@ +/* +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. +*/ + + +#pragma once + +#include +#include +#define NTSTRSAFE_LIB +#include +#include +#include +#include "ViGEmBusDriver.h" +#include "ViGEmBusShared.h" +#include "Queue.h" +#include +#include +#include "Context.h" +#include "Util.h" +#include "UsbPdo.h" +#include "Xusb.h" +#include "Ds4.h" +#include "Xgip.h" + + +#pragma region Macros + +#define MAX_INSTANCE_ID_LEN 80 +#define HID_LANGUAGE_ID_LENGTH 0x04 + +#define HID_REQUEST_GET_REPORT 0x01 +#define HID_REQUEST_SET_REPORT 0x09 +#define HID_REPORT_TYPE_FEATURE 0x03 + +#define VIGEM_POOL_TAG 0x45476956 // "EGiV" +#define DRIVERNAME "ViGEm: " +#define MAX_HARDWARE_ID_LENGTH 0xFF + +#pragma endregion + +#pragma region Helpers + +// +// Extracts the HID Report ID from the supplied class request. +// +#define HID_GET_REPORT_ID(_req_) ((_req_->Value) & 0xFF) + +// +// Extracts the HID Report type from the supplied class request. +// +#define HID_GET_REPORT_TYPE(_req_) ((_req_->Value >> 8) & 0xFF) + +// +// Some insane macro-magic =3 +// +#define P99_PROTECT(...) __VA_ARGS__ +#define COPY_BYTE_ARRAY(_dst_, _bytes_) do {BYTE b[] = _bytes_; \ + RtlCopyMemory(_dst_, b, RTL_NUMBER_OF_V1(b)); } while (0) + +#pragma endregion + + +#pragma region WDF callback prototypes + +DRIVER_INITIALIZE DriverEntry; + +EVT_WDF_DRIVER_DEVICE_ADD Bus_EvtDeviceAdd; +EVT_WDF_DEVICE_FILE_CREATE Bus_DeviceFileCreate; +EVT_WDF_FILE_CLOSE Bus_FileClose; + +EVT_WDF_CHILD_LIST_CREATE_DEVICE Bus_EvtDeviceListCreatePdo; + +EVT_WDF_CHILD_LIST_IDENTIFICATION_DESCRIPTION_COMPARE Bus_EvtChildListIdentificationDescriptionCompare; + +EVT_WDF_DEVICE_PREPARE_HARDWARE Bus_EvtDevicePrepareHardware; + +EVT_WDF_IO_QUEUE_IO_INTERNAL_DEVICE_CONTROL Pdo_EvtIoInternalDeviceControl; + +EVT_WDF_TIMER Xgip_SysInitTimerFunc; + +#pragma endregion + +#pragma region Bus enumeration-specific functions + +NTSTATUS +Bus_PlugInDevice( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request, + _In_ BOOLEAN IsInternal, + _Out_ size_t* Transferred +); + +NTSTATUS +Bus_UnPlugDevice( + _In_ WDFDEVICE Device, + _In_ WDFREQUEST Request, + _In_ BOOLEAN IsInternal, + _Out_ size_t* Transferred +); + +NTSTATUS +Bus_CreatePdo( + _In_ WDFDEVICE Device, + _In_ PWDFDEVICE_INIT ChildInit, + _In_ PPDO_IDENTIFICATION_DESCRIPTION Description +); + +NTSTATUS +Bus_QueueNotification( + WDFDEVICE Device, + ULONG SerialNo, + WDFREQUEST Request +); + +NTSTATUS +Bus_XgipSubmitReport( + WDFDEVICE Device, + ULONG SerialNo, + PXGIP_SUBMIT_REPORT Report, + _In_ BOOLEAN FromInterface +); + +NTSTATUS +Bus_SubmitReport( + WDFDEVICE Device, + ULONG SerialNo, + PVOID Report, + _In_ BOOLEAN FromInterface +); + +WDFDEVICE +Bus_GetPdo( + IN WDFDEVICE Device, + IN ULONG SerialNo); + +VOID +Bus_PdoStageResult( + _In_ PINTERFACE InterfaceHeader, + _In_ VIGEM_PDO_STAGE Stage, + _In_ ULONG Serial, + _In_ NTSTATUS Status +); + +#pragma endregion + diff --git a/sys/buspdo.c b/sys/buspdo.c new file mode 100644 index 0000000..108245d --- /dev/null +++ b/sys/buspdo.c @@ -0,0 +1,755 @@ +/* +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 +#include + +#ifdef ALLOC_PRAGMA +#pragma alloc_text(PAGE, Bus_CreatePdo) +#pragma alloc_text(PAGE, Bus_EvtDeviceListCreatePdo) +#pragma alloc_text(PAGE, Bus_EvtDevicePrepareHardware) +#endif + +NTSTATUS Bus_EvtDeviceListCreatePdo( + WDFCHILDLIST DeviceList, + PWDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER IdentificationDescription, + PWDFDEVICE_INIT ChildInit) +{ + PPDO_IDENTIFICATION_DESCRIPTION pDesc; + + PAGED_CODE(); + + pDesc = CONTAINING_RECORD(IdentificationDescription, PDO_IDENTIFICATION_DESCRIPTION, Header); + + return Bus_CreatePdo(WdfChildListGetDevice(DeviceList), ChildInit, pDesc); +} + +// +// Compares two children on the bus based on their serial numbers. +// +BOOLEAN Bus_EvtChildListIdentificationDescriptionCompare( + WDFCHILDLIST DeviceList, + PWDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER FirstIdentificationDescription, + PWDF_CHILD_IDENTIFICATION_DESCRIPTION_HEADER SecondIdentificationDescription) +{ + PPDO_IDENTIFICATION_DESCRIPTION lhs, rhs; + + UNREFERENCED_PARAMETER(DeviceList); + + lhs = CONTAINING_RECORD(FirstIdentificationDescription, + PDO_IDENTIFICATION_DESCRIPTION, + Header); + rhs = CONTAINING_RECORD(SecondIdentificationDescription, + PDO_IDENTIFICATION_DESCRIPTION, + Header); + + return (lhs->SerialNo == rhs->SerialNo) ? TRUE : FALSE; +} + +// +// Creates and initializes a PDO (child). +// +NTSTATUS Bus_CreatePdo( + _In_ WDFDEVICE Device, + _In_ PWDFDEVICE_INIT DeviceInit, + _In_ PPDO_IDENTIFICATION_DESCRIPTION Description) +{ + NTSTATUS status; + PPDO_DEVICE_DATA pdoData; + WDFDEVICE hChild = NULL; + WDF_DEVICE_PNP_CAPABILITIES pnpCaps; + WDF_DEVICE_POWER_CAPABILITIES powerCaps; + WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks; + WDF_OBJECT_ATTRIBUTES pdoAttributes; + WDF_IO_QUEUE_CONFIG defaultPdoQueueConfig; + WDFQUEUE defaultPdoQueue; + UNICODE_STRING deviceDescription; + VIGEM_BUS_INTERFACE busInterface; + WDF_OBJECT_ATTRIBUTES attributes; + WDF_IO_QUEUE_CONFIG usbInQueueConfig; + WDF_IO_QUEUE_CONFIG notificationsQueueConfig; + + DECLARE_CONST_UNICODE_STRING(deviceLocation, L"Virtual Gamepad Emulation Bus"); + DECLARE_UNICODE_STRING_SIZE(buffer, MAX_INSTANCE_ID_LEN); + // reserve space for device id + DECLARE_UNICODE_STRING_SIZE(deviceId, MAX_INSTANCE_ID_LEN); + + + PAGED_CODE(); + + + KdPrint((DRIVERNAME "Entered Bus_CreatePdo\n")); + + // + // Get the FDO interface ASAP to report progress to bus + // + status = WdfFdoQueryForInterface(Device, + &GUID_VIGEM_INTERFACE_PDO, + (PINTERFACE)&busInterface, + sizeof(VIGEM_BUS_INTERFACE), + VIGEM_BUS_INTERFACE_VERSION, + NULL); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfFdoQueryForInterface failed status 0x%x\n", status)); + return status; + } + + // set device type + WdfDeviceInitSetDeviceType(DeviceInit, FILE_DEVICE_BUS_EXTENDER); + // Bus is power policy owner + WdfDeviceInitSetPowerPolicyOwnership(DeviceInit, FALSE); + +#pragma region Enter RAW device mode + + status = WdfPdoInitAssignRawDevice(DeviceInit, &GUID_DEVCLASS_VIGEM_RAWPDO); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfPdoInitAssignRawDevice failed status 0x%x\n", status)); + goto endCreatePdo; + } + + WdfDeviceInitSetCharacteristics(DeviceInit, FILE_AUTOGENERATED_DEVICE_NAME, TRUE); + + status = WdfDeviceInitAssignSDDLString(DeviceInit, &SDDL_DEVOBJ_SYS_ALL_ADM_RWX_WORLD_RWX_RES_RWX); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfDeviceInitAssignSDDLString failed status 0x%x\n", status)); + goto endCreatePdo; + } + +#pragma endregion + +#pragma region Prepare PDO + + // set parameters matching desired target device + switch (Description->TargetType) + { + // + // A Xbox 360 device was requested + // + case Xbox360Wired: + + status = Xusb_PreparePdo( + DeviceInit, + Description->VendorId, + Description->ProductId, + &deviceId, + &deviceDescription); + + if (!NT_SUCCESS(status)) + goto endCreatePdo; + + break; + + // + // A Sony DualShock 4 device was requested + // + case DualShock4Wired: + + status = Ds4_PreparePdo(DeviceInit, &deviceId, &deviceDescription); + + if (!NT_SUCCESS(status)) + goto endCreatePdo; + + break; + + // + // A Xbox One device was requested + // + case XboxOneWired: + + status = Xgip_PreparePdo(DeviceInit, &deviceId, &deviceDescription); + + if (!NT_SUCCESS(status)) + goto endCreatePdo; + + break; + + default: + + KdPrint((DRIVERNAME "Unsupported target type\n")); + status = STATUS_INVALID_PARAMETER; + goto endCreatePdo; + } + + // set device id + status = WdfPdoInitAssignDeviceID(DeviceInit, &deviceId); + if (!NT_SUCCESS(status)) + goto endCreatePdo; + + // prepare instance id + status = RtlUnicodeStringPrintf(&buffer, L"%02d", Description->SerialNo); + if (!NT_SUCCESS(status)) + goto endCreatePdo; + + // set instance id + status = WdfPdoInitAssignInstanceID(DeviceInit, &buffer); + if (!NT_SUCCESS(status)) + goto endCreatePdo; + + // set device description (for English operating systems) + status = WdfPdoInitAddDeviceText(DeviceInit, &deviceDescription, &deviceLocation, 0x409); + if (!NT_SUCCESS(status)) + goto endCreatePdo; + + // default locale is English + // TODO: add more locales + WdfPdoInitSetDefaultLocale(DeviceInit, 0x409); + +#pragma endregion + +#pragma region PNP/Power event callbacks + + WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks); + + pnpPowerCallbacks.EvtDevicePrepareHardware = Bus_EvtDevicePrepareHardware; + + WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks); + +#pragma endregion + + // NOTE: not utilized at the moment + WdfPdoInitAllowForwardingRequestToParent(DeviceInit); + +#pragma region Create PDO + + // Add common device data context + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&pdoAttributes, PDO_DEVICE_DATA); + + status = WdfDeviceCreate(&DeviceInit, &pdoAttributes, &hChild); + if (!NT_SUCCESS(status)) + goto endCreatePdo; + + KdPrint((DRIVERNAME "Created PDO: 0x%X\n", hChild)); + + switch (Description->TargetType) + { + // Add XUSB-specific device data context + case Xbox360Wired: + { + PXUSB_DEVICE_DATA xusbData = NULL; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&pdoAttributes, XUSB_DEVICE_DATA); + + status = WdfObjectAllocateContext(hChild, &pdoAttributes, (PVOID)&xusbData); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfObjectAllocateContext failed status 0x%x\n", status)); + goto endCreatePdo; + } + + break; + } + case DualShock4Wired: + { + PDS4_DEVICE_DATA ds4Data = NULL; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&pdoAttributes, DS4_DEVICE_DATA); + + status = WdfObjectAllocateContext(hChild, &pdoAttributes, (PVOID)&ds4Data); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfObjectAllocateContext failed status 0x%x\n", status)); + goto endCreatePdo; + } + + break; + } + case XboxOneWired: + { + PXGIP_DEVICE_DATA xgipData = NULL; + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&pdoAttributes, XGIP_DEVICE_DATA); + + status = WdfObjectAllocateContext(hChild, &pdoAttributes, (PVOID)&xgipData); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfObjectAllocateContext failed status 0x%x\n", status)); + goto endCreatePdo; + } + + break; + } + default: + break; + } + +#pragma endregion + +#pragma region Expose USB Interface + + status = WdfDeviceCreateDeviceInterface(Device, (LPGUID)&GUID_DEVINTERFACE_USB_DEVICE, NULL); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfDeviceCreateDeviceInterface failed status 0x%x\n", status)); + goto endCreatePdo; + } + +#pragma endregion + +#pragma region Set PDO contexts + + pdoData = PdoGetData(hChild); + + pdoData->BusInterface = busInterface; + + pdoData->SerialNo = Description->SerialNo; + pdoData->TargetType = Description->TargetType; + pdoData->OwnerProcessId = Description->OwnerProcessId; + pdoData->VendorId = Description->VendorId; + pdoData->ProductId = Description->ProductId; + + // Initialize additional contexts (if available) + switch (Description->TargetType) + { + case Xbox360Wired: + + status = Xusb_AssignPdoContext(hChild, Description); + + break; + + case DualShock4Wired: + + status = Ds4_AssignPdoContext(hChild, Description); + + break; + + case XboxOneWired: + + status = Xgip_AssignPdoContext(hChild); + + break; + + default: + break; + } + + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "Couldn't initialize additional contexts\n")); + goto endCreatePdo; + } + +#pragma endregion + +#pragma region Create Queues & Locks + + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = hChild; + + // Create and assign queue for incoming interrupt transfer + WDF_IO_QUEUE_CONFIG_INIT(&usbInQueueConfig, WdfIoQueueDispatchManual); + + status = WdfIoQueueCreate(Device, &usbInQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, &pdoData->PendingUsbInRequests); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfIoQueueCreate (PendingUsbInRequests) failed 0x%x\n", status)); + goto endCreatePdo; + } + + // Create and assign queue for user-land notification requests + WDF_IO_QUEUE_CONFIG_INIT(¬ificationsQueueConfig, WdfIoQueueDispatchManual); + + status = WdfIoQueueCreate(Device, ¬ificationsQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, &pdoData->PendingNotificationRequests); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfIoQueueCreate (PendingNotificationRequests) failed 0x%x\n", status)); + goto endCreatePdo; + } + +#pragma endregion + +#pragma region Default I/O queue setup + + WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&defaultPdoQueueConfig, WdfIoQueueDispatchParallel); + + defaultPdoQueueConfig.EvtIoInternalDeviceControl = Pdo_EvtIoInternalDeviceControl; + + status = WdfIoQueueCreate(hChild, &defaultPdoQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, &defaultPdoQueue); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfIoQueueCreate failed 0x%x\n", status)); + goto endCreatePdo; + } + +#pragma endregion + +#pragma region PNP capabilities + + WDF_DEVICE_PNP_CAPABILITIES_INIT(&pnpCaps); + + pnpCaps.Removable = WdfTrue; + pnpCaps.EjectSupported = WdfTrue; + pnpCaps.SurpriseRemovalOK = WdfTrue; + + pnpCaps.Address = Description->SerialNo; + pnpCaps.UINumber = Description->SerialNo; + + WdfDeviceSetPnpCapabilities(hChild, &pnpCaps); + +#pragma endregion + +#pragma region Power capabilities + + WDF_DEVICE_POWER_CAPABILITIES_INIT(&powerCaps); + + powerCaps.DeviceD1 = WdfTrue; + powerCaps.WakeFromD1 = WdfTrue; + powerCaps.DeviceWake = PowerDeviceD1; + + powerCaps.DeviceState[PowerSystemWorking] = PowerDeviceD0; + powerCaps.DeviceState[PowerSystemSleeping1] = PowerDeviceD1; + powerCaps.DeviceState[PowerSystemSleeping2] = PowerDeviceD3; + powerCaps.DeviceState[PowerSystemSleeping3] = PowerDeviceD3; + powerCaps.DeviceState[PowerSystemHibernate] = PowerDeviceD3; + powerCaps.DeviceState[PowerSystemShutdown] = PowerDeviceD3; + + WdfDeviceSetPowerCapabilities(hChild, &powerCaps); + +#pragma endregion + + endCreatePdo: + KdPrint((DRIVERNAME "BUS_PDO_REPORT_STAGE_RESULT Stage: ViGEmPdoCreate, Serial: 0x%X, Status: 0x%X (%d)\n", + Description->SerialNo, status, NT_SUCCESS(status))); + + BUS_PDO_REPORT_STAGE_RESULT(busInterface, ViGEmPdoCreate, Description->SerialNo, status); + return status; +} + +// +// Exposes necessary interfaces on PDO power-up. +// +NTSTATUS Bus_EvtDevicePrepareHardware( + _In_ WDFDEVICE Device, + _In_ WDFCMRESLIST ResourcesRaw, + _In_ WDFCMRESLIST ResourcesTranslated +) +{ + PPDO_DEVICE_DATA pdoData; + NTSTATUS status = STATUS_UNSUCCESSFUL; + + PAGED_CODE(); + + UNREFERENCED_PARAMETER(ResourcesRaw); + UNREFERENCED_PARAMETER(ResourcesTranslated); + + KdPrint((DRIVERNAME "Bus_EvtDevicePrepareHardware: 0x%p\n", Device)); + + pdoData = PdoGetData(Device); + + switch (pdoData->TargetType) + { + // Expose XUSB interfaces + case Xbox360Wired: + + status = Xusb_PrepareHardware(Device); + + break; + + case DualShock4Wired: + + status = Ds4_PrepareHardware(Device); + + break; + + case XboxOneWired: + + status = Xgip_PrepareHardware(Device); + + break; + + default: + break; + } + + BUS_PDO_REPORT_STAGE_RESULT(pdoData->BusInterface, ViGEmPdoPrepareHardware, pdoData->SerialNo, status); + + return status; +} + +// +// Responds to IRP_MJ_INTERNAL_DEVICE_CONTROL requests sent to PDO. +// +VOID Pdo_EvtIoInternalDeviceControl( + _In_ WDFQUEUE Queue, + _In_ WDFREQUEST Request, + _In_ size_t OutputBufferLength, + _In_ size_t InputBufferLength, + _In_ ULONG IoControlCode +) +{ + // Regular buffers not used in USB communication + UNREFERENCED_PARAMETER(OutputBufferLength); + UNREFERENCED_PARAMETER(InputBufferLength); + + NTSTATUS status = STATUS_INVALID_PARAMETER; + WDFDEVICE hDevice; + PIRP irp; + PURB urb; + PPDO_DEVICE_DATA pdoData; + PIO_STACK_LOCATION irpStack; + + hDevice = WdfIoQueueGetDevice(Queue); + pdoData = PdoGetData(hDevice); + // No help from the framework available from here on + irp = WdfRequestWdmGetIrp(Request); + irpStack = IoGetCurrentIrpStackLocation(irp); + + switch (IoControlCode) + { + case IOCTL_INTERNAL_USB_SUBMIT_URB: + + KdPrint((DRIVERNAME ">> IOCTL_INTERNAL_USB_SUBMIT_URB\n")); + + urb = (PURB)URB_FROM_IRP(irp); + + switch (urb->UrbHeader.Function) + { + case URB_FUNCTION_CONTROL_TRANSFER: + + KdPrint((DRIVERNAME ">> >> URB_FUNCTION_CONTROL_TRANSFER\n")); + + switch (urb->UrbControlTransfer.SetupPacket[6]) + { + case 0x04: + // + // Xenon magic + // + COPY_BYTE_ARRAY(urb->UrbControlTransfer.TransferBuffer, P99_PROTECT({ + 0x31, 0x3F, 0xCF, 0xDC + })); + status = STATUS_SUCCESS; + break; + case 0x14: + // + // This is some weird USB 1.0 condition and _must fail_ + // + urb->UrbControlTransfer.Hdr.Status = USBD_STATUS_STALL_PID; + status = STATUS_UNSUCCESSFUL; + break; + case 0x08: + // + // This is some weird USB 1.0 condition and _must fail_ + // + urb->UrbControlTransfer.Hdr.Status = USBD_STATUS_STALL_PID; + status = STATUS_UNSUCCESSFUL; + break; + default: + status = STATUS_SUCCESS; + break; + } + + break; + + case URB_FUNCTION_CONTROL_TRANSFER_EX: + + KdPrint((DRIVERNAME ">> >> URB_FUNCTION_CONTROL_TRANSFER_EX\n")); + + status = STATUS_UNSUCCESSFUL; + + break; + + case URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER: + + KdPrint((DRIVERNAME ">> >> URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER\n")); + + status = UsbPdo_BulkOrInterruptTransfer(urb, hDevice, Request); + + break; + + case URB_FUNCTION_SELECT_CONFIGURATION: + + KdPrint((DRIVERNAME ">> >> URB_FUNCTION_SELECT_CONFIGURATION\n")); + + status = UsbPdo_SelectConfiguration(urb, pdoData); + + break; + + case URB_FUNCTION_SELECT_INTERFACE: + + KdPrint((DRIVERNAME ">> >> URB_FUNCTION_SELECT_INTERFACE\n")); + + status = UsbPdo_SelectInterface(urb, pdoData); + + break; + + case URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE: + + KdPrint((DRIVERNAME ">> >> URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE\n")); + + switch (urb->UrbControlDescriptorRequest.DescriptorType) + { + case USB_DEVICE_DESCRIPTOR_TYPE: + + KdPrint((DRIVERNAME ">> >> >> USB_DEVICE_DESCRIPTOR_TYPE\n")); + + status = UsbPdo_GetDeviceDescriptorType(urb, pdoData); + + break; + + case USB_CONFIGURATION_DESCRIPTOR_TYPE: + + KdPrint((DRIVERNAME ">> >> >> USB_CONFIGURATION_DESCRIPTOR_TYPE\n")); + + status = UsbPdo_GetConfigurationDescriptorType(urb, pdoData); + + if (!NT_SUCCESS(status)) + { + BUS_PDO_REPORT_STAGE_RESULT(pdoData->BusInterface, ViGEmPdoInternalIoControl, pdoData->SerialNo, status); + } + + break; + + case USB_STRING_DESCRIPTOR_TYPE: + + KdPrint((DRIVERNAME ">> >> >> USB_STRING_DESCRIPTOR_TYPE\n")); + + status = UsbPdo_GetStringDescriptorType(urb, pdoData); + + if (!NT_SUCCESS(status)) + { + BUS_PDO_REPORT_STAGE_RESULT(pdoData->BusInterface, ViGEmPdoInternalIoControl, pdoData->SerialNo, status); + } + + break; + case USB_INTERFACE_DESCRIPTOR_TYPE: + + KdPrint((DRIVERNAME ">> >> >> USB_INTERFACE_DESCRIPTOR_TYPE\n")); + + break; + + case USB_ENDPOINT_DESCRIPTOR_TYPE: + + KdPrint((DRIVERNAME ">> >> >> USB_ENDPOINT_DESCRIPTOR_TYPE\n")); + + break; + + default: + KdPrint((DRIVERNAME ">> >> >> Unknown descriptor type\n")); + break; + } + + KdPrint((DRIVERNAME "<< <<\n")); + + break; + + case URB_FUNCTION_GET_STATUS_FROM_DEVICE: + + KdPrint((DRIVERNAME ">> >> URB_FUNCTION_GET_STATUS_FROM_DEVICE\n")); + + // Defaults always succeed + status = STATUS_SUCCESS; + + // + // This IOCTL code is a nice indicator that the virtual XUSB device is + // "powered up" and ready to get interacted with so we can report + // success to the parent bus. + // + BUS_PDO_REPORT_STAGE_RESULT(pdoData->BusInterface, ViGEmPdoInternalIoControl, pdoData->SerialNo, status); + + break; + + case URB_FUNCTION_ABORT_PIPE: + + KdPrint((DRIVERNAME ">> >> URB_FUNCTION_ABORT_PIPE\n")); + + status = UsbPdo_AbortPipe(hDevice); + + BUS_PDO_REPORT_STAGE_RESULT(pdoData->BusInterface, ViGEmPdoInternalIoControl, pdoData->SerialNo, status); + + break; + + case URB_FUNCTION_CLASS_INTERFACE: + + KdPrint((DRIVERNAME ">> >> URB_FUNCTION_CLASS_INTERFACE\n")); + + status = UsbPdo_ClassInterface(urb, hDevice, pdoData); + + break; + + case URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE: + + KdPrint((DRIVERNAME ">> >> URB_FUNCTION_GET_DESCRIPTOR_FROM_INTERFACE\n")); + + status = UsbPdo_GetDescriptorFromInterface(urb, pdoData); + + // + // This IOCTL code is a nice indicator that the virtual HIDUSB device is + // "powered up" and ready to get interacted with so we can report + // success to the parent bus. + // + BUS_PDO_REPORT_STAGE_RESULT(pdoData->BusInterface, ViGEmPdoInternalIoControl, pdoData->SerialNo, status); + + break; + + default: + KdPrint((DRIVERNAME ">> >> Unknown function: 0x%X\n", urb->UrbHeader.Function)); + break; + } + + KdPrint((DRIVERNAME "<<\n")); + + break; + + case IOCTL_INTERNAL_USB_GET_PORT_STATUS: + + KdPrint((DRIVERNAME ">> IOCTL_INTERNAL_USB_GET_PORT_STATUS\n")); + + // We report the (virtual) port as always active + *(unsigned long *)irpStack->Parameters.Others.Argument1 = USBD_PORT_ENABLED | USBD_PORT_CONNECTED; + + status = STATUS_SUCCESS; + + break; + + case IOCTL_INTERNAL_USB_RESET_PORT: + + KdPrint((DRIVERNAME ">> IOCTL_INTERNAL_USB_RESET_PORT\n")); + + // Sure, why not ;) + status = STATUS_SUCCESS; + + break; + + case IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION: + + KdPrint((DRIVERNAME ">> IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION\n")); + + // TODO: implement + // This happens if the I/O latency is too high so HIDUSB aborts communication. + status = STATUS_SUCCESS; + + break; + + default: + KdPrint((DRIVERNAME ">> Unknown I/O control code 0x%X\n", IoControlCode)); + break; + } + + if (status != STATUS_PENDING) + { + WdfRequestComplete(Request, status); + } +} + diff --git a/sys/resource.h b/sys/resource.h new file mode 100644 index 0000000..97b180c --- /dev/null +++ b/sys/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ViGEmBus.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/sys/usbpdo.c b/sys/usbpdo.c new file mode 100644 index 0000000..7311007 --- /dev/null +++ b/sys/usbpdo.c @@ -0,0 +1,1208 @@ +/* +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" + + +// +// Dummy function to satisfy USB interface +// +BOOLEAN USB_BUSIFFN UsbPdo_IsDeviceHighSpeed(IN PVOID BusContext) +{ + UNREFERENCED_PARAMETER(BusContext); + + KdPrint((DRIVERNAME "UsbPdo_IsDeviceHighSpeed: TRUE\n")); + + return TRUE; +} + +// +// Dummy function to satisfy USB interface +// +NTSTATUS USB_BUSIFFN UsbPdo_QueryBusInformation( + IN PVOID BusContext, + IN ULONG Level, + IN OUT PVOID BusInformationBuffer, + IN OUT PULONG BusInformationBufferLength, + OUT PULONG BusInformationActualLength +) +{ + UNREFERENCED_PARAMETER(BusContext); + UNREFERENCED_PARAMETER(Level); + UNREFERENCED_PARAMETER(BusInformationBuffer); + UNREFERENCED_PARAMETER(BusInformationBufferLength); + UNREFERENCED_PARAMETER(BusInformationActualLength); + + KdPrint((DRIVERNAME "UsbPdo_QueryBusInformation: STATUS_UNSUCCESSFUL\n")); + return STATUS_UNSUCCESSFUL; +} + +// +// Dummy function to satisfy USB interface +// +NTSTATUS USB_BUSIFFN UsbPdo_SubmitIsoOutUrb(IN PVOID BusContext, IN PURB Urb) +{ + UNREFERENCED_PARAMETER(BusContext); + UNREFERENCED_PARAMETER(Urb); + + KdPrint((DRIVERNAME "UsbPdo_SubmitIsoOutUrb: STATUS_UNSUCCESSFUL\n")); + return STATUS_UNSUCCESSFUL; +} + +// +// Dummy function to satisfy USB interface +// +NTSTATUS USB_BUSIFFN UsbPdo_QueryBusTime(IN PVOID BusContext, IN OUT PULONG CurrentUsbFrame) +{ + UNREFERENCED_PARAMETER(BusContext); + UNREFERENCED_PARAMETER(CurrentUsbFrame); + + KdPrint((DRIVERNAME "UsbPdo_QueryBusTime: STATUS_UNSUCCESSFUL\n")); + return STATUS_UNSUCCESSFUL; +} + +// +// Dummy function to satisfy USB interface +// +VOID USB_BUSIFFN UsbPdo_GetUSBDIVersion( + IN PVOID BusContext, + IN OUT PUSBD_VERSION_INFORMATION VersionInformation, + IN OUT PULONG HcdCapabilities +) +{ + UNREFERENCED_PARAMETER(BusContext); + + KdPrint((DRIVERNAME "UsbPdo_GetUSBDIVersion: 0x500, 0x200\n")); + + if (VersionInformation != NULL) + { + VersionInformation->USBDI_Version = 0x500; /* Usbport */ + VersionInformation->Supported_USB_Version = 0x200; /* USB 2.0 */ + } + + if (HcdCapabilities != NULL) + { + *HcdCapabilities = 0; + } +} + +// +// Set device descriptor to identify the current USB device. +// +NTSTATUS UsbPdo_GetDeviceDescriptorType(PURB urb, PPDO_DEVICE_DATA pCommon) +{ + PUSB_DEVICE_DESCRIPTOR pDescriptor = (PUSB_DEVICE_DESCRIPTOR)urb->UrbControlDescriptorRequest.TransferBuffer; + + switch (pCommon->TargetType) + { + case Xbox360Wired: + + Xusb_GetDeviceDescriptorType(pDescriptor, pCommon); + + break; + + case DualShock4Wired: + + Ds4_GetDeviceDescriptorType(pDescriptor, pCommon); + + break; + + case XboxOneWired: + + Xgip_GetDeviceDescriptorType(pDescriptor, pCommon); + + break; + + default: + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +// +// Set configuration descriptor, expose interfaces and endpoints. +// +NTSTATUS UsbPdo_GetConfigurationDescriptorType(PURB urb, PPDO_DEVICE_DATA pCommon) +{ + PUCHAR Buffer = (PUCHAR)urb->UrbControlDescriptorRequest.TransferBuffer; + + // First request just gets required buffer size back + if (urb->UrbControlDescriptorRequest.TransferBufferLength == sizeof(USB_CONFIGURATION_DESCRIPTOR)) + { + ULONG length = sizeof(USB_CONFIGURATION_DESCRIPTOR); + + switch (pCommon->TargetType) + { + case Xbox360Wired: + + Xusb_GetConfigurationDescriptorType(Buffer, length); + + break; + case DualShock4Wired: + + Ds4_GetConfigurationDescriptorType(Buffer, length); + + break; + case XboxOneWired: + + Xgip_GetConfigurationDescriptorType(Buffer, length); + + break; + default: + return STATUS_UNSUCCESSFUL; + } + } + + ULONG length = urb->UrbControlDescriptorRequest.TransferBufferLength; + + // Second request can store the whole descriptor + switch (pCommon->TargetType) + { + case Xbox360Wired: + + if (length >= XUSB_DESCRIPTOR_SIZE) + { + Xusb_GetConfigurationDescriptorType(Buffer, XUSB_DESCRIPTOR_SIZE); + } + + break; + case DualShock4Wired: + + if (length >= DS4_DESCRIPTOR_SIZE) + { + Ds4_GetConfigurationDescriptorType(Buffer, DS4_DESCRIPTOR_SIZE); + } + + break; + case XboxOneWired: + + if (length >= XGIP_DESCRIPTOR_SIZE) + { + Xgip_GetConfigurationDescriptorType(Buffer, XGIP_DESCRIPTOR_SIZE); + } + + break; + default: + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +// +// Set device string descriptors (currently only used in DS4 emulation). +// +NTSTATUS UsbPdo_GetStringDescriptorType(PURB urb, PPDO_DEVICE_DATA pCommon) +{ + KdPrint((DRIVERNAME "Index = %d\n", urb->UrbControlDescriptorRequest.Index)); + + switch (pCommon->TargetType) + { + case DualShock4Wired: + { + switch (urb->UrbControlDescriptorRequest.Index) + { + case 0: + { + // "American English" + UCHAR LangId[HID_LANGUAGE_ID_LENGTH] = + { + 0x04, 0x03, 0x09, 0x04 + }; + + urb->UrbControlDescriptorRequest.TransferBufferLength = HID_LANGUAGE_ID_LENGTH; + RtlCopyBytes(urb->UrbControlDescriptorRequest.TransferBuffer, LangId, HID_LANGUAGE_ID_LENGTH); + + break; + } + case 1: + { + KdPrint((DRIVERNAME "LanguageId = 0x%X\n", urb->UrbControlDescriptorRequest.LanguageId)); + + if (urb->UrbControlDescriptorRequest.TransferBufferLength < DS4_MANUFACTURER_NAME_LENGTH) + { + PUSB_STRING_DESCRIPTOR pDesc = (PUSB_STRING_DESCRIPTOR)urb->UrbControlDescriptorRequest.TransferBuffer; + pDesc->bLength = DS4_MANUFACTURER_NAME_LENGTH; + break; + } + + // "Sony Computer Entertainment" + UCHAR ManufacturerString[DS4_MANUFACTURER_NAME_LENGTH] = + { + 0x38, 0x03, 0x53, 0x00, 0x6F, 0x00, 0x6E, 0x00, + 0x79, 0x00, 0x20, 0x00, 0x43, 0x00, 0x6F, 0x00, + 0x6D, 0x00, 0x70, 0x00, 0x75, 0x00, 0x74, 0x00, + 0x65, 0x00, 0x72, 0x00, 0x20, 0x00, 0x45, 0x00, + 0x6E, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x74, 0x00, 0x61, 0x00, 0x69, 0x00, 0x6E, 0x00, + 0x6D, 0x00, 0x65, 0x00, 0x6E, 0x00, 0x74, 0x00 + }; + + urb->UrbControlDescriptorRequest.TransferBufferLength = DS4_MANUFACTURER_NAME_LENGTH; + RtlCopyBytes(urb->UrbControlDescriptorRequest.TransferBuffer, ManufacturerString, DS4_MANUFACTURER_NAME_LENGTH); + + break; + } + case 2: + { + KdPrint((DRIVERNAME "LanguageId = 0x%X\n", urb->UrbControlDescriptorRequest.LanguageId)); + + if (urb->UrbControlDescriptorRequest.TransferBufferLength < DS4_PRODUCT_NAME_LENGTH) + { + PUSB_STRING_DESCRIPTOR pDesc = (PUSB_STRING_DESCRIPTOR)urb->UrbControlDescriptorRequest.TransferBuffer; + pDesc->bLength = DS4_PRODUCT_NAME_LENGTH; + break; + } + + // "Wireless Controller" + UCHAR ProductString[DS4_PRODUCT_NAME_LENGTH] = + { + 0x28, 0x03, 0x57, 0x00, 0x69, 0x00, 0x72, 0x00, + 0x65, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x73, 0x00, + 0x73, 0x00, 0x20, 0x00, 0x43, 0x00, 0x6F, 0x00, + 0x6E, 0x00, 0x74, 0x00, 0x72, 0x00, 0x6F, 0x00, + 0x6C, 0x00, 0x6C, 0x00, 0x65, 0x00, 0x72, 0x00 + }; + + urb->UrbControlDescriptorRequest.TransferBufferLength = DS4_PRODUCT_NAME_LENGTH; + RtlCopyBytes(urb->UrbControlDescriptorRequest.TransferBuffer, ProductString, DS4_PRODUCT_NAME_LENGTH); + + break; + } + default: + break; + } + + break; + } + default: + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +// +// Fakes a successfully selected configuration. +// +NTSTATUS UsbPdo_SelectConfiguration(PURB urb, PPDO_DEVICE_DATA pCommon) +{ + PUSBD_INTERFACE_INFORMATION pInfo; + + pInfo = &urb->UrbSelectConfiguration.Interface; + + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: TotalLength %d\n", urb->UrbHeader.Length)); + + if (urb->UrbHeader.Length == sizeof(struct _URB_SELECT_CONFIGURATION)) + { + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: NULL ConfigurationDescriptor\n")); + return STATUS_SUCCESS; + } + + switch (pCommon->TargetType) + { + case Xbox360Wired: + + if (urb->UrbHeader.Length < XUSB_CONFIGURATION_SIZE) + { + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: Invalid ConfigurationDescriptor\n")); + return STATUS_INVALID_PARAMETER; + } + + Xusb_SelectConfiguration(pInfo); + + break; + + case DualShock4Wired: + + if (urb->UrbHeader.Length < DS4_CONFIGURATION_SIZE) + { + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: Invalid ConfigurationDescriptor\n")); + return STATUS_INVALID_PARAMETER; + } + + Ds4_SelectConfiguration(pInfo); + + break; + + case XboxOneWired: + + if (urb->UrbHeader.Length < XGIP_CONFIGURATION_SIZE) + { + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: Invalid ConfigurationDescriptor\n")); + return STATUS_INVALID_PARAMETER; + } + + Xgip_SelectConfiguration(pInfo); + + break; + + default: + return STATUS_UNSUCCESSFUL; + } + + return STATUS_SUCCESS; +} + +// +// Fakes a successfully selected interface. +// +NTSTATUS UsbPdo_SelectInterface(PURB urb, PPDO_DEVICE_DATA pCommon) +{ + PUSBD_INTERFACE_INFORMATION pInfo = &urb->UrbSelectInterface.Interface; + + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_INTERFACE: Length %d, Interface %d, Alternate %d, Pipes %d\n", + (int)pInfo->Length, + (int)pInfo->InterfaceNumber, + (int)pInfo->AlternateSetting, + pInfo->NumberOfPipes)); + + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_INTERFACE: Class %d, SubClass %d, Protocol %d\n", + (int)pInfo->Class, + (int)pInfo->SubClass, + (int)pInfo->Protocol)); + + switch (pCommon->TargetType) + { + case Xbox360Wired: + { + if (pInfo->InterfaceNumber == 1) + { + pInfo[0].Class = 0xFF; + pInfo[0].SubClass = 0x5D; + pInfo[0].Protocol = 0x03; + pInfo[0].NumberOfPipes = 0x04; + + pInfo[0].InterfaceHandle = (USBD_INTERFACE_HANDLE)0xFFFF0000; + + pInfo[0].Pipes[0].MaximumTransferSize = 0x00400000; + pInfo[0].Pipes[0].MaximumPacketSize = 0x20; + pInfo[0].Pipes[0].EndpointAddress = 0x82; + pInfo[0].Pipes[0].Interval = 0x04; + pInfo[0].Pipes[0].PipeType = 0x03; + pInfo[0].Pipes[0].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0082; + pInfo[0].Pipes[0].PipeFlags = 0x00; + + pInfo[0].Pipes[1].MaximumTransferSize = 0x00400000; + pInfo[0].Pipes[1].MaximumPacketSize = 0x20; + pInfo[0].Pipes[1].EndpointAddress = 0x02; + pInfo[0].Pipes[1].Interval = 0x08; + pInfo[0].Pipes[1].PipeType = 0x03; + pInfo[0].Pipes[1].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0002; + pInfo[0].Pipes[1].PipeFlags = 0x00; + + pInfo[0].Pipes[2].MaximumTransferSize = 0x00400000; + pInfo[0].Pipes[2].MaximumPacketSize = 0x20; + pInfo[0].Pipes[2].EndpointAddress = 0x83; + pInfo[0].Pipes[2].Interval = 0x08; + pInfo[0].Pipes[2].PipeType = 0x03; + pInfo[0].Pipes[2].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0083; + pInfo[0].Pipes[2].PipeFlags = 0x00; + + pInfo[0].Pipes[3].MaximumTransferSize = 0x00400000; + pInfo[0].Pipes[3].MaximumPacketSize = 0x20; + pInfo[0].Pipes[3].EndpointAddress = 0x03; + pInfo[0].Pipes[3].Interval = 0x08; + pInfo[0].Pipes[3].PipeType = 0x03; + pInfo[0].Pipes[3].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0003; + pInfo[0].Pipes[3].PipeFlags = 0x00; + + return STATUS_SUCCESS; + } + + if (pInfo->InterfaceNumber == 2) + { + pInfo[0].Class = 0xFF; + pInfo[0].SubClass = 0x5D; + pInfo[0].Protocol = 0x02; + pInfo[0].NumberOfPipes = 0x01; + + pInfo[0].InterfaceHandle = (USBD_INTERFACE_HANDLE)0xFFFF0000; + + pInfo[0].Pipes[0].MaximumTransferSize = 0x00400000; + pInfo[0].Pipes[0].MaximumPacketSize = 0x20; + pInfo[0].Pipes[0].EndpointAddress = 0x84; + pInfo[0].Pipes[0].Interval = 0x04; + pInfo[0].Pipes[0].PipeType = 0x03; + pInfo[0].Pipes[0].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0084; + pInfo[0].Pipes[0].PipeFlags = 0x00; + + return STATUS_SUCCESS; + } + } + case DualShock4Wired: + { + KdPrint((DRIVERNAME "Warning: not implemented\n")); + break; + } + default: + break; + } + + return STATUS_INVALID_PARAMETER; +} + +// +// Dispatch interrupt transfers. +// +NTSTATUS UsbPdo_BulkOrInterruptTransfer(PURB urb, WDFDEVICE Device, WDFREQUEST Request) +{ + struct _URB_BULK_OR_INTERRUPT_TRANSFER* pTransfer = &urb->UrbBulkOrInterruptTransfer; + NTSTATUS status; + PPDO_DEVICE_DATA pdoData; + WDFREQUEST notifyRequest; + + pdoData = PdoGetData(Device); + + if (pdoData == NULL) + { + KdPrint((DRIVERNAME ">> >> >> Invalid common context\n")); + return STATUS_INVALID_PARAMETER; + } + + switch (pdoData->TargetType) + { + case Xbox360Wired: + { + PXUSB_DEVICE_DATA xusb = XusbGetData(Device); + + // Check context + if (xusb == NULL) + { + KdPrint((DRIVERNAME "No XUSB context found on device %p\n", Device)); + + return STATUS_UNSUCCESSFUL; + } + + // Data coming FROM us TO higher driver + if (pTransfer->TransferFlags & USBD_TRANSFER_DIRECTION_IN) + { + KdPrint((DRIVERNAME ">> >> >> Incoming request, queuing...\n")); + + if (XUSB_IS_DATA_PIPE(pTransfer)) + { + // + // Send "boot sequence" first, then the actual inputs + // + switch (xusb->InterruptInitStage) + { + case 0: + xusb->InterruptInitStage++; + pTransfer->TransferBufferLength = XUSB_INIT_STAGE_SIZE; + COPY_BYTE_ARRAY(pTransfer->TransferBuffer, P99_PROTECT({ + 0x01, 0x03, 0x0E + })); + return STATUS_SUCCESS; + case 1: + xusb->InterruptInitStage++; + pTransfer->TransferBufferLength = XUSB_INIT_STAGE_SIZE; + COPY_BYTE_ARRAY(pTransfer->TransferBuffer, P99_PROTECT({ + 0x02, 0x03, 0x00 + })); + return STATUS_SUCCESS; + case 2: + xusb->InterruptInitStage++; + pTransfer->TransferBufferLength = XUSB_INIT_STAGE_SIZE; + COPY_BYTE_ARRAY(pTransfer->TransferBuffer, P99_PROTECT({ + 0x03, 0x03, 0x03 + })); + return STATUS_SUCCESS; + case 3: + xusb->InterruptInitStage++; + pTransfer->TransferBufferLength = XUSB_INIT_STAGE_SIZE; + COPY_BYTE_ARRAY(pTransfer->TransferBuffer, P99_PROTECT({ + 0x08, 0x03, 0x00 + })); + return STATUS_SUCCESS; + case 4: + xusb->InterruptInitStage++; + pTransfer->TransferBufferLength = sizeof(XUSB_INTERRUPT_IN_PACKET); + COPY_BYTE_ARRAY(pTransfer->TransferBuffer, P99_PROTECT({ + 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0xe4, 0xf2, + 0xb3, 0xf8, 0x49, 0xf3, 0xb0, 0xfc, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + })); + return STATUS_SUCCESS; + case 5: + xusb->InterruptInitStage++; + pTransfer->TransferBufferLength = XUSB_INIT_STAGE_SIZE; + COPY_BYTE_ARRAY(pTransfer->TransferBuffer, P99_PROTECT({ + 0x01, 0x03, 0x03 + })); + return STATUS_SUCCESS; + default: + /* This request is sent periodically and relies on data the "feeder" + * has to supply, so we queue this request and return with STATUS_PENDING. + * The request gets completed as soon as the "feeder" sent an update. */ + status = WdfRequestForwardToIoQueue(Request, pdoData->PendingUsbInRequests); + + return (NT_SUCCESS(status)) ? STATUS_PENDING : status; + } + } + + if (XUSB_IS_CONTROL_PIPE(pTransfer)) + { + if (!xusb->ReportedCapabilities && pTransfer->TransferBufferLength >= XUSB_INIT_STAGE_SIZE) + { + pTransfer->TransferBufferLength = XUSB_INIT_STAGE_SIZE; + COPY_BYTE_ARRAY(pTransfer->TransferBuffer, P99_PROTECT({ + 0x05, 0x03, 0x00 + })); + + xusb->ReportedCapabilities = TRUE; + + return STATUS_SUCCESS; + } + + status = WdfRequestForwardToIoQueue(Request, xusb->HoldingUsbInRequests); + + return (NT_SUCCESS(status)) ? STATUS_PENDING : status; + } + } + + // Data coming FROM the higher driver TO us + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER: Handle %p, Flags %X, Length %d\n", + pTransfer->PipeHandle, + pTransfer->TransferFlags, + pTransfer->TransferBufferLength)); + + if (pTransfer->TransferBufferLength == XUSB_LEDSET_SIZE) // Led + { + PUCHAR Buffer = pTransfer->TransferBuffer; + + KdPrint((DRIVERNAME "-- LED Buffer: %02X %02X %02X\n", Buffer[0], Buffer[1], Buffer[2])); + + // extract LED byte to get controller slot + if (Buffer[0] == 0x01 && Buffer[1] == 0x03 && Buffer[2] >= 0x02) + { + if (Buffer[2] == 0x02) xusb->LedNumber = 0; + if (Buffer[2] == 0x03) xusb->LedNumber = 1; + if (Buffer[2] == 0x04) xusb->LedNumber = 2; + if (Buffer[2] == 0x05) xusb->LedNumber = 3; + + KdPrint((DRIVERNAME "-- LED Number: %d\n", xusb->LedNumber)); + } + } + + // Extract rumble (vibration) information + if (pTransfer->TransferBufferLength == XUSB_RUMBLE_SIZE) + { + PUCHAR Buffer = pTransfer->TransferBuffer; + + KdPrint((DRIVERNAME "-- Rumble Buffer: %02X %02X %02X %02X %02X %02X %02X %02X\n", + Buffer[0], + Buffer[1], + Buffer[2], + Buffer[3], + Buffer[4], + Buffer[5], + Buffer[6], + Buffer[7])); + + RtlCopyBytes(xusb->Rumble, Buffer, pTransfer->TransferBufferLength); + } + + // Notify user-mode process that new data is available + status = WdfIoQueueRetrieveNextRequest(pdoData->PendingNotificationRequests, ¬ifyRequest); + + if (NT_SUCCESS(status)) + { + PXUSB_REQUEST_NOTIFICATION notify = NULL; + + status = WdfRequestRetrieveOutputBuffer(notifyRequest, sizeof(XUSB_REQUEST_NOTIFICATION), (PVOID)¬ify, NULL); + + if (NT_SUCCESS(status)) + { + // Assign values to output buffer + notify->Size = sizeof(XUSB_REQUEST_NOTIFICATION); + notify->SerialNo = pdoData->SerialNo; + notify->LedNumber = xusb->LedNumber; + notify->LargeMotor = xusb->Rumble[3]; + notify->SmallMotor = xusb->Rumble[4]; + + WdfRequestCompleteWithInformation(notifyRequest, status, notify->Size); + } + else + { + KdPrint((DRIVERNAME "WdfRequestRetrieveOutputBuffer failed with status 0x%X\n", status)); + } + } + + break; + } + case DualShock4Wired: + { + PDS4_DEVICE_DATA ds4Data = Ds4GetData(Device); + + // Data coming FROM us TO higher driver + if (pTransfer->TransferFlags & USBD_TRANSFER_DIRECTION_IN + && pTransfer->PipeHandle == (USBD_PIPE_HANDLE)0xFFFF0084) + { + // KdPrint((DRIVERNAME ">> >> >> Incoming request, queuing...\n")); + + /* This request is sent periodically and relies on data the "feeder" + has to supply, so we queue this request and return with STATUS_PENDING. + The request gets completed as soon as the "feeder" sent an update. */ + status = WdfRequestForwardToIoQueue(Request, pdoData->PendingUsbInRequests); + + return (NT_SUCCESS(status)) ? STATUS_PENDING : status; + } + + // Store relevant bytes of buffer in PDO context + RtlCopyBytes(&ds4Data->OutputReport, + (PUCHAR)pTransfer->TransferBuffer + DS4_OUTPUT_BUFFER_OFFSET, + DS4_OUTPUT_BUFFER_LENGTH); + + // Notify user-mode process that new data is available + status = WdfIoQueueRetrieveNextRequest(pdoData->PendingNotificationRequests, ¬ifyRequest); + + if (NT_SUCCESS(status)) + { + PDS4_REQUEST_NOTIFICATION notify = NULL; + + status = WdfRequestRetrieveOutputBuffer(notifyRequest, sizeof(DS4_REQUEST_NOTIFICATION), (PVOID)¬ify, NULL); + + if (NT_SUCCESS(status)) + { + // Assign values to output buffer + notify->Size = sizeof(DS4_REQUEST_NOTIFICATION); + notify->SerialNo = pdoData->SerialNo; + notify->Report = ds4Data->OutputReport; + + WdfRequestCompleteWithInformation(notifyRequest, status, notify->Size); + } + else + { + KdPrint((DRIVERNAME "WdfRequestRetrieveOutputBuffer failed with status 0x%X\n", status)); + } + } + + break; + } + case XboxOneWired: + { + PXGIP_DEVICE_DATA xgipData = XgipGetData(Device); + + // Data coming FROM us TO higher driver + if (pTransfer->TransferFlags & USBD_TRANSFER_DIRECTION_IN) + { + KdPrint((DRIVERNAME ">> >> >> Incoming request, queuing...\n")); + + /* This request is sent periodically and relies on data the "feeder" + has to supply, so we queue this request and return with STATUS_PENDING. + The request gets completed as soon as the "feeder" sent an update. */ + status = WdfRequestForwardToIoQueue(Request, xgipData->PendingUsbInRequests); + + return (NT_SUCCESS(status)) ? STATUS_PENDING : status; + } + + // Data coming FROM the higher driver TO us + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER: Handle %p, Flags %X, Length %d\n", + pTransfer->PipeHandle, + pTransfer->TransferFlags, + pTransfer->TransferBufferLength)); + + break; + } + default: + break; + } + + return STATUS_SUCCESS; +} + +// +// Clean-up actions on shutdown. +// +NTSTATUS UsbPdo_AbortPipe(WDFDEVICE Device) +{ + PPDO_DEVICE_DATA pdoData = PdoGetData(Device); + + switch (pdoData->TargetType) + { + case DualShock4Wired: + { + PDS4_DEVICE_DATA ds4 = Ds4GetData(Device); + + // Check context + if (ds4 == NULL) + { + KdPrint((DRIVERNAME "No DS4 context found on device %p\n", Device)); + + return STATUS_UNSUCCESSFUL; + } + + // Higher driver shutting down, emptying PDOs queues + WdfTimerStop(ds4->PendingUsbInRequestsTimer, TRUE); + + break; + } + default: + break; + } + + // Higher driver shutting down, emptying PDOs queues + WdfIoQueuePurge(pdoData->PendingUsbInRequests, NULL, NULL); + WdfIoQueuePurge(pdoData->PendingNotificationRequests, NULL, NULL); + + return STATUS_SUCCESS; +} + +// +// Processes URBs containing HID-related requests. +// +NTSTATUS UsbPdo_ClassInterface(PURB urb, WDFDEVICE Device, PPDO_DEVICE_DATA pCommon) +{ + struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST* pRequest = &urb->UrbControlVendorClassRequest; + + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_CLASS_INTERFACE\n")); + KdPrint((DRIVERNAME ">> >> >> TransferFlags = 0x%X, Request = 0x%X, Value = 0x%X, Index = 0x%X, BufLen = %d\n", + pRequest->TransferFlags, + pRequest->Request, + pRequest->Value, + pRequest->Index, + pRequest->TransferBufferLength)); + + switch (pCommon->TargetType) + { + case DualShock4Wired: + { + PDS4_DEVICE_DATA ds4 = Ds4GetData(Device); + + switch (pRequest->Request) + { + case HID_REQUEST_GET_REPORT: + { + UCHAR reportId = HID_GET_REPORT_ID(pRequest); + UCHAR reportType = HID_GET_REPORT_TYPE(pRequest); + + KdPrint((DRIVERNAME ">> >> >> >> GET_REPORT(%d): %d\n", reportType, reportId)); + + switch (reportType) + { + case HID_REPORT_TYPE_FEATURE: + { + switch (reportId) + { + case HID_REPORT_ID_0: + { + // Source: http://eleccelerator.com/wiki/index.php?title=DualShock_4#Class_Requests + UCHAR Response[HID_GET_FEATURE_REPORT_SIZE_0] = + { + 0xA3, 0x41, 0x75, 0x67, 0x20, 0x20, 0x33, 0x20, + 0x32, 0x30, 0x31, 0x33, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x30, 0x37, 0x3A, 0x30, 0x31, 0x3A, 0x31, + 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x31, 0x03, 0x00, 0x00, + 0x00, 0x49, 0x00, 0x05, 0x00, 0x00, 0x80, 0x03, + 0x00 + }; + + pRequest->TransferBufferLength = HID_GET_FEATURE_REPORT_SIZE_0; + RtlCopyBytes(pRequest->TransferBuffer, Response, HID_GET_FEATURE_REPORT_SIZE_0); + + break; + } + case HID_REPORT_ID_1: + { + // Source: http://eleccelerator.com/wiki/index.php?title=DualShock_4#Class_Requests + UCHAR Response[HID_GET_FEATURE_REPORT_SIZE_1] = + { + 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, + 0x22, 0x7B, 0xDD, 0xB2, 0x22, 0x47, 0xDD, 0xBD, + 0x22, 0x43, 0xDD, 0x1C, 0x02, 0x1C, 0x02, 0x7F, + 0x1E, 0x2E, 0xDF, 0x60, 0x1F, 0x4C, 0xE0, 0x3A, + 0x1D, 0xC6, 0xDE, 0x08, 0x00 + }; + + pRequest->TransferBufferLength = HID_GET_FEATURE_REPORT_SIZE_1; + RtlCopyBytes(pRequest->TransferBuffer, Response, HID_GET_FEATURE_REPORT_SIZE_1); + + break; + } + case HID_REPORT_MAC_ADDRESSES_ID: + { + // Source: http://eleccelerator.com/wiki/index.php?title=DualShock_4#Class_Requests + UCHAR Response[HID_GET_FEATURE_REPORT_MAC_ADDRESSES_SIZE] = + { + 0x12, 0x8B, 0x09, 0x07, 0x6D, 0x66, 0x1C, 0x08, + 0x25, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + // Insert (auto-generated) target MAC address into response + RtlCopyBytes(Response + 1, &ds4->TargetMacAddress, sizeof(MAC_ADDRESS)); + // Adjust byte order + ReverseByteArray(Response + 1, sizeof(MAC_ADDRESS)); + + // Insert (auto-generated) host MAC address into response + RtlCopyBytes(Response + 10, &ds4->HostMacAddress, sizeof(MAC_ADDRESS)); + // Adjust byte order + ReverseByteArray(Response + 10, sizeof(MAC_ADDRESS)); + + pRequest->TransferBufferLength = HID_GET_FEATURE_REPORT_MAC_ADDRESSES_SIZE; + RtlCopyBytes(pRequest->TransferBuffer, Response, HID_GET_FEATURE_REPORT_MAC_ADDRESSES_SIZE); + + break; + } + default: + break; + } + break; + } + default: + break; + } + + break; + } + case HID_REQUEST_SET_REPORT: + { + UCHAR reportId = HID_GET_REPORT_ID(pRequest); + UCHAR reportType = HID_GET_REPORT_TYPE(pRequest); + + KdPrint((DRIVERNAME ">> >> >> >> SET_REPORT(%d): %d\n", reportType, reportId)); + + switch (reportType) + { + case HID_REPORT_TYPE_FEATURE: + { + switch (reportId) + { + case HID_REPORT_ID_3: + { + // Source: http://eleccelerator.com/wiki/index.php?title=DualShock_4#Class_Requests + UCHAR Response[HID_SET_FEATURE_REPORT_SIZE_0] = + { + 0x13, 0xAC, 0x9E, 0x17, 0x94, 0x05, 0xB0, 0x56, + 0xE8, 0x81, 0x38, 0x08, 0x06, 0x51, 0x41, 0xC0, + 0x7F, 0x12, 0xAA, 0xD9, 0x66, 0x3C, 0xCE + }; + + pRequest->TransferBufferLength = HID_SET_FEATURE_REPORT_SIZE_0; + RtlCopyBytes(pRequest->TransferBuffer, Response, HID_SET_FEATURE_REPORT_SIZE_0); + + break; + } + case HID_REPORT_ID_4: + { + // Source: http://eleccelerator.com/wiki/index.php?title=DualShock_4#Class_Requests + UCHAR Response[HID_SET_FEATURE_REPORT_SIZE_1] = + { + 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00 + }; + + pRequest->TransferBufferLength = HID_SET_FEATURE_REPORT_SIZE_1; + RtlCopyBytes(pRequest->TransferBuffer, Response, HID_SET_FEATURE_REPORT_SIZE_1); + + break; + } + default: + break; + } + break; + } + default: + break; + } + + break; + } + default: + break; + } + + break; + } + } + + return STATUS_SUCCESS; +} + +// +// Returns interface HID report descriptor. +// +NTSTATUS UsbPdo_GetDescriptorFromInterface(PURB urb, PPDO_DEVICE_DATA pCommon) +{ + NTSTATUS status = STATUS_INVALID_PARAMETER; + UCHAR Ds4HidReportDescriptor[DS4_HID_REPORT_DESCRIPTOR_SIZE] = + { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x05, // Usage (Game Pad) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x01, // Report ID (1) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x09, 0x32, // Usage (Z) + 0x09, 0x35, // Usage (Rz) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x04, // Report Count (4) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x09, 0x39, // Usage (Hat switch) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x07, // Logical Maximum (7) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x3B, 0x01, // Physical Maximum (315) + 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) + 0x75, 0x04, // Report Size (4) + 0x95, 0x01, // Report Count (1) + 0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State) + 0x65, 0x00, // Unit (None) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0x0E, // Usage Maximum (0x0E) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0E, // Report Count (14) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x09, 0x20, // Usage (0x20) + 0x75, 0x06, // Report Size (6) + 0x95, 0x01, // Report Count (1) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x7F, // Logical Maximum (127) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x33, // Usage (Rx) + 0x09, 0x34, // Usage (Ry) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x09, 0x21, // Usage (0x21) + 0x95, 0x36, // Report Count (54) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x85, 0x05, // Report ID (5) + 0x09, 0x22, // Usage (0x22) + 0x95, 0x1F, // Report Count (31) + 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x04, // Report ID (4) + 0x09, 0x23, // Usage (0x23) + 0x95, 0x24, // Report Count (36) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x02, // Report ID (2) + 0x09, 0x24, // Usage (0x24) + 0x95, 0x24, // Report Count (36) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x08, // Report ID (8) + 0x09, 0x25, // Usage (0x25) + 0x95, 0x03, // Report Count (3) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x10, // Report ID (16) + 0x09, 0x26, // Usage (0x26) + 0x95, 0x04, // Report Count (4) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x11, // Report ID (17) + 0x09, 0x27, // Usage (0x27) + 0x95, 0x02, // Report Count (2) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x12, // Report ID (18) + 0x06, 0x02, 0xFF, // Usage Page (Vendor Defined 0xFF02) + 0x09, 0x21, // Usage (0x21) + 0x95, 0x0F, // Report Count (15) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x13, // Report ID (19) + 0x09, 0x22, // Usage (0x22) + 0x95, 0x16, // Report Count (22) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x14, // Report ID (20) + 0x06, 0x05, 0xFF, // Usage Page (Vendor Defined 0xFF05) + 0x09, 0x20, // Usage (0x20) + 0x95, 0x10, // Report Count (16) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x15, // Report ID (21) + 0x09, 0x21, // Usage (0x21) + 0x95, 0x2C, // Report Count (44) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x06, 0x80, 0xFF, // Usage Page (Vendor Defined 0xFF80) + 0x85, 0x80, // Report ID (128) + 0x09, 0x20, // Usage (0x20) + 0x95, 0x06, // Report Count (6) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x81, // Report ID (129) + 0x09, 0x21, // Usage (0x21) + 0x95, 0x06, // Report Count (6) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x82, // Report ID (130) + 0x09, 0x22, // Usage (0x22) + 0x95, 0x05, // Report Count (5) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x83, // Report ID (131) + 0x09, 0x23, // Usage (0x23) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x84, // Report ID (132) + 0x09, 0x24, // Usage (0x24) + 0x95, 0x04, // Report Count (4) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x85, // Report ID (133) + 0x09, 0x25, // Usage (0x25) + 0x95, 0x06, // Report Count (6) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x86, // Report ID (134) + 0x09, 0x26, // Usage (0x26) + 0x95, 0x06, // Report Count (6) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x87, // Report ID (135) + 0x09, 0x27, // Usage (0x27) + 0x95, 0x23, // Report Count (35) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x88, // Report ID (136) + 0x09, 0x28, // Usage (0x28) + 0x95, 0x22, // Report Count (34) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x89, // Report ID (137) + 0x09, 0x29, // Usage (0x29) + 0x95, 0x02, // Report Count (2) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x90, // Report ID (144) + 0x09, 0x30, // Usage (0x30) + 0x95, 0x05, // Report Count (5) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x91, // Report ID (145) + 0x09, 0x31, // Usage (0x31) + 0x95, 0x03, // Report Count (3) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x92, // Report ID (146) + 0x09, 0x32, // Usage (0x32) + 0x95, 0x03, // Report Count (3) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0x93, // Report ID (147) + 0x09, 0x33, // Usage (0x33) + 0x95, 0x0C, // Report Count (12) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xA0, // Report ID (160) + 0x09, 0x40, // Usage (0x40) + 0x95, 0x06, // Report Count (6) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xA1, // Report ID (161) + 0x09, 0x41, // Usage (0x41) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xA2, // Report ID (162) + 0x09, 0x42, // Usage (0x42) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xA3, // Report ID (163) + 0x09, 0x43, // Usage (0x43) + 0x95, 0x30, // Report Count (48) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xA4, // Report ID (164) + 0x09, 0x44, // Usage (0x44) + 0x95, 0x0D, // Report Count (13) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xA5, // Report ID (165) + 0x09, 0x45, // Usage (0x45) + 0x95, 0x15, // Report Count (21) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xA6, // Report ID (166) + 0x09, 0x46, // Usage (0x46) + 0x95, 0x15, // Report Count (21) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xF0, // Report ID (240) + 0x09, 0x47, // Usage (0x47) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xF1, // Report ID (241) + 0x09, 0x48, // Usage (0x48) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xF2, // Report ID (242) + 0x09, 0x49, // Usage (0x49) + 0x95, 0x0F, // Report Count (15) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xA7, // Report ID (167) + 0x09, 0x4A, // Usage (0x4A) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xA8, // Report ID (168) + 0x09, 0x4B, // Usage (0x4B) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xA9, // Report ID (169) + 0x09, 0x4C, // Usage (0x4C) + 0x95, 0x08, // Report Count (8) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xAA, // Report ID (170) + 0x09, 0x4E, // Usage (0x4E) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xAB, // Report ID (171) + 0x09, 0x4F, // Usage (0x4F) + 0x95, 0x39, // Report Count (57) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xAC, // Report ID (172) + 0x09, 0x50, // Usage (0x50) + 0x95, 0x39, // Report Count (57) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xAD, // Report ID (173) + 0x09, 0x51, // Usage (0x51) + 0x95, 0x0B, // Report Count (11) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xAE, // Report ID (174) + 0x09, 0x52, // Usage (0x52) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xAF, // Report ID (175) + 0x09, 0x53, // Usage (0x53) + 0x95, 0x02, // Report Count (2) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x85, 0xB0, // Report ID (176) + 0x09, 0x54, // Usage (0x54) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0xC0, // End Collection + }; + + struct _URB_CONTROL_DESCRIPTOR_REQUEST* pRequest = &urb->UrbControlDescriptorRequest; + + KdPrint((DRIVERNAME ">> >> >> _URB_CONTROL_DESCRIPTOR_REQUEST: Buffer Length %d\n", pRequest->TransferBufferLength)); + + switch (pCommon->TargetType) + { + case DualShock4Wired: + { + if (pRequest->TransferBufferLength >= DS4_HID_REPORT_DESCRIPTOR_SIZE) + { + RtlCopyMemory(pRequest->TransferBuffer, Ds4HidReportDescriptor, DS4_HID_REPORT_DESCRIPTOR_SIZE); + status = STATUS_SUCCESS; + } + + break; + } + default: + break; + } + + return status; +} + diff --git a/sys/util.c b/sys/util.c new file mode 100644 index 0000000..0d6b7bc --- /dev/null +++ b/sys/util.c @@ -0,0 +1,59 @@ +/* +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 +#include "busenum.h" + + +VOID ReverseByteArray(PUCHAR Array, INT Length) +{ + PUCHAR s = (PUCHAR)ExAllocatePoolWithTag(NonPagedPool, sizeof(UCHAR) * Length, VIGEM_POOL_TAG); + INT c, d; + + if (s == NULL) + return; + + for (c = Length - 1, d = 0; c >= 0; c--, d++) + *(s + d) = *(Array + c); + + for (c = 0; c < Length; c++) + *(Array + c) = *(s + c); + + ExFreePoolWithTag(s, VIGEM_POOL_TAG); +} + +VOID GenerateRandomMacAddress(PMAC_ADDRESS Address) +{ + // Vendor "C0:13:37" + Address->Vendor0 = 0xC0; + Address->Vendor1 = 0x13; + Address->Vendor2 = 0x37; + + ULONG seed = KeQueryPerformanceCounter(NULL).LowPart; + + Address->Nic0 = RtlRandomEx(&seed) % 0xFF; + Address->Nic1 = RtlRandomEx(&seed) % 0xFF; + Address->Nic2 = RtlRandomEx(&seed) % 0xFF; +} diff --git a/sys/xgip.c b/sys/xgip.c new file mode 100644 index 0000000..6c42d3f --- /dev/null +++ b/sys/xgip.c @@ -0,0 +1,405 @@ +/* +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" + +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)) + { + KdPrint((DRIVERNAME "WdfDeviceAddQueryInterface failed status 0x%x\n", 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); + + KdPrint((DRIVERNAME "Initializing XGIP context...\n")); + + 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)) + { + KdPrint((DRIVERNAME "WdfIoQueueCreate failed 0x%x\n", 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)) + { + KdPrint((DRIVERNAME "WdfIoQueueCreate failed 0x%x\n", status)); + return status; + } + + WDF_OBJECT_ATTRIBUTES collectionAttribs; + WDF_OBJECT_ATTRIBUTES_INIT(&collectionAttribs); + + collectionAttribs.ParentObject = Device; + + status = WdfCollectionCreate(&collectionAttribs, &xgip->XboxgipSysInitCollection); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfCollectionCreate failed 0x%x\n", 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)) + { + KdPrint((DRIVERNAME "WdfTimerCreate failed 0x%x\n", 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) +{ + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: Length %d, Interface %d, Alternate %d, Pipes %d\n", + (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); + + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: Length %d, Interface %d, Alternate %d, Pipes %d\n", + (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) + { + KdPrint((DRIVERNAME "XBOXGIP ready, completing requests...\n")); + + // Get pending IN request + status = WdfIoQueueRetrieveNextRequest(xgip->PendingUsbInRequests, &usbRequest); + + if (NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "Request found\n")); + + // 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); + + KdPrint((DRIVERNAME "[%X] Buffer length: %d\n", + ((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) + { + KdPrint((DRIVERNAME "Collection finished\n")); + + WdfTimerStop(xgip->XboxgipSysInitTimer, FALSE); + } + } +} + diff --git a/sys/xusb.c b/sys/xusb.c new file mode 100644 index 0000000..7bda9e0 --- /dev/null +++ b/sys/xusb.c @@ -0,0 +1,506 @@ +/* +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" + +NTSTATUS Xusb_PreparePdo( + PWDFDEVICE_INIT DeviceInit, + USHORT VendorId, + USHORT ProductId, + PUNICODE_STRING DeviceId, + PUNICODE_STRING DeviceDescription) +{ + NTSTATUS status; + DECLARE_UNICODE_STRING_SIZE(buffer, MAX_HARDWARE_ID_LENGTH); + + // prepare device description + status = RtlUnicodeStringInit(DeviceDescription, L"Virtual Xbox 360 Controller"); + if (!NT_SUCCESS(status)) + return status; + + // Set hardware ID + RtlUnicodeStringPrintf(&buffer, L"USB\\VID_%04X&PID_%04X", VendorId, ProductId); + + RtlUnicodeStringCopy(DeviceId, &buffer); + + status = WdfPdoInitAddHardwareID(DeviceInit, &buffer); + if (!NT_SUCCESS(status)) + return status; + + // Set compatible IDs + RtlUnicodeStringInit(&buffer, L"USB\\MS_COMP_XUSB10"); + + status = WdfPdoInitAddCompatibleID(DeviceInit, &buffer); + if (!NT_SUCCESS(status)) + return status; + + RtlUnicodeStringInit(&buffer, L"USB\\Class_FF&SubClass_5D&Prot_01"); + + status = WdfPdoInitAddCompatibleID(DeviceInit, &buffer); + if (!NT_SUCCESS(status)) + return status; + + RtlUnicodeStringInit(&buffer, L"USB\\Class_FF&SubClass_5D"); + + 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 Xusb_PrepareHardware(WDFDEVICE Device) +{ + NTSTATUS status; + WDF_QUERY_INTERFACE_CONFIG ifaceCfg; + + INTERFACE dummyIface; + + dummyIface.Size = sizeof(INTERFACE); + dummyIface.Version = 1; + dummyIface.Context = (PVOID)Device; + + dummyIface.InterfaceReference = WdfDeviceInterfaceReferenceNoOp; + dummyIface.InterfaceDereference = WdfDeviceInterfaceDereferenceNoOp; + + /* XUSB.sys will query for the following three (unknown) interfaces + * BUT WONT USE IT so we just expose them to satisfy initialization. */ + + // Dummy 0 + + WDF_QUERY_INTERFACE_CONFIG_INIT(&ifaceCfg, (PINTERFACE)&dummyIface, &GUID_DEVINTERFACE_XUSB_UNKNOWN_0, NULL); + + status = WdfDeviceAddQueryInterface(Device, &ifaceCfg); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "Couldn't register unknown interface GUID: %08X-%04X-%04X-%02X%02X%02X%02X%02X%02X%02X%02X (status 0x%x)\n", + GUID_DEVINTERFACE_XUSB_UNKNOWN_0.Data1, + GUID_DEVINTERFACE_XUSB_UNKNOWN_0.Data2, + GUID_DEVINTERFACE_XUSB_UNKNOWN_0.Data3, + GUID_DEVINTERFACE_XUSB_UNKNOWN_0.Data4[0], + GUID_DEVINTERFACE_XUSB_UNKNOWN_0.Data4[1], + GUID_DEVINTERFACE_XUSB_UNKNOWN_0.Data4[2], + GUID_DEVINTERFACE_XUSB_UNKNOWN_0.Data4[3], + GUID_DEVINTERFACE_XUSB_UNKNOWN_0.Data4[4], + GUID_DEVINTERFACE_XUSB_UNKNOWN_0.Data4[5], + GUID_DEVINTERFACE_XUSB_UNKNOWN_0.Data4[6], + GUID_DEVINTERFACE_XUSB_UNKNOWN_0.Data4[7], + status)); + + return status; + } + + // Dummy 1 + + WDF_QUERY_INTERFACE_CONFIG_INIT(&ifaceCfg, (PINTERFACE)&dummyIface, &GUID_DEVINTERFACE_XUSB_UNKNOWN_1, NULL); + + status = WdfDeviceAddQueryInterface(Device, &ifaceCfg); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "Couldn't register unknown interface GUID: %08X-%04X-%04X-%02X%02X%02X%02X%02X%02X%02X%02X (status 0x%x)\n", + GUID_DEVINTERFACE_XUSB_UNKNOWN_1.Data1, + GUID_DEVINTERFACE_XUSB_UNKNOWN_1.Data2, + GUID_DEVINTERFACE_XUSB_UNKNOWN_1.Data3, + GUID_DEVINTERFACE_XUSB_UNKNOWN_1.Data4[0], + GUID_DEVINTERFACE_XUSB_UNKNOWN_1.Data4[1], + GUID_DEVINTERFACE_XUSB_UNKNOWN_1.Data4[2], + GUID_DEVINTERFACE_XUSB_UNKNOWN_1.Data4[3], + GUID_DEVINTERFACE_XUSB_UNKNOWN_1.Data4[4], + GUID_DEVINTERFACE_XUSB_UNKNOWN_1.Data4[5], + GUID_DEVINTERFACE_XUSB_UNKNOWN_1.Data4[6], + GUID_DEVINTERFACE_XUSB_UNKNOWN_1.Data4[7], + status)); + + return status; + } + + // Dummy 2 + + WDF_QUERY_INTERFACE_CONFIG_INIT(&ifaceCfg, (PINTERFACE)&dummyIface, &GUID_DEVINTERFACE_XUSB_UNKNOWN_2, NULL); + + status = WdfDeviceAddQueryInterface(Device, &ifaceCfg); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "Couldn't register unknown interface GUID: %08X-%04X-%04X-%02X%02X%02X%02X%02X%02X%02X%02X (status 0x%x)\n", + GUID_DEVINTERFACE_XUSB_UNKNOWN_2.Data1, + GUID_DEVINTERFACE_XUSB_UNKNOWN_2.Data2, + GUID_DEVINTERFACE_XUSB_UNKNOWN_2.Data3, + GUID_DEVINTERFACE_XUSB_UNKNOWN_2.Data4[0], + GUID_DEVINTERFACE_XUSB_UNKNOWN_2.Data4[1], + GUID_DEVINTERFACE_XUSB_UNKNOWN_2.Data4[2], + GUID_DEVINTERFACE_XUSB_UNKNOWN_2.Data4[3], + GUID_DEVINTERFACE_XUSB_UNKNOWN_2.Data4[4], + GUID_DEVINTERFACE_XUSB_UNKNOWN_2.Data4[5], + GUID_DEVINTERFACE_XUSB_UNKNOWN_2.Data4[6], + GUID_DEVINTERFACE_XUSB_UNKNOWN_2.Data4[7], + status)); + + return status; + } + + // Expose USB_BUS_INTERFACE_USBDI_GUID + + // This interface actually IS used + USB_BUS_INTERFACE_USBDI_V1 xusbInterface; + + xusbInterface.Size = sizeof(USB_BUS_INTERFACE_USBDI_V1); + xusbInterface.Version = USB_BUSIF_USBDI_VERSION_1; + xusbInterface.BusContext = (PVOID)Device; + + xusbInterface.InterfaceReference = WdfDeviceInterfaceReferenceNoOp; + xusbInterface.InterfaceDereference = WdfDeviceInterfaceDereferenceNoOp; + + xusbInterface.SubmitIsoOutUrb = UsbPdo_SubmitIsoOutUrb; + xusbInterface.GetUSBDIVersion = UsbPdo_GetUSBDIVersion; + xusbInterface.QueryBusTime = UsbPdo_QueryBusTime; + xusbInterface.QueryBusInformation = UsbPdo_QueryBusInformation; + xusbInterface.IsDeviceHighSpeed = UsbPdo_IsDeviceHighSpeed; + + WDF_QUERY_INTERFACE_CONFIG_INIT(&ifaceCfg, (PINTERFACE)&xusbInterface, &USB_BUS_INTERFACE_USBDI_GUID, NULL); + + status = WdfDeviceAddQueryInterface(Device, &ifaceCfg); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfDeviceAddQueryInterface failed status 0x%x\n", status)); + return status; + } + + return STATUS_SUCCESS; +} + +NTSTATUS Xusb_AssignPdoContext(WDFDEVICE Device, PPDO_IDENTIFICATION_DESCRIPTION Description) +{ + NTSTATUS status; + WDF_OBJECT_ATTRIBUTES attributes; + + WDF_OBJECT_ATTRIBUTES_INIT(&attributes); + attributes.ParentObject = Device; + + KdPrint((DRIVERNAME "Initializing XUSB context...\n")); + + PXUSB_DEVICE_DATA xusb = XusbGetData(Device); + + RtlZeroMemory(xusb, sizeof(XUSB_DEVICE_DATA)); + + // Is later overwritten by actual XInput slot + xusb->LedNumber = (UCHAR)Description->SerialNo; + // Packet size (20 bytes = 0x14) + xusb->Packet.Size = 0x14; + + // I/O Queue for pending IRPs + WDF_IO_QUEUE_CONFIG holdingInQueueConfig; + + // Create and assign queue for unhandled interrupt requests + WDF_IO_QUEUE_CONFIG_INIT(&holdingInQueueConfig, WdfIoQueueDispatchManual); + + status = WdfIoQueueCreate(Device, &holdingInQueueConfig, WDF_NO_OBJECT_ATTRIBUTES, &xusb->HoldingUsbInRequests); + if (!NT_SUCCESS(status)) + { + KdPrint((DRIVERNAME "WdfIoQueueCreate (HoldingUsbInRequests) failed 0x%x\n", status)); + return status; + } + + return STATUS_SUCCESS; +} + +VOID Xusb_GetConfigurationDescriptorType(PUCHAR Buffer, ULONG Length) +{ + UCHAR XusbDescriptorData[XUSB_DESCRIPTOR_SIZE] = + { + 0x09, // bLength + 0x02, // bDescriptorType (Configuration) + 0x99, 0x00, // wTotalLength 153 + 0x04, // bNumInterfaces 4 + 0x01, // bConfigurationValue + 0x00, // iConfiguration (String Index) + 0xA0, // bmAttributes Remote Wakeup + 0xFA, // bMaxPower 500mA + + 0x09, // bLength + 0x04, // bDescriptorType (Interface) + 0x00, // bInterfaceNumber 0 + 0x00, // bAlternateSetting + 0x02, // bNumEndpoints 2 + 0xFF, // bInterfaceClass + 0x5D, // bInterfaceSubClass + 0x01, // bInterfaceProtocol + 0x00, // iInterface (String Index) + + 0x11, // bLength + 0x21, // bDescriptorType (HID) + 0x00, 0x01, // bcdHID 1.00 + 0x01, // bCountryCode + 0x25, // bNumDescriptors + 0x81, // bDescriptorType[0] (Unknown 0x81) + 0x14, 0x00, // wDescriptorLength[0] 20 + 0x00, // bDescriptorType[1] (Unknown 0x00) + 0x00, 0x00, // wDescriptorLength[1] 0 + 0x13, // bDescriptorType[2] (Unknown 0x13) + 0x01, 0x08, // wDescriptorLength[2] 2049 + 0x00, // bDescriptorType[3] (Unknown 0x00) + 0x00, + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x81, // bEndpointAddress (IN/D2H) + 0x03, // bmAttributes (Interrupt) + 0x20, 0x00, // wMaxPacketSize 32 + 0x04, // bInterval 4 (unit depends on device speed) + + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x01, // bEndpointAddress (OUT/H2D) + 0x03, // bmAttributes (Interrupt) + 0x20, 0x00, // wMaxPacketSize 32 + 0x08, // bInterval 8 (unit depends on device speed) + + 0x09, // bLength + 0x04, // bDescriptorType (Interface) + 0x01, // bInterfaceNumber 1 + 0x00, // bAlternateSetting + 0x04, // bNumEndpoints 4 + 0xFF, // bInterfaceClass + 0x5D, // bInterfaceSubClass + 0x03, // bInterfaceProtocol + 0x00, // iInterface (String Index) + + 0x1B, // bLength + 0x21, // bDescriptorType (HID) + 0x00, 0x01, // bcdHID 1.00 + 0x01, // bCountryCode + 0x01, // bNumDescriptors + 0x82, // bDescriptorType[0] (Unknown 0x82) + 0x40, 0x01, // wDescriptorLength[0] 320 + 0x02, 0x20, 0x16, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x82, // bEndpointAddress (IN/D2H) + 0x03, // bmAttributes (Interrupt) + 0x20, 0x00, // wMaxPacketSize 32 + 0x02, // bInterval 2 (unit depends on device speed) + + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x02, // bEndpointAddress (OUT/H2D) + 0x03, // bmAttributes (Interrupt) + 0x20, 0x00, // wMaxPacketSize 32 + 0x04, // bInterval 4 (unit depends on device speed) + + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x83, // bEndpointAddress (IN/D2H) + 0x03, // bmAttributes (Interrupt) + 0x20, 0x00, // wMaxPacketSize 32 + 0x40, // bInterval 64 (unit depends on device speed) + + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x03, // bEndpointAddress (OUT/H2D) + 0x03, // bmAttributes (Interrupt) + 0x20, 0x00, // wMaxPacketSize 32 + 0x10, // bInterval 16 (unit depends on device speed) + + 0x09, // bLength + 0x04, // bDescriptorType (Interface) + 0x02, // bInterfaceNumber 2 + 0x00, // bAlternateSetting + 0x01, // bNumEndpoints 1 + 0xFF, // bInterfaceClass + 0x5D, // bInterfaceSubClass + 0x02, // bInterfaceProtocol + 0x00, // iInterface (String Index) + + 0x09, // bLength + 0x21, // bDescriptorType (HID) + 0x00, 0x01, // bcdHID 1.00 + 0x01, // bCountryCode + 0x22, // bNumDescriptors + 0x84, // bDescriptorType[0] (Unknown 0x84) + 0x07, 0x00, // wDescriptorLength[0] 7 + + 0x07, // bLength + 0x05, // bDescriptorType (Endpoint) + 0x84, // bEndpointAddress (IN/D2H) + 0x03, // bmAttributes (Interrupt) + 0x20, 0x00, // wMaxPacketSize 32 + 0x10, // bInterval 16 (unit depends on device speed) + + 0x09, // bLength + 0x04, // bDescriptorType (Interface) + 0x03, // bInterfaceNumber 3 + 0x00, // bAlternateSetting + 0x00, // bNumEndpoints 0 + 0xFF, // bInterfaceClass + 0xFD, // bInterfaceSubClass + 0x13, // bInterfaceProtocol + 0x04, // iInterface (String Index) + + 0x06, // bLength + 0x41, // bDescriptorType (Unknown) + 0x00, 0x01, 0x01, 0x03, + // 153 bytes + + // best guess: USB Standard Descriptor + }; + + RtlCopyBytes(Buffer, XusbDescriptorData, Length); +} + +VOID Xusb_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 = 0xFF; + pDescriptor->bDeviceProtocol = 0xFF; + pDescriptor->bMaxPacketSize0 = 0x08; + pDescriptor->idVendor = pCommon->VendorId; + pDescriptor->idProduct = pCommon->ProductId; + pDescriptor->bcdDevice = 0x0114; + pDescriptor->iManufacturer = 0x01; + pDescriptor->iProduct = 0x02; + pDescriptor->iSerialNumber = 0x03; + pDescriptor->bNumConfigurations = 0x01; +} + +VOID Xusb_SelectConfiguration(PUSBD_INTERFACE_INFORMATION pInfo) +{ + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: Length %d, Interface %d, Alternate %d, Pipes %d\n", + (int)pInfo->Length, + (int)pInfo->InterfaceNumber, + (int)pInfo->AlternateSetting, + pInfo->NumberOfPipes)); + + pInfo->Class = 0xFF; + pInfo->SubClass = 0x5D; + pInfo->Protocol = 0x01; + + pInfo->InterfaceHandle = (USBD_INTERFACE_HANDLE)0xFFFF0000; + + pInfo->Pipes[0].MaximumTransferSize = 0x00400000; + pInfo->Pipes[0].MaximumPacketSize = 0x20; + pInfo->Pipes[0].EndpointAddress = 0x81; + pInfo->Pipes[0].Interval = 0x04; + pInfo->Pipes[0].PipeType = 0x03; + pInfo->Pipes[0].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0081; + pInfo->Pipes[0].PipeFlags = 0x00; + + pInfo->Pipes[1].MaximumTransferSize = 0x00400000; + pInfo->Pipes[1].MaximumPacketSize = 0x20; + pInfo->Pipes[1].EndpointAddress = 0x01; + pInfo->Pipes[1].Interval = 0x08; + pInfo->Pipes[1].PipeType = 0x03; + pInfo->Pipes[1].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0001; + pInfo->Pipes[1].PipeFlags = 0x00; + + pInfo = (PUSBD_INTERFACE_INFORMATION)((PCHAR)pInfo + pInfo->Length); + + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: Length %d, Interface %d, Alternate %d, Pipes %d\n", + (int)pInfo->Length, + (int)pInfo->InterfaceNumber, + (int)pInfo->AlternateSetting, + pInfo->NumberOfPipes)); + + pInfo->Class = 0xFF; + pInfo->SubClass = 0x5D; + pInfo->Protocol = 0x03; + + pInfo->InterfaceHandle = (USBD_INTERFACE_HANDLE)0xFFFF0000; + + pInfo->Pipes[0].MaximumTransferSize = 0x00400000; + pInfo->Pipes[0].MaximumPacketSize = 0x20; + pInfo->Pipes[0].EndpointAddress = 0x82; + pInfo->Pipes[0].Interval = 0x04; + pInfo->Pipes[0].PipeType = 0x03; + pInfo->Pipes[0].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0082; + pInfo->Pipes[0].PipeFlags = 0x00; + + pInfo->Pipes[1].MaximumTransferSize = 0x00400000; + pInfo->Pipes[1].MaximumPacketSize = 0x20; + pInfo->Pipes[1].EndpointAddress = 0x02; + pInfo->Pipes[1].Interval = 0x08; + pInfo->Pipes[1].PipeType = 0x03; + pInfo->Pipes[1].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0002; + pInfo->Pipes[1].PipeFlags = 0x00; + + pInfo->Pipes[2].MaximumTransferSize = 0x00400000; + pInfo->Pipes[2].MaximumPacketSize = 0x20; + pInfo->Pipes[2].EndpointAddress = 0x83; + pInfo->Pipes[2].Interval = 0x08; + pInfo->Pipes[2].PipeType = 0x03; + pInfo->Pipes[2].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0083; + pInfo->Pipes[2].PipeFlags = 0x00; + + pInfo->Pipes[3].MaximumTransferSize = 0x00400000; + pInfo->Pipes[3].MaximumPacketSize = 0x20; + pInfo->Pipes[3].EndpointAddress = 0x03; + pInfo->Pipes[3].Interval = 0x08; + pInfo->Pipes[3].PipeType = 0x03; + pInfo->Pipes[3].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0003; + pInfo->Pipes[3].PipeFlags = 0x00; + + pInfo = (PUSBD_INTERFACE_INFORMATION)((PCHAR)pInfo + pInfo->Length); + + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: Length %d, Interface %d, Alternate %d, Pipes %d\n", + (int)pInfo->Length, + (int)pInfo->InterfaceNumber, + (int)pInfo->AlternateSetting, + pInfo->NumberOfPipes)); + + pInfo->Class = 0xFF; + pInfo->SubClass = 0x5D; + pInfo->Protocol = 0x02; + + pInfo->InterfaceHandle = (USBD_INTERFACE_HANDLE)0xFFFF0000; + + pInfo->Pipes[0].MaximumTransferSize = 0x00400000; + pInfo->Pipes[0].MaximumPacketSize = 0x20; + pInfo->Pipes[0].EndpointAddress = 0x84; + pInfo->Pipes[0].Interval = 0x04; + pInfo->Pipes[0].PipeType = 0x03; + pInfo->Pipes[0].PipeHandle = (USBD_PIPE_HANDLE)0xFFFF0084; + pInfo->Pipes[0].PipeFlags = 0x00; + + pInfo = (PUSBD_INTERFACE_INFORMATION)((PCHAR)pInfo + pInfo->Length); + + KdPrint((DRIVERNAME ">> >> >> URB_FUNCTION_SELECT_CONFIGURATION: Length %d, Interface %d, Alternate %d, Pipes %d\n", + (int)pInfo->Length, + (int)pInfo->InterfaceNumber, + (int)pInfo->AlternateSetting, + pInfo->NumberOfPipes)); + + pInfo->Class = 0xFF; + pInfo->SubClass = 0xFD; + pInfo->Protocol = 0x13; + + pInfo->InterfaceHandle = (USBD_INTERFACE_HANDLE)0xFFFF0000; +} +