qs-uvk5-firmware-modder/uvk5.py

138 wiersze
3.5 KiB
Python
Czysty Zwykły widok Historia

2023-05-17 19:43:24 +00:00
#!/usr/bin/env python3
from binascii import crc_hqx
from itertools import cycle
2023-06-10 05:59:55 +00:00
from pathlib import Path
2023-05-17 19:43:24 +00:00
from sys import argv
from time import time
from serial import Serial
2023-06-10 05:59:55 +00:00
from lib.encdec import eprint
DATA_DIR = Path(__file__).parent / 'data'
KEY = (DATA_DIR / 'comm-key.bin').read_bytes()
2023-05-18 18:11:36 +00:00
BLOCK_SIZE = 0x80
2023-05-17 19:43:24 +00:00
PREAMBLE = b'\xab\xcd'
POSTAMBLE = b'\xdc\xba'
CMD_VERSION_REQ = 0x0514
CMD_VERSION_RES = 0x0515
2023-05-17 20:26:21 +00:00
CMD_SETTINGS_REQ = 0x051B
CMD_SETTINGS_RES = 0x051C
2023-05-17 19:43:24 +00:00
2023-05-18 19:41:27 +00:00
CMD_SETTINGS_WRITE_REQ = 0x051D # then addr (0x0E70) then size (0x0160) then data
2023-05-18 18:11:36 +00:00
2023-05-18 19:41:27 +00:00
TIMESTAMP = int(time()).to_bytes(4, 'little')
2023-05-17 19:43:24 +00:00
2023-05-17 21:17:24 +00:00
def xor(var):
return bytes(a ^ b for a, b in zip(var, cycle(KEY)))
def i2b16(cmd_id):
return cmd_id.to_bytes(2,'little')
def b2i(data):
return int.from_bytes(data, 'little')
def len16(data):
return i2b16(len(data))
def crc16(data):
return i2b16(crc_hqx(data, 0))
def chunk(data, n):
for i in range(0,len(data), n):
yield data[i:i+n]
2023-05-17 21:17:24 +00:00
def cmd_make_req(cmd_id, body=b''):
2023-05-18 18:11:36 +00:00
data = body + TIMESTAMP
2023-05-17 21:17:24 +00:00
payload = i2b16(cmd_id) + len16(data) + data
encoded_payload = xor(payload + crc16(payload))
return PREAMBLE + len16(payload) + encoded_payload + POSTAMBLE
2023-05-17 20:26:21 +00:00
class UVK5(Serial):
def __init__(self, port: str | None = None) -> None:
super().__init__(port, 38400, timeout=5)
2023-05-17 19:43:24 +00:00
2023-06-10 05:59:55 +00:00
def get_version(self):
return self.cmd(CMD_VERSION_REQ)[1][:10].decode().rstrip('\x00')
2023-05-18 18:11:36 +00:00
def read_mem(self, offset, size):
return self.cmd(CMD_SETTINGS_REQ, i2b16(offset) + i2b16(size))
2023-05-17 19:43:24 +00:00
2023-05-17 20:26:21 +00:00
def cmd(self, id, body = b''):
2023-05-17 21:17:24 +00:00
self.write(cmd_make_req(id, body))
2023-05-17 20:26:21 +00:00
preamble = self.read(2)
2023-05-17 19:43:24 +00:00
2023-05-17 20:26:21 +00:00
if preamble != PREAMBLE:
2023-05-18 18:11:36 +00:00
raise ValueError('Bad response (PRE)')
2023-05-17 19:43:24 +00:00
2023-05-18 18:11:36 +00:00
payload_len = b2i(self.read(2)) + 2 # CRC len
payload = xor(self.read(payload_len))
2023-05-17 19:43:24 +00:00
2023-05-18 18:11:36 +00:00
# crc = payload[-2:]
2023-05-17 20:26:21 +00:00
postamble = self.read(2)
if postamble != POSTAMBLE:
2023-05-18 18:11:36 +00:00
raise ValueError('Bad response (POST)')
2023-05-17 20:26:21 +00:00
# print(data.hex())
2023-05-18 18:11:36 +00:00
cmd_id = b2i(payload[:2])
data_len = b2i(payload[2:4])
data = payload[4:4+data_len]
2023-05-17 19:43:24 +00:00
2023-05-18 18:11:36 +00:00
return (cmd_id, data)
2023-05-17 19:43:24 +00:00
2023-06-10 05:59:55 +00:00
def read_channels_settings(self):
names = []
settings = []
2023-06-10 05:59:55 +00:00
data_size = 16 * 200
names_offset = 0x0F50
settings_offset = 0x0000
2023-06-10 05:59:55 +00:00
passes = data_size//BLOCK_SIZE
2023-06-10 05:59:55 +00:00
for block in range(passes):
offset = names_offset + block*BLOCK_SIZE
names_set = self.read_mem(offset, BLOCK_SIZE)[1][4:]
names += [name.decode(errors='ignore').rstrip('\x00') for name in chunk(names_set, 16)]
2023-06-10 05:59:55 +00:00
for block in range(passes):
offset = settings_offset + block*BLOCK_SIZE
settings_set = self.read_mem(offset, BLOCK_SIZE)[1][4:]
settings += [(b2i(setting[:4])/100000.0, ) for setting in chunk(settings_set, 16)]
for i, name in enumerate(names):
if name:
print(f'{i+1:0>3}. {name: <16} {settings[i][0]:0<8} M')
else:
print(f'{i+1:0>3}. -')
2023-06-10 05:59:55 +00:00
def main(port, cmd, args):
2023-05-17 20:26:21 +00:00
with UVK5(port) as s:
2023-06-10 05:59:55 +00:00
print('FW version:', s.get_version())
s.read_channels_settings()
2023-05-18 19:41:27 +00:00
# data = s.read_mem(0x0E70, 0x80)[1]
# print('0x%x' % b2i(data[:2]), '0x%x' % b2i(data[2:4]), data[4:])
2023-05-17 19:43:24 +00:00
if __name__ == '__main__':
2023-06-10 05:59:55 +00:00
if len(argv) < 3:
eprint(f'Usage: {argv[0]} <port> <command> [args]')
exit(255)
main(argv[1], argv[2], argv[3:])