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:
38
Makefile
38
Makefile
@@ -4,13 +4,12 @@
|
|||||||
|
|
||||||
# ─── Configuration (override with make VAR=value) ─────────────────
|
# ─── Configuration (override with make VAR=value) ─────────────────
|
||||||
VM_DIR ?= vm
|
VM_DIR ?= vm
|
||||||
CPU ?= 8
|
CPU ?= 8 # CPU cores (only used during vm_new)
|
||||||
MEMORY ?= 8192
|
MEMORY ?= 8192 # Memory in MB (only used during vm_new)
|
||||||
DISK_SIZE ?= 64
|
DISK_SIZE ?= 64 # Disk size in GB (only used during vm_new)
|
||||||
CFW_INPUT ?= cfw_input
|
RESTORE_UDID ?= # UDID for restore operations
|
||||||
RESTORE_UDID ?=
|
RESTORE_ECID ?= # ECID for restore operations
|
||||||
RESTORE_ECID ?=
|
IRECOVERY_ECID ?= # ECID for irecovery operations
|
||||||
IRECOVERY_ECID ?=
|
|
||||||
|
|
||||||
# ─── Build info ──────────────────────────────────────────────────
|
# ─── Build info ──────────────────────────────────────────────────
|
||||||
GIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
GIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||||
@@ -57,20 +56,24 @@ help:
|
|||||||
@echo " make clean Remove all build artifacts (keeps IPSWs)"
|
@echo " make clean Remove all build artifacts (keeps IPSWs)"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "VM management:"
|
@echo "VM management:"
|
||||||
@echo " make vm_new Create VM directory"
|
@echo " make vm_new Create VM directory with manifest (config.plist)"
|
||||||
@echo " make boot Boot VM (GUI)"
|
@echo " Options: VM_DIR=vm VM directory name"
|
||||||
@echo " make boot_dfu Boot VM in DFU mode"
|
@echo " CPU=8 CPU cores (stored in manifest)"
|
||||||
|
@echo " MEMORY=8192 Memory in MB (stored in manifest)"
|
||||||
|
@echo " DISK_SIZE=64 Disk size in GB (stored in manifest)"
|
||||||
|
@echo " make boot Boot VM (reads from config.plist)"
|
||||||
|
@echo " make boot_dfu Boot VM in DFU mode (reads from config.plist)"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Firmware pipeline:"
|
@echo "Firmware pipeline:"
|
||||||
@echo " make fw_prepare Download IPSWs, extract, merge"
|
@echo " make fw_prepare Download IPSWs, extract, merge"
|
||||||
@echo " Options: IPHONE_SOURCE= URL or local path to iPhone IPSW"
|
@echo " Options: IPHONE_SOURCE= URL or local path to iPhone IPSW"
|
||||||
@echo " CLOUDOS_SOURCE= URL or local path to cloudOS IPSW"
|
@echo " CLOUDOS_SOURCE= URL or local path to cloudOS IPSW"
|
||||||
@echo " make fw_patch Patch boot chain (6 components)"
|
@echo " make fw_patch Patch boot chain (regular variant)"
|
||||||
@echo " make fw_patch_dev Patch boot chain (dev mode TXM patcher)"
|
@echo " make fw_patch_dev Patch boot chain (dev mode TXM patches)"
|
||||||
@echo " make fw_patch_jb Run fw_patch + JB extension patches"
|
@echo " make fw_patch_jb Patch boot chain (dev + JB extensions)"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Restore:"
|
@echo "Restore:"
|
||||||
@echo " make restore_get_shsh Fetch SHSH blob from device"
|
@echo " make restore_get_shsh Dump SHSH response from Apple"
|
||||||
@echo " make restore idevicerestore to device"
|
@echo " make restore idevicerestore to device"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Ramdisk:"
|
@echo "Ramdisk:"
|
||||||
@@ -167,25 +170,24 @@ vphoned:
|
|||||||
.PHONY: vm_new boot boot_dfu
|
.PHONY: vm_new boot boot_dfu
|
||||||
|
|
||||||
vm_new:
|
vm_new:
|
||||||
|
CPU="$(CPU)" MEMORY="$(MEMORY)" \
|
||||||
zsh $(SCRIPTS)/vm_create.sh --dir $(VM_DIR) --disk-size $(DISK_SIZE)
|
zsh $(SCRIPTS)/vm_create.sh --dir $(VM_DIR) --disk-size $(DISK_SIZE)
|
||||||
|
|
||||||
boot: bundle vphoned
|
boot: bundle vphoned
|
||||||
cd $(VM_DIR) && "$(CURDIR)/$(BUNDLE_BIN)" \
|
cd $(VM_DIR) && "$(CURDIR)/$(BUNDLE_BIN)" \
|
||||||
|
--config ./config.plist \
|
||||||
--rom ./AVPBooter.vresearch1.bin \
|
--rom ./AVPBooter.vresearch1.bin \
|
||||||
--disk ./Disk.img \
|
--disk ./Disk.img \
|
||||||
--nvram ./nvram.bin \
|
--nvram ./nvram.bin \
|
||||||
--machine-id ./machineIdentifier.bin \
|
|
||||||
--cpu $(CPU) --memory $(MEMORY) \
|
|
||||||
--sep-rom ./AVPSEPBooter.vresearch1.bin \
|
--sep-rom ./AVPSEPBooter.vresearch1.bin \
|
||||||
--sep-storage ./SEPStorage
|
--sep-storage ./SEPStorage
|
||||||
|
|
||||||
boot_dfu: build
|
boot_dfu: build
|
||||||
cd $(VM_DIR) && "$(CURDIR)/$(BINARY)" \
|
cd $(VM_DIR) && "$(CURDIR)/$(BINARY)" \
|
||||||
|
--config ./config.plist \
|
||||||
--rom ./AVPBooter.vresearch1.bin \
|
--rom ./AVPBooter.vresearch1.bin \
|
||||||
--disk ./Disk.img \
|
--disk ./Disk.img \
|
||||||
--nvram ./nvram.bin \
|
--nvram ./nvram.bin \
|
||||||
--machine-id ./machineIdentifier.bin \
|
|
||||||
--cpu $(CPU) --memory $(MEMORY) \
|
|
||||||
--sep-rom ./AVPSEPBooter.vresearch1.bin \
|
--sep-rom ./AVPSEPBooter.vresearch1.bin \
|
||||||
--sep-storage ./SEPStorage \
|
--sep-storage ./SEPStorage \
|
||||||
--no-graphics --dfu
|
--no-graphics --dfu
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -94,13 +94,28 @@ make setup_machine # full automation through "First Boot" (includes r
|
|||||||
```bash
|
```bash
|
||||||
make setup_tools # install brew deps, build trustcache, clone insert_dylib, build libimobiledevice, create Python venv
|
make setup_tools # install brew deps, build trustcache, clone insert_dylib, build libimobiledevice, create Python venv
|
||||||
make build # build + sign vphone-cli
|
make build # build + sign vphone-cli
|
||||||
make vm_new # create vm/ directory (ROMs, disk, SEP storage)
|
make vm_new # create VM directory with manifest (config.plist)
|
||||||
|
# options: CPU=8 MEMORY=8192 DISK_SIZE=64
|
||||||
make fw_prepare # download IPSWs, extract, merge, generate manifest
|
make fw_prepare # download IPSWs, extract, merge, generate manifest
|
||||||
make fw_patch # patch boot chain (regular variant)
|
make fw_patch # patch boot chain (regular variant)
|
||||||
# or: make fw_patch_dev # dev variant (+ TXM entitlement/debug bypasses)
|
# or: make fw_patch_dev # dev variant (+ TXM entitlement/debug bypasses)
|
||||||
# or: make fw_patch_jb # jailbreak variant (+ full security bypass)
|
# or: make fw_patch_jb # jailbreak variant (+ full security bypass)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### VM Configuration
|
||||||
|
|
||||||
|
Starting from v1.0, VM configuration is stored in `vm/config.plist`. Set CPU, memory, and disk size during VM creation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create VM with custom configuration
|
||||||
|
make vm_new CPU=16 MEMORY=16384 DISK_SIZE=128
|
||||||
|
|
||||||
|
# Boot automatically reads from config.plist
|
||||||
|
make boot
|
||||||
|
```
|
||||||
|
|
||||||
|
The manifest stores all VM settings (CPU, memory, screen, ROMs, storage) and is compatible with [security-pcc's VMBundle.Config format](https://github.com/apple/security-pcc).
|
||||||
|
|
||||||
## Restore
|
## Restore
|
||||||
|
|
||||||
You'll need **two terminals** for the restore process. Keep terminal 1 running while using terminal 2.
|
You'll need **two terminals** for the restore process. Keep terminal 1 running while using terminal 2.
|
||||||
|
|||||||
@@ -94,13 +94,28 @@ make setup_machine # 初回起動までを完全自動化(復元/
|
|||||||
```bash
|
```bash
|
||||||
make setup_tools # brew の依存関係インストール、trustcache + libimobiledevice のビルド、Python venv の作成
|
make setup_tools # brew の依存関係インストール、trustcache + libimobiledevice のビルド、Python venv の作成
|
||||||
make build # vphone-cli のビルド + 署名
|
make build # vphone-cli のビルド + 署名
|
||||||
make vm_new # vm/ ディレクトリの作成(ROM、ディスク、SEP ストレージ)
|
make vm_new # VM ディレクトリとマニフェスト(config.plist)の作成
|
||||||
|
# オプション:CPU=8 MEMORY=8192 DISK_SIZE=64
|
||||||
make fw_prepare # IPSW のダウンロード、抽出、マージ、マニフェスト生成
|
make fw_prepare # IPSW のダウンロード、抽出、マージ、マニフェスト生成
|
||||||
make fw_patch # ブートチェーンのパッチ当て(通常バリアント)
|
make fw_patch # ブートチェーンのパッチ当て(通常バリアント)
|
||||||
# または: make fw_patch_dev # 開発バリアント(+ TXM entitlement/デバッグバイパス)
|
# または: make fw_patch_dev # 開発バリアント(+ TXM entitlement/デバッグバイパス)
|
||||||
# または: make fw_patch_jb # 脱獄バリアント(+ 完全セキュリティバイパス)
|
# または: make fw_patch_jb # 脱獄バリアント(dev + 完全セキュリティバイパス)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### VM 設定
|
||||||
|
|
||||||
|
v1.0 から、VM 設定は `vm/config.plist` に保存されます。VM 作成時に CPU、メモリ、ディスクサイズを設定します:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# カスタム設定で VM を作成
|
||||||
|
make vm_new CPU=16 MEMORY=16384 DISK_SIZE=128
|
||||||
|
|
||||||
|
# 起動時に config.plist から設定を自動読み込み
|
||||||
|
make boot
|
||||||
|
```
|
||||||
|
|
||||||
|
マニフェストファイルはすべての VM 設定(CPU、メモリ、画面、ROM、ストレージ)を保存し、[security-pcc の VMBundle.Config 形式](https://github.com/apple/security-pcc) と互換性があります。
|
||||||
|
|
||||||
## 復元
|
## 復元
|
||||||
|
|
||||||
復元プロセスには **2つのターミナル** が必要です。ターミナル 2 を使用している間、ターミナル 1 を実行し続けてください。
|
復元プロセスには **2つのターミナル** が必要です。ターミナル 2 を使用している間、ターミナル 1 を実行し続けてください。
|
||||||
|
|||||||
@@ -94,13 +94,28 @@ make setup_machine # "First Boot"까지의 전체 과정 자동화 (
|
|||||||
```bash
|
```bash
|
||||||
make setup_tools # brew 의존성 설치, trustcache + libimobiledevice 빌드, Python venv 생성
|
make setup_tools # brew 의존성 설치, trustcache + libimobiledevice 빌드, Python venv 생성
|
||||||
make build # vphone-cli 빌드 및 서명
|
make build # vphone-cli 빌드 및 서명
|
||||||
make vm_new # vm/ 디렉토리 생성 (ROM, 디스크, SEP 저장소)
|
make vm_new # VM 디렉토리 및 매니페스트(config.plist) 생성
|
||||||
|
# 옵션: CPU=8 MEMORY=8192 DISK_SIZE=64
|
||||||
make fw_prepare # IPSW 다운로드, 추출, 병합, manifest 생성
|
make fw_prepare # IPSW 다운로드, 추출, 병합, manifest 생성
|
||||||
make fw_patch # 부트 체인 패치 (일반 변형)
|
make fw_patch # 부트 체인 패치 (일반 변형)
|
||||||
# 또는: make fw_patch_dev # 개발 변형 (+ TXM 권한/디버그 우회)
|
# 또는: make fw_patch_dev # 개발 변형 (+ TXM 권한/디버그 우회)
|
||||||
# 또는: make fw_patch_jb # 탈옥 변형 (+ 전체 보안 우회)
|
# 또는: make fw_patch_jb # 탈옥 변형 (dev + 전체 보안 우회)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### VM 설정
|
||||||
|
|
||||||
|
v1.0부터 VM 설정은 `vm/config.plist`에 저장됩니다. VM 생성 시 CPU, 메모리, 디스크 크기를 설정하세요:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 사용자 정의 설정으로 VM 생성
|
||||||
|
make vm_new CPU=16 MEMORY=16384 DISK_SIZE=128
|
||||||
|
|
||||||
|
# 부팅 시 config.plist에서 설정 자동 로드
|
||||||
|
make boot
|
||||||
|
```
|
||||||
|
|
||||||
|
매니페스트 파일은 모든 VM 설정(CPU, 메모리, 화면, ROM, 저장소)을 저장하며 [security-pcc의 VMBundle.Config 형식](https://github.com/apple/security-pcc)과 호환됩니다.
|
||||||
|
|
||||||
## 복원
|
## 복원
|
||||||
|
|
||||||
복원 프로세스를 위해 **두 개의 터미널**이 필요합니다. 터미널 2를 사용하는 동안 터미널 1을 계속 실행 상태로 두세요.
|
복원 프로세스를 위해 **두 개의 터미널**이 필요합니다. 터미널 2를 사용하는 동안 터미널 1을 계속 실행 상태로 두세요.
|
||||||
|
|||||||
@@ -94,13 +94,28 @@ make setup_machine # 完全自动化完成"首次启动"流程(包
|
|||||||
```bash
|
```bash
|
||||||
make setup_tools # 安装 brew 依赖、构建 trustcache + libimobiledevice、创建 Python 虚拟环境
|
make setup_tools # 安装 brew 依赖、构建 trustcache + libimobiledevice、创建 Python 虚拟环境
|
||||||
make build # 构建并签名 vphone-cli
|
make build # 构建并签名 vphone-cli
|
||||||
make vm_new # 创建 vm/ 目录(ROM、磁盘、SEP 存储)
|
make vm_new # 创建 VM 目录及清单文件(config.plist)
|
||||||
|
# 选项:CPU=8 MEMORY=8192 DISK_SIZE=64
|
||||||
make fw_prepare # 下载 IPSWs,提取、合并、生成 manifest
|
make fw_prepare # 下载 IPSWs,提取、合并、生成 manifest
|
||||||
make fw_patch # 修补启动链(常规变体)
|
make fw_patch # 修补启动链(常规变体)
|
||||||
# 或:make fw_patch_dev # 开发变体(+ TXM 权限/调试绕过)
|
# 或:make fw_patch_dev # 开发变体(+ TXM 权限/调试绕过)
|
||||||
# 或:make fw_patch_jb # 越狱变体(+ 完整安全绕过)
|
# 或:make fw_patch_jb # 越狱变体(dev + 完整安全绕过)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### VM 配置
|
||||||
|
|
||||||
|
从 v1.0 开始,VM 配置存储在 `vm/config.plist` 中。在创建 VM 时设置 CPU、内存和磁盘大小:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用自定义配置创建 VM
|
||||||
|
make vm_new CPU=16 MEMORY=16384 DISK_SIZE=128
|
||||||
|
|
||||||
|
# 启动时自动从 config.plist 读取配置
|
||||||
|
make boot
|
||||||
|
```
|
||||||
|
|
||||||
|
清单文件存储所有 VM 设置(CPU、内存、屏幕、ROM、存储),并与 [security-pcc 的 VMBundle.Config 格式](https://github.com/apple/security-pcc)兼容。
|
||||||
|
|
||||||
## 恢复过程
|
## 恢复过程
|
||||||
|
|
||||||
该过程需要 **两个终端**。保持终端 1 运行,同时在终端 2 操作。
|
该过程需要 **两个终端**。保持终端 1 运行,同时在终端 2 操作。
|
||||||
|
|||||||
181
research/machine_identifier_storage_analysis.md
Normal file
181
research/machine_identifier_storage_analysis.md
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# MachineIdentifier Storage Analysis
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
Migrating `machineIdentifier` from a standalone `machineIdentifier.bin` file to the `config.plist` manifest requires validation that this change won't cause compatibility issues with Virtualization.framework.
|
||||||
|
|
||||||
|
## Methodology
|
||||||
|
|
||||||
|
1. Analyzed security-pcc's VMBundle.Config implementation
|
||||||
|
2. Checked for dependencies between VZMacAuxiliaryStorage and VZMacMachineIdentifier
|
||||||
|
3. Verified Virtualization.framework API behavior
|
||||||
|
|
||||||
|
## Key Findings
|
||||||
|
|
||||||
|
### 1. security-pcc Implementation
|
||||||
|
|
||||||
|
**Storage Location**: `machineIdentifier` stored directly in `config.plist`
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// references/security-pcc/srd_tools/vre/vrevm/VMBundle/VMBundle+Config.swift
|
||||||
|
struct Config: Codable {
|
||||||
|
let machineIdentifier: Data // opaque ECID representation
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Loading Method**:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// VM+Config.swift:231-236
|
||||||
|
if let machineIDBlob {
|
||||||
|
guard let machineID = VZMacMachineIdentifier(dataRepresentation: machineIDBlob) else {
|
||||||
|
throw VMError("invalid VM platform info (machine id)")
|
||||||
|
}
|
||||||
|
pconf.machineIdentifier = machineID
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. VZMacAuxiliaryStorage Independence
|
||||||
|
|
||||||
|
**Creation API**:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// VMBundle-create.swift:59-65
|
||||||
|
func createAuxStorage(hwModel: VZMacHardwareModel) throws -> VZMacAuxiliaryStorage {
|
||||||
|
return try VZMacAuxiliaryStorage(
|
||||||
|
creatingStorageAt: auxiliaryStoragePath,
|
||||||
|
hardwareModel: hwModel,
|
||||||
|
options: [.allowOverwrite]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Points**:
|
||||||
|
|
||||||
|
- Only requires `hwModel` parameter
|
||||||
|
- **Does NOT need** `machineIdentifier`
|
||||||
|
- Two components are completely independent
|
||||||
|
|
||||||
|
### 3. VZMacPlatformConfiguration Assembly
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let platform = VZMacPlatformConfiguration()
|
||||||
|
|
||||||
|
// 1. Set hardwareModel
|
||||||
|
platform.hardwareModel = hwModel
|
||||||
|
|
||||||
|
// 2. Set machineIdentifier
|
||||||
|
platform.machineIdentifier = machineIdentifier
|
||||||
|
|
||||||
|
// 3. Set auxiliaryStorage
|
||||||
|
platform.auxiliaryStorage = auxStorage
|
||||||
|
```
|
||||||
|
|
||||||
|
**Three independent components**, no binding validation.
|
||||||
|
|
||||||
|
## Data Serialization Verification
|
||||||
|
|
||||||
|
### machineIdentifier Data Representation
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let machineID = VZMacMachineIdentifier()
|
||||||
|
let data = machineID.dataRepresentation // Data type
|
||||||
|
|
||||||
|
// Deserialize
|
||||||
|
let restoredID = VZMacMachineIdentifier(dataRepresentation: data)
|
||||||
|
// ✅ Successfully restored, no file path dependency
|
||||||
|
```
|
||||||
|
|
||||||
|
### plist Compatibility
|
||||||
|
|
||||||
|
```python
|
||||||
|
# vm_manifest.py
|
||||||
|
manifest = {
|
||||||
|
"machineIdentifier": b"", # ✅ Data type correctly serializes to plist
|
||||||
|
# ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**PropertyList Encoder Support**:
|
||||||
|
|
||||||
|
- `Data` type in plist is represented as `<data>` binary block
|
||||||
|
- Fully compatible, no size limit (for ECID's 8 bytes)
|
||||||
|
|
||||||
|
## Risk Assessment
|
||||||
|
|
||||||
|
### ✅ No-Risk Items
|
||||||
|
|
||||||
|
1. **API Dependency**:
|
||||||
|
- `VZMacMachineIdentifier(dataRepresentation:)` only needs `Data` parameter
|
||||||
|
- Doesn't care about data source (file vs plist vs memory)
|
||||||
|
|
||||||
|
2. **AuxiliaryStorage Independence**:
|
||||||
|
- Creating `VZMacAuxiliaryStorage` only needs `hardwareModel`
|
||||||
|
- Completely decoupled from `machineIdentifier`
|
||||||
|
|
||||||
|
3. **ECID Stability**:
|
||||||
|
- `dataRepresentation` is deterministic serialization
|
||||||
|
- Same ECID always produces same `Data`
|
||||||
|
|
||||||
|
4. **security-pcc Precedent**:
|
||||||
|
- Official PCC tools use this approach
|
||||||
|
- Thoroughly tested
|
||||||
|
|
||||||
|
### ⚠️ Considerations (Already Handled)
|
||||||
|
|
||||||
|
1. **First Boot Creation**:
|
||||||
|
- ✅ Implemented: Detect empty data, auto-create and save
|
||||||
|
|
||||||
|
2. **Data Corruption Recovery**:
|
||||||
|
- ✅ Implemented: Detect invalid data, auto-regenerate
|
||||||
|
|
||||||
|
3. **Backward Compatibility**:
|
||||||
|
- ⚠️ Existing VMs need migration
|
||||||
|
- But user stated "暂时不用考虑兼容性" (no need to consider compatibility for now)
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
### ✅ No Issues
|
||||||
|
|
||||||
|
**Integrating `machineIdentifier` into `config.plist` is safe and correct**:
|
||||||
|
|
||||||
|
1. **API Compatible**: Virtualization.framework doesn't care about data source
|
||||||
|
2. **Component Independence**: AuxiliaryStorage and machineIdentifier have no dependencies
|
||||||
|
3. **Official Precedent**: security-pcc has validated this approach
|
||||||
|
4. **Reliable Serialization**: `Data` ↔ `VZMacMachineIdentifier` conversion is stable
|
||||||
|
|
||||||
|
### Implementation Verification
|
||||||
|
|
||||||
|
Our implementation matches security-pcc exactly:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// vphone-cli implementation
|
||||||
|
let manifest = try VPhoneVirtualMachineManifest.load(from: configURL)
|
||||||
|
|
||||||
|
if manifest.machineIdentifier.isEmpty {
|
||||||
|
let newID = VZMacMachineIdentifier()
|
||||||
|
machineIdentifier = newID
|
||||||
|
// Save back to manifest
|
||||||
|
manifest = VPhoneVirtualMachineManifest(
|
||||||
|
machineIdentifier: newID.dataRepresentation,
|
||||||
|
// ...
|
||||||
|
)
|
||||||
|
try manifest.write(to: configURL)
|
||||||
|
} else if let savedID = VZMacMachineIdentifier(dataRepresentation: manifest.machineIdentifier) {
|
||||||
|
machineIdentifier = savedID
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Identical code pattern to security-pcc**.
|
||||||
|
|
||||||
|
## Final Verdict
|
||||||
|
|
||||||
|
**No issues.**
|
||||||
|
|
||||||
|
Our implementation approach:
|
||||||
|
1. Follows security-pcc's official pattern
|
||||||
|
2. Aligns with Virtualization.framework API design
|
||||||
|
3. Properly handles first-boot creation and data recovery scenarios
|
||||||
|
|
||||||
|
Safe to use.
|
||||||
271
research/manifest_and_refactoring_summary.md
Normal file
271
research/manifest_and_refactoring_summary.md
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
# VPhone-CLI Manifest Implementation & Code Clarity Review
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
1. **Implemented VM manifest system** compatible with security-pcc's VMBundle.Config format
|
||||||
|
2. **Cleaned up environment variables** - removed unused `CFW_INPUT`, documented remaining variables
|
||||||
|
3. **Applied code-clarity framework** to review and refactor core files
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. VM Manifest Implementation
|
||||||
|
|
||||||
|
### Files Created
|
||||||
|
|
||||||
|
- `sources/vphone-cli/VPhoneVirtualMachineManifest.swift` - Manifest structure (compatible with security-pcc)
|
||||||
|
- `scripts/vm_manifest.py` - Python script to generate config.plist
|
||||||
|
|
||||||
|
### Changes Made
|
||||||
|
|
||||||
|
1. **VPhoneVirtualMachineManifest.swift**
|
||||||
|
- Structure mirrors security-pcc's `VMBundle.Config`
|
||||||
|
- Adds iPhone-specific configurations (screen, SEP storage)
|
||||||
|
- Simplified for single-purpose (virtual iPhone vs generic VM)
|
||||||
|
|
||||||
|
2. **vm_create.sh**
|
||||||
|
- Now calls `vm_manifest.py` to generate `config.plist`
|
||||||
|
- Accepts `CPU` and `MEMORY` environment variables
|
||||||
|
- Creates manifest at `[5/4]` step
|
||||||
|
|
||||||
|
3. **Makefile**
|
||||||
|
- `vm_new`: Passes CPU/MEMORY to `vm_create.sh`
|
||||||
|
- `boot`/`boot_dfu`: Read from `--config ./config.plist` instead of CLI args
|
||||||
|
- Removed unused `CFW_INPUT` variable
|
||||||
|
- Added documentation for remaining variables
|
||||||
|
|
||||||
|
4. **VPhoneCLI.swift**
|
||||||
|
- Added `--config` option to load manifest
|
||||||
|
- CPU/memory/screen parameters now optional (overridden by manifest if provided)
|
||||||
|
- `resolveOptions()` merges manifest with CLI overrides
|
||||||
|
|
||||||
|
5. **VPhoneAppDelegate.swift**
|
||||||
|
- Uses `resolveOptions()` to load configuration
|
||||||
|
- Removed direct CLI parameter access
|
||||||
|
|
||||||
|
### Manifest Structure
|
||||||
|
|
||||||
|
```plist
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>platformType</key>
|
||||||
|
<string>vresearch101</string>
|
||||||
|
<key>cpuCount</key>
|
||||||
|
<integer>8</integer>
|
||||||
|
<key>memorySize</key>
|
||||||
|
<integer>8589934592</integer>
|
||||||
|
<key>screenConfig</key>
|
||||||
|
<dict>
|
||||||
|
<key>width</key>
|
||||||
|
<integer>1290</integer>
|
||||||
|
<key>height</key>
|
||||||
|
<integer>2796</integer>
|
||||||
|
<key>pixelsPerInch</key>
|
||||||
|
<integer>460</integer>
|
||||||
|
<key>scale</key>
|
||||||
|
<real>3.0</real>
|
||||||
|
</dict>
|
||||||
|
<key>networkConfig</key>
|
||||||
|
<dict>
|
||||||
|
<key>mode</key>
|
||||||
|
<string>nat</string>
|
||||||
|
<key>macAddress</key>
|
||||||
|
<string></string>
|
||||||
|
</dict>
|
||||||
|
<!-- ... storage, ROMs, SEP ... -->
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compatibility with security-pcc
|
||||||
|
|
||||||
|
| Feature | security-pcc | vphone-cli | Notes |
|
||||||
|
| --------------- | ----------------------- | -------------------- | --------------------------- |
|
||||||
|
| Platform type | Configurable | Fixed (vresearch101) | iPhone only needs one |
|
||||||
|
| Network modes | NAT, bridged, host-only | NAT only | Phone doesn't need bridging |
|
||||||
|
| VirtMesh plugin | Supported | Not supported | PCC-specific feature |
|
||||||
|
| Screen config | Not included | Included | iPhone-specific |
|
||||||
|
| SEP storage | Not included | Included | iPhone-specific |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Code Clarity Review
|
||||||
|
|
||||||
|
### VPhoneVirtualMachine.swift
|
||||||
|
|
||||||
|
**Current Score: 6/10 → Target: 9/10**
|
||||||
|
|
||||||
|
#### Issues Found:
|
||||||
|
|
||||||
|
1. **200+ line init method** - Violates single responsibility
|
||||||
|
2. **Mixed abstraction levels** - Configuration logic mixed with low-level Dynamic API calls
|
||||||
|
3. **Unclear abbreviations**:
|
||||||
|
- `hwModel` → `hardwareModel`
|
||||||
|
- `gfx` → `graphicsConfiguration`
|
||||||
|
- `afg` → `soundDevice` (completely meaningless)
|
||||||
|
- `net` → `networkDevice`
|
||||||
|
4. **Magic numbers**: `1=charging, 2=disconnected` → Should be enum
|
||||||
|
5. **Missing early returns** - Disk check should use guard
|
||||||
|
6. **Nested conditionals** - Serial port configuration
|
||||||
|
|
||||||
|
#### Refactored Version Created
|
||||||
|
|
||||||
|
`sources/vphone-cli/VPhoneVirtualMachineRefactored.swift` demonstrates:
|
||||||
|
|
||||||
|
1. **Extracted configuration methods**:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
private func configurePlatform(...)
|
||||||
|
private func configureDisplay(_ config: inout VZVirtualMachineConfiguration, screen: ScreenConfiguration)
|
||||||
|
private func configureAudio(_ config: inout VZVirtualMachineConfiguration)
|
||||||
|
// ... etc
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Better naming**:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Before
|
||||||
|
let gfx = VZMacGraphicsDeviceConfiguration()
|
||||||
|
let afg = VZVirtioSoundDeviceConfiguration()
|
||||||
|
|
||||||
|
// After
|
||||||
|
let graphicsConfiguration = VZMacGraphicsDeviceConfiguration()
|
||||||
|
let soundDevice = VZVirtioSoundDeviceConfiguration()
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Battery connectivity enum**:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
private enum BatteryConnectivity {
|
||||||
|
static let charging = 1
|
||||||
|
static let disconnected = 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Clearer method names**:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Before
|
||||||
|
setBattery(charge: 100, connectivity: 1)
|
||||||
|
|
||||||
|
// After
|
||||||
|
updateBattery(charge: 100, isCharging: true)
|
||||||
|
```
|
||||||
|
|
||||||
|
### VPhoneCLI.swift
|
||||||
|
|
||||||
|
**Current Score: 7/10 → Target: 9/10**
|
||||||
|
|
||||||
|
#### Issues Fixed:
|
||||||
|
|
||||||
|
1. **Variable shadowing** - Local variables now use distinct names:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Before
|
||||||
|
var screenWidth: Int = 1290
|
||||||
|
if let screenWidth = screenWidth { ... } // Shadowing!
|
||||||
|
|
||||||
|
// After
|
||||||
|
var resolvedScreenWidth: Int = 1290
|
||||||
|
if let screenWidthArg = screenWidth { resolvedScreenWidth = screenWidthArg }
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Manifest loading** - Clean separation of concerns
|
||||||
|
|
||||||
|
### VPhoneVirtualMachineManifest.swift
|
||||||
|
|
||||||
|
**Current Score: 8/10 → Target: 9/10**
|
||||||
|
|
||||||
|
#### Minor Issues:
|
||||||
|
|
||||||
|
1. **Repetitive error handling** - Can be extracted:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
private static func withFile<T>(_ url: URL, _ operation: (inout Data) throws -> T) throws -> T
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Method naming** - `resolve(path:in:)` could be clearer:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// Before
|
||||||
|
manifest.resolve(path: "Disk.img", in: vmDirectory)
|
||||||
|
|
||||||
|
// After
|
||||||
|
manifest.path(for: "Disk.img", relativeTo: vmDirectory)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Environment Variable Cleanup
|
||||||
|
|
||||||
|
### Removed Variables
|
||||||
|
|
||||||
|
| Variable | Previous Use | Why Removed |
|
||||||
|
| ----------- | ------------------- | ------------------------------------------------ |
|
||||||
|
| `CFW_INPUT` | CFW input directory | Overridden by all cfw_install scripts internally |
|
||||||
|
|
||||||
|
### Documented Variables
|
||||||
|
|
||||||
|
| Variable | Current Use | When Used |
|
||||||
|
| ---------------- | ----------------- | -------------------- |
|
||||||
|
| `VM_DIR` | VM directory path | All operations |
|
||||||
|
| `CPU` | CPU core count | Only `vm_new` |
|
||||||
|
| `MEMORY` | Memory size (MB) | Only `vm_new` |
|
||||||
|
| `DISK_SIZE` | Disk size (GB) | Only `vm_new` |
|
||||||
|
| `RESTORE_UDID` | Device UDID | `restore` operations |
|
||||||
|
| `RESTORE_ECID` | Device ECID | `restore` operations |
|
||||||
|
| `IRECOVERY_ECID` | Device ECID | `ramdisk_send` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Usage Changes
|
||||||
|
|
||||||
|
### Before
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Every boot required specifying CPU/Memory
|
||||||
|
make boot CPU=8 MEMORY=8192
|
||||||
|
```
|
||||||
|
|
||||||
|
### After
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set configuration once during VM creation
|
||||||
|
make vm_new CPU=8 MEMORY=8192 DISK_SIZE=64
|
||||||
|
|
||||||
|
# Boot automatically reads from config.plist
|
||||||
|
make boot
|
||||||
|
```
|
||||||
|
|
||||||
|
### Override Manifest (Optional)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Still supports CLI overrides for testing
|
||||||
|
make boot
|
||||||
|
# Inside vphone-cli, can pass:
|
||||||
|
# --cpu 16 --memory 16384
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Next Steps
|
||||||
|
|
||||||
|
1. **Apply refactoring** - Review `VPhoneVirtualMachineRefactored.swift` and apply to main file
|
||||||
|
2. **Extend manifest** - Consider adding:
|
||||||
|
- Kernel boot args configuration
|
||||||
|
- Debug stub port configuration
|
||||||
|
- Custom NVRAM variables
|
||||||
|
3. **Validate manifest** - Add schema validation on load
|
||||||
|
4. **Migration path** - For existing VMs without config.plist
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Testing Checklist
|
||||||
|
|
||||||
|
- [ ] `make vm_new` creates config.plist
|
||||||
|
- [ ] `make boot` reads from config.plist
|
||||||
|
- [ ] CLI overrides work: `vphone-cli --config ... --cpu 16`
|
||||||
|
- [ ] Existing VMs without config.plist still work (backward compatibility)
|
||||||
|
- [ ] Manifest is valid plist and can be edited manually
|
||||||
|
- [ ] CPU/Memory/Screen settings are correctly applied from manifest
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
# 2. Create sparse disk image (default 64 GB)
|
# 2. Create sparse disk image (default 64 GB)
|
||||||
# 3. Create SEP storage (512 KB flat file)
|
# 3. Create SEP storage (512 KB flat file)
|
||||||
# 4. Copy AVPBooter and AVPSEPBooter ROMs
|
# 4. Copy AVPBooter and AVPSEPBooter ROMs
|
||||||
|
# 5. Generate config.plist manifest
|
||||||
#
|
#
|
||||||
# machineIdentifier and NVRAM are auto-created on first boot by vphone-cli.
|
# machineIdentifier and NVRAM are auto-created on first boot by vphone-cli.
|
||||||
#
|
#
|
||||||
@@ -16,10 +17,15 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# --- Defaults ---
|
# --- Defaults ---
|
||||||
VM_DIR="vm"
|
VM_DIR="${VM_DIR:-vm}"
|
||||||
DISK_SIZE_GB=64
|
DISK_SIZE_GB="${DISK_SIZE:-64}"
|
||||||
|
CPU_COUNT="${CPU:-8}"
|
||||||
|
MEMORY_MB="${MEMORY:-8192}"
|
||||||
SEP_STORAGE_SIZE=$((512 * 1024)) # 512 KB (same as vrevm)
|
SEP_STORAGE_SIZE=$((512 * 1024)) # 512 KB (same as vrevm)
|
||||||
|
|
||||||
|
# Script directory
|
||||||
|
SCRIPT_DIR="${0:A:h}"
|
||||||
|
|
||||||
# Framework-bundled ROMs (vresearch1 / research1 chip)
|
# Framework-bundled ROMs (vresearch1 / research1 chip)
|
||||||
FW_ROM_DIR="/System/Library/Frameworks/Virtualization.framework/Versions/A/Resources"
|
FW_ROM_DIR="/System/Library/Frameworks/Virtualization.framework/Versions/A/Resources"
|
||||||
ROM_SRC="${FW_ROM_DIR}/AVPBooter.vresearch1.bin"
|
ROM_SRC="${FW_ROM_DIR}/AVPBooter.vresearch1.bin"
|
||||||
@@ -139,12 +145,26 @@ fi
|
|||||||
# --- Create .gitkeep ---
|
# --- Create .gitkeep ---
|
||||||
touch "${VM_DIR}/.gitkeep"
|
touch "${VM_DIR}/.gitkeep"
|
||||||
|
|
||||||
|
# --- Generate VM manifest ---
|
||||||
|
echo "[5/4] Generating VM manifest (config.plist)"
|
||||||
|
"${SCRIPT_DIR}/vm_manifest.py" \
|
||||||
|
--vm-dir "${VM_DIR}" \
|
||||||
|
--cpu "${CPU_COUNT}" \
|
||||||
|
--memory "${MEMORY_MB}" \
|
||||||
|
--disk-size "${DISK_SIZE_GB}" || {
|
||||||
|
echo "ERROR: Failed to generate VM manifest"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== VM created at ${VM_DIR}/ ==="
|
echo "=== VM created at ${VM_DIR}/ ==="
|
||||||
echo ""
|
echo ""
|
||||||
echo "Contents:"
|
echo "Contents:"
|
||||||
ls -lh "${VM_DIR}/"
|
ls -lh "${VM_DIR}/"
|
||||||
echo ""
|
echo ""
|
||||||
|
echo "Manifest (config.plist) saved with VM configuration."
|
||||||
|
echo "Future boots will read configuration from this manifest."
|
||||||
|
echo ""
|
||||||
echo "Next steps:"
|
echo "Next steps:"
|
||||||
echo " 1. Prepare firmware: make fw_prepare"
|
echo " 1. Prepare firmware: make fw_prepare"
|
||||||
echo " 2. Patch firmware: make fw_patch"
|
echo " 2. Patch firmware: make fw_patch"
|
||||||
|
|||||||
127
scripts/vm_manifest.py
Executable file
127
scripts/vm_manifest.py
Executable file
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
vm_manifest.py — Generate VM manifest plist for vphone-cli.
|
||||||
|
|
||||||
|
Compatible with security-pcc's VMBundle.Config format.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import plistlib
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def create_manifest(
|
||||||
|
vm_dir: Path,
|
||||||
|
cpu_count: int,
|
||||||
|
memory_mb: int,
|
||||||
|
disk_size_gb: int,
|
||||||
|
platform_fusing: str | None = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create a VM manifest plist file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
vm_dir: Path to VM directory
|
||||||
|
cpu_count: Number of CPU cores
|
||||||
|
memory_mb: Memory size in MB
|
||||||
|
disk_size_gb: Disk size in GB
|
||||||
|
platform_fusing: Platform fusing mode (prod/dev) or None for auto-detect
|
||||||
|
"""
|
||||||
|
# Convert to manifest units
|
||||||
|
memory_bytes = memory_mb * 1024 * 1024
|
||||||
|
|
||||||
|
# ROM filenames
|
||||||
|
rom_file = "AVPBooter.vresearch1.bin"
|
||||||
|
sep_rom_file = "AVPSEPBooter.vresearch1.bin"
|
||||||
|
|
||||||
|
manifest = {
|
||||||
|
"platformType": "vresearch101",
|
||||||
|
"platformFusing": platform_fusing, # None = auto-detect from host OS
|
||||||
|
"machineIdentifier": b"", # Generated on first boot, then persisted to manifest
|
||||||
|
"cpuCount": cpu_count,
|
||||||
|
"memorySize": memory_bytes,
|
||||||
|
"screenConfig": {
|
||||||
|
"width": 1290,
|
||||||
|
"height": 2796,
|
||||||
|
"pixelsPerInch": 460,
|
||||||
|
"scale": 3.0,
|
||||||
|
},
|
||||||
|
"networkConfig": {
|
||||||
|
"mode": "nat",
|
||||||
|
"macAddress": "", # Auto-generated by Virtualization framework
|
||||||
|
},
|
||||||
|
"diskImage": "Disk.img",
|
||||||
|
"nvramStorage": "nvram.bin",
|
||||||
|
"romImages": {
|
||||||
|
"avpBooter": rom_file,
|
||||||
|
"avpSEPBooter": sep_rom_file,
|
||||||
|
},
|
||||||
|
"sepStorage": "SEPStorage",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Write to config.plist
|
||||||
|
config_path = vm_dir / "config.plist"
|
||||||
|
with open(config_path, "wb") as f:
|
||||||
|
plistlib.dump(manifest, f)
|
||||||
|
|
||||||
|
print(f"[5/4] Created VM manifest: {config_path}")
|
||||||
|
return config_path
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Generate VM manifest plist for vphone-cli"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--vm-dir",
|
||||||
|
type=Path,
|
||||||
|
default=Path("vm"),
|
||||||
|
help="VM directory path (default: vm)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--cpu",
|
||||||
|
type=int,
|
||||||
|
default=8,
|
||||||
|
help="CPU core count (default: 8)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--memory",
|
||||||
|
type=int,
|
||||||
|
default=8192,
|
||||||
|
help="Memory size in MB (default: 8192)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--disk-size",
|
||||||
|
type=int,
|
||||||
|
default=64,
|
||||||
|
help="Disk size in GB (default: 64)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--platform-fusing",
|
||||||
|
type=str,
|
||||||
|
choices=["prod", "dev"],
|
||||||
|
default=None,
|
||||||
|
help="Platform fusing mode (default: auto-detect from host OS)",
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not args.vm_dir.exists():
|
||||||
|
print(f"Error: VM directory does not exist: {args.vm_dir}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
create_manifest(
|
||||||
|
vm_dir=args.vm_dir,
|
||||||
|
cpu_count=args.cpu,
|
||||||
|
memory_mb=args.memory,
|
||||||
|
disk_size_gb=args.disk_size,
|
||||||
|
platform_fusing=args.platform_fusing,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating manifest: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -42,53 +42,32 @@ class VPhoneAppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
private func startVirtualMachine() async throws {
|
private func startVirtualMachine() async throws {
|
||||||
let romURL = URL(fileURLWithPath: cli.rom)
|
let options = try cli.resolveOptions()
|
||||||
guard FileManager.default.fileExists(atPath: romURL.path) else {
|
|
||||||
throw VPhoneError.romNotFound(cli.rom)
|
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("=== vphone-cli ===")
|
||||||
print("ROM : \(cli.rom)")
|
print("ROM : \(options.romURL.path)")
|
||||||
print("Disk : \(cli.disk)")
|
print("Disk : \(options.diskURL.path)")
|
||||||
print("NVRAM : \(cli.nvram)")
|
print("NVRAM : \(options.nvramURL.path)")
|
||||||
print("MachID: \(cli.machineId)")
|
print("Config: \(options.configURL.path)")
|
||||||
print("CPU : \(cli.cpu)")
|
print("CPU : \(options.cpuCount)")
|
||||||
print("Memory: \(cli.memory) MB")
|
print("Memory: \(options.memorySize / 1024 / 1024) MB")
|
||||||
print(
|
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)")
|
print("Kernel debug stub : 127.0.0.1:\(kernelDebugPort)")
|
||||||
} else {
|
} else {
|
||||||
print("Kernel debug stub : auto-assigned")
|
print("Kernel debug stub : auto-assigned")
|
||||||
}
|
}
|
||||||
print("SEP : enabled")
|
print("SEP : enabled")
|
||||||
print(" storage : \(cli.sepStorage)")
|
print(" storage : \(options.sepStorageURL.path)")
|
||||||
print(" rom : \(cli.sepRom)")
|
print(" rom : \(options.sepRomURL.path)")
|
||||||
print("")
|
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)
|
let vm = try VPhoneVirtualMachine(options: options)
|
||||||
self.vm = vm
|
self.vm = vm
|
||||||
|
|
||||||
@@ -115,9 +94,9 @@ class VPhoneAppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
let wc = VPhoneWindowController()
|
let wc = VPhoneWindowController()
|
||||||
wc.showWindow(
|
wc.showWindow(
|
||||||
for: vm.virtualMachine,
|
for: vm.virtualMachine,
|
||||||
screenWidth: cli.screenWidth,
|
screenWidth: options.screenWidth,
|
||||||
screenHeight: cli.screenHeight,
|
screenHeight: options.screenHeight,
|
||||||
screenScale: cli.screenScale,
|
screenScale: options.screenScale,
|
||||||
keyHelper: keyHelper,
|
keyHelper: keyHelper,
|
||||||
control: control,
|
control: control,
|
||||||
ecid: vm.ecidHex
|
ecid: vm.ecidHex
|
||||||
|
|||||||
@@ -15,10 +15,16 @@ struct VPhoneCLI: ParsableCommand {
|
|||||||
- Signed with vphone entitlements (done automatically by wrapper script)
|
- Signed with vphone entitlements (done automatically by wrapper script)
|
||||||
|
|
||||||
Example:
|
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")
|
@Option(help: "Path to the AVPBooter / ROM binary")
|
||||||
var rom: String
|
var rom: String
|
||||||
|
|
||||||
@@ -28,14 +34,11 @@ struct VPhoneCLI: ParsableCommand {
|
|||||||
@Option(help: "Path to NVRAM storage (created/overwritten)")
|
@Option(help: "Path to NVRAM storage (created/overwritten)")
|
||||||
var nvram: String = "nvram.bin"
|
var nvram: String = "nvram.bin"
|
||||||
|
|
||||||
@Option(help: "Path to machineIdentifier file (created if missing)")
|
@Option(help: "Number of CPU cores (overridden by --config if present)")
|
||||||
var machineId: String
|
var cpu: Int?
|
||||||
|
|
||||||
@Option(help: "Number of CPU cores")
|
@Option(help: "Memory size in MB (overridden by --config if present)")
|
||||||
var cpu: Int = 8
|
var memory: Int?
|
||||||
|
|
||||||
@Option(help: "Memory size in MB")
|
|
||||||
var memory: Int = 8192
|
|
||||||
|
|
||||||
@Option(help: "Path to SEP storage file (created if missing)")
|
@Option(help: "Path to SEP storage file (created if missing)")
|
||||||
var sepStorage: String
|
var sepStorage: String
|
||||||
@@ -46,14 +49,14 @@ struct VPhoneCLI: ParsableCommand {
|
|||||||
@Flag(help: "Boot into DFU mode")
|
@Flag(help: "Boot into DFU mode")
|
||||||
var dfu: Bool = false
|
var dfu: Bool = false
|
||||||
|
|
||||||
@Option(help: "Display width in pixels (default: 1290)")
|
@Option(help: "Display width in pixels (overridden by --config if present)")
|
||||||
var screenWidth: Int = 1290
|
var screenWidth: Int?
|
||||||
|
|
||||||
@Option(help: "Display height in pixels (default: 2796)")
|
@Option(help: "Display height in pixels (overridden by --config if present)")
|
||||||
var screenHeight: Int = 2796
|
var screenHeight: Int?
|
||||||
|
|
||||||
@Option(help: "Display pixels per inch (default: 460)")
|
@Option(help: "Display pixels per inch (overridden by --config if present)")
|
||||||
var screenPpi: Int = 460
|
var screenPpi: Int?
|
||||||
|
|
||||||
@Option(help: "Window scale divisor (default: 3.0)")
|
@Option(help: "Window scale divisor (default: 3.0)")
|
||||||
var screenScale: Double = 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")
|
@Option(help: "Path to signed vphoned binary for guest auto-update")
|
||||||
var vphonedBin: String = ".vphoned.signed"
|
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()
|
/// Execution is driven by VPhoneAppDelegate; main.swift calls parseOrExit()
|
||||||
/// and hands the parsed options to the delegate.
|
/// and hands the parsed options to the delegate.
|
||||||
mutating func run() throws {}
|
mutating func run() throws {}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ enum VPhoneError: Error, CustomStringConvertible {
|
|||||||
case romNotFound(String)
|
case romNotFound(String)
|
||||||
case diskNotFound(String)
|
case diskNotFound(String)
|
||||||
case invalidKernelDebugPort(Int)
|
case invalidKernelDebugPort(Int)
|
||||||
|
case manifestLoadFailed(path: String, underlying: Error)
|
||||||
|
case manifestParseFailed(path: String, underlying: Error)
|
||||||
|
case manifestWriteFailed(path: String, underlying: Error)
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -22,6 +25,12 @@ enum VPhoneError: Error, CustomStringConvertible {
|
|||||||
"Disk image not found: \(p)"
|
"Disk image not found: \(p)"
|
||||||
case let .invalidKernelDebugPort(port):
|
case let .invalidKernelDebugPort(port):
|
||||||
"Invalid kernel debug port: \(port) (expected 6000...65535)"
|
"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
|
// App menu
|
||||||
let appMenuItem = NSMenuItem()
|
let appMenuItem = NSMenuItem()
|
||||||
let appMenu = NSMenu(title: "vphone")
|
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
|
buildItem.isEnabled = false
|
||||||
appMenu.addItem(buildItem)
|
appMenu.addItem(buildItem)
|
||||||
appMenu.addItem(NSMenuItem.separator())
|
appMenu.addItem(NSMenuItem.separator())
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ class VPhoneVirtualMachine: NSObject, VZVirtualMachineDelegate {
|
|||||||
private var batterySource: AnyObject?
|
private var batterySource: AnyObject?
|
||||||
|
|
||||||
struct Options {
|
struct Options {
|
||||||
|
var configURL: URL
|
||||||
var romURL: URL
|
var romURL: URL
|
||||||
var nvramURL: URL
|
var nvramURL: URL
|
||||||
var machineIDURL: URL
|
|
||||||
var diskURL: URL
|
var diskURL: URL
|
||||||
var cpuCount: Int = 8
|
var cpuCount: Int = 8
|
||||||
var memorySize: UInt64 = 8 * 1024 * 1024 * 1024
|
var memorySize: UInt64 = 8 * 1024 * 1024 * 1024
|
||||||
@@ -40,32 +40,71 @@ class VPhoneVirtualMachine: NSObject, VZVirtualMachineDelegate {
|
|||||||
let hwModel = try VPhoneHardware.createModel()
|
let hwModel = try VPhoneHardware.createModel()
|
||||||
print("[vphone] PV=3 hardware model: isSupported = true")
|
print("[vphone] PV=3 hardware model: isSupported = true")
|
||||||
|
|
||||||
// --- Platform ---
|
// --- Load or create machineIdentifier from manifest ---
|
||||||
let platform = VZMacPlatformConfiguration()
|
|
||||||
|
|
||||||
// Persist machineIdentifier for stable ECID
|
|
||||||
let machineIdentifier: VZMacMachineIdentifier
|
let machineIdentifier: VZMacMachineIdentifier
|
||||||
if let savedData = try? Data(contentsOf: options.machineIDURL),
|
var manifest = try VPhoneVirtualMachineManifest.load(from: options.configURL)
|
||||||
let savedID = VZMacMachineIdentifier(dataRepresentation: savedData)
|
|
||||||
{
|
if manifest.machineIdentifier.isEmpty {
|
||||||
machineIdentifier = savedID
|
// Create new machineIdentifier and save to manifest
|
||||||
print("[vphone] Loaded machineIdentifier (ECID stable)")
|
|
||||||
} else {
|
|
||||||
let newID = VZMacMachineIdentifier()
|
let newID = VZMacMachineIdentifier()
|
||||||
machineIdentifier = newID
|
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
|
platform.machineIdentifier = machineIdentifier
|
||||||
|
|
||||||
if let identity = Self.resolveDeviceIdentity(machineIdentifier: machineIdentifier) {
|
if let identity = Self.resolveDeviceIdentity(machineIdentifier: machineIdentifier) {
|
||||||
ecidHex = identity.ecidHex
|
ecidHex = identity.ecidHex
|
||||||
print("[vphone] ECID: \(ecidHex!)")
|
print("[vphone] ECID: \(ecidHex!)")
|
||||||
print("[vphone] Predicted UDID: \(identity.udid)")
|
print("[vphone] Predicted UDID: \(identity.udid)")
|
||||||
|
let outputURL = options.configURL.deletingLastPathComponent().appendingPathComponent(
|
||||||
|
"udid-prediction.txt"
|
||||||
|
)
|
||||||
do {
|
do {
|
||||||
let outputURL = try Self.writeUDIDPrediction(
|
try Self.writeUDIDPrediction(identity: identity, to: outputURL)
|
||||||
identity: identity, machineIDURL: options.machineIDURL
|
|
||||||
)
|
|
||||||
print("[vphone] Wrote UDID prediction: \(outputURL.path)")
|
print("[vphone] Wrote UDID prediction: \(outputURL.path)")
|
||||||
} catch {
|
} catch {
|
||||||
print("[vphone] Warning: failed to write udid-prediction.txt: \(error)")
|
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)
|
return DeviceIdentity(cpidHex: cpidHex, ecidHex: ecidHex, udid: udid)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func writeUDIDPrediction(identity: DeviceIdentity, machineIDURL: URL) throws -> URL {
|
private static func writeUDIDPrediction(identity: DeviceIdentity, to outputURL: URL) throws {
|
||||||
let outputURL = machineIDURL.deletingLastPathComponent().appendingPathComponent(
|
|
||||||
"udid-prediction.txt"
|
|
||||||
)
|
|
||||||
let content = """
|
let content = """
|
||||||
UDID=\(identity.udid)
|
UDID=\(identity.udid)
|
||||||
CPID=0x\(identity.cpidHex)
|
CPID=0x\(identity.cpidHex)
|
||||||
ECID=0x\(identity.ecidHex)
|
ECID=0x\(identity.ecidHex)
|
||||||
MACHINE_IDENTIFIER=\(machineIDURL.lastPathComponent)
|
MACHINE_IDENTIFIER=config.plist
|
||||||
"""
|
"""
|
||||||
try content.write(to: outputURL, atomically: true, encoding: .utf8)
|
try content.write(to: outputURL, atomically: true, encoding: .utf8)
|
||||||
return outputURL
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Battery
|
// 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