from collections import defaultdict, Counter import os from os import path from shlex import quote import struct import hashlib from . import dispatcher from .slow_lzss import decompress from .lowlevel import PrclNodeStruct, PrclChildStruct from .pef_info import suggest_name HEADER_COMMENT = """ # Automated dump of Toolbox Parcels (magic number 'prcl') #parcel_type [metadata...] \t#child_type [metadata...] [src=*[.lzss]] \t\t#null_terminated_strings_instead_of_src_file # Parcel types are four bytes (child types are unimportant) # 'prop': match and edit an existing DT node # 'node': create a new DT node # 'rom ': Power Macintosh ROM image # 'psum': black/whitelists for computing DT checksum # Flag Struct Meaning of known flag # ----- ------ -------------------------------------------- # F0000 parcel (bitmask) number of new 'special' DT node # 00200 parcel edit DT node only if required for boot disk # 00010 parcel use only once # 00008 parcel match DT node if: ('device_type' == b field) # 00004 parcel AND ('compatible' contains a field # 00002 parcel OR parent 'name' == a field # 00001 parcel OR 'name' == a field) # ----- ------ -------------------------------------------- # F0000 child (bitmask) number of 'special' parent # 00080 child create DT prop under 'special' DT node above # 00100 child DT prop is for boot debugging only # 00040 child delete existing DT prop (vs create) # 00020 child do not replace existing DT prop # 00010 child use only once # 00004 child checksum enabled (crc32) """.strip() def quickhash(foo): return hashlib.sha512(foo).hexdigest() def walk_tree(binary): """Get low level representation of tree e.g. [(prclnodetuple, [prclchildtuple, ...]), ...] """ prclnode = None parents = [] for i in iter(lambda: prclnode.link if prclnode else struct.unpack_from('>12xI', binary)[0], 0): prclnode = PrclNodeStruct.unpack_from(binary, offset=i) children = [] for j in range(i + PrclNodeStruct.size, i + prclnode.hdr_size, prclnode.child_size): prclchild = PrclChildStruct.unpack_from(binary, offset=j) children.append(prclchild) parents.append((prclnode, children)) return parents def unique_binary_tpl(prclchild): return (prclchild.ptr, prclchild.packedlen, prclchild.compress) def guess_binary_name(parent_struct, child_struct, adjacent_name, data): # 4 MB ROM-in-RAM image if parent_struct.ostype == child_struct.ostype == 'rom ': return 'MacROM' # Native (PCI) driver with an embedded name and version ndrv_name = suggest_name(data) if ndrv_name: return ndrv_name # A "special" property called by its actual name if parent_struct.flags & 0xF0000 or child_struct.flags & 0x80: return child_struct.name # A driver property with an adjacent name property if 'AAPL,MacOS,PowerPC' in child_struct.name and adjacent_name: return adjacent_name # A lanLib (for netbooting) if child_struct.name == 'lanLib,AAPL,MacOS,PowerPC': return parent_struct.a + '_lanLib' return '' def dump(binary, dest_dir): if not binary.startswith(b'prcl'): raise dispatcher.WrongFormat os.makedirs(dest_dir, exist_ok=True) basic_structure = walk_tree(binary) # Decompress everything unpacked_dict = {} binary_of = lambda child: unpacked_dict[unique_binary_tpl(child)] binary_counts = Counter() for prclnode, children in basic_structure: for prclchild in children: binary_counts[unique_binary_tpl(prclchild)] += 1 data = binary[prclchild.ptr:prclchild.ptr+prclchild.packedlen] if prclchild.compress == 'lzss': data = decompress(data) unpacked_dict[unique_binary_tpl(prclchild)] = data filename_dict = {} # maps binary data to a filename for prclnode, children in basic_structure: # A fragment prop may have an adjacent prop giving it a name, get this ready adjacent_name = None for check_child in children: if check_child.name == 'code,AAPL,MacOS,name': adjacent_name = unpacked_dict[unique_binary_tpl(check_child)].rstrip(b'\0').decode('ascii') # Best guess original-ish name for this binary for prclchild in children: if prclchild.ostype not in ('cstr', 'csta'): base = guess_binary_name( parent_struct=prclnode, child_struct=prclchild, adjacent_name=adjacent_name, data=binary_of(prclchild), ) filename_dict[binary_of(prclchild)] = base # Post-process to ensure that all names are unique used_names = Counter(filename_dict.values()) for binary, filename in list(filename_dict.items()): if used_names[filename] > 1: if filename: filename += '-' filename += quickhash(binary) filename_dict[binary] = filename filename_dict = {b: (fn+'.pef' if b.startswith(b'Joy!peff') else fn) for (b, fn) in filename_dict.items()} # Dump blobs to disk for data, filename in filename_dict.items(): dispatcher.dump(data, path.join(dest_dir, filename)) # Get printing!!! with open(path.join(dest_dir, 'Parcelfile'), 'w') as f: f.write(HEADER_COMMENT + '\n\n') for prclnode, children in basic_structure: line = quote(prclnode.ostype) line += ' flags=0x%05x' % prclnode.flags if prclnode.a: line += ' a=%s' % quote(prclnode.a) if prclnode.b: line += ' b=%s' % quote(prclnode.b) print(line, file=f) for prclchild in children: line = '\t%s' % quote(prclchild.ostype) line += ' flags=0x%05x' % prclchild.flags if prclchild.name: line += ' name=%s' % quote(prclchild.name) if prclchild.ostype not in ('cstr', 'csta'): filename = filename_dict[binary_of(prclchild)] if prclchild.compress == 'lzss': filename += '.lzss' line += ' src=%s' % quote(filename) if binary_counts[unique_binary_tpl(prclchild)] > 1: line += ' deduplicate=1' if prclnode.ostype == 'psum' and prclchild.ostype == 'csta': if prclchild is children[0]: line += " # [5] Property whitelist:" if prclchild is children[1]: line += " # [4] Node 'name' whitelist:" if prclchild is children[2]: line += " # [3] Node 'name' blacklist:" if prclchild is children[3]: line += " # [2] Node 'device-type' whitelist:" if prclchild is children[4]: line += " # [1] Node 'device-type' blacklist:" print(line, file=f) if prclchild.ostype in ('cstr', 'csta'): strangs = unpacked_dict[unique_binary_tpl(prclchild)].split(b'\0')[:-1] for s in strangs: line = '\t\t%s' % quote(s.decode('ascii')) print(line, file=f) print(file=f)