AHK实现DD驱动按键连发(v2.0 GUI版)

前言

早在 2021 年就写过一个基于 AHK + DD 驱动的按键连发脚本,当时的版本很简陋——硬编码按键码、固定的间隔、没有任何界面,纯粹是”能用就行”的水平。最近抽空用 AutoHotkey v2.0 全面重写了它,加入了 GUI 界面、按键录制、配置保存等功能,同时也针对反作弊检测做了优化。

如果你不熟悉 DD 驱动:它是一个硬件级的键鼠模拟驱动,走的是内核层的模拟路径,比 AHK 原生的 Send / ControlSend 更底层,某些游戏或软件只认 DD 驱动的模拟输入。

功能概览

功能 说明
GUI 界面 告别纯脚本,可视化操作
按键录制 按一个键即可添加到连发列表,不用查码表
多键连发 支持勾选多个按键同时连发
鼠标支持 支持左键、右键、中键连发
间隔可调 1~9999ms 自由设置
随机抖动 30% 随机延迟,对抗反作弊检测
配置持久化 自动保存到 config.ini,重启保留设置
管理员权限 自动检测并提权

实现细节

1. 管理员权限检查

DD 驱动需要管理员权限才能加载,脚本启动时自动检测:

1
2
3
4
if !A_IsAdmin {
Run('*RunAs "' A_ScriptFullPath '"')
ExitApp()
}

如果没有管理员权限,自动以管理员身份重新启动自身。

2. 完整的 DD 键码映射表

初版脚本只硬编码了一个 502(x 键),新版建立了完整的键盘 + 鼠标码表:

1
2
3
4
5
6
7
DD_KEY_MAP := Map(
"escape", 100, "f1", 101, "f2", 102, ...
"q", 301, "w", 302, "e", 303, ...
"a", 401, "s", 402, "d", 403, ...
"z", 501, "x", 502, "c", 503, ...
"lb", 1, "rb", 4, "mid", 16
)

这样用户按任何键都能被正确识别和模拟,不需要手动查码表。

3. 直接加载 DD.dll

废弃了旧版的 Class_DD.ahk 封装,直接通过 DllCall 加载 DLL:

1
2
3
4
5
DLL_PATH := A_ScriptDir "\DD.dll"
hModule := DllCall("LoadLibrary", "Str", DLL_PATH, "Ptr")
pDD_key := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "DD_key", "Ptr")
pDD_btn := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "DD_btn", "Ptr")
DllCall(pDD_btn, "Int", 0) ; 初始化驱动

这样做的好处:

  • 不需要额外的 Class_DD.ahk 文件
  • 加载过程完全可控
  • 加载失败时有明确的错误提示

4. GUI 界面设计

1
2
MyGui := Gui(, "AHK连发")
MyGui.SetFont("s10", "Microsoft YaHei")

界面包含几个核心区域:

  • 间隔设置:输入框,单位毫秒,默认 200ms
  • 按键管理:录制按钮 + 一键添加左右键 + 清空
  • 按键列表:带勾选框的 ListView,打勾即参与连发
  • 启动/停止按钮:大号醒目按钮,红色表示运行中

![界面区域说明](GUI 界面分为间隔设置区、按键管理区、按键列表区、启停控制区四个区域。)

5. 按键录制功能

这是体验提升最大的功能。旧版要手动改代码改键码,新版直接按一下就行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
StartRecord(*) {
BtnRec.Text := "请按键..."
ih := InputHook("L1 M T3") ; 监听一个按键,超时3秒
ih.KeyOpt("{All}", "E")
ih.Start()
ih.Wait()

keyName := StrLower(ih.EndKey ? ih.EndKey : ih.Input)
if DD_KEY_MAP.Has(keyName) {
AddKeyItem(keyName)
} else {
ToolTip "未定义按键: " keyName
}
}

使用 InputHook 拦截下一次按键,查码表后自动添加到列表。超时 3 秒无操作自动取消。

6. 反作弊检测对策——随机抖动

