mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-04-05 04:59:05 +08:00
Isolate multi-VM setup with deterministic device targeting
This commit is contained in:
25
Makefile
25
Makefile
@@ -8,6 +8,10 @@ CPU ?= 8
|
||||
MEMORY ?= 8192
|
||||
DISK_SIZE ?= 64
|
||||
CFW_INPUT ?= cfw_input
|
||||
RESTORE_UDID ?=
|
||||
RESTORE_ECID ?=
|
||||
IRECOVERY_ECID ?=
|
||||
SSH_PORT ?= 2222
|
||||
|
||||
# ─── Build info ──────────────────────────────────────────────────
|
||||
GIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
@@ -77,7 +81,7 @@ help:
|
||||
@echo " make cfw_install_dev Install CFW mods via SSH (dev mode)"
|
||||
@echo " make cfw_install_jb Install CFW + JB extensions (jetsam/procursus/basebin)"
|
||||
@echo ""
|
||||
@echo "Variables: VM_DIR=$(VM_DIR) CPU=$(CPU) MEMORY=$(MEMORY) DISK_SIZE=$(DISK_SIZE)"
|
||||
@echo "Variables: VM_DIR=$(VM_DIR) CPU=$(CPU) MEMORY=$(MEMORY) DISK_SIZE=$(DISK_SIZE) SSH_PORT=$(SSH_PORT)"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Setup
|
||||
@@ -208,10 +212,16 @@ fw_patch_jb:
|
||||
.PHONY: restore_get_shsh restore
|
||||
|
||||
restore_get_shsh:
|
||||
cd $(VM_DIR) && "$(CURDIR)/$(IDEVICERESTORE)" -e -y ./iPhone*_Restore -t
|
||||
cd $(VM_DIR) && "$(CURDIR)/$(IDEVICERESTORE)" \
|
||||
$(if $(RESTORE_UDID),-u $(RESTORE_UDID),) \
|
||||
$(if $(RESTORE_ECID),-i $(RESTORE_ECID),) \
|
||||
-e -y ./iPhone*_Restore -t
|
||||
|
||||
restore:
|
||||
cd $(VM_DIR) && "$(CURDIR)/$(IDEVICERESTORE)" -e -y ./iPhone*_Restore
|
||||
cd $(VM_DIR) && "$(CURDIR)/$(IDEVICERESTORE)" \
|
||||
$(if $(RESTORE_UDID),-u $(RESTORE_UDID),) \
|
||||
$(if $(RESTORE_ECID),-i $(RESTORE_ECID),) \
|
||||
-e -y ./iPhone*_Restore
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Ramdisk
|
||||
@@ -223,7 +233,8 @@ ramdisk_build:
|
||||
cd $(VM_DIR) && $(PYTHON) "$(CURDIR)/$(SCRIPTS)/ramdisk_build.py" .
|
||||
|
||||
ramdisk_send:
|
||||
cd $(VM_DIR) && IRECOVERY="$(CURDIR)/$(IRECOVERY)" zsh "$(CURDIR)/$(SCRIPTS)/ramdisk_send.sh"
|
||||
cd $(VM_DIR) && IRECOVERY="$(CURDIR)/$(IRECOVERY)" IRECOVERY_ECID="$(IRECOVERY_ECID)" \
|
||||
zsh "$(CURDIR)/$(SCRIPTS)/ramdisk_send.sh"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# CFW
|
||||
@@ -232,10 +243,10 @@ ramdisk_send:
|
||||
.PHONY: cfw_install cfw_install_dev cfw_install_jb
|
||||
|
||||
cfw_install:
|
||||
cd $(VM_DIR) && zsh "$(CURDIR)/$(SCRIPTS)/cfw_install.sh" .
|
||||
cd $(VM_DIR) && SSH_PORT="$(SSH_PORT)" zsh "$(CURDIR)/$(SCRIPTS)/cfw_install.sh" .
|
||||
|
||||
cfw_install_dev:
|
||||
cd $(VM_DIR) && zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_dev.sh" .
|
||||
cd $(VM_DIR) && SSH_PORT="$(SSH_PORT)" zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_dev.sh" .
|
||||
|
||||
cfw_install_jb:
|
||||
cd $(VM_DIR) && zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_jb.sh" .
|
||||
cd $(VM_DIR) && SSH_PORT="$(SSH_PORT)" zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_jb.sh" .
|
||||
|
||||
@@ -29,7 +29,7 @@ CFW_INPUT="cfw_input"
|
||||
CFW_ARCHIVE="cfw_input.tar.zst"
|
||||
TEMP_DIR="$VM_DIR/.cfw_temp"
|
||||
|
||||
SSH_PORT=2222
|
||||
SSH_PORT="${SSH_PORT:-2222}"
|
||||
SSH_PASS="alpine"
|
||||
SSH_USER="root"
|
||||
SSH_HOST="localhost"
|
||||
|
||||
@@ -29,7 +29,7 @@ CFW_INPUT="cfw_input"
|
||||
CFW_ARCHIVE="cfw_input.tar.zst"
|
||||
TEMP_DIR="$VM_DIR/.cfw_temp"
|
||||
|
||||
SSH_PORT=2222
|
||||
SSH_PORT="${SSH_PORT:-2222}"
|
||||
SSH_PASS="alpine"
|
||||
SSH_USER="root"
|
||||
SSH_HOST="localhost"
|
||||
|
||||
@@ -35,7 +35,7 @@ CFW_JB_INPUT="cfw_jb_input"
|
||||
CFW_JB_ARCHIVE="cfw_jb_input.tar.zst"
|
||||
TEMP_DIR="$VM_DIR/.cfw_temp"
|
||||
|
||||
SSH_PORT=2222
|
||||
SSH_PORT="${SSH_PORT:-2222}"
|
||||
SSH_PASS="alpine"
|
||||
SSH_USER="root"
|
||||
SSH_HOST="localhost"
|
||||
|
||||
@@ -8,8 +8,26 @@
|
||||
set -euo pipefail
|
||||
|
||||
IRECOVERY="${IRECOVERY:-irecovery}"
|
||||
IRECOVERY_ECID="${IRECOVERY_ECID:-}"
|
||||
RAMDISK_DIR="${1:-Ramdisk}"
|
||||
|
||||
IRECOVERY_ARGS=()
|
||||
if [[ -n "$IRECOVERY_ECID" ]]; then
|
||||
IRECOVERY_ECID="${IRECOVERY_ECID#0x}"
|
||||
IRECOVERY_ECID="${IRECOVERY_ECID#0X}"
|
||||
[[ "$IRECOVERY_ECID" =~ ^[0-9A-Fa-f]{1,16}$ ]] || {
|
||||
echo "[-] Invalid IRECOVERY_ECID: ${IRECOVERY_ECID}"
|
||||
exit 1
|
||||
}
|
||||
IRECOVERY_ECID="0x${IRECOVERY_ECID:u}"
|
||||
IRECOVERY_ARGS=(-i "$IRECOVERY_ECID")
|
||||
echo "[*] Using ECID selector for irecovery: ${IRECOVERY_ECID}"
|
||||
fi
|
||||
|
||||
irecovery_cmd() {
|
||||
"$IRECOVERY" "${IRECOVERY_ARGS[@]}" "$@"
|
||||
}
|
||||
|
||||
if [[ ! -d "$RAMDISK_DIR" ]]; then
|
||||
echo "[-] Ramdisk directory not found: $RAMDISK_DIR"
|
||||
echo " Run 'make ramdisk_build' first."
|
||||
@@ -30,48 +48,48 @@ fi
|
||||
|
||||
# 1. Load iBSS + iBEC (DFU → recovery)
|
||||
echo " [1/8] Loading iBSS..."
|
||||
"$IRECOVERY" -f "$RAMDISK_DIR/iBSS.vresearch101.RELEASE.img4"
|
||||
irecovery_cmd -f "$RAMDISK_DIR/iBSS.vresearch101.RELEASE.img4"
|
||||
|
||||
echo " [2/8] Loading iBEC..."
|
||||
"$IRECOVERY" -f "$RAMDISK_DIR/iBEC.vresearch101.RELEASE.img4"
|
||||
"$IRECOVERY" -c go
|
||||
irecovery_cmd -f "$RAMDISK_DIR/iBEC.vresearch101.RELEASE.img4"
|
||||
irecovery_cmd -c go
|
||||
|
||||
sleep 1
|
||||
|
||||
# 2. Load SPTM
|
||||
echo " [3/8] Loading SPTM..."
|
||||
"$IRECOVERY" -f "$RAMDISK_DIR/sptm.vresearch1.release.img4"
|
||||
"$IRECOVERY" -c firmware
|
||||
irecovery_cmd -f "$RAMDISK_DIR/sptm.vresearch1.release.img4"
|
||||
irecovery_cmd -c firmware
|
||||
|
||||
# 3. Load TXM
|
||||
echo " [4/8] Loading TXM..."
|
||||
"$IRECOVERY" -f "$RAMDISK_DIR/txm.img4"
|
||||
"$IRECOVERY" -c firmware
|
||||
irecovery_cmd -f "$RAMDISK_DIR/txm.img4"
|
||||
irecovery_cmd -c firmware
|
||||
|
||||
# 4. Load trustcache
|
||||
echo " [5/8] Loading trustcache..."
|
||||
"$IRECOVERY" -f "$RAMDISK_DIR/trustcache.img4"
|
||||
"$IRECOVERY" -c firmware
|
||||
irecovery_cmd -f "$RAMDISK_DIR/trustcache.img4"
|
||||
irecovery_cmd -c firmware
|
||||
|
||||
# 5. Load ramdisk
|
||||
echo " [6/8] Loading ramdisk..."
|
||||
"$IRECOVERY" -f "$RAMDISK_DIR/ramdisk.img4"
|
||||
irecovery_cmd -f "$RAMDISK_DIR/ramdisk.img4"
|
||||
sleep 2
|
||||
"$IRECOVERY" -c ramdisk
|
||||
irecovery_cmd -c ramdisk
|
||||
|
||||
# 6. Load device tree
|
||||
echo " [7/8] Loading device tree..."
|
||||
"$IRECOVERY" -f "$RAMDISK_DIR/DeviceTree.vphone600ap.img4"
|
||||
"$IRECOVERY" -c devicetree
|
||||
irecovery_cmd -f "$RAMDISK_DIR/DeviceTree.vphone600ap.img4"
|
||||
irecovery_cmd -c devicetree
|
||||
|
||||
# 7. Load SEP
|
||||
echo " [8/8] Loading SEP..."
|
||||
"$IRECOVERY" -f "$RAMDISK_DIR/sep-firmware.vresearch101.RELEASE.img4"
|
||||
"$IRECOVERY" -c firmware
|
||||
irecovery_cmd -f "$RAMDISK_DIR/sep-firmware.vresearch101.RELEASE.img4"
|
||||
irecovery_cmd -c firmware
|
||||
|
||||
# 8. Load kernel and boot
|
||||
echo " [*] Booting kernel..."
|
||||
"$IRECOVERY" -f "$KERNEL_IMG"
|
||||
"$IRECOVERY" -c bootx
|
||||
irecovery_cmd -f "$KERNEL_IMG"
|
||||
irecovery_cmd -c bootx
|
||||
|
||||
echo "[+] Boot sequence complete. Device should be booting into ramdisk."
|
||||
|
||||
@@ -17,7 +17,7 @@ cd "$PROJECT_ROOT"
|
||||
|
||||
LOG_DIR="${PROJECT_ROOT}/setup_logs"
|
||||
DFU_LOG="${LOG_DIR}/boot_dfu.log"
|
||||
IPROXY_LOG="${LOG_DIR}/iproxy_2222.log"
|
||||
IPROXY_LOG=""
|
||||
|
||||
DFU_PID=""
|
||||
IPROXY_PID=""
|
||||
@@ -32,9 +32,16 @@ POST_RESTORE_KILL_DELAY="${POST_RESTORE_KILL_DELAY:-30}"
|
||||
POST_KILL_SETTLE_DELAY="${POST_KILL_SETTLE_DELAY:-5}"
|
||||
RAMDISK_SSH_TIMEOUT="${RAMDISK_SSH_TIMEOUT:-60}"
|
||||
RAMDISK_SSH_INTERVAL="${RAMDISK_SSH_INTERVAL:-2}"
|
||||
RAMDISK_SSH_PORT="${RAMDISK_SSH_PORT:-2222}"
|
||||
RAMDISK_SSH_PORT="${RAMDISK_SSH_PORT:-}"
|
||||
RAMDISK_SSH_USER="${RAMDISK_SSH_USER:-root}"
|
||||
RAMDISK_SSH_PASS="${RAMDISK_SSH_PASS:-alpine}"
|
||||
RAMDISK_SSH_PORT_EXPLICIT=0
|
||||
if [[ -n "$RAMDISK_SSH_PORT" ]]; then
|
||||
RAMDISK_SSH_PORT_EXPLICIT=1
|
||||
fi
|
||||
|
||||
DEVICE_UDID=""
|
||||
DEVICE_ECID=""
|
||||
JB_MODE=0
|
||||
DEV_MODE=0
|
||||
SKIP_PROJECT_SETUP=0
|
||||
@@ -49,6 +56,92 @@ require_cmd() {
|
||||
command -v "$cmd" >/dev/null 2>&1 || die "Missing required command: $cmd"
|
||||
}
|
||||
|
||||
normalize_ecid() {
|
||||
local ecid="$1"
|
||||
ecid="${ecid#0x}"
|
||||
ecid="${ecid#0X}"
|
||||
[[ "$ecid" =~ ^[0-9A-Fa-f]{1,16}$ ]] || return 1
|
||||
printf "%016s" "${ecid:u}" | tr ' ' '0'
|
||||
}
|
||||
|
||||
load_device_identity() {
|
||||
local prediction_file="${VM_DIR_ABS}/udid-prediction.txt"
|
||||
local timeout=30
|
||||
local waited=0
|
||||
local key value
|
||||
local udid_ecid
|
||||
|
||||
while [[ ! -f "$prediction_file" && "$waited" -lt "$timeout" ]]; do
|
||||
if [[ -n "$DFU_PID" ]] && ! kill -0 "$DFU_PID" 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
(( waited++ ))
|
||||
done
|
||||
|
||||
[[ -f "$prediction_file" ]] || die "Missing ${prediction_file}. Rebuild and run make boot_dfu to generate it."
|
||||
|
||||
DEVICE_UDID=""
|
||||
DEVICE_ECID=""
|
||||
while IFS='=' read -r key value; do
|
||||
case "$key" in
|
||||
UDID)
|
||||
DEVICE_UDID="${value:u}"
|
||||
;;
|
||||
ECID)
|
||||
DEVICE_ECID="$(normalize_ecid "$value" || true)"
|
||||
;;
|
||||
esac
|
||||
done < "$prediction_file"
|
||||
|
||||
[[ "$DEVICE_UDID" =~ ^[0-9A-F]{8}-[0-9A-F]{16}$ ]] \
|
||||
|| die "Invalid UDID in ${prediction_file}: ${DEVICE_UDID}"
|
||||
|
||||
if [[ -z "$DEVICE_ECID" ]]; then
|
||||
DEVICE_ECID="${DEVICE_UDID#*-}"
|
||||
fi
|
||||
[[ "$DEVICE_ECID" =~ ^[0-9A-F]{16}$ ]] \
|
||||
|| die "Invalid ECID in ${prediction_file}: ${DEVICE_ECID}"
|
||||
|
||||
udid_ecid="${DEVICE_UDID#*-}"
|
||||
[[ "$udid_ecid" == "$DEVICE_ECID" ]] \
|
||||
|| die "UDID/ECID mismatch in ${prediction_file}: ${DEVICE_UDID} vs 0x${DEVICE_ECID}"
|
||||
|
||||
echo "[+] Device identity loaded: UDID=${DEVICE_UDID} ECID=0x${DEVICE_ECID}"
|
||||
}
|
||||
|
||||
port_is_listening() {
|
||||
local port="$1"
|
||||
lsof -n -t -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1
|
||||
}
|
||||
|
||||
pick_random_ssh_port() {
|
||||
local attempt port
|
||||
for attempt in {1..200}; do
|
||||
port=$((20000 + (RANDOM % 40000)))
|
||||
if ! port_is_listening "$port"; then
|
||||
echo "$port"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
choose_ramdisk_ssh_port() {
|
||||
if [[ -n "$RAMDISK_SSH_PORT" ]]; then
|
||||
[[ "$RAMDISK_SSH_PORT" == <-> ]] || die "RAMDISK_SSH_PORT must be an integer"
|
||||
(( RAMDISK_SSH_PORT >= 1 && RAMDISK_SSH_PORT <= 65535 )) \
|
||||
|| die "RAMDISK_SSH_PORT out of range: ${RAMDISK_SSH_PORT}"
|
||||
if port_is_listening "$RAMDISK_SSH_PORT"; then
|
||||
die "RAMDISK_SSH_PORT ${RAMDISK_SSH_PORT} is already in use"
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
RAMDISK_SSH_PORT="$(pick_random_ssh_port)" \
|
||||
|| die "Failed to allocate a random local SSH forward port"
|
||||
}
|
||||
|
||||
collect_vm_lock_pids() {
|
||||
local -a paths pids
|
||||
local path pid
|
||||
@@ -329,12 +422,19 @@ wait_for_post_restore_reboot() {
|
||||
|
||||
wait_for_recovery() {
|
||||
local irecovery="${PROJECT_ROOT}/.limd/bin/irecovery"
|
||||
local -a query_args
|
||||
[[ -x "$irecovery" ]] || die "irecovery not found at $irecovery"
|
||||
|
||||
if [[ -n "$DEVICE_ECID" ]]; then
|
||||
query_args=(-i "0x${DEVICE_ECID}")
|
||||
else
|
||||
query_args=()
|
||||
fi
|
||||
|
||||
echo "[*] Waiting for recovery/DFU endpoint..."
|
||||
local i
|
||||
for i in {1..90}; do
|
||||
if "$irecovery" -q >/dev/null 2>&1; then
|
||||
if "$irecovery" "${query_args[@]}" -q >/dev/null 2>&1; then
|
||||
echo "[+] Device endpoint is reachable"
|
||||
return
|
||||
fi
|
||||
@@ -346,29 +446,28 @@ wait_for_recovery() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
start_iproxy_2222() {
|
||||
start_iproxy() {
|
||||
local iproxy_bin
|
||||
local -a stale_pids
|
||||
local pid
|
||||
iproxy_bin="${PROJECT_ROOT}/.limd/bin/iproxy"
|
||||
[[ -x "$iproxy_bin" ]] || die "iproxy not found at $iproxy_bin (run: make setup_libimobiledevice)"
|
||||
[[ -n "$DEVICE_UDID" ]] || die "Device UDID is empty; cannot start isolated iproxy"
|
||||
|
||||
stale_pids=(${(@f)$(lsof -n -t -iTCP:2222 -sTCP:LISTEN 2>/dev/null || true)})
|
||||
if (( ${#stale_pids[@]} > 0 )); then
|
||||
echo "[*] Found stale listener(s) on tcp/2222, terminating..."
|
||||
for pid in "${stale_pids[@]}"; do
|
||||
[[ -z "$pid" || "$pid" == "$$" ]] && continue
|
||||
kill_descendants "$pid"
|
||||
kill -9 "$pid" >/dev/null 2>&1 || true
|
||||
done
|
||||
sleep 1
|
||||
choose_ramdisk_ssh_port
|
||||
|
||||
if port_is_listening "$RAMDISK_SSH_PORT"; then
|
||||
if [[ "$RAMDISK_SSH_PORT_EXPLICIT" == "1" ]]; then
|
||||
die "RAMDISK_SSH_PORT ${RAMDISK_SSH_PORT} is already in use"
|
||||
fi
|
||||
RAMDISK_SSH_PORT="$(pick_random_ssh_port)" \
|
||||
|| die "Failed to allocate a free random local SSH forward port"
|
||||
fi
|
||||
|
||||
IPROXY_LOG="${LOG_DIR}/iproxy_${RAMDISK_SSH_PORT}.log"
|
||||
mkdir -p "$LOG_DIR"
|
||||
: > "$IPROXY_LOG"
|
||||
|
||||
echo "[*] Starting iproxy 2222 -> 22..."
|
||||
("$iproxy_bin" 2222 22 >"$IPROXY_LOG" 2>&1) &
|
||||
echo "[*] Starting iproxy ${RAMDISK_SSH_PORT} -> 22 (UDID=${DEVICE_UDID})..."
|
||||
("$iproxy_bin" -u "$DEVICE_UDID" "$RAMDISK_SSH_PORT" 22 >"$IPROXY_LOG" 2>&1) &
|
||||
IPROXY_PID=$!
|
||||
|
||||
sleep 1
|
||||
@@ -431,14 +530,16 @@ wait_for_ramdisk_ssh() {
|
||||
done
|
||||
|
||||
echo "[-] Timed out waiting for ramdisk SSH readiness."
|
||||
echo "[-] iproxy log tail:"
|
||||
tail -n 40 "$IPROXY_LOG" 2>/dev/null || true
|
||||
if [[ -n "$IPROXY_LOG" ]]; then
|
||||
echo "[-] iproxy log tail:"
|
||||
tail -n 40 "$IPROXY_LOG" 2>/dev/null || true
|
||||
fi
|
||||
echo "[-] boot_dfu log tail:"
|
||||
tail -n 60 "$DFU_LOG" 2>/dev/null || true
|
||||
die "Ramdisk SSH did not become ready in ${RAMDISK_SSH_TIMEOUT}s."
|
||||
}
|
||||
|
||||
stop_iproxy_2222() {
|
||||
stop_iproxy() {
|
||||
if [[ -n "$IPROXY_PID" ]] && kill -0 "$IPROXY_PID" 2>/dev/null; then
|
||||
echo "[*] Stopping iproxy (pid=$IPROXY_PID)..."
|
||||
kill_descendants "$IPROXY_PID"
|
||||
@@ -522,9 +623,10 @@ main() {
|
||||
echo ""
|
||||
echo "=== Restore phase ==="
|
||||
start_boot_dfu
|
||||
load_device_identity
|
||||
wait_for_recovery
|
||||
run_make "Restore" restore_get_shsh
|
||||
run_make "Restore" restore
|
||||
run_make "Restore" restore_get_shsh RESTORE_UDID="$DEVICE_UDID" RESTORE_ECID="0x$DEVICE_ECID"
|
||||
run_make "Restore" restore RESTORE_UDID="$DEVICE_UDID" RESTORE_ECID="0x$DEVICE_ECID"
|
||||
wait_for_post_restore_reboot
|
||||
stop_boot_dfu
|
||||
echo "[*] Waiting ${POST_KILL_SETTLE_DELAY}s for cleanup before ramdisk stage..."
|
||||
@@ -533,16 +635,17 @@ main() {
|
||||
echo ""
|
||||
echo "=== Ramdisk + CFW phase ==="
|
||||
start_boot_dfu
|
||||
load_device_identity
|
||||
wait_for_recovery
|
||||
run_make "Ramdisk" ramdisk_build
|
||||
run_make "Ramdisk" ramdisk_send
|
||||
start_iproxy_2222
|
||||
run_make "Ramdisk" ramdisk_send IRECOVERY_ECID="0x$DEVICE_ECID"
|
||||
start_iproxy
|
||||
|
||||
wait_for_ramdisk_ssh
|
||||
|
||||
run_make "CFW install" "$cfw_install_target"
|
||||
run_make "CFW install" "$cfw_install_target" SSH_PORT="$RAMDISK_SSH_PORT"
|
||||
stop_boot_dfu
|
||||
stop_iproxy_2222
|
||||
stop_iproxy
|
||||
|
||||
echo ""
|
||||
echo "=== First boot ==="
|
||||
|
||||
@@ -544,7 +544,7 @@ class VPhoneControl {
|
||||
let timeoutSeconds = max(Int(timeout.rounded()), 1)
|
||||
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + timeout) { [weak self] in
|
||||
guard let self else { return }
|
||||
guard let pending = self.removePending(id: id) else { return }
|
||||
guard let pending = removePending(id: id) else { return }
|
||||
pending.handler(.failure(ControlError.requestTimedOut(type: type, seconds: timeoutSeconds)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ class VPhoneFileBrowserModel {
|
||||
transferName = nil
|
||||
await refresh()
|
||||
// Set error after refresh so refresh() doesn't clear it before the alert fires.
|
||||
if let e = uploadError { self.error = e }
|
||||
if let e = uploadError { error = e }
|
||||
}
|
||||
|
||||
func createNewFolder(name: String) async {
|
||||
|
||||
@@ -13,6 +13,9 @@ import Virtualization
|
||||
/// Minimum host OS for PV=3: macOS 15.0 (Sequoia)
|
||||
///
|
||||
enum VPhoneHardware {
|
||||
/// Fixed CPID for the current vphone hardware descriptor.
|
||||
static let udidChipID: UInt32 = 0xFE01
|
||||
|
||||
static func createModel() throws -> VZMacHardwareModel {
|
||||
// platformVersion=3, boardID=0x90, ISA=2 matches vresearch101
|
||||
let desc = Dynamic._VZMacHardwareModelDescriptor()
|
||||
|
||||
@@ -46,7 +46,9 @@ class VPhoneLocationProvider: NSObject {
|
||||
private var replayTask: Task<Void, Never>?
|
||||
private var replayName: String?
|
||||
|
||||
var isReplaying: Bool { replayTask != nil }
|
||||
var isReplaying: Bool {
|
||||
replayTask != nil
|
||||
}
|
||||
|
||||
init(control: VPhoneControl) {
|
||||
self.control = control
|
||||
@@ -135,7 +137,7 @@ class VPhoneLocationProvider: NSObject {
|
||||
var index = 0
|
||||
while !Task.isCancelled {
|
||||
let point = points[index]
|
||||
self.sendSimulatedLocation(
|
||||
sendSimulatedLocation(
|
||||
latitude: point.latitude,
|
||||
longitude: point.longitude,
|
||||
altitude: point.altitude,
|
||||
|
||||
@@ -27,6 +27,12 @@ class VPhoneVirtualMachine: NSObject, VZVirtualMachineDelegate {
|
||||
var kernelDebugPort: Int = 5909
|
||||
}
|
||||
|
||||
private struct DeviceIdentity {
|
||||
let cpidHex: String
|
||||
let ecidHex: String
|
||||
let udid: String
|
||||
}
|
||||
|
||||
init(options: Options) throws {
|
||||
// --- Hardware model (PV=3) ---
|
||||
let hwModel = try VPhoneHardware.createModel()
|
||||
@@ -36,17 +42,34 @@ class VPhoneVirtualMachine: NSObject, VZVirtualMachineDelegate {
|
||||
let platform = VZMacPlatformConfiguration()
|
||||
|
||||
// Persist machineIdentifier for stable ECID
|
||||
let machineIdentifier: VZMacMachineIdentifier
|
||||
if let savedData = try? Data(contentsOf: options.machineIDURL),
|
||||
let savedID = VZMacMachineIdentifier(dataRepresentation: savedData)
|
||||
{
|
||||
platform.machineIdentifier = savedID
|
||||
machineIdentifier = savedID
|
||||
print("[vphone] Loaded machineIdentifier (ECID stable)")
|
||||
} else {
|
||||
let newID = VZMacMachineIdentifier()
|
||||
platform.machineIdentifier = newID
|
||||
machineIdentifier = newID
|
||||
try newID.dataRepresentation.write(to: options.machineIDURL)
|
||||
print("[vphone] Created new machineIdentifier -> \(options.machineIDURL.lastPathComponent)")
|
||||
}
|
||||
platform.machineIdentifier = machineIdentifier
|
||||
|
||||
if let identity = Self.resolveDeviceIdentity(machineIdentifier: machineIdentifier) {
|
||||
print("[vphone] ECID: 0x\(identity.ecidHex)")
|
||||
print("[vphone] Predicted UDID: \(identity.udid)")
|
||||
do {
|
||||
let outputURL = try Self.writeUDIDPrediction(
|
||||
identity: identity, machineIDURL: options.machineIDURL
|
||||
)
|
||||
print("[vphone] Wrote UDID prediction: \(outputURL.path)")
|
||||
} catch {
|
||||
print("[vphone] Warning: failed to write udid-prediction.txt: \(error)")
|
||||
}
|
||||
} else {
|
||||
print("[vphone] Warning: failed to resolve ECID from machineIdentifier")
|
||||
}
|
||||
|
||||
let auxStorage = try VZMacAuxiliaryStorage(
|
||||
creatingStorageAt: options.nvramURL,
|
||||
@@ -204,6 +227,39 @@ class VPhoneVirtualMachine: NSObject, VZVirtualMachineDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private static func resolveDeviceIdentity(machineIdentifier: VZMacMachineIdentifier)
|
||||
-> DeviceIdentity?
|
||||
{
|
||||
let ecidValue: UInt64? = if let ecid = Dynamic(machineIdentifier)._ECID.asUInt64 {
|
||||
ecid
|
||||
} else if let ecidNumber = Dynamic(machineIdentifier)._ECID.asObject as? NSNumber {
|
||||
ecidNumber.uint64Value
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
|
||||
guard let ecidValue else { return nil }
|
||||
|
||||
let cpidHex = String(format: "%08X", VPhoneHardware.udidChipID)
|
||||
let ecidHex = String(format: "%016llX", ecidValue)
|
||||
let udid = "\(cpidHex)-\(ecidHex)"
|
||||
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"
|
||||
)
|
||||
let content = """
|
||||
UDID=\(identity.udid)
|
||||
CPID=0x\(identity.cpidHex)
|
||||
ECID=0x\(identity.ecidHex)
|
||||
MACHINE_IDENTIFIER=\(machineIDURL.lastPathComponent)
|
||||
"""
|
||||
try content.write(to: outputURL, atomically: true, encoding: .utf8)
|
||||
return outputURL
|
||||
}
|
||||
|
||||
// MARK: - Battery
|
||||
|
||||
/// Update the synthetic battery charge and connectivity at runtime.
|
||||
|
||||
Reference in New Issue
Block a user