diff --git a/README.md b/README.md index 38cfc39..1a141a7 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## About -Cross-platform MediaTek feature phone dumping had been achieved long ago with [MTreader](https://gitlab.com/suborg/mtreader). Now it's time to do the same for Unisoc (ex-Spreadtrum) phones. And not only that, but also to be able to flash them! +Cross-platform MediaTek feature phone dumping had been achieved long ago with [MTreader](https://gitlab.com/suborg/mtreader). Now it's time to do the same for Unisoc (ex-Spreadtrum) phones. And not only that, but also to be able to flash them and extract their compressed firmware contents! Unfortunately, the architecture of Unisoc chipset boot ROM doesn't allow us to get away without any loader blobs. So, some FDL binaries are also shipped in this repo. @@ -18,7 +18,7 @@ For further dumped firmware unpacking/repacking, I recommend [bzpwork](https://g Python 3.8+ and PyUSB. -## Usage +## Usage as a flasher/dumper Run `python uniflash.py -h` to see all parameters. But there are several typical scenarios that UniFlash officially supports. @@ -70,6 +70,20 @@ For single-FDL targets, the FDL file name must be `[targetname]_[load_addr]_sing For example, if you have found signed FDL loaders for Nokia 105 2019 somewhere and want to add this phone as a target, you know that they are loaded into `0x40004000` and `0x14000000` respectively, so you can rename them, for instance, to `sc6531efm_nokia105_0x40004000_fdl1.bin` and `sc6531efm_nokia105_0x14000000_fdl2.bin`, place them into `fdls/` and then just use `-t sc6531efm_nokia105` in your commands. This signed target, by the way, has already been added as an example. +## Usage as a stone image unpacker + +The main syntax is as follows: `python uniflash.py stone-unpack [stone-file.bin] [-d target_dir]` + +The firmware is going to be unpacked into the following files in the target directory (specified with `-d` parameter, defaults to the same directory as the source stone image file): + +- `ps.bin` - protocol station image, always uncompressed, the first in the binary; +- `kern.bin` (optional) - single-block LZMA-SPD compressed kernel partition; +- `user.bin` - multi-block LZMA-SPD compressed user firmware partition; +- `rsrc.bin` - multi-block LZMA-SPD compressed user resources partition; +- `blk_xxxxxxxx` (optional) - any of the additional LZMA-SPD or standard LZMA compressed sections found in the image. + +LZMA-SPD, also known as LZMA-B3, is a proprietary modification of LZMA algorithm with simplified literal encoder/decoder. + ## Credits Created by Luxferre in 2021. All files except the FDL blobs are public domain. diff --git a/custlzma/frenchlzma.py b/custlzma/frenchlzma.py new file mode 100644 index 0000000..86ceca1 --- /dev/null +++ b/custlzma/frenchlzma.py @@ -0,0 +1,231 @@ +from io import BytesIO +from collections import defaultdict + +class LecteurDeBits: + + def __init__(self, entree : bytes): + self.octets = BytesIO(entree) + self.bits_non_lus = 0 + self.taille_bits_non_lus = 0 + + def lire_bits(self, nombre_bits : int) -> int: + while self.taille_bits_non_lus < nombre_bits: + prochain_octet = self.octets.read(1) + if not prochain_octet: + raise EOFError + self.bits_non_lus |= prochain_octet[0] << self.taille_bits_non_lus + self.taille_bits_non_lus += 8 + masque_bits_lus = (1 << nombre_bits) - 1 + bits_lus = self.bits_non_lus & masque_bits_lus + self.bits_non_lus >>= nombre_bits + self.taille_bits_non_lus -= nombre_bits + return bits_lus + + def lire_octets(self, nombre_octets : int) -> bytes: + self.aligner_bits_sur_octet() + octets_lus = self.octets.read(nombre_octets) + if len(octets_lus) < nombre_octets: + raise EOFError + return octets_lus + + def aligner_bits_sur_octet(self): + + self.bits_non_lus = 0 + self.taille_bits_non_lus = 0 + +class LecteurDeBitsRangeCode: + + def __init__(self, entree : bytes): + self.octets = BytesIO(entree) + self.taille_code = 0xffffffff + self.code = 0 + assert self.octets.read(1)[0] == 0 + + for position in range(4): + self.code = (self.code << 8) | self.octets.read(1)[0] + + assert self.code < self.taille_code + + def reprendre_des_bits_si_besoin(self): + if self.taille_code <= 0xffffff: + self.taille_code <<= 8 + self.code <<= 8 + self.code |= self.octets.read(1)[0] + +class RangeDecoder: + + def __init__(self, lecteur_de_bits : LecteurDeBitsRangeCode, probabilite_initiale : int = 0x400): + self.lecteur_de_bits : LecteurDeBitsRangeCode = lecteur_de_bits + self.probabilite : int = probabilite_initiale # 0 - 0x800 + + def lire_bit(self, utiliser_probas = True): + if utiliser_probas: + milieu_du_code_pondere = self.lecteur_de_bits.taille_code // 0x800 * self.probabilite + else: + milieu_du_code_pondere = self.lecteur_de_bits.taille_code // 2 + + if self.lecteur_de_bits.code < milieu_du_code_pondere: + bit_lu = 0 + self.probabilite += (0x800 - self.probabilite) // 32 + self.lecteur_de_bits.taille_code = milieu_du_code_pondere + else: + bit_lu = 1 + self.probabilite -= self.probabilite // 32 + self.lecteur_de_bits.code -= milieu_du_code_pondere + if utiliser_probas: + self.lecteur_de_bits.taille_code -= milieu_du_code_pondere + else: + self.lecteur_de_bits.taille_code = milieu_du_code_pondere + self.lecteur_de_bits.reprendre_des_bits_si_besoin() + return bit_lu + +class DecodeurLZMA: + + def decode(self, entree : bytes) -> bytes: + self.flux_decompresse = b'' + self.dernieres_distances : List[int] = [0] * 4 + lecteur_de_bits = LecteurDeBits(entree) + properties = lecteur_de_bits.lire_bits(8) + self.literal_context_bits = properties % 9 + literal_position_bits = (properties // 9) % 5 + position_bits = properties // 9 // 5 + + if position_bits > 4: + raise ValueError('LZMA invalid') + + self.taille_fenetre = lecteur_de_bits.lire_bits(32) + self.uncompressed_size = lecteur_de_bits.lire_bits(64) + lecteur_de_bits = LecteurDeBitsRangeCode(entree[13:]) + self.nom_vers_range_decodeur : Dict[tuple, RangeDecoder] = defaultdict(lambda: RangeDecoder(lecteur_de_bits)) + self.state = 0 + self.masque_pos_state = (1 << position_bits) - 1 + self.masque_lit_state = (1 << literal_position_bits) - 1 + + while len(self.flux_decompresse) < self.uncompressed_size: + try: + pos_state = len(self.flux_decompresse) & self.masque_pos_state + bit_choice = self.nom_vers_range_decodeur[('IsMatch', self.state, pos_state)].lire_bit() + if bit_choice == 0: + self.LITERAL() + elif bit_choice == 1: + is_rep = self.nom_vers_range_decodeur[('IsRep', self.state)].lire_bit() + if is_rep == 0: + self.MATCH() + elif is_rep == 1: + is_rep0 = self.nom_vers_range_decodeur[('IsRepG0', self.state)].lire_bit() + if is_rep0 == 0: + is_rep0_long = self.nom_vers_range_decodeur[('IsRep0Long', self.state, pos_state)].lire_bit() + if is_rep0_long == 0: + self.SHORTREP() + elif is_rep0_long == 1: + self.LONGREP(0) + elif is_rep0 == 1: + is_rep1 = self.nom_vers_range_decodeur[('IsRepG1', self.state)].lire_bit() + if is_rep1 == 0: + self.LONGREP(1) + elif is_rep1 == 1: + is_rep2 = self.nom_vers_range_decodeur[('IsRepG2', self.state)].lire_bit() + if is_rep2 == 0: + self.LONGREP(2) + elif is_rep2 == 1: + self.LONGREP(3) + except EOFError: + break + + return self.flux_decompresse + + def LITERAL(self): + dernier_octet_decompresse = self.flux_decompresse[-1] if self.flux_decompresse else 0 + octet_lu = self.bit_tree_decode(('LiteralNormal', + len(self.flux_decompresse) & self.masque_lit_state, # total_pos + dernier_octet_decompresse >> (8 - self.literal_context_bits), # prev_byte + ), None, 8, use_pos_state = False) + self.flux_decompresse += bytes([octet_lu]) + if self.state > 9: + self.state -= 6 + elif self.state > 3: + self.state -= 3 + else: + self.state = 0 + + def MATCH(self): + match_len = 2 + self.len_decode('LenDecoder') + pos_slot = self.bit_tree_decode(('PosSlot', min(5, match_len)), None, 6, use_pos_state = False) + if pos_slot >= 4: + num_direct_bits = (pos_slot >> 1) - 1 + distance = (2 | (pos_slot & 1)) << num_direct_bits + if pos_slot < 14: + distance += self.bit_tree_decode('SpecPos', None, num_direct_bits + (distance - pos_slot - 1), + use_pos_state = False, reverse = True, + debut_bit_tree = distance - pos_slot - 1) + else: + distance += self.bit_tree_decode('AlignFixed', None, num_direct_bits - 4, utiliser_probas = False) << 4 + distance += self.bit_tree_decode('Align', None, 4, use_pos_state = False, reverse = True) + else: + distance = pos_slot + + self.dernieres_distances.append(distance) + if distance == 0xffffffff: + raise EOFError + assert distance < len(self.flux_decompresse) + assert distance < self.taille_fenetre + self.repeter_donnees(distance, match_len) + if self.state < 7: + self.state = 7 + else: + self.state = 10 + + def SHORTREP(self): # Réutiliser la dernière distance pour un octet + if self.state < 7: + self.state = 9 + else: + self.state = 11 + self.repeter_donnees(self.dernieres_distances[-1], 1) + + def LONGREP(self, num): # Réutiliser l'une des dernières distances pour une taille donnée + match_len = 2 + self.len_decode('RepLenDecoder') + self.dernieres_distances.append(self.dernieres_distances.pop(-(1 + num))) + distance = self.dernieres_distances[-1] + self.repeter_donnees(distance, match_len) + if self.state < 7: + self.state = 8 + else: + self.state = 11 + + def repeter_donnees(self, distance, match_len): + debut_slice = len(self.flux_decompresse) - (1 + distance) + fin_slice = match_len + a_repeter = self.flux_decompresse[debut_slice:debut_slice + fin_slice] + fin_slice -= len(self.flux_decompresse) - debut_slice + while fin_slice > 0: + a_repeter += self.flux_decompresse[debut_slice:debut_slice + fin_slice] + fin_slice -= min(len(self.flux_decompresse), debut_slice + fin_slice) - debut_slice + + self.flux_decompresse += a_repeter + + def len_decode(self, len_decoder_name): + if self.nom_vers_range_decodeur[('LenChoice', len_decoder_name)].lire_bit() == 0: + return self.bit_tree_decode('LenLow', len_decoder_name, 3) + else: + if self.nom_vers_range_decodeur[('LenChoice2', len_decoder_name)].lire_bit() == 0: + return (1 << 3) + self.bit_tree_decode('LenMid', len_decoder_name, 3) + else: + return (1 << 4) + self.bit_tree_decode('LenHigh', len_decoder_name, 8, use_pos_state = False) + + def bit_tree_decode(self, bit_tree_decoder_name, len_decoder_name, num_bits, + use_pos_state = True, utiliser_probas = True, reverse = False, + bit_tree_lu = 0, debut_bit_tree = 0): + for position_bit in range(debut_bit_tree, num_bits): + bit_lu = self.nom_vers_range_decodeur[( + bit_tree_decoder_name, + len_decoder_name, + bit_tree_lu, + position_bit, + (len(self.flux_decompresse) & self.masque_pos_state) if use_pos_state else None, + )].lire_bit(utiliser_probas = utiliser_probas) + if not reverse: + bit_tree_lu <<= 1 + bit_tree_lu |= bit_lu + else: + bit_tree_lu |= bit_lu << (position_bit - debut_bit_tree) + return bit_tree_lu diff --git a/stoned.py b/stoned.py new file mode 100644 index 0000000..743a40d --- /dev/null +++ b/stoned.py @@ -0,0 +1,156 @@ +# StoneD (Stone Depacker) - unpack Unisoc SC6531 stone images (part of UniFlash) +# Created by Luxferre in 2021, released into public domain + +import os +import sys +import struct +import lzma +#from custlzma.lzma_decoder import LZMADecoder # (for LZMA_SPD decompression) +from custlzma.frenchlzma import DecodeurLZMA + +# common utils + +def readFile(fname): + f = open(fname, 'rb') + fdata = f.read() + f.close() + return fdata + +def writeFile(fname, fdata): + outf = open(fname, 'wb') + outf.write(fdata) + outf.close() + +# unpack part + +CMP_NONE=0 +CMP_LZMA_SPRD=1 +CMP_LZMA=2 + +def getCompType(data): + if (data[0] == 0x5d or data[0] == 0x67) and data[1] == 0: + return CMP_LZMA + elif data[0] == 0x5a and data[1] == 0: + return CMP_LZMA_SPRD + else: + return CMP_NONE + +def getTblOffset(blocksOffTbl, index): + tind = index << 2 + return struct.unpack(' kernel image + targetFile = targetDir + '/kern.bin' + elif blkId == 0x75736572: # resu -> user image + targetFile = targetDir + '/user.bin' + elif blkId == 0x7253736F: # resources + targetFile = targetDir + '/rsrc.bin' + else: + targetFile = targetDir + ('/blk_%X.bin' % blkId) + unpack_block(sectionData[blkDataOffset:], blkPacSize, targetFile) + +def unpack_stone(fname, targetDir): + fdata = readFile(fname) + flen = len(fdata) + assert flen >= 0x10, 'Input file %s is too small' % fname + + # check for security header + sectionOffset = 0 + if fdata[0:15] == b'SPRD-SECUREFLAG': + sectionOffset = 1024 + print('Signed image detected, using section offset %d' % sectionOffset) + + # look for TRAPGAMI header + startPos = -1 + for i in range(flen): + if fdata[i:i+8] == b'TRAPGAMI': + startPos = i + break + assert startPos > 0, 'No stone header found in %s' % fname + print('Stone header found at 0x%X' % startPos) + + psImageEnd = 0xffffffff # PS (protocol station) image is the first in the flash backup and not compressed + + dfcStruct = fdata[startPos+8:startPos+120] + for i in range(0,112,4): + targetAddr = struct.unpack(' 0: + psPath = targetDir + '/ps.bin' + writeFile(psPath, fdata[:psImageEnd]) + print('Protocol station image %s written!' % psPath) + +# main code start + +if __name__ == '__main__': # main app start + from argparse import ArgumentParser + rootdir = os.path.dirname(os.path.realpath(__file__)) + parser = ArgumentParser(description='StoneD: an opensource Unisoc/Spreadtrum stone image unpacker', epilog='(c) Luxferre 2021 --- No rights reserved ') + parser.add_argument('file', help='Stone image file to unpack') + parser.add_argument('-d','--directory', default=None, help='Directory where component files will be written to (defaults to the same where the main stone file resides)') + + args = parser.parse_args() + + imgfile = args.file + imgdir = os.path.dirname(os.path.realpath(imgfile)) + if args.directory is not None: + imgdir = os.path.realpath(args.directory) + + print('Unpacking %s to %s' % (imgfile, imgdir)) + unpack_stone(imgfile, imgdir) + diff --git a/uniflash.py b/uniflash.py index 2059bd7..d63a418 100644 --- a/uniflash.py +++ b/uniflash.py @@ -4,6 +4,7 @@ import usb import sys, time import os import unicmd +import stoned # global params @@ -162,12 +163,13 @@ if __name__ == '__main__': # main app start from argparse import ArgumentParser rootdir = os.path.dirname(os.path.realpath(__file__)) parser = ArgumentParser(description='UniFlash: an opensource Unisoc/Spreadtrum feature phone flash reader/writer', epilog='(c) Luxferre 2021 --- No rights reserved ') - parser.add_argument('mode', help='Operation mode (flash/dump)') - parser.add_argument('file', help='File to read the flash data from or write the dump into') + parser.add_argument('mode', help='Operation mode (flash/dump/stone-unpack)') + parser.add_argument('file', help='File to read the flash data from or write the dump into, or the stone file to unpack') parser.add_argument('-p','--partid', type=auto_int, default=0x80000003, help='partition ID for readback (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/write, defaults to 0x400000') parser.add_argument('-t','--target', default='sc6531efm_generic', help='Preinstalled target (defaults to sc6531efm_generic, overridable with individual FDL parameters)') + parser.add_argument('-d','--directory', default=None, help='Directory where component files will be written to in stone-unpack mode (defaults to the same where the main stone file resides)') parser.add_argument('-nr','--flash-noremap', action='store_true', help='Disable base address remapping for flashing') parser.add_argument('-e','--force-erase', action='store_true', help='Erase target flash memory area before flashing') parser.add_argument('-wf','--enable-write-flash', action='store_true', help='Send the write flash enable command before flashing (if necessary and supported)') @@ -183,137 +185,147 @@ if __name__ == '__main__': # main app start args = parser.parse_args() - is_flash = False - if args.mode == 'flash': - is_flash = True + if args.mode.startswith('stone'): # stone-unpack mode + imgfile = args.file + imgdir = os.path.dirname(os.path.realpath(imgfile)) + if args.directory is not None: + imgdir = os.path.realpath(args.directory) - # parse target and resolve the parameters from it first - paramdelim = '_' - target = args.target + paramdelim - fdlDir = rootdir + '/fdls' - fdlList = [] - for root, dirs, files in os.walk(fdlDir): - for name in files: - if name.startswith(target): - paramstr = os.path.splitext(name)[0].split(target)[1] - params = paramstr.split(paramdelim) - fdlList.append((params[1], params[0], fdlDir+'/'+name)) - # resulting fdl list: (tag, address, path) - fdlSingleName = None - fdlSingleAddr = None - for tag, addr, path in fdlList: - if tag == 'single': - fdlSingleName = path - fdlSingleAddr = auto_int(addr) - elif tag == 'fdl1': - fdl1Name = path - fdl1Addr = auto_int(addr) - elif tag == 'fdl2': - fdl2Name = path - fdl2Addr = auto_int(addr) + print('Unpacking %s to %s' % (imgfile, imgdir)) + stoned.unpack_stone(imgfile, imgdir) - # override target with the individual parameters if necessary - UNISOC_VID = args.device_vid - UNISOC_PID = args.device_pid - if args.fdl1_addr is not None: - fdl1Addr = args.fdl1_addr - if args.fdl2_addr is not None: - fdl2Addr = args.fdl2_addr - if args.fdl1_file is not None: - fdl1Name = args.fdl1_file - if args.fdl2_file is not None: - fdl2Name = args.fdl2_file - if args.single_fdl_file is not None: - fdlSingleName = args.single_fdl_file - if args.single_fdl_addr is not None: - fdlSingleAddr = args.single_fdl_addr - outfile = args.file - partitionId = args.partid - readbs = args.block_size - readoffset = args.start - readlen = args.length - forceErase = args.force_erase - sendEnableWriteFlash = args.enable_write_flash - singleFdlMode = False - fdl1Label = 'FDL1' - fdl2Label = 'FDL2' + else: # flash/dump mode + is_flash = False + if args.mode == 'flash': + is_flash = True - # override flash base addr based on the target + # parse target and resolve the parameters from it first + paramdelim = '_' + target = args.target + paramdelim + fdlDir = rootdir + '/fdls' + fdlList = [] + for root, dirs, files in os.walk(fdlDir): + for name in files: + if name.startswith(target): + paramstr = os.path.splitext(name)[0].split(target)[1] + params = paramstr.split(paramdelim) + fdlList.append((params[1], params[0], fdlDir+'/'+name)) + # resulting fdl list: (tag, address, path) + fdlSingleName = None + fdlSingleAddr = None + for tag, addr, path in fdlList: + if tag == 'single': + fdlSingleName = path + fdlSingleAddr = auto_int(addr) + elif tag == 'fdl1': + fdl1Name = path + fdl1Addr = auto_int(addr) + elif tag == 'fdl2': + fdl2Name = path + fdl2Addr = auto_int(addr) - if target.startswith('sc6530'): - UNISOC_FLASH_BASE_ADDR = UNISOC_FLASH_BASE_ADDR_OLD + # override target with the individual parameters if necessary + UNISOC_VID = args.device_vid + UNISOC_PID = args.device_pid + if args.fdl1_addr is not None: + fdl1Addr = args.fdl1_addr + if args.fdl2_addr is not None: + fdl2Addr = args.fdl2_addr + if args.fdl1_file is not None: + fdl1Name = args.fdl1_file + if args.fdl2_file is not None: + fdl2Name = args.fdl2_file + if args.single_fdl_file is not None: + fdlSingleName = args.single_fdl_file + if args.single_fdl_addr is not None: + fdlSingleAddr = args.single_fdl_addr + outfile = args.file + partitionId = args.partid + readbs = args.block_size + readoffset = args.start + readlen = args.length + forceErase = args.force_erase + sendEnableWriteFlash = args.enable_write_flash + singleFdlMode = False + fdl1Label = 'FDL1' + fdl2Label = 'FDL2' - if args.flash_noremap == True: - print('Flash remapping disabled') - UNISOC_FLASH_BASE_ADDR = 0 + # override flash base addr based on the target - if fdlSingleName is not None: - singleFdlMode = True - fdl1Addr = fdlSingleAddr - fdl1Name = fdlSingleName - fdl1Label = 'FDL' - fdl2Label = 'FDL' - print('Using a single FDL %s, loading to 0x%X' % (fdlSingleName, fdlSingleAddr)) - else: - print('Using FDL1 %s, loading to 0x%X' % (fdl1Name, fdl1Addr)) - print('Using FDL2 %s, loading to 0x%X' % (fdl2Name, fdl2Addr)) + if target.startswith('sc6530'): + UNISOC_FLASH_BASE_ADDR = UNISOC_FLASH_BASE_ADDR_OLD - # 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() + if args.flash_noremap == True: + print('Flash remapping disabled') + UNISOC_FLASH_BASE_ADDR = 0 - 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 ' + fdl1Label) - send_file_to_addr(fdl1Name, fdl1Addr) - print('Starting ' + fdl1Label) - resp = reqresp(unicmd.cmd_data_exec(fdl1Addr)) - rcode, rlen, r = unicmd.resp_decode(resp, False) - if rcode == unicmd.BSL_REP_ACK: - print(fdl1Label + ' started successfully, reconnecting...') - reconnect() - handshake(True) - - if singleFdlMode: - rcode = unicmd.BSL_REP_ACK + if fdlSingleName is not None: + singleFdlMode = True + fdl1Addr = fdlSingleAddr + fdl1Name = fdlSingleName + fdl1Label = 'FDL' + fdl2Label = 'FDL' + print('Using a single FDL %s, loading to 0x%X' % (fdlSingleName, fdlSingleAddr)) else: - 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) + 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 ' + fdl1Label) + send_file_to_addr(fdl1Name, fdl1Addr) + print('Starting ' + fdl1Label) + resp = reqresp(unicmd.cmd_data_exec(fdl1Addr)) + rcode, rlen, r = unicmd.resp_decode(resp, False) if rcode == unicmd.BSL_REP_ACK: - print(fdl2Label + ' started successfully!') + print(fdl1Label + ' started successfully, reconnecting...') + reconnect() + handshake(True) - 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(fdl2Label + ' running, may start interacting with flash memory') - - if is_flash: - - if sendEnableWriteFlash: - resp = reqresp(unicmd.cmd_enable_write_flash(), True) - rcode, rlen, r = unicmd.resp_decode(resp, True) - assert rcode == unicmd.BSL_REP_ACK, 'Could not send the flash write request, response code is %X' % rcode - - print('Writing flash at offset 0x%X from %s...' % (readoffset, outfile)) - write_flash_mem(outfile, readoffset, readbs, forceErase) - print('Flash memory written, disconnect the device!') + if singleFdlMode: + rcode = unicmd.BSL_REP_ACK else: - read_partition(partitionId, readlen, readoffset, outfile, readbs) - resp = reqresp(unicmd.cmd_reset(), 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) - assert rcode == unicmd.BSL_REP_ACK, 'Could not reset the device, response code is %X' % rcode + if rcode == unicmd.BSL_REP_ACK: + print(fdl2Label + ' started successfully!') - if dev is not None: - usb.util.dispose_resources(dev) + 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(fdl2Label + ' running, may start interacting with flash memory') + + if is_flash: + + if sendEnableWriteFlash: + resp = reqresp(unicmd.cmd_enable_write_flash(), True) + rcode, rlen, r = unicmd.resp_decode(resp, True) + assert rcode == unicmd.BSL_REP_ACK, 'Could not send the flash write request, response code is %X' % rcode + + print('Writing flash at offset 0x%X from %s...' % (readoffset, outfile)) + write_flash_mem(outfile, readoffset, readbs, forceErase) + print('Flash memory written, disconnect the device!') + else: + 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)