338 lines
11 KiB
Python
338 lines
11 KiB
Python
'''
|
|
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 <config file>")
|
|
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')
|