返回提交历史
Modified
.github/PUBLISHING.md
+2
-2
Modified
README.md
+24
-10
Modified
Start-InputConsole.cmd
+3
-1
Modified
XFEExtension.NetCore.InputSimulator.Console/Program.cs
+31
-17
Added
XFEExtension.NetCore.InputSimulator.Tests/DriverDeploymentTests.cs
+96
-0
Modified
XFEExtension.NetCore.InputSimulator/DriverBackend.cs
+3
-3
Added
XFEExtension.NetCore.InputSimulator/DriverCheckResult.cs
+33
-0
Modified
XFEExtension.NetCore.InputSimulator/DriverDeployment.cs
+37
-3
Modified
XFEExtension.NetCore.InputSimulator/InputController.cs
+1
-1
Modified
XFEExtension.NetCore.InputSimulator/InputSimulator.cs
+2
-2
Modified
XFEExtension.NetCore.InputSimulator/Native/DriverProtocol.cs
+3
-0
Modified
XFEExtension.NetCore.InputSimulator/Native/DriverTransport.cs
+17
-19
Modified
XFEExtension.NetCore.InputSimulator/XFEExtension.NetCore.InputSimulator.csproj
+2
-2
Modified
driver/SIGNING.md
+4
-4
XFEstudio/XFEExtension.NetCore.InputSimulator
驱动安装改为显式调用,完善检测与文档
将驱动安装方式由自动改为仅显式调用时安装,新增 DriverCheckResult 和 DriverStatus 详细描述检测结果。调整 DriverBackend、InputController、InputSimulator 等类,连接时不再自动安装驱动,缺失直接报错。DriverTransport 构造函数移除自动安装逻辑,未检测到设备时抛异常。DriverDeployment 新增 CheckDriver(),InstallEmbeddedDriver() 仅主动调用时安装。控制台程序菜单和参数说明同步更新,所有检测和输入操作均不自动安装驱动。完善文档,强调需显式调用安装接口。新增 DriverDeploymentTests 单元测试,覆盖检测状态、异常和连接行为。项目描述与发布说明同步更新。
47e5fb5
代码差异
14 个文件
+258
-64
@@ -10,7 +10,7 @@
10
10
11
11
`unsigned-driver` 产物包含 UMDF DLL、INF、CAT、PDB 及安装器。CI 不使用签名私钥、不生成或提交微软审核 CAB。签名及后续处理见 [驱动签名说明](../driver/SIGNING.md)。
12
12
13
开发 NuGet 包默认带 `-driver-dev` 后缀。完整驱动文件作为资源嵌入 DLL;它不会自动安装未签名负载。CI 不安装驱动或修改 runner 的证书信任与启动安全策略。
13
开发 NuGet 包默认带 `-driver-dev` 后缀。完整驱动文件作为资源嵌入 DLL;显式安装接口会拒绝未签名负载。CI 不安装驱动或修改 runner 的证书信任与启动安全策略。
14
14
15
15
自动测试使用记录后端,不向 runner 的桌面发送真实输入。Windows CI 使用默认的 x64 .NET 主机;本地可按 README 中的命令额外运行 x86 测试。
16
16
@@ -20,7 +20,7 @@
20
20
21
21
当前 `-driver-local` 自签名包供用户明确授权信任后安装。维护者首次运行 `driver/New-SigningCertificate.ps1` 创建十年专用证书,再通过 `Build.ps1 -LocalSigning` 复用身份、签名并请求 RFC 3161 时间戳;也支持显式指定 `-LocalCertificateThumbprint '<指纹>'`。私钥不进入工作流或包。公共发布工作流不会将它误发布为公共受信任版本,也不会在 runner 上导入本地 Root 证书。
22
22
23
签名是维护者的发布步骤。调用方只需要 DLL/NuGet,首次使用由内嵌安装器申请管理员授权和所需的签名者信任;无需自己签名、下载驱动或配置 WDK。`driver/signed/win-x64` 中只保存发行二进制,不保存私钥。
23
签名是维护者的发布步骤。调用方只需要 DLL/NuGet,使用 `DriverDeployment.CheckDriver()` 检测设备,在显式调用 `InstallEmbeddedDriver()` 时由内嵌安装器申请管理员授权和所需的签名者信任。连接和输入均不会自动安装;无需自己签名、下载驱动或配置 WDK。`driver/signed/win-x64` 中只保存发行二进制,不保存私钥。
24
24
25
25
在 GitHub 仓库的 Settings → Secrets and variables → Actions 中配置:
26
26
@@ -4,13 +4,19 @@
4
4
5
5
## 调用方如何使用
6
6
7
签名包的 DLL 内嵌 `XfeInputDriver.dll`、`XfeInput.inf`、`XfeInput.cat`、`XfeInputSetup.exe`、发布者公开证书和校验清单。**使用者不需要自己签名或申请证书。** 引用 DLL/NuGet 后,首次连接自动请求管理员安装;签名者尚未受信任时显示证书指纹,确认后才添加信任。之后可以普通权限运行,无需 Visual Studio、WDK、第三方输入驱动或额外下载。
7
签名包的 DLL 内嵌 `XfeInputDriver.dll`、`XfeInput.inf`、`XfeInput.cat`、`XfeInputSetup.exe`、发布者公开证书和校验清单。**使用者不需要自己签名或申请证书。** 引用 DLL/NuGet、创建控制器和发送输入都不会自动安装驱动。由调用方使用 `DriverDeployment.CheckDriver()` 检测,在自己的安装按钮或命令中显式调用 `DriverDeployment.InstallEmbeddedDriver()` 才请求管理员授权。安装后可以普通权限运行,无需 Visual Studio、WDK、第三方输入驱动或额外下载。
8
8
9
9
```csharp
10
10
using XFEExtension.NetCore.InputSimulator;
11
11
12
// 首次连接发现驱动缺失时,从 DLL 提取驱动包并请求 UAC 安装授权。
13
// 已安装后直接连接,不重复安装;不存在软件输入回退。
12
// 只检测设备和协议;不安装、不请求 UAC、不占用输入会话。
13
var driver = DriverDeployment.CheckDriver();
14
if (!driver.IsAvailable)
15
{
16
Console.WriteLine($"{driver.Status}: {driver.Message}");
17
return; // 由调用方在需要时提供安装入口。
18
}
19
14
20
using var input = new InputController();
15
21
await input.PressKeyAsync(ScanCode.W, holdTime: 2000);
16
22
await input.PressCombinationAsync([ScanCode.LeftShift, ScanCode.W], holdTime: 1000);
@@ -19,13 +25,22 @@ await input.MouseClickAsync(MouseButton.Left);
19
25
input.MouseWheelRoll(-120);
20
26
```
21
27
28
在调用方的“安装或更新驱动”操作中单独执行:
29
30
```csharp
31
DriverDeployment.InstallEmbeddedDriver(); // 此处才请求 UAC 和所需的签名者信任。
32
Console.WriteLine(DriverDeployment.CheckDriver().Message);
33
```
34
35
`CheckDriver()` 返回 `Ready`、`NotDetected`、`Incompatible`、`Unavailable` 或 `UnsupportedPlatform`,并提供 `IsAvailable` 和 `Message`。`NotDetected` 也可能表示设备被禁用或尚未启动;`Ready` 表示设备可访问且协议兼容,不保证会话空闲或已安装最新驱动。检测不会抢占、释放或续约其他控制器的会话。直接创建控制器遇到驱动缺失时会抛出异常,提示显式检测和安装。
36
22
37
自签名包需要用户在首次安装时同意信任发布者的专用代码签名证书;它会加入本机 `Root` 和 `TrustedPublisher`,使系统信任该证书签名的代码。发布者私钥不会分发。设备安装策略仍然适用,系统也可能要求重启。安装器不更改 Secure Boot、内存完整性、测试模式或签名强制策略,不自动重启。
23
38
24
包有三种状态:`Build.ps1` 默认重新编译的驱动为 `unsigned` / `-driver-dev`,禁止自动安装;仓库内置的自签名包是 `self-signed` / `-driver-local`,需要信任专用证书;公共代码签名发行包标记为 `trusted-publisher`。本地信任方案已经在 Windows 11、Secure Boot 开启的机器上安装成功,但不是微软 WHQL 认证。4.0.1 驱动使用有效期至 **2036-09-23(UTC+8)**的专用 RSA 4096 位代码证书,并附加 RFC 3161 时间戳。证书持续复用于后续构建,期限和私钥备份详见 [签名与分发](driver/SIGNING.md)。
39
包有三种状态:`Build.ps1` 默认重新编译的驱动为 `unsigned` / `-driver-dev`,安装接口会拒绝该包;仓库内置的自签名包是 `self-signed` / `-driver-local`,需要信任专用证书;公共代码签名发行包标记为 `trusted-publisher`。本地信任方案已经在 Windows 11、Secure Boot 开启的机器上安装成功,但不是微软 WHQL 认证。4.0.1 驱动使用有效期至 **2036-09-23(UTC+8)**的专用 RSA 4096 位代码证书,并附加 RFC 3161 时间戳。证书持续复用于后续构建,期限和私钥备份详见 [签名与分发](driver/SIGNING.md)。
25
40
26
41
当前驱动支持 **Windows 11 x64**;C# 调用方可以是 x64 或 x86。调用方自己的 .NET 10 应用仍需运行时或采用自包含发布。
27
42
28
可在应用安装阶段提前安装,或检查内嵌状态:
43
以下属性仅检查 DLL 内嵌包,不表示系统已经安装驱动;系统设备状态使用上面的 `CheckDriver()`:
29
44
30
45
```csharp
31
46
Console.WriteLine(DriverDeployment.HasEmbeddedDriver);
@@ -33,7 +48,6 @@ Console.WriteLine(DriverDeployment.IsEmbeddedDriverSigned);
33
48
Console.WriteLine(DriverDeployment.EmbeddedDriverSigningKind);
34
49
Console.WriteLine(DriverDeployment.EmbeddedDriverCertificateExpires);
35
50
Console.WriteLine(DriverDeployment.IsEmbeddedDriverTimestamped);
36
DriverDeployment.InstallEmbeddedDriver(); // 需要安装时请求 UAC;不必在每次输入前调用。
37
51
```
38
52
39
53
驱动文件安装进 Windows 驱动存储,临时提取文件在安装完成后清理。取消 UAC、签名不受信任、需要重启、设备被其他控制器占用等情况均会抛出明确异常。
@@ -82,21 +96,21 @@ await sequence.PlayAsync(input, repeat: 3);
82
96
83
97
## 交互式控制台
84
98
85
完成本地构建后,双击 `Start-InputConsole.cmd`,或打开发布的 `InputSimulator.Console.exe`。签名发行版首次选择测试时先完成驱动安装,再开始倒计时。
99
完成本地构建后,双击 `Start-InputConsole.cmd`,或打开发布的 `InputSimulator.Console.exe`。启动菜单和选择输入测试均不会自动安装。先选 `9` 检测驱动,需要安装时选 `I` 显式安装或更新,然后选择输入测试。
86
100
87
101
1. 打开目标程序,控制台输入 `2` 测试 W 长按 2 秒。
88
102
2. 在默认 5 秒倒计时内 Alt+Tab 切回目标,松开切换窗口使用的按键。
89
103
3. 查看是否产生输入,再切回控制台选择下一项。
90
104
91
`1` 短按 W,`3` 空格,`4` Shift + W,`5` 鼠标移动,`6` 鼠标按键,`7` 滚轮,`8` 自定义组合及重复次数,`9` 驱动包状态。`T` 修改倒计时,`K` 查看键名,`0` 退出。
105
`1` 短按 W,`3` 空格,`4` Shift + W,`5` 鼠标移动,`6` 鼠标按键,`7` 滚轮,`8` 自定义组合及重复次数,`9` 检测驱动状态,`I` 安装或更新驱动。`T` 修改倒计时,`K` 查看键名,`0` 退出。预演模式下 `9` 和 `I` 均跳过系统设备操作。
92
106
93
107
F8 在倒计时或执行中取消;控制台内也可以 Ctrl+C 取消。F8 通过系统键状态轮询检测,不屏蔽目标对 F8 的处理。预演不会连接驱动、安装驱动或向桌面发送输入:
94
108
95
109
```powershell
96
110
dotnet run --project .\XFEExtension.NetCore.InputSimulator.Console -c Release -- --dry-run
97
# 检查首次安装、连接、心跳和会话释放,不发送按键或鼠标移动:
111
# 只检测设备及协议,不安装、不申请输入会话、不发送键鼠输入;就绪返回 0,否则返回 1:
98
112
dotnet run --project .\XFEExtension.NetCore.InputSimulator.Console -c Release -- --driver-check
99
# 已装旧版时,显式更新内嵌驱动和签名证书:
113
# 显式安装或更新内嵌驱动,需要管理员授权:
100
114
dotnet run --project .\XFEExtension.NetCore.InputSimulator.Console -c Release -- --driver-install
101
115
```
102
116
@@ -4,6 +4,8 @@ cd /d "%~dp0"
4
4
dotnet run --project "XFEExtension.NetCore.InputSimulator.Console\XFEExtension.NetCore.InputSimulator.Console.csproj" --configuration Release --no-launch-profile -p:GeneratePackageOnBuild=false -- %*
5
5
if errorlevel 1 (
6
6
echo.
7
echo Input console failed. Check the messages above. A fresh source checkout needs: pwsh -File Build.ps1
7
echo Input console failed. Check the messages above. Building requires the .NET 10 SDK.
8
echo Detect driver: Start-InputConsole.cmd --driver-check
9
echo Explicitly install or update driver: Start-InputConsole.cmd --driver-install
8
10
pause
9
11
)
@@ -13,7 +13,7 @@ internal static class Program
13
13
{
14
14
Console.WriteLine("InputSimulator.Console.exe [--dry-run | --driver-check | --driver-install]");
15
15
Console.WriteLine("无参数:交互式真实输入测试;--dry-run:只打印事件,跳过倒计时,不发送输入。");
16
Console.WriteLine("真实输入使用内置 UMDF HID 驱动;首次需要管理员安装及签名者信任。--driver-check 仅验证连接和心跳,不按键、不移动鼠标。");
16
Console.WriteLine("真实输入使用内置 UMDF HID 驱动;不会自动安装。--driver-check 只检测设备和协议,不安装、不占用输入会话。");
17
17
Console.WriteLine("--driver-install:安装或更新为内嵌驱动包,更换签名证书时使用;需要管理员授权。");
18
18
Console.WriteLine("选择测试后切回目标窗口;默认倒计时 5 秒,F8 取消;Ctrl+C 取消当前操作。");
19
19
return 0;
@@ -24,14 +24,7 @@ internal static class Program
24
24
try
25
25
{
26
26
if (args[0] == "--driver-install") DriverDeployment.InstallEmbeddedDriver();
27
string name;
28
using (var driver = new DriverBackend())
29
{
30
name = driver.Name;
31
await Task.Delay(4000);
32
}
33
Console.WriteLine($"连接、心跳及释放成功:{name};签名:{DriverDeployment.EmbeddedDriverSigningKind};时间戳:{DriverDeployment.IsEmbeddedDriverTimestamped};证书到期:{DriverDeployment.EmbeddedDriverCertificateExpires:O}");
34
return 0;
27
return PrintDriverStatus().IsAvailable ? 0 : 1;
35
28
}
36
29
catch (Exception error) { Console.Error.WriteLine(error.Message); return 1; }
37
30
}
@@ -54,8 +47,8 @@ internal static class Program
54
47
}
55
48
56
49
Console.WriteLine("=== InputSimulator 手动测试 / .NET 10 ===");
57
Console.WriteLine(preview ? "预演模式:只打印事件,不加载驱动或发送真实输入。" : "后端:项目自有 UMDF 虚拟 HID 驱动。启动菜单不会发送输入。");
58
Console.WriteLine($"内嵌驱动:{(DriverDeployment.HasEmbeddedDriver ? "有" : "缺失")};签名:{(DriverDeployment.IsEmbeddedDriverSigned ? "已校验" : "开发版,未签名,不能自动安装")}");
50
Console.WriteLine(preview ? "预演模式:只打印事件,不加载驱动或发送真实输入。" : "后端:项目自有 UMDF 虚拟 HID 驱动。启动和测试均不会自动安装;9 检测,I 安装或更新。");
51
Console.WriteLine($"内嵌驱动:{(DriverDeployment.HasEmbeddedDriver ? "有" : "缺失")};签名:{(DriverDeployment.IsEmbeddedDriverSigned ? "已校验" : "开发版,未签名,不能安装")}");
59
52
Console.WriteLine($"签名类型:{DriverDeployment.EmbeddedDriverSigningKind};证书到期:{DriverDeployment.EmbeddedDriverCertificateExpires:yyyy-MM-dd}");
60
53
Console.WriteLine("驱动设备仍可被目标识别;发送成功不代表游戏已接受。");
61
54
Console.WriteLine("真实测试时请在倒计时内 Alt+Tab 切回目标,并松开切换窗口使用的按键。");
@@ -72,7 +65,7 @@ internal static class Program
72
65
Console.WriteLine("3 空格 100ms 4 Shift + W 长按 2 秒");
73
66
Console.WriteLine("5 鼠标向右相对移动 6 鼠标按键测试(可选左/右/中/侧键)");
74
67
Console.WriteLine("7 滚轮向下一格 8 自定义键/组合键、保持时间和次数");
75
Console.WriteLine("9 查看驱动状态(此驱动不支持直接 Unicode 输入和绝对鼠标定位)");
68
Console.WriteLine("9 检测驱动状态 I 安装或更新驱动(需要管理员授权)");
76
69
Console.WriteLine($"T 设置倒计时(当前 {countdown} 秒) K 查看可用键名 0 退出");
77
70
Console.Write("请选择 > ");
78
71
var choice = Console.ReadLine()?.Trim().ToUpperInvariant();
@@ -94,8 +87,19 @@ internal static class Program
94
87
95
88
if (choice == "9")
96
89
{
97
Console.WriteLine($"内嵌驱动:{DriverDeployment.HasEmbeddedDriver};已校验签名:{DriverDeployment.IsEmbeddedDriverSigned}");
98
Console.WriteLine("签名版首次测试时自动安装;系统同时只允许一个控制器连接。最多同时按住 6 个普通键及 8 个修饰键。");
90
if (preview) Console.WriteLine("预演模式:跳过设备检测,不打开驱动。");
91
else PrintDriverStatus();
92
Console.WriteLine("系统同时只允许一个控制器连接。最多同时按住 6 个普通键及 8 个修饰键;不支持直接 Unicode 输入和绝对鼠标定位。");
93
continue;
94
}
95
if (choice == "I")
96
{
97
if (preview) Console.WriteLine("预演模式:跳过驱动安装,不请求管理员授权。");
98
else
99
{
100
DriverDeployment.InstallEmbeddedDriver();
101
PrintDriverStatus();
102
}
99
103
continue;
100
104
}
101
105
var test = CreateTest(choice);
@@ -113,15 +117,25 @@ internal static class Program
113
117
catch (Exception error)
114
118
{
115
119
exitCode = 1;
116
Console.Error.WriteLine($"测试未完成:{error.Message}");
120
Console.Error.WriteLine($"操作未完成:{error.Message}");
117
121
if (error is AggregateException aggregate)
118
122
foreach (var inner in aggregate.Flatten().InnerExceptions)
119
123
Console.Error.WriteLine($" {inner.Message}");
120
Console.WriteLine("请按错误提示检查驱动签名、安装和设备占用情况。清理报错时请手动按下并松开对应键。");
124
Console.WriteLine("可选择 9 检测驱动,或 I 显式安装或更新。清理报错时请手动按下并松开对应键。");
121
125
}
122
126
}
123
127
}
124
128
129
private static DriverCheckResult PrintDriverStatus()
130
{
131
var result = DriverDeployment.CheckDriver();
132
Console.WriteLine($"驱动状态:{result.Status};{result.Message}");
133
Console.WriteLine($"内嵌包签名:{DriverDeployment.EmbeddedDriverSigningKind};时间戳:{DriverDeployment.IsEmbeddedDriverTimestamped};证书到期:{DriverDeployment.EmbeddedDriverCertificateExpires:O}");
134
if (!result.IsAvailable)
135
Console.WriteLine("需要安装或更新时,选择 I 或运行 --driver-install;检测不会触发安装。");
136
return result;
137
}
138
125
139
private static ManualTest? CreateTest(string choice) => choice switch
126
140
{
127
141
"1" => new("W 短按 100ms", new InputSequence().PressKey(ScanCode.W, 100)),
@@ -218,7 +232,7 @@ internal static class Program
218
232
var watcher = preview ? Task.CompletedTask : WatchStopKeyAsync(cancellation, stopWatching.Token);
219
233
try
220
234
{
221
// Complete driver connection / first-time UAC installation before starting the countdown.
235
// Connect to the already installed driver before starting the countdown.
222
236
using var input = new InputController(preview ? new PreviewBackend() : new DriverBackend());
223
237
Console.WriteLine($"\n本次测试:{test.Name}");
224
238
if (!preview)
@@ -0,0 +1,96 @@
1
using System.ComponentModel;
2
using XFEExtension.NetCore.InputSimulator.Native;
3
4
namespace XFEExtension.NetCore.InputSimulator.Tests;
5
6
[TestFixture]
7
public class DriverDeploymentTests
8
{
9
[Test]
10
public void MissingDeviceReturnsDiagnosticWithoutRetryingOrInstalling()
11
{
12
var queries = 0;
13
var result = DriverDeployment.CheckDriver(() => { queries++; return null; });
14
Assert.Equal(DriverStatus.NotDetected, result.Status);
15
Assert.False(result.IsAvailable);
16
Assert.True(result.Message.Contains(nameof(DriverDeployment.InstallEmbeddedDriver)));
17
Assert.Equal(1, queries);
18
}
19
20
[Test]
21
public void CompatibleDeviceIsAvailable()
22
{
23
var result = DriverDeployment.CheckDriver(() => new DriverInfo { Version = 1, Capabilities = 31, MaxKeys = 6 });
24
Assert.Equal(DriverStatus.Ready, result.Status);
25
Assert.True(result.IsAvailable);
26
}
27
28
[TestCase(99, 31, 6, 0)]
29
[TestCase(1, 0, 6, 0)]
30
[TestCase(1, 31, 5, 0)]
31
[TestCase(1, 31, 6, 1)]
32
public void IncompatibleDeviceIsNotReportedAsMissing(int version, int capabilities, int maxKeys, int reserved)
33
{
34
var result = DriverDeployment.CheckDriver(() => new DriverInfo
35
{
36
Version = (uint)version, Capabilities = (uint)capabilities, MaxKeys = (uint)maxKeys, Reserved = (uint)reserved
37
});
38
Assert.Equal(DriverStatus.Incompatible, result.Status);
39
Assert.False(result.IsAvailable);
40
}
41
42
[Test]
43
public void MalformedReportIsIncompatible()
44
{
45
var result = DriverDeployment.CheckDriver(() => throw new InvalidDataException("invalid HID report"));
46
Assert.Equal(DriverStatus.Incompatible, result.Status);
47
Assert.False(result.IsAvailable);
48
Assert.Equal("invalid HID report", result.Message);
49
}
50
51
[Test]
52
public void DeviceAccessFailureIsNotReportedAsMissing()
53
{
54
foreach (var error in new Exception[]
55
{
56
new Win32Exception(5, "access denied"), new IOException("device disconnected"),
57
new UnauthorizedAccessException("device policy")
58
})
59
{
60
var result = DriverDeployment.CheckDriver(() => throw error);
61
Assert.Equal(DriverStatus.Unavailable, result.Status);
62
Assert.False(result.IsAvailable);
63
Assert.Equal(error.Message, result.Message);
64
}
65
}
66
67
[Test]
68
public void UnsupportedPlatformReturnsDiagnostic()
69
{
70
var result = DriverDeployment.CheckDriver(() => throw new PlatformNotSupportedException("unsupported OS"));
71
Assert.Equal(DriverStatus.UnsupportedPlatform, result.Status);
72
Assert.False(result.IsAvailable);
73
}
74
75
[Test]
76
public void MissingDeviceConnectionFailsBeforeAnyNativeSessionOrInstallation()
77
{
78
var attempts = 0;
79
var error = Assert.Throws<InvalidOperationException>(() => new DriverTransport(() => { attempts++; return null; }));
80
Assert.True(error.Message.Contains(nameof(DriverDeployment.InstallEmbeddedDriver)));
81
Assert.Equal(1, attempts);
82
}
83
84
[Test]
85
public void ConnectionAccessFailureIsPreservedWithoutRetryingOrInstalling()
86
{
87
var attempts = 0;
88
var error = Assert.Throws<Win32Exception>(() => new DriverTransport(() =>
89
{
90
attempts++;
91
throw new Win32Exception(5, "access denied");
92
}));
93
Assert.Equal(5, error.NativeErrorCode);
94
Assert.Equal(1, attempts);
95
}
96
}
@@ -2,7 +2,7 @@ using XFEExtension.NetCore.InputSimulator.Native;
2
2
3
3
namespace XFEExtension.NetCore.InputSimulator;
4
4
5
/// <summary>连接项目内置的 UMDF 虚拟 HID 驱动;缺失时首次调用自动部署签名驱动。</summary>
5
/// <summary>连接已安装的 UMDF 虚拟 HID 驱动;驱动缺失或不可用时报告错误,不自动安装。</summary>
6
6
/// <remarks>输入会话独占,同一时间只能有一个控制器。支持六个普通键及八个修饰键;不回退到软件注入。</remarks>
7
7
public sealed class DriverBackend : IInputBackend
8
8
{
@@ -10,7 +10,7 @@ public sealed class DriverBackend : IInputBackend
10
10
private readonly object sync = new();
11
11
private bool disposed;
12
12
13
/// <summary>连接驱动;首次安装需要 Windows 管理员授权。</summary>
13
/// <summary>连接已安装的驱动。使用 DriverDeployment 显式检测和安装驱动。</summary>
14
14
public DriverBackend() : this(new DriverTransport()) { }
15
15
16
16
internal DriverBackend(IDriverTransport transport)
@@ -19,7 +19,7 @@ public sealed class DriverBackend : IInputBackend
19
19
try
20
20
{
21
21
var info = transport.Query();
22
if (info.Version != DriverProtocol.Version || info.Capabilities != DriverProtocol.Capabilities || info.MaxKeys != 6 || info.Reserved != 0)
22
if (!DriverProtocol.IsCompatible(info))
23
23
throw new NotSupportedException("已安装的 XFE 驱动与本 DLL 协议不匹配,请更新驱动后重新启动应用。");
24
24
}
25
25
catch { transport.Dispose(); throw; }
@@ -0,0 +1,33 @@
1
namespace XFEExtension.NetCore.InputSimulator;
2
3
/// <summary>驱动控制设备的检测状态;检测不会安装驱动或占用输入会话。</summary>
4
public enum DriverStatus
5
{
6
/// <summary>未发现已启动的控制设备;驱动可能未安装、被禁用或尚未启动。</summary>
7
NotDetected,
8
/// <summary>控制设备可访问且协议兼容;实际连接时仍可能存在会话占用。</summary>
9
Ready,
10
/// <summary>设备协议或状态报告与本 DLL 不兼容。</summary>
11
Incompatible,
12
/// <summary>枚举或读取设备失败,具体原因见检测结果的 Message。</summary>
13
Unavailable,
14
/// <summary>系统不支持此驱动。</summary>
15
UnsupportedPlatform
16
}
17
18
/// <summary>一次驱动检测的结果,不保证后续连接成功,也不代表已安装最新版本。</summary>
19
public sealed class DriverCheckResult
20
{
21
internal DriverCheckResult(DriverStatus status, string message)
22
{
23
Status = status;
24
Message = message;
25
}
26
27
/// <summary>设备状态。</summary>
28
public DriverStatus Status { get; }
29
/// <summary>状态说明或设备错误信息。</summary>
30
public string Message { get; }
31
/// <summary>控制设备可访问且协议兼容;不申请或检查独占输入会话。</summary>
32
public bool IsAvailable => Status == DriverStatus.Ready;
33
}
@@ -4,6 +4,7 @@ using System.Reflection;
4
4
using System.Runtime.InteropServices;
5
5
using System.Security.Cryptography;
6
6
using System.Text.Json;
7
using XFEExtension.NetCore.InputSimulator.Native;
7
8
8
9
namespace XFEExtension.NetCore.InputSimulator;
9
10
@@ -14,6 +15,29 @@ public static class DriverDeployment
14
15
private static readonly string[] Files = ["XfeInputDriver.dll", "XfeInput.inf", "XfeInput.cat", "XfeInputSetup.exe", "publisher.cer"];
15
16
private static readonly object InstallLock = new();
16
17
18
/// <summary>检测已启动的驱动控制设备及协议;不安装、不请求管理员授权、不发送输入或占用会话。</summary>
19
/// <remarks>未检测到设备也可能是设备被禁用或尚未启动。Ready 不代表已安装最新驱动或输入会话空闲。</remarks>
20
public static DriverCheckResult CheckDriver() => CheckDriver(DriverTransport.QueryDevice);
21
22
internal static DriverCheckResult CheckDriver(Func<DriverInfo?> queryDevice)
23
{
24
try
25
{
26
var info = queryDevice();
27
if (info is null)
28
return new(DriverStatus.NotDetected, "未检测到已启动的 XFE HID 控制设备。可显式调用 DriverDeployment.InstallEmbeddedDriver() 安装,或检查设备管理器中的设备状态。");
29
if (!DriverProtocol.IsCompatible(info.Value))
30
return new(DriverStatus.Incompatible, "已安装的驱动协议与本 DLL 不兼容,请显式调用 DriverDeployment.InstallEmbeddedDriver() 更新驱动。");
31
return new(DriverStatus.Ready, "驱动控制设备可访问且协议兼容;连接时仍需申请独占输入会话。");
32
}
33
catch (PlatformNotSupportedException error) { return new(DriverStatus.UnsupportedPlatform, error.Message); }
34
catch (InvalidDataException error) { return new(DriverStatus.Incompatible, error.Message); }
35
catch (Exception error) when (error is Win32Exception or IOException or UnauthorizedAccessException)
36
{
37
return new(DriverStatus.Unavailable, error.Message);
38
}
39
}
40
17
41
/// <summary>此 DLL 是否包含完整驱动负载。</summary>
18
42
public static bool HasEmbeddedDriver
19
43
{
@@ -72,8 +96,8 @@ public static class DriverDeployment
72
96
}
73
97
}
74
98
75
/// <summary>安装内嵌驱动。首次需要 UAC 管理员授权;不会修改启动安全策略或自动重启。</summary>
76
/// <remarks>默认控制器在设备不存在时自动调用。可在应用安装阶段提前调用,避免首次输入时弹出授权。</remarks>
99
/// <summary>显式安装或更新内嵌驱动,请求 UAC 管理员授权;不会修改启动安全策略或自动重启。</summary>
100
/// <remarks>仅在调用方主动调用本方法时安装。创建控制器、发送输入及 CheckDriver 均不会自动安装。</remarks>
77
101
public static void InstallEmbeddedDriver()
78
102
{
79
103
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) || RuntimeInformation.OSArchitecture != Architecture.X64)
@@ -86,7 +110,7 @@ public static class DriverDeployment
86
110
if (root.GetProperty("protocol").GetUInt32() != Native.DriverProtocol.Version)
87
111
throw new InvalidDataException("内嵌驱动包协议版本不匹配。");
88
112
if (!root.GetProperty("signed").GetBoolean())
89
throw new InvalidOperationException("此 DLL 属于未签名开发版,不能自动安装。请由发布者签名 UMDF 驱动包并重新打包;调用方无需申请证书或安装 WDK。");
113
throw new InvalidOperationException("此 DLL 属于未签名开发版,不能安装。请由发布者签名 UMDF 驱动包并重新打包;调用方无需申请证书或安装 WDK。");
90
114
if (root.GetProperty("certificateExpires").GetDateTimeOffset() <= DateTimeOffset.UtcNow)
91
115
throw new InvalidOperationException("内嵌驱动的签名证书已过期,请向发布者获取使用有效证书签名的新版本。");
92
116
@@ -126,6 +150,16 @@ public static class DriverDeployment
126
150
}
127
151
if (installer.ExitCode == 3010) throw new InvalidOperationException("驱动已安装,Windows 要求重启后才能使用;未自动重启。");
128
152
if (installer.ExitCode != 0) throw new Win32Exception(installer.ExitCode, $"Windows 驱动安装失败({installer.ExitCode}),请检查签名和系统安装日志。");
153
// Device enumeration may lag behind a successful installer exit.
154
var readyTimer = Stopwatch.StartNew();
155
while (true)
156
{
157
var result = CheckDriver();
158
if (result.IsAvailable) break;
159
if (readyTimer.Elapsed >= TimeSpan.FromSeconds(10))
160
throw new IOException($"驱动安装器已完成,但控制设备尚未就绪:{result.Message}");
161
Thread.Sleep(250);
162
}
129
163
}
130
164
finally
131
165
{
@@ -14,7 +14,7 @@ public sealed class InputController : IDisposable
14
14
private readonly List<Button> pressOrder = [];
15
15
private bool disposed;
16
16
17
/// <summary>创建控制器;默认连接内置 UMDF HID 驱动,首次部署需要管理员授权和签名者信任。</summary>
17
/// <summary>创建控制器;默认连接已安装的 UMDF HID 驱动,缺失时报告错误,不自动安装。</summary>
18
18
public InputController(IInputBackend? backend = null) => this.backend = backend ?? new DriverBackend();
19
19
20
20
/// <summary>正在使用的后端名称。</summary>
@@ -3,14 +3,14 @@ using XFEExtension.NetCore.InputSimulator.Native;
3
3
4
4
namespace XFEExtension.NetCore.InputSimulator;
5
5
6
/// <summary>使用共享的 UMDF HID 驱动控制器模拟输入。需要独立生命周期时请创建 InputController。</summary>
6
/// <summary>使用共享的 UMDF HID 驱动控制器模拟输入;驱动需显式安装。需要独立生命周期时请创建 InputController。</summary>
7
7
public static class InputSimulator
8
8
{
9
9
private static readonly object SharedSync = new();
10
10
private static InputController? sharedController;
11
11
private static InputController Controller
12
12
{
13
// Do not cache a failed first installation (for example, cancelled UAC).
13
// A failed connection can be retried after the caller explicitly installs the driver.
14
14
get { lock (SharedSync) return sharedController ??= new InputController(); }
15
15
}
16
16
@@ -6,6 +6,9 @@ internal static class DriverProtocol
6
6
{
7
7
internal const uint Version = 1;
8
8
internal const uint Capabilities = 31;
9
10
internal static bool IsCompatible(DriverInfo info) =>
11
info.Version == Version && info.Capabilities == Capabilities && info.MaxKeys == 6 && info.Reserved == 0;
9
12
}
10
13
11
14
// Matches driver/Protocol.h on both x86 and x64. No pointers cross this boundary.
@@ -14,26 +14,16 @@ internal sealed unsafe partial class DriverTransport : IDriverTransport
14
14
private Exception? failure;
15
15
private bool disposed;
16
16
17
internal DriverTransport()
17
internal DriverTransport() : this(FindDevice) { }
18
19
internal DriverTransport(Func<SafeFileHandle?> findDevice)
18
20
{
19
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) || RuntimeInformation.OSArchitecture != Architecture.X64)
20
throw new PlatformNotSupportedException("内置 UMDF HID 驱动支持 Windows 11 x64。");
21
var opened = FindDevice();
22
if (opened is null)
23
{
24
DriverDeployment.InstallEmbeddedDriver();
25
for (var attempt = 0; attempt < 40 && opened is null; attempt++)
26
{
27
Thread.Sleep(250);
28
opened = FindDevice();
29
}
30
}
31
handle = opened ?? throw new IOException("驱动已安装,但 HID 控制设备尚未启动。请查看设备管理器中的 XFE UMDF 设备状态。");
21
handle = findDevice() ?? throw new InvalidOperationException("未检测到已启动的 XFE HID 控制设备。请先调用 DriverDeployment.CheckDriver() 检测,并在需要安装时显式调用 DriverDeployment.InstallEmbeddedDriver();连接不会自动安装驱动。");
32
22
try
33
23
{
34
var info = ReadInfo();
35
if (info.Version != DriverProtocol.Version || info.Capabilities != DriverProtocol.Capabilities || info.MaxKeys != 6 || info.Reserved != 0)
36
throw new NotSupportedException("已安装的 XFE UMDF 驱动与本 DLL 协议不匹配。");
24
var info = ReadInfo(handle);
25
if (!DriverProtocol.IsCompatible(info))
26
throw new NotSupportedException("已安装的 XFE UMDF 驱动与本 DLL 协议不匹配,请显式调用 DriverDeployment.InstallEmbeddedDriver() 更新驱动。");
37
27
do { session = BitConverter.ToUInt64(RandomNumberGenerator.GetBytes(sizeof(ulong))); } while (session == 0);
38
28
WriteControl(HidControlProtocol.Acquire);
39
29
heartbeat = new Timer(KeepAlive, null, 500, 500);
@@ -41,11 +31,17 @@ internal sealed unsafe partial class DriverTransport : IDriverTransport
41
31
catch { handle.Dispose(); throw; }
42
32
}
43
33
44
public DriverInfo Query() { lock (sync) { CheckState(); return ReadInfo(); } }
34
public DriverInfo Query() { lock (sync) { CheckState(); return ReadInfo(handle); } }
45
35
public void Send(DriverCommand command) { lock (sync) { CheckState(); WriteControl(HidControlProtocol.Send, command); } }
46
36
public void Reset() { lock (sync) { CheckState(); WriteControl(HidControlProtocol.Reset); } }
47
37
48
private DriverInfo ReadInfo()
38
internal static DriverInfo? QueryDevice()
39
{
40
using var opened = FindDevice();
41
return opened is null ? null : ReadInfo(opened);
42
}
43
44
private static DriverInfo ReadInfo(SafeFileHandle handle)
49
45
{
50
46
var bytes = new byte[HidControlProtocol.ReportSize];
51
47
bytes[0] = HidControlProtocol.ReportId;
@@ -99,6 +95,8 @@ internal sealed unsafe partial class DriverTransport : IDriverTransport
99
95
100
96
private static SafeFileHandle? FindDevice()
101
97
{
98
if (!OperatingSystem.IsWindowsVersionAtLeast(10, 0, 22000) || RuntimeInformation.OSArchitecture != Architecture.X64)
99
throw new PlatformNotSupportedException("内置 UMDF HID 驱动支持 Windows 11 x64。");
102
100
HidD_GetHidGuid(out var guid);
103
101
var devices = SetupDiGetClassDevsW(in guid, null, 0, 0x12);
104
102
if (devices == -1) throw Error("枚举 HID 设备");