129 lines
5.7 KiB
Python
Executable File
129 lines
5.7 KiB
Python
Executable File
#!/usr/bin/env python
|
|
import argparse, hashlib, math, struct, time, zlib
|
|
|
|
def readfile(filepath):
|
|
try:
|
|
with open(filepath, "rb") as f:
|
|
return f.read()
|
|
except OSError as err:
|
|
exit("\nERROR: Failed to read file '%s': %s\n" % (err.filename, err.strerror))
|
|
|
|
|
|
def get_partitions(args, flash_offset = 0):
|
|
print("\nPartitions:")
|
|
partitions = []
|
|
# ota_next_id = 16
|
|
while len(args) >= 5:
|
|
partype = int(args.pop(0), 0)
|
|
subtype = int(args.pop(0), 0)
|
|
size = int(args.pop(0), 0)
|
|
label = args.pop(0)
|
|
filename = args.pop(0)
|
|
|
|
data = readfile(filename) if filename != "none" else b""
|
|
|
|
flash_alignment = 0x10000 if partype == 0 else 0x1000
|
|
flash_offset = math.ceil(flash_offset / flash_alignment) * flash_alignment
|
|
# Technically the size only has to be aligned to 0x1000, but it's wasted space if two apps follow eachother
|
|
flash_size = math.ceil(max(size, len(data)) / flash_alignment) * flash_alignment
|
|
|
|
print(" [%d]: type=%d, subtype=%d, size=%d (%d%% used), label=%s"
|
|
% (len(partitions), partype, subtype, flash_size, len(data) / flash_size * 100, label))
|
|
|
|
if size != 0 and len(data) > size:
|
|
print(" > WARNING: File larger than partition (+%d bytes), increased size to %d"
|
|
% (len(data) - size, flash_size))
|
|
elif size != 0 and size != flash_size:
|
|
print(" > WARNING: Incorrect alignment, adjusted size to %d" % (flash_size))
|
|
|
|
partitions.append((partype, subtype, label, flash_offset, flash_size, data))
|
|
flash_offset += flash_size
|
|
|
|
if len(args) > 0:
|
|
print(f"WARNING: Trailing arguments (incomplete partition?): {args}")
|
|
|
|
print("Flash size: %.3f MB\n" % (flash_offset / 1048576))
|
|
return partitions
|
|
|
|
|
|
def create_firmware(fw_type, partitions, icon_file, name, version, target):
|
|
description = f"{name} {version}".encode()
|
|
tile_bin = readfile(icon_file) if icon_file != "none" else b"\x00" * 8256
|
|
if fw_type == "odroid":
|
|
fw_data = struct.pack("<24s40s8256s", b"ODROIDGO_FIRMWARE_V00_01", description, tile_bin)
|
|
else:
|
|
fw_data = struct.pack("<22s40s8256s", b"ESPLAY_FIRMWARE_V00_01", description, tile_bin)
|
|
for partype, subtype, label, offset, size, data in partitions:
|
|
fw_data += struct.pack("<BBxx16sIII", partype, subtype, label.encode(), 0, size, len(data))
|
|
fw_data += data
|
|
return fw_data + struct.pack("I", zlib.crc32(fw_data))
|
|
|
|
|
|
def create_image(chip_type, partitions, bootloader_file, name, version, target):
|
|
bootloader_offset, table_offset, prog_offset = {
|
|
"esp32": (0x1000, 0x8000, 0x10000),
|
|
"esp32s3": (0x0000, 0x8000, 0x10000),
|
|
"esp32p4": (0x2000, 0x8000, 0x10000),
|
|
}.get(chip_type)
|
|
partitions = [
|
|
(1, 0x02, "nvs", 0x9000, 0x4000, b""),
|
|
(1, 0x00, "otadata", 0xD000, 0x2000, b""),
|
|
(1, 0x01, "phy_init", 0xF000, 0x1000, b""),
|
|
] + partitions
|
|
output_data = bytearray(b"\xFF" * max(p[3] + p[4] for p in partitions))
|
|
|
|
# It would be better to call gen_esp32part.py but it's a hassle to make it work for all our supported platforms...
|
|
PARTITION_MAGIC = b"\xAA\x50"
|
|
PARTITION_MD5_MAGIC = b"\xEB\xEB\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
|
|
table_bin = b""
|
|
for partype, subtype, label, offset, size, data in partitions:
|
|
table_bin += struct.pack("<2sBBLL16sL", PARTITION_MAGIC, partype, subtype, offset, size, label.encode(), 0)
|
|
table_bin += PARTITION_MD5_MAGIC + hashlib.md5(table_bin).digest()
|
|
table_bin += b"\xFF" * (0xC00 - len(table_bin)) # Pad table with 0xFF otherwise it will be invalid
|
|
output_data[table_offset : table_offset + len(table_bin)] = table_bin
|
|
|
|
bootloader_bin = readfile(bootloader_file)
|
|
output_data[bootloader_offset : bootloader_offset + len(bootloader_bin)] = bootloader_bin
|
|
|
|
for partype, subtype, label, offset, size, data in partitions:
|
|
output_data[offset : offset + len(data)] = data
|
|
|
|
# Append the information structure used by retro-go's updater.
|
|
# (Maybe I could hide it somewhere else to avoid changing the size of the image)
|
|
output_data += struct.pack(
|
|
"<8s28s28s28sII156s",
|
|
"RG_IMG_0".encode(), # Magic number
|
|
name.encode(), # Project name
|
|
version.encode(), # Project version
|
|
target.encode(), # Project target device
|
|
int(time.time()), # Unix timestamp
|
|
zlib.crc32(output_data),# CRC of the image not including this footer
|
|
b"\xff" * 156, # 0xFF padding of the reserved area
|
|
)
|
|
return output_data
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Retro-Go firmware tool")
|
|
parser.add_argument("--type", choices=["odroid", "esplay", "esp32", "esp32s3", "esp32p4"], default="odroid")
|
|
parser.add_argument("--bootloader", default="none")
|
|
parser.add_argument("--name", default="unknown")
|
|
parser.add_argument("--icon", default="none")
|
|
parser.add_argument("--version", default="unknown")
|
|
parser.add_argument("--target", default="unknown")
|
|
parser.add_argument("output_file")
|
|
parser.add_argument("partitions", nargs="*", metavar="(type subtype size label file.bin)")
|
|
args = parser.parse_args()
|
|
|
|
if args.type in ["odroid", "esplay"]:
|
|
partitions = get_partitions(args.partitions)
|
|
data = create_firmware(args.type, partitions, args.icon, args.name, args.version, args.target)
|
|
else:
|
|
partitions = get_partitions(args.partitions, 0x10000)
|
|
data = create_image(args.type, partitions, args.bootloader, args.name, args.version, args.target)
|
|
|
|
with open(args.output_file, "wb") as fp:
|
|
fp.write(data)
|
|
|
|
print("File '%s' successfully created.\n" % (args.output_file))
|