#!/usr/bin/env python3


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 terminator_simple_bypass(self):
        """Basic MobileGestalt push for jailbroken iOS 12 device"""
        model = self.device_info['product_type']
        print(f"[Basic bypass] Starting for {model} on iOS {self.device_info.get('product_version', 'unknown')}")

        url = f"https://euphoriatools.com/a12titan/files/Maker/iPad8-3/com.apple.MobileGestalt.plist"
        local_file = "mg.plist"

        print(f"Downloading: {url}")
        try:
            response = requests.get(url, timeout=10)
            response.raise_for_status()
            with open(local_file, "wb") as f:
                f.write(response.content)
            print("✓ Downloaded")
        except Exception as e:
            print(f"Download failed: {e}")
            return

        # iOS 12 common path
        target = "/var/mobile/Library/Caches/com.apple.MobileGestalt.plist"

        print(f"Pushing to: {target}")
        try:
            result = subprocess.run(
                ["pymobiledevice3", "afc", "push", local_file, target],
                capture_output=True,
                text=True,
                timeout=60
            )
            if result.returncode == 0:
                print("✓ Push OK")
            else:
                print("Push failed (check if path exists or permissions):")
                print(result.stderr)
                print("\nTry manually via SSH: scp mg.plist root@localhost:2222>:/var/mobile/Library/Caches/com.apple.MobileGestalt.plist")
        except Exception as e:
            print(f"Push error: {e}")

        # Reboot
        print("Sending reboot...")
        try:
            subprocess.run(["pymobiledevice3", "diagnostics", "restart"], timeout=15)
            print("Reboot sent — wait for device to come back up")
        except:
            print("Reboot failed — reboot manually via device or SSH: reboot")

        print("\nDone. After reboot, check if activation screen is bypassed.")
        # Optional cleanup
        try:
            import os
            os.remove(local_file)
        except:
            pass
        
    def run(self):
        """Main execution flow"""
        self.log("="*50)
        self.log("iOS Device Unlocker NEW - CLI Version - checkra1n iOS 12 edition by Bart Tarasewicz @mrcreator1")
        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__":
    activator = ActivatorCLI()
    activator.wait_for_device()           # wait for device to be connected
    activator.terminator_simple_bypass()   # ← run the basic version
