mirror of
https://github.com/elliotnunn/tbxi.git
synced 2024-09-27 06:54:42 +00:00
199 lines
7.1 KiB
Python
199 lines
7.1 KiB
Python
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)
|