Фикс для downloader.py для работы с чипами серии EPIC.
Чтение работает отлично, а вот запись работает странно: бьются следующие области:
0x00000–0x0FFFF OK
0x10000–0x1FFFF битый
0x20000–0x2FFFF OK
0x30000–0x3FFFF битый
0x40000–0x4FFFF OK
0x50000–0x5FFFF битый
0x60000 + в основном OK.
Так что при записи лучше перепроверять и перезаписывать битые области.Скрипт для разделения областей которые бьются конкретно у меня:
Ну и пишем их в чип:
Нашёл причину, программа полностью не стирала чип.
Пофикшенный download.py
from bluetrum.cipher import ab_calckey
from bluetrum.utils import *
import struct
import argparse
from base64 import b64decode
from tqdm import tqdm
###############################################################################
try:
from serial import Serial
from bluetrum.dl.uart import UARTDownload
have_uart = True
except ImportError:
have_uart = False
try:
from scsiio import SCSIDev
have_scsi = True
except ImportError:
have_scsi = False
if not have_uart and not have_scsi:
print('No available ways to communicate with the hardware.')
print('Install pyserial for UART or rip "scsiio" from jl-uboot-tool for USB MSC.')
exit(1)
###############################################################################
ap = argparse.ArgumentParser(description='Tool to communicate with the bootloader in the Bluetrum chips.')
if have_uart:
ap.add_argument('--init-baud', type=int, default=115200,
help='Initial baudrate (default: %(default)d baud)')
ap.add_argument('--baud', type=int, default=921600,
help='Baudrate to use (default: %(default)d baud)')
ap.add_argument('--port',
help='Serial port to use for UART bootloader')
ap.add_argument('--no-echo', action='store_true', default=False,
help='Disable echo (for CRWN/AB530X chips)')
if have_scsi:
ap.add_argument('--mscdev',
help='USB MSC (SCSI) device to use for USB bootloader')
ap.add_argument('-r', '--reboot', action='store_true',
help='Reboot the chip after completion')
ap.add_argument('--debug', action='store_true', default=False,
help='Enable extra diagnostic output')
actsp = ap.add_subparsers(dest='action')
asp_erase = actsp.add_parser('erase', help='Erase one or more flash areas')
asp_erase.add_argument('areas', metavar='address size', nargs='+',
help='Erase <size> bytes at <address>.'
' If <size> is 0, assumed to be whole flash.')
asp_read = actsp.add_parser('read', help='Read the flash into the file')
asp_read.add_argument('areas', metavar='address size file', nargs='+',
help='Read <size> bytes from <address> into <file>.'
' If <size> is 0, assumed to be whole flash.')
asp_write = actsp.add_parser('write', help='Write the file into flash')
asp_write.add_argument('areas', metavar='address file', nargs='+',
help='Write <file> starting at <address>.')
args = ap.parse_args()
###############################################################################
# PRAO (AB560x) — hand-built blob; bytes [4..23] are reserved for
# chipid / iface / blocksize injection at load time.
dl_blob = b64decode(
"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"
)
# CRWN (AB530x) — decrypted uartdown-001.dll.
# Bytes [4..23] contain live UART helper functions used by the blob itself;
# do NOT overwrite them at load time (unlike the PRAO blob).
dl_blob_crwn = b64decode(
"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"
"AAAAAAAAAAAAAAAA"
)
###############################################################################
# EPIC (AB532x / BLUEEPIC) — extracted from official Downloader v2.2.0
# during a real erase+write session. On-wire data (already prepared).
# Do NOT patch header at load time (same as CRWN).
dl_blob_epic = b64decode(
"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"
)
class BlCmd:
IFACE_PARAM = 0x50
AUTHORIZE = 0x55
MEM_WRITE = 0x57
SET_CMD_HANDLER = 0x58
GET_INFO = 0x5A
REBOOT = 0x5E
class NitDlCmd:
"""Command codes for PRAO (AB560x) blob."""
INIT = 0x00
DEV_READ = 0x01
DEV_WRITE = 0x02
DEV_ERASE = 0x03
class NitDlCmdCRWN:
"""Command codes for CRWN (AB530x) blob (uartdown-001.dll)."""
INIT = 0x00 # returns 4 bytes: [status, ?, density, ?]
DEV_READ = 0x28 # arg1=addr arg3=n_blocks(x512B) recv=n_blocks*512
DEV_WRITE = 0x2a # arg1=addr arg3=1 send=512B recv=1 (one block)
DEV_ERASE = 0x2f # arg1=addr arg3=1 recv=512 (one 64K block)
GET_INFO = 0x25 # recv=16 → bytes [0xC..0xE] = JEDEC device ID
def make_cb(cmd, arg1=0, arg2=0, arg3=0):
return struct.pack('>BIBH', cmd, arg1, arg2, arg3)
def patch_crwn_blob(blob_bytes):
"""
Fix six bugs in the CRWN blob (uartdown-001.dll).
Each patch replaces one wrong instruction with the correct one.
All offsets are relative to the start of the decrypted blob binary.
P3 READ 0x0B3C BLTU x21,x18 → NOP
Removes a hardcoded address ceiling (0x1E00) that caused the READ
loop to return zeros for any flash address >= 0x1E00.
P4a INIT 0x04D6 addi a5,a5,-0x1C4 → addi a5,a5,+0x2D6
In the flash-detection fallback path, INIT stores a do-nothing stub
at all iface function slots. This patch redirects the iface[0x10]
store to the real flash_erase function (VA 0x132D6).
P4b INIT 0x04EA addi a5,a5,-0x1C4 → addi a5,a5,+0x266
Same fallback path — redirects the iface[0x0C] store to the real
flash_write function (VA 0x13266).
P5 ERASE 0x0C40 c.beqz a5,+70 → c.nop
A flag check (iface[0x18].bit3) gates the DMA erase path. The flag
is never set on this chip variant so the handler always branched to
a no-op. This patch makes it always take the DMA path.
P6 ERASE 0x0C42 lw a5,8(s4) → lw a5,0x10(s4)
The handler was loading iface[8] (flash_read pointer) into a5 and
calling it as if it were flash_erase. Changed load offset from 8 to
0x10 so it loads iface[0x10] (flash_erase pointer) instead.
P7 WRITE 0x0BB8 c.beqz a5,+14 → c.nop
A bit1 guard inside the per-block success path skipped the
flash_write call when the flag was clear. The flag is never set by
INIT on this chip. Removed so flash_write is always called.
P8 ERASE 0x0C4E addi a0,s8,0x2B8 → addi a0,zero,0xD8
The handler passed a buffer pointer as a0 to flash_erase. The low
byte (0xB8) is not a valid SPI opcode so the flash ignored every
erase command. Replaced with the correct 64K block-erase opcode
(0xD8).
"""
b = bytearray(blob_bytes)
def patch(off, expected, replacement, name):
exp = bytes(expected)
assert b[off:off+len(exp)] == exp, (
f'CRWN blob {name}: expected {exp.hex()} at {off:#06x}, '
f'got {b[off:off+len(exp)].hex()}')
b[off:off+len(replacement)] = bytes(replacement)
patch(0x0b3c, [0x63,0xee,0x2a,0x01], [0x13,0x00,0x00,0x00], 'P3')
patch(0x04d6, [0x93,0x87,0xc7,0xe3], [0x93,0x87,0x67,0x2d], 'P4a')
patch(0x04ea, [0x93,0x87,0xc7,0xe3], [0x93,0x87,0x67,0x26], 'P4b')
patch(0x0c40, [0xb9,0xc3], [0x01,0x00], 'P5')
patch(0x0c42, [0x83,0x27,0x8a,0x00], [0x83,0x27,0x0a,0x01], 'P6')
patch(0x0bb8, [0x99,0xc7], [0x01,0x00], 'P7')
patch(0x0c4e, [0x13,0x05,0x8c,0x2b], [0x13,0x05,0x80,0x0d], 'P8')
return bytes(b)
def do_the_stuff(execcmd, blocksize, iface, udl=None):
# -------------------------------------------------------------------------
# Bootloader handshake
# -------------------------------------------------------------------------
resp = execcmd(make_cb(BlCmd.GET_INFO, arg1=0x5259414E, arg3=0x67ca), recv=24)
chipid, loadaddr, commskey, _ = struct.unpack('>12sIII', resp)
print(f' Chip ID: {chipid}')
print(f' Load address: ${loadaddr:08X}')
print(f' Init. commkey: ${commskey:08X}')
resp = execcmd(make_cb(BlCmd.AUTHORIZE, arg1=ab_calckey(commskey)), recv=4)
commskey, = struct.unpack('>I', resp)
print(f' New commkey: ${commskey:08X}')
if iface == 'uart' and args.baud != args.init_baud:
print(f'Changing baudrate to {args.baud} baud...')
execcmd(make_cb(BlCmd.IFACE_PARAM, arg2=0xf0), recv=2)
execcmd(make_cb(BlCmd.IFACE_PARAM, arg1=args.baud, arg2=0x02),
recv=2, switch_baud=args.baud)
# -------------------------------------------------------------------------
# Load and patch blob
# -------------------------------------------------------------------------
is_crwn = b'CRWN' in chipid
is_epic = b'EPIC' in chipid
is_zoom = b'ZOOM' in chipid
# CRWN-family: CRWN + EPIC + ZOOM use the same agent/command set
use_crwn = is_crwn or is_epic or is_zoom
if use_crwn:
Cmd = NitDlCmdCRWN
blob_data = patch_crwn_blob(dl_blob_crwn)
tag = 'CRWN' if is_crwn else ('EPIC' if is_epic else 'ZOOM')
print(f'[*] Using CRWN agent blob for {tag} ({len(blob_data)} bytes)')
else:
Cmd = NitDlCmd
blob_data = dl_blob
print('[*] Using PRAO agent blob')
data = bytearray(blob_data) + b'\x00' * align_by(len(blob_data), blocksize)
# PRAO template needs header patch; CRWN-family blobs are pre-built
if not use_crwn:
struct.pack_into('<12s4sI', data, 4, chipid, iface.encode(), blocksize)
execcmd(make_cb(BlCmd.MEM_WRITE, arg1=loadaddr,
arg3=(len(data) // blocksize)), send=data)
execcmd(make_cb(BlCmd.SET_CMD_HANDLER, arg1=loadaddr))
# ZOOM (and some other agents) reset the rolling counter to 0 after
# takeover. Resync so the next command (INIT) is not rejected.
# Counter resync for ZOOM is handled in uart.py (_recv_token_packet)
# -------------------------------------------------------------------------
# Blob init — detect flash, print chip info
# -------------------------------------------------------------------------
if use_crwn:
if is_zoom:
# ZOOM: INIT ACKs but does not return the 4-byte payload.
# Skip data recv and probe flash directly.
print('[*] ZOOM: INIT without payload, probing flash...')
try:
execcmd(make_cb(Cmd.INIT, arg1=0)) # ACK only
except Exception as e:
print(f'[!] INIT warning: {e}')
codekey = 0
flash_id = 0
flash_uid = None
fsize = 0x100000 # assume 1MB, adjust if needed
# Probe: try reading first 512 bytes at address 0
try:
probe = execcmd(make_cb(Cmd.DEV_READ, arg1=0, arg3=1), recv=512)
print(f'[+] DEV_READ @0 ok, first 16: {probe[:16].hex()}')
nonzero = sum(1 for b in probe if b != 0)
print(f' non-zero bytes: {nonzero}/512')
except Exception as e:
print(f'[!] DEV_READ @0 failed: {e}')
probe = None
print(f'- Code key: >>>> {codekey:08X} <<<< (unknown on ZOOM)')
print(f'- Flash size: {fsize} bytes (assumed)')
else:
init_resp = execcmd(make_cb(Cmd.INIT, arg1=0), recv=4)
density = init_resp[2] or init_resp[1]
fsize = (1 << (density + 6)) if density else None
# Codekey is derived by INIT from flash[0x1FC..0x1FD] using:
# val = uint16_LE( flash[0x1FC] )
# codekey = (~val & 0xFFFF) ^ (val << 16) ^ 0x594B5048
# This mirrors what the chip writes to SFR 0x348 (flash DMA key).
raw_ck = execcmd(make_cb(Cmd.DEV_READ, arg1=0x1fc, arg3=1), recv=512)
val = struct.unpack_from('<H', raw_ck, 0)[0]
codekey = ((~val & 0xffff) ^ (val << 16) ^ 0x594b5048) & 0xffffffff
# CMD 0x25 reads the JEDEC device ID via ROM 0x80064 (opcode 0x9F).
try:
resp25 = execcmd(make_cb(Cmd.GET_INFO), recv=16)
flash_id = struct.unpack_from('>I', resp25, 0xc)[0] & 0xffffff
flash_uid_raw = resp25[0:8]
flash_uid = flash_uid_raw if any(b != 0 for b in flash_uid_raw) else None
except Exception:
flash_id = 0
flash_uid = None
print(f'- Code key: >>>> {codekey:08X} <<<<')
if flash_id:
print(f'- Flash device ID: {flash_id:06X}')
if flash_uid:
print(f'- Flash unique ID: {flash_uid.hex()}')
if fsize:
print(f'- Flash size: {fsize} bytes')
else:
print('- Flash size: unknown')
else:
codekey, flashid, flashuid = struct.unpack(
'II16s', execcmd(make_cb(Cmd.INIT), recv=48))
density = flashid & 0xff
fsize = (1 << density) if 0x10 <= density <= 0x18 else None
print(f'- Code key: >>>> {codekey:08X} <<<<')
print(f'- Flash device ID: {flashid:06X}')
print(f'- Flash unique ID: {flashuid.hex()}')
if fsize:
print(f'- Flash size: {fsize} bytes')
else:
print('- Flash size: unknown')
# -------------------------------------------------------------------------
# Flash operations
# -------------------------------------------------------------------------
def do_dev_erase(addr, size):
"""Erase a flash region, choosing 64K or 4K blocks automatically."""
saddr = addr & ~0xFFF
eaddr = (addr + size + 0xFFF) & ~0xFFF
tq = tqdm(desc='Erasing', total=(eaddr - saddr),
unit='B', unit_divisor=1024, unit_scale=True)
try:
cur = saddr
while cur < eaddr:
aligned_64k = (cur & 0xFFFF) == 0
space_64k = (eaddr - cur) >= 0x10000
if use_crwn:
# CMD 0x2F: 64K erase (SPI 0xD8), returns without waiting BUSY.
# Busy-wait by reading the SAME block being erased (not addr 0):
# on EPIC, reading addr 0 after a previous erase can return too
# early and leave odd 64K banks (0x10000, 0x30000, ...) unerased.
def erase_64k(at):
execcmd(make_cb(Cmd.DEV_ERASE, arg1=at,
arg2=0x01, arg3=1), recv=512)
# Poll WIP via read of the block we just erased
execcmd(make_cb(Cmd.DEV_READ, arg1=at, arg3=1), recv=512)
erase_64k(cur)
# EPIC: verify first 512B are blank; re-erase once if not
if is_epic:
probe = execcmd(make_cb(Cmd.DEV_READ, arg1=cur, arg3=1),
recv=512)
if any(b != 0xFF for b in probe):
print(f'\n[!] erase verify fail @0x{cur:06X}, retry')
erase_64k(cur)
cur += 0x10000
tq.update(0x10000)
else:
if aligned_64k and space_64k:
blksize, flags = 0x10000, 0x00
else:
blksize, flags = 0x1000, 0x02
execcmd(make_cb(Cmd.DEV_ERASE, arg1=cur, arg2=flags))
cur += blksize
tq.update(blksize)
finally:
tq.close()
# ------------------------------------------------------------------
try:
if args.action == 'erase':
for i in range(0, len(args.areas), 2):
addr = int(args.areas[i], 0)
size = int(args.areas[i + 1], 0)
if size <= 0:
if fsize is None: raise RuntimeError('Unknown flash size')
size = fsize - addr
do_dev_erase(addr, size)
elif args.action == 'read':
for i in range(0, len(args.areas), 3):
addr = int(args.areas[i], 0)
size = int(args.areas[i + 1], 0)
path = args.areas[i + 2]
if size <= 0:
if fsize is None: raise RuntimeError('Unknown flash size')
size = fsize - addr
io_size = min(0x8000, max(blocksize,
align_to(size // 100, blocksize)))
print(f'Reading {size} bytes from @{addr:06X} into "{path}"...')
tq = tqdm(desc='Reading', total=size,
unit='B', unit_divisor=1024, unit_scale=True)
try:
with open(path, 'wb') as f:
done = 0
while done < size:
n = min(io_size, size - done)
_a3 = (n // 512) if use_crwn else n
f.write(execcmd(
make_cb(Cmd.DEV_READ, arg1=addr + done, arg3=_a3),
recv=n))
tq.update(n)
done += n
finally:
tq.close()
elif args.action == 'write':
for i in range(0, len(args.areas), 2):
addr = int(args.areas[i], 0)
path = args.areas[i + 1]
with open(path, 'rb') as f:
raw = f.read()
BLOCK = 512
if len(raw) % BLOCK:
raw += b'\xff' * (BLOCK - len(raw) % BLOCK)
n_blocks = len(raw) // BLOCK
print(f'Writing {len(raw)} bytes to @{addr:06X} from "{path}"...')
do_dev_erase(addr, len(raw))
if use_crwn:
# CMD 0x2A — CRWN write, one 512-byte block per call.
#
# Flow for each block:
# send_packet(CB) — BL ACKs immediately, then calls blob.
# Blob enters ROM recv() and waits for
# the data packet.
# send_packet(data) — BL's UART ISR independently receives
# this framed 512-byte packet, ACKs it,
# and stores the payload in the buffer
# pointed to by s0 (= iface[4], the CB
# pointer set at blob entry). ROM recv()
# reads from that same buffer and returns
# 512.
# recv=1 — drains the 1-byte blob response, keeps
# BL's TX queue empty and the packet
# counter in sync for the next block.
#
# arg2=0x00 keeps a3=0 inside the blob so flash_write programs
# flash with DMA key=0 (no encryption). arg2=0x02 would load
# iface[4] as the DMA key and silently corrupt every byte.
tq = tqdm(desc='Writing', total=len(raw),
unit='B', unit_divisor=1024, unit_scale=True)
try:
for blk in range(n_blocks):
cb = make_cb(Cmd.DEV_WRITE,
arg1=addr + blk * BLOCK,
arg2=0x00, arg3=1)
execcmd(cb,
send=raw[blk * BLOCK : (blk + 1) * BLOCK],
recv=1)
tq.update(BLOCK)
finally:
tq.close()
else:
io_size = min(0x8000, max(blocksize,
align_to(len(raw) // 100, blocksize)))
tq = tqdm(desc='Writing', total=len(raw),
unit='B', unit_divisor=1024, unit_scale=True)
try:
done = 0
while done < len(raw):
block = raw[done:done + io_size]
execcmd(make_cb(Cmd.DEV_WRITE,
arg1=addr + done, arg3=len(block)),
send=block)
tq.update(len(block))
done += len(block)
finally:
tq.close()
except KeyboardInterrupt:
print('interrupted!')
except Exception as e:
print('failed:', e)
raise
if args.reboot:
execcmd(make_cb(BlCmd.REBOOT))
###############################################################################
if have_uart and args.port is not None:
with Serial(args.port) as port:
# Detect whether the adapter has a TX→RX loopback (e.g. CH340 with
# 200 Ω resistor) or not (e.g. Bluetrum's original half-duplex dongle).
#
# Half-duplex dongle (no loopback):
# port_write sends bytes to chip, chip replies → those replies are
# read back as the "echo". has_echo=True is correct.
#
# Full-duplex adapter + resistor (loopback present):
# Our own TX bytes come straight back on RX before the chip replies.
# port_write must NOT try to consume them; the real chip reply is
# read later by send_packet/_recv_token_packet. has_echo=False.
#
# Detection: send one byte at low baud with a short timeout.
# If it echoes back → loopback adapter → has_echo=False.
# If nothing comes back → half-duplex dongle → has_echo=True.
if args.no_echo:
has_echo = False
else:
# Detect TX→RX loopback.
#
# CH340 (standard driver + 200Ω resistor): TX bytes loop back on
# RX immediately (within ~1 byte-time = 87µs at 115200).
# has_echo=True so port_write consumes them before reading the
# real chip response.
#
# Original Bluetrum dongle (AB5305A / CP2102 modified driver):
# driver suppresses local echo → TX bytes do NOT return on RX.
# has_echo=False so port_write doesn't try to read phantom bytes.
#
# Detection: send 0xFF (ignored by BL sync state machine) with a
# 5 ms read timeout — long enough for loopback (arrives in <1 ms)
# but shorter than the minimum chip processing + reply time.
port.baudrate = args.init_baud
port.timeout = 0.005
port.write(b'\xff')
loopback = port.read(1) == b'\xff'
port.reset_input_buffer()
port.timeout = 0.01
has_echo = loopback
udl = UARTDownload(port, has_echo=has_echo)
print('Trying to synchronize.', end='')
port.timeout = .01
try:
done = False
num = 0
turn = 0
while not done:
if num < 10:
udl.port.reset_input_buffer()
udl.port.write(UARTDownload.SYNC_TOKEN)
while not done:
recv = udl.port.read(4)
if recv == b'': break
if recv == UARTDownload.SYNC_RESP:
done = True
num += 1
else:
print('.', end='', flush=True)
if turn == 0:
udl.port.baudrate = args.init_baud
udl.send_reset(True)
turn = 1
else:
udl.port.baudrate = args.baud
udl.send_reset(True)
udl.port.baudrate = args.init_baud
turn = 0
num = 0
except Exception as e:
print(' failed:')
raise e
else:
print(' done.')
port.reset_input_buffer()
port.timeout = .1
def execcmd(cb, send=None, recv=None, max_io=512, switch_baud=None):
udl.send_packet(cb)
if switch_baud is not None:
port.baudrate = switch_baud
if send is not None:
sent = 0
while sent < len(send):
n = min(len(send) - sent, max_io)
udl.send_packet(send[sent:sent + n])
sent += n
elif recv is not None:
data = b''
while len(data) < recv:
n = min(recv - len(data), max_io)
block = udl.recv_packet()
data += block
if len(block) != n:
break
return data
do_the_stuff(execcmd, 512, 'uart', udl=udl)
elif have_scsi and args.mscdev is not None:
with SCSIDev(args.mscdev) as dev:
def execcmd(cb, send=None, recv=None, **_):
if recv is not None:
recv = bytearray(recv)
if send is not None and not isinstance(send, bytes):
send = bytes(send)
dev.execute(b'\xfc' + cb, send, recv)
return recv
do_the_stuff(execcmd, 512, 'usb')
else:
print('No device specified:')
if have_uart: print(' - UART: specify the serial port via --port')
if have_scsi: print(' - USB MSC: specify the device via --mscdev')
Отредактировано Givanich (2026-08-15 17:14:57)