mirror of
https://github.com/GaryOderNichts/drc-fw-patches.git
synced 2024-11-01 00:15:11 +01:00
173 lines
6.0 KiB
Python
173 lines
6.0 KiB
Python
|
#!/usr/bin/env python3
|
||
|
"""
|
||
|
fwpack - Pack/Unpack DRC/DRH firmware files
|
||
|
Created in 2024 by GaryOderNichts
|
||
|
<https://github.com/GaryOderNichts/drc-fw-patches>
|
||
|
|
||
|
Credits to drxtool for the firmware header logic and extracted files structure.
|
||
|
"""
|
||
|
|
||
|
import sys, os
|
||
|
import binascii
|
||
|
import construct
|
||
|
|
||
|
class FirmwareType:
|
||
|
FIRMWARE_TYPE_DRC = 0x01010000
|
||
|
FIRMWARE_TYPE_DRH = 0x00010000
|
||
|
|
||
|
BlobHeader = construct.Struct(
|
||
|
"imageVersion" / construct.Int32ub,
|
||
|
"blockSize" / construct.Int32ub,
|
||
|
"sequencePerSession" / construct.Int32ub,
|
||
|
"imageSize" / construct.Int32ub,
|
||
|
)
|
||
|
assert(BlobHeader.sizeof() == 0x10)
|
||
|
|
||
|
FirmwareHeader = construct.Struct(
|
||
|
"type" / construct.Int32ul,
|
||
|
"superCRCs" / construct.Array(4, construct.Int32ul),
|
||
|
construct.Padding(0xFE8),
|
||
|
"headerCRC" / construct.Int32ul,
|
||
|
"subCRCs" / construct.Array(0x1000, construct.Int32ul),
|
||
|
)
|
||
|
assert(FirmwareHeader.sizeof() == 0x5000)
|
||
|
|
||
|
FirmwareSection = construct.Struct(
|
||
|
"offset" / construct.Int32ul,
|
||
|
"size" / construct.Int32ul,
|
||
|
"name" / construct.PaddedString(4, "ascii"),
|
||
|
"version" / construct.Int32ul,
|
||
|
)
|
||
|
assert(FirmwareSection.sizeof() == 0x10)
|
||
|
|
||
|
FirmwareFile = construct.Struct(
|
||
|
"blobHeader" / BlobHeader,
|
||
|
"firmwareHeader" / FirmwareHeader,
|
||
|
"firmwareData" / construct.Bytes(construct.this.blobHeader.imageSize - FirmwareHeader.sizeof()),
|
||
|
)
|
||
|
|
||
|
# Thanks to drxtool for the crctable logic
|
||
|
def verify_firmware_header(fw) -> bool:
|
||
|
# Verify header CRC
|
||
|
header_crc = binascii.crc32(FirmwareHeader.build(fw.firmwareHeader)[0:0xFFC])
|
||
|
if header_crc != fw.firmwareHeader.headerCRC:
|
||
|
return False
|
||
|
|
||
|
# Verify super crcs
|
||
|
subcrc_data = construct.Array(0x1000, construct.Int32ul).build(fw.firmwareHeader.subCRCs)
|
||
|
for i in range(4):
|
||
|
super_crc = binascii.crc32(subcrc_data[i*0x1000:i*0x1000+0x1000])
|
||
|
if super_crc != fw.firmwareHeader.superCRCs[i]:
|
||
|
return False
|
||
|
|
||
|
# Verify sub crcs
|
||
|
for i in range(len(fw.firmwareData) // 0x1000 + 1):
|
||
|
offset = i * 0x1000
|
||
|
length = 0x1000
|
||
|
if len(fw.firmwareData) - offset < length:
|
||
|
length = len(fw.firmwareData) - offset
|
||
|
|
||
|
sub_crc = binascii.crc32(fw.firmwareData[offset:offset + length])
|
||
|
if sub_crc != fw.firmwareHeader.subCRCs[i]:
|
||
|
return False
|
||
|
|
||
|
return True
|
||
|
|
||
|
def build_firmware_header(blob_type, firmware_data) -> dict:
|
||
|
# Calculate CRC for every 0x1000 bytes of firmware data
|
||
|
sub_crcs = [0] * 0x1000
|
||
|
for i in range(len(firmware_data) // 0x1000 + 1):
|
||
|
offset = i * 0x1000
|
||
|
length = 0x1000
|
||
|
if len(firmware_data) - offset < length:
|
||
|
length = len(firmware_data) - offset
|
||
|
|
||
|
sub_crcs[i] = binascii.crc32(firmware_data[offset:offset + length])
|
||
|
|
||
|
# Calculate the super CRCs
|
||
|
super_crcs = [0] * 4
|
||
|
subcrc_data = construct.Array(0x1000, construct.Int32ul).build(sub_crcs)
|
||
|
for i in range(4):
|
||
|
super_crcs[i] = binascii.crc32(subcrc_data[i*0x1000:i*0x1000+0x1000])
|
||
|
|
||
|
firmware_header = dict(type=blob_type, superCRCs=super_crcs, headerCRC=0, subCRCs=sub_crcs)
|
||
|
|
||
|
# Calculate the header CRC
|
||
|
firmware_header["headerCRC"] = binascii.crc32(FirmwareHeader.build(firmware_header)[0:0xFFC])
|
||
|
|
||
|
return firmware_header
|
||
|
|
||
|
def unpack_firmware(source_file, dest_dir):
|
||
|
fw = FirmwareFile.parse_file(source_file)
|
||
|
if not verify_firmware_header(fw):
|
||
|
print("Firmware header verification failed")
|
||
|
sys.exit(1)
|
||
|
|
||
|
if fw.firmwareHeader.type == FirmwareType.FIRMWARE_TYPE_DRC:
|
||
|
print(f"DRC firmware version 0x{fw.blobHeader.imageVersion:08x}")
|
||
|
elif fw.firmwareHeader.type == FirmwareType.FIRMWARE_TYPE_DRH:
|
||
|
print(f"DRH firmware version 0x{fw.blobHeader.imageVersion:08x}")
|
||
|
else:
|
||
|
print(f"Unsupported firmware type 0x{fw.firmwareHeader.type:08x}")
|
||
|
sys.exit(1)
|
||
|
|
||
|
if not os.path.isdir(dest_dir):
|
||
|
os.mkdir(dest_dir)
|
||
|
|
||
|
# Write blob header and type
|
||
|
BlobHeader.build_file(fw.blobHeader, os.path.join(dest_dir, "blob_header.bin"))
|
||
|
construct.Int32ul.build_file(fw.firmwareHeader.type, os.path.join(dest_dir, "blob_type.bin"))
|
||
|
|
||
|
# Assume first part of the data is the index
|
||
|
index = FirmwareSection.parse(fw.firmwareData)
|
||
|
|
||
|
# Parse sections
|
||
|
sections = construct.Array(index.size // FirmwareSection.sizeof(), FirmwareSection).parse(fw.firmwareData)
|
||
|
for s in sections:
|
||
|
print(f"Saving {s.name} version 0x{s.version:08x} offset 0x{s.offset} size 0x{s.size}")
|
||
|
|
||
|
# write section to file
|
||
|
with open(os.path.join(dest_dir, s.name + ".bin"), "wb") as f:
|
||
|
f.write(fw.firmwareData[s.offset:s.offset + s.size])
|
||
|
|
||
|
def pack_firmware(source_dir, dest_file):
|
||
|
# Read blob header and type
|
||
|
blob_header = BlobHeader.parse_file(os.path.join(source_dir, "blob_header.bin"))
|
||
|
blob_type = construct.Int32ul.parse_file(os.path.join(source_dir, "blob_type.bin"))
|
||
|
|
||
|
# Parse sections from INDX
|
||
|
firmware_data = b""
|
||
|
sections = construct.GreedyRange(FirmwareSection).parse_file(os.path.join(source_dir, "INDX.bin"))
|
||
|
for s in sections:
|
||
|
print(f"Storing {s.name}...")
|
||
|
|
||
|
# read section data
|
||
|
section_data = b""
|
||
|
with open(os.path.join(source_dir, s.name + ".bin"), "rb") as f:
|
||
|
section_data = f.read()
|
||
|
|
||
|
# update section
|
||
|
s.size = len(section_data)
|
||
|
s.offset = len(firmware_data)
|
||
|
firmware_data += section_data
|
||
|
|
||
|
# Update total image size
|
||
|
blob_header.imageSize = FirmwareHeader.sizeof() + len(firmware_data)
|
||
|
|
||
|
# Build the final image size
|
||
|
FirmwareFile.build_file(dict(blobHeader=blob_header, firmwareHeader=build_firmware_header(blob_type, firmware_data), firmwareData=firmware_data), dest_file)
|
||
|
|
||
|
if len(sys.argv) != 4:
|
||
|
print(f"Usage:\n{sys.argv[0]} <unpack/pack> <source> <destination>")
|
||
|
sys.exit(0)
|
||
|
|
||
|
if sys.argv[1] == "unpack":
|
||
|
print(f"Unpacking {sys.argv[2]}...")
|
||
|
unpack_firmware(sys.argv[2], sys.argv[3])
|
||
|
elif sys.argv[1] == "pack":
|
||
|
print(f"Packing {sys.argv[3]}...")
|
||
|
pack_firmware(sys.argv[2], sys.argv[3])
|
||
|
else:
|
||
|
print(f"Unknown mode \"{sys.argv[1]}\"")
|
||
|
sys.exit(1)
|