How VPhoneCore Library Works with Apple Virtualization.framework: A Complete Technical Breakdown
VPhoneCore library builds a virtual iPhone by translating a high-level manifest into a VZVirtualMachineConfiguration using both public and private APIs from Apple's Virtualization.framework.
The VPhoneCore library orchestrates a complete macOS-based virtual iPhone (vPhone) by assembling and configuring Apple Virtualization.framework's device classes. The implementation centers on VPhoneVirtualMachine.swift, which converts a codable manifest into a fully operational VZVirtualMachine. This article examines how VPhoneCore integrates with Virtualization.framework at every layer—from hardware modeling to runtime control.
Creating the Hardware Model and Platform Configuration
Every virtual iPhone begins with a hardware model definition. VPhoneCore creates a PV-3 iPhone-compatible model through VPhoneHardware.createModel():
let hwModel = try VPhoneHardware.createModel() // → VZMacHardwareModel (PV = 3)
The hardware model attaches to a VZMacPlatformConfiguration, which also requires:
- A persistent
VZMacMachineIdentifier(stored in the manifest and regenerated if absent) - ECID extraction via the private-API-friendly
Dynamicwrapper to form a predictable UDID - Auxiliary NVRAM storage initialized with boot arguments
The platform configuration setup in VPhoneVirtualMachine.swift demonstrates this assembly:
let platform = VZMacPlatformConfiguration()
platform.machineIdentifier = machineIdentifier
platform.hardwareModel = hwModel
platform.auxiliaryStorage = try VZMacAuxiliaryStorage(
creatingStorageAt: options.nvramURL,
hardwareModel: hwModel,
options: .allowOverwrite
)
VPhoneCore writes boot arguments ("serial=3 debug=0x104c04") using Dynamic to reach the private setDataValue(_:forNVRAMVariableNamed:) API. This pattern—public structure with private injection—recurs throughout the codebase.
Boot Loader Configuration and DFU Support
The boot loader supports optional custom ROM injection for DFU-style operations:
let bootloader = VZMacOSBootLoader()
if let romURL = options.romURL {
Dynamic(bootloader)._setROMURL(romURL) // private API injection
}
When forceDFU is requested, VPhoneCore constructs VZMacOSVirtualMachineStartOptions and sets the private flag via Dynamic before calling await vm.start(options: opts).
virtual iPhone Device Architecture
VPhoneCore instantiates all essential Virtualization.framework device classes and attaches them to a VZVirtualMachineConfiguration. The following table maps each subsystem to its framework implementation:
| Component | Virtualization.framework Class | VPhoneCore Integration |
|---|---|---|
| CPU / Memory | config.cpuCount, config.memorySize |
Validated against framework minimums |
| Graphics | VZMacGraphicsDeviceConfiguration → VZMacGraphicsDisplayConfiguration |
Screen dimensions and PPI from manifest |
| Audio | VZVirtioSoundDeviceConfiguration |
Host audio input/output streams |
| Storage | VZVirtioBlockDeviceConfiguration |
VZDiskImageStorageDeviceAttachment for disk images |
| Network | VZVirtioNetworkDeviceConfiguration |
Generated by VPhoneNetworking.makeNetworkDevice with NAT/bridged/off modes |
| Serial (PL011 UART) | VZSerialPortConfiguration |
Pipe-based I/O with interactive host stdin/stdout |
| VideoToolbox | VZMacVideoToolboxDeviceConfiguration |
Injected via Dynamic._setAcceleratorDevices |
| Neural Engine | VZMacNeuralEngineDeviceConfiguration |
Injected via Dynamic._setAcceleratorDevices |
| Scaler Accelerator | VZMacScalerAcceleratorDeviceConfiguration |
Injected via Dynamic._setAcceleratorDevices |
| Touch Screen | VZUSBTouchScreenConfiguration |
Injected via Dynamic._setMultiTouchDevices |
| Entropy | VZVirtioEntropyDeviceConfiguration |
Always present |
| Keyboard | VZUSBKeyboardConfiguration |
Added to config.keyboards |
| Vsock Control Channel | VZVirtioSocketDeviceConfiguration |
Enabled unless --no-vphoned |
| Synthetic Battery | VZMacBatteryPowerSourceDeviceConfiguration + VZMacSyntheticBatterySource |
Runtime charge and state control |
| SEP Coprocessor | VZSEPCoprocessorConfiguration |
Storage and optional ROM with debug stub |
| Kernel GDB Stub | VZGDBDebugStubConfiguration |
Auto-assigned or user-specified port |
Private-API devices follow a consistent pattern: Dynamic._VZ… creates the hidden object, then Dynamic(config)._set… attaches it to the configuration.
Validation and VM Lifecycle
Once assembled, the configuration undergoes framework validation before instantiation:
try config.validate()
let virtualMachine = VZVirtualMachine(configuration: config)
virtualMachine.delegate = self
VPhoneVirtualMachine conforms to VZVirtualMachineDelegate, handling guest termination, errors, and network device disconnections. All delegate callbacks log events and exit the host process appropriately.
Post-launch, VPhoneCore prints the automatically assigned kernel-debug port (macOS 26+ only) and wires the VM's serial output to host stdout.
Manifest-Driven Configuration
VPhoneVirtualMachineManifest.swift defines the codable representation of persistent VM state. The manifest stores CPU count, memory, screen parameters, network mode, storage paths, ROM locations, and SEP configuration. At startup, VPhoneVirtualMachineManifest.load(from:) deserializes this state; new machine identifiers are generated and persisted back to disk.
Building a VM from Manifest: Complete Example
import VPhoneCore
let manifestURL = URL(fileURLWithPath: "config.plist")
let manifest = try VPhoneVirtualMachineManifest.load(from: manifestURL)
let options = VPhoneVirtualMachine.Options(
configURL: manifestURL,
romURL: nil,
nvramURL: URL(fileURLWithPath: "nvram.bin"),
diskURL: URL(fileURLWithPath: "Disk.img"),
cpuCount: 8,
memorySize: 8 * 1024 * 1024 * 1024,
sepStorageURL: URL(fileURLWithPath: "SEPStorage"),
sepRomURL: nil,
screenWidth: 1290,
screenHeight: 2796,
screenPPI: 460,
screenScale: 3.0,
kernelDebugPort: nil,
variant: .regular,
noVphoned: false
)
let vm = try VPhoneVirtualMachine(options: options)
try await vm.start(forceDFU: false) // Normal boot
Runtime Control and Guest Communication
VPhoneCore provides runtime manipulation through several channels:
Synthetic battery updates:
vm.setBattery(charge: 45.0, connectivity: 2) // 45% charge, disconnected
DFU mode for low-level flashing:
try await vm.start(forceDFU: true) // Boots into DFU, ready for irecovery
The vsock-based control channel (VPhoneControl.swift) enables host-guest communication through VZVirtioSocketDeviceConfiguration. This channel supports IPA installation, screen recording, and other management operations without requiring network connectivity.
Key Source Files
The VPhoneCore library spans multiple files with distinct responsibilities:
sources/vphone-cli/VPhoneVirtualMachine.swift— Core class assemblingVZVirtualMachineConfigurationand driving VM lifecyclesources/VPhoneCore/VPhoneVirtualMachineManifest.swift— Codable manifest for persistent VM parameterssources/vphone-cli/VPhoneHardwareModel.swift— PV-3 hardware model creation helpersources/VPhoneCore/VPhoneNetworking.swift— Network device factory for NAT/bridged/off modessources/vphone-cli/VPhoneControl.swift— Host-side vsock client for guest daemon communicationsources/vphone-cli/VPhoneMenuRecord.swift— UI layer exposing VM controls to userssources/vphone-cli/VPhoneIPAInstaller.swift— IPA installation via vsock channel
Summary
- VPhoneCore library integrates with Virtualization.framework by translating a manifest into a complete
VZVirtualMachineConfigurationinVPhoneVirtualMachine.swift - Private API access through the
Dynamiclibrary enables touch screen, accelerators, and DFU booting that lack public SwiftPM headers - Hardware modeling uses
VZMacHardwareModel(PV-3) with persistentVZMacMachineIdentifierand ECID-derived UDID - Device configuration spans 15+ Virtualization.framework classes covering compute, graphics, storage, network, audio, serial, and security subsystems
- Runtime control operates through synthetic battery APIs, vsock socket device, and optional kernel GDB debug stub
Frequently Asked Questions
What is the Dynamic library used for in VPhoneCore?
The Dynamic library provides runtime Objective-C method dispatch to access private Virtualization.framework APIs that Apple does not expose in public SwiftPM headers. VPhoneCore uses Dynamic to inject ROM URLs, attach accelerator devices (VideoToolbox, Neural Engine, Scaler), enable multi-touch screens, and set DFU boot flags without compile-time symbol resolution.
How does VPhoneCore persist VM state between launches?
VM state persists through VPhoneVirtualMachineManifest, a Codable struct stored as a property list. The manifest records CPU count, memory allocation, screen dimensions, network mode, storage paths, ROM locations, machine identifier, and SEP configuration. On startup, VPhoneVirtualMachineManifest.load(from:) deserializes this state; new identifiers are generated and written back when created.
Can VPhoneCore run on any Apple Silicon Mac?
VPhoneCore requires macOS with Virtualization.framework support and Apple Silicon hardware. Specific features like the automatically assigned kernel-debug port require macOS 26 or later. Some functionality depends on private APIs that may change between macOS versions.
How does the vsock control channel work without network connectivity?
VPhoneCore uses VZVirtioSocketDeviceConfiguration—a paravirtualized socket device that operates independently of network stack configuration. The host-side VPhoneControl.swift connects to the guest's vphoned daemon through this channel, enabling management operations even when the VM's VZVirtioNetworkDeviceConfiguration is disabled or misconfigured.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →