|
| 1 | +from io import BytesIO |
| 2 | +from pathlib import Path |
| 3 | +from struct import unpack |
| 4 | +from argparse import ArgumentParser |
| 5 | +from typing import List |
| 6 | + |
| 7 | +MAGIC = b'\x55\xAA\x5A\xA5' |
| 8 | + |
| 9 | +CHUNK_SIZE = 0x400 # 1024 bytes |
| 10 | +ALIGNMENT = 4 # Alignment bytes |
| 11 | + |
| 12 | +class Partition: |
| 13 | + def __init__(self, start: int, hdr_sz: int, unk1: int, hw_id: int, seq: int, |
| 14 | + size: int, date: str, time: str, ftype: str, blank1: bytes, |
| 15 | + hdr_crc: int, block_size: int, blank2: bytes, checksum: bytes, |
| 16 | + data: bytes, end: int): |
| 17 | + self.start = start |
| 18 | + self.hdr_sz = hdr_sz |
| 19 | + self.unk1 = unk1 |
| 20 | + self.hw_id = hw_id |
| 21 | + self.seq = seq |
| 22 | + self.size = size |
| 23 | + self.date = date |
| 24 | + self.time = time |
| 25 | + self.type = ftype |
| 26 | + self.blank1 = blank1 |
| 27 | + self.hdr_crc = hdr_crc |
| 28 | + self.block_size = block_size |
| 29 | + self.blank2 = blank2 |
| 30 | + self.checksum = checksum |
| 31 | + self.data = data |
| 32 | + self.end = end |
| 33 | + |
| 34 | + @classmethod |
| 35 | + def from_file(cls, file, offset: int = 0): |
| 36 | + hdr_sz, unk1, hw_id, seq, size = unpack('<LLQLL', file.read(24)) |
| 37 | + date, time, type = file.read(16).decode().strip('\x00'), \ |
| 38 | + file.read(16).decode().strip('\x00'), file.read(16).decode().strip('\x00') |
| 39 | + blank1, hdr_crc, block_size, blank2, checksum = file.read(16), file.read(2).hex(), \ |
| 40 | + file.read(2).hex(), file.read(2), file.read(hdr_sz - 98) |
| 41 | + |
| 42 | + data = file.read(size) if size > 0 else b'' |
| 43 | + |
| 44 | + file.seek((ALIGNMENT - file.tell() % ALIGNMENT) % ALIGNMENT, 1) |
| 45 | + return cls(offset, hdr_sz, unk1, hw_id, seq, size, date, time, type, blank1, |
| 46 | + hdr_crc, block_size, blank2, checksum, data, file.tell()) |
| 47 | + |
| 48 | +class UpdateExtractor: |
| 49 | + def __init__(self, package: Path, output: Path): |
| 50 | + self.package = package.open('rb') |
| 51 | + self.output = output |
| 52 | + self.partitions: List[Partition] = [] |
| 53 | + |
| 54 | + self.parse_partitions() |
| 55 | + |
| 56 | + def parse_partitions(self): |
| 57 | + while True: |
| 58 | + buffer = self.package.read(4) |
| 59 | + if not buffer: break |
| 60 | + |
| 61 | + if buffer == MAGIC: |
| 62 | + self.partitions.append(Partition.from_file( |
| 63 | + self.package, self.package.tell() |
| 64 | + )) |
| 65 | + |
| 66 | + def extract(self, name: str = None): |
| 67 | + self.output.mkdir(exist_ok=True) |
| 68 | + for partition in self.partitions: |
| 69 | + if name is not None and partition.type != name: |
| 70 | + continue |
| 71 | + with open('%s/%s.img' % (self.output, |
| 72 | + partition.type), 'wb') as f: |
| 73 | + f.write(partition.data) |
| 74 | + |
| 75 | +def main(): |
| 76 | + parser = ArgumentParser() |
| 77 | + parser.add_argument('package', help='UPDATE.APP package.', type=Path) |
| 78 | + parser.add_argument('-e', '--extract', help='Extract partitions to files.', action='store_true') |
| 79 | + parser.add_argument('-o', '--output', help='Output folder.', default='output', type=Path) |
| 80 | + parser.add_argument('-p', '--partition', help='Partition name to extract.', type=str, default=None) |
| 81 | + args = parser.parse_args() |
| 82 | + |
| 83 | + extractor = UpdateExtractor( |
| 84 | + args.package, args.output) |
| 85 | + |
| 86 | + for partition in extractor.partitions: |
| 87 | + print("%s (%d bytes) @ %s - %s" % (partition.type, partition.size, |
| 88 | + hex(partition.start), hex(partition.end))) |
| 89 | + |
| 90 | + if args.extract: |
| 91 | + extractor.extract(args.partition) |
| 92 | + |
| 93 | +if __name__ == '__main__': |
| 94 | + main() |
0 commit comments