"""Shared parser/packer for Lofi-12 XT Sonicware .bin images. Format: [cmtd 48B][mmtd 36B][sect xN -> EOF, exact fit] Each sect: b'sect' | u32 LE load_addr | u32 LE len | payload[len] All addresses are DDR2 (0xC2xxxxxx, TMS320C6748). Code is C674x DSP LE. Stdlib only. """ import struct import json import binascii CMTD_LEN = 48 MMTD_LEN = 36 # Checksum seed: cmtd+0x08 coverage replaces file bytes [8:12] with this # constant before CRC32-IEEE (init 0). Proven 3/3 against stock images # (solved independently per image via GF(2), all give this same value; # it is also built literally in the bootloader: MVK 0x6282/MVKH 0xC27C). CKSEED = 0xC27C6282 def compute_checksum(d: bytes) -> int: """Valid cmtd+0x08 for a complete image: CRC32 of the image with bytes [8:12] replaced by CKSEED.""" b = bytearray(d) b[8:12] = struct.pack(' dict: assert d[0:4] == b'cmtd', "bad cmtd magic" assert d[0x30:0x34] == b'mmtd', "bad mmtd magic" fsize, cksum = struct.unpack(' bytes: """Rebuild image from meta (cmtd/mmtd dicts) + payload list [(addr, bytes)]. Recalculates filesize/remaining fields. Preserves entry/flags/fw unless caller edited meta. Checksum: explicit value, 'keep' preserves the original from meta, None (default) computes the valid checksum.""" nsect = len(payloads) total = CMTD_LEN + MMTD_LEN + sum(12 + len(p) for _, p in payloads) # checksum covers the whole file, so build with a zero placeholder, # then finalize: explicit int wins, 'keep' preserves meta, None computes. ck = 0 out = bytearray() out += b'cmtd' out += struct.pack(' dict: return { 'cmtd': info['cmtd'], 'mmtd': {**info['mmtd'], 'entry_hex': f"{info['mmtd']['entry']:08X}", 'flags_hex': f"{info['mmtd']['flags']:08X}"}, 'sects': [{'index': s['index'], 'off_hex': f"{s['off']:06X}", 'addr_hex': f"{s['addr']:08X}", 'len': s['len'], 'end_hex': f"{s['end']:08X}"} for s in info['sects']], } def find_sect_by_role(info: dict, role: str) -> dict: """role='code' -> largest sect; role='rodata' -> sect holding b'Threshold' (fallback: second largest).""" sects = info['sects'] if role == 'code': return max(sects, key=lambda s: s['len']) if role == 'rodata': for s in sects: if b'Threshold' in s['payload']: return s rest = sorted(sects, key=lambda s: s['len'], reverse=True) return rest[1] if len(rest) > 1 else rest[0] raise ValueError(role) def code_file_off(info: dict, addr: int) -> int | None: for s in info['sects']: if s['addr'] <= addr < s['end']: return s['off'] + 12 + (addr - s['addr']) return None