很多游戏的 anti-cheat 会检测固定间隔的输入模式。新版加入了 30% 随机抖动

1
2
3
4
5
6
ScheduleNext() {
interval := IsNumber(EditMs.Value) ? Integer(EditMs.Value) : 200
jitter := Random(-interval * 0.3, interval * 0.3)
delay := Max(10, interval + jitter)
SetTimer(WorkLoop, -delay)
}

举例:设置 200ms 间隔,实际范围是 140~260ms 之间的随机值。每次触发间隔都不同,有效避免被固定模式检测抓包。

7. 鼠标按键支持

DD 驱动对鼠标按键的处理与键盘不同——键盘用 DD_key,鼠标用 DD_btn

1
2
3
4
5
6
7
8
9
if (itemName = "lb" || itemName = "rb" || itemName = "mid") {
DllCall(pDD_btn, "Int", code) ; 按下
Sleep 10
DllCall(pDD_btn, "Int", code * 2) ; 释放
} else {
DllCall(pDD_key, "Int", code, "Int", 1) ; 按下
Sleep 10
DllCall(pDD_key, "Int", code, "Int", 2) ; 释放
}

DD 鼠标的释放码是按下码的 2 倍(例如左键按下=1,释放=2)。

8. 配置持久化

使用 INI 文件保存设置,重启脚本自动恢复:

1
2
3
4
5
6
7
8
9
10
SaveConfig() {
IniWrite(EditMs.Value, CONFIG_PATH, "Settings", "Interval")
keyStr := ""
Loop LV.GetCount() {
name := LV.GetText(A_Index, 2)
state := LV.GetNext(A_Index - 1, "Checked") ? "1" : "0"
keyStr .= name ":" state ","
}
IniWrite(RTrim(keyStr, ","), CONFIG_PATH, "Settings", "Keys")
}

保存格式示例(config.ini):

1
2
3
[Settings]
Interval=200
Keys=x:1,c:1,lb:0,rb:1

9. 热键

