forked from shuffle2/nxpad
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tool.py
180 lines (164 loc) · 6.2 KB
/
tool.py
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
import usb.core
import usb.util
import array
import struct
import sys
import binascii
import time
from construct import *
class ProController:
USB_BUF_LEN = 64
# pro controller, app fw
DEV_ID = (0x057E, 0x2009)
# pro controller, bl fw
DEV_ID_BL = (0x057E, 0x200f)
def __init__(s):
s.wait_for_device(s.DEV_ID)
def wait_for_device(s, dev_id):
s.dev = usb.core.find(idVendor=dev_id[0], idProduct=dev_id[1])
while s.dev is None:
time.sleep(1)
s.dev = usb.core.find(idVendor=dev_id[0], idProduct=dev_id[1])
# fw does actually seem to support sending these cmds via ep0 under some condition,
# but using a nonzero ep makes it more simple.
def usb_write(s, cmd): return s.dev.write(1 | usb.util.ENDPOINT_OUT, cmd)
def usb_read_all(s):
try:
return s.dev.read(1 | usb.util.ENDPOINT_IN, s.USB_BUF_LEN, 200)
except usb.core.USBError:
return array.array('B', [0] * s.USB_BUF_LEN)
class UsbResponse:
def __init__(s, pkt, data_len):
#print('pkt: %s' % (binascii.hexlify(pkt)))
s.cmd_type = pkt[0]
s.cmd = pkt[1]
s.status = pkt[2]
s.data = pkt[3 : 3 + data_len]
def usb_cmd(s, cmd, resp_len = USB_BUF_LEN):
s.usb_write(cmd)
if resp_len == 0: return None
resp = None
while True:
resp = s.UsbResponse(s.usb_read_all(), resp_len)
if resp.cmd_type == cmd[0] | 1 and resp.cmd == cmd[1]:
break
# app mainloop resets usb and sends empty device_id_response in case of error...
# need to check for that specficially and give up.
if (resp.cmd_type, resp.cmd) == (0x81, 0x01) and resp.status != 0:
return None
# fw may respond with old data (e.g. if it's going through reset), so
# we simply resend cmd until a decently-related looking response comes back.
# fw could also be throwing us a lot of uart spew, which we want to skip.
s.usb_write(cmd)
if resp.status != 0:
print('resp %02x:%02x error %x' % (resp.cmd_type, resp.cmd, resp.status))
return resp.data
# returns device_id_response
def cmd_80_01(s): return s.usb_cmd([0x80, 0x01], 8)
# returns static 4 bytes
def cmd_80_07(s): return s.usb_cmd([0x80, 0x07], 4)
# returns field_1B5
def cmd_80_08(s): return s.usb_cmd([0x80, 0x08], 1)
# set HSITRIM, returns new HSICAL
# see STM DocID15965
def cmd_80_a0(s, hsi_trim): return s.usb_cmd([0x80, 0xa0, hsi_trim & 0x1f], 1)
# set or clear comms_running. returns True if change was made successfully.
def cmd_80_02(s): return s.usb_cmd([0x80, 0x02]) is not None
def cmd_80_03(s): return s.usb_cmd([0x80, 0x03]) is not None
# set or clear poll_enabled. if comms_running, will start relaying uart data out usb
def cmd_80_04(s): s.usb_cmd([0x80, 0x04], 0)
def cmd_80_05(s): s.usb_cmd([0x80, 0x05], 0)
# resets uart comms and usb state
# returns True if it was stopped (else it probably wasn't running)
def cmd_80_06(s): return s.usb_cmd([0x80, 0x06]) is not None
# forwards the encapsulated uart cmd
def cmd_80_91(s): return s.usb_cmd([0x80, 0x91])
def cmd_80_92(s): return s.usb_cmd([0x80, 0x92])
# forwards as uart cmd 92_00.
# Note response buffer is from uart (may not match cmd_type/cmd?)
# Note cmds are only processed if comms_running
def cmd_01(s): return s.usb_cmd([0x01])
def cmd_10(s): return s.usb_cmd([0x10])
def reacquire_device(s, dev_id):
time.sleep(1)
s.wait_for_device(dev_id)
# note these apply to STM only
# erases eeprom@0 and resets
def enter_dfu_and_reset(s):
print('do not enter dfu unless you have a fw image to flash')
return
s.usb_cmd([0x82, 1], 0)
s.reacquire_device(s.DEV_ID_BL)
# just resets
def reset(s):
s.usb_cmd([0x82, 2], 0)
s.reacquire_device(s.DEV_ID)
def uart_forward(s, cmd, subcmd, buf):
# fw will calculate crcs for us
hdr = Struct('uart_crc_hdr',
ULInt8('cmd'),
ULInt8('subcmd'),
ULInt16('trailing_len'),
ULInt8('error'),
ULInt8('crc_data'),
ULInt8('crc_hdr')
).build(Container(
cmd = cmd,
subcmd = subcmd,
trailing_len = len(buf),
error = 0,
crc_data = 0,
crc_hdr = 0,
)
)
return s.usb_cmd(struct.pack('B', 0x80) + hdr + buf)
def brcm_cmd_01(s, subcmd, buf):
hdr = Struct('brcm_hdr',
ULInt8('cmd'),
Bytes('rumble_base', 9),
ULInt8('subcmd'),
).build(Container(
cmd = 0x01,
rumble_base = bytes([0x00, 0x00, 0x01, 0x40, 0x40, 0x00, 0x01, 0x40, 0x40]),
subcmd = subcmd,
)
)
return s.uart_forward(0x92, 0x00, hdr + buf)
def brcm_spi_read(s, offset, size):
buf = Struct('spi_read_cmd',
ULInt32('offset'),
ULInt8('size'),
Padding(0x20)
).build(Container(offset = offset, size = size))
return s.brcm_cmd_01(0x10, buf)
def brcm_spi_dump(s, fname):
dump = []
SPI_FLASH_SIZE = 0x80000
MAX_SPI_XFER = 0x1d
offset = 0
while offset < SPI_FLASH_SIZE:
# +0x16 looks like it might be a spi_read_cmd, but offset is
# always 0 and size is always 0x20...
resp_offset = 0x16 + 4 + 1
read_len = min(MAX_SPI_XFER, SPI_FLASH_SIZE - offset)
data = s.brcm_spi_read(offset, read_len)[resp_offset:resp_offset+read_len]
offset += read_len
dump.append(data)
print('%4x %s' % (offset, binascii.hexlify(data)))
with open(fname, 'wb') as f:
f.write(b''.join(dump))
c = ProController()
print(binascii.hexlify(c.cmd_80_08()))
print(binascii.hexlify(c.cmd_80_07()))
print(binascii.hexlify(c.cmd_80_01()))
c.cmd_80_02()
#c.cmd_80_04()
'''
i = 0
while True:
buf = c.dev.read(1|usb.util.ENDPOINT_IN, c.USB_BUF_LEN)
print(binascii.hexlify(buf))
if i == 100:
break
i += 1
#'''