ps5手柄usb&蓝牙流量协议

打Π被手把✌锁破防了,于是斥巨资成为手把练习生。

初步分析

抓了一下手柄的流量
在descriptor response device 中可以找到流量所属的产品名称以及相关信息
image.png
可以发现每个interrupt 输入都是91byte
而hid data都是64byte
image.png

在google寻找相关ps5 usb协议的资料

找到了https://github.com/nondebug/dualsense?tab=readme-ov-file
其中https://github.com/nondebug/dualsense/blob/main/dualsense-explorer.html 是一个手柄测试界面,调用了webapi来连接手柄,可以实时显示手柄的输入和输出数据
image.png
连上发现,在我把手柄放在桌子上时没有任何操作,输入有些字节一直在变化
往下可以看到,是陀螺仪的数据在变化,可以猜测,变化的哪些字节代表着陀螺仪的数据,而剩下大致不变字节的则是按键的数据
image.png

根据该示例进行研究

image.png
这是我抓取的一个hid data 此时我按下了十字按键(ps :由于陀螺仪一直变化的原因,所以抓包的时候是实时的,而不是按下了某个按键才会抓取,所以流量很多)

1
01 82 7d 82 80 00 00 33 28 00 00 00 e5 8d 07 6f 00 00 ff ff ff 1d 00 a0 1f fc 4d 73 30 e0 3f f8 00 00 00 08 00 00 00 00 50 40 00 00 00 01 04 50 e0 32 91 80 9e f5 95 00 c7 3b cd 25

根据map :
第0个字节01是固定的
第1个字节82代表左摇杆x轴上的数据 80代表在中间位置 从左到右 最左left: 0x00 到 最右ight: 0xff
第2个字节代表左摇杆y轴上的数据 规则同上
下面第3第4字节是右摇杆的数据规则同上
第五第六字节代表的是左右扳机的数据 未按下时 neutral: 0x00 按到底时pressed: 0xff
第七字节分析流量包每一个数据发现是从第一个hid data开始往下按+1递增的
第8字节的0-3bit代表的是方向键(byte->bit 就是转为8位的2进制数)
方向规则如下

1
neutral: 0x8, N: 0x0, NE: 0x1, E: 0x2, SE: 0x3, S: 0x4, SW: 0x5, W: 0x6, NW: 0x7

这里要看后一位数字 如该字节是08 则表示没有按下,00表示按下了指向北方的按键
第8字节第4bit开始是右边4个图形按键的数据
例如我上面按下十字按键时是28
image.png
可能是是因为usb传输读取数据顺序的问题,这里要倒着来读 即从右往左第5bit是1
根据

1
byte 8, bit 5: Button / 0x02 - 1 bit - Cross button

可以知道按下了十字按键 即按下了什么按键,该对应的bit位就是1
后面的按键以此类推
如果同时按下了多个按键,分析方法也一样
一个简单的脚本解析按键

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
def decode_hid_data(hex_string):
# 将十六进制字符串转换为字节串
bytes_data = bytes.fromhex(hex_string)

# 解析数据包
report_id = bytes_data[0]
left_stick_x = bytes_data[1]
left_stick_y = bytes_data[2]
right_stick_x = bytes_data[3]
right_stick_y = bytes_data[4]
l2_trigger = bytes_data[5]
r2_trigger = bytes_data[6]
vendor_defined = bytes_data[7]
# 解析 Hat switch 按键
hat_switch = bytes_data[8] & 0x0F
hat_direction = ""
if hat_switch == 0x0:
hat_direction = "N"
elif hat_switch == 0x1:
hat_direction = "NE"
elif hat_switch == 0x2:
hat_direction = "E"
elif hat_switch == 0x3:
hat_direction = "SE"
elif hat_switch == 0x4:
hat_direction = "S"
elif hat_switch == 0x5:
hat_direction = "SW"
elif hat_switch == 0x6:
hat_direction = "W"
elif hat_switch == 0x7:
hat_direction = "NW"
elif hat_switch == 0x8:
hat_direction = "Neutral"
square_button = (bytes_data[8] >> 4) & 0x01
cross_button = (bytes_data[8] >> 5) & 0x01
circle_button = (bytes_data[8] >> 6) & 0x01
triangle_button = (bytes_data[8] >> 7) & 0x01
l1_button = bytes_data[9] & 0x01
r1_button = (bytes_data[9] >> 1) & 0x01
l2_button = (bytes_data[9] >> 2) & 0x01
r2_button = (bytes_data[9] >> 3) & 0x01
create_button = (bytes_data[9] >> 4) & 0x01
options_button = (bytes_data[9] >> 5) & 0x01
l3_button = (bytes_data[9] >> 6) & 0x01
r3_button = (bytes_data[9] >> 7) & 0x01
ps_button = bytes_data[10] & 0x01
touchpad_button = (bytes_data[10] >> 1) & 0x01
mute_button = (bytes_data[10] >> 2) & 0x01

