n8felton
11/11/2015 - 6:55 AM

Decompressing and re-assembling LVZN compressed animations and logos from boot.efi

Decompressing and re-assembling LVZN compressed animations and logos from boot.efi

# This file parses this file:
# https://github.com/Piker-Alpha/macosxbootloader/blob/El-Capitan/src/boot/NetBootImages.h
# and this one:
# https://github.com/Piker-Alpha/macosxbootloader/blob/El-Capitan/src/boot/AppleLogoData.h

from ctypes import CDLL, create_string_buffer, c_size_t, c_void_p
import re

CPK = CDLL('/System/Library/PrivateFrameworks/PackageKit.framework/Versions/Current/PackageKit')
lzvn_decode = CPK.lzvn_decode
lzvn_decode.restype  = c_size_t
lzvn_decode.argtypes = [c_void_p, c_size_t, c_void_p, c_size_t]

def lzvn_unpack(buf):
    el = len(buf)
    dl = 0x80000
    encoded_buf = create_string_buffer(buf, el)
    decoded_buf = create_string_buffer(dl)
    s = lzvn_decode(decoded_buf, dl, encoded_buf, el)
    return decoded_buf.raw[:s]

def clean_data(raw_data):
    return re.sub('[^A-F0-9]', '', re.sub('0x', '', raw_data)).decode('hex')

def parse_h(h_file):
    with open(h_file) as f:
        header = f.read()
    # Look for declarations of the form: STATIC UINT8 DataNameHere[ SizeNumberHere ] = { HexDataHere ... };
    byte_dump = re.compile(r'STATIC UINT8 ([^ ]+?)\[ [0-9]+ \][^{]+?\{([^;}]+?)\};', re.MULTILINE)
    # Build up a dictionary where key name is from .h (example: NetBootBlackPacked), value is raw bytes
    members = dict()
    for data_name, raw_data in byte_dump.findall(header):
        # Find all matches, store the raw bytes
        members[data_name] = clean_data(raw_data)
    return members

class Icon(object):
    pass

def parse_members(members):
    icons = dict()
    for icon_name in [x.split('Packed',1)[0] for x in members.keys() if x.endswith('Packed')]:
        i = Icon()
        i.name = icon_name
        i.data = [ord(x) for x in lzvn_unpack(members[icon_name + 'Packed'])]
        i.clut = map(''.join, zip(*[iter(members[icon_name + 'Clut'])]*3))
        icons[icon_name] = i
    return icons

def dump_animated_icon(icon_obj, file_prefix):
    # Build a new RAW using the .data + .clut
    RAW = ''.join(icon_obj.clut[x] for x in icon_obj.data)
    # If the icon's name ends in '2X', it's 64x64 pixels, otherwise 32x32 (and x3 for RGB)
    icon_length = [3*32*32,3*64*64][icon_obj.name.endswith('2X')]
    frames = map(''.join, zip(*[iter(RAW)]*icon_length))
    # Save output to file_prefix%02.RAW
    for i,data in enumerate(frames):
        with open('%s%02d.RAW' % (file_prefix, i+1), 'wb') as f:
            f.write(data)

def dump_single_icon(icon_obj, file_prefix):
    # Build a new RAW using the .data + .clut
    RAW = ''.join(icon_obj.clut[x] for x in icon_obj.data)
    # If the icon's name ends in '2X', it's 64x64 pixels, otherwise 32x32 (and x3 for RGB)
    with open('%s.RAW' % (file_prefix), 'wb') as f:
        f.write(RAW)

def dump_NetBootImages_icons(file_path):
    members = parse_h(file_path)
    icons   = parse_members(members)
    for name, icon in icons.items():
        dump_animated_icon(icon, name+'_')

# dump_NetBootImages_icons('NetBootImages.h')

def dump_AppleLogoData_icons(file_path):
    members = parse_h(file_path)
    icons   = parse_members(members)
    for name, icon in icons.items():
        dump_single_icon(icon, name)

# dump_AppleLogoData_icons('AppleLogoData.h')