diff --git a/ControlApp/ControlApp.csproj b/ControlApp/ControlApp.csproj index 4ddb04a7..248475d9 100644 --- a/ControlApp/ControlApp.csproj +++ b/ControlApp/ControlApp.csproj @@ -13,9 +13,8 @@ Nefarius.DsHidMini.ControlApp true 3.0.0 - Kanuan, Nefarius - Copyright (c) 2024 Nefarius Software Solutions e.U. Debug;Release + false diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..6cb89ccc --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,22 @@ + + + + + Kanuan, Benjamin Höglinger-Stelzer + Copyright © Nefarius Software Solutions e.U. 2022-2024 + + + + latest + + + $(NoWarn),0219,1587,1591,8600,8601,8602,8603,8604,8618,8619,8622,8629,8765,8767 + + + true + true + true + snupkg + true + + \ No newline at end of file diff --git a/README.md b/README.md index 752e23a1..edf01012 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,7 @@ The following awesome resources have made this project possible. - [linux/drivers/hid/hid-sony.c](https://github.com/torvalds/linux/blob/master/drivers/hid/hid-sony.c) - [The HID Page](http://janaxelson.com/hidpage.htm) - [CircumSpector/Research/Sony DualShock 3](https://github.com/CircumSpector/Research/tree/master/Sony%20DualShock%203) +- [Memory-Mapped Files and Overlaid Structs](https://blog.stephencleary.com/2023/09/memory-mapped-files-overlaid-structs.html) ### DevOps diff --git a/SDK/Nefarius.DsHidMini.IPC/DsHidMiniInterop.Commands.cs b/SDK/Nefarius.DsHidMini.IPC/DsHidMiniInterop.Commands.cs new file mode 100644 index 00000000..e045a7b6 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/DsHidMiniInterop.Commands.cs @@ -0,0 +1,305 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Net.NetworkInformation; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Nefarius.DsHidMini.IPC.Exceptions; +using Nefarius.DsHidMini.IPC.Models; +using Nefarius.DsHidMini.IPC.Models.Public; + +namespace Nefarius.DsHidMini.IPC; + +public partial class DsHidMiniInterop +{ + /// + /// Attempts to read the from a given device instance. + /// + /// + /// If is null, this method returns the last known input report copy immediately. If + /// you use this call in a busy loop, you should set a timeout so this call becomes event-based, meaning the call will + /// only return when the driver signaled that new data is available, otherwise you will just burn through CPU for no + /// good reason. A new input report is typically available each average 5 milliseconds, depending on the connection + /// (wired or wireless) so a timeout of 20 milliseconds should be a good recommendation. + /// + /// The one-based device index. + /// The to populate. + /// Optional timeout to wait for a report update to arrive. Default invocation returns immediately. + /// + /// Driver process interaction failed due to missing permissions; + /// this operation requires elevated privileges. + /// + /// The driver returned unexpected or malformed data. + /// Handle duplication failed. + /// The driver didn't respond within an expected period. + /// A different thread is currently performing a data exchange. + /// + /// No driver instance is available. Make sure that at least one + /// device is connected and that the driver is installed and working properly. Call prior to + /// avoid this exception. + /// + /// + /// TRUE if got filled in or FALSE if the given is not + /// occupied. + /// + [SuppressMessage("ReSharper", "UnusedMember.Global")] + public unsafe bool GetRawInputReport(int deviceIndex, ref DS3_RAW_INPUT_REPORT report, TimeSpan? timeout = null) + { + if (_hidView is null) + { + throw new DsHidMiniInteropUnavailableException(); + } + + ValidateDeviceIndex(deviceIndex); + + ref IPC_HID_INPUT_REPORT_MESSAGE message = ref Unsafe.AsRef(_hidView); + + if (timeout.HasValue) + { + _inputReportEvent ??= GetHidReportWaitHandle(deviceIndex); + + _inputReportEvent.WaitOne(timeout.Value); + } + + // + // Device is/got disconnected + // + if (message.SlotIndex == 0) + { + return false; + } + + // + // Index mismatch is not supposed to happen + // + if (message.SlotIndex != deviceIndex) + { + throw new DsHidMiniInteropUnexpectedReplyException(); + } + + report = message.InputReport; + + return true; + } + + /// + /// Send a PING to the driver and awaits the reply. + /// + /// + /// Driver IPC unavailable, make sure that at least one compatible + /// controller is connected and operational. + /// + /// The driver didn't respond within an expected period. + /// The driver returned unexpected or malformed data. + [SuppressMessage("ReSharper", "UnusedMember.Global")] + public unsafe void SendPing() + { + if (_commandMutex is null || _cmdView is null) + { + throw new DsHidMiniInteropUnavailableException(); + } + + AcquireCommandLock(); + + try + { + ref DSHM_IPC_MSG_HEADER message = ref Unsafe.AsRef(_cmdView); + + message.Type = DSHM_IPC_MSG_TYPE.DSHM_IPC_MSG_TYPE_REQUEST_RESPONSE; + message.Target = DSHM_IPC_MSG_TARGET.DSHM_IPC_MSG_TARGET_DRIVER; + message.Command.Driver = DSHM_IPC_MSG_CMD_DRIVER.DSHM_IPC_MSG_CMD_DRIVER_PING; + message.TargetIndex = 0; + message.Size = (uint)Marshal.SizeOf(); + + if (!SendAndWait()) + { + throw new DsHidMiniInteropReplyTimeoutException(); + } + + ref DSHM_IPC_MSG_HEADER reply = ref Unsafe.AsRef(_cmdView); + + // + // Plausibility check + // + if (reply is + { + Type: DSHM_IPC_MSG_TYPE.DSHM_IPC_MSG_TYPE_REQUEST_REPLY, + Target: DSHM_IPC_MSG_TARGET.DSHM_IPC_MSG_TARGET_CLIENT, + Command.Driver: DSHM_IPC_MSG_CMD_DRIVER.DSHM_IPC_MSG_CMD_DRIVER_PING, TargetIndex: 0 + } + && reply.Size == Marshal.SizeOf()) + { + return; + } + + throw new DsHidMiniInteropUnexpectedReplyException(ref reply); + } + finally + { + _commandMutex.ReleaseMutex(); + } + } + + /// + /// Writes a new host address to the given device. + /// + /// + /// Driver IPC unavailable, make sure that at least one compatible + /// controller is connected and operational. + /// + /// A containing success (or error) details. + /// This is synonymous with "pairing" to a new Bluetooth host. + /// The one-based device index. + /// The new host address. + /// + /// The was outside a valid + /// range. + /// + /// A different thread is currently performing a data exchange. + /// The driver didn't respond within an expected period. + /// The driver returned unexpected or malformed data. + [SuppressMessage("ReSharper", "UnusedMember.Global")] + public unsafe SetHostResult SetHostAddress(int deviceIndex, PhysicalAddress hostAddress) + { + if (_commandMutex is null || _cmdView is null) + { + throw new DsHidMiniInteropUnavailableException(); + } + + ValidateDeviceIndex(deviceIndex); + + AcquireCommandLock(); + + try + { + ref DSHM_IPC_MSG_PAIR_TO_REQUEST request = ref Unsafe.AsRef(_cmdView); + + request.Header.Type = DSHM_IPC_MSG_TYPE.DSHM_IPC_MSG_TYPE_REQUEST_RESPONSE; + request.Header.Target = DSHM_IPC_MSG_TARGET.DSHM_IPC_MSG_TARGET_DEVICE; + request.Header.Command.Device = DSHM_IPC_MSG_CMD_DEVICE.DSHM_IPC_MSG_CMD_DEVICE_PAIR_TO; + request.Header.TargetIndex = (uint)deviceIndex; + request.Header.Size = (uint)Marshal.SizeOf(); + + fixed (byte* source = hostAddress.GetAddressBytes()) + fixed (byte* address = request.Address) + { + if (source is not null) + { + Buffer.MemoryCopy(source, address, 6, 6); + } + else + { + // there might be previous values there we need to zero out + Unsafe.InitBlockUnaligned(address, 0, 6); + } + } + + if (!SendAndWait()) + { + throw new DsHidMiniInteropReplyTimeoutException(); + } + + ref DSHM_IPC_MSG_PAIR_TO_REPLY reply = ref Unsafe.AsRef(_cmdView); + + // + // Plausibility check + // + if (reply.Header.Type == DSHM_IPC_MSG_TYPE.DSHM_IPC_MSG_TYPE_REQUEST_REPLY + && reply.Header is + { + Target: DSHM_IPC_MSG_TARGET.DSHM_IPC_MSG_TARGET_CLIENT, + Command.Device: DSHM_IPC_MSG_CMD_DEVICE.DSHM_IPC_MSG_CMD_DEVICE_PAIR_TO + } + && reply.Header.TargetIndex == deviceIndex + && reply.Header.Size == Marshal.SizeOf()) + { + return new SetHostResult { WriteStatus = reply.WriteStatus, ReadStatus = reply.ReadStatus }; + } + + throw new DsHidMiniInteropUnexpectedReplyException(ref reply.Header); + } + finally + { + _commandMutex.ReleaseMutex(); + } + } + + /// + /// Overwrites the player slot indicator (player LEDs) of the given device. + /// + /// The one-based device index. + /// The player index to set to. Valid values include 1 to 7. + /// + /// Driver IPC unavailable, make sure that at least one compatible + /// controller is connected and operational. + /// + /// + /// + /// The or + /// were out of range. + /// + /// A different thread is currently performing a data exchange. + /// The driver didn't respond within an expected period. + /// The driver returned unexpected or malformed data. + [SuppressMessage("ReSharper", "UnusedMember.Global")] + public unsafe UInt32 SetPlayerIndex(int deviceIndex, byte playerIndex) + { + if (_commandMutex is null || _cmdView is null) + { + throw new DsHidMiniInteropUnavailableException(); + } + + ValidateDeviceIndex(deviceIndex); + + if (playerIndex is < 1 or > 7) + { + throw new ArgumentOutOfRangeException(nameof(playerIndex), + "Player index must be between (including) 1 and 7."); + } + + AcquireCommandLock(); + + try + { + ref DSHM_IPC_MSG_SET_PLAYER_INDEX_REQUEST request = + ref Unsafe.AsRef(_cmdView); + + request.Header.Type = DSHM_IPC_MSG_TYPE.DSHM_IPC_MSG_TYPE_REQUEST_RESPONSE; + request.Header.Target = DSHM_IPC_MSG_TARGET.DSHM_IPC_MSG_TARGET_DEVICE; + request.Header.Command.Device = DSHM_IPC_MSG_CMD_DEVICE.DSHM_IPC_MSG_CMD_DEVICE_SET_PLAYER_INDEX; + request.Header.TargetIndex = (uint)deviceIndex; + request.Header.Size = (uint)Marshal.SizeOf(); + + request.PlayerIndex = playerIndex; + + if (!SendAndWait()) + { + throw new DsHidMiniInteropReplyTimeoutException(); + } + + ref DSHM_IPC_MSG_SET_PLAYER_INDEX_REPLY reply = + ref Unsafe.AsRef(_cmdView); + + // + // Plausibility check + // + if (reply.Header is + { + Type: DSHM_IPC_MSG_TYPE.DSHM_IPC_MSG_TYPE_REQUEST_REPLY, + Target: DSHM_IPC_MSG_TARGET.DSHM_IPC_MSG_TARGET_CLIENT, + Command.Device: DSHM_IPC_MSG_CMD_DEVICE.DSHM_IPC_MSG_CMD_DEVICE_SET_PLAYER_INDEX + } + && reply.Header.TargetIndex == deviceIndex + && reply.Header.Size == Marshal.SizeOf()) + { + return reply.NtStatus; + } + + throw new DsHidMiniInteropUnexpectedReplyException(ref reply.Header); + } + finally + { + _commandMutex.ReleaseMutex(); + } + } +} \ No newline at end of file diff --git a/SDK/Nefarius.DsHidMini.IPC/DsHidMiniInterop.cs b/SDK/Nefarius.DsHidMini.IPC/DsHidMiniInterop.cs new file mode 100644 index 00000000..24c8f9df --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/DsHidMiniInterop.cs @@ -0,0 +1,393 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.IO.MemoryMappedFiles; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.System.Memory; +using Windows.Win32.System.SystemInformation; +using Windows.Win32.System.Threading; + +using Microsoft.Win32.SafeHandles; + +using Nefarius.DsHidMini.IPC.Exceptions; +using Nefarius.DsHidMini.IPC.Models; +using Nefarius.DsHidMini.IPC.Models.Drivers; +using Nefarius.Utilities.DeviceManagement.PnP; + +namespace Nefarius.DsHidMini.IPC; + +/// +/// Connects to the drivers shared memory region and offers utility methods for data exchange. +/// +public sealed partial class DsHidMiniInterop : IDisposable +{ + private const string FileMapName = "Global\\DsHidMiniSharedMemory"; + private const string ReadEventName = "Global\\DsHidMiniReadEvent"; + private const string WriteEventName = "Global\\DsHidMiniWriteEvent"; + private const string MutexName = "Global\\DsHidMiniCommandMutex"; + + private readonly Dictionary _connectedDevices = new(); + private readonly DeviceNotificationListener _deviceListener = new(); + private MEMORY_MAPPED_VIEW_ADDRESS? _cmdView; + + private Mutex? _commandMutex; + + private SafeFileHandle? _fileMapping; + private MEMORY_MAPPED_VIEW_ADDRESS? _hidView; + + private EventWaitHandle? _inputReportEvent; + + private EventWaitHandle? _readEvent; + private EventWaitHandle? _writeEvent; + + /// + /// Creates a new instance by connecting to the driver IPC mechanism. + /// + /// + /// No driver instance is available. Make sure that at least one + /// device is connected and that the driver is installed and working properly. Call prior to + /// avoid this exception. + /// + public DsHidMiniInterop() + { + Reconnect(); + + _deviceListener.RegisterDeviceArrived(DsHidMiniDeviceArrived, DsHidMiniDriver.DeviceInterfaceGuid); + _deviceListener.RegisterDeviceRemoved(DsHidMiniDeviceRemoved, DsHidMiniDriver.DeviceInterfaceGuid); + + RefreshDevices(); + } + + /// + /// Gets whether driver IPC is available. + /// + [SuppressMessage("ReSharper", "UnusedMember.Global")] + public static bool IsAvailable + { + get + { + try + { + using MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(FileMapName); + + return true; + } + catch (FileNotFoundException) + { + return false; + } + } + } + + /// + public void Dispose() + { + if (_cmdView.HasValue) + { + PInvoke.UnmapViewOfFile(_cmdView.Value); + } + + if (_hidView.HasValue) + { + PInvoke.UnmapViewOfFile(_hidView.Value); + } + + _fileMapping?.Dispose(); + + _readEvent?.Dispose(); + _writeEvent?.Dispose(); + _inputReportEvent?.Dispose(); + + _commandMutex?.Dispose(); + } + + /// + /// Attempt re-initialization of IPC after all devices got disconnected. + /// + /// + /// No driver instance is available. Make sure that at least one + /// device is connected and that the driver is installed and working properly. Call prior to + /// avoid this exception. + /// + public void Reconnect() + { + PInvoke.GetSystemInfo(out SYSTEM_INFO systemInfo); + + try + { + _commandMutex = Mutex.OpenExisting(MutexName); + _readEvent = EventWaitHandle.OpenExisting(ReadEventName); + _writeEvent = EventWaitHandle.OpenExisting(WriteEventName); + } + catch (WaitHandleCannotBeOpenedException) + { + throw new DsHidMiniInteropUnavailableException(); + } + + try + { + _fileMapping = PInvoke.OpenFileMapping( + (uint)(FILE_MAP.FILE_MAP_READ | FILE_MAP.FILE_MAP_WRITE), + new BOOL(false), + FileMapName + ); + + _cmdView = PInvoke.MapViewOfFile( + _fileMapping, + FILE_MAP.FILE_MAP_READ | FILE_MAP.FILE_MAP_WRITE, + 0, + 0, + systemInfo.dwAllocationGranularity + ); + + if (_cmdView.Value == 0) + { + throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to access command view"); + } + + uint pageSize = systemInfo.dwAllocationGranularity; + long alignedOffset = systemInfo.dwAllocationGranularity / pageSize * pageSize; + long offsetWithinPage = systemInfo.dwAllocationGranularity % pageSize; + + _hidView = PInvoke.MapViewOfFile( + _fileMapping, + FILE_MAP.FILE_MAP_READ, + 0, + (uint)alignedOffset, + (uint)(systemInfo.dwAllocationGranularity + offsetWithinPage) + ); + + if (_hidView.Value == 0) + { + throw new Win32Exception(Marshal.GetLastWin32Error(), "Failed to access HID view"); + } + } + catch (FileNotFoundException) + { + throw new DsHidMiniInteropUnavailableException(); + } + } + + private void RefreshDevices() + { + _connectedDevices.Clear(); + + int instanceIndex = 0; + + while (Devcon.FindByInterfaceGuid(DsHidMiniDriver.DeviceInterfaceGuid, out PnPDevice device, instanceIndex++)) + { + _connectedDevices.Add(instanceIndex, device); + } + + // + // We need to let go of all shared resources or the driver won't be able to re-create the named objects + // + if (_connectedDevices.Count == 0) + { + Dispose(); + } + } + + private void DsHidMiniDeviceRemoved(DeviceEventArgs obj) + { + PnPDevice? device = PnPDevice.GetDeviceByInterfaceId(obj.SymLink, DeviceLocationFlags.Phantom); + + KeyValuePair item = _connectedDevices.Single(kvp => kvp.Value.Equals(device)); + + // TODO: react to removal + + RefreshDevices(); + } + + private void DsHidMiniDeviceArrived(DeviceEventArgs obj) + { + if (_connectedDevices.Count == 0) + { + Reconnect(); + } + + RefreshDevices(); + } + + /// + /// Gets the input report wait handle from the driver and duplicates it into the current process. + /// + /// + /// Driver process interaction failed due to missing permissions; + /// this operation requires elevated privileges. + /// + /// The driver returned unexpected or malformed data. + /// Handle duplication failed. + /// The driver didn't respond within an expected period. + /// A different thread is currently performing a data exchange. + /// + /// No driver instance is available. Make sure that at least one + /// device is connected and that the driver is installed and working properly. Call prior to + /// avoid this exception. + /// + private unsafe EventWaitHandle GetHidReportWaitHandle(int deviceIndex) + { + if (_commandMutex is null || _cmdView is null) + { + throw new DsHidMiniInteropUnavailableException(); + } + + ValidateDeviceIndex(deviceIndex); + + AcquireCommandLock(); + + try + { + ref DSHM_IPC_MSG_HEADER request = ref Unsafe.AsRef(_cmdView); + + request.Type = DSHM_IPC_MSG_TYPE.DSHM_IPC_MSG_TYPE_RESPONSE_ONLY; + request.Target = DSHM_IPC_MSG_TARGET.DSHM_IPC_MSG_TARGET_DEVICE; + request.Command.Device = DSHM_IPC_MSG_CMD_DEVICE.DSHM_IPC_MSG_CMD_DEVICE_GET_HID_WAIT_HANDLE; + request.TargetIndex = (uint)deviceIndex; + request.Size = (uint)Marshal.SizeOf(); + + if (!SendAndWait()) + { + throw new DsHidMiniInteropReplyTimeoutException(); + } + + ref DSHM_IPC_MSG_GET_HID_WAIT_HANDLE_RESPONSE reply = + ref Unsafe.AsRef(_cmdView); + + // + // Plausibility check + // + if (reply.Header is + { + Type: DSHM_IPC_MSG_TYPE.DSHM_IPC_MSG_TYPE_RESPONSE_ONLY, + Target: DSHM_IPC_MSG_TARGET.DSHM_IPC_MSG_TARGET_CLIENT, + Command.Device: DSHM_IPC_MSG_CMD_DEVICE.DSHM_IPC_MSG_CMD_DEVICE_GET_HID_WAIT_HANDLE + } + && reply.Header.TargetIndex == deviceIndex + && reply.Header.Size == Marshal.SizeOf()) + { + HANDLE driverProcess = PInvoke.OpenProcess( + PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE, + new BOOL(false), + reply.ProcessId + ); + + try + { + if (driverProcess.IsNull) + { + if (Marshal.GetLastWin32Error() == (int)WIN32_ERROR.ERROR_ACCESS_DENIED) + { + throw new DsHidMiniInteropAccessDeniedException(); + } + + throw new Win32Exception(Marshal.GetLastWin32Error(), "OpenProcess call failed."); + } + + HANDLE dupHandle; + + if (!PInvoke.DuplicateHandle( + driverProcess, + new HANDLE(reply.WaitHandle), + PInvoke.GetCurrentProcess(), + &dupHandle, + 0, + new BOOL(false), + DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS + )) + { + throw new Win32Exception(Marshal.GetLastWin32Error(), "DuplicateHandle call failed."); + } + + return new EventWaitHandle(false, EventResetMode.AutoReset) + { + SafeWaitHandle = new SafeWaitHandle(dupHandle, true) + }; + } + finally + { + if (!driverProcess.IsNull) + { + PInvoke.CloseHandle(driverProcess); + } + } + } + + throw new DsHidMiniInteropUnexpectedReplyException(ref reply.Header); + } + finally + { + _commandMutex.ReleaseMutex(); + } + } + + private void AcquireCommandLock() + { + if (_commandMutex is null) + { + throw new DsHidMiniInteropUnavailableException(); + } + + try + { + if (!_commandMutex.WaitOne(0)) + { + throw new DsHidMiniInteropConcurrencyException(); + } + } + catch (AbandonedMutexException) { } + } + + /// + /// Ensures the target device index is in a valid range. + /// + /// + /// The was outside a valid + /// range. + /// + private static void ValidateDeviceIndex(int deviceIndex) + { + if (deviceIndex is <= 0 or > byte.MaxValue) + { + throw new DsHidMiniInteropInvalidDeviceIndexException(deviceIndex); + } + } + + /// + /// Signal the driver that we are done modifying the shared region and are now awaiting an update from the driver. + /// + /// Timeout to wait for a reply. Defaults to 500ms. + /// TRUE if we got a reply in time, FALSE otherwise. + private bool SendAndWait(int timeoutMs = 500) + { + return SendAndWait(TimeSpan.FromMilliseconds(timeoutMs)); + } + + /// + /// Signal the driver that we are done modifying the shared region and are now awaiting an update from the driver. + /// + /// Timeout to wait for a reply. + /// TRUE if we got a reply in time, FALSE otherwise. + private bool SendAndWait(TimeSpan timeout) + { + SignalWriteFinished(); + return _writeEvent!.WaitOne(timeout); + } + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + private void SignalReadFinished() + { + // we are done reading from the region, driver can now write + _writeEvent!.Set(); + } + + [SuppressMessage("ReSharper", "UnusedMember.Local")] + private void SignalWriteFinished() + { + // we are done writing to the region, driver can now read + _readEvent!.Set(); + } +} \ No newline at end of file diff --git a/SDK/Nefarius.DsHidMini.IPC/Exceptions/Exceptions.cs b/SDK/Nefarius.DsHidMini.IPC/Exceptions/Exceptions.cs new file mode 100644 index 00000000..b344f44f --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/Exceptions/Exceptions.cs @@ -0,0 +1,78 @@ +using Nefarius.DsHidMini.IPC.Models; + +namespace Nefarius.DsHidMini.IPC.Exceptions; + +/// +/// Driver IPC unavailable, make sure that at least one compatible controller is connected and operational. +/// +public sealed class DsHidMiniInteropUnavailableException : Exception +{ + internal DsHidMiniInteropUnavailableException() : base( + "Driver IPC unavailable, make sure that at least one compatible controller is connected and operational.") + { + } +} + +/// +/// Operation timed out while waiting for a request reply. +/// +public sealed class DsHidMiniInteropReplyTimeoutException : Exception +{ + internal DsHidMiniInteropReplyTimeoutException() : base("Operation timed out while waiting for a request reply.") + { + } +} + +/// +/// A request reply was malformed. +/// +public sealed class DsHidMiniInteropUnexpectedReplyException : Exception +{ + private readonly DSHM_IPC_MSG_HEADER? _header; + + internal DsHidMiniInteropUnexpectedReplyException() : base("A request reply was malformed.") { } + + internal DsHidMiniInteropUnexpectedReplyException(ref DSHM_IPC_MSG_HEADER header) : this() + { + _header = header; + } + + /// + public override string Message => + _header.HasValue + ? $"{base.Message}, Type: {_header.Value.Type}, Target: {_header.Value.Target}, Command: {_header.Value.Command.Driver}, TargetIndex: {_header.Value.TargetIndex}, Size: {_header.Value.Size}" + : base.Message; +} + +/// +/// Multiple threads tried to invoke IPC methods at the same time, which is not supported. +/// +public sealed class DsHidMiniInteropConcurrencyException : Exception +{ + internal DsHidMiniInteropConcurrencyException() : base( + "Multiple threads tried to invoke IPC methods at the same time, which is not supported.") + { + } +} + +/// +/// The provided device index was out of range; allowed values must remain between (including) 1 and 255. +/// +public sealed class DsHidMiniInteropInvalidDeviceIndexException : Exception +{ + internal DsHidMiniInteropInvalidDeviceIndexException(int deviceIndex) : base( + $"The provided device index {deviceIndex} was out of range; allowed values must remain between (including) 1 and 255.") + { + } +} + +/// +/// Driver process interaction failed due to missing permissions; this operation requires elevated privileges. +/// +public sealed class DsHidMiniInteropAccessDeniedException : Exception +{ + internal DsHidMiniInteropAccessDeniedException() : base( + "Driver process interaction failed due to missing permissions; this operation requires elevated privileges.") + { + } +} \ No newline at end of file diff --git a/SDK/Nefarius.DsHidMini.IPC/Models/CmdStructs.cs b/SDK/Nefarius.DsHidMini.IPC/Models/CmdStructs.cs new file mode 100644 index 00000000..199e8ba0 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/Models/CmdStructs.cs @@ -0,0 +1,148 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +using Nefarius.DsHidMini.IPC.Models.Public; + +namespace Nefarius.DsHidMini.IPC.Models; + +/// +/// What command is this message carrying +/// +[StructLayout(LayoutKind.Explicit)] +[SuppressMessage("ReSharper", "InconsistentNaming")] +internal struct DSHM_IPC_MSG_COMMAND +{ + /// + /// Driver global command + /// + [FieldOffset(0)] + public DSHM_IPC_MSG_CMD_DRIVER Driver; + + /// + /// Device-specific command + /// + [FieldOffset(0)] + public DSHM_IPC_MSG_CMD_DEVICE Device; +} + +/// +/// Prefix of every packet describing the message +/// +[StructLayout(LayoutKind.Sequential)] +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +internal struct DSHM_IPC_MSG_HEADER +{ + /// + /// What request-behavior is expected (request, request-reply, ...) + /// + public DSHM_IPC_MSG_TYPE Type; + + /// + /// What component is this message targeting (driver, device, ...) + /// + public DSHM_IPC_MSG_TARGET Target; + + /// + /// What command is this message carrying + /// + public DSHM_IPC_MSG_COMMAND Command; + + /// + /// One-based index of which device is this message for + /// + /// Set to 0 if driver is targeted + public uint TargetIndex; + + /// + /// The size of the entire message (header + payload) in bytes + /// + /// A size of 0 is invalid + public uint Size; +} + +/// +/// Updates a specified devices' host address +/// +[StructLayout(LayoutKind.Sequential)] +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +internal unsafe struct DSHM_IPC_MSG_PAIR_TO_REQUEST +{ + public DSHM_IPC_MSG_HEADER Header; + + public fixed byte Address[6]; +} + +/// +/// Reply to . +/// +[StructLayout(LayoutKind.Sequential)] +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +internal struct DSHM_IPC_MSG_PAIR_TO_REPLY +{ + public DSHM_IPC_MSG_HEADER Header; + + /// + /// NTSTATUS of the set address action + /// + public UInt32 WriteStatus; + + /// + /// NTSTATUS of the get address action + /// + public UInt32 ReadStatus; +} + +/// +/// Updates the player index of a given device +/// +[StructLayout(LayoutKind.Sequential)] +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +internal struct DSHM_IPC_MSG_SET_PLAYER_INDEX_REQUEST +{ + public DSHM_IPC_MSG_HEADER Header; + + /// + /// The new player index to set + /// + /// Valid values are 1 to 7 + public byte PlayerIndex; +} + +/// +/// Reply to . +/// +[StructLayout(LayoutKind.Sequential)] +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +internal struct DSHM_IPC_MSG_SET_PLAYER_INDEX_REPLY +{ + public DSHM_IPC_MSG_HEADER Header; + + public UInt32 NtStatus; +} + +/// +/// Requests the driver host process PID and a wait handle for new input reports +/// +/// Post-processing this command requires elevated privileges. +[SuppressMessage("ReSharper", "InconsistentNaming")] +[StructLayout(LayoutKind.Sequential)] +internal struct DSHM_IPC_MSG_GET_HID_WAIT_HANDLE_RESPONSE +{ + public DSHM_IPC_MSG_HEADER Header; + + /// + /// The driver hosting process PID + /// + public UInt32 ProcessId; + + /// + /// A handle to an auto-reset event + /// + /// The requester of this handle must duplicate it into the current process before it becomes usable. + public IntPtr WaitHandle; +} diff --git a/SDK/Nefarius.DsHidMini.IPC/Models/Drivers/DsHidMiniDriver.cs b/SDK/Nefarius.DsHidMini.IPC/Models/Drivers/DsHidMiniDriver.cs new file mode 100644 index 00000000..a917065e --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/Models/Drivers/DsHidMiniDriver.cs @@ -0,0 +1,166 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; + +using Nefarius.DsHidMini.IPC.Util.Converters; +using Nefarius.Utilities.DeviceManagement.PnP; + +namespace Nefarius.DsHidMini.IPC.Models.Drivers; + +/// +/// Interface and property information about the DsHidMini driver. +/// +[SuppressMessage("ReSharper", "UnusedMember.Global")] +public static class DsHidMiniDriver +{ + /// + /// Interface GUID common to all devices the DsHidMini driver supports. + /// + public static Guid DeviceInterfaceGuid => Guid.Parse("{399ED672-E0BD-4FB3-AB0C-4955B56FB86A}"); + + #region Read-only properties + + /// + /// The last reported of the device. + /// + public static DevicePropertyKey BatteryStatusProperty => CustomDeviceProperty.CreateCustomDeviceProperty( + Guid.Parse("{3FECF510-CC94-4FBE-8839-738201F84D59}"), 2, + typeof(byte)); + + public static DevicePropertyKey LastPairingStatusProperty => CustomDeviceProperty.CreateCustomDeviceProperty( + Guid.Parse("{3FECF510-CC94-4FBE-8839-738201F84D59}"), 3, + typeof(int)); + + public static DevicePropertyKey LastHostRequestStatusProperty => CustomDeviceProperty.CreateCustomDeviceProperty( + Guid.Parse("{3FECF510-CC94-4FBE-8839-738201F84D59}"), 5, + typeof(int)); + + #endregion + + #region Common device properties + + /// + /// The currently active . + /// + public static DevicePropertyKey HidDeviceModeProperty => CustomDeviceProperty.CreateCustomDeviceProperty( + Guid.Parse("{6D293077-C3D6-4062-9597-BE4389404C02}"), 2, + typeof(byte)); + + /// + /// The Bluetooth MAC address the device is currently paired to. + /// + public static DevicePropertyKey HostAddressProperty => CustomDeviceProperty.CreateCustomDeviceProperty( + Guid.Parse("{0xa92f26ca, 0xeda7, 0x4b1d, {0x9d, 0xb2, 0x27, 0xb6, 0x8a, 0xa5, 0xa2, 0xeb}}"), 1, + typeof(ulong)); + + /// + /// The Bluetooth MAC address of the device itself. + /// + public static DevicePropertyKey DeviceAddressProperty => CustomDeviceProperty.CreateCustomDeviceProperty( + Guid.Parse("{0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a}}"), 1, + typeof(string)); + + /// + /// Timestamp of last wireless connection. + /// + public static DevicePropertyKey BluetoothLastConnectedTimeProperty => + CustomDeviceProperty.CreateCustomDeviceProperty( + Guid.Parse("{0x2bd67d8b, 0x8beb, 0x48d5, {0x87, 0xe0, 0x6c, 0xda, 0x34, 0x28, 0x04, 0x0a}}"), 11, + typeof(DateTimeOffset)); + + #endregion +} + +/// +/// Battery status values. +/// +[TypeConverter(typeof(EnumDescriptionTypeConverter))] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +public enum DsBatteryStatus : byte +{ + /// + /// Unknown/not yet reported. + /// + [Description("Unknown")] + Unknown = 0x00, + + /// + /// Dying. Battery is so low the device is barely being kept on. + /// + [Description("Dying")] + Dying = 0x01, + + /// + /// Low. Device should be charged soon. + /// + [Description("Low")] + Low = 0x02, + + /// + /// Medium. Will last for a while but should be charged soon. + /// + [Description("Medium")] + Medium = 0x03, + + /// + /// High. Will last for a few sessions. + /// + [Description("High")] + High = 0x04, + + /// + /// Full. Status right after successful charging. + /// + [Description("Full")] + Full = 0x05, + + /// + /// Charging. The default state while wired until is reached. + /// + [Description("Charging")] + Charging = 0xEE, + + /// + /// Charged. While wired synonymous to while wireless. + /// + [Description("Charged")] + Charged = 0xEF +} + +/// +/// HID device emulation modes. +/// +[TypeConverter(typeof(EnumDescriptionTypeConverter))] +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +public enum DsHidDeviceMode : byte +{ + /// + /// Single Device with Force Feedback mode. + /// + [Description("SDF (PCSX2 Non-Qt-Edition)")] + SDF = 0x01, + + /// + /// Gamepad plus Joystick mode. + /// + [Description("GPJ (Separated pressure)")] + GPJ = 0x02, + + /// + /// SIXAXIS.SYS mode. + /// + [Description("SXS (Steam, RPCS3, PCSX2 Qt-Edition)")] + SXS = 0x03, + + /// + /// DS4Windows DualShock 4 emulation mode. + /// + [Description("DS4Windows")] + DS4W = 0x04, + + /// + /// Xbox One Controller mode. + /// + [Description("XInput (Xbox One)")] + XInput = 0x05 +} \ No newline at end of file diff --git a/SDK/Nefarius.DsHidMini.IPC/Models/Enums.cs b/SDK/Nefarius.DsHidMini.IPC/Models/Enums.cs new file mode 100644 index 00000000..ed00a677 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/Models/Enums.cs @@ -0,0 +1,108 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Nefarius.DsHidMini.IPC.Models; + +/// +/// Describes the type of IPC message response behavior +/// +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +internal enum DSHM_IPC_MSG_TYPE : UInt32 +{ + /// + /// Invalid/reserved, do not use + /// + DSHM_IPC_MSG_TYPE_INVALID = 0, + + /// + /// Client-to-driver data incoming, no acknowledgment/reply requested + /// + DSHM_IPC_MSG_TYPE_REQUEST_ONLY, + + /// + /// Client-to-driver data incoming, must be acknowledged by reply + /// + DSHM_IPC_MSG_TYPE_REQUEST_RESPONSE, + + /// + /// Client requested data, there is nothing to read for the driver + /// + DSHM_IPC_MSG_TYPE_RESPONSE_ONLY, + + /// + /// Driver-to-client response to a previous DSHM_IPC_MSG_TYPE_REQUEST_RESPONSE + /// + DSHM_IPC_MSG_TYPE_REQUEST_REPLY +} + +/// +/// Describes the message receiver +/// +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +internal enum DSHM_IPC_MSG_TARGET : UInt32 +{ + /// + /// Invalid/reserved, do not use + /// + DSHM_IPC_MSG_TARGET_INVALID = 0, + + /// + /// The message is targeted at the driver + /// + DSHM_IPC_MSG_TARGET_DRIVER, + + /// + /// The message is targeted at a device + /// + DSHM_IPC_MSG_TARGET_DEVICE, + + /// + /// The message is targeted at the client/caller/app + /// + DSHM_IPC_MSG_TARGET_CLIENT +} + +/// +/// Describes a per-driver command +/// +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +internal enum DSHM_IPC_MSG_CMD_DRIVER : UInt32 +{ + /// + /// Invalid/reserved, do not use + /// + DSHM_IPC_MSG_CMD_DRIVER_INVALID = 0, + + /// + /// Message without payload, useful to check for functionality + /// + DSHM_IPC_MSG_CMD_DRIVER_PING +} + +// Describes a per-device command +[SuppressMessage("ReSharper", "InconsistentNaming")] +[SuppressMessage("ReSharper", "UnusedMember.Global")] +internal enum DSHM_IPC_MSG_CMD_DEVICE : UInt32 +{ + /// + /// Invalid/reserved, do not use + /// + DSHM_IPC_MSG_CMD_DEVICE_INVALID = 0, + + /// + /// Pair a given device to a new host + /// + DSHM_IPC_MSG_CMD_DEVICE_PAIR_TO, + + /// + /// Requests a player index update (switch player LED etc.) + /// + DSHM_IPC_MSG_CMD_DEVICE_SET_PLAYER_INDEX, + + /// + /// Requests a wait handle for input report state changes + /// + DSHM_IPC_MSG_CMD_DEVICE_GET_HID_WAIT_HANDLE +} \ No newline at end of file diff --git a/SDK/Nefarius.DsHidMini.IPC/Models/HidStructs.cs b/SDK/Nefarius.DsHidMini.IPC/Models/HidStructs.cs new file mode 100644 index 00000000..e789dfd5 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/Models/HidStructs.cs @@ -0,0 +1,24 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +using Nefarius.DsHidMini.IPC.Models.Public; + +namespace Nefarius.DsHidMini.IPC.Models; + +/// +/// Represents the most current raw DS3 HID input report. +/// +[StructLayout(LayoutKind.Sequential)] +[SuppressMessage("ReSharper", "InconsistentNaming")] +internal struct IPC_HID_INPUT_REPORT_MESSAGE +{ + /// + /// The one-based device index this report belongs to. + /// + public UInt32 SlotIndex; + + /// + /// The coming directly from the device with no transformations applied. + /// + public DS3_RAW_INPUT_REPORT InputReport; +} \ No newline at end of file diff --git a/SDK/Nefarius.DsHidMini.IPC/Models/Public/RawInputReport.cs b/SDK/Nefarius.DsHidMini.IPC/Models/Public/RawInputReport.cs new file mode 100644 index 00000000..4395372b --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/Models/Public/RawInputReport.cs @@ -0,0 +1,276 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +namespace Nefarius.DsHidMini.IPC.Models.Public; + +/// +/// Raw input report as it is sent by the SIXAXIS/DualShock 3. +/// +[StructLayout(LayoutKind.Sequential, Pack = 1)] +[SuppressMessage("ReSharper", "InconsistentNaming")] +public unsafe struct DS3_RAW_INPUT_REPORT +{ + /// + /// Report ID (always 1). + /// + public byte ReportId; + + internal byte Reserved0; + + /// + /// Button breakouts in various formats. + /// + [StructLayout(LayoutKind.Explicit)] + [SuppressMessage("ReSharper", "UnusedMember.Global")] + public struct ButtonUnion + { + [FieldOffset(0)] + internal uint lButtons; + + [FieldOffset(0)] + internal fixed byte bButtons[4]; + + [FieldOffset(0)] + internal ushort packedButtons; + + /// + /// Gets or sets the Select button state. + /// + public bool Select { get => GetBit(packedButtons, 0); set => SetBit(ref packedButtons, 0, value); } + + /// + /// Gets or sets the Left Thumb button state. + /// + public bool L3 { get => GetBit(packedButtons, 1); set => SetBit(ref packedButtons, 1, value); } + + /// + /// Gets or sets the Right Thumb button state. + /// + public bool R3 { get => GetBit(packedButtons, 2); set => SetBit(ref packedButtons, 2, value); } + + /// + /// Gets or sets the Start button state. + /// + public bool Start { get => GetBit(packedButtons, 3); set => SetBit(ref packedButtons, 3, value); } + + /// + /// Gets or sets the DPad Up button state. + /// + public bool Up { get => GetBit(packedButtons, 4); set => SetBit(ref packedButtons, 4, value); } + + /// + /// Gets or sets the DPad Right button state. + /// + public bool Right { get => GetBit(packedButtons, 5); set => SetBit(ref packedButtons, 5, value); } + + /// + /// Gets or sets the DPad Down button state. + /// + public bool Down { get => GetBit(packedButtons, 6); set => SetBit(ref packedButtons, 6, value); } + + /// + /// Gets or sets the DPad Left button state. + /// + public bool Left { get => GetBit(packedButtons, 7); set => SetBit(ref packedButtons, 7, value); } + + /// + /// Gets or sets the Left Trigger button state. + /// + public bool L2 { get => GetBit(packedButtons, 8); set => SetBit(ref packedButtons, 8, value); } + + /// + /// Gets or sets the Right Trigger button state. + /// + public bool R2 { get => GetBit(packedButtons, 9); set => SetBit(ref packedButtons, 9, value); } + + /// + /// Gets or sets the Left Shoulder button state. + /// + public bool L1 { get => GetBit(packedButtons, 10); set => SetBit(ref packedButtons, 10, value); } + + /// + /// Gets or sets the Right Shoulder button state. + /// + public bool R1 { get => GetBit(packedButtons, 11); set => SetBit(ref packedButtons, 11, value); } + + /// + /// Gets or sets the Triangle button state. + /// + public bool Triangle { get => GetBit(packedButtons, 12); set => SetBit(ref packedButtons, 12, value); } + + /// + /// Gets or sets the Circle button state. + /// + public bool Circle { get => GetBit(packedButtons, 13); set => SetBit(ref packedButtons, 13, value); } + + /// + /// Gets or sets the Cross button state. + /// + public bool Cross { get => GetBit(packedButtons, 14); set => SetBit(ref packedButtons, 14, value); } + + /// + /// Gets or sets the Square button state. + /// + public bool Square { get => GetBit(packedButtons, 15); set => SetBit(ref packedButtons, 15, value); } + + /// + /// Gets or sets the PS button state. + /// + [SuppressMessage("ReSharper", "InconsistentNaming")] + public bool PS { get => GetBit(packedButtons, 16); set => SetBit(ref packedButtons, 16, value); } + + // Helper methods to manipulate individual bits + private static bool GetBit(ushort value, int bitNumber) + { + return (value & (1 << bitNumber)) != 0; + } + + private static void SetBit(ref ushort value, int bitNumber, bool bitValue) + { + if (bitValue) + { + value |= (ushort)(1 << bitNumber); + } + else + { + value &= (ushort)~(1 << bitNumber); + } + } + } + + /// + /// The individual controller buttons. + /// + public ButtonUnion Buttons; + + /// + /// Left Thumb X-axis. + /// + /// 0x00 = left/bottom, 0x80 = centered, 0xFF = right/top + public byte LeftThumbX; + + /// + /// Left Thumb Y-axis. + /// + /// 0x00 = left/bottom, 0x80 = centered, 0xFF = right/top + public byte LeftThumbY; + + /// + /// Right Thumb X-axis. + /// + /// 0x00 = left/bottom, 0x80 = centered, 0xFF = right/top + public byte RightThumbX; + + /// + /// Right Thumb Y-axis. + /// + /// 0x00 = left/bottom, 0x80 = centered, 0xFF = right/top + public byte RightThumbY; + + internal fixed byte Reserved1[4]; + + /// + /// Pressure value breakouts in various formats. + /// + [StructLayout(LayoutKind.Explicit)] + public struct PressureUnion + { + [FieldOffset(0)] + internal fixed byte bValues[12]; + + /// + /// The individual pressure values. + /// + [FieldOffset(0)] + public PressureValues Values; + + /// + /// Pressure button values. + /// + [StructLayout(LayoutKind.Sequential)] + public struct PressureValues + { + /// + /// DPad Up pressure value. + /// + public byte Up; + + /// + /// DPad Right pressure value. + /// + public byte Right; + + /// + /// DPad Down pressure value. + /// + public byte Down; + + /// + /// DPad Left pressure value. + /// + public byte Left; + + /// + /// Left Trigger pressure value. + /// + public byte L2; + + /// + /// Right Trigger pressure value. + /// + public byte R2; + + /// + /// Left Shoulder pressure value. + /// + public byte L1; + + /// + /// Right Shoulder pressure value. + /// + public byte R1; + + /// + /// Triangle pressure value. + /// + public byte Triangle; + + /// + /// Circle pressure value. + /// + public byte Circle; + + /// + /// Cross pressure value. + /// + public byte Cross; + + /// + /// Square pressure value. + /// + public byte Square; + } + } + + /// + /// Pressure button values. + /// + public PressureUnion Pressure; + + internal fixed byte Reserved2[4]; + + /// + /// Battery charge status. + /// + public byte BatteryStatus; + + internal fixed byte Reserved3[10]; + + // + // Motion information + // + public ushort AccelerometerX; + public ushort AccelerometerY; + public ushort AccelerometerZ; + public ushort Gyroscope; +} \ No newline at end of file diff --git a/SDK/Nefarius.DsHidMini.IPC/Models/Public/SetHostResult.cs b/SDK/Nefarius.DsHidMini.IPC/Models/Public/SetHostResult.cs new file mode 100644 index 00000000..912460f6 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/Models/Public/SetHostResult.cs @@ -0,0 +1,19 @@ +namespace Nefarius.DsHidMini.IPC.Models.Public; + +public struct SetHostResult +{ + /// + /// The NTSTATUS value of the "pairing" or address overwrite action. + /// + public UInt32 WriteStatus; + + /// + /// The NTSTATUS value of the address read/query action to verify the new address. + /// + public UInt32 ReadStatus; + + public override string ToString() + { + return $"Pairing result: 0x{WriteStatus:X}, query result: 0x{ReadStatus:X}"; + } +} \ No newline at end of file diff --git a/SDK/Nefarius.DsHidMini.IPC/Models/Public/UsbSetupPacket.cs b/SDK/Nefarius.DsHidMini.IPC/Models/Public/UsbSetupPacket.cs new file mode 100644 index 00000000..d0531537 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/Models/Public/UsbSetupPacket.cs @@ -0,0 +1,106 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +namespace Nefarius.DsHidMini.IPC.Models.Public; + +/// +/// Describes a USB control transfer setup packet. +/// +[StructLayout(LayoutKind.Explicit)] +[SuppressMessage("ReSharper", "InconsistentNaming")] +public struct WDF_USB_CONTROL_SETUP_PACKET +{ + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RequestStruct + { + private byte _byte; + + public byte Recipient + { + get => (byte)(_byte & 0x03); // Mask for the lowest 2 bits + set => _byte = (byte)((_byte & ~0x03) | (value & 0x03)); + } + + public byte Reserved + { + get => (byte)((_byte >> 2) & 0x07); // Next 3 bits + set => _byte = (byte)((_byte & ~(0x07 << 2)) | ((value & 0x07) << 2)); + } + + public byte Type + { + get => (byte)((_byte >> 5) & 0x03); // Next 2 bits + set => _byte = (byte)((_byte & ~(0x03 << 5)) | ((value & 0x03) << 5)); + } + + public byte Dir + { + get => (byte)((_byte >> 7) & 0x01); // The highest bit + set => _byte = (byte)((_byte & ~(0x01 << 7)) | ((value & 0x01) << 7)); + } + + public byte Byte + { + get => _byte; + set => _byte = value; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct BytesStruct + { + public byte LowByte; + public byte HiByte; + } + + [StructLayout(LayoutKind.Explicit, Pack = 1)] + public struct PacketStruct + { + [FieldOffset(0)] + public RequestStruct bm; + + [FieldOffset(1)] + public byte bRequest; + + [FieldOffset(2)] + internal BytesStruct wValueBytes; + + [FieldOffset(4)] + internal BytesStruct wIndexBytes; + + [FieldOffset(6)] + public ushort wLength; + + public ushort wValue + { + get => (ushort)((wValueBytes.HiByte << 8) | wValueBytes.LowByte); + set + { + wValueBytes.LowByte = (byte)(value & 0xFF); + wValueBytes.HiByte = (byte)((value >> 8) & 0xFF); + } + } + + public ushort wIndex + { + get => (ushort)((wIndexBytes.HiByte << 8) | wIndexBytes.LowByte); + set + { + wIndexBytes.LowByte = (byte)(value & 0xFF); + wIndexBytes.HiByte = (byte)((value >> 8) & 0xFF); + } + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public unsafe struct GenericStruct + { + public fixed byte Bytes[8]; + } + + [FieldOffset(0)] + public PacketStruct Packet; + + [FieldOffset(0)] + public GenericStruct Generic; +} \ No newline at end of file diff --git a/SDK/Nefarius.DsHidMini.IPC/NativeMethods.txt b/SDK/Nefarius.DsHidMini.IPC/NativeMethods.txt new file mode 100644 index 00000000..4aef14ee --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/NativeMethods.txt @@ -0,0 +1,10 @@ +OpenProcess +DuplicateHandle +GetCurrentProcess +DUPLICATE_HANDLE_OPTIONS +OpenFileMapping +MapViewOfFile +UnmapViewOfFile +FILE_MAP +GetSystemInfo +WIN32_ERROR \ No newline at end of file diff --git a/SDK/Nefarius.DsHidMini.IPC/Nefarius.DsHidMini.IPC.csproj b/SDK/Nefarius.DsHidMini.IPC/Nefarius.DsHidMini.IPC.csproj new file mode 100644 index 00000000..1107aa4c --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/Nefarius.DsHidMini.IPC.csproj @@ -0,0 +1,25 @@ + + + + net8.0-windows + enable + enable + true + 1591 + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/SDK/Nefarius.DsHidMini.IPC/README.md b/SDK/Nefarius.DsHidMini.IPC/README.md new file mode 100644 index 00000000..a75af670 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/README.md @@ -0,0 +1,17 @@ +# Nefarius.DsHidMini.IPC + +![Requirements](https://img.shields.io/badge/Requires-.NET%208.0-blue.svg) +[![NuGet Version](https://img.shields.io/nuget/v/Nefarius.DsHidMini.IPC)](https://www.nuget.org/packages/Nefarius.DsHidMini.IPC/) +[![NuGet](https://img.shields.io/nuget/dt/Nefarius.DsHidMini.IPC)](https://www.nuget.org/packages/Nefarius.DsHidMini.IPC/) + +Interop SDK for DsHidMini IPC mechanism. + +## Documentation + +[Link to API docs](docs/index.md). + +### Generating documentation + +- `dotnet build -c:Release` +- `dotnet tool install -g Nefarius.Tools.XMLDoc2Markdown` +- `xmldoc2md .\bin\Release\net8.0-windows\Nefarius.DsHidMini.IPC.dll .\docs\` diff --git a/SDK/Nefarius.DsHidMini.IPC/Util/Converters/EnumDescriptionTypeConverter.cs b/SDK/Nefarius.DsHidMini.IPC/Util/Converters/EnumDescriptionTypeConverter.cs new file mode 100644 index 00000000..0af79b33 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/Util/Converters/EnumDescriptionTypeConverter.cs @@ -0,0 +1,37 @@ +using System.ComponentModel; +using System.Globalization; +using System.Reflection; + +namespace Nefarius.DsHidMini.IPC.Util.Converters; + +public class EnumDescriptionTypeConverter : EnumConverter +{ + public EnumDescriptionTypeConverter(Type type) + : base(type) + { + } + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, + Type destinationType) + { + if (destinationType == typeof(string)) + { + if (value != null) + { + FieldInfo? fi = value.GetType().GetField(value.ToString()); + if (fi != null) + { + DescriptionAttribute[] attributes = + (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + return attributes.Length > 0 && !string.IsNullOrEmpty(attributes[0].Description) + ? attributes[0].Description + : value.ToString(); + } + } + + return string.Empty; + } + + return base.ConvertTo(context, culture, value, destinationType); + } +} \ No newline at end of file diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/index.md b/SDK/Nefarius.DsHidMini.IPC/docs/index.md new file mode 100644 index 00000000..bc37b8bc --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/index.md @@ -0,0 +1,35 @@ +# Assembly Nefarius.DsHidMini.IPC + +## Namespace Nefarius.DsHidMini.IPC + +- [DsHidMiniInterop](./nefarius.dshidmini.ipc.dshidminiinterop.md) + +## Namespace Nefarius.DsHidMini.IPC.Exceptions + +- [DsHidMiniInteropConcurrencyException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropconcurrencyexception.md) + +- [DsHidMiniInteropInvalidDeviceIndexException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropinvaliddeviceindexexception.md) + +- [DsHidMiniInteropReplyTimeoutException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropreplytimeoutexception.md) + +- [DsHidMiniInteropUnavailableException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunavailableexception.md) + +- [DsHidMiniInteropUnexpectedReplyException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunexpectedreplyexception.md) + +## Namespace Nefarius.DsHidMini.IPC.Models.Drivers + +- [DsBatteryStatus](./nefarius.dshidmini.ipc.models.drivers.dsbatterystatus.md) + +- [DsHidDeviceMode](./nefarius.dshidmini.ipc.models.drivers.dshiddevicemode.md) + +- [DsHidMiniDriver](./nefarius.dshidmini.ipc.models.drivers.dshidminidriver.md) + +## Namespace Nefarius.DsHidMini.IPC.Models.Public + +- [Ds3RawInputReport](./nefarius.dshidmini.ipc.models.public.ds3rawinputreport.md) + +- [SetHostResult](./nefarius.dshidmini.ipc.models.public.sethostresult.md) + +## Namespace Nefarius.DsHidMini.IPC.Util.Converters + +- [EnumDescriptionTypeConverter](./nefarius.dshidmini.ipc.util.converters.enumdescriptiontypeconverter.md) diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.dshidminiinterop.md b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.dshidminiinterop.md new file mode 100644 index 00000000..d4a9f925 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.dshidminiinterop.md @@ -0,0 +1,209 @@ +# DsHidMiniInterop + +Namespace: Nefarius.DsHidMini.IPC + +Connects to the drivers shared memory region and offers utility methods for data exchange. + +```csharp +public sealed class DsHidMiniInterop : System.IDisposable +``` + +Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [DsHidMiniInterop](./nefarius.dshidmini.ipc.dshidminiinterop.md)
+Implements [IDisposable](https://docs.microsoft.com/en-us/dotnet/api/system.idisposable) + +## Properties + +### **IsAvailable** + +Gets whether driver IPC is available. + +```csharp +public static bool IsAvailable { get; } +``` + +#### Property Value + +[Boolean](https://docs.microsoft.com/en-us/dotnet/api/system.boolean)
+ +## Constructors + +###
**DsHidMiniInterop()** + +Creates a new [DsHidMiniInterop](./nefarius.dshidmini.ipc.dshidminiinterop.md) instance by connecting to the driver IPC mechanism. + +```csharp +public DsHidMiniInterop() +``` + +#### Exceptions + +[DsHidMiniInteropUnavailableException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunavailableexception.md)
+No driver instance is available. Make sure that at least one + device is connected and that the driver is installed and working properly. Call [DsHidMiniInterop.IsAvailable](./nefarius.dshidmini.ipc.dshidminiinterop.md#isavailable) prior to + avoid this exception. + +## Methods + +###
**Dispose()** + +```csharp +public void Dispose() +``` + +### **GetRawInputReport(Int32, ref Ds3RawInputReport, Nullable<TimeSpan>)** + +Attempts to read the [Ds3RawInputReport](./nefarius.dshidmini.ipc.models.public.ds3rawinputreport.md) from a given device instance. + +```csharp +public bool GetRawInputReport(int deviceIndex, ref Ds3RawInputReport report, Nullable timeout) +``` + +#### Parameters + +`deviceIndex` [Int32](https://docs.microsoft.com/en-us/dotnet/api/system.int32)
+The one-based device index. + +`report` [Ds3RawInputReport&](./nefarius.dshidmini.ipc.models.public.ds3rawinputreport&.md)
+The [Ds3RawInputReport](./nefarius.dshidmini.ipc.models.public.ds3rawinputreport.md) to populate. + +`timeout` [Nullable<TimeSpan>](https://docs.microsoft.com/en-us/dotnet/api/system.nullable-1)
+Optional timeout to wait for a report update to arrive. Default invocation returns immediately. + +#### Returns + +TRUE if `report` got filled in or FALSE if the given `deviceIndex` is not + occupied. + +#### Exceptions + +[DsHidMiniInteropUnavailableException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunavailableexception.md)
+Driver IPC unavailable, make sure that at least one compatible + controller is connected and operational. + +[DsHidMiniInteropUnexpectedReplyException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunexpectedreplyexception.md)
+The driver returned unexpected or malformed data. + +**Remarks:** + +If `timeout` is null, this method returns the last known input report copy immediately. If + you use this call in a busy loop, you should set a timeout so this call becomes event-based, meaning the call will + only return when the driver signaled that new data is available, otherwise you will just burn through CPU for no + good reason. A new input report is typically available each average 5 milliseconds, depending on the connection + (wired or wireless) so a timeout of 20 milliseconds should be a good recommendation. + +###
**Reconnect()** + +Attempt re-initialization of IPC after all devices got disconnected. + +```csharp +public void Reconnect() +``` + +#### Exceptions + +[DsHidMiniInteropUnavailableException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunavailableexception.md)
+No driver instance is available. Make sure that at least one + device is connected and that the driver is installed and working properly. Call [DsHidMiniInterop.IsAvailable](./nefarius.dshidmini.ipc.dshidminiinterop.md#isavailable) prior to + avoid this exception. + +###
**SendPing()** + +Send a PING to the driver and awaits the reply. + +```csharp +public void SendPing() +``` + +#### Exceptions + +[DsHidMiniInteropUnavailableException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunavailableexception.md)
+Driver IPC unavailable, make sure that at least one compatible + controller is connected and operational. + +[DsHidMiniInteropReplyTimeoutException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropreplytimeoutexception.md)
+The driver didn't respond within an expected period. + +[DsHidMiniInteropUnexpectedReplyException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunexpectedreplyexception.md)
+The driver returned unexpected or malformed data. + +###
**SetHostAddress(Int32, PhysicalAddress)** + +Writes a new host address to the given device. + +```csharp +public SetHostResult SetHostAddress(int deviceIndex, PhysicalAddress hostAddress) +``` + +#### Parameters + +`deviceIndex` [Int32](https://docs.microsoft.com/en-us/dotnet/api/system.int32)
+The one-based device index. + +`hostAddress` PhysicalAddress
+The new host address. + +#### Returns + +A [SetHostResult](./nefarius.dshidmini.ipc.models.public.sethostresult.md) containing success (or error) details. + +#### Exceptions + +[DsHidMiniInteropUnavailableException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunavailableexception.md)
+Driver IPC unavailable, make sure that at least one compatible + controller is connected and operational. + +[DsHidMiniInteropInvalidDeviceIndexException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropinvaliddeviceindexexception.md)
+The `deviceIndex` was outside a valid + range. + +[DsHidMiniInteropConcurrencyException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropconcurrencyexception.md)
+A different thread is currently performing a data exchange. + +[DsHidMiniInteropReplyTimeoutException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropreplytimeoutexception.md)
+The driver didn't respond within an expected period. + +[DsHidMiniInteropUnexpectedReplyException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunexpectedreplyexception.md)
+The driver returned unexpected or malformed data. + +**Remarks:** + +This is synonymous with "pairing" to a new Bluetooth host. + +###
**SetPlayerIndex(Int32, Byte)** + +Overwrites the player slot indicator (player LEDs) of the given device. + +```csharp +public uint SetPlayerIndex(int deviceIndex, byte playerIndex) +``` + +#### Parameters + +`deviceIndex` [Int32](https://docs.microsoft.com/en-us/dotnet/api/system.int32)
+The one-based device index. + +`playerIndex` [Byte](https://docs.microsoft.com/en-us/dotnet/api/system.byte)
+The player index to set to. Valid values include 1 to 7. + +#### Returns + +[UInt32](https://docs.microsoft.com/en-us/dotnet/api/system.uint32) + +#### Exceptions + +[DsHidMiniInteropUnavailableException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunavailableexception.md)
+Driver IPC unavailable, make sure that at least one compatible + controller is connected and operational. + +[ArgumentOutOfRangeException](https://docs.microsoft.com/en-us/dotnet/api/system.argumentoutofrangeexception)
+The `deviceIndex` or `playerIndex` + were out of range. + +[DsHidMiniInteropConcurrencyException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropconcurrencyexception.md)
+A different thread is currently performing a data exchange. + +[DsHidMiniInteropReplyTimeoutException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropreplytimeoutexception.md)
+The driver didn't respond within an expected period. + +[DsHidMiniInteropUnexpectedReplyException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunexpectedreplyexception.md)
+The driver returned unexpected or malformed data. diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropconcurrencyexception.md b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropconcurrencyexception.md new file mode 100644 index 00000000..9c69dab5 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropconcurrencyexception.md @@ -0,0 +1,94 @@ +# DsHidMiniInteropConcurrencyException + +Namespace: Nefarius.DsHidMini.IPC.Exceptions + +Multiple threads tried to invoke IPC methods at the same time, which is not supported. + +```csharp +public sealed class DsHidMiniInteropConcurrencyException : System.Exception, System.Runtime.Serialization.ISerializable +``` + +Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [Exception](https://docs.microsoft.com/en-us/dotnet/api/system.exception) → [DsHidMiniInteropConcurrencyException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropconcurrencyexception.md)
+Implements [ISerializable](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.iserializable) + +## Properties + +###
**Data** + +```csharp +public IDictionary Data { get; } +``` + +#### Property Value + +[IDictionary](https://docs.microsoft.com/en-us/dotnet/api/system.collections.idictionary)
+ +###
**HelpLink** + +```csharp +public string HelpLink { get; set; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**HResult** + +```csharp +public int HResult { get; set; } +``` + +#### Property Value + +[Int32](https://docs.microsoft.com/en-us/dotnet/api/system.int32)
+ +###
**InnerException** + +```csharp +public Exception InnerException { get; } +``` + +#### Property Value + +[Exception](https://docs.microsoft.com/en-us/dotnet/api/system.exception)
+ +###
**Message** + +```csharp +public string Message { get; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**Source** + +```csharp +public string Source { get; set; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**StackTrace** + +```csharp +public string StackTrace { get; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**TargetSite** + +```csharp +public MethodBase TargetSite { get; } +``` + +#### Property Value + +[MethodBase](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase)
diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropexclusiveaccessexception.md b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropexclusiveaccessexception.md new file mode 100644 index 00000000..41876dcd --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropexclusiveaccessexception.md @@ -0,0 +1,94 @@ +# DsHidMiniInteropExclusiveAccessException + +Namespace: Nefarius.DsHidMini.IPC.Exceptions + +Another client is already connected to the driver, can't continue initialization. + +```csharp +public sealed class DsHidMiniInteropExclusiveAccessException : System.Exception, System.Runtime.Serialization.ISerializable +``` + +Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [Exception](https://docs.microsoft.com/en-us/dotnet/api/system.exception) → [DsHidMiniInteropExclusiveAccessException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropexclusiveaccessexception.md)
+Implements [ISerializable](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.iserializable) + +## Properties + +###
**Data** + +```csharp +public IDictionary Data { get; } +``` + +#### Property Value + +[IDictionary](https://docs.microsoft.com/en-us/dotnet/api/system.collections.idictionary)
+ +###
**HelpLink** + +```csharp +public string HelpLink { get; set; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**HResult** + +```csharp +public int HResult { get; set; } +``` + +#### Property Value + +[Int32](https://docs.microsoft.com/en-us/dotnet/api/system.int32)
+ +###
**InnerException** + +```csharp +public Exception InnerException { get; } +``` + +#### Property Value + +[Exception](https://docs.microsoft.com/en-us/dotnet/api/system.exception)
+ +###
**Message** + +```csharp +public string Message { get; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**Source** + +```csharp +public string Source { get; set; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**StackTrace** + +```csharp +public string StackTrace { get; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**TargetSite** + +```csharp +public MethodBase TargetSite { get; } +``` + +#### Property Value + +[MethodBase](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase)
diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropinvaliddeviceindexexception.md b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropinvaliddeviceindexexception.md new file mode 100644 index 00000000..88e73d40 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropinvaliddeviceindexexception.md @@ -0,0 +1,94 @@ +# DsHidMiniInteropInvalidDeviceIndexException + +Namespace: Nefarius.DsHidMini.IPC.Exceptions + +The provided device index was out of range; allowed values must remain between (including) 1 and 255. + +```csharp +public sealed class DsHidMiniInteropInvalidDeviceIndexException : System.Exception, System.Runtime.Serialization.ISerializable +``` + +Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [Exception](https://docs.microsoft.com/en-us/dotnet/api/system.exception) → [DsHidMiniInteropInvalidDeviceIndexException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropinvaliddeviceindexexception.md)
+Implements [ISerializable](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.iserializable) + +## Properties + +###
**Data** + +```csharp +public IDictionary Data { get; } +``` + +#### Property Value + +[IDictionary](https://docs.microsoft.com/en-us/dotnet/api/system.collections.idictionary)
+ +###
**HelpLink** + +```csharp +public string HelpLink { get; set; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**HResult** + +```csharp +public int HResult { get; set; } +``` + +#### Property Value + +[Int32](https://docs.microsoft.com/en-us/dotnet/api/system.int32)
+ +###
**InnerException** + +```csharp +public Exception InnerException { get; } +``` + +#### Property Value + +[Exception](https://docs.microsoft.com/en-us/dotnet/api/system.exception)
+ +###
**Message** + +```csharp +public string Message { get; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**Source** + +```csharp +public string Source { get; set; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**StackTrace** + +```csharp +public string StackTrace { get; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**TargetSite** + +```csharp +public MethodBase TargetSite { get; } +``` + +#### Property Value + +[MethodBase](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase)
diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropreplytimeoutexception.md b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropreplytimeoutexception.md new file mode 100644 index 00000000..2a70f9a8 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropreplytimeoutexception.md @@ -0,0 +1,94 @@ +# DsHidMiniInteropReplyTimeoutException + +Namespace: Nefarius.DsHidMini.IPC.Exceptions + +Operation timed out while waiting for a request reply. + +```csharp +public sealed class DsHidMiniInteropReplyTimeoutException : System.Exception, System.Runtime.Serialization.ISerializable +``` + +Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [Exception](https://docs.microsoft.com/en-us/dotnet/api/system.exception) → [DsHidMiniInteropReplyTimeoutException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropreplytimeoutexception.md)
+Implements [ISerializable](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.iserializable) + +## Properties + +###
**Data** + +```csharp +public IDictionary Data { get; } +``` + +#### Property Value + +[IDictionary](https://docs.microsoft.com/en-us/dotnet/api/system.collections.idictionary)
+ +###
**HelpLink** + +```csharp +public string HelpLink { get; set; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**HResult** + +```csharp +public int HResult { get; set; } +``` + +#### Property Value + +[Int32](https://docs.microsoft.com/en-us/dotnet/api/system.int32)
+ +###
**InnerException** + +```csharp +public Exception InnerException { get; } +``` + +#### Property Value + +[Exception](https://docs.microsoft.com/en-us/dotnet/api/system.exception)
+ +###
**Message** + +```csharp +public string Message { get; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**Source** + +```csharp +public string Source { get; set; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**StackTrace** + +```csharp +public string StackTrace { get; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**TargetSite** + +```csharp +public MethodBase TargetSite { get; } +``` + +#### Property Value + +[MethodBase](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase)
diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropunavailableexception.md b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropunavailableexception.md new file mode 100644 index 00000000..5c46d8f7 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropunavailableexception.md @@ -0,0 +1,94 @@ +# DsHidMiniInteropUnavailableException + +Namespace: Nefarius.DsHidMini.IPC.Exceptions + +Driver IPC unavailable, make sure that at least one compatible controller is connected and operational. + +```csharp +public sealed class DsHidMiniInteropUnavailableException : System.Exception, System.Runtime.Serialization.ISerializable +``` + +Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [Exception](https://docs.microsoft.com/en-us/dotnet/api/system.exception) → [DsHidMiniInteropUnavailableException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunavailableexception.md)
+Implements [ISerializable](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.iserializable) + +## Properties + +###
**Data** + +```csharp +public IDictionary Data { get; } +``` + +#### Property Value + +[IDictionary](https://docs.microsoft.com/en-us/dotnet/api/system.collections.idictionary)
+ +###
**HelpLink** + +```csharp +public string HelpLink { get; set; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**HResult** + +```csharp +public int HResult { get; set; } +``` + +#### Property Value + +[Int32](https://docs.microsoft.com/en-us/dotnet/api/system.int32)
+ +###
**InnerException** + +```csharp +public Exception InnerException { get; } +``` + +#### Property Value + +[Exception](https://docs.microsoft.com/en-us/dotnet/api/system.exception)
+ +###
**Message** + +```csharp +public string Message { get; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**Source** + +```csharp +public string Source { get; set; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**StackTrace** + +```csharp +public string StackTrace { get; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**TargetSite** + +```csharp +public MethodBase TargetSite { get; } +``` + +#### Property Value + +[MethodBase](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase)
diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropunexpectedreplyexception.md b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropunexpectedreplyexception.md new file mode 100644 index 00000000..f40108e0 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.exceptions.dshidminiinteropunexpectedreplyexception.md @@ -0,0 +1,94 @@ +# DsHidMiniInteropUnexpectedReplyException + +Namespace: Nefarius.DsHidMini.IPC.Exceptions + +A request reply was malformed. + +```csharp +public sealed class DsHidMiniInteropUnexpectedReplyException : System.Exception, System.Runtime.Serialization.ISerializable +``` + +Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [Exception](https://docs.microsoft.com/en-us/dotnet/api/system.exception) → [DsHidMiniInteropUnexpectedReplyException](./nefarius.dshidmini.ipc.exceptions.dshidminiinteropunexpectedreplyexception.md)
+Implements [ISerializable](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.serialization.iserializable) + +## Properties + +###
**Data** + +```csharp +public IDictionary Data { get; } +``` + +#### Property Value + +[IDictionary](https://docs.microsoft.com/en-us/dotnet/api/system.collections.idictionary)
+ +###
**HelpLink** + +```csharp +public string HelpLink { get; set; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**HResult** + +```csharp +public int HResult { get; set; } +``` + +#### Property Value + +[Int32](https://docs.microsoft.com/en-us/dotnet/api/system.int32)
+ +###
**InnerException** + +```csharp +public Exception InnerException { get; } +``` + +#### Property Value + +[Exception](https://docs.microsoft.com/en-us/dotnet/api/system.exception)
+ +###
**Message** + +```csharp +public string Message { get; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**Source** + +```csharp +public string Source { get; set; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**StackTrace** + +```csharp +public string StackTrace { get; } +``` + +#### Property Value + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string)
+ +###
**TargetSite** + +```csharp +public MethodBase TargetSite { get; } +``` + +#### Property Value + +[MethodBase](https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase)
diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.drivers.dsbatterystatus.md b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.drivers.dsbatterystatus.md new file mode 100644 index 00000000..9fbafa1e --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.drivers.dsbatterystatus.md @@ -0,0 +1,25 @@ +# DsBatteryStatus + +Namespace: Nefarius.DsHidMini.IPC.Models.Drivers + +Battery status values. + +```csharp +public enum DsBatteryStatus +``` + +Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [ValueType](https://docs.microsoft.com/en-us/dotnet/api/system.valuetype) → [Enum](https://docs.microsoft.com/en-us/dotnet/api/system.enum) → [DsBatteryStatus](./nefarius.dshidmini.ipc.models.drivers.dsbatterystatus.md)
+Implements [IComparable](https://docs.microsoft.com/en-us/dotnet/api/system.icomparable), [ISpanFormattable](https://docs.microsoft.com/en-us/dotnet/api/system.ispanformattable), [IFormattable](https://docs.microsoft.com/en-us/dotnet/api/system.iformattable), [IConvertible](https://docs.microsoft.com/en-us/dotnet/api/system.iconvertible) + +## Fields + +| Name | Value | Description | +| --- | --: | --- | +| Unknown | 0 | Unknown/not yet reported. | +| Dying | 1 | Dying. Battery is so low the device is barely being kept on. | +| Low | 2 | Low. Device should be charged soon. | +| Medium | 3 | Medium. Will last for a while but should be charged soon. | +| High | 4 | High. Will last for a few sessions. | +| Full | 5 | Full. Status right after successful charging. | +| Charging | 238 | Charging. The default state while wired until [DsBatteryStatus.Charged](./nefarius.dshidmini.ipc.models.drivers.dsbatterystatus.md#charged) is reached. | +| Charged | 239 | Charged. While wired synonymous to [DsBatteryStatus.Full](./nefarius.dshidmini.ipc.models.drivers.dsbatterystatus.md#full) while wireless. | diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.drivers.dshiddevicemode.md b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.drivers.dshiddevicemode.md new file mode 100644 index 00000000..75c178e7 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.drivers.dshiddevicemode.md @@ -0,0 +1,22 @@ +# DsHidDeviceMode + +Namespace: Nefarius.DsHidMini.IPC.Models.Drivers + +HID device emulation modes. + +```csharp +public enum DsHidDeviceMode +``` + +Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [ValueType](https://docs.microsoft.com/en-us/dotnet/api/system.valuetype) → [Enum](https://docs.microsoft.com/en-us/dotnet/api/system.enum) → [DsHidDeviceMode](./nefarius.dshidmini.ipc.models.drivers.dshiddevicemode.md)
+Implements [IComparable](https://docs.microsoft.com/en-us/dotnet/api/system.icomparable), [ISpanFormattable](https://docs.microsoft.com/en-us/dotnet/api/system.ispanformattable), [IFormattable](https://docs.microsoft.com/en-us/dotnet/api/system.iformattable), [IConvertible](https://docs.microsoft.com/en-us/dotnet/api/system.iconvertible) + +## Fields + +| Name | Value | Description | +| --- | --: | --- | +| SDF | 1 | Single Device with Force Feedback mode. | +| GPJ | 2 | Gamepad plus Joystick mode. | +| SXS | 3 | SIXAXIS.SYS mode. | +| DS4W | 4 | DS4Windows DualShock 4 emulation mode. | +| XInput | 5 | Xbox One Controller mode. | diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.drivers.dshidminidriver.md b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.drivers.dshidminidriver.md new file mode 100644 index 00000000..426a7743 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.drivers.dshidminidriver.md @@ -0,0 +1,105 @@ +# DsHidMiniDriver + +Namespace: Nefarius.DsHidMini.IPC.Models.Drivers + +Interface and property information about the DsHidMini driver. + +```csharp +public static class DsHidMiniDriver +``` + +Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [DsHidMiniDriver](./nefarius.dshidmini.ipc.models.drivers.dshidminidriver.md) + +## Properties + +###
**BatteryStatusProperty** + +The last reported [DsBatteryStatus](./nefarius.dshidmini.ipc.models.drivers.dsbatterystatus.md) of the device. + +```csharp +public static DevicePropertyKey BatteryStatusProperty { get; } +``` + +#### Property Value + +DevicePropertyKey
+ +###
**BluetoothLastConnectedTimeProperty** + +Timestamp of last wireless connection. + +```csharp +public static DevicePropertyKey BluetoothLastConnectedTimeProperty { get; } +``` + +#### Property Value + +DevicePropertyKey
+ +###
**DeviceAddressProperty** + +The Bluetooth MAC address of the device itself. + +```csharp +public static DevicePropertyKey DeviceAddressProperty { get; } +``` + +#### Property Value + +DevicePropertyKey
+ +###
**DeviceInterfaceGuid** + +Interface GUID common to all devices the DsHidMini driver supports. + +```csharp +public static Guid DeviceInterfaceGuid { get; } +``` + +#### Property Value + +[Guid](https://docs.microsoft.com/en-us/dotnet/api/system.guid)
+ +###
**HidDeviceModeProperty** + +The currently active [DsHidDeviceMode](./nefarius.dshidmini.ipc.models.drivers.dshiddevicemode.md). + +```csharp +public static DevicePropertyKey HidDeviceModeProperty { get; } +``` + +#### Property Value + +DevicePropertyKey
+ +###
**HostAddressProperty** + +The Bluetooth MAC address the device is currently paired to. + +```csharp +public static DevicePropertyKey HostAddressProperty { get; } +``` + +#### Property Value + +DevicePropertyKey
+ +###
**LastHostRequestStatusProperty** + +```csharp +public static DevicePropertyKey LastHostRequestStatusProperty { get; } +``` + +#### Property Value + +DevicePropertyKey
+ +###
**LastPairingStatusProperty** + +```csharp +public static DevicePropertyKey LastPairingStatusProperty { get; } +``` + +#### Property Value + +DevicePropertyKey
diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.public.ds3rawinputreport.md b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.public.ds3rawinputreport.md new file mode 100644 index 00000000..26fa5a09 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.public.ds3rawinputreport.md @@ -0,0 +1,117 @@ +# Ds3RawInputReport + +Namespace: Nefarius.DsHidMini.IPC.Models.Public + +Raw input report as it is sent by the SIXAXIS/DualShock 3. + +```csharp +public struct Ds3RawInputReport +``` + +Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [ValueType](https://docs.microsoft.com/en-us/dotnet/api/system.valuetype) → [Ds3RawInputReport](./nefarius.dshidmini.ipc.models.public.ds3rawinputreport.md) + +## Fields + +###
**AccelerometerX** + +```csharp +public ushort AccelerometerX; +``` + +### **AccelerometerY** + +```csharp +public ushort AccelerometerY; +``` + +### **AccelerometerZ** + +```csharp +public ushort AccelerometerZ; +``` + +### **BatteryStatus** + +Battery charge status. + +```csharp +public byte BatteryStatus; +``` + +### **Buttons** + +The individual controller buttons. + +```csharp +public ButtonUnion Buttons; +``` + +### **Gyroscope** + +```csharp +public ushort Gyroscope; +``` + +### **LeftThumbX** + +Left Thumb X-axis. + +```csharp +public byte LeftThumbX; +``` + +**Remarks:** + +0x00 = left/bottom, 0x80 = centered, 0xFF = right/top + +### **LeftThumbY** + +Left Thumb Y-axis. + +```csharp +public byte LeftThumbY; +``` + +**Remarks:** + +0x00 = left/bottom, 0x80 = centered, 0xFF = right/top + +### **Pressure** + +Pressure button values. + +```csharp +public PressureUnion Pressure; +``` + +### **ReportId** + +Report ID (always 1). + +```csharp +public byte ReportId; +``` + +### **RightThumbX** + +Right Thumb X-axis. + +```csharp +public byte RightThumbX; +``` + +**Remarks:** + +0x00 = left/bottom, 0x80 = centered, 0xFF = right/top + +### **RightThumbY** + +Right Thumb Y-axis. + +```csharp +public byte RightThumbY; +``` + +**Remarks:** + +0x00 = left/bottom, 0x80 = centered, 0xFF = right/top diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.public.sethostresult.md b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.public.sethostresult.md new file mode 100644 index 00000000..cc08f090 --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.models.public.sethostresult.md @@ -0,0 +1,39 @@ +# SetHostResult + +Namespace: Nefarius.DsHidMini.IPC.Models.Public + +```csharp +public struct SetHostResult +``` + +Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → [ValueType](https://docs.microsoft.com/en-us/dotnet/api/system.valuetype) → [SetHostResult](./nefarius.dshidmini.ipc.models.public.sethostresult.md) + +## Fields + +### **ReadStatus** + +The NTSTATUS value of the address read/query action to verify the new address. + +```csharp +public uint ReadStatus; +``` + +### **WriteStatus** + +The NTSTATUS value of the "pairing" or address overwrite action. + +```csharp +public uint WriteStatus; +``` + +## Methods + +### **ToString()** + +```csharp +string ToString() +``` + +#### Returns + +[String](https://docs.microsoft.com/en-us/dotnet/api/system.string) diff --git a/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.util.converters.enumdescriptiontypeconverter.md b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.util.converters.enumdescriptiontypeconverter.md new file mode 100644 index 00000000..15300acd --- /dev/null +++ b/SDK/Nefarius.DsHidMini.IPC/docs/nefarius.dshidmini.ipc.util.converters.enumdescriptiontypeconverter.md @@ -0,0 +1,43 @@ +# EnumDescriptionTypeConverter + +Namespace: Nefarius.DsHidMini.IPC.Util.Converters + +```csharp +public class EnumDescriptionTypeConverter : System.ComponentModel.EnumConverter +``` + +Inheritance [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) → TypeConverter → EnumConverter → [EnumDescriptionTypeConverter](./nefarius.dshidmini.ipc.util.converters.enumdescriptiontypeconverter.md) + +## Constructors + +### **EnumDescriptionTypeConverter(Type)** + +```csharp +public EnumDescriptionTypeConverter(Type type) +``` + +#### Parameters + +`type` [Type](https://docs.microsoft.com/en-us/dotnet/api/system.type)
+ +## Methods + +###
**ConvertTo(ITypeDescriptorContext, CultureInfo, Object, Type)** + +```csharp +public object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) +``` + +#### Parameters + +`context` ITypeDescriptorContext
+ +`culture` [CultureInfo](https://docs.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo)
+ +`value` [Object](https://docs.microsoft.com/en-us/dotnet/api/system.object)
+ +`destinationType` [Type](https://docs.microsoft.com/en-us/dotnet/api/system.type)
+ +#### Returns + +[Object](https://docs.microsoft.com/en-us/dotnet/api/system.object) diff --git a/appveyor.yml b/appveyor.yml index e7bff559..1c990008 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 3.1.{build}.0 +version: 3.2.{build}.0 build_cloud: WIN-LKR467JS4GL image: Windows test: off @@ -13,6 +13,9 @@ platform: - x86 configuration: - Release +branches: + only: + - master skip_commits: files: - '**/*.md' @@ -27,6 +30,7 @@ install: cd "C:\projects\DMF" git pull > NUL cd %appveyor_build_folder% +- cmd: mklink /J "%APPVEYOR_BUILD_FOLDER%\XInputBridge\vcpkg_installed" "C:\tools\build-cache\dshidmini\XInputBridge\vcpkg_installed\%PLATFORM%" dotnet_csproj: patch: true file: '**\*.csproj' @@ -42,6 +46,7 @@ before_build: - cmd: vpatch --stamp-version "%APPVEYOR_BUILD_VERSION%" --target-file ".\sys\dshidmini.rc" --resource.file-version --resource.product-version - cmd: vpatch --stamp-version "%APPVEYOR_BUILD_VERSION%" --target-file ".\XInputBridge\XInputBridge.rc" --resource.file-version --resource.product-version - cmd: dotnet restore .\ControlApp\ +- cmd: dotnet restore .\ipctest\ build_script: - cmd: .\build.cmd - cmd: if %PLATFORM%==x64 dotnet publish /p:PublishProfile=Properties\PublishProfiles\release-win-x64.pubxml .\ControlApp\ diff --git a/build/Build.cs b/build/Build.cs index 6579b4a4..007af8a4 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -76,23 +76,6 @@ class Build : NukeBuild .SetMaxCpuCount(Environment.ProcessorCount) .SetNodeReuse(IsLocalBuild) ); - - bool enableTelemetry = - bool.TryParse(Environment.GetEnvironmentVariable("SCPLIB_ENABLE_TELEMETRY"), out bool isSet) && isSet; - - if (enableTelemetry) - { - // OTEL build - MSBuildTasks.MSBuild(s => s - .SetTargetPath(Solution) - .SetTargets("Rebuild") - .SetConfiguration("Release_OTEL") - .SetMaxCpuCount(Environment.ProcessorCount) - .SetNodeReuse(IsLocalBuild) - // enables building with OTEL in XInputBridge - .SetProperty("SCPLIB_ENABLE_TELEMETRY", true) - ); - } }); /// Support plugins are available for: diff --git a/build/_build.csproj b/build/_build.csproj index 1810d5f5..75492e73 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -15,7 +15,8 @@ - + + diff --git a/dshidmini.sln b/dshidmini.sln index eb8172f8..97b9bab7 100644 --- a/dshidmini.sln +++ b/dshidmini.sln @@ -23,6 +23,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitattributes = .gitattributes .gitignore = .gitignore appveyor.yml = appveyor.yml + Directory.Build.props = Directory.Build.props global.json = global.json LICENSE = LICENSE README.md = README.md @@ -39,6 +40,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DsHidMini.Installer", "setu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlApp", "ControlApp\ControlApp.csproj", "{AD47E724-2038-46EA-ACF9-C28B53D39A9A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ipctest", "ipctest\ipctest.csproj", "{45C7103C-2F57-45AD-84BA-1498BC9F21CC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SDK", "SDK", "{63280790-A828-4A50-B136-D7A4D0846808}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nefarius.DsHidMini.IPC", "SDK\Nefarius.DsHidMini.IPC\Nefarius.DsHidMini.IPC.csproj", "{52BDD811-0B9A-428D-96B6-496322D7398E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -151,6 +158,38 @@ Global {AD47E724-2038-46EA-ACF9-C28B53D39A9A}.Release|x64.Build.0 = Release|x64 {AD47E724-2038-46EA-ACF9-C28B53D39A9A}.Release|x86.ActiveCfg = Release|Any CPU {AD47E724-2038-46EA-ACF9-C28B53D39A9A}.Release|x86.Build.0 = Release|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Debug|ARM64.Build.0 = Debug|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Debug|x64.ActiveCfg = Debug|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Debug|x64.Build.0 = Debug|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Debug|x86.ActiveCfg = Debug|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Debug|x86.Build.0 = Debug|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Release|Any CPU.Build.0 = Release|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Release|ARM64.ActiveCfg = Release|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Release|ARM64.Build.0 = Release|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Release|x64.ActiveCfg = Release|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Release|x64.Build.0 = Release|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Release|x86.ActiveCfg = Release|Any CPU + {45C7103C-2F57-45AD-84BA-1498BC9F21CC}.Release|x86.Build.0 = Release|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Debug|ARM64.Build.0 = Debug|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Debug|x64.ActiveCfg = Debug|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Debug|x64.Build.0 = Debug|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Debug|x86.ActiveCfg = Debug|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Debug|x86.Build.0 = Debug|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Release|Any CPU.Build.0 = Release|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Release|ARM64.ActiveCfg = Release|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Release|ARM64.Build.0 = Release|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Release|x64.ActiveCfg = Release|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Release|x64.Build.0 = Release|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Release|x86.ActiveCfg = Release|Any CPU + {52BDD811-0B9A-428D-96B6-496322D7398E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -162,6 +201,8 @@ Global {FEB89FC0-BF9B-43F3-8467-B3496D61B76E} = {D5FFDFFE-55A3-4AF3-92A6-13F28598A5EE} {7D07C49F-A5A8-44AD-83B5-1E5B1F3080AA} = {58E023F1-01BB-4D75-90A5-2E6049F94048} {AD47E724-2038-46EA-ACF9-C28B53D39A9A} = {CE492389-7FB3-4DC4-9AFF-B7A04F70F891} + {45C7103C-2F57-45AD-84BA-1498BC9F21CC} = {CE492389-7FB3-4DC4-9AFF-B7A04F70F891} + {52BDD811-0B9A-428D-96B6-496322D7398E} = {63280790-A828-4A50-B136-D7A4D0846808} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5A4A580B-8A5D-4E6A-A75D-ADFDD46F6F37} diff --git a/dshidmini.sln.DotSettings b/dshidmini.sln.DotSettings index 15a23627..1daa2e6b 100644 --- a/dshidmini.sln.DotSettings +++ b/dshidmini.sln.DotSettings @@ -21,11 +21,13 @@ True True True + True True True True True True + True True True True @@ -38,11 +40,14 @@ True True True + True True True True True + True True + True True True True diff --git a/dshidmini_tvp.twx b/dshidmini_tvp.twx new file mode 100644 index 00000000..a9bd454a --- /dev/null +++ b/dshidmini_tvp.twx @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/include/DsHidMini/dshmguid.h b/include/DsHidMini/dshmguid.h index b883cb52..03f01326 100644 --- a/include/DsHidMini/dshmguid.h +++ b/include/DsHidMini/dshmguid.h @@ -31,6 +31,7 @@ DEFINE_DEVPROPKEY(DEVPKEY_DsHidMini_RO_BatteryStatus, 0x3fecf510, 0xcc94, 0x4fbe, 0x88, 0x39, 0x73, 0x82, 0x1, 0xf8, 0x4d, 0x59, 2); // DEVPROP_TYPE_BYTE // {3FECF510-CC94-4FBE-8839-738201F84D59} +__declspec(deprecated) DEFINE_DEVPROPKEY(DEVPKEY_DsHidMini_RO_LastPairingStatus, 0x3fecf510, 0xcc94, 0x4fbe, 0x88, 0x39, 0x73, 0x82, 0x1, 0xf8, 0x4d, 0x59, 3); // DEVPROP_TYPE_NTSTATUS diff --git a/ipctest/Program.cs b/ipctest/Program.cs new file mode 100644 index 00000000..ba671d96 --- /dev/null +++ b/ipctest/Program.cs @@ -0,0 +1,56 @@ +// See https://aka.ms/new-console-template for more information + +using System.Diagnostics; + +using Nefarius.DsHidMini.IPC; +#if INPUT_TEST +using Nefarius.DsHidMini.IPC.Models.Public; +#endif + +if (!DsHidMiniInterop.IsAvailable) +{ + Console.WriteLine("DsHidMini IPC not available, make sure that at least one controller is connected"); + return; +} + +using DsHidMiniInterop ipc = new(); + +#if INPUT_TEST +DS3_RAW_INPUT_REPORT report = new(); +#endif + +Stopwatch stopwatch = new(); + +do +{ + while (!Console.KeyAvailable) + { + int executionCount = 0; + + stopwatch.Restart(); + + while (stopwatch.ElapsedMilliseconds < 1000) + { +#if INPUT_TEST + bool success = ipc.GetRawInputReport(1, ref report, TimeSpan.FromMilliseconds(50)); + + if (success && report.Buttons.Cross) + { + Console.WriteLine("Cross pressed"); + } +#else + ipc.SendPing(); +#endif + + executionCount++; + } + + stopwatch.Stop(); + +#if INPUT_TEST + Console.WriteLine($"Read {executionCount} input reports in one second."); +#else + Console.WriteLine($"Executed {executionCount} PINGs in one second."); +#endif + } +} while (Console.ReadKey(true).Key != ConsoleKey.Escape); \ No newline at end of file diff --git a/ipctest/ipctest.csproj b/ipctest/ipctest.csproj new file mode 100644 index 00000000..0697d258 --- /dev/null +++ b/ipctest/ipctest.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0-windows + enable + enable + Nefarius.DsHidMini.IPC + true + false + + + + + + + \ No newline at end of file diff --git a/nuget.config b/nuget.config new file mode 100644 index 00000000..c3a4ad79 --- /dev/null +++ b/nuget.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/sys/Device.c b/sys/Device.c index 9529a318..cf28f5ee 100644 --- a/sys/Device.c +++ b/sys/Device.c @@ -9,7 +9,6 @@ EVT_DMF_DEVICE_MODULES_ADD DmfDeviceModulesAdd; EVT_WDF_DEVICE_CONTEXT_CLEANUP DsHidMini_DeviceCleanup; #pragma code_seg("PAGED") -DMF_DEFAULT_DRIVERCLEANUP(dshidminiEvtDriverContextCleanup) // // Bootstrap device @@ -216,6 +215,25 @@ void DsHidMini_DeviceCleanup( PAGED_CODE(); + const WDFDEVICE device = Object; + const PDEVICE_CONTEXT deviceContext = DeviceGetContext(device); + const WDFDRIVER driver = WdfGetDriver(); + const PDSHM_DRIVER_CONTEXT driverContext = DriverGetContext(driver); + + WdfWaitLockAcquire(driverContext->SlotsLock, NULL); + { + CLEAR_SLOT(driverContext, deviceContext->SlotIndex); + driverContext->IPC.DeviceDispatchers.Callbacks[deviceContext->SlotIndex] = NULL; + driverContext->IPC.DeviceDispatchers.Contexts[deviceContext->SlotIndex] = NULL; + } + WdfWaitLockRelease(driverContext->SlotsLock); + + const size_t offset = (sizeof(IPC_HID_INPUT_REPORT_MESSAGE) * (deviceContext->SlotIndex - 1)); + const PUCHAR pHIDBuffer = (driverContext->IPC.SharedRegions.HID.Buffer + offset); + + // zero out the slot so potential readers get notified we're gone + RtlZeroMemory(pHIDBuffer, sizeof(IPC_HID_INPUT_REPORT_MESSAGE)); + EventWriteUnloadEvent(Object); FuncExitNoReturn(TRACE_DEVICE); @@ -405,13 +423,50 @@ DsDevice_InitContext( ) { const PDEVICE_CONTEXT pDevCtx = DeviceGetContext(Device); - NTSTATUS status = STATUS_SUCCESS; + const PDSHM_DRIVER_CONTEXT pDrvCtx = DriverGetContext(WdfGetDriver()); + NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES; WDF_OBJECT_ATTRIBUTES attributes; PUCHAR outReportBuffer = NULL; WDF_TIMER_CONFIG timerCfg; FuncEntry(TRACE_DEVICE); + WdfWaitLockAcquire(pDrvCtx->SlotsLock, NULL); + { + // + // Get next free slot + // + for (UINT32 slotIndex = 1; slotIndex <= DSHM_MAX_DEVICES; slotIndex++) + { + if (!TEST_SLOT(pDrvCtx, slotIndex)) + { + SET_SLOT(pDrvCtx, slotIndex); + status = STATUS_SUCCESS; + + TraceVerbose( + TRACE_DEVICE, + "Claimed device slot: %d", + slotIndex + ); + + pDevCtx->SlotIndex = slotIndex; + pDrvCtx->IPC.DeviceDispatchers.Callbacks[slotIndex] = DSHM_EvtDispatchDeviceMessage; + pDrvCtx->IPC.DeviceDispatchers.Contexts[slotIndex] = pDevCtx; + break; + } + } + } + WdfWaitLockRelease(pDrvCtx->SlotsLock); + + if (!NT_SUCCESS(status)) + { + FuncExit(TRACE_DEVICE, "status=%!STATUS!", status); + + return status; + } + + // ReSharper disable once CppIncompleteSwitchStatement + // ReSharper disable once CppDefaultCaseNotHandledInSwitchStatement switch (pDevCtx->ConnectionType) { case DsDeviceConnectionTypeUsb: @@ -645,6 +700,63 @@ DsDevice_InitContext( break; } +#pragma region IPC + + SECURITY_DESCRIPTOR sd = { 0 }; + + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) + { + TraceError( + TRACE_IPC, + "InitializeSecurityDescriptor failed with error: %!WINERROR!", + GetLastError() + ); + EventWriteFailedWithWin32Error(__FUNCTION__, L"InitializeSecurityDescriptor", GetLastError()); + break; + } + + SECURITY_ATTRIBUTES sa = { 0 }; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = &sd; + + CHAR* szSD = "D:" // Discretionary ACL + "(D;OICI;GA;;;BG)" // Deny access to Built-in Guests + "(D;OICI;GA;;;AN)" // Deny access to Anonymous Logon + "(A;OICI;GRGWGX;;;AU)" // Allow read/write/execute to Authenticated Users + "(A;OICI;GA;;;BA)"; // Allow full control to Administrators + + if (!ConvertStringSecurityDescriptorToSecurityDescriptorA( + szSD, + SDDL_REVISION_1, + &sa.lpSecurityDescriptor, + NULL + )) + { + TraceError( + TRACE_IPC, + "ConvertStringSecurityDescriptorToSecurityDescriptor failed with error: %!WINERROR!", + GetLastError() + ); + EventWriteFailedWithWin32Error(__FUNCTION__, L"ConvertStringSecurityDescriptorToSecurityDescriptor", GetLastError()); + break; + } + + pDevCtx->IPC.InputReportWaitHandle = CreateEventA(&sa, FALSE, FALSE, NULL); + + if (pDevCtx->IPC.InputReportWaitHandle == NULL) + { + TraceError( + TRACE_IPC, + "CreateEventA failed with error: %!WINERROR!", + GetLastError() + ); + EventWriteFailedWithWin32Error(__FUNCTION__, L"CreateEventA", GetLastError()); + break; + } + +#pragma endregion + } while (FALSE); FuncExit(TRACE_DEVICE, "status=%!STATUS!", status); diff --git a/sys/Device.h b/sys/Device.h index f45b4c4e..4e14aea4 100644 --- a/sys/Device.h +++ b/sys/Device.h @@ -357,8 +357,33 @@ typedef struct _DEVICE_CONTEXT } RumbleControlState; + UINT32 SlotIndex; + + struct + { + HANDLE InputReportWaitHandle; + } IPC; + } DEVICE_CONTEXT, * PDEVICE_CONTEXT; +#include +// +// Describes a raw input report packet shared via IPC +// +typedef struct _IPC_HID_INPUT_REPORT_MESSAGE +{ + // + // One-based device index + // + UINT32 SlotIndex; + + // + // Input report copy + // + DS3_RAW_INPUT_REPORT InputReport; +} IPC_HID_INPUT_REPORT_MESSAGE, *PIPC_HID_INPUT_REPORT_MESSAGE; +#include + // // This macro will generate an inline function called DeviceGetContext // which will be used to get a pointer to the device context memory @@ -416,6 +441,8 @@ EVT_WDF_TIMER DSHM_OutputReportDelayTimerElapsed; EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL DSHM_EvtWdfIoQueueIoDeviceControl; +EVT_DSHM_IPC_DispatchDeviceMessage DSHM_EvtDispatchDeviceMessage; + NTSTATUS DsDevice_ReadProperties( WDFDEVICE Device diff --git a/sys/Driver.c b/sys/Driver.c index a946b139..0b984ad4 100644 --- a/sys/Driver.c +++ b/sys/Driver.c @@ -2,12 +2,83 @@ #include "Driver.tmh" -/*WPP_INIT_TRACING(); (This comment is necessary for WPP Scanner.)*/ #pragma code_seg("INIT") -DMF_DEFAULT_DRIVERENTRY(DriverEntry, - dshidminiEvtDriverContextCleanup, - dshidminiEvtDeviceAdd, - L"DsHidMini") +NTSTATUS +DriverEntry( + _In_ PDRIVER_OBJECT DriverObject, + _In_ PUNICODE_STRING RegistryPath +) +{ + WDF_DRIVER_CONFIG config; + WDF_OBJECT_ATTRIBUTES attributes; + + // + // Initialize WPP Tracing + // + WPP_INIT_TRACING(DriverObject, RegistryPath); + + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry"); + + // + // Register a cleanup callback so that we can call WPP_CLEANUP when + // the framework driver object is deleted during driver unload. + // + WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attributes, DSHM_DRIVER_CONTEXT); + attributes.EvtCleanupCallback = dshidminiEvtDriverContextCleanup; + + WDF_DRIVER_CONFIG_INIT( + &config, + dshidminiEvtDeviceAdd + ); + + WDFDRIVER driver = NULL; + + NTSTATUS status = WdfDriverCreate( + DriverObject, + RegistryPath, + &attributes, + &config, + &driver + ); + + if (!NT_SUCCESS(status)) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed with status %!STATUS!", status); + WPP_CLEANUP(DriverObject); + return status; + } + + if (!NT_SUCCESS(status = InitIPC())) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "InitIPC failed with status %!STATUS!", status); + WPP_CLEANUP(DriverObject); + return status; + } + + const PDSHM_DRIVER_CONTEXT context = DriverGetContext(driver); + + if (!NT_SUCCESS(status = WdfWaitLockCreate(WDF_NO_OBJECT_ATTRIBUTES, &context->SlotsLock))) + { + TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfWaitLockCreate failed with status %!STATUS!", status); + WPP_CLEANUP(DriverObject); + return status; + } + + TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit"); + + return status; +} +#pragma code_seg() + +#pragma code_seg("PAGED") +void dshidminiEvtDriverContextCleanup(WDFOBJECT DriverObject) +{ + UNREFERENCED_PARAMETER(DriverObject); + + DestroyIPC(); + + WPP_CLEANUP(WdfDriverWdmGetDriverObject( (WDFDRIVER) DriverObject)); +} #pragma code_seg() // diff --git a/sys/Driver.h b/sys/Driver.h index 14e6dd96..08d9a0ca 100644 --- a/sys/Driver.h +++ b/sys/Driver.h @@ -23,7 +23,14 @@ #ifdef DSHM_FEATURE_FFB #include "PID/PIDTypes.h" #endif + +// +// Artificial limit to make memory management easier (should be enough ;) +// +#define DSHM_MAX_DEVICES UCHAR_MAX + #include "Configuration.h" +#include "IPC.h" #include "Device.h" #include @@ -40,6 +47,140 @@ EXTERN_C_START +typedef struct _DSHM_DRIVER_CONTEXT +{ + // + // IPC-specific fields + // + struct + { + // + // Handle of the memory-mapped file + // + HANDLE MapFile; + + // + // Mutex handle to sync a connected client application + // + HANDLE ConnectMutex; + + // + // Read ready event handle + // + HANDLE ReadEvent; + + // + // Write ready event handle + // + HANDLE WriteEvent; + + // + // Dispatch thread handle + // + HANDLE DispatchThread; + + // + // Thread termination event + // + HANDLE DispatchThreadTermination; + + // + // Shared memory regions details + // + struct + { + // + // Low frequency, low lifetime command exchange region + // + struct + { + // + // Pointer to shared memory buffer + // + PUCHAR Buffer; + + // + // Total size of shared memory region + // + size_t BufferSize; + } Commands; + + // + // High-frequency, high throughput read-only region + struct + { + // + // Pointer to shared memory buffer + // + PUCHAR Buffer; + + // + // Total size of shared memory region + // + size_t BufferSize; + } HID; + } SharedRegions; + + // + // Message dispatcher callback handlers + // + struct + { + PFN_DSHM_IPC_DispatchDeviceMessage Callbacks[DSHM_MAX_DEVICES]; + + PDEVICE_CONTEXT Contexts[DSHM_MAX_DEVICES]; + } DeviceDispatchers; + } IPC; + + // + // Index slots to associate connected devices in IPC + // + LONG Slots[8]; // 256 usable bits + + // + // Lock protecting access to Slots + // + WDFWAITLOCK SlotsLock; +} DSHM_DRIVER_CONTEXT, * PDSHM_DRIVER_CONTEXT; + +WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(DSHM_DRIVER_CONTEXT, DriverGetContext) + +LONG +FORCEINLINE +SET_SLOT( + PDSHM_DRIVER_CONTEXT Context, + UINT32 SlotIndex +) +{ + const UINT32 bits = sizeof(Context->Slots); + + return InterlockedOr(&Context->Slots[SlotIndex / bits], 1 << (SlotIndex % bits)); +} + +BOOLEAN +FORCEINLINE +TEST_SLOT( + PDSHM_DRIVER_CONTEXT Context, + UINT32 SlotIndex +) +{ + const UINT32 bits = sizeof(Context->Slots); + + return (BOOLEAN)(Context->Slots[SlotIndex / bits] & (1 << (SlotIndex % bits))); +} + +LONG +FORCEINLINE +CLEAR_SLOT( + PDSHM_DRIVER_CONTEXT Context, + UINT32 SlotIndex +) +{ + const UINT32 bits = sizeof(Context->Slots); + + return InterlockedAnd(&Context->Slots[SlotIndex / bits], ~(1 << (SlotIndex % bits))); +} + // // WDFDRIVER Events // diff --git a/sys/Ds3.c b/sys/Ds3.c index f5e5094d..320770fd 100644 --- a/sys/Ds3.c +++ b/sys/Ds3.c @@ -411,7 +411,6 @@ NTSTATUS DS3_GetActiveRadioAddress(BD_ADDR* Address) NTSTATUS DsUsb_Ds3PairToNewHost(WDFDEVICE Device) { NTSTATUS status = STATUS_UNSUCCESSFUL; - WDF_DEVICE_PROPERTY_DATA propertyData; const PDEVICE_CONTEXT pDevCtx = DeviceGetContext(Device); BD_ADDR newHostAddress = { 0 }; @@ -500,28 +499,6 @@ NTSTATUS DsUsb_Ds3PairToNewHost(WDFDEVICE Device) } while (FALSE); - WDF_DEVICE_PROPERTY_DATA_INIT(&propertyData, &DEVPKEY_DsHidMini_RO_LastPairingStatus); - propertyData.Flags |= PLUGPLAY_PROPERTY_PERSISTENT; - propertyData.Lcid = LOCALE_NEUTRAL; - - // - // Store in property - // - if (!NT_SUCCESS(status = WdfDeviceAssignProperty( - Device, - &propertyData, - DEVPROP_TYPE_NTSTATUS, - sizeof(NTSTATUS), - &status - ))) - { - TraceError( - TRACE_DS3, - "Setting DEVPKEY_DsHidMini_RO_LastPairingStatus failed with status %!STATUS!", - status - ); - } - FuncExit(TRACE_DS3, "status=%!STATUS!", status); return status; diff --git a/sys/IPC.Device.c b/sys/IPC.Device.c new file mode 100644 index 00000000..5e82b346 --- /dev/null +++ b/sys/IPC.Device.c @@ -0,0 +1,87 @@ +#include "Driver.h" +#include "IPC.Device.tmh" + + +// +// Processes incoming IPC messages targeted to this device instance +// +_Use_decl_annotations_ +NTSTATUS +DSHM_EvtDispatchDeviceMessage( + _In_ PDEVICE_CONTEXT DeviceContext, + _In_ PDSHM_IPC_MSG_HEADER MessageHeader +) +{ + FuncEntry(TRACE_IPC); + + NTSTATUS status = STATUS_NOT_IMPLEMENTED; + + if (MessageHeader->Command.Device == DSHM_IPC_MSG_CMD_DEVICE_PAIR_TO) + { + const PDSHM_IPC_MSG_PAIR_TO_REQUEST request = (PDSHM_IPC_MSG_PAIR_TO_REQUEST)MessageHeader; + + TraceVerbose( + TRACE_IPC, + "Received pairing request, new host address: %02X:%02X:%02X:%02X:%02X:%02X", + request->Address.Address[0], + request->Address.Address[1], + request->Address.Address[2], + request->Address.Address[3], + request->Address.Address[4], + request->Address.Address[5] + ); + + // TODO: this changes state for runtime settings, improve + DeviceContext->Configuration.DevicePairingMode = DsDevicePairingModeCustom; + RtlCopyMemory(&DeviceContext->Configuration.CustomHostAddress, &request->Address, sizeof(BD_ADDR)); + + const WDFDEVICE device = WdfObjectContextGetObject(DeviceContext); + + NTSTATUS writeStatus = DsUsb_Ds3PairToNewHost(device); + NTSTATUS readStatus = STATUS_UNSUCCESSFUL; + if (!NT_SUCCESS(readStatus = DsUsb_Ds3RequestHostAddress(device))) + { + TraceError( + TRACE_IPC, + "DsUsb_Ds3RequestHostAddress failed with status %!STATUS!", + readStatus + ); + } + + DSHM_IPC_MSG_PAIR_TO_RESPONSE_INIT( + (PDSHM_IPC_MSG_PAIR_TO_REPLY)MessageHeader, + MessageHeader->TargetIndex, + writeStatus, + readStatus + ); + + status = STATUS_SUCCESS; + } + else if (MessageHeader->Command.Device == DSHM_IPC_MSG_CMD_DEVICE_SET_PLAYER_INDEX) + { + // TODO: implement me! + + DSHM_IPC_MSG_SET_PLAYER_INDEX_RESPONSE_INIT( + (PDSHM_IPC_MSG_SET_PLAYER_INDEX_REPLY)MessageHeader, + MessageHeader->TargetIndex, + STATUS_NOT_IMPLEMENTED + ); + + status = STATUS_SUCCESS; + } + else if (MessageHeader->Command.Device == DSHM_IPC_MSG_CMD_DEVICE_GET_HID_WAIT_HANDLE) + { + DSHM_IPC_MSG_GET_HID_WAIT_HANDLE_RESPONSE_INIT( + (PDSHM_IPC_MSG_GET_HID_WAIT_HANDLE_RESPONSE)MessageHeader, + MessageHeader->TargetIndex, + GetCurrentProcessId(), + DeviceContext->IPC.InputReportWaitHandle + ); + + status = STATUS_SUCCESS; + } + + FuncExit(TRACE_IPC, "status=%!STATUS!", status); + + return status; +} diff --git a/sys/IPC.c b/sys/IPC.c new file mode 100644 index 00000000..e51d9199 --- /dev/null +++ b/sys/IPC.c @@ -0,0 +1,456 @@ +#include "Driver.h" +#include "IPC.tmh" + + +static DWORD WINAPI DSHM_IPC_ClientDispatchProc( + _In_ LPVOID lpParameter +); + +NTSTATUS InitIPC(void) +{ + FuncEntry(TRACE_IPC); + + const WDFDRIVER driver = WdfGetDriver(); + const PDSHM_DRIVER_CONTEXT context = DriverGetContext(driver); + + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + DWORD pageSize = sysInfo.dwAllocationGranularity; // Usually 4096 bytes (4KB) + + DWORD cmdRegionSize = pageSize; + DWORD hidRegionSize = pageSize; + DWORD totalRegionSize = cmdRegionSize + hidRegionSize; + + TraceVerbose( + TRACE_IPC, + "pageSize = %d, cmdRegionSize = %d, hidRegionSize = %d, totalRegionSize = %d", + pageSize, cmdRegionSize, hidRegionSize, totalRegionSize + ); + + PUCHAR pCmdBuf = NULL; + PUCHAR pHIDBuf = NULL; + HANDLE hReadEvent = NULL; + HANDLE hWriteEvent = NULL; + HANDLE hMapFile = NULL; + HANDLE hMutex = NULL; + HANDLE hThread = NULL; + HANDLE hThreadTermination = NULL; + + SECURITY_DESCRIPTOR sd = { 0 }; + + if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) + { + TraceError( + TRACE_IPC, + "InitializeSecurityDescriptor failed with error: %!WINERROR!", + GetLastError() + ); + goto exitFailure; + } + + SECURITY_ATTRIBUTES sa = { 0 }; + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = &sd; + + CHAR* szSD = "D:" // Discretionary ACL + "(D;OICI;GA;;;BG)" // Deny access to Built-in Guests + "(D;OICI;GA;;;AN)" // Deny access to Anonymous Logon + "(A;OICI;GRGWGX;;;AU)" // Allow read/write/execute to Authenticated Users + "(A;OICI;GA;;;BA)"; // Allow full control to Administrators + + if (!ConvertStringSecurityDescriptorToSecurityDescriptorA( + szSD, + SDDL_REVISION_1, + &sa.lpSecurityDescriptor, + NULL + )) + { + TraceError( + TRACE_IPC, + "ConvertStringSecurityDescriptorToSecurityDescriptor failed with error: %!WINERROR!", + GetLastError() + ); + goto exitFailure; + } + + hMutex = CreateMutexA(&sa, FALSE, DSHM_IPC_MUTEX_NAME); + if (hMutex == NULL) + { + TraceError( + TRACE_IPC, + "Could not create mutex (%!WINERROR!).", + GetLastError() + ); + goto exitFailure; + } + + // Create a named event for signaling + hReadEvent = CreateEventA(&sa, FALSE, FALSE, DSHM_IPC_READ_EVENT_NAME); + if (hReadEvent == NULL) + { + TraceError( + TRACE_IPC, + "Could not create READ event (%!WINERROR!).", + GetLastError() + ); + goto exitFailure; + } + + hWriteEvent = CreateEventA(&sa, FALSE, FALSE, DSHM_IPC_WRITE_EVENT_NAME); + if (hWriteEvent == NULL) + { + TraceError( + TRACE_IPC, + "Could not create WRITE event (%!WINERROR!).", + GetLastError() + ); + goto exitFailure; + } + + hThreadTermination = CreateEventA(&sa, FALSE, FALSE, NULL); + if (hThreadTermination == NULL) + { + TraceError( + TRACE_IPC, + "Could not create event (%!WINERROR!).", + GetLastError() + ); + goto exitFailure; + } + + // Create a memory-mapped file + hMapFile = CreateFileMappingA( + INVALID_HANDLE_VALUE, // use paging file + &sa, // custom security + PAGE_READWRITE, // read/write access + 0, // maximum object size (high-order DWORD) + totalRegionSize, // maximum object size (low-order DWORD) + DSHM_IPC_FILE_MAP_NAME // name of mapping object + ); + + if (hMapFile == NULL) + { + TraceError( + TRACE_IPC, + "Could not create file mapping object (%!WINERROR!).", + GetLastError() + ); + goto exitFailure; + } + + // Map a view of the file in the calling process's address space + pCmdBuf = MapViewOfFile( + hMapFile, // handle to map object + FILE_MAP_ALL_ACCESS, // read/write permission + 0, + 0, + cmdRegionSize + ); + + if (pCmdBuf == NULL) + { + TraceError( + TRACE_IPC, + "Could not map view of file CMD REGION (%!WINERROR!).", + GetLastError() + ); + goto exitFailure; + } + + // Calculate the nearest page-aligned offset for the HID region + DWORD alignedOffset = (cmdRegionSize / pageSize) * pageSize; // Page-aligned offset + DWORD offsetWithinPage = cmdRegionSize % pageSize; // Offset within the mapped page + + pHIDBuf = MapViewOfFile( + hMapFile, // handle to map object + FILE_MAP_ALL_ACCESS, // read/write permission + 0, + alignedOffset, + hidRegionSize + offsetWithinPage + ); + + if (pHIDBuf == NULL) + { + TraceError( + TRACE_IPC, + "Could not map view of file HID REGION (%!WINERROR!).", + GetLastError() + ); + goto exitFailure; + } + + context->IPC.DispatchThreadTermination = hThreadTermination; + context->IPC.MapFile = hMapFile; + context->IPC.ConnectMutex = hMutex; + context->IPC.ReadEvent = hReadEvent; + context->IPC.WriteEvent = hWriteEvent; + + context->IPC.SharedRegions.Commands.Buffer = pCmdBuf; + context->IPC.SharedRegions.Commands.BufferSize = cmdRegionSize; + + context->IPC.SharedRegions.HID.Buffer = pHIDBuf; + context->IPC.SharedRegions.HID.BufferSize = hidRegionSize; + + // + // Start thread now that context is initialized at its minimum requirement + // + hThread = CreateThread( + NULL, + 0, + DSHM_IPC_ClientDispatchProc, + context, + 0, + NULL + ); + + if (hThread == NULL) + { + TraceError( + TRACE_IPC, + "Could not create dispatch thread (%!WINERROR!).", + GetLastError() + ); + goto exitFailure; + } + + context->IPC.DispatchThread = hThread; + + FuncExitNoReturn(TRACE_IPC); + + return STATUS_SUCCESS; + +exitFailure: + if (pCmdBuf) + UnmapViewOfFile(pCmdBuf); + + if (pHIDBuf) + UnmapViewOfFile(pHIDBuf); + + if (hReadEvent) + CloseHandle(hReadEvent); + + if (hWriteEvent) + CloseHandle(hWriteEvent); + + if (hMapFile) + CloseHandle(hMapFile); + + if (hMutex) + CloseHandle(hMutex); + + if (hThread) + CloseHandle(hThread); + + if (hThreadTermination) + CloseHandle(hThreadTermination); + + const NTSTATUS status = NTSTATUS_FROM_WIN32(GetLastError()); + + FuncExit(TRACE_IPC, "status=%!STATUS!", status); + + return status; +} + +void DestroyIPC(void) +{ + FuncEntry(TRACE_IPC); + + const WDFDRIVER driver = WdfGetDriver(); + const PDSHM_DRIVER_CONTEXT context = DriverGetContext(driver); + + // + // Thread running; signal termination, wait on exit, free resources + // + if (context->IPC.DispatchThread && context->IPC.DispatchThreadTermination) + { + SetEvent(context->IPC.DispatchThreadTermination); + WaitForSingleObject(context->IPC.DispatchThread, 500); + CloseHandle(context->IPC.DispatchThread); + CloseHandle(context->IPC.DispatchThreadTermination); + } + + if (context->IPC.SharedRegions.Commands.Buffer) + UnmapViewOfFile(context->IPC.SharedRegions.Commands.Buffer); + + if (context->IPC.SharedRegions.HID.Buffer) + UnmapViewOfFile(context->IPC.SharedRegions.HID.Buffer); + + if (context->IPC.MapFile) + CloseHandle(context->IPC.MapFile); + + if (context->IPC.ReadEvent) + CloseHandle(context->IPC.ReadEvent); + + if (context->IPC.WriteEvent) + CloseHandle(context->IPC.WriteEvent); + + if (context->IPC.ConnectMutex) + CloseHandle(context->IPC.ConnectMutex); + + FuncExitNoReturn(TRACE_IPC); +} + +static NTSTATUS DSHM_IPC_DispatchIncomingCommandMessage( + _In_ const PDSHM_DRIVER_CONTEXT Context, + _In_ const PDSHM_IPC_MSG_HEADER Message +) +{ + FuncEntry(TRACE_IPC); + + NTSTATUS status = STATUS_NOT_IMPLEMENTED; + + // + // Sanity check + // + if (Message->Size < sizeof(DSHM_IPC_MSG_HEADER)) + { + return STATUS_INVALID_USER_BUFFER; + } + + // + // Message outside of region bounds + // + if (Message->Size > Context->IPC.SharedRegions.Commands.BufferSize) + { + return STATUS_BUFFER_OVERFLOW; + } + + // + // Incoming PING message + // + if (DSHM_IPC_MSG_IS_PING(Message)) + { + TraceVerbose( + TRACE_IPC, + "IPC: PING message received" + ); + + const PDSHM_IPC_MSG_HEADER header = (PDSHM_IPC_MSG_HEADER)Context->IPC.SharedRegions.Commands.Buffer; + + DSHM_IPC_MSG_PING_RESPONSE_INIT(header); + + DSHM_IPC_SIGNAL_WRITE_DONE(Context); + + status = STATUS_SUCCESS; + } + + // + // Message is for a device instance + // + if (DSHM_IPC_MSG_IS_FOR_DEVICE(Message)) + { + const BOOLEAN expectsReply = DSHM_IPC_MSG_EXPECTS_REPLY(Message); + const PDEVICE_CONTEXT deviceContext = Context->IPC.DeviceDispatchers.Contexts[Message->TargetIndex]; + const PFN_DSHM_IPC_DispatchDeviceMessage callback = Context->IPC.DeviceDispatchers.Callbacks[Message->TargetIndex]; + + // + // Proxy the message dispatching to the targeted device instance + // + if (callback && deviceContext) + { + status = callback(deviceContext, Message); + } + else + { + TraceWarning( + TRACE_IPC, + "Device with index %d has no valid callback or context assigned", + Message->TargetIndex + ); + } + + if (expectsReply) + { + DSHM_IPC_SIGNAL_WRITE_DONE(Context); + } + } + + FuncExit(TRACE_IPC, "status=%!STATUS!", status); + + return status; +} + +// +// Listens for client connection and processes data exchange +// +static DWORD WINAPI DSHM_IPC_ClientDispatchProc( + _In_ LPVOID lpParameter +) +{ + FuncEntry(TRACE_IPC); + + const PDSHM_DRIVER_CONTEXT context = lpParameter; + + const HANDLE waits[] = { + // driver shutdown signals thread termination + context->IPC.DispatchThreadTermination, + // read is signaled when an outside app has finished writing + context->IPC.ReadEvent + }; + + do + { + DWORD waitResult = WaitForMultipleObjects(ARRAYSIZE(waits), waits, FALSE, 1000); + + // + // Retry indefinitely until an event is signaled + // + if (waitResult == WAIT_TIMEOUT) + continue; + + // + // Unexpected result + // + if (waitResult == WAIT_FAILED) + { + TraceError( + TRACE_IPC, + "Wait failed with error %!WINERROR!", + GetLastError() + ); + break; + } + + // + // Thread termination signaled, exiting + // + if (waitResult == WAIT_OBJECT_0) + { + break; + } + + // + // New data is available in the shared region + // + if (waitResult == WAIT_OBJECT_0 + 1) + { + // + // Each valid message is expected to be prefixed with this header + // + const PDSHM_IPC_MSG_HEADER header = (PDSHM_IPC_MSG_HEADER)context->IPC.SharedRegions.Commands.Buffer; + + TraceInformation( + TRACE_IPC, + "Got message type %d for target %d and command %d", + header->Type, header->Target, header->Command.Device + ); + + NTSTATUS status = DSHM_IPC_DispatchIncomingCommandMessage(context, header); + + if (!NT_SUCCESS(status)) + { + TraceError( + TRACE_IPC, + "DSHM_IPC_DispatchIncomingCommandMessage reported non-success status %!STATUS!", + status + ); + } + } + + } while (TRUE); + + FuncExitNoReturn(TRACE_IPC); + + return ERROR_SUCCESS; +} diff --git a/sys/IPC.h b/sys/IPC.h new file mode 100644 index 00000000..db515455 --- /dev/null +++ b/sys/IPC.h @@ -0,0 +1,326 @@ +#pragma once + +#define DSHM_IPC_FILE_MAP_NAME "Global\\DsHidMiniSharedMemory" +#define DSHM_IPC_MUTEX_NAME "Global\\DsHidMiniCommandMutex" +#define DSHM_IPC_READ_EVENT_NAME "Global\\DsHidMiniReadEvent" +#define DSHM_IPC_WRITE_EVENT_NAME "Global\\DsHidMiniWriteEvent" + + +// +// Describes the type of IPC message response behavior +// +typedef enum +{ + // + // Invalid/reserved, do not use + // + DSHM_IPC_MSG_TYPE_INVALID = 0, + // + // Client-to-driver data incoming, no acknowledgment/reply requested + // + DSHM_IPC_MSG_TYPE_REQUEST_ONLY, + // + // Client-to-driver data incoming, must be acknowledged by reply + // + DSHM_IPC_MSG_TYPE_REQUEST_RESPONSE, + // + // Client requested data, there is nothing to read for the driver + // + DSHM_IPC_MSG_TYPE_RESPONSE_ONLY, + // + // Driver-to-client response to a previous DSHM_IPC_MSG_TYPE_REQUEST_RESPONSE + // + DSHM_IPC_MSG_TYPE_REQUEST_REPLY +} DSHM_IPC_MSG_TYPE; + +// +// Describes the message receiver +// +typedef enum +{ + // + // Invalid/reserved, do not use + // + DSHM_IPC_MSG_TARGET_INVALID = 0, + // + // The message is targeted at the driver + // + DSHM_IPC_MSG_TARGET_DRIVER, + // + // The message is targeted at a device + // + DSHM_IPC_MSG_TARGET_DEVICE, + // + // The message is targeted at the client/caller/app + // + DSHM_IPC_MSG_TARGET_CLIENT +} DSHM_IPC_MSG_TARGET; + +// +// Describes a per-driver command +// +typedef enum +{ + // + // Invalid/reserved, do not use + // + DSHM_IPC_MSG_CMD_DRIVER_INVALID = 0, + // + // Message without payload, useful to check for functionality + // + DSHM_IPC_MSG_CMD_DRIVER_PING +} DSHM_IPC_MSG_CMD_DRIVER; + +// +// Describes a per-device command +// +typedef enum +{ + // + // Invalid/reserved, do not use + // + DSHM_IPC_MSG_CMD_DEVICE_INVALID = 0, + // + // Pair a given device to a new host + // + DSHM_IPC_MSG_CMD_DEVICE_PAIR_TO, + // + // Requests a player index update (switch player LED etc.) + // + DSHM_IPC_MSG_CMD_DEVICE_SET_PLAYER_INDEX, + // + // Requests a wait handle for input report state changes + // + DSHM_IPC_MSG_CMD_DEVICE_GET_HID_WAIT_HANDLE, +} DSHM_IPC_MSG_CMD_DEVICE; + +// +// Prefix of every packet describing the message +// +typedef struct _DSHM_IPC_MSG_HEADER +{ + // + // What request-behavior is expected (request, request-reply, ...) + // + DSHM_IPC_MSG_TYPE Type; + + // + // What component is this message targeting (driver, device, ...) + // + DSHM_IPC_MSG_TARGET Target; + + // + // What command is this message carrying + // + union + { + DSHM_IPC_MSG_CMD_DRIVER Driver; + + DSHM_IPC_MSG_CMD_DEVICE Device; + } Command; + + // + // One-based index of which device is this message for + // Set to 0 if driver is targeted + // + UINT32 TargetIndex; + + // + // The size of the entire message (header + payload) in bytes + // A size of 0 is invalid + // + UINT32 Size; +} DSHM_IPC_MSG_HEADER, * PDSHM_IPC_MSG_HEADER; + +// +// Updates a specified devices' host address +// +typedef struct _DSHM_IPC_MSG_PAIR_TO_REQUEST +{ + DSHM_IPC_MSG_HEADER Header; + + BD_ADDR Address; + +} DSHM_IPC_MSG_PAIR_TO_REQUEST, *PDSHM_IPC_MSG_PAIR_TO_REQUEST; + +// +// Reply to struct _DSHM_IPC_MSG_PAIR_TO_REQUEST +// +typedef struct _DSHM_IPC_MSG_PAIR_TO_REPLY +{ + DSHM_IPC_MSG_HEADER Header; + + // + // NTSTATUS of the set address action + // + NTSTATUS WriteStatus; + + // + // NTSTATUS of the get address action + // + NTSTATUS ReadStatus; + +} DSHM_IPC_MSG_PAIR_TO_REPLY, *PDSHM_IPC_MSG_PAIR_TO_REPLY; + +// +// Updates the player index of a given device +// +typedef struct _DSHM_IPC_MSG_SET_PLAYER_INDEX_REQUEST +{ + DSHM_IPC_MSG_HEADER Header; + + // + // The new player index to set + // Valid values are 1 to 7 + // + BYTE PlayerIndex; + +} DSHM_IPC_MSG_SET_PLAYER_INDEX_REQUEST, *PDSHM_IPC_MSG_SET_PLAYER_INDEX_REQUEST; + +// +// Reply to struct _DSHM_IPC_MSG_SET_PLAYER_INDEX_REQUEST +// +typedef struct _DSHM_IPC_MSG_SET_PLAYER_INDEX_REPLY +{ + DSHM_IPC_MSG_HEADER Header; + + NTSTATUS NtStatus; + +} DSHM_IPC_MSG_SET_PLAYER_INDEX_REPLY, *PDSHM_IPC_MSG_SET_PLAYER_INDEX_REPLY; + +// +// Requests the driver host process PID and a wait handle for new input reports +// +typedef struct _DSHM_IPC_MSG_GET_HID_WAIT_HANDLE_RESPONSE +{ + DSHM_IPC_MSG_HEADER Header; + + // + // The driver hosting process PID + // + DWORD ProcessId; + + // + // A handle to an auto-reset event + // + HANDLE WaitHandle; + +} DSHM_IPC_MSG_GET_HID_WAIT_HANDLE_RESPONSE, *PDSHM_IPC_MSG_GET_HID_WAIT_HANDLE_RESPONSE; + +typedef +_Function_class_(EVT_DSHM_IPC_DispatchDeviceMessage) +_IRQL_requires_same_ +_IRQL_requires_max_(PASSIVE_LEVEL) +NTSTATUS +EVT_DSHM_IPC_DispatchDeviceMessage( + _In_ PDEVICE_CONTEXT DeviceContext, + _In_ PDSHM_IPC_MSG_HEADER MessageHeader +); + +typedef EVT_DSHM_IPC_DispatchDeviceMessage *PFN_DSHM_IPC_DispatchDeviceMessage; + +#define DSHM_IPC_SIGNAL_WRITE_DONE(_ctx_) \ + SetEvent((_ctx_)->IPC.WriteEvent) + +#define DSHM_IPC_MSG_EXPECTS_REPLY(_msg_) \ + ((_msg_)->Type == DSHM_IPC_MSG_TYPE_REQUEST_RESPONSE \ + || (_msg_)->Type == DSHM_IPC_MSG_TYPE_RESPONSE_ONLY) + +#define DSHM_IPC_MSG_IS_PING(_msg_) \ + ((_msg_)->Type == DSHM_IPC_MSG_TYPE_REQUEST_RESPONSE \ + && (_msg_)->Target == DSHM_IPC_MSG_TARGET_DRIVER \ + && (_msg_)->Command.Driver == DSHM_IPC_MSG_CMD_DRIVER_PING \ + && (_msg_)->TargetIndex == 0 \ + && (_msg_)->Size == sizeof(DSHM_IPC_MSG_HEADER)) + +#define DSHM_IPC_MSG_IS_FOR_DEVICE(_msg_) \ + ((_msg_)->Type != DSHM_IPC_MSG_TYPE_INVALID \ + && (_msg_)->Target == DSHM_IPC_MSG_TARGET_DEVICE \ + && (_msg_)->Command.Device != DSHM_IPC_MSG_CMD_DEVICE_INVALID \ + && (_msg_)->TargetIndex > 0 \ + && (_msg_)->TargetIndex < DSHM_MAX_DEVICES \ + && (_msg_)->Size >= sizeof(DSHM_IPC_MSG_HEADER)) + +VOID +FORCEINLINE +DSHM_IPC_MSG_PING_RESPONSE_INIT( + _Inout_ PDSHM_IPC_MSG_HEADER Message +) +{ + RtlZeroMemory(Message, sizeof(DSHM_IPC_MSG_HEADER)); + + Message->Type = DSHM_IPC_MSG_TYPE_REQUEST_REPLY; + Message->Target = DSHM_IPC_MSG_TARGET_CLIENT; + Message->Command.Driver = DSHM_IPC_MSG_CMD_DRIVER_PING; + Message->TargetIndex = 0; + Message->Size = sizeof(DSHM_IPC_MSG_HEADER); // no payload +} + +VOID +FORCEINLINE +DSHM_IPC_MSG_PAIR_TO_RESPONSE_INIT( + _Inout_ PDSHM_IPC_MSG_PAIR_TO_REPLY Message, + _In_ UINT32 DeviceIndex, + _In_ NTSTATUS WriteStatus, + _In_ NTSTATUS ReadStatus +) +{ + const UINT32 size = sizeof(DSHM_IPC_MSG_PAIR_TO_REPLY); + RtlZeroMemory(Message, size); + + Message->Header.Type = DSHM_IPC_MSG_TYPE_REQUEST_REPLY; + Message->Header.Target = DSHM_IPC_MSG_TARGET_CLIENT; + Message->Header.Command.Device = DSHM_IPC_MSG_CMD_DEVICE_PAIR_TO; + Message->Header.TargetIndex = DeviceIndex; + Message->Header.Size = size; + + Message->WriteStatus = WriteStatus; + Message->ReadStatus = ReadStatus; +} + +VOID +FORCEINLINE +DSHM_IPC_MSG_SET_PLAYER_INDEX_RESPONSE_INIT( + _Inout_ PDSHM_IPC_MSG_SET_PLAYER_INDEX_REPLY Message, + _In_ UINT32 DeviceIndex, + _In_ NTSTATUS Status +) +{ + const UINT32 size = sizeof(DSHM_IPC_MSG_SET_PLAYER_INDEX_REPLY); + RtlZeroMemory(Message, size); + + Message->Header.Type = DSHM_IPC_MSG_TYPE_REQUEST_REPLY; + Message->Header.Target = DSHM_IPC_MSG_TARGET_CLIENT; + Message->Header.Command.Device = DSHM_IPC_MSG_CMD_DEVICE_PAIR_TO; + Message->Header.TargetIndex = DeviceIndex; + Message->Header.Size = size; + + Message->NtStatus = Status; +} + +VOID +FORCEINLINE +DSHM_IPC_MSG_GET_HID_WAIT_HANDLE_RESPONSE_INIT( + _Inout_ PDSHM_IPC_MSG_GET_HID_WAIT_HANDLE_RESPONSE Message, + _In_ UINT32 DeviceIndex, + _In_ DWORD ProcessId, + _In_opt_ HANDLE WaitHandle +) +{ + const UINT32 size = sizeof(DSHM_IPC_MSG_GET_HID_WAIT_HANDLE_RESPONSE); + RtlZeroMemory(Message, size); + + Message->Header.Type = DSHM_IPC_MSG_TYPE_RESPONSE_ONLY; + Message->Header.Target = DSHM_IPC_MSG_TARGET_CLIENT; + Message->Header.Command.Device = DSHM_IPC_MSG_CMD_DEVICE_GET_HID_WAIT_HANDLE; + Message->Header.TargetIndex = DeviceIndex; + Message->Header.Size = size; + + Message->ProcessId = ProcessId; + Message->WaitHandle = WaitHandle; +} + + +NTSTATUS InitIPC(void); + +void DestroyIPC(void); diff --git a/sys/InputReport.c b/sys/InputReport.c index ef623c9d..856d241f 100644 --- a/sys/InputReport.c +++ b/sys/InputReport.c @@ -2,6 +2,9 @@ #include "InputReport.tmh" +// +// Protocol-agnostic function that transforms the raw input report to HID-mode-compatible ones +// _Use_decl_annotations_ void DSHM_ParseInputReport( @@ -12,6 +15,31 @@ DSHM_ParseInputReport( { FuncEntry(TRACE_DSHIDMINIDRV); +#pragma region IPC Copy + + const WDFDRIVER driver = WdfGetDriver(); + const PDSHM_DRIVER_CONTEXT pDrvCtx = DriverGetContext(driver); + /* + * Offset calculation puts each devices' input report copy + * in their respective position in the memory region, like: + * 1st device: ((4 + 49) * (1 - 1)) = 0 + * 2nd device: ((4 + 49) * (2 - 1)) = 53 + * 3rd device: ((4 + 49) * (3 - 1)) = 106 + * and so on + */ + const size_t offset = (sizeof(IPC_HID_INPUT_REPORT_MESSAGE) * (DeviceContext->SlotIndex - 1)); + const PIPC_HID_INPUT_REPORT_MESSAGE pHIDBuffer = (PIPC_HID_INPUT_REPORT_MESSAGE)(pDrvCtx->IPC.SharedRegions.HID.Buffer + offset); + + // prefix each report with associated device index + pHIDBuffer->SlotIndex = DeviceContext->SlotIndex; + // skip index and copy unmodified raw report to the section + RtlCopyMemory(&pHIDBuffer->InputReport, Report, sizeof(DS3_RAW_INPUT_REPORT)); + + // signal any reader that there is new data available + SetEvent(DeviceContext->IPC.InputReportWaitHandle); + +#pragma endregion + #pragma region HID Input Report (SDF, GPJ ID 01) processing switch (DeviceContext->Configuration.HidDeviceMode) // NOLINT(clang-diagnostic-switch-enum) diff --git a/sys/Trace.h b/sys/Trace.h index 93e708d8..eb4af983 100644 --- a/sys/Trace.h +++ b/sys/Trace.h @@ -14,6 +14,7 @@ WPP_DEFINE_BIT(TRACE_DS3) \ WPP_DEFINE_BIT(TRACE_DSBTH) \ WPP_DEFINE_BIT(TRACE_CONFIG) \ + WPP_DEFINE_BIT(TRACE_IPC) \ ) \ #define WPP_FLAG_LEVEL_LOGGER(flag, level) \ diff --git a/sys/dshidmini.inf b/sys/dshidmini.inf index adf8b073..f7f2c2e5 100644 Binary files a/sys/dshidmini.inf and b/sys/dshidmini.inf differ diff --git a/sys/dshidmini.vcxproj b/sys/dshidmini.vcxproj index dc60ac4f..af66d7be 100644 --- a/sys/dshidmini.vcxproj +++ b/sys/dshidmini.vcxproj @@ -31,6 +31,8 @@ + + @@ -59,6 +61,7 @@ + diff --git a/sys/dshidmini.vcxproj.filters b/sys/dshidmini.vcxproj.filters index f3414f81..1edd3242 100644 --- a/sys/dshidmini.vcxproj.filters +++ b/sys/dshidmini.vcxproj.filters @@ -171,6 +171,9 @@ Header Files + + Header Files +
@@ -224,6 +227,12 @@ Source Files + + Source Files + + + Source Files +