#!/usr/bin/env python3 """Derive a VM config.plist from OSX-KVM's OpenCore config. - keep only kexts/ACPI/drivers whose files exist in the ESP - SMBIOS (model, serial, MLB, UUID, ROM=MAC) for Apple ID / iMessage - mark the vmix NIC PCI path built-in, drop iMac19,1 GPU/audio properties - boot-args, picker, resolution; optional JSON merged on top """ import argparse import json import os import plistlib APPLE_NVRAM = '7C436110-AB2A-4BBB-A880-FE41995C9F82' MCE_KEXT = { 'Arch': 'Any', 'BundlePath': 'MCEReporterDisabler.kext', 'Comment': 'Fix kernel panic on MacPro/iMacPro SMBIOS (vmix)', 'Enabled': True, 'ExecutablePath': '', 'MaxKernel': '', 'MinKernel': '', 'PlistPath': 'Contents/Info.plist', } def merge(dst, src): for k, v in src.items(): if isinstance(v, dict) and isinstance(dst.get(k), dict): merge(dst[k], v) else: dst[k] = v def main(): p = argparse.ArgumentParser() p.add_argument('--base', required=True) p.add_argument('--esp', required=True, help='directory containing EFI/OC') p.add_argument('--out', required=True) p.add_argument('--model', required=True) p.add_argument('--serial', required=True) p.add_argument('--mlb', required=True) p.add_argument('--uuid', required=True) p.add_argument('--mac', required=True) p.add_argument('--nic-path', required=True) p.add_argument('--boot-args', default='keepsyms=1') p.add_argument('--resolution', default='1024x768') p.add_argument('--show-picker', default='true') p.add_argument('--timeout', type=int, default=2) p.add_argument('--extra-json', default='{}') p.add_argument('--memory-mb', type=int, default=8192, help='VM RAM, described as 4 DIMMs') a = p.parse_args() with open(a.base, 'rb') as f: cfg = plistlib.load(f) oc = os.path.join(a.esp, 'EFI', 'OC') def exists(sub, path): return os.path.exists(os.path.join(oc, sub, path)) kexts = [k for k in cfg['Kernel']['Add'] if exists('Kexts', k['BundlePath'])] if a.model.startswith(('MacPro', 'iMacPro')) and exists('Kexts', MCE_KEXT['BundlePath']) \ and not any(k['BundlePath'] == MCE_KEXT['BundlePath'] for k in kexts): kexts.append(dict(MCE_KEXT)) cfg['Kernel']['Add'] = kexts cfg['ACPI']['Add'] = [x for x in cfg['ACPI']['Add'] if exists('ACPI', x['Path'])] cfg['UEFI']['Drivers'] = [d for d in cfg['UEFI']['Drivers'] if exists('Drivers', d['Path'])] cfg['Misc']['Tools'] = [t for t in cfg['Misc'].get('Tools', []) if exists('Tools', t['Path'])] cfg['Misc']['Entries'] = [] g = cfg['PlatformInfo']['Generic'] g['SystemProductName'] = a.model g['SystemSerialNumber'] = a.serial g['MLB'] = a.mlb g['SystemUUID'] = a.uuid.upper() g['ROM'] = bytes.fromhex(a.mac.replace(':', '').replace('-', '')) g['SpoofVendor'] = True g['AdviseFeatures'] = False cfg['DeviceProperties']['Add'] = {a.nic_path: {'built-in': b'\x01'}} cfg['DeviceProperties']['Delete'] = {} nv = cfg['NVRAM']['Add'].setdefault(APPLE_NVRAM, {}) nv['boot-args'] = a.boot_args nv['prev-lang:kbd'] = b'en-US:0' nv['csr-active-config'] = b'\x00\x00\x00\x00' cfg['UEFI']['Output']['Resolution'] = a.resolution cfg['Misc']['Boot']['ShowPicker'] = a.show_picker.lower() == 'true' cfg['Misc']['Boot']['Timeout'] = a.timeout cfg['Misc']['Boot']['HideAuxiliary'] = True cfg['Misc']['Security']['ScanPolicy'] = 0 # MacPro7,1 firmware expects DIMMs in pairs (>= 4); with QEMU's single SMBIOS # module macOS shows "Memory Modules Misconfigured" at every login. Describe # the VM's RAM as four DDR4 modules instead. if a.model.startswith('MacPro7'): size = max(1024, a.memory_mb // 4) cfg['PlatformInfo']['CustomMemory'] = True cfg['PlatformInfo']['Memory'] = { 'DataWidth': 64, 'ErrorCorrection': 3, 'FormFactor': 9, 'MaxCapacity': 1536 * 1024 * 1024 * 1024, 'TotalWidth': 64, 'Type': 26, 'TypeDetail': 128, 'Devices': [{ 'AssetTag': '', 'BankLocator': f'BANK {i}', 'DeviceLocator': f'DIMM{i + 1}', 'Manufacturer': 'Apple', 'PartNumber': f'VMIX{size}', 'SerialNumber': f'VMIX{i:04d}', 'Size': size, 'Speed': 2666, } for i in range(4)], } cfg['Misc']['Security']['SecureBootModel'] = 'Disabled' cfg['Misc']['Security']['AllowSetDefault'] = True cfg['Misc']['Debug']['Target'] = 0 merge(cfg, json.loads(a.extra_json)) with open(a.out, 'wb') as f: plistlib.dump(cfg, f, sort_keys=True) print('kexts:', ', '.join(k['BundlePath'] for k in kexts)) print('acpi:', ', '.join(x['Path'] for x in cfg['ACPI']['Add'])) if __name__ == '__main__': main()