''' Mstar bin firmware packer ''' ''' Header structure ------- Multi-line script which contains MBOOT commands The header script ends with line: '% <- this is end of file symbol' Line separator is '\n' The header is filled by 0xFF to 16KB The header size is always 16KB ''' ''' Bin structure ------- Basically it's merged parts: [part 1] [part 2] .... [part n] Each part is 4 byte aligned (filled by 0xFF) ''' ''' Footer structure |MAGIC|CRC1: SWAPPED HEADER CRC32|CRC2: SWAPPED BIN CRC32|FIRST 16 BYTES OF HEADER| # NB XGIMI uses HEADER+BIN+MAGIC+HEADER_CRC to calculate crc2 # Use USE_XGIMI_CRC2=True option to enable "XGIMI" mode ''' import configparser import sys import time import os import struct import utils import shutil tmpDir = 'tmp' headerPart = os.path.join(tmpDir, '~header') binPart = os.path.join(tmpDir, '~bin') footerPart = os.path.join(tmpDir, '~footer') # Command line args if len(sys.argv) == 1: print ("Usage: pack.py ") print ("Example: pack.py configs/letv-x355pro.ini") quit() configFile = sys.argv[1] # Parse config file config = configparser.ConfigParser(interpolation=configparser.ExtendedInterpolation()) #config = configparser.ConfigParser() config.read(configFile) # Main main = config['Main']; firmwareFileName = main['FirmwareFileName'] projectFolder = main['ProjectFolder'] useHexValuesPrefix = utils.str2bool(main['useHexValuesPrefix']) SCRIPT_FIRMWARE_FILE_NAME = main['SCRIPT_FIRMWARE_FILE_NAME'] DRAM_BUF_ADDR = main['DRAM_BUF_ADDR'] MAGIC_FOOTER = main['MAGIC_FOOTER'] HEADER_SIZE = utils.sizeInt(main['HEADER_SIZE']) # XGIMI uses HEADER+BIN+MAGIC+HEADER_CRC to calculate crc2 if 'USE_XGIMI_CRC2' in main: USE_XGIMI_CRC2 = utils.str2bool(main['USE_XGIMI_CRC2']) else: USE_XGIMI_CRC2 = False # Header header = config['HeaderScript']; headerScriptPrefix = config.get('HeaderScript', 'Prefix', raw = True) headerScriptSuffix = config.get('HeaderScript', 'Suffix', raw = True) # Parts parts = list(filter(lambda s: s.startswith('part/'), config.sections())) print("\n") print ("[i] Date: {}".format(time.strftime("%d/%m/%Y %H:%M:%S"))) print ("[i] Firmware file name: {}".format(firmwareFileName)) print ("[i] Project folder: {}".format(projectFolder)) print ("[i] Use hex values: {}".format(useHexValuesPrefix)) print ("[i] Script firmware filename: {}".format(SCRIPT_FIRMWARE_FILE_NAME)) print ("[i] DRAM_BUF_ADDR: {}".format(DRAM_BUF_ADDR)) print ("[i] MAGIC_FOOTER: {}".format(MAGIC_FOOTER)) print ("[i] HEADER_SIZE: {}".format(HEADER_SIZE)) # Create working directory print ('[i] Create working directory ...') utils.createDirectory(tmpDir) print ('[i] Generating header and bin ...') # Initial empty bin to store merged parts open(binPart, 'w').close() with open(headerPart, 'wb') as header: header.write('#\n'.encode()) header.write('# Generated by mstar-bin-tools\n'.encode()) header.write('# https://github.com/dipcore/mstar-bin-tool\n'.encode()) header.write('# dipcore@gmail.com\n'.encode()) header.write('#\n\n'.encode()) # Directive tool directive = utils.directive(header, DRAM_BUF_ADDR, useHexValuesPrefix) header.write('# Header prefix'.encode()) header.write(headerScriptPrefix.encode()) header.write('\n\n'.encode()) header.write('# Partitions'.encode()) for sectionName in parts: part = config[sectionName] name = sectionName.replace('part/', '') create = utils.str2bool(utils.getConfigValue(part, 'create', '')) size = utils.getConfigValue(part, 'size', 'NOT_SET') erase = utils.str2bool(utils.getConfigValue(part, 'erase', '')) type = utils.getConfigValue(part, 'type', 'NOT_SET') imageFile = utils.getConfigValue(part, 'imageFile', 'NOT_SET') chunkSize = utils.sizeInt(utils.getConfigValue(part, 'chunkSize', '0')) lzo = utils.str2bool(utils.getConfigValue(part, 'lzo', '')) memoryOffset = utils.getConfigValue(part, 'memoryOffset', 'NOT_SET') emptySkip = utils.str2bool(utils.getConfigValue(part, 'emptySkip', 'True')) print("\n") print("[i] Processing partition") print("[i] Name: {}".format(name)) print("[i] Create: {}".format(create)) print("[i] Size: {}".format(size)) print("[i] Erase: {}".format(erase)) print("[i] Type: {}".format(type)) print("[i] Image: {}".format(imageFile)) print("[i] LZO: {}".format(lzo)) print("[i] Memory Offset: {}".format(memoryOffset)) print("[i] Empty Skip: {}".format(emptySkip)) emptySkip = utils.bool2int(emptySkip) # 0 - False, 1 - True header.write('\n'.encode()) header.write('# {}\n'.format(name).encode()) if (create): directive.create(name, size) if (erase and imageFile == 'NOT_SET'): directive.erase_p(name) if (type == 'partitionImage'): if (chunkSize > 0): print ('[i] Splitting ...') chunks = utils.splitFile(imageFile, tmpDir, chunksize = chunkSize) else: # It will contain whole image as a single chunk chunks = utils.splitFile(imageFile, tmpDir, chunksize = 0) for index, inputChunk in enumerate(chunks): print ('[i] Processing chunk: {}'.format(inputChunk)) (name1, ext1) = os.path.splitext(inputChunk) if lzo: outputChunk = name1 + '.lzo' print ('[i] LZO: {} -> {}'.format(inputChunk, outputChunk)) utils.lzo(inputChunk, outputChunk) else: outputChunk = inputChunk # Size, offset (hex) size = "{:02X}".format(os.path.getsize(outputChunk)) offset = "{:02X}".format(os.path.getsize(binPart) + HEADER_SIZE) directive.filepartload(SCRIPT_FIRMWARE_FILE_NAME, offset, size) if (index == 0 and erase): directive.erase_p(name) print ('[i] Align chunk') utils.alignFile(outputChunk) print ('[i] Append: {} -> {}'.format(outputChunk, binPart)) utils.appendFile(outputChunk, binPart) if lzo: if index == 0: directive.unlzo(name, size, DRAM_BUF_ADDR, emptySkip) else: directive.unlzo_cont(name, size, DRAM_BUF_ADDR, emptySkip) else: if len(chunks) == 1: directive.write_p(name, size, DRAM_BUF_ADDR, emptySkip) else: # filepartload 50000000 MstarUpgrade.bin e04000 c800000 # mmc write.p.continue 50000000 system 0 c800000 1 # filepartload 50000000 MstarUpgrade.bin d604000 c800000 # mmc write.p.continue 50000000 system 64000 c800000 1 # Why offset is 64000 but not c800000 ??? print ('[!] UNSUPPORTED: mmc write.p.continue') quit() if (type == 'secureInfo'): chunks = utils.splitFile(imageFile, tmpDir, chunksize = 0) outputChunk = chunks[0] size = "{:02X}".format(os.path.getsize(outputChunk)) offset = "{:02X}".format(os.path.getsize(binPart) + HEADER_SIZE) directive.filepartload(SCRIPT_FIRMWARE_FILE_NAME, offset, size) print ('[i] Align') utils.alignFile(outputChunk) print ('[i] Append: {} -> {}'.format(outputChunk, binPart)) utils.appendFile(outputChunk, binPart) directive.store_secure_info(name) if (type == 'nuttxConfig'): chunks = utils.splitFile(imageFile, tmpDir, chunksize = 0) outputChunk = chunks[0] size = "{:02X}".format(os.path.getsize(outputChunk)) offset = "{:02X}".format(os.path.getsize(binPart) + HEADER_SIZE) directive.filepartload(SCRIPT_FIRMWARE_FILE_NAME, offset, size) print ('[i] Align') utils.alignFile(outputChunk) print ('[i] Append: {} -> {}'.format(outputChunk, binPart)) utils.appendFile(outputChunk, binPart) directive.store_nuttx_config(name) if (type == 'sboot'): chunks = utils.splitFile(imageFile, tmpDir, chunksize = 0) outputChunk = chunks[0] size = "{:02X}".format(os.path.getsize(outputChunk)) offset = "{:02X}".format(os.path.getsize(binPart) + HEADER_SIZE) directive.filepartload(SCRIPT_FIRMWARE_FILE_NAME, offset, size) print ('[i] Align') utils.alignFile(outputChunk) print ('[i] Append: {} -> {}'.format(outputChunk, binPart)) utils.appendFile(outputChunk, binPart) directive.write_boot(size, DRAM_BUF_ADDR, emptySkip) if (type == 'inMemory'): chunks = utils.splitFile(imageFile, tmpDir, chunksize = 0) outputChunk = chunks[0] size = "{:02X}".format(os.path.getsize(outputChunk)) offset = "{:02X}".format(os.path.getsize(binPart) + HEADER_SIZE) directive.filepartload(SCRIPT_FIRMWARE_FILE_NAME, offset, size, memoryOffset=memoryOffset) print ('[i] Align') utils.alignFile(outputChunk) print ('[i] Append: {} -> {}'.format(outputChunk, binPart)) utils.appendFile(outputChunk, binPart) header.write('\n'.encode()) header.write('# Header suffix'.encode()) header.write(headerScriptSuffix.encode()) header.write('\n'.encode()) header.write('% <- this is end of file symbol\n'.encode()) header.flush() print ('[i] Fill header script to 16KB') header.write( ('\xff' * (HEADER_SIZE - os.path.getsize(headerPart))).encode(encoding='iso-8859-1') ) print ('[i] Generating footer ...') if (USE_XGIMI_CRC2): # NB XGIMI uses HEADER+BIN+MAGIC+HEADER_CRC to calculate crc2 headerCRC = utils.crc32(headerPart) header16bytes = utils.loadPart(headerPart, 0, 16) # Step #1. Merge HEADER+BIN+MAGIC+HEADER_CRC to one file mergedPart = os.path.join(tmpDir, '~merged') open(mergedPart, 'w').close() utils.appendFile(headerPart, mergedPart) utils.appendFile(binPart, mergedPart) with open(mergedPart, 'ab') as part: print ('[i] Magic: {}'.format(MAGIC_FOOTER)) part.write(MAGIC_FOOTER.encode()) print ('[i] Header CRC: 0x{:02X}'.format(headerCRC)) part.write(struct.pack('L', headerCRC)) # Step #2 Calculate CRC2 mergedCRC = utils.crc32(mergedPart) with open(footerPart, 'wb') as footer: print ('[i] Merged CRC: 0x{:02X}'.format(mergedCRC)) footer.write(struct.pack('L', mergedCRC)) print ('[i] First 16 bytes of header: {}'.format(header16bytes)) footer.write(header16bytes) print ('[i] Merging parts ...') open(firmwareFileName, 'w').close() utils.appendFile(mergedPart, firmwareFileName) utils.appendFile(footerPart, firmwareFileName) else: headerCRC = utils.crc32(headerPart) binCRC = utils.crc32(binPart) header16bytes = utils.loadPart(headerPart, 0, 16) with open(footerPart, 'wb') as footer: print ('[i] Magic: {}'.format(MAGIC_FOOTER)) footer.write(MAGIC_FOOTER.encode()) print ('[i] Header CRC: 0x{:02X}'.format(headerCRC)) footer.write(struct.pack('L', headerCRC)) # struct.pack('L', data) <- returns byte swapped data print ('[i] Bin CRC: 0x{:02X}'.format(binCRC)) footer.write(struct.pack('L', binCRC)) print ('[i] First 16 bytes of header: {}'.format(header16bytes)) footer.write(header16bytes) print ('[i] Merging header, bin, footer ...') open(firmwareFileName, 'w').close() utils.appendFile(headerPart, firmwareFileName) utils.appendFile(binPart, firmwareFileName) utils.appendFile(footerPart, firmwareFileName) shutil.rmtree(tmpDir) print ('[i] Done')