203 lines
7.7 KiB
Python
203 lines
7.7 KiB
Python
#!/usr/bin/env python
|
|
|
|
import usb
|
|
import sys, time
|
|
import os
|
|
import unicmd
|
|
|
|
# global params
|
|
|
|
UNISOC_VID = 0x1782
|
|
UNISOC_PID = 0x4d00
|
|
MAX_PKT_SIZE = 1024
|
|
bSize = 512 # read block size
|
|
genTimeout = 4000
|
|
|
|
# all main procedures
|
|
|
|
def connect(vid, pid):
|
|
while True:
|
|
dev = usb.core.find(idVendor=vid, idProduct=pid)
|
|
sys.stdout.write('.')
|
|
sys.stdout.flush()
|
|
if dev is not None:
|
|
print('\nDevice connected')
|
|
break
|
|
time.sleep(0.1)
|
|
dev.set_configuration()
|
|
cfg = dev.get_active_configuration()
|
|
intf = cfg[(0,0)]
|
|
epIn = usb.util.find_descriptor(
|
|
intf,
|
|
custom_match = \
|
|
lambda e: \
|
|
usb.util.endpoint_direction(e.bEndpointAddress) == \
|
|
usb.util.ENDPOINT_IN)
|
|
epOut = usb.util.find_descriptor(
|
|
intf,
|
|
custom_match = \
|
|
lambda e: \
|
|
usb.util.endpoint_direction(e.bEndpointAddress) == \
|
|
usb.util.ENDPOINT_OUT)
|
|
assert epIn is not None
|
|
assert epOut is not None
|
|
return dev, epIn, epOut
|
|
|
|
|
|
def reqonly(packet, fdlBooted = False, noCrc = False):
|
|
packet = unicmd.hdlc_encode(packet, fdlBooted, noCrc)
|
|
dev.write(epOut, packet, genTimeout)
|
|
|
|
def reqresp(packet, fdlBooted = False, noCrc = False):
|
|
reqonly(packet, fdlBooted, noCrc)
|
|
resp = bytes(dev.read(epIn, bSize, genTimeout))
|
|
return resp
|
|
|
|
def handshake(fdlBooted = False):
|
|
resp = reqresp(unicmd.cmd_sync(), fdlBooted)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
|
|
if len(r):
|
|
print('>', r.decode())
|
|
resp = reqresp(unicmd.cmd_connect(), fdlBooted)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
|
|
if len(r):
|
|
print('>', r.decode())
|
|
|
|
def send_file_to_addr(fname, faddr, fdlBooted = False):
|
|
pSize = MAX_PKT_SIZE
|
|
f = open(fname, 'rb')
|
|
fdata = f.read()
|
|
f.close()
|
|
flen = len(fdata)
|
|
print('Initializing data transfer...')
|
|
resp = reqresp(unicmd.cmd_data_start(faddr, flen), fdlBooted)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
|
|
assert rcode == unicmd.BSL_REP_ACK, 'Could not start data transfer, response code is %X' % rcode
|
|
print('Starting data transfer...')
|
|
while fdata:
|
|
buf = fdata[:pSize]
|
|
resp = reqresp(unicmd.cmd_data_send(buf), fdlBooted)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
|
|
assert rcode == unicmd.BSL_REP_ACK, 'Something is wrong and response code is %X, block is %s' % (rcode, buf.hex())
|
|
fdata = fdata[pSize:]
|
|
sys.stdout.write('.')
|
|
sys.stdout.flush()
|
|
print('\nEnding data transfer...')
|
|
resp = reqresp(unicmd.cmd_data_end(), fdlBooted)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, fdlBooted)
|
|
assert rcode == unicmd.BSL_REP_ACK, 'Could not finalize data transfer, response code is %X' % rcode
|
|
print('Data transfer successful')
|
|
|
|
def read_partdata(baseaddr, size, offset):
|
|
t = b''
|
|
reqonly(unicmd.cmd_read_flash(baseaddr, size, offset), True)
|
|
while True:
|
|
xr = bytes(dev.read(epIn, bSize, genTimeout))
|
|
t += xr
|
|
if len(xr) < bSize:
|
|
break
|
|
return t
|
|
|
|
def read_partition(baseaddr, partsize, partoffset, outfile, rbblocksize):
|
|
outf = open(outfile, 'wb')
|
|
psize = partsize
|
|
offset = partoffset
|
|
print('Dumping %d bytes from partition 0x%X at offset 0x%X to %s...' % (partsize, baseaddr, partoffset, outfile))
|
|
bufsize = rbblocksize
|
|
while psize > 0:
|
|
if psize < bufsize:
|
|
bufsize = psize
|
|
resp = read_partdata(baseaddr, bufsize, offset)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, True)
|
|
outf.write(r)
|
|
sys.stdout.write('.')
|
|
sys.stdout.flush()
|
|
psize -= rlen
|
|
offset += rlen
|
|
outf.flush()
|
|
print('\nPartition dumped!')
|
|
|
|
def auto_int(x):
|
|
return int(x,0)
|
|
|
|
# main code start
|
|
|
|
if __name__ == '__main__': # main app start
|
|
from argparse import ArgumentParser
|
|
rootdir = os.path.dirname(os.path.realpath(__file__))
|
|
parser = ArgumentParser(description='UniDump: an opensource Unisoc SC6531E/F/M phone dumper', epilog='(c) Luxferre 2021 --- No rights reserved <https://unlicense.org>')
|
|
parser.add_argument('file', help='File to write the dump into')
|
|
parser.add_argument('-p','--partid', type=auto_int, default=0x80000003, help='partition ID (defaults to 0x80000003 that can address full flash space on SC6531E/F/M)')
|
|
parser.add_argument('-s','--start', type=auto_int, default=0, help='start position (in the partition, defaults to 0)')
|
|
parser.add_argument('-l', '--length', type=auto_int, default=0x400000, help='data length in bytes to read, defaults to 0x400000')
|
|
parser.add_argument('-bs','--block-size', type=auto_int, default=4096, help='Readback block size (in bytes), defaults to 4096')
|
|
parser.add_argument('-dv','--device-vid', type=auto_int, default=UNISOC_VID, help='Override device vendor ID')
|
|
parser.add_argument('-dp','--device-pid', type=auto_int, default=UNISOC_PID, help='Override device product ID')
|
|
parser.add_argument('-fdl1','--fdl1-file', default=rootdir+'/fdls/sc6531efm/nor_fdl1.bin', help='Path to FDL1, defaults to the generic SC6531E/F/M FDL1 shipped with UniDump')
|
|
parser.add_argument('-addr1','--fdl1-addr', type=auto_int, default=0x40004000, help='Address to load FDL1 into, defaults to 0x40004000')
|
|
parser.add_argument('-fdl2','--fdl2-file', default=rootdir+'/fdls/sc6531efm/nor_fdl.bin', help='Path to FDL2, defaults to the generic SC6531E/F/M FDL2 shipped with UniDump')
|
|
parser.add_argument('-addr2','--fdl2-addr', type=auto_int, default=0x14000000, help='Address to load FDL2 into, defaults to 0x14000000')
|
|
args = parser.parse_args()
|
|
|
|
UNISOC_VID = args.device_vid
|
|
UNISOC_PID = args.device_pid
|
|
fdl1Addr = args.fdl1_addr
|
|
fdl2Addr = args.fdl2_addr
|
|
fdl1Name = args.fdl1_file
|
|
fdl2Name = args.fdl2_file
|
|
outfile = args.file
|
|
partitionId = args.partid
|
|
readbs = args.block_size
|
|
readoffset = args.start
|
|
readlen = args.length
|
|
|
|
print('Using FDL1 %s, loading to 0x%X' % (fdl1Name, fdl1Addr))
|
|
print('Using FDL2 %s, loading to 0x%X' % (fdl2Name, fdl2Addr))
|
|
|
|
# initial connection
|
|
print('Connect the device %X:%X while holding the bootkey...' % (UNISOC_VID, UNISOC_PID) )
|
|
dev, epIn, epOut = connect(UNISOC_VID, UNISOC_PID)
|
|
handshake()
|
|
|
|
def reconnect():
|
|
global dev
|
|
if dev is not None:
|
|
usb.util.dispose_resources(dev)
|
|
time.sleep(0.5)
|
|
dev, epIn, epOut = connect(UNISOC_VID, UNISOC_PID)
|
|
|
|
print('Boot mode entered')
|
|
|
|
print('Sending FDL1')
|
|
send_file_to_addr(fdl1Name, fdl1Addr)
|
|
print('Starting FDL1')
|
|
resp = reqresp(unicmd.cmd_data_exec(fdl1Addr))
|
|
rcode, rlen, r = unicmd.resp_decode(resp, False)
|
|
if rcode == unicmd.BSL_REP_ACK:
|
|
print('FDL1 started successfully, reconnecting...')
|
|
reconnect()
|
|
handshake(True)
|
|
|
|
print('Protocol set up, sending FDL2')
|
|
send_file_to_addr(fdl2Name, fdl2Addr, True)
|
|
print('Starting FDL2')
|
|
resp = reqresp(unicmd.cmd_data_exec(fdl2Addr), True)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, True)
|
|
if rcode == unicmd.BSL_REP_ACK:
|
|
print('FDL2 started successfully!')
|
|
|
|
resp = reqresp(unicmd.cmd_sync_full(), True)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, True)
|
|
assert rcode == unicmd.BSL_REP_ACK, 'Could not set the baudrate, response code is %X' % rcode
|
|
|
|
print('FDL2 running, may start interacting with flash memory')
|
|
|
|
read_partition(partitionId, readlen, readoffset, outfile, readbs)
|
|
|
|
resp = reqresp(unicmd.cmd_reset(), True)
|
|
rcode, rlen, r = unicmd.resp_decode(resp, True)
|
|
assert rcode == unicmd.BSL_REP_ACK, 'Could not reset the device, response code is %X' % rcode
|
|
|
|
if dev is not None:
|
|
usb.util.dispose_resources(dev)
|