main_GUI.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. #!/usr/bin/env python3
  2. # main.py — iOS Activation Bypass GUI (macOS, PyQt6)
  3. import sys
  4. import os
  5. import re
  6. import time
  7. from pathlib import Path
  8. from typing import Optional, Dict, Any
  9. from PyQt6.QtWidgets import (
  10. QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
  11. QPushButton, QTextEdit, QLabel, QLineEdit, QGroupBox,
  12. QRadioButton, QButtonGroup, QMessageBox, QProgressBar, QFrame
  13. )
  14. from PyQt6.QtCore import Qt, QThread, pyqtSignal, QObject, QTimer, QSettings
  15. from PyQt6.QtGui import QFont, QTextCursor, QPalette, QColor, QPixmap, QIcon
  16. # === CLI module import ===
  17. try:
  18. from activator_macos import log as original_log, run as original_run, validate_guid, run_cmd, find_binary
  19. except ImportError as e:
  20. app = QApplication(sys.argv) # temporary QApplication for dialog
  21. QMessageBox.critical(None, "Import Error", f"Failed to import 'activator_macos.py':\n{e}\n\n"
  22. "Ensure it's in the same directory.")
  23. sys.exit(1)
  24. # === Thread-safe signal communication ===
  25. class SignalEmitter(QObject):
  26. log_signal = pyqtSignal(str, str)
  27. progress_signal = pyqtSignal(int)
  28. success_signal = pyqtSignal()
  29. error_signal = pyqtSignal(str)
  30. device_update_signal = pyqtSignal(dict)
  31. stage_signal = pyqtSignal(str)
  32. emitter = SignalEmitter()
  33. def gui_log(msg: str, level='info'):
  34. emitter.log_signal.emit(msg, level)
  35. original_log(msg, level)
  36. # Patch the activator logger
  37. import activator_macos
  38. activator_macos.log = gui_log
  39. # === Worker thread ===
  40. class ActivatorWorker(QThread):
  41. def __init__(self, auto: bool = False, guid: Optional[str] = None):
  42. super().__init__()
  43. self.auto = auto
  44. self.guid = guid
  45. self._stopped = False
  46. def stop(self):
  47. self._stopped = True
  48. self.requestInterruption()
  49. def _set_stage(self, stage_name: str):
  50. emitter.stage_signal.emit(stage_name)
  51. def run(self):
  52. try:
  53. if self._stopped:
  54. return
  55. # Progress through stage signals (approximate)
  56. QTimer.singleShot(300, lambda: self._set_stage("detect"))
  57. QTimer.singleShot(1000, lambda: self._set_stage("guid"))
  58. QTimer.singleShot(4000, lambda: self._set_stage("download"))
  59. QTimer.singleShot(9000, lambda: self._set_stage("upload"))
  60. QTimer.singleShot(16000, lambda: self._set_stage("reboot"))
  61. original_run(auto=self.auto, preset_guid=self.guid)
  62. if not self._stopped:
  63. self._set_stage("done")
  64. emitter.success_signal.emit()
  65. except Exception as e:
  66. if not self._stopped:
  67. emitter.error_signal.emit(str(e))
  68. finally:
  69. if not self._stopped:
  70. emitter.progress_signal.emit(100)
  71. # === Device information panel ===
  72. class DeviceInfoPanel(QWidget):
  73. def __init__(self):
  74. super().__init__()
  75. self.setup_ui()
  76. self.update_info()
  77. def setup_ui(self):
  78. layout = QHBoxLayout()
  79. layout.setContentsMargins(12, 12, 12, 12)
  80. layout.setSpacing(16)
  81. # Left side: status + image
  82. img_container = QVBoxLayout()
  83. img_container.setAlignment(Qt.AlignmentFlag.AlignTop)
  84. self.status_indicator = QLabel("●")
  85. self.status_indicator.setFont(QFont("SF Mono", 16))
  86. self.status_indicator.setStyleSheet("color: #999;")
  87. img_container.addWidget(self.status_indicator, alignment=Qt.AlignmentFlag.AlignLeft)
  88. self.img_label = QLabel()
  89. img_path = Path("assets/iphone.png")
  90. if img_path.exists():
  91. pixmap = QPixmap(str(img_path))
  92. size = 128
  93. screen = QApplication.primaryScreen()
  94. if screen:
  95. size = int(128 * screen.devicePixelRatio())
  96. pixmap = pixmap.scaled(size, size, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
  97. self.img_label.setPixmap(pixmap)
  98. else:
  99. self.img_label.setText("📱\n\niPhone\n(not found)")
  100. self.img_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
  101. self.img_label.setStyleSheet("font-size: 14px; color: #888;")
  102. img_container.addWidget(self.img_label)
  103. layout.addLayout(img_container)
  104. # Right side: text
  105. info_layout = QVBoxLayout()
  106. info_layout.setSpacing(6)
  107. title_label = QLabel("Device Information")
  108. title_label.setFont(QFont("Segoe UI", 11, QFont.Weight.Bold))
  109. info_layout.addWidget(title_label)
  110. self.model_label = QLabel("Model: —")
  111. self.ios_label = QLabel("iOS: —")
  112. self.activation_label = QLabel("Activation: —")
  113. self.udid_label = QLabel("UDID: —")
  114. for lbl in [self.model_label, self.ios_label, self.activation_label, self.udid_label]:
  115. lbl.setFont(QFont("SF Mono, Menlo, monospace", 10))
  116. info_layout.addWidget(lbl)
  117. layout.addLayout(info_layout)
  118. self.setLayout(layout)
  119. def update_info(self, info: Optional[Dict[str, str]] = None):
  120. if not info or not isinstance(info, dict):
  121. info = {"ProductType": "—", "ProductVersion": "—", "ActivationState": "—", "UniqueDeviceID": "—"}
  122. # Status
  123. act = info.get("ActivationState", "").strip()
  124. if act == "Activated":
  125. self.status_indicator.setStyleSheet("color: #4caf50;")
  126. self.status_indicator.setToolTip("✅ Activated")
  127. elif act in ("Unactivated", "—"):
  128. self.status_indicator.setStyleSheet("color: #ff9800;")
  129. self.status_indicator.setToolTip("⚠ Not activated")
  130. else:
  131. self.status_indicator.setStyleSheet("color: #f44336;")
  132. self.status_indicator.setToolTip("❌ Unknown")
  133. # Model
  134. model_map = {
  135. "iPhone13,4": "iPhone 12 Pro Max",
  136. "iPhone13,3": "iPhone 12 Pro",
  137. "iPhone13,2": "iPhone 12",
  138. "iPhone14,5": "iPhone 13",
  139. "iPhone15,2": "iPhone 14 Pro",
  140. "iPhone15,3": "iPhone 14 Pro Max",
  141. "iPhone16,1": "iPhone 15 Pro",
  142. "iPhone16,2": "iPhone 15 Pro Max",
  143. }
  144. code = info.get("ProductType", "Unknown")
  145. name = model_map.get(code, code)
  146. self.model_label.setText(f"Model: {name}")
  147. # Other info
  148. self.ios_label.setText(f"iOS: {info.get('ProductVersion', '—')}")
  149. state = info.get("ActivationState", "—")
  150. color = "#4caf50" if state == "Activated" else "#ff9800"
  151. self.activation_label.setText(f'<span style="color:{color};">Activation: <b>{state}</b></span>')
  152. self.activation_label.setTextFormat(Qt.TextFormat.RichText)
  153. udid = info.get("UniqueDeviceID", "—")
  154. if len(udid) > 10:
  155. udid = udid[:6] + "…" + udid[-4:]
  156. self.udid_label.setText(f"UDID: {udid}")
  157. # === Main window ===
  158. class MainWindow(QMainWindow):
  159. def __init__(self):
  160. super().__init__()
  161. self.setWindowTitle("📱 iOS Activation Bypass (GUI)")
  162. self.resize(920, 700)
  163. self.thread = None
  164. # ✅ QSettings — only after QApplication
  165. self.settings = QSettings("Rust505", "iOSActivatorGUI")
  166. # Device update timer
  167. self._device_timer = QTimer()
  168. self._device_timer.timeout.connect(self.detect_device)
  169. self._device_timer.start(3000)
  170. # Central widget
  171. central = QWidget()
  172. self.setCentralWidget(central)
  173. main_layout = QVBoxLayout(central)
  174. main_layout.setContentsMargins(16, 16, 16, 16)
  175. main_layout.setSpacing(12)
  176. # Device panel
  177. device_frame = QFrame()
  178. device_frame.setFrameShape(QFrame.Shape.StyledPanel)
  179. device_frame.setFrameShadow(QFrame.Shadow.Raised)
  180. device_layout = QVBoxLayout(device_frame)
  181. device_layout.addWidget(QLabel("📱 Connected Device"), alignment=Qt.AlignmentFlag.AlignLeft)
  182. self.device_panel = DeviceInfoPanel()
  183. device_layout.addWidget(self.device_panel)
  184. main_layout.addWidget(device_frame)
  185. # GUID mode
  186. mode_group = QGroupBox("GUID Input Mode")
  187. mode_layout = QHBoxLayout()
  188. self.radio_auto = QRadioButton("Auto-detect (recommended)")
  189. self.radio_manual = QRadioButton("Manual input")
  190. self.radio_auto.setChecked(True)
  191. group = QButtonGroup()
  192. group.addButton(self.radio_auto)
  193. group.addButton(self.radio_manual)
  194. mode_layout.addWidget(self.radio_auto)
  195. mode_layout.addWidget(self.radio_manual)
  196. mode_group.setLayout(mode_layout)
  197. main_layout.addWidget(mode_group)
  198. # GUID field
  199. guid_layout = QHBoxLayout()
  200. guid_layout.addWidget(QLabel("GUID:"))
  201. self.guid_edit = QLineEdit()
  202. self.guid_edit.setPlaceholderText("e.g. 1A2B3C4D-1234-4123-8888-ABCDEF012345")
  203. self.guid_edit.textChanged.connect(self._validate_guid)
  204. self.guid_edit.setMaximumWidth(500)
  205. guid_layout.addWidget(self.guid_edit)
  206. main_layout.addLayout(guid_layout)
  207. # Buttons
  208. btn_layout = QHBoxLayout()
  209. self.start_btn = QPushButton("▶ Start Activation")
  210. self.stop_btn = QPushButton("⏹ Stop")
  211. self.stop_btn.setEnabled(False)
  212. self.start_btn.clicked.connect(self.start_activation)
  213. self.stop_btn.clicked.connect(self.stop_activation)
  214. btn_layout.addWidget(self.start_btn)
  215. btn_layout.addWidget(self.stop_btn)
  216. main_layout.addLayout(btn_layout)
  217. # Progress
  218. self.progress = QProgressBar()
  219. self.progress.setRange(0, 100)
  220. self.progress.setValue(0)
  221. main_layout.addWidget(self.progress)
  222. # Logs
  223. log_group = QGroupBox("Logs")
  224. log_layout = QVBoxLayout()
  225. self.log_view = QTextEdit()
  226. self.log_view.setReadOnly(True)
  227. self.log_view.setFont(QFont("SF Mono, Menlo, Monaco, monospace", 10))
  228. self.log_view.setStyleSheet("background: #1e1e1e; color: #e0e0e0;")
  229. log_layout.addWidget(self.log_view)
  230. log_group.setLayout(log_layout)
  231. main_layout.addWidget(log_group)
  232. # Connect signals
  233. emitter.log_signal.connect(self.append_log)
  234. emitter.progress_signal.connect(self.progress.setValue)
  235. emitter.success_signal.connect(self.on_success)
  236. emitter.error_signal.connect(self.on_error)
  237. emitter.device_update_signal.connect(self.device_panel.update_info)
  238. emitter.stage_signal.connect(self._on_stage_change)
  239. # Load last GUID (if exists)
  240. last_guid = self.settings.value("last_guid", "")
  241. if last_guid and validate_guid(last_guid):
  242. self.radio_manual.setChecked(True)
  243. self.guid_edit.setText(last_guid.upper())
  244. # Check dependencies and update device
  245. self._check_dependencies()
  246. self.detect_device()
  247. def _check_dependencies(self):
  248. missing = [b for b in ['ideviceinfo', 'idevice_id', 'pymobiledevice3', 'curl'] if not find_binary(b)]
  249. if missing:
  250. msg = (
  251. f"⚠ Missing tools: {', '.join(missing)}\n\n"
  252. "Install with:\n"
  253. " brew install libimobiledevice usbmuxd\n"
  254. " pip3 install -U pymobiledevice3"
  255. )
  256. QMessageBox.critical(self, "Dependency Error", msg)
  257. self.start_btn.setEnabled(False)
  258. def _validate_guid(self):
  259. text = self.guid_edit.text().strip()
  260. valid = bool(text and validate_guid(text.upper()))
  261. self.guid_edit.setStyleSheet("" if valid else "background: #ffebee;" if len(text) > 8 else "")
  262. def detect_device(self):
  263. if self.thread and self.thread.isRunning():
  264. return
  265. try:
  266. code, out, _ = run_cmd(["ideviceinfo"], timeout=5)
  267. info = {}
  268. if code == 0:
  269. for line in out.splitlines():
  270. if ": " in line:
  271. k, v = line.split(": ", 1)
  272. info[k.strip()] = v.strip()
  273. # UDID
  274. udid_code, udid_out, _ = run_cmd(["idevice_id", "-l"], timeout=3)
  275. if udid_code == 0:
  276. info["UniqueDeviceID"] = udid_out.strip() or "—"
  277. emitter.device_update_signal.emit(info)
  278. except Exception as e:
  279. emitter.device_update_signal.emit({})
  280. def _on_stage_change(self, stage: str):
  281. labels = {
  282. "detect": "🔍 Detecting device...",
  283. "guid": "📡 Extracting GUID...",
  284. "download": "📥 Downloading payload...",
  285. "upload": "📤 Uploading to /Downloads/...",
  286. "reboot": "🔄 Rebooting (×3)...",
  287. "done": "✅ Done!",
  288. }
  289. self.progress.setFormat(labels.get(stage, stage))
  290. def start_activation(self):
  291. if self.thread and self.thread.isRunning():
  292. return
  293. auto = self.radio_auto.isChecked()
  294. guid = self.guid_edit.text().strip().upper() if self.radio_manual.isChecked() else None
  295. if self.radio_manual.isChecked():
  296. if not guid:
  297. QMessageBox.warning(self, "Input Required", "GUID field is empty.")
  298. return
  299. if not validate_guid(guid):
  300. QMessageBox.warning(self, "Invalid GUID", "GUID must be UUID v4 (e.g. XXXXXXXX-XXXX-4XXX-YXXX-XXXXXXXXXXXX).")
  301. return
  302. self.log_view.clear()
  303. self.append_log("🚀 Starting activation process...", "info")
  304. self.start_btn.setEnabled(False)
  305. self.stop_btn.setEnabled(True)
  306. self.progress.setValue(0)
  307. self.progress.setFormat("Initializing...")
  308. self.thread = ActivatorWorker(auto=auto, guid=guid)
  309. self.thread.finished.connect(self._on_thread_finished)
  310. self.thread.start()
  311. def stop_activation(self):
  312. if self.thread and self.thread.isRunning():
  313. self.append_log("⏹ Stop requested...", "warn")
  314. self.thread.stop()
  315. self.thread.wait(2000)
  316. if self.thread.isRunning():
  317. self.thread.terminate()
  318. self.append_log("⚠ Thread forcibly terminated.", "error")
  319. self._on_thread_finished()
  320. def _on_thread_finished(self):
  321. self.start_btn.setEnabled(True)
  322. self.stop_btn.setEnabled(False)
  323. def append_log(self, msg: str, level='info'):
  324. colors = {'success': '#4caf50', 'error': '#f44336', 'warn': '#ff9800',
  325. 'step': '#2196f3', 'info': '#64b5f6', 'detail': '#90a4ae'}
  326. color = colors.get(level, '#e0e0e0')
  327. ts = time.strftime("%H:%M:%S")
  328. html = f'<span style="color:#78909c;">[{ts}]</span> <span style="color:{color};">{msg}</span><br>'
  329. self.log_view.append(html)
  330. self.log_view.moveCursor(QTextCursor.MoveOperation.End)
  331. self.log_view.ensureCursorVisible()
  332. def on_success(self):
  333. self.append_log("🎉 ACTIVATION SUCCESSFUL!", "success")
  334. # Save GUID
  335. guid = self.guid_edit.text().strip().upper()
  336. if guid and validate_guid(guid):
  337. self.settings.setValue("last_guid", guid)
  338. QMessageBox.information(self, "Success", "✅ Activation bypass completed!\n\n"
  339. "Check Settings → General → About.")
  340. self.detect_device()
  341. def on_error(self, err: str):
  342. self.append_log(f"❌ Fatal: {err}", "error")
  343. QMessageBox.critical(self, "Activation Failed", f"Process terminated with error:\n\n<b>{err}</b>")
  344. # === Theme and icon ===
  345. def enable_dark_mode(app: QApplication):
  346. try:
  347. import subprocess
  348. res = subprocess.run(["defaults", "read", "-g", "AppleInterfaceStyle"],
  349. capture_output=True, text=True)
  350. is_dark = res.returncode == 0 and "Dark" in res.stdout
  351. except:
  352. is_dark = True
  353. if is_dark:
  354. app.setStyle("Fusion")
  355. p = QPalette()
  356. p.setColor(QPalette.ColorRole.Window, QColor(38, 38, 38))
  357. p.setColor(QPalette.ColorRole.WindowText, Qt.GlobalColor.white)
  358. p.setColor(QPalette.ColorRole.Base, QColor(28, 28, 28))
  359. p.setColor(QPalette.ColorRole.AlternateBase, QColor(48, 48, 48))
  360. p.setColor(QPalette.ColorRole.ToolTipBase, Qt.GlobalColor.white)
  361. p.setColor(QPalette.ColorRole.ToolTipText, Qt.GlobalColor.white)
  362. p.setColor(QPalette.ColorRole.Text, Qt.GlobalColor.white)
  363. p.setColor(QPalette.ColorRole.Button, QColor(68, 68, 68))
  364. p.setColor(QPalette.ColorRole.ButtonText, Qt.GlobalColor.white)
  365. p.setColor(QPalette.ColorRole.BrightText, Qt.GlobalColor.red)
  366. p.setColor(QPalette.ColorRole.Link, QColor(40, 140, 240))
  367. p.setColor(QPalette.ColorRole.Highlight, QColor(40, 140, 240))
  368. p.setColor(QPalette.ColorRole.HighlightedText, Qt.GlobalColor.black)
  369. app.setPalette(p)
  370. def set_app_icon(app: QApplication):
  371. icon_path = Path("assets/app_icon.png")
  372. if icon_path.exists():
  373. app.setWindowIcon(QIcon(str(icon_path)))
  374. # === Entry point ===
  375. if __name__ == "__main__":
  376. app = QApplication(sys.argv)
  377. app.setApplicationName("iOS Activation Bypass")
  378. app.setOrganizationName("Rust505")
  379. enable_dark_mode(app)
  380. set_app_icon(app)
  381. window = MainWindow()
  382. window.show()
  383. sys.exit(app.exec())