using System.ComponentModel; using System.Runtime.InteropServices; using System.Security.Cryptography; using Microsoft.Win32.SafeHandles; namespace XFEExtension.NetCore.InputSimulator.Native; internal sealed unsafe partial class DriverTransport : IDriverTransport { private readonly SafeFileHandle handle; private readonly object sync = new(); private readonly ulong session; private readonly Timer heartbeat; private Exception? failure; private bool disposed; internal DriverTransport() : this(FindDevice) { } internal DriverTransport(Func findDevice) { handle = findDevice() ?? throw new InvalidOperationException("未检测到已启动的 XFE HID 控制设备。请先调用 DriverDeployment.CheckDriver() 检测,并在需要安装时显式调用 DriverDeployment.InstallEmbeddedDriver();连接不会自动安装驱动。"); try { var info = ReadInfo(handle); if (!DriverProtocol.IsCompatible(info)) throw new NotSupportedException("已安装的 XFE UMDF 驱动与本 DLL 协议不匹配,请显式调用 DriverDeployment.InstallEmbeddedDriver() 更新驱动。"); do { session = BitConverter.ToUInt64(RandomNumberGenerator.GetBytes(sizeof(ulong))); } while (session == 0); WriteControl(HidControlProtocol.Acquire); heartbeat = new Timer(KeepAlive, null, 500, 500); } catch { handle.Dispose(); throw; } } public DriverInfo Query() { lock (sync) { CheckState(); return ReadInfo(handle); } } public void Send(DriverCommand command) { lock (sync) { CheckState(); WriteControl(HidControlProtocol.Send, command); } } public void Reset() { lock (sync) { CheckState(); WriteControl(HidControlProtocol.Reset); } } internal static DriverInfo? QueryDevice() { using var opened = FindDevice(); return opened is null ? null : ReadInfo(opened); } private static DriverInfo ReadInfo(SafeFileHandle handle) { var bytes = new byte[HidControlProtocol.ReportSize]; bytes[0] = HidControlProtocol.ReportId; fixed (byte* buffer = bytes) if (!HidD_GetFeature(handle, buffer, (uint)bytes.Length)) throw Error("读取驱动状态"); return HidControlProtocol.DecodeInfo(bytes); } private void WriteControl(byte operation, DriverCommand command = default) { var bytes = HidControlProtocol.Encode(operation, session, command); fixed (byte* buffer = bytes) if (!HidD_SetFeature(handle, buffer, (uint)bytes.Length)) throw Error(operation == HidControlProtocol.Acquire ? "申请输入会话(可能已有另一个控制器)" : "发送 HID 指令"); } private void KeepAlive(object? state) { lock (sync) { if (disposed || failure is not null) return; try { WriteControl(HidControlProtocol.Heartbeat); } catch (Exception error) { failure = error; } } } private void CheckState() { ObjectDisposedException.ThrowIf(disposed, this); if (failure is not null) throw new IOException("HID 会话已失效;请释放控制器并重新连接。驱动在失联后会尝试松开输入。", failure); } public void Dispose() { lock (sync) { if (disposed) return; disposed = true; heartbeat.Dispose(); try { if (failure is null) WriteControl(HidControlProtocol.Release); } catch (Win32Exception) { } finally { handle.Dispose(); } } } private static Win32Exception Error(string action) { var code = Marshal.GetLastPInvokeError(); return new Win32Exception(code, $"{action}失败(Win32 {code})。请检查设备状态、会话占用及六键并发限制。"); } private static SafeFileHandle? FindDevice() { if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) || RuntimeInformation.OSArchitecture != Architecture.X64) throw new PlatformNotSupportedException("内置 UMDF HID 驱动支持 Windows 11 x64。"); HidD_GetHidGuid(out var guid); var devices = SetupDiGetClassDevsW(in guid, null, 0, 0x12); if (devices == -1) throw Error("枚举 HID 设备"); try { var item = new DeviceInterface { Size = (uint)sizeof(DeviceInterface) }; for (uint index = 0; ; index++) { if (!SetupDiEnumDeviceInterfaces(devices, 0, in guid, index, ref item)) { if (Marshal.GetLastPInvokeError() == 259) return null; throw Error("枚举 HID 接口"); } SetupDiGetDeviceInterfaceDetailW(devices, ref item, null, 0, out var length, 0); if (length < 6 || length > 65536) continue; var detail = (byte*)NativeMemory.AllocZeroed(length); try { *(uint*)detail = Environment.Is64BitProcess ? 8u : 6u; if (!SetupDiGetDeviceInterfaceDetailW(devices, ref item, detail, length, out _, 0)) throw Error("读取 HID 接口路径"); var path = Marshal.PtrToStringUni((nint)(detail + 4))!; using var probe = CreateFileW(path, 0, 3, 0, 3, 0, 0); if (probe.IsInvalid) continue; var attributes = new HidAttributes { Size = (uint)sizeof(HidAttributes) }; if (!HidD_GetAttributes(probe, ref attributes) || attributes.Vendor != 0xFEFE || attributes.Product != 0x0003) continue; if (!HidD_GetPreparsedData(probe, out var preparsed)) continue; HidCapabilities caps; int status; try { status = HidP_GetCaps(preparsed, out caps); } finally { HidD_FreePreparsedData(preparsed); } if (status != 0x00110000 || caps.UsagePage != 0xFF00 || caps.Usage != 1 || caps.FeatureBytes != HidControlProtocol.ReportSize) continue; var opened = CreateFileW(path, 0xC0000000, 3, 0, 3, 0, 0); if (!opened.IsInvalid) return opened; var error = Error("打开 HID 控制通道"); opened.Dispose(); throw error; } finally { NativeMemory.Free(detail); } } } finally { SetupDiDestroyDeviceInfoList(devices); } } [StructLayout(LayoutKind.Sequential)] private struct DeviceInterface { internal uint Size; internal Guid Interface; internal uint Flags; internal nint Reserved; } [StructLayout(LayoutKind.Sequential)] private struct HidAttributes { internal uint Size; internal ushort Vendor, Product, Version; } [StructLayout(LayoutKind.Sequential)] private struct HidCapabilities { internal ushort Usage, UsagePage, InputBytes, OutputBytes, FeatureBytes; internal fixed ushort Reserved[17]; internal fixed ushort Counts[10]; } [LibraryImport("kernel32.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] private static partial SafeFileHandle CreateFileW(string name, uint access, uint share, nint security, uint disposition, uint flags, nint template); [LibraryImport("hid.dll")] private static partial void HidD_GetHidGuid(out Guid guid); [LibraryImport("hid.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.U1)] private static partial bool HidD_GetAttributes(SafeFileHandle handle, ref HidAttributes attributes); [LibraryImport("hid.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.U1)] private static partial bool HidD_GetPreparsedData(SafeFileHandle handle, out nint data); [LibraryImport("hid.dll")] [return: MarshalAs(UnmanagedType.U1)] private static partial bool HidD_FreePreparsedData(nint data); [LibraryImport("hid.dll")] private static partial int HidP_GetCaps(nint data, out HidCapabilities caps); [LibraryImport("hid.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.U1)] private static partial bool HidD_GetFeature(SafeFileHandle handle, byte* bytes, uint length); [LibraryImport("hid.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.U1)] private static partial bool HidD_SetFeature(SafeFileHandle handle, byte* bytes, uint length); [LibraryImport("setupapi.dll", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] private static partial nint SetupDiGetClassDevsW(in Guid guid, string? enumerator, nint parent, uint flags); [LibraryImport("setupapi.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool SetupDiEnumDeviceInterfaces(nint devices, nint device, in Guid guid, uint index, ref DeviceInterface data); [LibraryImport("setupapi.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool SetupDiGetDeviceInterfaceDetailW(nint devices, ref DeviceInterface data, byte* detail, uint length, out uint required, nint device); [LibraryImport("setupapi.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool SetupDiDestroyDeviceInfoList(nint devices); }