#!/usr/bin/env python3 """ build_ramdisk.py — Build a signed SSH ramdisk for vphone600. Expects the VM restore tree to have already been patched by the Swift firmware pipeline. Extracts patched components, signs with SHSH, and builds SSH ramdisk. Usage: python3 build_ramdisk.py [vm_directory] Directory structure: ./shsh/ — SHSH blobs (auto-discovered) ./ramdisk_input/ — Tools and SSH resources (auto-setup from CFW) ./ramdisk_builder_temp/ — Intermediate .raw files (cleaned up) ./Ramdisk/ — Final signed IMG4 output Prerequisites: pip install pyimg4 Run make fw_patch / make fw_patch_dev / make fw_patch_jb first to patch boot-chain components. """ import gzip import glob import os import plistlib import shutil import subprocess import sys import tempfile from pyimg4 import IM4M, IM4P, IMG4 _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) # ══════════════════════════════════════════════════════════════════ # Configuration # ══════════════════════════════════════════════════════════════════ OUTPUT_DIR = "Ramdisk" TEMP_DIR = "ramdisk_builder_temp" INPUT_DIR = "ramdisk_input" RESTORED_EXTERNAL_PATH = "usr/local/bin/restored_external" RESTORED_EXTERNAL_SERIAL_MARKER = b"SSHRD_Script Sep 22 2022 18:56:50" DEFAULT_IBEC_BOOT_ARGS = b"serial=3 -v debug=0x2014e %s" # Ramdisk boot-args RAMDISK_BOOT_ARGS = b"serial=3 rd=md0 debug=0x2014e -v wdt=-1 %s" # IM4P fourccs for restore mode TXM_FOURCC = "trxm" KERNEL_FOURCC = "rkrn" RAMDISK_KERNEL_SUFFIX = ".ramdisk" RAMDISK_KERNEL_IMG4 = "krnl.ramdisk.img4" SUDO_PASSWORD = os.environ.get("VPHONE_SUDO_PASSWORD", None) # Files to remove from ramdisk to save space RAMDISK_REMOVE = [ "usr/bin/img4tool", "usr/bin/img4", "usr/sbin/dietappleh13camerad", "usr/sbin/dietappleh16camerad", "usr/local/bin/wget", "usr/local/bin/procexp", ] # Directories to re-sign in ramdisk SIGN_DIRS = [ "usr/local/bin/*", "usr/local/lib/*", "usr/bin/*", "bin/*", "usr/lib/*", "sbin/*", "usr/sbin/*", "usr/libexec/*", ] # Compressed archive of ramdisk_input/ (located next to this script) INPUT_ARCHIVE = "ramdisk_input.tar.zst" PATCHER_BINARY_ENV = "VPHONE_PATCHER_BINARY" # ══════════════════════════════════════════════════════════════════ # Setup — extract ramdisk_input/ from zstd archive if needed # ══════════════════════════════════════════════════════════════════ def setup_input(vm_dir): """Ensure ramdisk_input/ exists, extracting from .tar.zst if needed.""" input_dir = os.path.join(vm_dir, INPUT_DIR) if os.path.isdir(input_dir): return input_dir # Look for archive next to this script, then in vm_dir for search_dir in (os.path.join(_SCRIPT_DIR, "resources"), _SCRIPT_DIR, vm_dir): archive = os.path.join(search_dir, INPUT_ARCHIVE) if os.path.isfile(archive): print(f" Extracting {INPUT_ARCHIVE}...") subprocess.run( ["tar", "--zstd", "-xf", archive, "-C", vm_dir], check=True, ) return input_dir print(f"[-] Neither {INPUT_DIR}/ nor {INPUT_ARCHIVE} found.") print(f" Place {INPUT_ARCHIVE} next to this script or in the VM directory.") sys.exit(1) # ══════════════════════════════════════════════════════════════════ # SHSH / signing helpers # ══════════════════════════════════════════════════════════════════ def find_shsh(shsh_dir): """Find first SHSH blob in directory.""" for ext in ("*.shsh", "*.shsh2"): matches = sorted(glob.glob(os.path.join(shsh_dir, ext))) if matches: return matches[0] return None def extract_im4m(shsh_path, im4m_path): """Extract IM4M manifest from SHSH blob (handles gzip-compressed).""" raw = open(shsh_path, "rb").read() if raw[:2] == b"\x1f\x8b": raw = gzip.decompress(raw) tmp = shsh_path + ".tmp" try: open(tmp, "wb").write(raw) subprocess.run( ["pyimg4", "im4m", "extract", "-i", tmp, "-o", im4m_path], check=True, capture_output=True, ) finally: if os.path.exists(tmp): os.remove(tmp) def sign_img4(im4p_path, img4_path, im4m_path, tag=None): """Create IMG4 from IM4P + IM4M using pyimg4 Python API.""" im4p = IM4P(open(im4p_path, "rb").read()) if tag: im4p.fourcc = tag im4m = IM4M(open(im4m_path, "rb").read()) img4 = IMG4(im4p=im4p, im4m=im4m) with open(img4_path, "wb") as f: f.write(img4.output()) def run(cmd, **kwargs): """Run a command, raising on failure.""" return subprocess.run(cmd, check=True, **kwargs) def run_sudo(cmd, **kwargs): """Run sudo command non-interactively using VPHONE_SUDO_PASSWORD.""" if SUDO_PASSWORD: return run( ["sudo", "-S", *cmd], input=f"{SUDO_PASSWORD}\n", text=True, **kwargs, ) return run(["sudo", *cmd], **kwargs) def ensure_path_within_vm(path, vm_dir, label): """Fail if path escapes vm_dir.""" vm_real = os.path.realpath(vm_dir) path_real = os.path.realpath(path) if path_real == vm_real or path_real.startswith(vm_real + os.sep): return print(f"[-] {label} must be inside VM dir") print(f" VM dir: {vm_real}") print(f" Path: {path_real}") sys.exit(1) def check_prerequisites(): """Verify required host tools are available.""" missing = [] for tool, pkg in [("gtar", "gnu-tar"), ("ldid", "ldid-procursus"), ("trustcache", "trustcache (make setup_tools)")]: if not shutil.which(tool): missing.append(f" {tool:12s} — {pkg}") if missing: print("[-] Missing required tools:") for m in missing: print(m) print("\n Run: make setup_tools") sys.exit(1) def project_root(): return os.path.abspath(os.path.join(_SCRIPT_DIR, "..")) def patcher_binary_path(): override = os.environ.get(PATCHER_BINARY_ENV, "").strip() if override: return os.path.abspath(override) return os.path.join(project_root(), ".build", "debug", "vphone-cli") def run_swift_patch_component(component, src_path, output_path): """Patch a single component via the Swift FirmwarePatcher CLI.""" binary = patcher_binary_path() if not os.path.isfile(binary): print(f"[-] Swift patcher binary not found: {binary}") print(" Run: make patcher_build") sys.exit(1) run( [ binary, "patch-component", "--component", component, "--input", src_path, "--output", output_path, "--quiet", ] ) def load_firmware(path): """Load firmware file, auto-detecting IM4P vs raw.""" with open(path, "rb") as f: raw = f.read() try: im4p = IM4P(raw) if im4p.payload.compression: im4p.payload.decompress() return im4p, bytearray(im4p.payload.data), True, raw except Exception: return None, bytearray(raw), False, raw def _save_im4p_with_payp(path, fourcc, patched_data, original_raw): """Repackage as LZFSE-compressed IM4P and append PAYP from original.""" with ( tempfile.NamedTemporaryFile(suffix=".raw", delete=False) as tmp_raw, tempfile.NamedTemporaryFile(suffix=".im4p", delete=False) as tmp_im4p, ): tmp_raw_path = tmp_raw.name tmp_im4p_path = tmp_im4p.name tmp_raw.write(bytes(patched_data)) try: subprocess.run( [ "pyimg4", "im4p", "create", "-i", tmp_raw_path, "-o", tmp_im4p_path, "-f", fourcc, "--lzfse", ], check=True, capture_output=True, ) output = bytearray(open(tmp_im4p_path, "rb").read()) finally: os.unlink(tmp_raw_path) os.unlink(tmp_im4p_path) payp_offset = original_raw.rfind(b"PAYP") if payp_offset >= 0: payp_data = original_raw[payp_offset - 10 :] output.extend(payp_data) old_len = int.from_bytes(output[2:5], "big") output[2:5] = (old_len + len(payp_data)).to_bytes(3, "big") print(f" [+] preserved PAYP ({len(payp_data)} bytes)") with open(path, "wb") as f: f.write(output) def find_restore_dir(base_dir): for entry in sorted(os.listdir(base_dir)): full = os.path.join(base_dir, entry) if os.path.isdir(full) and "Restore" in entry: return full return None def find_file(base_dir, patterns, label): for pattern in patterns: matches = sorted(glob.glob(os.path.join(base_dir, pattern))) if matches: return matches[0] print(f"[-] {label} not found. Searched patterns:") for pattern in patterns: print(f" {os.path.join(base_dir, pattern)}") sys.exit(1) # ══════════════════════════════════════════════════════════════════ # Firmware extraction and IM4P creation # ══════════════════════════════════════════════════════════════════ def extract_to_raw(src_path, raw_path): """Extract IM4P payload to .raw file. Returns (im4p_obj, data, original_raw).""" im4p, data, was_im4p, original_raw = load_firmware(src_path) with open(raw_path, "wb") as f: f.write(bytes(data)) return im4p, data, original_raw def create_im4p_uncompressed(raw_data, fourcc, description, output_path): """Create uncompressed IM4P from raw data.""" new_im4p = IM4P( fourcc=fourcc, description=description, payload=bytes(raw_data), ) with open(output_path, "wb") as f: f.write(new_im4p.output()) def build_kernel_img4(kernel_src, output_dir, temp_dir, im4m_path, output_name, temp_tag): """Build one signed kernel IMG4 from a kernelcache source file.""" kc_raw = os.path.join(temp_dir, f"{temp_tag}.raw") kc_im4p = os.path.join(temp_dir, f"{temp_tag}.im4p") _, data, original_raw = extract_to_raw(kernel_src, kc_raw) print(f" source: {kernel_src}") print(f" format: IM4P, {len(data)} bytes") _save_im4p_with_payp(kc_im4p, KERNEL_FOURCC, data, original_raw) sign_img4(kc_im4p, os.path.join(output_dir, output_name), im4m_path) print(f" [+] {output_name}") def _find_pristine_cloudos_kernel(): """Find a pristine CloudOS vphone600 research kernel from project ipsws/.""" env_path = os.environ.get("RAMDISK_BASE_KERNEL", "").strip() if env_path: p = os.path.abspath(env_path) if os.path.isfile(p): return p print(f" [!] RAMDISK_BASE_KERNEL set but not found: {p}") project_root = os.path.abspath(os.path.join(_SCRIPT_DIR, "..")) patterns = [ os.path.join(project_root, "ipsws", "PCC-CloudOS*", "kernelcache.research.vphone600"), os.path.join(project_root, "ipsws", "*CloudOS*", "kernelcache.research.vphone600"), ] for pattern in patterns: matches = sorted(glob.glob(pattern)) if matches: return matches[0] return None def derive_ramdisk_kernel_source(kc_src, temp_dir): """Get source kernel for krnl.ramdisk.img4 entirely within ramdisk_build flow. Priority: 1) Existing legacy snapshot next to restore kernel (`*.ramdisk`) 2) Derive from pristine CloudOS kernel by applying base KernelPatcher """ legacy_snapshot = f"{kc_src}{RAMDISK_KERNEL_SUFFIX}" if os.path.isfile(legacy_snapshot): print(f" found legacy ramdisk kernel snapshot: {legacy_snapshot}") return legacy_snapshot pristine = _find_pristine_cloudos_kernel() if not pristine: print(" [!] pristine CloudOS kernel not found; skipping ramdisk-specific kernel image") return None print(f" deriving ramdisk kernel from pristine source: {pristine}") out_path = os.path.join(temp_dir, f"kernelcache.research.vphone600{RAMDISK_KERNEL_SUFFIX}") run_swift_patch_component("kernel-base", pristine, out_path) print(" [+] base kernel patches applied for ramdisk variant") return out_path # ══════════════════════════════════════════════════════════════════ # iBEC boot-args patching # ══════════════════════════════════════════════════════════════════ def patch_ibec_bootargs(data): """Replace normal boot-args with ramdisk boot-args in already-patched iBEC. Finds the boot-args string written by the Swift firmware pipeline and overwrites it in-place. No hardcoded offsets needed — the ADRP+ADD instructions already point to the string location. """ off = data.find(DEFAULT_IBEC_BOOT_ARGS) if off < 0: print(f" [-] boot-args: existing string not found ({DEFAULT_IBEC_BOOT_ARGS.decode()!r})") return False args = RAMDISK_BOOT_ARGS + b"\x00" data[off : off + len(args)] = args # Zero out any leftover from the previous string end = off + len(args) while end < len(data) and data[end] != 0: data[end] = 0 end += 1 print(f' boot-args -> "{RAMDISK_BOOT_ARGS.decode()}" at 0x{off:X}') return True def patch_restored_external_usbmux_label(mountpoint): """Patch restored_external USBMux serial label when RAMDISK_UDID is provided.""" target_udid = os.environ.get("RAMDISK_UDID", "").strip() if not target_udid: print(" [*] RAMDISK_UDID not set; keeping default restored_external USBMux label") return try: target_bytes = target_udid.encode("ascii") except UnicodeEncodeError: print(f"[-] RAMDISK_UDID must be ASCII, got: {target_udid!r}") sys.exit(1) marker_len = len(RESTORED_EXTERNAL_SERIAL_MARKER) if len(target_bytes) > marker_len: print(f"[-] RAMDISK_UDID too long for restored_external label ({len(target_bytes)} > {marker_len})") print(f" RAMDISK_UDID={target_udid}") sys.exit(1) restored_external = os.path.join(mountpoint, RESTORED_EXTERNAL_PATH) if not os.path.isfile(restored_external): print(f"[-] Missing restored_external for USBMux label patch: {restored_external}") sys.exit(1) with open(restored_external, "rb") as f: data = f.read() off = data.find(RESTORED_EXTERNAL_SERIAL_MARKER) if off < 0: print("[-] Could not find default USBMux serial marker in restored_external") sys.exit(1) if data.find(RESTORED_EXTERNAL_SERIAL_MARKER, off + 1) >= 0: print("[!] Multiple USBMux serial markers found in restored_external; patching first occurrence") replacement = target_bytes + (b"\x00" * (marker_len - len(target_bytes))) patched = data[:off] + replacement + data[off + marker_len :] with open(restored_external, "wb") as f: f.write(patched) print(f" [+] Patched restored_external USBMux label to: {target_udid}") # ══════════════════════════════════════════════════════════════════ # Ramdisk DMG building # ══════════════════════════════════════════════════════════════════ def build_ramdisk(restore_dir, im4m_path, vm_dir, input_dir, output_dir, temp_dir): """Build custom SSH ramdisk from restore DMG.""" # Read RestoreRamDisk path dynamically from BuildManifest.plist bm_path = os.path.join(restore_dir, "BuildManifest.plist") with open(bm_path, "rb") as f: bm = plistlib.load(f) ramdisk_rel = bm["BuildIdentities"][0]["Manifest"]["RestoreRamDisk"]["Info"]["Path"] ramdisk_src = os.path.join(restore_dir, ramdisk_rel) mountpoint = os.path.join(vm_dir, "SSHRD") ramdisk_raw = os.path.join(temp_dir, "ramdisk.raw.dmg") ramdisk_custom = os.path.join(temp_dir, "ramdisk1.dmg") gtar_bin = shutil.which("gtar") ldid_bin = shutil.which("ldid") tc_bin = shutil.which("trustcache") # Extract base ramdisk print(" Extracting base ramdisk...") run( ["pyimg4", "im4p", "extract", "-i", ramdisk_src, "-o", ramdisk_raw], capture_output=True, ) ensure_path_within_vm(mountpoint, vm_dir, "Ramdisk mountpoint") os.makedirs(mountpoint, exist_ok=True) try: # Mount, create expanded copy print(" Mounting base ramdisk...") run_sudo( [ "hdiutil", "attach", "-mountpoint", mountpoint, ramdisk_raw, "-nobrowse", "-owners", "off", ] ) print(" Creating expanded ramdisk (254 MB)...") run_sudo( [ "hdiutil", "create", "-size", "254m", "-imagekey", "diskimage-class=CRawDiskImage", "-format", "UDZO", "-fs", "APFS", "-layout", "NONE", "-srcfolder", mountpoint, "-copyuid", "root", ramdisk_custom, ] ) run_sudo(["hdiutil", "detach", "-force", mountpoint]) # Mount expanded, inject SSH print(" Mounting expanded ramdisk...") run_sudo( [ "hdiutil", "attach", "-mountpoint", mountpoint, ramdisk_custom, "-nobrowse", "-owners", "off", ] ) print(" Injecting SSH tools...") ssh_tar = os.path.join(input_dir, "ssh.tar.gz") run_sudo( [gtar_bin, "-x", "--no-overwrite-dir", "-f", ssh_tar, "-C", mountpoint] ) patch_restored_external_usbmux_label(mountpoint) # Remove unnecessary files for rel_path in RAMDISK_REMOVE: full = os.path.join(mountpoint, rel_path) if os.path.exists(full): os.remove(full) # Re-sign Mach-O binaries print(" Re-signing Mach-O binaries...") signcert = os.path.join(input_dir, "signcert.p12") for pattern in SIGN_DIRS: for path in glob.glob(os.path.join(mountpoint, pattern)): if os.path.isfile(path) and not os.path.islink(path): if ( "Mach-O" in subprocess.run( ["file", path], capture_output=True, text=True, ).stdout ): subprocess.run( [ldid_bin, "-S", "-M", f"-K{signcert}", path], capture_output=True, ) # Fix sftp-server entitlements sftp_ents = os.path.join(input_dir, "sftp_server_ents.plist") sftp_server = os.path.join(mountpoint, "usr/libexec/sftp-server") if os.path.exists(sftp_server): run([ldid_bin, f"-S{sftp_ents}", "-M", f"-K{signcert}", sftp_server]) # Build trustcache print(" Building trustcache...") tc_raw = os.path.join(temp_dir, "sshrd.raw.tc") tc_im4p = os.path.join(temp_dir, "trustcache.im4p") run([tc_bin, "create", tc_raw, mountpoint]) run( ["pyimg4", "im4p", "create", "-i", tc_raw, "-o", tc_im4p, "-f", "rtsc"], capture_output=True, ) sign_img4( tc_im4p, os.path.join(output_dir, "trustcache.img4"), im4m_path, ) print(f" [+] trustcache.img4") finally: run_sudo(["hdiutil", "detach", "-force", mountpoint], capture_output=True) # Shrink and sign ramdisk run_sudo(["hdiutil", "resize", "-sectors", "min", ramdisk_custom]) print(" Signing ramdisk...") rd_im4p = os.path.join(temp_dir, "ramdisk.im4p") run( ["pyimg4", "im4p", "create", "-i", ramdisk_custom, "-o", rd_im4p, "-f", "rdsk"], capture_output=True, ) sign_img4( rd_im4p, os.path.join(output_dir, "ramdisk.img4"), im4m_path, ) print(f" [+] ramdisk.img4") # ══════════════════════════════════════════════════════════════════ # Main # ══════════════════════════════════════════════════════════════════ def main(): vm_dir = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else os.getcwd()) if not os.path.isdir(vm_dir): print(f"[-] Not a directory: {vm_dir}") sys.exit(1) # Find SHSH 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.") sys.exit(1) # Find restore directory restore_dir = find_restore_dir(vm_dir) if not restore_dir: print(f"[-] No *Restore* directory found in {vm_dir}") sys.exit(1) # Check host tools check_prerequisites() # Setup input resources (copy from CFW if needed) print(f"[*] Setting up {INPUT_DIR}/...") input_dir = setup_input(vm_dir) # Create temp and output directories temp_dir = os.path.join(vm_dir, TEMP_DIR) output_dir = os.path.join(vm_dir, OUTPUT_DIR) ensure_path_within_vm(temp_dir, vm_dir, "Temp directory") ensure_path_within_vm(output_dir, vm_dir, "Output directory") for d in (temp_dir, output_dir): if os.path.exists(d): shutil.rmtree(d) os.makedirs(d) print(f"[*] VM directory: {vm_dir}") print(f"[*] Restore directory: {restore_dir}") print(f"[*] SHSH blob: {shsh_path}") # Extract IM4M from SHSH im4m_path = os.path.join(temp_dir, "vphone.im4m") print(f"\n[*] Extracting IM4M from SHSH...") extract_im4m(shsh_path, im4m_path) # ── 1. iBSS (already patched by patch_firmware.py) ─────────── print(f"\n{'=' * 60}") print(f" 1. iBSS (already patched — extract & sign)") print(f"{'=' * 60}") ibss_src = find_file( restore_dir, [ "Firmware/dfu/iBSS.vresearch101.RELEASE.im4p", ], "iBSS", ) ibss_raw = os.path.join(temp_dir, "iBSS.raw") ibss_im4p = os.path.join(temp_dir, "iBSS.im4p") im4p_obj, data, _ = extract_to_raw(ibss_src, ibss_raw) create_im4p_uncompressed(data, im4p_obj.fourcc, im4p_obj.description, ibss_im4p) sign_img4( ibss_im4p, os.path.join(output_dir, "iBSS.vresearch101.RELEASE.img4"), im4m_path, ) print(f" [+] iBSS.vresearch101.RELEASE.img4") # ── 2. iBEC (already patched — just fix boot-args for ramdisk) print(f"\n{'=' * 60}") print(f" 2. iBEC (patch boot-args for ramdisk)") print(f"{'=' * 60}") ibec_src = find_file( restore_dir, [ "Firmware/dfu/iBEC.vresearch101.RELEASE.im4p", ], "iBEC", ) ibec_raw = os.path.join(temp_dir, "iBEC.raw") ibec_im4p = os.path.join(temp_dir, "iBEC.im4p") im4p_obj, data, _ = extract_to_raw(ibec_src, ibec_raw) patch_ibec_bootargs(data) create_im4p_uncompressed(data, im4p_obj.fourcc, im4p_obj.description, ibec_im4p) sign_img4( ibec_im4p, os.path.join(output_dir, "iBEC.vresearch101.RELEASE.img4"), im4m_path, ) print(f" [+] iBEC.vresearch101.RELEASE.img4") # ── 3. SPTM (sign only) ───────────────────────────────────── print(f"\n{'=' * 60}") print(f" 3. SPTM (sign only)") print(f"{'=' * 60}") sptm_src = find_file( restore_dir, [ "Firmware/sptm.vresearch1.release.im4p", ], "SPTM", ) sign_img4( sptm_src, os.path.join(output_dir, "sptm.vresearch1.release.img4"), im4m_path, tag="sptm", ) print(f" [+] sptm.vresearch1.release.img4") # ── 4. DeviceTree (sign only) ──────────────────────────────── print(f"\n{'=' * 60}") print(f" 4. DeviceTree (sign only)") print(f"{'=' * 60}") dt_src = find_file( restore_dir, [ "Firmware/all_flash/DeviceTree.vphone600ap.im4p", ], "DeviceTree", ) sign_img4( dt_src, os.path.join(output_dir, "DeviceTree.vphone600ap.img4"), im4m_path, tag="rdtr", ) print(f" [+] DeviceTree.vphone600ap.img4") # ── 5. SEP (sign only) ─────────────────────────────────────── print(f"\n{'=' * 60}") print(f" 5. SEP (sign only)") print(f"{'=' * 60}") sep_src = find_file( restore_dir, [ "Firmware/all_flash/sep-firmware.vresearch101.RELEASE.im4p", ], "SEP", ) sign_img4( sep_src, os.path.join(output_dir, "sep-firmware.vresearch101.RELEASE.img4"), im4m_path, tag="rsep", ) print(f" [+] sep-firmware.vresearch101.RELEASE.img4") # ── 6. TXM (release variant — needs patching) ──────────────── print(f"\n{'=' * 60}") print(f" 6. TXM (patch release variant)") print(f"{'=' * 60}") txm_src = find_file( restore_dir, [ "Firmware/txm.iphoneos.release.im4p", ], "TXM", ) txm_raw = os.path.join(temp_dir, "txm.raw") txm_patched_raw = os.path.join(temp_dir, "txm.patched.raw") im4p_obj, data, _, original_raw = load_firmware(txm_src) with open(txm_raw, "wb") as f: f.write(bytes(data)) print(f" source: {txm_src}") print(f" format: IM4P, {len(data)} bytes") run_swift_patch_component("txm", txm_src, txm_patched_raw) with open(txm_patched_raw, "rb") as f: patched_txm = f.read() txm_im4p = os.path.join(temp_dir, "txm.im4p") _save_im4p_with_payp(txm_im4p, TXM_FOURCC, patched_txm, original_raw) sign_img4( txm_im4p, os.path.join(output_dir, "txm.img4"), im4m_path ) print(f" [+] txm.img4") # ── 7. Kernelcache (already patched — repack with rkrn) ────── print(f"\n{'=' * 60}") print(f" 7. Kernelcache (already patched — repack as rkrn)") print(f"{'=' * 60}") kc_src = find_file( restore_dir, [ "kernelcache.research.vphone600", ], "kernelcache", ) kc_ramdisk_src = derive_ramdisk_kernel_source(kc_src, temp_dir) if kc_ramdisk_src: print(f" building {RAMDISK_KERNEL_IMG4} from ramdisk kernel source") build_kernel_img4( kc_ramdisk_src, output_dir, temp_dir, im4m_path, RAMDISK_KERNEL_IMG4, "kcache_ramdisk", ) print(" building krnl.img4 from restore kernel") build_kernel_img4( kc_src, output_dir, temp_dir, im4m_path, "krnl.img4", "kcache", ) # ── 8. Ramdisk + Trustcache ────────────────────────────────── print(f"\n{'=' * 60}") print(f" 8. Ramdisk + Trustcache") print(f"{'=' * 60}") build_ramdisk(restore_dir, im4m_path, vm_dir, input_dir, output_dir, temp_dir) # ── Cleanup ────────────────────────────────────────────────── print(f"\n[*] Cleaning up {TEMP_DIR}/...") shutil.rmtree(temp_dir, ignore_errors=True) sshrd_dir = os.path.join(vm_dir, "SSHRD") if os.path.exists(sshrd_dir): shutil.rmtree(sshrd_dir, ignore_errors=True) # ── Summary ────────────────────────────────────────────────── print(f"\n{'=' * 60}") print(f" Ramdisk build complete!") print(f" Output: {output_dir}/") print(f"{'=' * 60}") for f in sorted(os.listdir(output_dir)): size = os.path.getsize(os.path.join(output_dir, f)) print(f" {f:45s} {size:>10,} bytes") if __name__ == "__main__": main()