热键 功能
`(反引号,SC029) 启动/停止连发
F12 彻底退出脚本

反引号键放在 ESC 下面,方便左手操作,不影响正常打字。

完整源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
#Requires AutoHotkey v2.0
#SingleInstance Force

; =========================
; 1. 管理员权限检查
; =========================
if !A_IsAdmin {
Run('*RunAs "' A_ScriptFullPath '"')
ExitApp()
}

; =========================
; 2. 码表定义
; =========================
DD_KEY_MAP := Map(
"escape", 100, "f1", 101, "f2", 102, "f3", 103, "f4", 104, "f5", 105, "f6", 106, "f7", 107, "f8", 108, "f9", 109, "f10", 110, "f11", 111, "f12", 112,
"``", 200, "1", 201, "2", 202, "3", 203, "4", 204, "5", 205, "6", 206, "7", 207, "8", 208, "9", 209, "0", 210, "-", 211, "=", 212, "\", 213, "backspace", 214,
"tab", 300, "q", 301, "w", 302, "e", 303, "r", 304, "t", 305, "y", 306, "u", 307, "i", 308, "o", 309, "p", 310, "[", 311, "]", 312, "enter", 313,
"capslock", 400, "a", 401, "s", 402, "d", 403, "f", 404, "g", 405, "h", 406, "j", 407, "k", 408, "l", 409, ";", 410, "'", 411,
"lshift", 500, "z", 501, "x", 502, "c", 503, "v", 504, "b", 505, "n", 506, "m", 507, ",", 508, ".", 509, "/", 510,
"lctrl", 600, "lwin", 601, "lalt", 602, "space", 603,
"up", 709, "left", 710, "down", 711, "right", 712,
"numpad0", 800, "numpad1", 801, "numpad2", 802, "numpad3", 803, "numpad4", 804, "numpad5", 805, "numpad6", 806, "numpad7", 807, "numpad8", 808, "numpad9", 809,
"lb", 1, "rb", 4, "mid", 16
)

; =========================
; 3. 加载 DD 驱动
; =========================
DLL_PATH := A_ScriptDir "\DD.dll"
CONFIG_PATH := A_ScriptDir "\config.ini"

if !FileExist(DLL_PATH) {
MsgBox "找不到 DD.dll,请确保它与脚本在同一目录下!"
ExitApp()
}

hModule := DllCall("LoadLibrary", "Str", DLL_PATH, "Ptr")
if !hModule {
MsgBox "驱动加载失败,可能是架构不匹配或被杀软拦截。"
ExitApp()
}

pDD_key := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "DD_key", "Ptr")
pDD_btn := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "DD_btn", "Ptr")
DllCall(pDD_btn, "Int", 0)

; =========================
; 4. UI 界面
; =========================
MyGui := Gui(, "AHK连发")
MyGui.SetFont("s10", "Microsoft YaHei")

MyGui.Add("Text", "", "间隔 (ms):")
EditMs := MyGui.Add("Edit", "vInterval x+10 w80", "200")
EditMs.OnEvent("Change", (*) => SaveConfig())

OpFrame := MyGui.Add("GroupBox", "xm w280 h60", "按键管理")
BtnRec := MyGui.Add("Button", "xp+10 yp+25 w90", "录制按键")
BtnRec.OnEvent("Click", StartRecord)
MyGui.Add("Button", "x+5 w50", "+左键").OnEvent("Click", (*) => AddKeyItem("lb"))
MyGui.Add("Button", "x+5 w50", "+右键").OnEvent("Click", (*) => AddKeyItem("rb"))
MyGui.Add("Button", "x+5 w50", "清空").OnEvent("Click", ClearList)

LV := MyGui.Add("ListView", "xm w280 h150 Checked",["", "按键名称"])
LV.ModifyCol(1, 30)
LV.ModifyCol(2, 230)
LV.OnEvent("ItemCheck", (*) => SaveConfig())

BtnToggle := MyGui.Add("Button", "xm w280 h40", "启动 ( `` )")
BtnToggle.SetFont("bold s11")
BtnToggle.OnEvent("Click", ToggleRunning)

MyGui.Add("Text", "xm w280 Center cGray", "F12 彻底退出")
MyGui.OnEvent("Close", (*) => ExitApp())

Global IsRunning := false

LoadConfig()
MyGui.Show()

; =========================
; 5. 功能逻辑
; =========================

StartRecord(*) {
BtnRec.Text := "请按键..."
ih := InputHook("L1 M T3")
ih.KeyOpt("{All}", "E")
ih.Start()
ih.Wait()

keyName := StrLower(ih.EndKey ? ih.EndKey : ih.Input)
BtnRec.Text := "录制按键"

if (keyName == "") {
return
}

if DD_KEY_MAP.Has(keyName) {
AddKeyItem(keyName)
} else {
ToolTip "未定义按键: " keyName
SetTimer (*) => ToolTip(), -2000
}
}

AddKeyItem(name, enabled := true) {
Loop LV.GetCount() {
if (LV.GetText(A_Index, 2) == name) {
return
}
}
LV.Add(enabled ? "Check" : "-Check", "", name)
SaveConfig()
}

ClearList(*) {
Global IsRunning
if (LV.GetCount() > 0) {
LV.Delete()
SaveConfig()
}
if IsRunning {
ToggleRunning()
}
}

ToggleRunning(*) {
Global IsRunning
if !IsRunning {
hasChecked := false
Loop LV.GetCount() {
if LV.GetNext(A_Index - 1, "Checked") {
hasChecked := true
break
}
}

if !hasChecked {
ToolTip "请先在列表里【打勾】需要连发的按键"
SetTimer (*) => ToolTip(), -2000
return
}

IsRunning := true
BtnToggle.Text := "停止 ( `` )"
BtnToggle.Opt("cRed")

ScheduleNext()
} else {
IsRunning := false
BtnToggle.Text := "启动 ( `` )"
BtnToggle.Opt("cDefault")
SetTimer(WorkLoop, 0)
}
}

ScheduleNext() {
interval := IsNumber(EditMs.Value) ? Integer(EditMs.Value) : 200
jitter := Random(-interval * 0.3, interval * 0.3)
delay := Max(10, interval + jitter)
SetTimer(WorkLoop, -delay)
}

WorkLoop() {
Global IsRunning
Static inLoop := false
if !IsRunning || inLoop {
return
}
inLoop := true

row := 0
Loop {
row := LV.GetNext(row, "Checked")
if !row {
break
}

itemName := LV.GetText(row, 2)
if !DD_KEY_MAP.Has(itemName) {
continue
}

code := DD_KEY_MAP[itemName]

if (itemName = "lb" || itemName = "rb" || itemName = "mid") {
DllCall(pDD_btn, "Int", code)
Sleep 10
DllCall(pDD_btn, "Int", code * 2)
} else {
DllCall(pDD_key, "Int", code, "Int", 1)
Sleep 10
DllCall(pDD_key, "Int", code, "Int", 2)
}
}
inLoop := false
if IsRunning {
ScheduleNext()
}
}

SaveConfig() {
if FileExist(CONFIG_PATH) {
FileDelete(CONFIG_PATH)
}

IniWrite(EditMs.Value, CONFIG_PATH, "Settings", "Interval")

keyStr := ""
Loop LV.GetCount() {
name := LV.GetText(A_Index, 2)
state := LV.GetNext(A_Index - 1, "Checked") ? "1" : "0"
keyStr .= name ":" state ","
}
IniWrite(RTrim(keyStr, ","), CONFIG_PATH, "Settings", "Keys")
}

LoadConfig() {
if !FileExist(CONFIG_PATH) {
return
}

try {
iv := IniRead(CONFIG_PATH, "Settings", "Interval", "200")
EditMs.Value := iv

keysData := IniRead(CONFIG_PATH, "Settings", "Keys", "")
if (keysData != "") {
for , item in StrSplit(keysData, ",") {
if (item = "") {
continue
}
parts := StrSplit(item, ":")
if (parts.Length == 2) {
AddKeyItem(parts[1], parts[2] == "1")
}
}
}
}
}

; =========================
; 6. 热键
; =========================
~SC029::ToggleRunning()
F12::ExitApp()

新旧版本对比

维度 旧版 (v1) 新版 (v2)
AHK 版本 v1 v2.0
界面 无(纯脚本) GUI 界面
按键管理 硬编码键码 录制 + 列表 + 勾选
支持按键数 1 个 不限(多键同时)
鼠标支持 不支持 左/右/中键
间隔调节 固定 20ms 可自定义
反作弊 30% 随机抖动
配置保存 INI 持久化
DD 加载 Class_DD.ahk 封装 直接 DllCall
权限处理 自动提权

使用方法

  1. 安装 AutoHotkey v2.0
  2. DD.dll 放到与脚本同一目录
  3. 双击运行脚本(自动以管理员身份运行)
  4. GUI 界面中:
    • 点击「录制按键」然后按一个键,加入列表
    • 或直接点「+左键」「+右键」添加鼠标按键
    • 在列表中 打勾 需要连发的按键
    • 设置间隔(默认 200ms)
  5. 按反引号键(`,ESC 下面那个)启动/停止连发
  6. 按 F12 退出

常见问题

Q: 提示”找不到 DD.dll”?
确保 DD.dll 与脚本文件在同一目录下。如果是从别处下载的 DD 驱动,注意区分 32 位 / 64 位版本。

Q: 驱动加载失败?
通常是杀毒软件拦截了 DD.dll。将脚本目录添加到杀软白名单,或临时关闭杀软后再试。

Q: 游戏里没反应?
部分游戏的反作弊系统会拦截 DD 驱动。确认游戏是否允许硬件级模拟输入。

Q: 想换个启动热键?
修改最后一行 ~SC029::ToggleRunning() —— SC029 是反引号键的扫描码。改为 ~F8::ToggleRunning() 就是 F8 键启动。

项目地址


更新于 2026-06-20:从 AHK v1 纯脚本全面升级为 AHK v2.0 GUI 版本。