mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-04-05 13:09:06 +08:00
feat: Add VM manifest system and code clarity improvements
Implement VM configuration manifest system compatible with security-pcc's
VMBundle.Config format, storing VM settings in config.plist.
**Manifest System:**
- Add VPhoneVirtualMachineManifest.swift with security-pcc compatible structure
- Add scripts/vm_manifest.py for manifest generation during vm_new
- Update VPhoneCLI to support --config option with CLI overrides
- Update vm_create.sh to generate config.plist with CPU/memory/screen settings
**Environment Variables:**
- CPU/MEMORY/DISK_SIZE now only used during vm_new (written to manifest)
- boot/boot_dfu automatically read from config.plist
- Remove unused CFW_INPUT variable (overridden by scripts internally)
- Document remaining variables with their usage scope
**Documentation:**
- Update README.md with VM configuration section
- Update docs/README_{zh,ja,ko}.md with translated VM configuration docs
- Update Makefile help output with vm_new options and config.plist usage
- Fix fw_patch_jb description: "dev + JB extensions"
- Fix restore_get_shsh description: "Dump SHSH response from Apple"
**Code Quality:**
- Add VPhoneVirtualMachineRefactored.swift demonstrating code-clarity principles
- Extract 200+ line init into focused configuration methods
- Improve naming: hardwareModel, graphicsConfiguration, soundDevice
- Add BatteryConnectivity enum for magic numbers
- Create research/manifest_and_refactoring_summary.md with full analysis
**Compatibility with security-pcc:**
- Platform type: Fixed vresearch101 (iPhone-only)
- Network: NAT only (no bridging/host-only needed)
- Added: ScreenConfig and SEP storage (iPhone-specific)
- Removed: VirtMesh plugin support (PCC-specific)
docs: add machineIdentifier storage analysis
Research and validate the integration of machineIdentifier into config.plist.
**Findings:**
- security-pcc stores machineIdentifier in config.plist (same approach)
- VZMacAuxiliaryStorage creation is independent of machineIdentifier
- VZMacMachineIdentifier only requires Data representation, not file source
- No binding or validation between components
**Conclusion:**
- ✅ No compatibility issues
- ✅ Matches security-pcc official implementation
- ✅ Proper handling of first-boot creation and data recovery
- ✅ Safe to use
Delete VPhoneVirtualMachineRefactored.swift
refactor: integrate machineIdentifier into config.plist
Move machineIdentifier storage from standalone machineIdentifier.bin file
into the central config.plist manifest for simpler VM configuration.
**Changes:**
- VPhoneVirtualMachineManifest: Remove machineIDFile field
- VPhoneVirtualMachine: Load/create machineIdentifier from manifest
- VPhoneCLI: Remove --machine-id parameter, require --config
- Makefile: Remove --machine-id from boot/boot_dfu targets
- vm_manifest.py: Remove machineIDFile from manifest structure
**Behavior:**
- First boot: Creates machineIdentifier and saves to config.plist
- Subsequent boots: Loads machineIdentifier from config.plist
- Invalid/empty machineIdentifier: Auto-regenerates and updates manifest
- All VM configuration now centralized in single config.plist file
**File cleanup:**
- Move VPhoneVirtualMachineRefactored.swift to research/ as reference
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -42,53 +42,32 @@ class VPhoneAppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
@MainActor
|
||||
private func startVirtualMachine() async throws {
|
||||
let romURL = URL(fileURLWithPath: cli.rom)
|
||||
guard FileManager.default.fileExists(atPath: romURL.path) else {
|
||||
throw VPhoneError.romNotFound(cli.rom)
|
||||
let options = try cli.resolveOptions()
|
||||
|
||||
guard FileManager.default.fileExists(atPath: options.romURL.path) else {
|
||||
throw VPhoneError.romNotFound(options.romURL.path)
|
||||
}
|
||||
|
||||
let diskURL = URL(fileURLWithPath: cli.disk)
|
||||
let nvramURL = URL(fileURLWithPath: cli.nvram)
|
||||
let machineIDURL = URL(fileURLWithPath: cli.machineId)
|
||||
let sepStorageURL = URL(fileURLWithPath: cli.sepStorage)
|
||||
let sepRomURL = URL(fileURLWithPath: cli.sepRom)
|
||||
|
||||
print("=== vphone-cli ===")
|
||||
print("ROM : \(cli.rom)")
|
||||
print("Disk : \(cli.disk)")
|
||||
print("NVRAM : \(cli.nvram)")
|
||||
print("MachID: \(cli.machineId)")
|
||||
print("CPU : \(cli.cpu)")
|
||||
print("Memory: \(cli.memory) MB")
|
||||
print("ROM : \(options.romURL.path)")
|
||||
print("Disk : \(options.diskURL.path)")
|
||||
print("NVRAM : \(options.nvramURL.path)")
|
||||
print("Config: \(options.configURL.path)")
|
||||
print("CPU : \(options.cpuCount)")
|
||||
print("Memory: \(options.memorySize / 1024 / 1024) MB")
|
||||
print(
|
||||
"Screen: \(cli.screenWidth)x\(cli.screenHeight) @ \(cli.screenPpi) PPI (scale \(cli.screenScale)x)"
|
||||
"Screen: \(options.screenWidth)x\(options.screenHeight) @ \(options.screenPPI) PPI (scale \(options.screenScale)x)"
|
||||
)
|
||||
if let kernelDebugPort = cli.kernelDebugPort {
|
||||
if let kernelDebugPort = options.kernelDebugPort {
|
||||
print("Kernel debug stub : 127.0.0.1:\(kernelDebugPort)")
|
||||
} else {
|
||||
print("Kernel debug stub : auto-assigned")
|
||||
}
|
||||
print("SEP : enabled")
|
||||
print(" storage : \(cli.sepStorage)")
|
||||
print(" rom : \(cli.sepRom)")
|
||||
print(" storage : \(options.sepStorageURL.path)")
|
||||
print(" rom : \(options.sepRomURL.path)")
|
||||
print("")
|
||||
|
||||
let options = VPhoneVirtualMachine.Options(
|
||||
romURL: romURL,
|
||||
nvramURL: nvramURL,
|
||||
machineIDURL: machineIDURL,
|
||||
diskURL: diskURL,
|
||||
cpuCount: cli.cpu,
|
||||
memorySize: UInt64(cli.memory) * 1024 * 1024,
|
||||
sepStorageURL: sepStorageURL,
|
||||
sepRomURL: sepRomURL,
|
||||
screenWidth: cli.screenWidth,
|
||||
screenHeight: cli.screenHeight,
|
||||
screenPPI: cli.screenPpi,
|
||||
screenScale: cli.screenScale,
|
||||
kernelDebugPort: cli.kernelDebugPort
|
||||
)
|
||||
|
||||
let vm = try VPhoneVirtualMachine(options: options)
|
||||
self.vm = vm
|
||||
|
||||
@@ -115,9 +94,9 @@ class VPhoneAppDelegate: NSObject, NSApplicationDelegate {
|
||||
let wc = VPhoneWindowController()
|
||||
wc.showWindow(
|
||||
for: vm.virtualMachine,
|
||||
screenWidth: cli.screenWidth,
|
||||
screenHeight: cli.screenHeight,
|
||||
screenScale: cli.screenScale,
|
||||
screenWidth: options.screenWidth,
|
||||
screenHeight: options.screenHeight,
|
||||
screenScale: options.screenScale,
|
||||
keyHelper: keyHelper,
|
||||
control: control,
|
||||
ecid: vm.ecidHex
|
||||
|
||||
@@ -15,10 +15,16 @@ struct VPhoneCLI: ParsableCommand {
|
||||
- Signed with vphone entitlements (done automatically by wrapper script)
|
||||
|
||||
Example:
|
||||
vphone-cli --rom firmware/rom.bin --disk firmware/disk.img
|
||||
vphone-cli --config config.plist --rom ./AVPBooter.vresearch1.bin --disk ./Disk.img
|
||||
"""
|
||||
)
|
||||
|
||||
@Option(
|
||||
help: "Path to VM manifest plist (config.plist). Required.",
|
||||
transform: URL.init(fileURLWithPath:)
|
||||
)
|
||||
var config: URL
|
||||
|
||||
@Option(help: "Path to the AVPBooter / ROM binary")
|
||||
var rom: String
|
||||
|
||||
@@ -28,14 +34,11 @@ struct VPhoneCLI: ParsableCommand {
|
||||
@Option(help: "Path to NVRAM storage (created/overwritten)")
|
||||
var nvram: String = "nvram.bin"
|
||||
|
||||
@Option(help: "Path to machineIdentifier file (created if missing)")
|
||||
var machineId: String
|
||||
@Option(help: "Number of CPU cores (overridden by --config if present)")
|
||||
var cpu: Int?
|
||||
|
||||
@Option(help: "Number of CPU cores")
|
||||
var cpu: Int = 8
|
||||
|
||||
@Option(help: "Memory size in MB")
|
||||
var memory: Int = 8192
|
||||
@Option(help: "Memory size in MB (overridden by --config if present)")
|
||||
var memory: Int?
|
||||
|
||||
@Option(help: "Path to SEP storage file (created if missing)")
|
||||
var sepStorage: String
|
||||
@@ -46,14 +49,14 @@ struct VPhoneCLI: ParsableCommand {
|
||||
@Flag(help: "Boot into DFU mode")
|
||||
var dfu: Bool = false
|
||||
|
||||
@Option(help: "Display width in pixels (default: 1290)")
|
||||
var screenWidth: Int = 1290
|
||||
@Option(help: "Display width in pixels (overridden by --config if present)")
|
||||
var screenWidth: Int?
|
||||
|
||||
@Option(help: "Display height in pixels (default: 2796)")
|
||||
var screenHeight: Int = 2796
|
||||
@Option(help: "Display height in pixels (overridden by --config if present)")
|
||||
var screenHeight: Int?
|
||||
|
||||
@Option(help: "Display pixels per inch (default: 460)")
|
||||
var screenPpi: Int = 460
|
||||
@Option(help: "Display pixels per inch (overridden by --config if present)")
|
||||
var screenPpi: Int?
|
||||
|
||||
@Option(help: "Window scale divisor (default: 3.0)")
|
||||
var screenScale: Double = 3.0
|
||||
@@ -67,6 +70,59 @@ struct VPhoneCLI: ParsableCommand {
|
||||
@Option(help: "Path to signed vphoned binary for guest auto-update")
|
||||
var vphonedBin: String = ".vphoned.signed"
|
||||
|
||||
/// Resolve final options by merging manifest with command-line overrides
|
||||
func resolveOptions() throws -> VPhoneVirtualMachine.Options {
|
||||
// Start with command-line paths
|
||||
let romURL = URL(fileURLWithPath: rom)
|
||||
let diskURL = URL(fileURLWithPath: disk)
|
||||
let nvramURL = URL(fileURLWithPath: nvram)
|
||||
let sepStorageURL = URL(fileURLWithPath: sepStorage)
|
||||
let sepRomURL = URL(fileURLWithPath: sepRom)
|
||||
|
||||
// Default values
|
||||
var resolvedCpuCount = 8
|
||||
var resolvedMemorySize: UInt64 = 8 * 1024 * 1024 * 1024
|
||||
var resolvedScreenWidth = 1290
|
||||
var resolvedScreenHeight = 2796
|
||||
var resolvedScreenPpi = 460
|
||||
var resolvedScreenScale = 3.0
|
||||
|
||||
// Load manifest (required)
|
||||
let manifest = try VPhoneVirtualMachineManifest.load(from: config)
|
||||
print("[vphone] Loaded VM manifest from \(config.path)")
|
||||
|
||||
// Apply manifest settings
|
||||
resolvedCpuCount = Int(manifest.cpuCount)
|
||||
resolvedMemorySize = manifest.memorySize
|
||||
resolvedScreenWidth = manifest.screenConfig.width
|
||||
resolvedScreenHeight = manifest.screenConfig.height
|
||||
resolvedScreenPpi = manifest.screenConfig.pixelsPerInch
|
||||
resolvedScreenScale = manifest.screenConfig.scale
|
||||
|
||||
// Apply command-line overrides (if provided)
|
||||
if let cpuArg = cpu { resolvedCpuCount = cpuArg }
|
||||
if let memoryArg = memory { resolvedMemorySize = UInt64(memoryArg) * 1024 * 1024 }
|
||||
if let screenWidthArg = screenWidth { resolvedScreenWidth = screenWidthArg }
|
||||
if let screenHeightArg = screenHeight { resolvedScreenHeight = screenHeightArg }
|
||||
if let screenPpiArg = screenPpi { resolvedScreenPpi = screenPpiArg }
|
||||
|
||||
return VPhoneVirtualMachine.Options(
|
||||
configURL: config,
|
||||
romURL: romURL,
|
||||
nvramURL: nvramURL,
|
||||
diskURL: diskURL,
|
||||
cpuCount: resolvedCpuCount,
|
||||
memorySize: resolvedMemorySize,
|
||||
sepStorageURL: sepStorageURL,
|
||||
sepRomURL: sepRomURL,
|
||||
screenWidth: resolvedScreenWidth,
|
||||
screenHeight: resolvedScreenHeight,
|
||||
screenPPI: resolvedScreenPpi,
|
||||
screenScale: resolvedScreenScale,
|
||||
kernelDebugPort: kernelDebugPort
|
||||
)
|
||||
}
|
||||
|
||||
/// Execution is driven by VPhoneAppDelegate; main.swift calls parseOrExit()
|
||||
/// and hands the parsed options to the delegate.
|
||||
mutating func run() throws {}
|
||||
|
||||
@@ -5,6 +5,9 @@ enum VPhoneError: Error, CustomStringConvertible {
|
||||
case romNotFound(String)
|
||||
case diskNotFound(String)
|
||||
case invalidKernelDebugPort(Int)
|
||||
case manifestLoadFailed(path: String, underlying: Error)
|
||||
case manifestParseFailed(path: String, underlying: Error)
|
||||
case manifestWriteFailed(path: String, underlying: Error)
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
@@ -22,6 +25,12 @@ enum VPhoneError: Error, CustomStringConvertible {
|
||||
"Disk image not found: \(p)"
|
||||
case let .invalidKernelDebugPort(port):
|
||||
"Invalid kernel debug port: \(port) (expected 6000...65535)"
|
||||
case let .manifestLoadFailed(path: path, underlying: _):
|
||||
"Failed to load manifest from \(path)"
|
||||
case let .manifestParseFailed(path: path, underlying: _):
|
||||
"Failed to parse manifest at \(path)"
|
||||
case let .manifestWriteFailed(path: path, underlying: _):
|
||||
"Failed to write manifest to \(path)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,11 @@ class VPhoneMenuController {
|
||||
// App menu
|
||||
let appMenuItem = NSMenuItem()
|
||||
let appMenu = NSMenu(title: "vphone")
|
||||
let buildItem = NSMenuItem(title: "Build: \(VPhoneBuildInfo.commitHash)", action: nil, keyEquivalent: "")
|
||||
#if canImport(VPhoneBuildInfo)
|
||||
let buildItem = NSMenuItem(title: "Build: \(VPhoneBuildInfo.commitHash)", action: nil, keyEquivalent: "")
|
||||
#else
|
||||
let buildItem = NSMenuItem(title: "Build: unknown", action: nil, keyEquivalent: "")
|
||||
#endif
|
||||
buildItem.isEnabled = false
|
||||
appMenu.addItem(buildItem)
|
||||
appMenu.addItem(NSMenuItem.separator())
|
||||
|
||||
@@ -14,9 +14,9 @@ class VPhoneVirtualMachine: NSObject, VZVirtualMachineDelegate {
|
||||
private var batterySource: AnyObject?
|
||||
|
||||
struct Options {
|
||||
var configURL: URL
|
||||
var romURL: URL
|
||||
var nvramURL: URL
|
||||
var machineIDURL: URL
|
||||
var diskURL: URL
|
||||
var cpuCount: Int = 8
|
||||
var memorySize: UInt64 = 8 * 1024 * 1024 * 1024
|
||||
@@ -40,32 +40,71 @@ class VPhoneVirtualMachine: NSObject, VZVirtualMachineDelegate {
|
||||
let hwModel = try VPhoneHardware.createModel()
|
||||
print("[vphone] PV=3 hardware model: isSupported = true")
|
||||
|
||||
// --- Platform ---
|
||||
let platform = VZMacPlatformConfiguration()
|
||||
|
||||
// Persist machineIdentifier for stable ECID
|
||||
// --- Load or create machineIdentifier from manifest ---
|
||||
let machineIdentifier: VZMacMachineIdentifier
|
||||
if let savedData = try? Data(contentsOf: options.machineIDURL),
|
||||
let savedID = VZMacMachineIdentifier(dataRepresentation: savedData)
|
||||
{
|
||||
machineIdentifier = savedID
|
||||
print("[vphone] Loaded machineIdentifier (ECID stable)")
|
||||
} else {
|
||||
var manifest = try VPhoneVirtualMachineManifest.load(from: options.configURL)
|
||||
|
||||
if manifest.machineIdentifier.isEmpty {
|
||||
// Create new machineIdentifier and save to manifest
|
||||
let newID = VZMacMachineIdentifier()
|
||||
machineIdentifier = newID
|
||||
try newID.dataRepresentation.write(to: options.machineIDURL)
|
||||
print("[vphone] Created new machineIdentifier -> \(options.machineIDURL.lastPathComponent)")
|
||||
|
||||
// Update manifest with new machineIdentifier
|
||||
manifest = VPhoneVirtualMachineManifest(
|
||||
platformType: manifest.platformType,
|
||||
platformFusing: manifest.platformFusing,
|
||||
machineIdentifier: newID.dataRepresentation,
|
||||
cpuCount: manifest.cpuCount,
|
||||
memorySize: manifest.memorySize,
|
||||
screenConfig: manifest.screenConfig,
|
||||
networkConfig: manifest.networkConfig,
|
||||
diskImage: manifest.diskImage,
|
||||
nvramStorage: manifest.nvramStorage,
|
||||
romImages: manifest.romImages,
|
||||
sepStorage: manifest.sepStorage
|
||||
)
|
||||
try manifest.write(to: options.configURL)
|
||||
|
||||
print("[vphone] Created new machineIdentifier -> saved to config.plist")
|
||||
} else if let savedID = VZMacMachineIdentifier(dataRepresentation: manifest.machineIdentifier) {
|
||||
machineIdentifier = savedID
|
||||
print("[vphone] Loaded machineIdentifier from config.plist (ECID stable)")
|
||||
} else {
|
||||
// Invalid data in manifest, create new
|
||||
let newID = VZMacMachineIdentifier()
|
||||
machineIdentifier = newID
|
||||
|
||||
manifest = VPhoneVirtualMachineManifest(
|
||||
platformType: manifest.platformType,
|
||||
platformFusing: manifest.platformFusing,
|
||||
machineIdentifier: newID.dataRepresentation,
|
||||
cpuCount: manifest.cpuCount,
|
||||
memorySize: manifest.memorySize,
|
||||
screenConfig: manifest.screenConfig,
|
||||
networkConfig: manifest.networkConfig,
|
||||
diskImage: manifest.diskImage,
|
||||
nvramStorage: manifest.nvramStorage,
|
||||
romImages: manifest.romImages,
|
||||
sepStorage: manifest.sepStorage
|
||||
)
|
||||
try manifest.write(to: options.configURL)
|
||||
|
||||
print("[vphone] Invalid machineIdentifier in config.plist, created new")
|
||||
}
|
||||
|
||||
// --- Platform ---
|
||||
let platform = VZMacPlatformConfiguration()
|
||||
platform.machineIdentifier = machineIdentifier
|
||||
|
||||
if let identity = Self.resolveDeviceIdentity(machineIdentifier: machineIdentifier) {
|
||||
ecidHex = identity.ecidHex
|
||||
print("[vphone] ECID: \(ecidHex!)")
|
||||
print("[vphone] Predicted UDID: \(identity.udid)")
|
||||
let outputURL = options.configURL.deletingLastPathComponent().appendingPathComponent(
|
||||
"udid-prediction.txt"
|
||||
)
|
||||
do {
|
||||
let outputURL = try Self.writeUDIDPrediction(
|
||||
identity: identity, machineIDURL: options.machineIDURL
|
||||
)
|
||||
try Self.writeUDIDPrediction(identity: identity, to: outputURL)
|
||||
print("[vphone] Wrote UDID prediction: \(outputURL.path)")
|
||||
} catch {
|
||||
print("[vphone] Warning: failed to write udid-prediction.txt: \(error)")
|
||||
@@ -255,18 +294,14 @@ class VPhoneVirtualMachine: NSObject, VZVirtualMachineDelegate {
|
||||
return DeviceIdentity(cpidHex: cpidHex, ecidHex: ecidHex, udid: udid)
|
||||
}
|
||||
|
||||
private static func writeUDIDPrediction(identity: DeviceIdentity, machineIDURL: URL) throws -> URL {
|
||||
let outputURL = machineIDURL.deletingLastPathComponent().appendingPathComponent(
|
||||
"udid-prediction.txt"
|
||||
)
|
||||
private static func writeUDIDPrediction(identity: DeviceIdentity, to outputURL: URL) throws {
|
||||
let content = """
|
||||
UDID=\(identity.udid)
|
||||
CPID=0x\(identity.cpidHex)
|
||||
ECID=0x\(identity.ecidHex)
|
||||
MACHINE_IDENTIFIER=\(machineIDURL.lastPathComponent)
|
||||
MACHINE_IDENTIFIER=config.plist
|
||||
"""
|
||||
try content.write(to: outputURL, atomically: true, encoding: .utf8)
|
||||
return outputURL
|
||||
}
|
||||
|
||||
// MARK: - Battery
|
||||
|
||||
180
sources/vphone-cli/VPhoneVirtualMachineManifest.swift
Normal file
180
sources/vphone-cli/VPhoneVirtualMachineManifest.swift
Normal file
@@ -0,0 +1,180 @@
|
||||
import Foundation
|
||||
import Virtualization
|
||||
|
||||
/// VPhoneVirtualMachineManifest represents the on-disk VM configuration manifest.
|
||||
/// Structure is compatible with security-pcc's VMBundle.Config format.
|
||||
struct VPhoneVirtualMachineManifest: Codable {
|
||||
// MARK: - Platform
|
||||
|
||||
/// Platform type (fixed to vresearch101 for vphone)
|
||||
let platformType: PlatformType
|
||||
|
||||
/// Platform fusing mode (prod/dev) - determined by host OS capabilities
|
||||
let platformFusing: PlatformFusing?
|
||||
|
||||
/// Machine identifier (opaque ECID representation)
|
||||
let machineIdentifier: Data
|
||||
|
||||
// MARK: - Hardware
|
||||
|
||||
/// CPU core count
|
||||
let cpuCount: UInt
|
||||
|
||||
/// Memory size in bytes
|
||||
let memorySize: UInt64
|
||||
|
||||
// MARK: - Display
|
||||
|
||||
/// Screen configuration
|
||||
let screenConfig: ScreenConfig
|
||||
|
||||
// MARK: - Network
|
||||
|
||||
/// Network configuration (NAT mode for vphone)
|
||||
let networkConfig: NetworkConfig
|
||||
|
||||
// MARK: - Storage
|
||||
|
||||
/// Disk image filename
|
||||
let diskImage: String
|
||||
|
||||
/// NVRAM storage filename
|
||||
let nvramStorage: String
|
||||
|
||||
// MARK: - ROMs
|
||||
|
||||
/// ROM image paths
|
||||
let romImages: ROMImages
|
||||
|
||||
// MARK: - SEP
|
||||
|
||||
/// SEP storage filename
|
||||
let sepStorage: String
|
||||
|
||||
// MARK: - Nested Types
|
||||
|
||||
enum PlatformType: String, Codable {
|
||||
case vresearch101
|
||||
}
|
||||
|
||||
enum PlatformFusing: String, Codable {
|
||||
case prod
|
||||
case dev
|
||||
}
|
||||
|
||||
struct ScreenConfig: Codable {
|
||||
let width: Int
|
||||
let height: Int
|
||||
let pixelsPerInch: Int
|
||||
let scale: Double
|
||||
|
||||
static let `default` = ScreenConfig(
|
||||
width: 1290,
|
||||
height: 2796,
|
||||
pixelsPerInch: 460,
|
||||
scale: 3.0
|
||||
)
|
||||
}
|
||||
|
||||
struct NetworkConfig: Codable {
|
||||
let mode: NetworkMode
|
||||
let macAddress: String
|
||||
|
||||
enum NetworkMode: String, Codable {
|
||||
case nat
|
||||
case bridged
|
||||
case hostOnly
|
||||
case none
|
||||
}
|
||||
|
||||
static let `default` = NetworkConfig(mode: .nat, macAddress: "")
|
||||
}
|
||||
|
||||
struct ROMImages: Codable {
|
||||
let avpBooter: String
|
||||
let avpSEPBooter: String
|
||||
}
|
||||
|
||||
// MARK: - Init from VM creation parameters
|
||||
|
||||
init(
|
||||
platformType: PlatformType = .vresearch101,
|
||||
platformFusing: PlatformFusing? = nil,
|
||||
machineIdentifier: Data = Data(),
|
||||
cpuCount: UInt,
|
||||
memorySize: UInt64,
|
||||
screenConfig: ScreenConfig = .default,
|
||||
networkConfig: NetworkConfig = .default,
|
||||
diskImage: String = "Disk.img",
|
||||
nvramStorage: String = "nvram.bin",
|
||||
romImages: ROMImages,
|
||||
sepStorage: String = "SEPStorage"
|
||||
) {
|
||||
self.platformType = platformType
|
||||
self.platformFusing = platformFusing
|
||||
self.machineIdentifier = machineIdentifier
|
||||
self.cpuCount = cpuCount
|
||||
self.memorySize = memorySize
|
||||
self.screenConfig = screenConfig
|
||||
self.networkConfig = networkConfig
|
||||
self.diskImage = diskImage
|
||||
self.nvramStorage = nvramStorage
|
||||
self.romImages = romImages
|
||||
self.sepStorage = sepStorage
|
||||
}
|
||||
|
||||
// MARK: - Load/Save
|
||||
|
||||
/// Load manifest from a plist file
|
||||
static func load(from url: URL) throws -> VPhoneVirtualMachineManifest {
|
||||
let data: Data
|
||||
do {
|
||||
data = try Data(contentsOf: url)
|
||||
} catch {
|
||||
throw VPhoneError.manifestLoadFailed(path: url.path, underlying: error)
|
||||
}
|
||||
|
||||
let decoder = PropertyListDecoder()
|
||||
do {
|
||||
return try decoder.decode(VPhoneVirtualMachineManifest.self, from: data)
|
||||
} catch {
|
||||
throw VPhoneError.manifestParseFailed(path: url.path, underlying: error)
|
||||
}
|
||||
}
|
||||
|
||||
/// Save manifest to a plist file
|
||||
func write(to url: URL) throws {
|
||||
let encoder = PropertyListEncoder()
|
||||
encoder.outputFormat = .xml
|
||||
|
||||
do {
|
||||
let data = try encoder.encode(self)
|
||||
try data.write(to: url)
|
||||
} catch {
|
||||
throw VPhoneError.manifestWriteFailed(path: url.path, underlying: error)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
||||
/// Convert to JSON string for logging/debugging
|
||||
func asJSON() -> String {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .withoutEscapingSlashes
|
||||
do {
|
||||
return try String(decoding: encoder.encode(self), as: UTF8.self)
|
||||
} catch {
|
||||
return "{ }"
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve relative path to absolute URL within VM directory
|
||||
func resolve(path: String, in vmDirectory: URL) -> URL {
|
||||
vmDirectory.appendingPathComponent(path)
|
||||
}
|
||||
|
||||
/// Get VZMacMachineIdentifier from manifest data
|
||||
func vzMachineIdentifier() -> VZMacMachineIdentifier? {
|
||||
VZMacMachineIdentifier(dataRepresentation: machineIdentifier)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user