# 输出按键状态
print("Square" if square_button else "")
print("Cross" if cross_button else "")
print("Circle" if circle_button else "")
print("Triangle" if triangle_button else "")
print(hat_direction if hat_direction else "")
print("L1" if l1_button else "")
print("R1" if r1_button else "")
print("L2" if l2_button else "")
print("R2" if r2_button else "")
print("Create" if create_button else "")
print("Options" if options_button else "")
print("L3" if l3_button else "")
print("R3" if r3_button else "")
print("PS" if ps_button else "")
print("Touchpad" if touchpad_button else "")
print("Mute" if mute_button else "")

# 示例数据包的十六进制字符串表示
hex_string = "01827d828000003c23000000ee8d076ffffffeff00001c007d1fe8042ed60f03ff80000000800000000005040000000000f0ea0f032918008a72a3693ff4dec1"

# 解码并输出按键
decode_hid_data(hex_string)

image.png

蓝牙流量分析

这里的map跟usb的稍有不同,但规则是一样的

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
byte 0, bit 0: Report ID - 8 bits
always 0x01
byte 1, bit 0: Generic Desktop / X - 8 bits - left stick X axis
left: 0x00, right: 0xff, neutral: ~0x80
byte 2, bit 0: Generic Desktop / Y - 8 bits - left stick Y axis
up: 0x00, down: 0xff, neutral: ~0x80
byte 3, bit 0: Generic Desktop / Z - 8 bits - right stick X axis
left: 0x00, right: 0xff, neutral: ~0x80
byte 4, bit 0: Generic Desktop / Rz - 8 bits - right stick Y axis
up: 0x00, down: 0xff, neutral: ~0x80
byte 5, bit 0: Generic Desktop / Hat switch - 4 bits - directional buttons
neutral: 0x8, N: 0x0, NE: 0x1, E: 0x2, SE: 0x3, S: 0x4, SW: 0x5, W: 0x6, NW: 0x7
byte 5, bit 4: Button / 0x01 - 1 bit - Square button
byte 5, bit 5: Button / 0x02 - 1 bit - Cross button
byte 5, bit 6: Button / 0x03 - 1 bit - Circle button
byte 5, bit 7: Button / 0x04 - 1 bit - Triangle button
byte 6, bit 0: Button / 0x05 - 1 bit - L1 button
byte 6, bit 1: Button / 0x06 - 1 bit - R1 button
byte 6, bit 2: Button / 0x07 - 1 bit - L2 button
byte 6, bit 3: Button / 0x08 - 1 bit - R2 button
byte 6, bit 4: Button / 0x09 - 1 bit - Create button
byte 6, bit 5: Button / 0x0a - 1 bit - Options button
byte 6, bit 6: Button / 0x0b - 1 bit - L3 button
byte 6, bit 7: Button / 0x0c - 1 bit - R3 button
byte 7, bit 0: Button / 0x0d - 1 bit - PS button
byte 7, bit 1: Button / 0x0e - 1 bit - Touchpad button
byte 7, bit 2: Vendor defined 0xFF00 / 0x21 - 6 bits
byte 8, bit 0: Generic Desktop / Rx - 8 bits - L2 axis
neutral: 0x00, pressed: 0xff
byte 9, bit 0: Generic Desktop / Ry - 8 bits - R2 axis
neutral: 0x00, pressed: 0xff

