main_GUI.py 19 KB

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