#!/usr/bin/env python3
# pylint: disable=redefined-outer-name
'''
Quick and dirty unpacker for next generation Mac firmware.

Copyright (c) 2019, vit9696

All rights reserved.

This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
'''

import os
import struct
import sys

MAC_EFI_HEADER_FORMAT = '<8sII'
MAC_EFI_HEADER_SIZE = 256
MAC_EFI_MAGIC = b'\x5F\x4D\x45\x46\x49\x42\x49\x4E'
INTEL_EFI_MAGIC = b'\x5A\xA5\xF0\x0F'
IM4P_MAC_EFI_MAGIC = b'\x49\x4D\x34\x50\x16\x04\x6D\x65\x66\x69'


def save_macfw(firmware, path):
    # Hack for Apple images with an extra zero typo in NVRAM volume size.
    nvbug_find = b'\x8D\x2B\xF1\xFF\x96\x76\x8B\x4C\xA9\x85\x27\x47\x07\x5B\x4F\x50\x00\x00\x2F\x00'
    nvbug_repl = b'\x8D\x2B\xF1\xFF\x96\x76\x8B\x4C\xA9\x85\x27\x47\x07\x5B\x4F\x50\x0C\xEF\x02\x00'

    with open(path, 'wb') as fd1:
        fd1.write(firmware.replace(nvbug_find, nvbug_repl))
    return 0


def get_fwname(filename, outdir, suffix=''):
    outname = os.path.splitext(os.path.basename(filename))[0]
    return os.path.join(outdir, f'{outname}{suffix}.rom')


def unwrap_macefi(filename, outdir, firmware, firmware_len):
    if firmware_len < MAC_EFI_HEADER_SIZE:
        raise RuntimeError('Invalid firmware size')

    magic, first, second = struct.unpack_from(MAC_EFI_HEADER_FORMAT, firmware)
    if magic != MAC_EFI_MAGIC:
        raise RuntimeError('Unsupported magic!')

    if first >= firmware_len - MAC_EFI_HEADER_SIZE:
        raise RuntimeError(f'Invalid first image offset 0x{first:02X} for size 0x{firmware_len:02X}')

    if second >= firmware_len - MAC_EFI_HEADER_SIZE:
        raise RuntimeError(f'Invalid second image offset 0x{second:02X} for size 0x{firmware_len:02X}')

    if first >= second:
        raise RuntimeError(f'First image offset 0x{first:02X} comes after second 0x{second:02X}')

    save_macfw(firmware[first + MAC_EFI_HEADER_SIZE:second + MAC_EFI_HEADER_SIZE], get_fwname(filename, outdir, '1'))
    save_macfw(firmware[second + MAC_EFI_HEADER_SIZE:], get_fwname(filename, outdir, '2'))
    return 0


def unwrap_im4p(filename, outdir, firmware, firmware_len):
    # FIXME: This is ugly, as IM4P is actually an ASN1 encoded sequence.
    off = firmware.find(IM4P_MAC_EFI_MAGIC)
    if off < 0 or firmware[0] != 0x30:
        return unwrap_macefi(filename, outdir, firmware, firmware_len)

    off = firmware.find(MAC_EFI_MAGIC)
    if off >= 0:
        return unwrap_macefi(filename, outdir, firmware[off:], firmware_len - off)

    off = firmware.find(INTEL_EFI_MAGIC)
    if off >= 16:
        return save_macfw(firmware[off - 16:], get_fwname(filename, outdir))

    raise RuntimeError('Unsupported firmware format')


def unpack_image(filename, outdir):
    if not os.path.exists(filename):
        raise RuntimeError(f'Failed to find filename image {filename}!')

    with open(filename, 'rb') as fd:
        firmware = fd.read()
        firmware_len = len(firmware)
        return unwrap_im4p(filename, outdir, firmware, firmware_len)


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('Usage: ./MacEfiUnpack.py filename [outdir]')
        sys.exit(-1)
    try:
        if len(sys.argv) > 2:
            outdir = sys.argv[2]
        else:
            outdir = os.path.dirname(sys.argv[1])
        sys.exit(unpack_image(sys.argv[1], outdir))
    except Exception as e:
        print(f'ERROR {e}')