这里同样可以用https://github.com/nondebug/dualsense/blob/main/dualsense-explorer.html
本地搭建就可以了
image.png
此时我按下了L1按键,可以看到
31 41 81 81 83 80 00 00 01 08 01 00 00 第10byte发生了变化
这里的第0 byte是31 而byte 0, bit 0: Report ID - 8 bits always 0x01
貌似有点对不上,wireshark抓包看看(管理员运行)
wireshark抓蓝牙不要选蓝牙网络连接(抓不到数据)
image.png
选择usbpcap1并勾选 bluetooth
image.png
往下拉可以看到手柄设备
image.png
image.png
抓包时我按下的是L1,

1
2
   31 41 81 81 83 80 00 00 01 08 01 00 00
a1 31 e1 80 81 83 80 00 00 01 08 01 00 00 9ee81a8afbffb4fca901aa0c371ee8fabd8741de0f800000008000000000040400000000331d8d41de070400bcae8b7492ff450e0000000000000000008a6f3839

可以看到input数据跟上面测试程序的数据是一样的
这跟https://github.com/nondebug/dualsense?tab=readme-ov-file里蓝牙input为10byte 不对应
image.png
而且在流里面没发现设备名称,貌似无法像usb那样识别出设备
以下是根据测试程序写的map
Sample Bluetooth input report, all inputs neutral (10 bytes):
31 41 81 81 83 80 00 00 01 08 01 00 00

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
byte 0, bit 0: Report ID - 8 bits
always 0x31
byte 2, bit 0: Generic Desktop / X - 8 bits - left stick X axis
left: 0x00, right: 0xff, neutral: ~0x80
byte 3, bit 0: Generic Desktop / Y - 8 bits - left stick Y axis
up: 0x00, down: 0xff, neutral: ~0x80
byte 4, bit 0: Generic Desktop / Z - 8 bits - right stick X axis
left: 0x00, right: 0xff, neutral: ~0x80
byte 5, bit 0: Generic Desktop / Rz - 8 bits - right stick Y axis
up: 0x00, down: 0xff, neutral: ~0x80
byte 9, bit 0: Generic Desktop / Hat switch - 4 bits - directional buttons
neutral: 0x8, N: 0x0, NE: 0x1, E: 0x2, SE: 0x3, S: 0x4, SW: 0x5, W: 0x6, NW: 0x7
byte 9, bit 4: Button / 0x01 - 1 bit - Square button
byte 9, bit 5: Button / 0x02 - 1 bit - Cross button
byte 9, bit 6: Button / 0x03 - 1 bit - Circle button
byte 9, bit 7: Button / 0x04 - 1 bit - Triangle button
byte 10, bit 0: Button / 0x05 - 1 bit - L1 button
byte 10, bit 1: Button / 0x06 - 1 bit - R1 button
byte 10, bit 2: Button / 0x07 - 1 bit - L2 button
byte 10, bit 3: Button / 0x08 - 1 bit - R2 button
byte 10, bit 4: Button / 0x09 - 1 bit - Create button
byte 10, bit 5: Button / 0x0a - 1 bit - Options button
byte 10, bit 6: Button / 0x0b - 1 bit - L3 button
byte 10, bit 7: Button / 0x0c - 1 bit - R3 button
byte 11, bit 0: Button / 0x0d - 1 bit - PS button
byte 11, bit 1: Button / 0x0e - 1 bit - Touchpad button
byte 7, bit 2: Vendor defined 0xFF00 / 0x21 - 6 bits
byte 6, bit 0: Generic Desktop / Rx - 8 bits - L2 axis
neutral: 0x00, pressed: 0xff
byte 7, bit 0: Generic Desktop / Ry - 8 bits - R2 axis
neutral: 0x00, pressed: 0xff

就是按键byte里的bit规则不变但是数据的byte位变了

蓝牙协议还未详细深入学习,至此,下播了


ps5手柄usb&蓝牙流量协议
http://example.com/2024/04/26/ps5手柄usb&蓝牙流量协议/
作者
J_0k3r
发布于
2024年4月26日
许可协议
BY J_0K3R