#!/usr/bin/env python3

# updated code created by Mr Creator / Bart Tarasewicz / pwn2owned
# check out EuphoriaTools.com
# enjoy this code, I love when i see people learning!

import subprocess
import time
import re
import os
import requests
from datetime import datetime
import sys


class ActivatorCLI:
    def __init__(self):
        self.device_connected = False
        self.device_info = {}
        self.is_activating = False
        
    def log(self, message):
        """Print timestamped log message"""
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        print(f"[{timestamp}] {message}")
        sys.stdout.flush()
    
    def update_gui_status(self, message):
        """Send status update to GUI (Swift will capture this)"""
        print(f"GUI_STATUS:{message}")
        sys.stdout.flush()
    
    def get_device_info(self):
        """Get information about the connected device using ideviceinfo"""
        try:
            result = subprocess.run(
                ["ideviceinfo"],
                capture_output=True,
                text=True,
                timeout=5
            )
            
            if result.returncode != 0:
                return None
            
            info = {}
            for line in result.stdout.split('\n'):
                if ': ' in line:
                    key, value = line.split(': ', 1)
                    info[key.strip()] = value.strip()
            
            # Extract necessary information
            device_data = {
                'udid': info.get('UniqueDeviceID', ''),
                'product_type': info.get('ProductType', ''),
                'device_name': info.get('DeviceName', ''),
                'product_version': info.get('ProductVersion', ''),
                'serial_number': info.get('SerialNumber', ''),
                'wifi_address': info.get('WiFiAddress', ''),
                'bluetooth_address': info.get('BluetoothAddress', ''),
                'activation_state': info.get('ActivationState', '')
            }
            
            return device_data
        except subprocess.TimeoutExpired:
            return None
        except FileNotFoundError:
            self.log("ERROR: ideviceinfo not found. Install libimobiledevice.")
            return None
        except Exception as e:
            self.log(f"Error retrieving device info: {e}")
            return None
    
    def wait_for_device(self):
        """Wait for device to connect"""
        self.log("Waiting for device connection...")
        self.update_gui_status("Waiting for device...")
        
        while True:
            device_info = self.get_device_info()
            
            if device_info and device_info['udid']:
                self.device_connected = True
                self.device_info = device_info
                self.log(f"✓ Device detected: {self.device_info['product_type']}")
                self.log(f"  UDID: {self.device_info['udid']}")
                self.log(f"  Serial Number: {self.device_info['serial_number']}")
                self.log(f"  iOS Version: {self.device_info['product_version']}")
                self.log(f"  Activation State: {self.device_info.get('activation_state', 'Unknown')}")
                
                # Check if already activated
                if self.device_info.get('activation_state') == 'Activated':
                    self.log("✓ Device is already activated!")
                    self.update_gui_status("Device already activated!")
                    return False
                
                self.update_gui_status("Device detected!")
                return True
            
            time.sleep(2)
            
    def deactivate_device(self):
        """Deactivate device using pymobiledevice3"""
        try:
            self.update_gui_status("Deactivating device...")
            result = subprocess.run(
                ["pymobiledevice3", "activation", "deactivate"],
                capture_output=True,
                text=True,
                timeout=30
            )
            
            if result.returncode == 0:
                self.log("✓ Deactivate command sent successfully")
                return True
            else:
                self.log(f"✗ Deactivate error: {result.stderr}")
                return False
                
        except FileNotFoundError:
            self.log("ERROR: pymobiledevice3 not found")
            return False
        except Exception as e:
            self.log(f"Error during Deactivate: {e}")
            return False
    
    def restart_device(self):
        """Restart device using pymobiledevice3"""
        try:
            self.update_gui_status("Restarting device...")
            result = subprocess.run(
                ["pymobiledevice3", "diagnostics", "restart"],
                capture_output=True,
                text=True,
                timeout=30
            )
            
            if result.returncode == 0:
                self.log("✓ Restart command sent successfully")
                return True
            else:
                self.log(f"✗ Restart error: {result.stderr}")
                return False
                
        except FileNotFoundError:
            self.log("ERROR: pymobiledevice3 not found")
            return False
        except Exception as e:
            self.log(f"Error during restart: {e}")
            return False
    
    def wait_for_device_reconnection(self, expected_udid, timeout=120):
        """Wait for device to reconnect"""
        start_time = time.time()
        
        self.log(f"Waiting for device to reconnect (timeout: {timeout}s)...")
        self.update_gui_status("Waiting for device to reconnect...")
        
        while time.time() - start_time < timeout:
            device_info = self.get_device_info()
            
            if device_info and device_info['udid'] == expected_udid:
                self.log(f"✓ Device reconnected: {expected_udid}")
                self.update_gui_status("Device reconnected!")
                self.device_info = device_info
                return True
            
            time.sleep(3)
        
        return False
    
    def pair_device(self, udid, path):
        """Request Trust Permissions on iOS Device"""
        try:
            self.log("Requesting Trust Permissions on iOS Device...")
            self.update_gui_status("Requesting trust permissions...")
            
            result = subprocess.run(
                ["pymobiledevice3", "lockdown", "pair"],
                capture_output=True,
                text=True,
                timeout=60
            )
            
            if result.returncode == 0:
                self.log("✓ Paired to iOS Device successfully!")
                self.update_gui_status("Device paired successfully!")
                return True
            else:
                self.log("✗ Error: pairing failed")
                self.log(f"  Error: {result.stderr}")
                return False
                    
        except subprocess.TimeoutExpired:
            self.log("Timeout during pairing")
            return False
        except FileNotFoundError:
            self.log("ERROR: pymobiledevice3 not found")
            return False
        except Exception as e:
            self.log(f"Error during pairing: {e}")
            return False
    
    def extract_syslog(self, udid, output_path):
        """Extract syslog from device"""
        try:
            self.log(f"Extracting syslog: {output_path}")
            self.update_gui_status("Extracting device logs...")
            
            result = subprocess.run(
                [
                    "pymobiledevice3", "syslog", "collect",
                    output_path,      # OUT
                    "200",            # SIZE_LIMIT (200 MB)
                    "24",             # AGE_LIMIT (24 hours)
                    "0"               # START_TIME (0 = now/recent)
                ],
                capture_output=True,
                text=True,
                timeout=120
            )
            
            if os.path.exists(output_path):
                self.log(f"✓ Syslog extracted successfully: {output_path}")
                return True
            else:
                self.log(f"✗ Error: syslog file not created")
                self.log(f"  Error: {result.stderr}")
                return False
                
        except subprocess.TimeoutExpired:
            self.log("Timeout during syslog extraction")
            return False
        except Exception as e:
            self.log(f"Error during syslog extraction: {e}")
            return False
    
    def extract_guid_from_syslog(self, logarchive_path):
        """Extract GUID from syslog using log show command"""
        try:
            self.update_gui_status("Analyzing device logs...")
            
            # Rename file to final.logarchive if necessary
            final_path = "final.logarchive"
            if logarchive_path != final_path:
                # Remove final.logarchive if it already exists
                import shutil
                if os.path.exists(final_path):
                    shutil.rmtree(final_path)
                    self.log(f"Removed existing {final_path}")
                subprocess.run(["mv", logarchive_path, final_path], check=True)
            
            self.log("Analyzing syslog...")
            
            # Execute log show command
            cmd = [
                "/usr/bin/log", "show",
                "--info", "--debug",
                "--style", "syslog",
                "--predicate", "eventMessage CONTAINS \"/private/var/containers/Shared/SystemGroup/\"",
                "--archive", final_path
            ]
            
            result = subprocess.run(
                cmd,
                capture_output=True,
                text=True,
                timeout=120
            )
            
            # Search for BLDatabaseManager.sqlite pattern
            bldatabase_pattern = r'/private/var/containers/Shared/SystemGroup/([0-9A-Fa-f-]{8}-[0-9A-Fa-f-]{4}-[0-9A-Fa-f-]{4}-[0-9A-Fa-f-]{4}-[0-9A-Fa-f-]{12})/Documents/BLDatabaseManager/BLDatabaseManager\.sqlite'
            
            for line in result.stdout.split('\n'):
                if 'BLDatabaseManager.sqlite' in line:
                    self.log(f"BLDatabaseManager line found")
                    match = re.search(bldatabase_pattern, line, re.IGNORECASE)
                    if match:
                        guid = match.group(1).upper()
                        self.log(f"✓ GUID extracted: {guid}")
                        self.update_gui_status("GUID found!")
                        return guid
            
            self.log("✗ GUID not found in syslog")
            return None
            
        except subprocess.TimeoutExpired:
            self.log("Timeout during syslog analysis")
            return None
        except Exception as e:
            self.log(f"Error during GUID extraction: {e}")
            return None
    
    def call_activation_api(self, model, guid, serial_number):
        """Make activation API call"""
        try:
            self.update_gui_status("Contacting activation server...")
            
            url = f"https://euphoriatools.com/a12liberator/server/public/activate.php"
            params = {
                'prd': model,
                'guid': guid,
                'sn': serial_number
            }
            
            self.log(f"Making API call...")
            self.log(f"  Parameters: prd={model}, guid={guid}, sn={serial_number}")
            
            response = requests.get(url, params=params, timeout=30)
            
            if response.status_code == 200:
                self.log(f"✓ API response received (status {response.status_code})")
                self.update_gui_status("Server response received!")
                return response.text
            else:
                self.log(f"✗ API error (status {response.status_code})")
                return None
                
        except requests.RequestException as e:
            self.log(f"Error during API call: {e}")
            return None
    
    def download_file_from_api(self, url, filename):
        """Download file from API URL and save it"""
        try:
            self.update_gui_status("Downloading activation files...")
            self.log(f"Downloading from: {url}")
            response = requests.get(url, timeout=30)
            
            if response.status_code != 200:
                self.log(f"✗ ERROR: HTTP {response.status_code}")
                return False
            
            # Save the file
            file_path = os.path.join(os.getcwd(), filename)
            with open(file_path, 'wb') as f:
                f.write(response.content)
            
            self.log(f"✓ Saved to: {file_path}")
            self.update_gui_status("Activation files downloaded!")
            return True
            
        except requests.exceptions.RequestException as e:
            self.log(f"✗ ERROR: Download failed - {e}")
            return False
        except Exception as e:
            self.log(f"✗ ERROR: {e}")
            return False

    def pull_file_from_device(self, device_path, local_path):
        """Pull a file from device using pymobiledevice3 afc pull"""
        try:
            self.log(f"Pulling file: {device_path} -> {local_path}")
            
            result = subprocess.run(
                ["pymobiledevice3", "afc", "pull", "-i", device_path, local_path],
                capture_output=True,
                text=True,
                timeout=60
            )
            
            if result.returncode == 0 and os.path.exists(local_path):
                self.log(f"✓ File pulled successfully")
                return True
            else:
                if "OBJECT_NOT_FOUND" in result.stderr or "status: 8" in result.stderr or "AfcFileNotFoundError" in result.stderr:
                    self.log(f"  (file not found on device)")
                    return False
                else:
                    self.log(f"✗ ERROR during file pull: {result.stderr}")
                    return False
                
        except subprocess.TimeoutExpired:
            self.log("Timeout during file pull")
            return False
        except Exception as e:
            self.log(f"Error during file pull: {e}")
            return False

    def push_file_to_device(self, local_path, device_path):
        """Send file to device using pymobiledevice3 afc push"""
        try:
            self.update_gui_status("Transferring files to device...")
            self.log(f"Pushing file: {local_path} -> {device_path}")
            
            result = subprocess.run(
                ["pymobiledevice3", "afc", "push", local_path, device_path],
                capture_output=True,
                text=True,
                timeout=60
            )
            
            if result.returncode == 0:
                self.log("✓ File sent successfully")
                return True
            else:
                self.log(f"✗ ERROR during file push: {result.stderr}")
                return False
                
        except subprocess.TimeoutExpired:
            self.log("Timeout during file push")
            return False
        except Exception as e:
            self.log(f"Error during file push: {e}")
            return False

    def rm_file_to_device(self, local_path):
        """Remove file from device using pymobiledevice3 afc rm"""
        try:
            self.log(f"Removing file: {local_path}")

            result = subprocess.run(
                ["pymobiledevice3", "afc", "rm", local_path],
                capture_output=True,
                text=True,
                timeout=60
            )
            
            if result.returncode == 0:
                self.log("✓ File removed successfully")
                return True
            else:
                if "OBJECT_NOT_FOUND" in result.stderr or "status: 8" in result.stderr:
                    return True  # File doesn't exist - OK
                else:
                    self.log(f"✗ ERROR: {result.stderr}")
                    return False
                
        except subprocess.TimeoutExpired:
            self.log("Timeout during file removal")
            return False
        except Exception as e:
            self.log(f"Error during file removal: {e}")
            return False
    
    def activate_device(self):
        """Complete activation process"""
        try:
            original_udid = self.device_info['udid']
            
            self.log("\n[Step 0/10] Ensuring device is deactivated...")
            self.update_gui_status("[Step 0/10] Rebooting device...")
            if not self.deactivate_device():
                self.log("✗ ERROR: Cannot deactivate device")
                return False
            
            time.sleep(5)

            self.log("\n[Step 1/10] Rebooting device...")
            self.update_gui_status("[Step 1/10] Rebooting device...")
            if not self.restart_device():
                self.log("✗ ERROR: Cannot restart device")
                return False
            
            time.sleep(5)
            
            if not self.wait_for_device_reconnection(original_udid, timeout=120):
                self.log("✗ ERROR: Timeout - device not reconnected")
                return False
            
            self.log("Waiting 5 seconds...")
            time.sleep(5)
            
            #one more restart to ENSURE guid will show
            self.log("\n[Step 1.2/10] Rebooting device...")
            self.update_gui_status("[Step 1.2/10] Rebooting device...")
            if not self.restart_device():
                self.log("✗ ERROR: Cannot restart device")
                return False
            
            time.sleep(5)
            
            if not self.wait_for_device_reconnection(original_udid, timeout=120):
                self.log("✗ ERROR: Timeout - device not reconnected")
                return False
                
            # Pair device
            self.log("\n[Step 1.5/10] Requesting trust permissions...")
            self.update_gui_status("[Step 1.5/10] Requesting trust...")
            logarchive_path = f"{original_udid}.logarchive"
            if not self.pair_device(original_udid, logarchive_path):
                self.log("✗ ERROR: Cannot pair device")
                return False
                
            # Extract syslog again
            self.log("\n[Step 2/10] Processing device logs (retry)...")
            self.update_gui_status("[Step 2/10] Processing logs (retry)...")
            if not self.extract_syslog(original_udid, logarchive_path):
                self.log("✗ ERROR: Cannot extract syslog")
                return False
            
            # Search for GUID again
            self.log("\n[Step 3/10] Searching GUID in syslog (retry)...")
            self.update_gui_status("[Step 3/10] Searching GUID (retry)...")
            guid = self.extract_guid_from_syslog(logarchive_path)
            
            if not guid:
                self.log("✗ ERROR: GUID not found in syslog")
                return False
            
            # Delete .logarchive file
            try:
                import shutil
                if os.path.exists("final.logarchive"):
                    shutil.rmtree("final.logarchive")
                    self.log("✓ Cleaned up logarchive files")
            except Exception as e:
                self.log(f"Warning: cannot delete final.logarchive: {e}")
            
            # Step 4: API call
            self.log("\n[Step 4/10] Making API call...")
            self.update_gui_status("[Step 4/10] Contacting server...")
            model = self.device_info['product_type']
            serial = self.device_info['serial_number']

            api_response = self.call_activation_api(model, guid, serial)
            if not api_response:
                self.log("✗ ERROR: API call failed")
                return False

            # Step 5: Download file
            self.log("\n[Step 5/10] Downloading activation files...")
            self.update_gui_status("[Step 5/10] Downloading files...")

            try:
                import json
                
                if isinstance(api_response, str):
                    api_data = json.loads(api_response)
                else:
                    api_data = api_response
                
                if not api_data.get('success'):
                    self.log("✗ ERROR: API returned success=False")
                    return False
                
                if 'links' not in api_data or 'step3_final' not in api_data['links']:
                    self.log("✗ ERROR: step3_final not found in API response")
                    return False
                
                file_url = api_data['links']['step3_final']
                
                if not self.download_file_from_api(file_url, 'downloads.28.sqlitedb'):
                    self.log("✗ ERROR: Cannot download downloads.28.sqlitedb")
                    return False
                    
                self.log("✓ Successfully downloaded downloads.28.sqlitedb")
                
            except json.JSONDecodeError as e:
                self.log(f"✗ ERROR: Invalid JSON response: {e}")
                return False
            except Exception as e:
                self.log(f"✗ ERROR: Failed to process API response: {e}")
                return False
            
            # Step 6: Remove existing files
            self.log("\n[Step 6/10] Removing existing files on device...")
            self.update_gui_status("[Step 6/10] Preparing device files...")
            
            files_to_remove = [
                "/Downloads/downloads.28.sqlitedb",
                "/Downloads/downloads.28.sqlitedb-wal",
                "/Downloads/downloads.28.sqlitedb-shm",
                "/Books/asset.epub",
                "/iTunes_Control/iTunes/iTunesMetadata.plist",
                "/Books/iTunesMetadata.plist",
                "/iTunes_Control/iTunes/iTunesMetadata.plist.ext"
            ]
            
            for file_path in files_to_remove:
                self.rm_file_to_device(file_path)

            # Step 7: Push file
            self.log("\n[Step 7/10] Pushing payload file to device...")
            self.update_gui_status("[Step 7/10] Transferring to device...")
            if not self.push_file_to_device("./downloads.28.sqlitedb", "/Downloads/downloads.28.sqlitedb"):
                self.log("✗ ERROR: Cannot send file to device")
                return False
            
            # Step 8: First restart
            self.log("\n[Step 8/10] Rebooting device (1/3)...")
            self.update_gui_status("[Step 8/10] Rebooting device (1/3)...")
            if not self.restart_device():
                self.log("✗ ERROR: Cannot restart device")
                return False
            
            time.sleep(5)
            
            if not self.wait_for_device_reconnection(original_udid, timeout=120):
                self.log("✗ ERROR: Timeout - device not reconnected")
                return False
            
            self.log("Waiting 30 seconds for device to stabilize...")
            self.update_gui_status("Waiting for device to stabilize...")
            time.sleep(30)

            # Step 8.5: Copy iTunesMetadata.plist
            self.log("\n[Step 8.5/10] Copying metadata files...")
            self.update_gui_status("[Step 8.5/10] Copying metadata...")
            
            src = "/iTunes_Control/iTunes/iTunesMetadata.plist"
            dst = "/Books/iTunesMetadata.plist"
            tmp_plist = "./tmp_metadata.plist"
            
            if self.pull_file_from_device(src, tmp_plist):
                if self.push_file_to_device(tmp_plist, dst):
                    self.log("✓ Successfully copied metadata to /Books/")
                else:
                    self.log("⚠ Warning: Failed to push to /Books/")
                
                try:
                    if os.path.exists(tmp_plist):
                        os.remove(tmp_plist)
                except Exception as e:
                    self.log(f"Warning: Could not delete temp file: {e}")
            else:
                self.log("⚠ Warning: Metadata file not found - skipping")
            
            # Step 9: Second restart
            self.log("\n[Step 9/10] Rebooting device (2/3)...")
            self.update_gui_status("[Step 9/10] Rebooting device (2/3)...")
            if not self.restart_device():
                self.log("✗ ERROR: Cannot restart device")
                return False
            
            time.sleep(5)
            
            if not self.wait_for_device_reconnection(original_udid, timeout=120):
                self.log("✗ ERROR: Timeout - device not reconnected")
                return False
            
            self.log("Waiting 30 seconds for device to stabilize...")
            self.update_gui_status("Waiting for device to stabilize...")
            time.sleep(30)

            # Step 10: Final restart
            self.log("\n[Step 10/10] Rebooting device (3/3)...")
            self.update_gui_status("[Step 10/10] Final reboot (3/3)...")
            if not self.restart_device():
                self.log("✗ ERROR: Cannot restart device")
                return False
            
            time.sleep(5)
            
            if not self.wait_for_device_reconnection(original_udid, timeout=120):
                self.log("✗ ERROR: Timeout - device not reconnected")
                return False
            
            self.log("Waiting 5 seconds...")
            time.sleep(5)
            
            self.log("\n" + "="*50)
            self.log("✓✓✓ ACTIVATION COMPLETED SUCCESSFULLY ✓✓✓")
            self.log("="*50)
            self.update_gui_status("✓ Activation complete!")
            return True
            
        except Exception as e:
            self.log(f"\n✗ ERROR during activation: {e}")
            import traceback
            self.log(traceback.format_exc())
            return False
    
    def run(self):
        """Main execution flow"""
        self.log("="*50)
        self.log("iOS Device Unlocker - CLI Version")
        self.log("="*50)
        
        # Wait for device
        if not self.wait_for_device():
            self.log("\nExiting - device already activated")
            return
        
        # Start activation
        self.log("\n" + "="*50)
        self.log("STARTING ACTIVATION PROCESS")
        self.log("="*50)
        self.update_gui_status("Starting activation process...")
        
        success = self.activate_device()
        
        if not success:
            self.log("\n" + "="*50)
            self.log("✗✗✗ ACTIVATION FAILED ✗✗✗")
            self.log("="*50)
            self.update_gui_status("✗ Activation failed!")
            sys.exit(1)


def main():
    try:
        activator = ActivatorCLI()
        activator.run()
    except KeyboardInterrupt:
        print("\n\nProcess interrupted by user")
        sys.exit(1)
    except Exception as e:
        print(f"\nFatal error: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)


if __name__ == "__main__":
    main()
