From c3547202d9f00ab378cebe1e864e349098a536ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Pa=C3=9F?= <22845248+mpass99@users.noreply.github.com> Date: Thu, 21 May 2026 15:14:34 +0200 Subject: [PATCH 1/3] Fix Patchless for cloudOS 26.4 by dynamically matching and downloading the required apfs_sealvolume version. --- scripts/fw_prepare.sh | 88 +++++++++++++++++++ scripts/setup_machine.sh | 9 +- scripts/setup_tools.sh | 41 +-------- .../Filesystem/CryptexFilesystemPatcher.swift | 15 +++- 4 files changed, 109 insertions(+), 44 deletions(-) diff --git a/scripts/fw_prepare.sh b/scripts/fw_prepare.sh index 81f2dab..23545b6 100755 --- a/scripts/fw_prepare.sh +++ b/scripts/fw_prepare.sh @@ -410,6 +410,87 @@ extract() { cp -R "$cache" "$out" } +download_apfs_sealvolume() { + local src="$1" + IOS_VERSION="$(basename "$src" | awk -F_ '{print $2}')" + 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)" + + # 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 + + selected_src="$url" + BUILD="$(awk -F_ '{print $3}' <<<"$bn")" + break + done < <( + ipsw download appledb \ + --os macOS \ + --version $IOS_VERSION \ + --urls + ) + + # 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" + rm -rf "$TMP_DIR" + 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" + rm -rf "$TMP_DIR" + 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" + rm -rf "$TMP_DIR" + 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 + rm -rf "$TMP_DIR" + echo " Downloaded: $TOOLS_PREFIX/$filename" + echo " Resigning $filename" + codesign --force --sign - "$TOOLS_PREFIX/$filename" + fi +} + LIST_FIRMWARES="${LIST_FIRMWARES:-0}" IPHONE_DEVICE="${IPHONE_DEVICE:-$DEFAULT_IPHONE_DEVICE}" IPHONE_VERSION="${IPHONE_VERSION:-}" @@ -537,6 +618,13 @@ echo "" fetch "$IPHONE_SOURCE" "$IPHONE_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}" CLOUDOS_CACHE="${IPSW_DIR}/${CLOUDOS_DIR}" diff --git a/scripts/setup_machine.sh b/scripts/setup_machine.sh index b794a64..33d827a 100755 --- a/scripts/setup_machine.sh +++ b/scripts/setup_machine.sh @@ -1053,11 +1053,7 @@ main() { install_brew_deps ensure_python_linked - if [[ "$LESS_MODE" -eq 1 ]]; then - VARIANT=less run_make "Project setup" setup_tools - else - run_make "Project setup" setup_tools - fi + run_make "Project setup" setup_tools run_make "Project setup" build fi @@ -1066,10 +1062,11 @@ main() { export PATH="$PROJECT_ROOT/.venv/bin:$PATH" run_make "Firmware prep" vm_new - run_make "Firmware prep" fw_prepare if [[ "$LESS_MODE" -eq 0 ]]; then + run_make "Firmware prep" fw_prepare run_make "Firmware patch" "$fw_patch_target" else + VARIANT=less run_make "Firmware prep" fw_prepare run_make_sudo "Firmware patch" "$fw_patch_target" fi diff --git a/scripts/setup_tools.sh b/scripts/setup_tools.sh index 05484cd..d6d3b1b 100755 --- a/scripts/setup_tools.sh +++ b/scripts/setup_tools.sh @@ -25,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 zstd) BREW_MISSING=() @@ -45,7 +45,7 @@ fi # ── Trustcache ───────────────────────────────────────────────── -echo "[2/5] trustcache" +echo "[2/4] trustcache" TRUSTCACHE_BIN="$TOOLS_PREFIX/bin/trustcache" if [[ -x "$TRUSTCACHE_BIN" ]]; then @@ -74,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 @@ -90,41 +90,8 @@ fi # ── Python venv ──────────────────────────────────────────────── -echo "[4/5] Python venv" +echo "[4/4] Python venv" 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 "All tools installed." diff --git a/sources/FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift b/sources/FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift index 74b6683..d596d8d 100644 --- a/sources/FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift +++ b/sources/FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift @@ -394,6 +394,11 @@ public final class CryptexFilesystemPatcher: Patcher { return im4pPath } + func identify_apfs_sealvolume() throws -> URL { + let iosVersion = try getProductVersion() + return self.vphoneCliDirectory.appending(path: ".tools/apfs_sealvolume_\(iosVersion)") + } + func createDigestAndHash(filesystem: URL, mtree: URL, remap: Bool) throws -> (URL, URL) { let (device, mount) = try attachImage(path: filesystem) defer { try? detachImage(deviceNode: device) } @@ -423,7 +428,7 @@ public final class CryptexFilesystemPatcher: Patcher { FileManager.default.createFile(atPath: mtreeRemapPath.path, contents: remapContent.data(using: .utf8)) try unmount(mount: mount) - let sealvolume = self.vphoneCliDirectory.appending(path: ".tools/apfs_sealvolume") + let sealvolume = try identify_apfs_sealvolume() _ = try runProcess(sealvolume.path, [ "-R", mtreeRemapPath.path, "-U", digestDbPath.path, // Save digest records @@ -615,6 +620,14 @@ public final class CryptexFilesystemPatcher: Patcher { return remaining.joined(separator: "/") } + func getProductVersion() throws -> String { + var 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 { let path = self.restoreDir.appending(path: "iPhone-BuildManifest.plist") let manifest = try getBuildIdentityManifest(path: path) From ad943941e92498ecc1b98d30dc23f3246674bf83 Mon Sep 17 00:00:00 2001 From: zqxwce Date: Sun, 24 May 2026 13:21:25 +0300 Subject: [PATCH 2/3] patchless: Add `seal.log` for `apfs_sealvolume` command --- .../FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sources/FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift b/sources/FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift index d596d8d..8efceee 100644 --- a/sources/FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift +++ b/sources/FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift @@ -407,6 +407,7 @@ public final class CryptexFilesystemPatcher: Patcher { let digestDbPath = tmpDir.appending(path: "digest.db") let rootHashPath = tmpDir.appending(path: "root_hash") 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 know that we remove directories in /private/var in removeSpecificSystemFiles last. @@ -429,12 +430,13 @@ public final class CryptexFilesystemPatcher: Patcher { try unmount(mount: mount) let sealvolume = try identify_apfs_sealvolume() + FileManager.default.createFile(atPath: sealLogPath.path, contents: nil) _ = try runProcess(sealvolume.path, [ "-R", mtreeRemapPath.path, "-U", digestDbPath.path, // Save digest records "-M", rootHashPath.path, // Save root hash device - ]) + ], output: sealLogPath) return (digestDbPath, rootHashPath) } From 1dd5f2b248f61a0f4969f8508214ecdc7eb8869a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Pa=C3=9F?= <22845248+mpass99@users.noreply.github.com> Date: Wed, 27 May 2026 15:46:17 +0200 Subject: [PATCH 3/3] Address review comments --- scripts/fw_prepare.sh | 139 ++++++++++-------- .../Filesystem/CryptexFilesystemPatcher.swift | 16 +- 2 files changed, 88 insertions(+), 67 deletions(-) diff --git a/scripts/fw_prepare.sh b/scripts/fw_prepare.sh index 23545b6..821cc69 100755 --- a/scripts/fw_prepare.sh +++ b/scripts/fw_prepare.sh @@ -6,6 +6,7 @@ # - direct iPhone IPSW URLs or local file paths # - version/build selectors 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 # elsewhere in this repo, so the script can work with the full downloadable @@ -412,8 +413,17 @@ extract() { download_apfs_sealvolume() { local src="$1" - IOS_VERSION="$(basename "$src" | awk -F_ '{print $2}')" - filename="apfs_sealvolume_$IOS_VERSION" + 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" @@ -422,69 +432,72 @@ download_apfs_sealvolume() { echo "$filename already present" else echo "Downloading $filename" - TMP_DIR="$(mktemp -d)" - - # 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 - - selected_src="$url" - BUILD="$(awk -F_ '{print $3}' <<<"$bn")" - break - done < <( - ipsw download appledb \ - --os macOS \ - --version $IOS_VERSION \ - --urls - ) - - # 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" - rm -rf "$TMP_DIR" - 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" - rm -rf "$TMP_DIR" - 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" - rm -rf "$TMP_DIR" - exit 1 - fi + ( + 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" + 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 - rm -rf "$TMP_DIR" + 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" diff --git a/sources/FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift b/sources/FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift index 8efceee..d9a938e 100644 --- a/sources/FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift +++ b/sources/FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift @@ -15,6 +15,7 @@ import Img4tool enum ProcessError: Error { case failed(Int32, String) + case notExecutable(String) } extension Data { @@ -394,9 +395,16 @@ public final class CryptexFilesystemPatcher: Patcher { return im4pPath } - func identify_apfs_sealvolume() throws -> URL { + private func identifyApfsSealvolume() throws -> URL { let iosVersion = try getProductVersion() - return self.vphoneCliDirectory.appending(path: ".tools/apfs_sealvolume_\(iosVersion)") + 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) { @@ -429,7 +437,7 @@ public final class CryptexFilesystemPatcher: Patcher { FileManager.default.createFile(atPath: mtreeRemapPath.path, contents: remapContent.data(using: .utf8)) try unmount(mount: mount) - let sealvolume = try identify_apfs_sealvolume() + let sealvolume = try identifyApfsSealvolume() FileManager.default.createFile(atPath: sealLogPath.path, contents: nil) _ = try runProcess(sealvolume.path, [ "-R", mtreeRemapPath.path, @@ -623,7 +631,7 @@ public final class CryptexFilesystemPatcher: Patcher { } func getProductVersion() throws -> String { - var root = try parsePlist(data: buildManiest) + let root = try parsePlist(data: buildManiest) guard let productVersion = root["ProductVersion"] as? String else { throw FirmwareManifest.ManifestError.missingKey("ProductVersion in BuildManifest") }