mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-09-05 17:14:28 +00:00
Fix Patchless for cloudOS 26.4 (#336)
* Fix Patchless for cloudOS 26.4 by dynamically matching and downloading the required apfs_sealvolume version. * patchless: Add `seal.log` for `apfs_sealvolume` command --------- Co-authored-by: zqxwce <[email protected]>
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
# - direct iPhone IPSW URLs or local file paths
|
# - direct iPhone IPSW URLs or local file paths
|
||||||
# - version/build selectors for the target device
|
# - version/build selectors for the target device
|
||||||
# - listing of all downloadable IPSWs for the target device
|
# - listing of all downloadable IPSWs for the target device
|
||||||
|
# - The "VARIANT" environment variable to download apfs_sealvolume for the patchless mode
|
||||||
#
|
#
|
||||||
# Listing and selection are resolved through the `ipsw` CLI already used
|
# Listing and selection are resolved through the `ipsw` CLI already used
|
||||||
# elsewhere in this repo, so the script can work with the full downloadable
|
# elsewhere in this repo, so the script can work with the full downloadable
|
||||||
@@ -410,6 +411,99 @@ extract() {
|
|||||||
cp -R "$cache" "$out"
|
cp -R "$cache" "$out"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
download_apfs_sealvolume() {
|
||||||
|
local src="$1"
|
||||||
|
local base ios_version filename PROJECT_DIR TOOLS_PREFIX TMP_DIR bn ver BUILD BUILD_MANIFEST RAMDISK_PATH RAMDISK_IM4P RAMDISK MOUNT
|
||||||
|
|
||||||
|
base="$(basename "$src")"
|
||||||
|
ios_version="$(awk -F_ 'NF >= 2 { print $2 }' <<<"$base")"
|
||||||
|
|
||||||
|
if [[ -z "$ios_version" ]]; then
|
||||||
|
echo "Error: could not determine iOS version from filename: $base" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
filename="apfs_sealvolume_${ios_version}"
|
||||||
|
|
||||||
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
TOOLS_PREFIX="$PROJECT_DIR/.tools"
|
||||||
|
|
||||||
|
if [[ -f "$TOOLS_PREFIX/$filename" ]]; then
|
||||||
|
echo "$filename already present"
|
||||||
|
else
|
||||||
|
echo "Downloading $filename"
|
||||||
|
(
|
||||||
|
TMP_DIR="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||||
|
|
||||||
|
# List matching macOS version to the iOS version
|
||||||
|
while IFS= read -r url; do
|
||||||
|
bn="$(basename "$url")"
|
||||||
|
ver="${bn#*_}"
|
||||||
|
ver="${ver%%_*}"
|
||||||
|
|
||||||
|
[[ "$ver" == "$ios_version" ]] || continue
|
||||||
|
|
||||||
|
BUILD="$(awk -F_ '{print $3}' <<<"$bn")"
|
||||||
|
break
|
||||||
|
done < <(
|
||||||
|
ipsw download appledb \
|
||||||
|
--os macOS \
|
||||||
|
--version $ios_version \
|
||||||
|
--urls
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ -z "${BUILD:-}" ]]; then
|
||||||
|
echo "Error: failed to determine macOS build from available URLs" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Download BuildManifest first
|
||||||
|
ipsw download appledb \
|
||||||
|
--os macOS \
|
||||||
|
--build $BUILD \
|
||||||
|
--pattern "^BuildManifest.plist\$" \
|
||||||
|
--output "$TMP_DIR"
|
||||||
|
|
||||||
|
BUILD_MANIFEST="$(find "$TMP_DIR" -name BuildManifest.plist -print -quit)"
|
||||||
|
if [ -z "$BUILD_MANIFEST" ]; then
|
||||||
|
echo "Failed to locate BuildManifest.plist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
RAMDISK_PATH="$(/usr/bin/plutil -extract 'BuildIdentities.0.Manifest.RestoreRamDisk.Info.Path' raw -o - "$BUILD_MANIFEST")"
|
||||||
|
if [ -z "$RAMDISK_PATH" ]; then
|
||||||
|
echo "Failed to read RestoreRamDisk path from BuildManifest"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Download the ramdisk referenced by BuildManifest
|
||||||
|
ipsw download appledb \
|
||||||
|
--os macOS \
|
||||||
|
--build $BUILD \
|
||||||
|
--pattern "$RAMDISK_PATH" \
|
||||||
|
--output "$TMP_DIR"
|
||||||
|
|
||||||
|
RAMDISK_IM4P="$(find "$TMP_DIR" -path "*${RAMDISK_PATH}" -print -quit)"
|
||||||
|
if [ -z "$RAMDISK_IM4P" ]; then
|
||||||
|
echo "Failed to locate downloaded ramdisk: $RAMDISK_PATH"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
RAMDISK="$TMP_DIR/ramdisk.dmg"
|
||||||
|
ipsw img4 im4p extract --output "$RAMDISK" "$RAMDISK_IM4P"
|
||||||
|
|
||||||
|
MOUNT=$(hdiutil attach -readonly -nobrowse "$RAMDISK" | awk 'END{ print$NF}')
|
||||||
|
cp "$MOUNT/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs_sealvolume" \
|
||||||
|
"$TOOLS_PREFIX/$filename"
|
||||||
|
hdiutil detach "$MOUNT" >/dev/null 2>&1 || true
|
||||||
|
)
|
||||||
|
echo " Downloaded: $TOOLS_PREFIX/$filename"
|
||||||
|
echo " Resigning $filename"
|
||||||
|
codesign --force --sign - "$TOOLS_PREFIX/$filename"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
LIST_FIRMWARES="${LIST_FIRMWARES:-0}"
|
LIST_FIRMWARES="${LIST_FIRMWARES:-0}"
|
||||||
IPHONE_DEVICE="${IPHONE_DEVICE:-$DEFAULT_IPHONE_DEVICE}"
|
IPHONE_DEVICE="${IPHONE_DEVICE:-$DEFAULT_IPHONE_DEVICE}"
|
||||||
IPHONE_VERSION="${IPHONE_VERSION:-}"
|
IPHONE_VERSION="${IPHONE_VERSION:-}"
|
||||||
@@ -537,6 +631,13 @@ echo ""
|
|||||||
fetch "$IPHONE_SOURCE" "$IPHONE_IPSW_PATH"
|
fetch "$IPHONE_SOURCE" "$IPHONE_IPSW_PATH"
|
||||||
fetch "$CLOUDOS_SOURCE" "$CLOUDOS_IPSW_PATH"
|
fetch "$CLOUDOS_SOURCE" "$CLOUDOS_IPSW_PATH"
|
||||||
|
|
||||||
|
VARIANT="${VARIANT:-}"
|
||||||
|
if [[ "$VARIANT" == "less" ]]; then
|
||||||
|
download_apfs_sealvolume "$IPHONE_SOURCE"
|
||||||
|
else
|
||||||
|
echo "==> Downloading apfs sealvolume (skipped — patchless variant only)"
|
||||||
|
fi
|
||||||
|
|
||||||
IPHONE_CACHE="${IPSW_DIR}/${IPHONE_DIR}"
|
IPHONE_CACHE="${IPSW_DIR}/${IPHONE_DIR}"
|
||||||
CLOUDOS_CACHE="${IPSW_DIR}/${CLOUDOS_DIR}"
|
CLOUDOS_CACHE="${IPSW_DIR}/${CLOUDOS_DIR}"
|
||||||
|
|
||||||
|
|||||||
@@ -1053,11 +1053,7 @@ main() {
|
|||||||
install_brew_deps
|
install_brew_deps
|
||||||
ensure_python_linked
|
ensure_python_linked
|
||||||
|
|
||||||
if [[ "$LESS_MODE" -eq 1 ]]; then
|
run_make "Project setup" setup_tools
|
||||||
VARIANT=less run_make "Project setup" setup_tools
|
|
||||||
else
|
|
||||||
run_make "Project setup" setup_tools
|
|
||||||
fi
|
|
||||||
run_make "Project setup" build
|
run_make "Project setup" build
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -1066,10 +1062,11 @@ main() {
|
|||||||
export PATH="$PROJECT_ROOT/.venv/bin:$PATH"
|
export PATH="$PROJECT_ROOT/.venv/bin:$PATH"
|
||||||
|
|
||||||
run_make "Firmware prep" vm_new
|
run_make "Firmware prep" vm_new
|
||||||
run_make "Firmware prep" fw_prepare
|
|
||||||
if [[ "$LESS_MODE" -eq 0 ]]; then
|
if [[ "$LESS_MODE" -eq 0 ]]; then
|
||||||
|
run_make "Firmware prep" fw_prepare
|
||||||
run_make "Firmware patch" "$fw_patch_target"
|
run_make "Firmware patch" "$fw_patch_target"
|
||||||
else
|
else
|
||||||
|
VARIANT=less run_make "Firmware prep" fw_prepare
|
||||||
run_make_sudo "Firmware patch" "$fw_patch_target"
|
run_make_sudo "Firmware patch" "$fw_patch_target"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
+4
-37
@@ -25,7 +25,7 @@ ensure_repo_submodule() {
|
|||||||
|
|
||||||
# ── Brew packages ──────────────────────────────────────────────
|
# ── Brew packages ──────────────────────────────────────────────
|
||||||
|
|
||||||
echo "[1/5] Checking brew packages..."
|
echo "[1/4] Checking brew packages..."
|
||||||
|
|
||||||
BREW_PACKAGES=(aria2 gnu-tar openssl@3 ldid-procursus sshpass zstd)
|
BREW_PACKAGES=(aria2 gnu-tar openssl@3 ldid-procursus sshpass zstd)
|
||||||
BREW_MISSING=()
|
BREW_MISSING=()
|
||||||
@@ -45,7 +45,7 @@ fi
|
|||||||
|
|
||||||
# ── Trustcache ─────────────────────────────────────────────────
|
# ── Trustcache ─────────────────────────────────────────────────
|
||||||
|
|
||||||
echo "[2/5] trustcache"
|
echo "[2/4] trustcache"
|
||||||
|
|
||||||
TRUSTCACHE_BIN="$TOOLS_PREFIX/bin/trustcache"
|
TRUSTCACHE_BIN="$TOOLS_PREFIX/bin/trustcache"
|
||||||
if [[ -x "$TRUSTCACHE_BIN" ]]; then
|
if [[ -x "$TRUSTCACHE_BIN" ]]; then
|
||||||
@@ -74,7 +74,7 @@ fi
|
|||||||
|
|
||||||
# ── insert_dylib ───────────────────────────────────────────────
|
# ── insert_dylib ───────────────────────────────────────────────
|
||||||
|
|
||||||
echo "[3/5] insert_dylib"
|
echo "[3/4] insert_dylib"
|
||||||
|
|
||||||
INSERT_DYLIB_BIN="$TOOLS_PREFIX/bin/insert_dylib"
|
INSERT_DYLIB_BIN="$TOOLS_PREFIX/bin/insert_dylib"
|
||||||
if [[ -x "$INSERT_DYLIB_BIN" ]]; then
|
if [[ -x "$INSERT_DYLIB_BIN" ]]; then
|
||||||
@@ -90,41 +90,8 @@ fi
|
|||||||
|
|
||||||
# ── Python venv ────────────────────────────────────────────────
|
# ── Python venv ────────────────────────────────────────────────
|
||||||
|
|
||||||
echo "[4/5] Python venv"
|
echo "[4/4] Python venv"
|
||||||
zsh "$SCRIPT_DIR/setup_venv.sh"
|
zsh "$SCRIPT_DIR/setup_venv.sh"
|
||||||
|
|
||||||
# ── APFS sealvolume (patchless variant only) ──────────────────────
|
|
||||||
|
|
||||||
VARIANT="${VARIANT:-}"
|
|
||||||
|
|
||||||
if [[ "$VARIANT" == "less" ]]; then
|
|
||||||
echo "[5/5] apfs sealvolume"
|
|
||||||
if [[ -f "$TOOLS_PREFIX/apfs_sealvolume" ]]; then
|
|
||||||
echo " apfs_sealvolume already present"
|
|
||||||
else
|
|
||||||
TMP_DIR="$(mktemp -d)"
|
|
||||||
ipsw download appledb \
|
|
||||||
--os macOS \
|
|
||||||
--build 25D2140 \
|
|
||||||
--pattern "094-33864-054.dmg" \
|
|
||||||
--output "$TMP_DIR"
|
|
||||||
|
|
||||||
RAMDISK_IM4P="$TMP_DIR/25D2140__MacOS/094-33864-054.dmg"
|
|
||||||
RAMDISK="$TMP_DIR/ramdisk.dmg"
|
|
||||||
ipsw img4 im4p extract --output "$RAMDISK" "$RAMDISK_IM4P"
|
|
||||||
|
|
||||||
MOUNT=$(hdiutil attach -readonly -nobrowse "$RAMDISK" | awk 'END{ print$NF}')
|
|
||||||
cp "$MOUNT/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs_sealvolume" \
|
|
||||||
"$TOOLS_PREFIX/apfs_sealvolume"
|
|
||||||
hdiutil detach "$MOUNT" >/dev/null 2>&1 || true
|
|
||||||
rm -rf "$TMP_DIR"
|
|
||||||
echo " Downloaded: $TOOLS_PREFIX/apfs_sealvolume"
|
|
||||||
echo " Resigning apfs_sealvolume"
|
|
||||||
codesign --force --sign - "$TOOLS_PREFIX/apfs_sealvolume"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "[5/5] apfs sealvolume (skipped — patchless variant only)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "All tools installed."
|
echo "All tools installed."
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import Img4tool
|
|||||||
|
|
||||||
enum ProcessError: Error {
|
enum ProcessError: Error {
|
||||||
case failed(Int32, String)
|
case failed(Int32, String)
|
||||||
|
case notExecutable(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Data {
|
extension Data {
|
||||||
@@ -394,6 +395,18 @@ public final class CryptexFilesystemPatcher: Patcher {
|
|||||||
return im4pPath
|
return im4pPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func identifyApfsSealvolume() throws -> URL {
|
||||||
|
let iosVersion = try getProductVersion()
|
||||||
|
let path = self.vphoneCliDirectory.appending(path: ".tools/apfs_sealvolume_\(iosVersion)")
|
||||||
|
guard FileManager.default.fileExists(atPath: path.path) else {
|
||||||
|
throw FirmwareManifest.ManifestError.fileNotFound(path.path)
|
||||||
|
}
|
||||||
|
guard FileManager.default.isExecutableFile(atPath: path.path) else {
|
||||||
|
throw ProcessError.notExecutable(path.path)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
func createDigestAndHash(filesystem: URL, mtree: URL, remap: Bool) throws -> (URL, URL) {
|
func createDigestAndHash(filesystem: URL, mtree: URL, remap: Bool) throws -> (URL, URL) {
|
||||||
let (device, mount) = try attachImage(path: filesystem)
|
let (device, mount) = try attachImage(path: filesystem)
|
||||||
defer { try? detachImage(deviceNode: device) }
|
defer { try? detachImage(deviceNode: device) }
|
||||||
@@ -402,6 +415,7 @@ public final class CryptexFilesystemPatcher: Patcher {
|
|||||||
let digestDbPath = tmpDir.appending(path: "digest.db")
|
let digestDbPath = tmpDir.appending(path: "digest.db")
|
||||||
let rootHashPath = tmpDir.appending(path: "root_hash")
|
let rootHashPath = tmpDir.appending(path: "root_hash")
|
||||||
let mtreeRemapPath = tmpDir.appending(path: "mtree_remap.xml")
|
let mtreeRemapPath = tmpDir.appending(path: "mtree_remap.xml")
|
||||||
|
let sealLogPath = tmpDir.appending(path: "seal.log")
|
||||||
|
|
||||||
// We want to get the nanosecond timestamp of the last modification before the mtree collection.
|
// We want to get the nanosecond timestamp of the last modification before the mtree collection.
|
||||||
// We know that we remove directories in /private/var in removeSpecificSystemFiles last.
|
// We know that we remove directories in /private/var in removeSpecificSystemFiles last.
|
||||||
@@ -423,13 +437,14 @@ public final class CryptexFilesystemPatcher: Patcher {
|
|||||||
FileManager.default.createFile(atPath: mtreeRemapPath.path, contents: remapContent.data(using: .utf8))
|
FileManager.default.createFile(atPath: mtreeRemapPath.path, contents: remapContent.data(using: .utf8))
|
||||||
|
|
||||||
try unmount(mount: mount)
|
try unmount(mount: mount)
|
||||||
let sealvolume = self.vphoneCliDirectory.appending(path: ".tools/apfs_sealvolume")
|
let sealvolume = try identifyApfsSealvolume()
|
||||||
|
FileManager.default.createFile(atPath: sealLogPath.path, contents: nil)
|
||||||
_ = try runProcess(sealvolume.path, [
|
_ = try runProcess(sealvolume.path, [
|
||||||
"-R", mtreeRemapPath.path,
|
"-R", mtreeRemapPath.path,
|
||||||
"-U", digestDbPath.path, // Save digest records
|
"-U", digestDbPath.path, // Save digest records
|
||||||
"-M", rootHashPath.path, // Save root hash
|
"-M", rootHashPath.path, // Save root hash
|
||||||
device
|
device
|
||||||
])
|
], output: sealLogPath)
|
||||||
return (digestDbPath, rootHashPath)
|
return (digestDbPath, rootHashPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -615,6 +630,14 @@ public final class CryptexFilesystemPatcher: Patcher {
|
|||||||
return remaining.joined(separator: "/")
|
return remaining.joined(separator: "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getProductVersion() throws -> String {
|
||||||
|
let root = try parsePlist(data: buildManiest)
|
||||||
|
guard let productVersion = root["ProductVersion"] as? String else {
|
||||||
|
throw FirmwareManifest.ManifestError.missingKey("ProductVersion in BuildManifest")
|
||||||
|
}
|
||||||
|
return productVersion
|
||||||
|
}
|
||||||
|
|
||||||
func getTrustcachePath() throws -> String {
|
func getTrustcachePath() throws -> String {
|
||||||
let path = self.restoreDir.appending(path: "iPhone-BuildManifest.plist")
|
let path = self.restoreDir.appending(path: "iPhone-BuildManifest.plist")
|
||||||
let manifest = try getBuildIdentityManifest(path: path)
|
let manifest = try getBuildIdentityManifest(path: path)
|
||||||
|
|||||||
Reference in New Issue
Block a user