mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-04-05 04:59:05 +08:00
pymobiledevice3: Replace most external tools with pymobiledevice3
This commit is contained in:
24
.gitmodules
vendored
24
.gitmodules
vendored
@@ -22,27 +22,3 @@
|
||||
[submodule "scripts/repos/insert_dylib"]
|
||||
path = scripts/repos/insert_dylib
|
||||
url = https://github.com/tyilo/insert_dylib.git
|
||||
[submodule "scripts/repos/libplist"]
|
||||
path = scripts/repos/libplist
|
||||
url = https://github.com/libimobiledevice/libplist.git
|
||||
[submodule "scripts/repos/libimobiledevice-glue"]
|
||||
path = scripts/repos/libimobiledevice-glue
|
||||
url = https://github.com/libimobiledevice/libimobiledevice-glue.git
|
||||
[submodule "scripts/repos/libusbmuxd"]
|
||||
path = scripts/repos/libusbmuxd
|
||||
url = https://github.com/libimobiledevice/libusbmuxd.git
|
||||
[submodule "scripts/repos/libtatsu"]
|
||||
path = scripts/repos/libtatsu
|
||||
url = https://github.com/libimobiledevice/libtatsu.git
|
||||
[submodule "scripts/repos/libimobiledevice"]
|
||||
path = scripts/repos/libimobiledevice
|
||||
url = https://github.com/libimobiledevice/libimobiledevice.git
|
||||
[submodule "scripts/repos/libirecovery"]
|
||||
path = scripts/repos/libirecovery
|
||||
url = https://github.com/libimobiledevice/libirecovery.git
|
||||
[submodule "scripts/repos/idevicerestore"]
|
||||
path = scripts/repos/idevicerestore
|
||||
url = https://github.com/libimobiledevice/idevicerestore.git
|
||||
[submodule "scripts/repos/libzip"]
|
||||
path = scripts/repos/libzip
|
||||
url = https://github.com/nih-at/libzip.git
|
||||
|
||||
34
Makefile
34
Makefile
@@ -13,7 +13,7 @@ BACKUP_INCLUDE_IPSW ?= 0
|
||||
FORCE ?= 0
|
||||
RESTORE_UDID ?= # UDID for restore operations
|
||||
RESTORE_ECID ?= # ECID for restore operations
|
||||
IRECOVERY_ECID ?= # ECID for irecovery operations
|
||||
IRECOVERY_ECID ?= # ECID for ramdisk send operations
|
||||
|
||||
# ─── Build info ──────────────────────────────────────────────────
|
||||
GIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||
@@ -28,16 +28,14 @@ BUNDLE_BIN := $(BUNDLE)/Contents/MacOS/vphone-cli
|
||||
INFO_PLIST := sources/Info.plist
|
||||
ENTITLEMENTS := sources/vphone.entitlements
|
||||
VENV := .venv
|
||||
LIMD_PREFIX := .limd
|
||||
TOOLS_PREFIX := .tools
|
||||
IRECOVERY := $(LIMD_PREFIX)/bin/irecovery
|
||||
IDEVICERESTORE := $(LIMD_PREFIX)/bin/idevicerestore
|
||||
PMD3_BRIDGE := $(CURDIR)/$(SCRIPTS)/pymobiledevice3_bridge.py
|
||||
PYTHON := $(CURDIR)/$(VENV)/bin/python3
|
||||
|
||||
SWIFT_SOURCES := $(shell find sources -name '*.swift')
|
||||
|
||||
# ─── Environment — prefer project-local binaries ────────────────
|
||||
export PATH := $(CURDIR)/$(TOOLS_PREFIX)/bin:$(CURDIR)/$(LIMD_PREFIX)/bin:$(CURDIR)/$(VENV)/bin:$(CURDIR)/.build/release:$(PATH)
|
||||
export PATH := $(CURDIR)/$(TOOLS_PREFIX)/bin:$(CURDIR)/$(VENV)/bin:$(CURDIR)/.build/release:$(PATH)
|
||||
|
||||
# ─── Default ──────────────────────────────────────────────────────
|
||||
.PHONY: help
|
||||
@@ -53,7 +51,7 @@ help:
|
||||
@echo " SUDO_PASSWORD=... Preload sudo credential for setup flow"
|
||||
@echo ""
|
||||
@echo "Setup (one-time):"
|
||||
@echo " make setup_tools Install all tools (brew, submodule-sourced trustcache/insert_dylib/libimobiledevice, venv)"
|
||||
@echo " make setup_tools Install all tools (brew, trustcache, insert_dylib, venv+pymobiledevice3)"
|
||||
@echo ""
|
||||
@echo "Build:"
|
||||
@echo " make build Build + sign vphone-cli"
|
||||
@@ -91,7 +89,7 @@ help:
|
||||
@echo ""
|
||||
@echo "Restore:"
|
||||
@echo " make restore_get_shsh Dump SHSH response from Apple"
|
||||
@echo " make restore idevicerestore to device"
|
||||
@echo " make restore Restore to device (pymobiledevice3 backend)"
|
||||
@echo ""
|
||||
@echo "Ramdisk:"
|
||||
@echo " make ramdisk_build Build signed SSH ramdisk"
|
||||
@@ -167,11 +165,7 @@ bundle: build $(INFO_PLIST)
|
||||
@cp -f sources/AppIcon.icns $(BUNDLE)/Contents/Resources/AppIcon.icns
|
||||
@cp -f $(SCRIPTS)/vphoned/signcert.p12 $(BUNDLE)/Contents/Resources/signcert.p12
|
||||
@cp -f $$(command -v ldid) $(BUNDLE)/Contents/MacOS/ldid
|
||||
@cp -f $$(command -v ideviceinstaller) $(BUNDLE)/Contents/MacOS/ideviceinstaller
|
||||
@cp -f $$(command -v idevice_id) $(BUNDLE)/Contents/MacOS/idevice_id
|
||||
@codesign --force --sign - $(BUNDLE)/Contents/MacOS/ldid
|
||||
@codesign --force --sign - $(BUNDLE)/Contents/MacOS/ideviceinstaller
|
||||
@codesign --force --sign - $(BUNDLE)/Contents/MacOS/idevice_id
|
||||
@codesign --force --sign - --entitlements $(ENTITLEMENTS) $(BUNDLE_BIN)
|
||||
@echo " bundled → $(BUNDLE)"
|
||||
|
||||
@@ -292,16 +286,16 @@ fw_patch_jb: patcher_build
|
||||
.PHONY: restore_get_shsh restore
|
||||
|
||||
restore_get_shsh:
|
||||
cd $(VM_DIR) && "$(CURDIR)/$(IDEVICERESTORE)" \
|
||||
$(if $(RESTORE_UDID),-u $(RESTORE_UDID),) \
|
||||
$(if $(RESTORE_ECID),-i $(RESTORE_ECID),) \
|
||||
-e -y ./iPhone*_Restore -t
|
||||
cd $(VM_DIR) && "$(PYTHON)" "$(PMD3_BRIDGE)" restore-get-shsh \
|
||||
--vm-dir . \
|
||||
$(if $(RESTORE_UDID),--udid $(RESTORE_UDID),) \
|
||||
$(if $(RESTORE_ECID),--ecid $(RESTORE_ECID),)
|
||||
|
||||
restore:
|
||||
cd $(VM_DIR) && "$(CURDIR)/$(IDEVICERESTORE)" \
|
||||
$(if $(RESTORE_UDID),-u $(RESTORE_UDID),) \
|
||||
$(if $(RESTORE_ECID),-i $(RESTORE_ECID),) \
|
||||
-e -y ./iPhone*_Restore
|
||||
cd $(VM_DIR) && "$(PYTHON)" "$(PMD3_BRIDGE)" restore-update \
|
||||
--vm-dir . \
|
||||
$(if $(RESTORE_UDID),--udid $(RESTORE_UDID),) \
|
||||
$(if $(RESTORE_ECID),--ecid $(RESTORE_ECID),)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Ramdisk
|
||||
@@ -313,7 +307,7 @@ ramdisk_build: patcher_build
|
||||
cd $(VM_DIR) && RAMDISK_UDID="$(RAMDISK_UDID)" $(PYTHON) "$(CURDIR)/$(SCRIPTS)/ramdisk_build.py" .
|
||||
|
||||
ramdisk_send:
|
||||
cd $(VM_DIR) && IRECOVERY="$(CURDIR)/$(IRECOVERY)" IRECOVERY_ECID="$(IRECOVERY_ECID)" RAMDISK_UDID="$(RAMDISK_UDID)" RESTORE_UDID="$(RESTORE_UDID)" \
|
||||
cd $(VM_DIR) && PMD3_BRIDGE="$(PMD3_BRIDGE)" PYTHON="$(PYTHON)" IRECOVERY_ECID="$(IRECOVERY_ECID)" RAMDISK_UDID="$(RAMDISK_UDID)" RESTORE_UDID="$(RESTORE_UDID)" \
|
||||
zsh "$(CURDIR)/$(SCRIPTS)/ramdisk_send.sh"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
20
README.md
20
README.md
@@ -87,7 +87,7 @@ Boot into Recovery (long press power button), open Terminal, then choose one set
|
||||
**Install dependencies:**
|
||||
|
||||
```bash
|
||||
brew install aria2 ideviceinstaller wget gnu-tar openssl@3 ldid-procursus sshpass keystone autoconf automake pkg-config libtool cmake
|
||||
brew install aria2 wget gnu-tar openssl@3 ldid-procursus sshpass keystone libusb ipsw
|
||||
```
|
||||
|
||||
`scripts/fw_prepare.sh` prefers `aria2c` for faster multi-connection downloads and falls back to `curl` or `wget` when needed.
|
||||
@@ -110,7 +110,7 @@ make setup_machine # full automation through "First Boot" (includes r
|
||||
## Manual Setup
|
||||
|
||||
```bash
|
||||
make setup_tools # install brew deps (including aria2c), build trustcache + insert_dylib + libimobiledevice from submodule sources, create Python venv
|
||||
make setup_tools # install brew deps, build trustcache + insert_dylib, create Python venv (pymobiledevice3, aria2c included)
|
||||
make build # build + sign vphone-cli
|
||||
make vm_new # create VM directory with manifest (config.plist)
|
||||
# options: CPU=8 MEMORY=8192 DISK_SIZE=64
|
||||
@@ -146,7 +146,7 @@ make boot_dfu # boot VM in DFU mode (keep running)
|
||||
```bash
|
||||
# terminal 2
|
||||
make restore_get_shsh # fetch SHSH blob
|
||||
make restore # flash firmware via idevicerestore
|
||||
make restore # flash firmware via pymobiledevice3 restore backend
|
||||
```
|
||||
|
||||
## Install Custom Firmware
|
||||
@@ -164,11 +164,11 @@ sudo make ramdisk_build # build signed SSH ramdisk
|
||||
make ramdisk_send # send to device
|
||||
```
|
||||
|
||||
Once the ramdisk is running (you should see `Running server` in the output), open a **third terminal** for the iproxy tunnel, then install CFW from terminal 2:
|
||||
Once the ramdisk is running (you should see `Running server` in the output), open a **third terminal** for the usbmux tunnel, then install CFW from terminal 2:
|
||||
|
||||
```bash
|
||||
# terminal 3 — keep running
|
||||
iproxy 2222 22
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -211,13 +211,13 @@ shutdown -h now
|
||||
make boot
|
||||
```
|
||||
|
||||
In a separate terminal, start iproxy tunnels:
|
||||
In a separate terminal, start usbmux forward tunnels:
|
||||
|
||||
```bash
|
||||
iproxy 2222 22222 # SSH (dropbear)
|
||||
iproxy 2222 22 # SSH (JB: if you install openssh-server from Sileo)
|
||||
iproxy 5901 5901 # VNC
|
||||
iproxy 5910 5910 # RPC
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSH (dropbear)
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22 # SSH (JB: if you install openssh-server from Sileo)
|
||||
python3 -m pymobiledevice3 usbmux forward 5901 5901 # VNC
|
||||
python3 -m pymobiledevice3 usbmux forward 5910 5910 # RPC
|
||||
```
|
||||
|
||||
Connect via:
|
||||
|
||||
@@ -75,10 +75,13 @@ Apple の Virtualization.framework と PCC の研究用 VM インフラを使用
|
||||
sudo amfree --path [PATH_TO_VPHONE_DIR]
|
||||
```
|
||||
|
||||
このリポジトリでは、`make amfidont_allow_vphone` を実行すると
|
||||
`amfidont` 用のエンコード済みパスと CDHash の許可設定をまとめて行えます。
|
||||
|
||||
**依存関係のインストール:**
|
||||
|
||||
```bash
|
||||
brew install aria2 ideviceinstaller wget gnu-tar openssl@3 ldid-procursus sshpass keystone autoconf automake pkg-config libtool cmake
|
||||
brew install aria2 wget gnu-tar openssl@3 ldid-procursus sshpass keystone libusb ipsw
|
||||
```
|
||||
|
||||
`scripts/fw_prepare.sh` は高速な多重接続ダウンロードのために `aria2c` を優先し、必要に応じて `curl` または `wget` にフォールバックします。
|
||||
@@ -94,12 +97,14 @@ git clone --recurse-submodules https://github.com/Lakr233/vphone-cli.git
|
||||
```bash
|
||||
make setup_machine # 初回起動までを完全自動化(復元/ラムディスク/CFWを含む)
|
||||
# オプション:NONE_INTERACTIVE=1 SUDO_PASSWORD=...
|
||||
# DEV=1 で開発バリアント(+ TXM entitlement/デバッグバイパス)
|
||||
# JB=1 で脱獄バリアント(dev + 完全セキュリティバイパス)
|
||||
```
|
||||
|
||||
## 手動セットアップ
|
||||
|
||||
```bash
|
||||
make setup_tools # brew の依存関係インストール(aria2c を含む)、submodule ソースから trustcache + insert_dylib + libimobiledevice をビルド、Python venv の作成
|
||||
make setup_tools # brew 依存関係のインストール、trustcache + insert_dylib のビルド、Python venv 作成(pymobiledevice3/aria2c を含む)
|
||||
make build # vphone-cli のビルド + 署名
|
||||
make vm_new # VM ディレクトリとマニフェスト(config.plist)の作成
|
||||
# オプション:CPU=8 MEMORY=8192 DISK_SIZE=64
|
||||
@@ -135,7 +140,7 @@ make boot_dfu # DFUモードでVMを起動(実行したまま
|
||||
```bash
|
||||
# ターミナル 2
|
||||
make restore_get_shsh # SHSH blob の取得
|
||||
make restore # idevicerestore 経由でファームウェアを焼き込み
|
||||
make restore # pymobiledevice3 restore バックエンドでファームウェアを焼き込み
|
||||
```
|
||||
|
||||
## カスタムファームウェアのインストール
|
||||
@@ -153,11 +158,11 @@ sudo make ramdisk_build # 署名済みSSH Ramdisk のビルド
|
||||
make ramdisk_send # デバイスへ送信
|
||||
```
|
||||
|
||||
Ramdisk が起動したら(出力に `Running server` と表示されるはずです)、iproxy トンネル用に **3つ目のターミナル** を開き、ターミナル 2 から CFW をインストールします:
|
||||
Ramdisk が起動したら(出力に `Running server` と表示されるはずです)、usbmux トンネル用に **3つ目のターミナル** を開き、ターミナル 2 から CFW をインストールします:
|
||||
|
||||
```bash
|
||||
# ターミナル 3 — 実行したままにする
|
||||
iproxy 2222 22
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -200,13 +205,13 @@ shutdown -h now
|
||||
make boot
|
||||
```
|
||||
|
||||
別のターミナルで iproxy トンネルを開始します:
|
||||
別のターミナルで usbmux 転送トンネルを開始します:
|
||||
|
||||
```bash
|
||||
iproxy 2222 22222 # SSH(dropbear)
|
||||
iproxy 2222 22 # SSH(脱獄版:Sileo で openssh-server を入れた場合)
|
||||
iproxy 5901 5901 # VNC
|
||||
iproxy 5910 5910 # RPC
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSH(dropbear)
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22 # SSH(脱獄版:Sileo で openssh-server を入れた場合)
|
||||
python3 -m pymobiledevice3 usbmux forward 5901 5901 # VNC
|
||||
python3 -m pymobiledevice3 usbmux forward 5910 5910 # RPC
|
||||
```
|
||||
|
||||
以下で接続します:
|
||||
@@ -247,6 +252,11 @@ AMFI/デバッグ制限が正しくバイパスされていません。以下の
|
||||
|
||||
- **方法 2(デバッグ制限のみ無効化):**
|
||||
復旧モードで `csrutil enable --without debug`(完全な SIP 無効化は不要)を使用し、[`amfidont`](https://github.com/zqxwce/amfidont) または [`amfree`](https://github.com/retX0/amfree) をインストール/ロードして AMFI のその他の機能は有効のままにします。
|
||||
このリポジトリでは、`make amfidont_allow_vphone` により `amfidont` で必要なエンコード済みパスと CDHash の許可設定を自動で行えます。
|
||||
|
||||
**Q: `make boot` / `make boot_dfu` が `VZErrorDomain Code=2 "Virtualization is not available on this hardware."` で失敗します**
|
||||
|
||||
ホスト自体が Apple 仮想マシン上で動作しているため、ネストされた Virtualization.framework のゲスト起動は利用できません。ネストされていない macOS 15+ ホストで実行してください。`make boot_host_preflight` ではこの状態を `Model Name: Apple Virtual Machine 1` と `kern.hv_vmm_present=1` として確認できます。現在は `boot_binary_check` により、該当ホストでは起動前に早期失敗します。
|
||||
|
||||
**Q: システムアプリ(App Store、メッセージなど)がダウンロード・インストールできません**
|
||||
|
||||
|
||||
@@ -75,10 +75,13 @@ PCC 리서치 VM 인프라와 Apple의 Virtualization.framework를 사용하여
|
||||
sudo amfree --path [PATH_TO_VPHONE_DIR]
|
||||
```
|
||||
|
||||
이 저장소에서는 `make amfidont_allow_vphone`으로 `amfidont`에 필요한
|
||||
인코딩 경로와 CDHash 허용 설정을 한 번에 적용할 수 있습니다.
|
||||
|
||||
**의존성(Dependencies) 설치:**
|
||||
|
||||
```bash
|
||||
brew install aria2 ideviceinstaller wget gnu-tar openssl@3 ldid-procursus sshpass keystone autoconf automake pkg-config libtool cmake
|
||||
brew install aria2 wget gnu-tar openssl@3 ldid-procursus sshpass keystone libusb ipsw
|
||||
```
|
||||
|
||||
`scripts/fw_prepare.sh` 는 더 빠른 다중 연결 다운로드를 위해 `aria2c` 를 우선 사용하고, 필요하면 `curl` 또는 `wget` 으로 폴백합니다.
|
||||
@@ -94,12 +97,14 @@ git clone --recurse-submodules https://github.com/Lakr233/vphone-cli.git
|
||||
```bash
|
||||
make setup_machine # "First Boot"까지의 전체 과정 자동화 (복원/Ramdisk/커스텀 펌웨어 포함)
|
||||
# 옵션: NONE_INTERACTIVE=1 SUDO_PASSWORD=...
|
||||
# DEV=1 개발 변형 (+ TXM 권한/디버그 우회)
|
||||
# JB=1 탈옥 변형 (dev + 전체 보안 우회)
|
||||
```
|
||||
|
||||
## 수동 설정
|
||||
|
||||
```bash
|
||||
make setup_tools # brew 의존성 설치(aria2c 포함), submodule 소스에서 trustcache + insert_dylib + libimobiledevice 빌드, Python venv 생성
|
||||
make setup_tools # brew 의존성 설치, trustcache + insert_dylib 빌드, Python venv 생성(pymobiledevice3/aria2c 포함)
|
||||
make build # vphone-cli 빌드 및 서명
|
||||
make vm_new # VM 디렉토리 및 매니페스트(config.plist) 생성
|
||||
# 옵션: CPU=8 MEMORY=8192 DISK_SIZE=64
|
||||
@@ -135,7 +140,7 @@ make boot_dfu # VM을 DFU 모드로 부팅 (계속 실행 유지
|
||||
```bash
|
||||
# 터미널 2
|
||||
make restore_get_shsh # SHSH blob 가져오기
|
||||
make restore # idevicerestore를 통해 펌웨어 플래싱
|
||||
make restore # pymobiledevice3 restore 백엔드로 펌웨어 플래싱
|
||||
```
|
||||
|
||||
## 커스텀 펌웨어 설치
|
||||
@@ -153,11 +158,11 @@ sudo make ramdisk_build # 서명된 SSH 램디스크 빌드
|
||||
make ramdisk_send # 장치로 전송
|
||||
```
|
||||
|
||||
램디스크가 실행되면(출력에 `Running server`가 표시됨), **세 번째 터미널**을 열어 iproxy 터널을 시작한 후, 터미널 2에서 커스텀 펌웨어를 설치합니다:
|
||||
램디스크가 실행되면(출력에 `Running server`가 표시됨), **세 번째 터미널**을 열어 usbmux 터널을 시작한 후, 터미널 2에서 커스텀 펌웨어를 설치합니다:
|
||||
|
||||
```bash
|
||||
# 터미널 3 — 계속 실행 유지
|
||||
iproxy 2222 22
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -200,13 +205,13 @@ shutdown -h now
|
||||
make boot
|
||||
```
|
||||
|
||||
별도의 터미널에서 iproxy 터널을 시작합니다:
|
||||
별도의 터미널에서 usbmux 포워딩 터널을 시작합니다:
|
||||
|
||||
```bash
|
||||
iproxy 2222 22222 # SSH (dropbear)
|
||||
iproxy 2222 22 # SSH (탈옥: Sileo에서 openssh-server를 설치한 경우)
|
||||
iproxy 5901 5901 # VNC
|
||||
iproxy 5910 5910 # RPC
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSH (dropbear)
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22 # SSH (탈옥: Sileo에서 openssh-server를 설치한 경우)
|
||||
python3 -m pymobiledevice3 usbmux forward 5901 5901 # VNC
|
||||
python3 -m pymobiledevice3 usbmux forward 5910 5910 # RPC
|
||||
```
|
||||
|
||||
다음을 통해 연결합니다:
|
||||
@@ -247,6 +252,11 @@ AMFI/디버그 제한이 올바르게 우회되지 않았습니다. 다음 중
|
||||
|
||||
- **방법 2 (디버그 제한만 비활성화):**
|
||||
복구 모드에서 `csrutil enable --without debug`(완전한 SIP 비활성화 없음)를 사용한 다음, [`amfidont`](https://github.com/zqxwce/amfidont) 또는 [`amfree`](https://github.com/retX0/amfree)를 설치/로드하여 AMFI의 나머지 기능은 활성 상태로 유지합니다.
|
||||
이 저장소에서는 `make amfidont_allow_vphone`으로 `amfidont`에 필요한 인코딩 경로와 CDHash 허용 설정을 자동 적용할 수 있습니다.
|
||||
|
||||
**Q: `make boot` / `make boot_dfu` 실행 시 `VZErrorDomain Code=2 "Virtualization is not available on this hardware."`로 실패합니다.**
|
||||
|
||||
호스트 자체가 Apple 가상 머신에서 실행 중이기 때문에, 중첩된 Virtualization.framework 게스트 부팅은 지원되지 않습니다. 중첩이 아닌 macOS 15+ 호스트에서 실행하세요. `make boot_host_preflight`에서 `Model Name: Apple Virtual Machine 1` 및 `kern.hv_vmm_present=1`로 이를 확인할 수 있습니다. 현재는 이런 호스트에서 `boot_binary_check`가 VM 시작 전에 빠르게 실패 처리합니다.
|
||||
|
||||
**Q: 시스템 앱(App Store, 메시지 등)을 다운로드하거나 설치할 수 없습니다.**
|
||||
|
||||
|
||||
@@ -75,10 +75,13 @@
|
||||
sudo amfree --path [PATH_TO_VPHONE_DIR]
|
||||
```
|
||||
|
||||
在本仓库中,可以运行 `make amfidont_allow_vphone` 一次性配置
|
||||
`amfidont` 所需的编码路径与 CDHash 允许项。
|
||||
|
||||
**安装依赖:**
|
||||
|
||||
```bash
|
||||
brew install aria2 ideviceinstaller wget gnu-tar openssl@3 ldid-procursus sshpass keystone autoconf automake pkg-config libtool cmake
|
||||
brew install aria2 wget gnu-tar openssl@3 ldid-procursus sshpass keystone libusb ipsw
|
||||
```
|
||||
|
||||
`scripts/fw_prepare.sh` 会优先使用 `aria2c` 进行更快的多连接下载,必要时再回退到 `curl` 或 `wget`。
|
||||
@@ -101,7 +104,7 @@ make setup_machine # 完全自动化完成"首次启动"流程(包
|
||||
## 手动设置
|
||||
|
||||
```bash
|
||||
make setup_tools # 安装 brew 依赖(含 aria2c)、从 submodule 源码构建 trustcache + insert_dylib + libimobiledevice、创建 Python 虚拟环境
|
||||
make setup_tools # 安装 brew 依赖,构建 trustcache + insert_dylib,创建 Python 虚拟环境(含 pymobiledevice3/aria2c)
|
||||
make build # 构建并签名 vphone-cli
|
||||
make vm_new # 创建 VM 目录及清单文件(config.plist)
|
||||
# 选项:CPU=8 MEMORY=8192 DISK_SIZE=64
|
||||
@@ -137,7 +140,7 @@ make boot_dfu # 以 DFU 模式启动 VM(保持运行)
|
||||
```bash
|
||||
# 终端 2
|
||||
make restore_get_shsh # 获取 SHSH blob
|
||||
make restore # 通过 idevicerestore 刷写固件
|
||||
make restore # 通过 pymobiledevice3 restore 后端刷写固件
|
||||
```
|
||||
|
||||
## 安装自定义固件
|
||||
@@ -155,11 +158,11 @@ sudo make ramdisk_build # 构建签名的 SSH ramdisk
|
||||
make ramdisk_send # 发送到设备
|
||||
```
|
||||
|
||||
当 ramdisk 运行后(输出中应显示 `Running server`),打开**第三个终端**运行 iproxy 隧道,然后在终端 2 安装 CFW:
|
||||
当 ramdisk 运行后(输出中应显示 `Running server`),打开**第三个终端**运行 usbmux 隧道,然后在终端 2 安装 CFW:
|
||||
|
||||
```bash
|
||||
# 终端 3 —— 保持运行
|
||||
iproxy 2222 22
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -202,13 +205,13 @@ shutdown -h now
|
||||
make boot
|
||||
```
|
||||
|
||||
在另一个终端中启动 iproxy 隧道:
|
||||
在另一个终端中启动 usbmux 转发隧道:
|
||||
|
||||
```bash
|
||||
iproxy 2222 22222 # SSH(dropbear)
|
||||
iproxy 2222 22 # SSH(越狱版:在 Sileo 中安装 openssh-server 后)
|
||||
iproxy 5901 5901 # VNC
|
||||
iproxy 5910 5910 # RPC
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22222 # SSH(dropbear)
|
||||
python3 -m pymobiledevice3 usbmux forward 2222 22 # SSH(越狱版:在 Sileo 中安装 openssh-server 后)
|
||||
python3 -m pymobiledevice3 usbmux forward 5901 5901 # VNC
|
||||
python3 -m pymobiledevice3 usbmux forward 5910 5910 # RPC
|
||||
```
|
||||
|
||||
连接方式:
|
||||
@@ -249,6 +252,11 @@ AMFI/调试限制未正确绕过。选择以下任一方式:
|
||||
|
||||
- **方式 2(仅禁用调试限制):**
|
||||
在恢复模式中使用 `csrutil enable --without debug`(不完全禁用 SIP),然后安装/加载 [`amfidont`](https://github.com/zqxwce/amfidont) 或 [`amfree`](https://github.com/retX0/amfree),保持 AMFI 其他功能不变。
|
||||
在本仓库中,也可通过 `make amfidont_allow_vphone` 自动写入 `amfidont` 所需的编码路径与 CDHash 允许配置。
|
||||
|
||||
**问:`make boot` / `make boot_dfu` 启动后报错 `VZErrorDomain Code=2 "Virtualization is not available on this hardware."`。**
|
||||
|
||||
这是因为宿主机本身运行在 Apple 虚拟机中,无法再进行嵌套 Virtualization.framework 来启动 guest。请在非嵌套的 macOS 15+ 主机上运行。可用 `make boot_host_preflight` 检查,若显示 `Model Name: Apple Virtual Machine 1` 和 `kern.hv_vmm_present=1` 即为该情况。当前版本会在此类宿主机上通过 `boot_binary_check` 在启动前快速失败。
|
||||
|
||||
**问:系统应用(App Store、信息等)无法下载或安装。**
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
typer
|
||||
capstone
|
||||
keystone-engine
|
||||
pyimg4
|
||||
pymobiledevice3>=9.5.0
|
||||
ipsw-parser
|
||||
|
||||
@@ -246,7 +246,7 @@ mkdir -p "$TEMP_DIR"
|
||||
# ── Parse Cryptex paths from BuildManifest ─────────────────────
|
||||
echo ""
|
||||
echo "[*] Parsing iPhone BuildManifest for Cryptex paths..."
|
||||
CRYPTEX_PATHS=$("$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" cryptex-paths "$RESTORE_DIR/BuildManifest-iPhone.plist")
|
||||
CRYPTEX_PATHS=$("$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" cryptex-paths "$RESTORE_DIR/iPhone-BuildManifest.plist")
|
||||
CRYPTEX_SYSOS=$(echo "$CRYPTEX_PATHS" | head -1)
|
||||
CRYPTEX_APPOS=$(echo "$CRYPTEX_PATHS" | tail -1)
|
||||
echo " SystemOS: $CRYPTEX_SYSOS"
|
||||
|
||||
@@ -227,7 +227,7 @@ mkdir -p "$TEMP_DIR"
|
||||
# ── Parse Cryptex paths from BuildManifest ─────────────────────
|
||||
echo ""
|
||||
echo "[*] Parsing iPhone BuildManifest for Cryptex paths..."
|
||||
CRYPTEX_PATHS=$("$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" cryptex-paths "$RESTORE_DIR/BuildManifest-iPhone.plist")
|
||||
CRYPTEX_PATHS=$("$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" cryptex-paths "$RESTORE_DIR/iPhone-BuildManifest.plist")
|
||||
CRYPTEX_SYSOS=$(echo "$CRYPTEX_PATHS" | head -1)
|
||||
CRYPTEX_APPOS=$(echo "$CRYPTEX_PATHS" | tail -1)
|
||||
echo " SystemOS: $CRYPTEX_SYSOS"
|
||||
|
||||
@@ -576,7 +576,7 @@ cp "${CLOUDOS_DIR}"/Firmware/*.im4p "$IPHONE_DIR/Firmware"/
|
||||
cp -n "${CLOUDOS_DIR}"/*.dmg "$IPHONE_DIR"/ 2>/dev/null || true
|
||||
cp -n "${CLOUDOS_DIR}"/Firmware/*.dmg.trustcache "$IPHONE_DIR/Firmware"/ 2>/dev/null || true
|
||||
|
||||
cp "$IPHONE_DIR/BuildManifest.plist" "$IPHONE_DIR/BuildManifest-iPhone.plist"
|
||||
cp "$IPHONE_DIR/BuildManifest.plist" "$IPHONE_DIR/iPhone-BuildManifest.plist"
|
||||
|
||||
echo "==> Generating hybrid plists ..."
|
||||
python3 "$SCRIPT_DIR/fw_manifest.py" "$IPHONE_DIR" "$CLOUDOS_DIR"
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
diff --git a/src/libirecovery.c b/src/libirecovery.c
|
||||
index bf9a0d6..1323891 100644
|
||||
--- a/src/libirecovery.c
|
||||
+++ b/src/libirecovery.c
|
||||
@@ -480,6 +480,8 @@ static struct irecv_device irecv_devices[] = {
|
||||
/* Apple Vision Pro */
|
||||
{ "RealityDevice14,1", "n301ap", 0x42, 0x8112, "Apple Vision Pro" },
|
||||
{ "RealityDevice17,1", "n301aap", 0x42, 0x8142, "Apple Vision Pro (M5)" },
|
||||
+ /* Private Cloud Compute Research Environment */
|
||||
+ { "iPhone99,11", "vresearch101ap", 0x90, 0xFE01, "iPhone 99,11" },
|
||||
{ NULL, NULL, -1, -1, NULL }
|
||||
};
|
||||
|
||||
299
scripts/pymobiledevice3_bridge.py
Executable file
299
scripts/pymobiledevice3_bridge.py
Executable file
@@ -0,0 +1,299 @@
|
||||
import asyncio
|
||||
import inspect
|
||||
import plistlib
|
||||
import sys
|
||||
import time
|
||||
from collections.abc import Awaitable
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from ipsw_parser.ipsw import IPSW
|
||||
from pymobiledevice3 import usbmux
|
||||
from pymobiledevice3.exceptions import (
|
||||
ConnectionFailedError,
|
||||
ConnectionFailedToUsbmuxdError,
|
||||
IRecvNoDeviceConnectedError,
|
||||
IncorrectModeError,
|
||||
)
|
||||
from pymobiledevice3.irecv import IRecv
|
||||
from pymobiledevice3.lockdown import create_using_usbmux
|
||||
from pymobiledevice3.restore.device import Device
|
||||
from pymobiledevice3.restore.recovery import Behavior, Recovery
|
||||
from pymobiledevice3.restore.restore import Restore
|
||||
import typer
|
||||
|
||||
|
||||
def parse_ecid(value: Optional[str]) -> Optional[int]:
|
||||
if not value:
|
||||
return None
|
||||
raw = value.strip().lower()
|
||||
if raw.startswith("0x"):
|
||||
raw = raw[2:]
|
||||
if not raw:
|
||||
raise ValueError("ECID is empty")
|
||||
if any(c not in "0123456789abcdef" for c in raw):
|
||||
raise ValueError(f"Invalid ECID: {value}")
|
||||
return int(raw, 16)
|
||||
|
||||
|
||||
def normalize_udid(value: Optional[str]) -> Optional[str]:
|
||||
return None if value is None else value.strip().upper()
|
||||
|
||||
|
||||
def find_restore_dir(vm_dir: Path) -> Path:
|
||||
candidates = sorted(p for p in vm_dir.glob("iPhone*_Restore") if p.is_dir())
|
||||
if not candidates:
|
||||
raise FileNotFoundError(f"No iPhone*_Restore directory found in {vm_dir}")
|
||||
if len(candidates) > 1:
|
||||
raise RuntimeError(
|
||||
"Multiple iPhone*_Restore directories found; keep only one active restore tree"
|
||||
)
|
||||
return candidates[0]
|
||||
|
||||
|
||||
async def resolve_device(ecid: Optional[int], udid: Optional[str]) -> Device:
|
||||
udid_normalized = normalize_udid(udid)
|
||||
|
||||
try:
|
||||
devices = [d for d in await usbmux.list_devices() if d.connection_type == "USB"]
|
||||
except ConnectionFailedToUsbmuxdError:
|
||||
devices = []
|
||||
|
||||
for usb_device in devices:
|
||||
serial = normalize_udid(getattr(usb_device, "serial", None))
|
||||
if udid_normalized and serial != udid_normalized:
|
||||
continue
|
||||
|
||||
try:
|
||||
lockdown = await create_using_usbmux(serial=usb_device.serial, connection_type="USB")
|
||||
except (ConnectionFailedError, IncorrectModeError):
|
||||
continue
|
||||
|
||||
lockdown_ecid = int(str(lockdown.ecid), 0)
|
||||
if ecid is not None and lockdown_ecid != ecid:
|
||||
continue
|
||||
|
||||
return Device(lockdown=lockdown)
|
||||
|
||||
if ecid is None and udid_normalized is not None:
|
||||
raise RuntimeError(
|
||||
"Target UDID not available over usbmux in lockdownd mode and ECID is unset; "
|
||||
"set RESTORE_ECID for DFU/Recovery targeting"
|
||||
)
|
||||
|
||||
return Device(irecv=IRecv(ecid=ecid))
|
||||
|
||||
|
||||
async def cmd_usbmux_list(usb_only: bool) -> None:
|
||||
devices = await usbmux.list_devices()
|
||||
for device in devices:
|
||||
if usb_only and getattr(device, "connection_type", None) != "USB":
|
||||
continue
|
||||
serial = getattr(device, "serial", None)
|
||||
if serial:
|
||||
print(serial)
|
||||
|
||||
|
||||
def wait_for_irecv(ecid: Optional[int], timeout: int, is_recovery: Optional[bool] = None) -> IRecv:
|
||||
deadline = time.monotonic() + timeout
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
return IRecv(ecid=ecid, timeout=2, is_recovery=is_recovery)
|
||||
except (IRecvNoDeviceConnectedError, ValueError):
|
||||
time.sleep(1)
|
||||
mode_label = "recovery" if is_recovery else "dfu/recovery"
|
||||
raise TimeoutError(f"Timed out waiting for {mode_label} endpoint")
|
||||
|
||||
|
||||
def irecv_send_file(irecv: IRecv, image_path: Path) -> None:
|
||||
data = image_path.read_bytes()
|
||||
irecv.send_buffer(data)
|
||||
|
||||
|
||||
def resolve_kernel_image(ramdisk_dir: Path) -> Path:
|
||||
ramdisk_variant = ramdisk_dir / "krnl.ramdisk.img4"
|
||||
if ramdisk_variant.exists():
|
||||
return ramdisk_variant
|
||||
default_kernel = ramdisk_dir / "krnl.img4"
|
||||
if default_kernel.exists():
|
||||
return default_kernel
|
||||
raise FileNotFoundError(f"Kernel image not found in {ramdisk_dir}")
|
||||
|
||||
|
||||
def cmd_ramdisk_send(ecid: Optional[int], ramdisk_dir: Path, timeout: int) -> None:
|
||||
if not ramdisk_dir.is_dir():
|
||||
raise FileNotFoundError(f"Ramdisk directory not found: {ramdisk_dir}")
|
||||
|
||||
kernel_img = resolve_kernel_image(ramdisk_dir)
|
||||
|
||||
print(f"[*] Sending ramdisk from {ramdisk_dir}")
|
||||
if kernel_img.name == "krnl.ramdisk.img4":
|
||||
print(" [*] Using ramdisk kernel variant: krnl.ramdisk.img4")
|
||||
|
||||
irecv = wait_for_irecv(ecid, timeout=timeout, is_recovery=False)
|
||||
|
||||
# 1) DFU stage: iBSS + iBEC, then switch to recovery.
|
||||
print(" [1/8] Loading iBSS...")
|
||||
irecv_send_file(irecv, ramdisk_dir / "iBSS.vresearch101.RELEASE.img4")
|
||||
|
||||
print(" [2/8] Loading iBEC...")
|
||||
irecv_send_file(irecv, ramdisk_dir / "iBEC.vresearch101.RELEASE.img4")
|
||||
irecv.send_command("go", b_request=1)
|
||||
time.sleep(1)
|
||||
|
||||
print(" [*] Waiting for device to reconnect in recovery...")
|
||||
irecv = wait_for_irecv(ecid, timeout=timeout, is_recovery=True)
|
||||
print(" [*] Reconnected in recovery")
|
||||
|
||||
# 2) Recovery stage payload chain.
|
||||
print(" [3/8] Loading SPTM...")
|
||||
irecv_send_file(irecv, ramdisk_dir / "sptm.vresearch1.release.img4")
|
||||
irecv.send_command("firmware")
|
||||
|
||||
print(" [4/8] Loading TXM...")
|
||||
irecv_send_file(irecv, ramdisk_dir / "txm.img4")
|
||||
irecv.send_command("firmware")
|
||||
|
||||
print(" [5/8] Loading trustcache...")
|
||||
irecv_send_file(irecv, ramdisk_dir / "trustcache.img4")
|
||||
irecv.send_command("firmware")
|
||||
|
||||
print(" [6/8] Loading ramdisk...")
|
||||
irecv_send_file(irecv, ramdisk_dir / "ramdisk.img4")
|
||||
time.sleep(2)
|
||||
irecv.send_command("ramdisk")
|
||||
|
||||
print(" [7/8] Loading device tree...")
|
||||
irecv_send_file(irecv, ramdisk_dir / "DeviceTree.vphone600ap.img4")
|
||||
irecv.send_command("devicetree")
|
||||
|
||||
print(" [8/8] Loading SEP...")
|
||||
irecv_send_file(irecv, ramdisk_dir / "sep-firmware.vresearch101.RELEASE.img4")
|
||||
irecv.send_command("firmware")
|
||||
|
||||
print(" [*] Booting kernel...")
|
||||
irecv_send_file(irecv, kernel_img)
|
||||
irecv.send_command("bootx", b_request=1)
|
||||
|
||||
print("[+] Boot sequence complete. Device should be booting into ramdisk.")
|
||||
|
||||
|
||||
def derive_shsh_output(vm_dir: Path, ecid: Optional[int]) -> Path:
|
||||
tag = f"{ecid:016X}" if ecid is not None else "auto"
|
||||
return vm_dir / f"{tag}.shsh"
|
||||
|
||||
|
||||
async def cmd_restore_get_shsh(
|
||||
vm_dir: Path, ecid: Optional[int], udid: Optional[str], out: Optional[Path]
|
||||
) -> None:
|
||||
restore_dir = find_restore_dir(vm_dir)
|
||||
ipsw = IPSW.create_from_path(str(restore_dir))
|
||||
device = await resolve_device(ecid, udid)
|
||||
tss = await Recovery(ipsw, device, behavior=Behavior.Erase).fetch_tss_record()
|
||||
|
||||
out_path = out or derive_shsh_output(vm_dir, device.get_ecid_value())
|
||||
with out_path.open("wb") as handle:
|
||||
plistlib.dump(tss, handle)
|
||||
|
||||
print(f"[+] SHSH saved: {out_path}")
|
||||
|
||||
|
||||
async def cmd_restore_update(vm_dir: Path, ecid: Optional[int], udid: Optional[str], erase: bool) -> None:
|
||||
restore_dir = find_restore_dir(vm_dir)
|
||||
ipsw = IPSW.create_from_path(str(restore_dir))
|
||||
behavior = Behavior.Erase if erase else Behavior.Update
|
||||
device = await resolve_device(ecid, udid)
|
||||
await Restore(ipsw, device, behavior=behavior, ignore_fdr=False).update()
|
||||
|
||||
|
||||
def require_ecid(value: str) -> Optional[int]:
|
||||
try:
|
||||
return parse_ecid(value)
|
||||
except ValueError as exc:
|
||||
raise typer.BadParameter(str(exc)) from exc
|
||||
|
||||
|
||||
app = typer.Typer(help="pymobiledevice3 bridge for vphone", pretty_exceptions_enable=False)
|
||||
|
||||
|
||||
@app.command("usbmux-list", help="List usbmux UDIDs")
|
||||
def usbmux_list_command(
|
||||
usb_only: bool = typer.Option(
|
||||
True,
|
||||
"--usb-only/--no-usb-only",
|
||||
help="Include network devices with --no-usb-only.",
|
||||
),
|
||||
) -> Awaitable[None]:
|
||||
return cmd_usbmux_list(usb_only=usb_only)
|
||||
|
||||
|
||||
@app.command("recovery-probe", help="Probe for DFU/recovery endpoint")
|
||||
def recovery_probe_command(
|
||||
ecid: Optional[str] = typer.Option(None, help="Hex ECID (with/without 0x)"),
|
||||
timeout: int = typer.Option(2, help="Probe timeout in seconds"),
|
||||
) -> None:
|
||||
parsed_ecid = require_ecid(ecid)
|
||||
wait_for_irecv(parsed_ecid, timeout=timeout)
|
||||
|
||||
|
||||
@app.command("ramdisk-send", help="Send ramdisk chain over irecv")
|
||||
def ramdisk_send_command(
|
||||
ecid: Optional[str] = typer.Option(None, help="Hex ECID (with/without 0x)"),
|
||||
timeout: int = typer.Option(90, help="Send timeout in seconds"),
|
||||
ramdisk_dir: Path = typer.Option(
|
||||
Path("Ramdisk"),
|
||||
help="Ramdisk directory",
|
||||
exists=False,
|
||||
file_okay=False,
|
||||
dir_okay=True,
|
||||
),
|
||||
) -> None:
|
||||
cmd_ramdisk_send(require_ecid(ecid), ramdisk_dir, timeout)
|
||||
|
||||
|
||||
@app.command("restore-get-shsh", help="Fetch SHSH from prepared restore dir")
|
||||
def restore_get_shsh_command(
|
||||
vm_dir: Path = typer.Option(
|
||||
Path("."),
|
||||
help="VM directory",
|
||||
exists=False,
|
||||
file_okay=False,
|
||||
dir_okay=True,
|
||||
),
|
||||
ecid: Optional[str] = typer.Option(None, help="Hex ECID (with/without 0x)"),
|
||||
udid: Optional[str] = typer.Option(None, help="Target USB UDID"),
|
||||
out: Optional[Path] = typer.Option(
|
||||
None,
|
||||
help="Output SHSH path",
|
||||
exists=False,
|
||||
file_okay=True,
|
||||
dir_okay=False,
|
||||
),
|
||||
) -> Awaitable[None]:
|
||||
return cmd_restore_get_shsh(vm_dir, require_ecid(ecid), udid, out)
|
||||
|
||||
|
||||
@app.command("restore-update", help="Run erase/update restore from prepared dir")
|
||||
def restore_update_command(
|
||||
vm_dir: Path = typer.Option(
|
||||
Path("."),
|
||||
help="VM directory",
|
||||
exists=False,
|
||||
file_okay=False,
|
||||
dir_okay=True,
|
||||
),
|
||||
ecid: Optional[str] = typer.Option(None, help="Hex ECID (with/without 0x)"),
|
||||
udid: Optional[str] = typer.Option(None, help="Target USB UDID"),
|
||||
erase: bool = typer.Option(True, "--erase/--no-erase", help="Run update-in-place with --no-erase."),
|
||||
) -> Awaitable[None]:
|
||||
return cmd_restore_update(vm_dir, require_ecid(ecid), udid, erase=erase)
|
||||
|
||||
|
||||
async def main(argv: list[str]) -> None:
|
||||
result = app(args=argv, prog_name="pymobiledevice3_bridge.py", standalone_mode=False)
|
||||
if inspect.isawaitable(result):
|
||||
await result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main(sys.argv[1:]))
|
||||
@@ -634,8 +634,7 @@ def main():
|
||||
sys.exit(1)
|
||||
|
||||
# Find SHSH
|
||||
shsh_dir = os.path.join(vm_dir, "shsh")
|
||||
shsh_path = find_shsh(shsh_dir)
|
||||
shsh_path = find_shsh(vm_dir)
|
||||
if not shsh_path:
|
||||
print(f"[-] No SHSH blob found in {shsh_dir}/")
|
||||
print(" Place your .shsh file in the shsh/ directory.")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/zsh
|
||||
# ramdisk_send.sh — Send signed ramdisk components to device via irecovery.
|
||||
# ramdisk_send.sh — Send signed ramdisk components to device via pymobiledevice3.
|
||||
#
|
||||
# Usage: ./ramdisk_send.sh [ramdisk_dir]
|
||||
#
|
||||
@@ -7,12 +7,13 @@
|
||||
# SPTM, TXM, trustcache, ramdisk, device tree, SEP, and kernel.
|
||||
set -euo pipefail
|
||||
|
||||
IRECOVERY="${IRECOVERY:-irecovery}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PMD3_BRIDGE="${PMD3_BRIDGE:-${SCRIPT_DIR}/pymobiledevice3_bridge.py}"
|
||||
PYTHON="${PYTHON:-python3}"
|
||||
IRECOVERY_ECID="${IRECOVERY_ECID:-}"
|
||||
RAMDISK_UDID="${RAMDISK_UDID:-${RESTORE_UDID:-}}"
|
||||
RAMDISK_DIR="${1:-Ramdisk}"
|
||||
|
||||
IRECOVERY_ARGS=()
|
||||
if [[ -n "$IRECOVERY_ECID" ]]; then
|
||||
IRECOVERY_ECID="${IRECOVERY_ECID#0x}"
|
||||
IRECOVERY_ECID="${IRECOVERY_ECID#0X}"
|
||||
@@ -21,8 +22,7 @@ if [[ -n "$IRECOVERY_ECID" ]]; then
|
||||
exit 1
|
||||
}
|
||||
IRECOVERY_ECID="0x${IRECOVERY_ECID:u}"
|
||||
IRECOVERY_ARGS=(-i "$IRECOVERY_ECID")
|
||||
echo "[*] Using ECID selector for irecovery: ${IRECOVERY_ECID}"
|
||||
echo "[*] Using ECID selector for ramdisk send: ${IRECOVERY_ECID}"
|
||||
fi
|
||||
|
||||
echo "[*] Identity context for ramdisk_send:"
|
||||
@@ -37,72 +37,19 @@ else
|
||||
echo " ECID: <unset>"
|
||||
fi
|
||||
|
||||
irecovery_cmd() {
|
||||
"$IRECOVERY" "${IRECOVERY_ARGS[@]}" "$@"
|
||||
}
|
||||
|
||||
if [[ ! -d "$RAMDISK_DIR" ]]; then
|
||||
echo "[-] Ramdisk directory not found: $RAMDISK_DIR"
|
||||
echo " Run 'make ramdisk_build' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[*] Sending ramdisk from $RAMDISK_DIR ..."
|
||||
|
||||
KERNEL_IMG="$RAMDISK_DIR/krnl.img4"
|
||||
if [[ -f "$RAMDISK_DIR/krnl.ramdisk.img4" ]]; then
|
||||
KERNEL_IMG="$RAMDISK_DIR/krnl.ramdisk.img4"
|
||||
echo " [*] Using ramdisk kernel variant: $(basename "$KERNEL_IMG")"
|
||||
fi
|
||||
[[ -f "$KERNEL_IMG" ]] || {
|
||||
echo "[-] Kernel image not found: $KERNEL_IMG"
|
||||
if [[ ! -f "$PMD3_BRIDGE" ]]; then
|
||||
echo "[-] pymobiledevice3 bridge script not found: $PMD3_BRIDGE"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 1. Load iBSS + iBEC (DFU → recovery)
|
||||
echo " [1/8] Loading iBSS..."
|
||||
irecovery_cmd -f "$RAMDISK_DIR/iBSS.vresearch101.RELEASE.img4"
|
||||
|
||||
echo " [2/8] Loading iBEC..."
|
||||
irecovery_cmd -f "$RAMDISK_DIR/iBEC.vresearch101.RELEASE.img4"
|
||||
irecovery_cmd -c go
|
||||
|
||||
sleep 1
|
||||
|
||||
# 2. Load SPTM
|
||||
echo " [3/8] Loading SPTM..."
|
||||
irecovery_cmd -f "$RAMDISK_DIR/sptm.vresearch1.release.img4"
|
||||
irecovery_cmd -c firmware
|
||||
|
||||
# 3. Load TXM
|
||||
echo " [4/8] Loading TXM..."
|
||||
irecovery_cmd -f "$RAMDISK_DIR/txm.img4"
|
||||
irecovery_cmd -c firmware
|
||||
|
||||
# 4. Load trustcache
|
||||
echo " [5/8] Loading trustcache..."
|
||||
irecovery_cmd -f "$RAMDISK_DIR/trustcache.img4"
|
||||
irecovery_cmd -c firmware
|
||||
|
||||
# 5. Load ramdisk
|
||||
echo " [6/8] Loading ramdisk..."
|
||||
irecovery_cmd -f "$RAMDISK_DIR/ramdisk.img4"
|
||||
sleep 2
|
||||
irecovery_cmd -c ramdisk
|
||||
|
||||
# 6. Load device tree
|
||||
echo " [7/8] Loading device tree..."
|
||||
irecovery_cmd -f "$RAMDISK_DIR/DeviceTree.vphone600ap.img4"
|
||||
irecovery_cmd -c devicetree
|
||||
|
||||
# 7. Load SEP
|
||||
echo " [8/8] Loading SEP..."
|
||||
irecovery_cmd -f "$RAMDISK_DIR/sep-firmware.vresearch101.RELEASE.img4"
|
||||
irecovery_cmd -c firmware
|
||||
|
||||
# 8. Load kernel and boot
|
||||
echo " [*] Booting kernel..."
|
||||
irecovery_cmd -f "$KERNEL_IMG"
|
||||
irecovery_cmd -c bootx
|
||||
|
||||
echo "[+] Boot sequence complete. Device should be booting into ramdisk."
|
||||
fi
|
||||
echo "[*] Using pymobiledevice3 backend for ramdisk send"
|
||||
cmd=("$PYTHON" "$PMD3_BRIDGE" ramdisk-send --ramdisk-dir "$RAMDISK_DIR")
|
||||
if [[ -n "$IRECOVERY_ECID" ]]; then
|
||||
cmd+=(--ecid "$IRECOVERY_ECID")
|
||||
fi
|
||||
"${cmd[@]}"
|
||||
|
||||
Submodule scripts/repos/idevicerestore deleted from 405fcd1794
Submodule scripts/repos/libimobiledevice deleted from c4f111800d
Submodule scripts/repos/libimobiledevice-glue deleted from da770a7687
Submodule scripts/repos/libirecovery deleted from b59ef48145
Submodule scripts/repos/libplist deleted from 6e03a1df6d
Submodule scripts/repos/libtatsu deleted from 60a39f36d7
Submodule scripts/repos/libusbmuxd deleted from 93eb168bf6
Submodule scripts/repos/libzip deleted from f2bbf19d9f
@@ -1,165 +0,0 @@
|
||||
#!/bin/bash
|
||||
# setup_libimobiledevice.sh — Build libimobiledevice toolchain (static)
|
||||
#
|
||||
# Produces: idevicerestore, irecovery, and related idevice* tools
|
||||
# Prefix: .limd/ (override with LIMD_PREFIX env var)
|
||||
# Source: scripts/repos/* git submodules (staged into .limd/src before build)
|
||||
# Requires: autoconf automake libtool pkg-config cmake git
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
REPOS_DIR="$SCRIPT_DIR/repos"
|
||||
|
||||
PREFIX="${LIMD_PREFIX:-$PROJECT_DIR/.limd}"
|
||||
SRC="$PREFIX/src"
|
||||
LOG="$PREFIX/log"
|
||||
|
||||
NPROC="$(sysctl -n hw.logicalcpu 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)"
|
||||
SDKROOT="$(xcrun --sdk macosx --show-sdk-path)"
|
||||
|
||||
OPENSSL_PREFIX="$(brew --prefix openssl@3 2>/dev/null || true)"
|
||||
[[ -d "$OPENSSL_PREFIX" ]] || {
|
||||
echo "[-] openssl@3 not found. Run: brew install openssl@3" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig:$OPENSSL_PREFIX/lib/pkgconfig"
|
||||
export CFLAGS="-mmacosx-version-min=14.0 -isysroot $SDKROOT"
|
||||
export CPPFLAGS="$CFLAGS"
|
||||
export LDFLAGS="-mmacosx-version-min=14.0"
|
||||
|
||||
mkdir -p "$SRC" "$LOG"
|
||||
|
||||
# ── Helpers ──────────────────────────────────────────────────────
|
||||
|
||||
die() {
|
||||
echo "[-] $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
check_tools() {
|
||||
local missing=()
|
||||
for cmd in autoconf automake pkg-config cmake git patch; do
|
||||
command -v "$cmd" &>/dev/null || missing+=("$cmd")
|
||||
done
|
||||
command -v glibtoolize &>/dev/null || command -v libtoolize &>/dev/null ||
|
||||
missing+=("libtool(ize)")
|
||||
((${#missing[@]} == 0)) || die "Missing: ${missing[*]} — brew install ${missing[*]}"
|
||||
}
|
||||
|
||||
ensure_repo_submodule() {
|
||||
local rel_path="$1"
|
||||
local abs_path="$PROJECT_DIR/$rel_path"
|
||||
|
||||
if [[ ! -e "$abs_path/.git" ]]; then
|
||||
git -C "$PROJECT_DIR" submodule update --init --recursive "$rel_path"
|
||||
fi
|
||||
}
|
||||
|
||||
stage_repo_source() {
|
||||
local name="$1"
|
||||
local src_dir="$REPOS_DIR/$name"
|
||||
local dst_dir="$SRC/$name"
|
||||
local version=""
|
||||
|
||||
ensure_repo_submodule "scripts/repos/$name"
|
||||
rm -rf "$dst_dir"
|
||||
ditto "$src_dir" "$dst_dir"
|
||||
|
||||
# Some autotools projects expect either git metadata or .tarball-version.
|
||||
# Staged sources are intentionally detached from git, so preserve version info.
|
||||
version="$(git -C "$src_dir" describe --tags --always 2>/dev/null || true)"
|
||||
if [[ -n "$version" ]]; then
|
||||
printf "%s\n" "$version" >"$dst_dir/.tarball-version"
|
||||
fi
|
||||
|
||||
rm -rf "$dst_dir/.git"
|
||||
}
|
||||
|
||||
build_lib() {
|
||||
local name="$1"
|
||||
shift
|
||||
echo " $name"
|
||||
cd "$SRC/$name"
|
||||
./autogen.sh --prefix="$PREFIX" \
|
||||
--enable-shared=no --enable-static=yes \
|
||||
"$@" >"$LOG/$name-configure.log" 2>&1
|
||||
make -j"$NPROC" >"$LOG/$name-build.log" 2>&1
|
||||
make install >"$LOG/$name-install.log" 2>&1
|
||||
cd "$SRC"
|
||||
}
|
||||
|
||||
# ── Preflight ────────────────────────────────────────────────────
|
||||
|
||||
check_tools
|
||||
echo "Building libimobiledevice toolchain → $PREFIX"
|
||||
echo ""
|
||||
echo "Using submodule sources from scripts/repos/"
|
||||
echo ""
|
||||
|
||||
# ── 1. Core libraries ───────────────────────────────────────────
|
||||
|
||||
echo "[1/3] Core libraries (using homebrew openssl@3)"
|
||||
for lib in libplist libimobiledevice-glue libusbmuxd libtatsu libimobiledevice; do
|
||||
stage_repo_source "$lib"
|
||||
case "$lib" in
|
||||
libplist | libimobiledevice) build_lib "$lib" --without-cython ;;
|
||||
*) build_lib "$lib" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# ── 2. libirecovery (+ PCC research VM patch) ───────────────────
|
||||
|
||||
echo "[2/3] libirecovery + libzip"
|
||||
stage_repo_source "libirecovery"
|
||||
|
||||
# PR #150: register iPhone99,11 / vresearch101ap for PCC research VMs
|
||||
if ! grep -q 'vresearch101ap' "$SRC/libirecovery/src/libirecovery.c"; then
|
||||
if ! (cd "$SRC/libirecovery" && patch -p1 --batch --forward --dry-run <"$SCRIPT_DIR/patches/libirecovery-pcc-vm.patch" >/dev/null); then
|
||||
die "Failed to validate libirecovery PCC patch — check context"
|
||||
fi
|
||||
if ! (cd "$SRC/libirecovery" && patch -p1 --batch --forward <"$SCRIPT_DIR/patches/libirecovery-pcc-vm.patch" >"$LOG/libirecovery-pcc-vm.patch.log" 2>&1); then
|
||||
die "Failed to apply libirecovery PCC patch — see $LOG/libirecovery-pcc-vm.patch.log"
|
||||
fi
|
||||
grep -q 'vresearch101ap' "$SRC/libirecovery/src/libirecovery.c" ||
|
||||
die "libirecovery PCC patch command succeeded but expected marker is still missing"
|
||||
fi
|
||||
build_lib libirecovery
|
||||
|
||||
# ── libzip (static, for idevicerestore, from submodule) ───────────
|
||||
|
||||
if [[ ! -f "$PREFIX/lib/pkgconfig/libzip.pc" ]]; then
|
||||
echo " libzip"
|
||||
stage_repo_source "libzip"
|
||||
cmake -S "$SRC/libzip" -B "$SRC/libzip/build" \
|
||||
-DCMAKE_INSTALL_PREFIX="$PREFIX" -DCMAKE_OSX_SYSROOT="$SDKROOT" \
|
||||
-DBUILD_SHARED_LIBS=OFF -DBUILD_DOC=OFF -DBUILD_EXAMPLES=OFF \
|
||||
-DBUILD_REGRESS=OFF -DBUILD_TOOLS=OFF \
|
||||
-DENABLE_BZIP2=OFF -DENABLE_LZMA=OFF -DENABLE_ZSTD=OFF \
|
||||
-DENABLE_GNUTLS=OFF -DENABLE_MBEDTLS=OFF -DENABLE_OPENSSL=OFF \
|
||||
>"$LOG/libzip-cmake.log" 2>&1
|
||||
cmake --build "$SRC/libzip/build" -j"$NPROC" \
|
||||
>"$LOG/libzip-build.log" 2>&1
|
||||
cmake --install "$SRC/libzip/build" \
|
||||
>"$LOG/libzip-install.log" 2>&1
|
||||
fi
|
||||
|
||||
# ── 3. idevicerestore ───────────────────────────────────────────
|
||||
|
||||
echo "[3/3] idevicerestore"
|
||||
stage_repo_source "idevicerestore"
|
||||
build_lib idevicerestore \
|
||||
libcurl_CFLAGS="-I$SDKROOT/usr/include" \
|
||||
libcurl_LIBS="-lcurl" \
|
||||
libcurl_VERSION="$(/usr/bin/curl-config --version | cut -d' ' -f2)" \
|
||||
zlib_CFLAGS="-I$SDKROOT/usr/include" \
|
||||
zlib_LIBS="-lz" \
|
||||
zlib_VERSION="1.2"
|
||||
|
||||
# ── Done ─────────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "Installed to $PREFIX/bin/:"
|
||||
ls "$PREFIX/bin/" | sed 's/^/ /'
|
||||
@@ -53,6 +53,7 @@ BOOT_ANALYSIS_TIMEOUT="${BOOT_ANALYSIS_TIMEOUT:-300}"
|
||||
BOOT_PROMPT_FALLBACK_TIMEOUT="${BOOT_PROMPT_FALLBACK_TIMEOUT:-60}"
|
||||
BOOT_BASH_PROMPT_REGEX="${BOOT_BASH_PROMPT_REGEX:-bash-[0-9]+(\.[0-9]+)+#}"
|
||||
BOOT_PANIC_REGEX="${BOOT_PANIC_REGEX:-panic|kernel panic|panic\\.apple\\.com|stackshot succeeded}"
|
||||
PMD3_BRIDGE="${PMD3_BRIDGE:-${PROJECT_ROOT}/scripts/pymobiledevice3_bridge.py}"
|
||||
NONE_INTERACTIVE_RAW="${NONE_INTERACTIVE:-0}"
|
||||
NONE_INTERACTIVE=0
|
||||
JB_MODE=0
|
||||
@@ -69,6 +70,22 @@ require_cmd() {
|
||||
command -v "$cmd" >/dev/null 2>&1 || die "Missing required command: $cmd"
|
||||
}
|
||||
|
||||
find_python_for_pmd3() {
|
||||
local candidate
|
||||
for candidate in \
|
||||
"${PROJECT_ROOT}/.venv/bin/python3" \
|
||||
"$(command -v python3 2>/dev/null || true)"
|
||||
do
|
||||
[[ -n "$candidate" ]] || continue
|
||||
[[ -x "$candidate" ]] || continue
|
||||
if "$candidate" -c "import pymobiledevice3" >/dev/null 2>&1; then
|
||||
echo "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
normalize_ecid() {
|
||||
local ecid="$1"
|
||||
ecid="${ecid#0x}"
|
||||
@@ -124,15 +141,11 @@ load_device_identity() {
|
||||
}
|
||||
|
||||
list_usbmux_udids() {
|
||||
local idevice_id_bin
|
||||
idevice_id_bin="${PROJECT_ROOT}/.limd/bin/idevice_id"
|
||||
|
||||
if [[ ! -x "$idevice_id_bin" ]]; then
|
||||
idevice_id_bin="$(command -v idevice_id || true)"
|
||||
fi
|
||||
[[ -x "$idevice_id_bin" ]] || return 0
|
||||
|
||||
"$idevice_id_bin" -l 2>/dev/null | tr -d '\r' | sed '/^[[:space:]]*$/d'
|
||||
local pmd3_python
|
||||
pmd3_python="$(find_python_for_pmd3 || true)"
|
||||
[[ -x "$pmd3_python" ]] || die "pymobiledevice3 python runtime not found (run: make setup_tools)"
|
||||
[[ -f "$PMD3_BRIDGE" ]] || die "Missing bridge script: $PMD3_BRIDGE"
|
||||
"$pmd3_python" "$PMD3_BRIDGE" usbmux-list 2>/dev/null | tr -d '\r' | sed '/^[[:space:]]*$/d'
|
||||
}
|
||||
|
||||
print_usbmux_udids() {
|
||||
@@ -685,8 +698,8 @@ install_brew_deps() {
|
||||
require_cmd brew
|
||||
|
||||
local deps=(
|
||||
ideviceinstaller wget gnu-tar openssl@3 ldid-procursus sshpass keystone autoconf automake pkg-config libtool git-lfs
|
||||
python@3.13
|
||||
wget gnu-tar openssl@3 ldid-procursus sshpass keystone git-lfs
|
||||
python@3.13 libusb ipsw
|
||||
)
|
||||
|
||||
echo "=== Installing Homebrew dependencies ==="
|
||||
@@ -787,20 +800,15 @@ 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
|
||||
local pmd3_python
|
||||
pmd3_python="$(find_python_for_pmd3 || true)"
|
||||
[[ -x "$pmd3_python" ]] || die "pymobiledevice3 python runtime not found (run: make setup_tools)"
|
||||
[[ -f "$PMD3_BRIDGE" ]] || die "Missing bridge script: $PMD3_BRIDGE"
|
||||
|
||||
echo "[*] Waiting for recovery/DFU endpoint..."
|
||||
local i
|
||||
for i in {1..90}; do
|
||||
if "$irecovery" "${query_args[@]}" -q >/dev/null 2>&1; then
|
||||
if "$pmd3_python" "$PMD3_BRIDGE" recovery-probe --ecid "0x${DEVICE_ECID}" --timeout 2 >/dev/null 2>&1; then
|
||||
echo "[+] Device endpoint is reachable"
|
||||
return
|
||||
fi
|
||||
@@ -813,9 +821,6 @@ wait_for_recovery() {
|
||||
}
|
||||
|
||||
start_iproxy() {
|
||||
local iproxy_bin
|
||||
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 resolve iproxy target"
|
||||
|
||||
choose_ramdisk_ssh_port
|
||||
@@ -833,8 +838,11 @@ start_iproxy() {
|
||||
mkdir -p "$LOG_DIR"
|
||||
: > "$IPROXY_LOG"
|
||||
|
||||
echo "[*] Starting iproxy ${RAMDISK_SSH_PORT} -> 22 (target_udid=${IPROXY_TARGET_UDID}, restore_udid=${DEVICE_UDID}, ecid=0x${DEVICE_ECID})..."
|
||||
("$iproxy_bin" -u "$IPROXY_TARGET_UDID" "$RAMDISK_SSH_PORT" 22 >"$IPROXY_LOG" 2>&1) &
|
||||
local pmd3_python
|
||||
pmd3_python="$(find_python_for_pmd3 || true)"
|
||||
[[ -x "$pmd3_python" ]] || die "pymobiledevice3 python runtime not found (run: make setup_tools)"
|
||||
echo "[*] Starting pymobiledevice3 usbmux forward ${RAMDISK_SSH_PORT} -> 22 (target_udid=${IPROXY_TARGET_UDID}, restore_udid=${DEVICE_UDID}, ecid=0x${DEVICE_ECID})..."
|
||||
("$pmd3_python" -m pymobiledevice3 usbmux forward --serial "$IPROXY_TARGET_UDID" "$RAMDISK_SSH_PORT" 22 >"$IPROXY_LOG" 2>&1) &
|
||||
IPROXY_PID=$!
|
||||
|
||||
sleep 1
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
# setup_tools.sh — Install all required host tools for vphone-cli
|
||||
#
|
||||
# Installs brew packages, builds trustcache from source,
|
||||
# builds insert_dylib from submodule source, builds libimobiledevice toolchain, and creates Python venv.
|
||||
# builds insert_dylib from submodule source, and creates Python venv
|
||||
# (including pymobiledevice3 restore/usbmux tooling).
|
||||
#
|
||||
# Run: make setup_tools
|
||||
|
||||
@@ -24,7 +25,7 @@ ensure_repo_submodule() {
|
||||
|
||||
# ── Brew packages ──────────────────────────────────────────────
|
||||
|
||||
echo "[1/5] Checking brew packages..."
|
||||
echo "[1/4] Checking brew packages..."
|
||||
|
||||
BREW_PACKAGES=(aria2 gnu-tar openssl@3 ldid-procursus sshpass)
|
||||
BREW_MISSING=()
|
||||
@@ -44,7 +45,7 @@ fi
|
||||
|
||||
# ── Trustcache ─────────────────────────────────────────────────
|
||||
|
||||
echo "[2/5] trustcache"
|
||||
echo "[2/4] trustcache"
|
||||
|
||||
TRUSTCACHE_BIN="$TOOLS_PREFIX/bin/trustcache"
|
||||
if [[ -x "$TRUSTCACHE_BIN" ]]; then
|
||||
@@ -73,7 +74,7 @@ fi
|
||||
|
||||
# ── insert_dylib ───────────────────────────────────────────────
|
||||
|
||||
echo "[3/5] insert_dylib"
|
||||
echo "[3/4] insert_dylib"
|
||||
|
||||
INSERT_DYLIB_BIN="$TOOLS_PREFIX/bin/insert_dylib"
|
||||
if [[ -x "$INSERT_DYLIB_BIN" ]]; then
|
||||
@@ -87,14 +88,9 @@ else
|
||||
echo " Installed: $INSERT_DYLIB_BIN"
|
||||
fi
|
||||
|
||||
# ── Libimobiledevice ──────────────────────────────────────────
|
||||
|
||||
echo "[4/5] libimobiledevice"
|
||||
bash "$SCRIPT_DIR/setup_libimobiledevice.sh"
|
||||
|
||||
# ── Python venv ────────────────────────────────────────────────
|
||||
|
||||
echo "[5/5] Python venv"
|
||||
echo "[4/4] Python venv"
|
||||
zsh "$SCRIPT_DIR/setup_venv.sh"
|
||||
|
||||
echo ""
|
||||
|
||||
@@ -73,9 +73,11 @@ python3 -c "
|
||||
from capstone import Cs, CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN
|
||||
from keystone import Ks, KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN
|
||||
from pyimg4 import IM4P
|
||||
import pymobiledevice3
|
||||
print(' capstone OK')
|
||||
print(' keystone OK')
|
||||
print(' pyimg4 OK')
|
||||
print(' pmd3 OK')
|
||||
"
|
||||
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user