import traceback import plistlib from pathlib import Path from PySide6.QtWidgets import QMessageBox from PySide6.QtCore import QSettings from pymobiledevice3 import usbmux from pymobiledevice3.lockdown import create_using_usbmux from devicemanagement.constants import Device, Version from devicemanagement.data_singleton import DataSingleton from tweaks.tweaks import tweaks, FeatureFlagTweak, EligibilityTweak, AITweak, BasicPlistTweak, RdarFixTweak from tweaks.custom_gestalt_tweaks import CustomGestaltTweaks from tweaks.basic_plist_locations import FileLocationsList from Sparserestore.restore import restore_files, FileToRestore def show_error_msg(txt: str, detailed_txt: str = None): detailsBox = QMessageBox() detailsBox.setIcon(QMessageBox.Critical) detailsBox.setWindowTitle("Error!") detailsBox.setText(txt) if detailed_txt != None: detailsBox.setDetailedText(detailed_txt) detailsBox.exec() def show_apply_error(e: Exception, update_label=lambda x: None): if "Find My" in str(e): show_error_msg("Find My must be disabled in order to use this tool.", detailed_txt="Disable Find My from Settings (Settings -> [Your Name] -> Find My) and then try again.") elif "SessionInactive" in str(e): show_error_msg("The session was terminated. Refresh the device list and try again.") elif "Password" in str(e): show_error_msg("Device is password protected! You must trust the computer on your device.", detailed_txt="Unlock your device. On the popup, click \"Trust\", enter your password, then try again.") else: show_error_msg(type(e).__name__ + ": " + repr(e), detailed_txt=str(traceback.format_exc())) print(traceback.format_exc()) update_label("Failed to restore") class DeviceManager: ## Class Functions def __init__(self): self.devices: list[Device] = [] self.data_singleton = DataSingleton() self.current_device_index = 0 # preferences self.apply_over_wifi = True self.skip_setup = True self.auto_reboot = True self.supervised = False self.organization_name = "" def get_devices(self, settings: QSettings): self.devices.clear() # handle errors when failing to get connected devices try: connected_devices = usbmux.list_devices() except: show_error_msg( """ Failed to get device list. Click \"Show Details\" for the traceback. If you are on Windows, make sure you have the \"Apple Devices\" app from the Microsoft Store or iTunes from Apple's website. If you are on Linux, make sure you have usbmuxd and libimobiledevice installed. """, detailed_txt=str(traceback.format_exc()) ) self.set_current_device(index=None) return # Connect via usbmuxd for device in connected_devices: if self.apply_over_wifi or device.is_usb: try: ld = create_using_usbmux(serial=device.serial) vals = ld.all_values model = vals['ProductType'] hardware = vals['HardwareModel'] cpu = vals['HardwarePlatform'] try: product_type = settings.value(device.serial + "_model", "", type=str) hardware_type = settings.value(device.serial + "_hardware", "", type=str) cpu_type = settings.value(device.serial + "_cpu", "", type=str) if product_type == "": # save the new product type settings.setValue(device.serial + "_model", model) else: model = product_type if hardware_type == "": # save the new hardware model settings.setValue(device.serial + "_hardware", hardware) else: hardware = hardware_type if cpu_type == "": # save the new cpu model settings.setValue(device.serial + "_cpu", cpu) else: cpu = cpu_type except: pass dev = Device( uuid=device.serial, name=vals['DeviceName'], version=vals['ProductVersion'], build=vals['BuildVersion'], model=model, hardware=hardware, cpu=cpu, locale=ld.locale, ld=ld ) tweaks["RdarFix"].get_rdar_mode(model) self.devices.append(dev) except Exception as e: print(f"ERROR with lockdown device with UUID {device.serial}") show_error_msg(type(e).__name__ + ": " + repr(e), detailed_txt=str(traceback.format_exc())) connected_devices.remove(device) else: connected_devices.remove(device) if len(connected_devices) > 0: self.set_current_device(index=0) else: self.set_current_device(index=None) ## CURRENT DEVICE def set_current_device(self, index: int = None): if index == None: self.data_singleton.current_device = None self.data_singleton.device_available = False self.data_singleton.gestalt_path = None self.current_device_index = 0 tweaks["SpoofModel"].value[0] = "Placeholder" tweaks["SpoofHardware"].value[0] = "Placeholder" tweaks["SpoofCPU"].value[0] = "Placeholder" else: self.data_singleton.current_device = self.devices[index] if Version(self.devices[index].version) < Version("17.0"): self.data_singleton.device_available = False self.data_singleton.gestalt_path = None else: self.data_singleton.device_available = True tweaks["SpoofModel"].value[0] = self.data_singleton.current_device.model tweaks["SpoofHardware"].value[0] = self.data_singleton.current_device.hardware tweaks["SpoofCPU"].value[0] = self.data_singleton.current_device.cpu self.current_device_index = index def get_current_device_name(self) -> str: if self.data_singleton.current_device == None: return "No Device" else: return self.data_singleton.current_device.name def get_current_device_version(self) -> str: if self.data_singleton.current_device == None: return "" else: return self.data_singleton.current_device.version def get_current_device_build(self) -> str: if self.data_singleton.current_device == None: return "" else: return self.data_singleton.current_device.build def get_current_device_uuid(self) -> str: if self.data_singleton.current_device == None: return "" else: return self.data_singleton.current_device.uuid def get_current_device_model(self) -> str: if self.data_singleton.current_device == None: return "" else: return self.data_singleton.current_device.model def get_current_device_supported(self) -> bool: if self.data_singleton.current_device == None: return False else: return self.data_singleton.current_device.supported() def get_current_device_patched(self) -> bool: if self.data_singleton.current_device == None: return True else: return self.data_singleton.current_device.is_exploit_fully_patched() def reset_device_pairing(self): # first, unpair it if self.data_singleton.current_device == None: return self.data_singleton.current_device.ld.unpair() # next, pair it again self.data_singleton.current_device.ld.pair() QMessageBox.information(None, "Pairing Reset", "Your device's pairing was successfully reset. Refresh the device list before applying.") def add_skip_setup(self, files_to_restore: list[FileToRestore]): if self.skip_setup and not self.get_current_device_supported(): # add the 2 skip setup files cloud_config_plist: dict = { "SkipSetup": ["WiFi", "Location", "Restore", "SIMSetup", "Android", "AppleID", "IntendedUser", "TOS", "Siri", "ScreenTime", "Diagnostics", "SoftwareUpdate", "Passcode", "Biometric", "Payment", "Zoom", "DisplayTone", "MessagingActivationUsingPhoneNumber", "HomeButtonSensitivity", "CloudStorage", "ScreenSaver", "TapToSetup", "Keyboard", "PreferredLanguage", "SpokenLanguage", "WatchMigration", "OnBoarding", "TVProviderSignIn", "TVHomeScreenSync", "Privacy", "TVRoom", "iMessageAndFaceTime", "AppStore", "Safety", "Multitasking", "ActionButton", "TermsOfAddress", "AccessibilityAppearance", "Welcome", "Appearance", "RestoreCompleted", "UpdateCompleted"], "AllowPairing": True, "ConfigurationWasApplied": True, "CloudConfigurationUIComplete": True, "ConfigurationSource": 0, "PostSetupProfileWasInstalled": True, "IsSupervised": False, } if self.supervised == True: cloud_config_plist["IsSupervised"] = True cloud_config_plist["OrganizationName"] = self.organization_name files_to_restore.append(FileToRestore( contents=plistlib.dumps(cloud_config_plist), restore_path="systemgroup.com.apple.configurationprofiles/Library/ConfigurationProfiles/CloudConfigurationDetails.plist", domain="SysSharedContainerDomain-." )) purplebuddy_plist: dict = { "SetupDone": True, "SetupFinishedAllSteps": True, "UserChoseLanguage": True } files_to_restore.append(FileToRestore( contents=plistlib.dumps(purplebuddy_plist), restore_path="mobile/com.apple.purplebuddy.plist", domain="ManagedPreferencesDomain" )) def get_domain_for_path(self, path: str, fully_patched: bool = False) -> str: # just make the Sys Containers to use the regular way (won't work for mga) sysSharedContainer = "SysSharedContainerDomain-" sysContainer = "SysContainerDomain-" if not fully_patched: sysSharedContainer += "." sysContainer += "." mappings: dict = { "/var/Managed Preferences/": "ManagedPreferencesDomain", "/var/root/": "RootDomain", "/var/preferences/": "SystemPreferencesDomain", "/var/MobileDevice/": "MobileDeviceDomain", "/var/mobile/": "HomeDomain", "/var/db/": "DatabaseDomain", "/var/containers/Shared/SystemGroup/": sysSharedContainer, "/var/containers/Data/SystemGroup/": sysContainer } for mapping in mappings.keys(): if path.startswith(mapping): new_path = path.replace(mapping, "") new_domain = mappings[mapping] # if patched, include the next part of the path in the domain if fully_patched and (new_domain == sysSharedContainer or new_domain == sysContainer): parts = new_path.split("/") new_domain += parts[0] new_path = new_path.replace(parts[0] + "/", "") return new_domain, new_path return None, path def concat_file(self, contents: str, path: str, files_to_restore: list[FileToRestore]): if self.get_current_device_supported(): files_to_restore.append(FileToRestore( contents=contents, restore_path=path )) else: domain, file_path = self.get_domain_for_path(path, fully_patched=self.get_current_device_patched()) files_to_restore.append(FileToRestore( contents=contents, restore_path=file_path, domain=domain )) ## APPLYING OR REMOVING TWEAKS AND RESTORING def apply_changes(self, resetting: bool = False, update_label=lambda x: None): # set the tweaks and apply # first open the file in read mode update_label("Applying changes to files...") gestalt_plist = None if self.data_singleton.gestalt_path != None: with open(self.data_singleton.gestalt_path, 'rb') as in_fp: gestalt_plist = plistlib.load(in_fp) # create the other plists flag_plist: dict = {} eligibility_files = None ai_file = None basic_plists: dict = {} # set the plist keys if not resetting: for tweak_name in tweaks: tweak = tweaks[tweak_name] if isinstance(tweak, FeatureFlagTweak): flag_plist = tweak.apply_tweak(flag_plist) elif isinstance(tweak, EligibilityTweak): eligibility_files = tweak.apply_tweak() elif isinstance(tweak, AITweak): ai_file = tweak.apply_tweak() elif isinstance(tweak, BasicPlistTweak) or isinstance(tweak, RdarFixTweak): basic_plists = tweak.apply_tweak(basic_plists) else: if gestalt_plist != None: gestalt_plist = tweak.apply_tweak(gestalt_plist) # set the custom gestalt keys if gestalt_plist != None: gestalt_plist = CustomGestaltTweaks.apply_tweaks(gestalt_plist) gestalt_data = None if resetting: gestalt_data = b"" elif gestalt_plist != None: gestalt_data = plistlib.dumps(gestalt_plist) # Generate backup update_label("Generating backup...") # create the restore file list files_to_restore: dict[FileToRestore] = [ ] self.concat_file( contents=plistlib.dumps(flag_plist), path="/var/preferences/FeatureFlags/Global.plist", files_to_restore=files_to_restore ) self.add_skip_setup(files_to_restore) if gestalt_data != None: self.concat_file( contents=gestalt_data, path="/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist", files_to_restore=files_to_restore ) if eligibility_files: new_eligibility_files: dict[FileToRestore] = [] if not self.get_current_device_supported(): # update the files for file in eligibility_files: self.concat_file( contents=file.contents, path=file.restore_path, files_to_restore=new_eligibility_files ) else: new_eligibility_files = eligibility_files files_to_restore += new_eligibility_files if ai_file != None: self.concat_file( contents=ai_file.contents, path=ai_file.restore_path, files_to_restore=files_to_restore ) for location, plist in basic_plists.items(): self.concat_file( contents=plistlib.dumps(plist), path=location.value, files_to_restore=files_to_restore ) # reset basic tweaks if resetting: empty_data = plistlib.dumps({}) for location in FileLocationsList: self.concat_file( contents=empty_data, path=location.value, files_to_restore=files_to_restore ) # restore to the device update_label("Restoring to device...") try: restore_files(files=files_to_restore, reboot=self.auto_reboot, lockdown_client=self.data_singleton.current_device.ld) msg = "Your device will now restart." if not self.auto_reboot: msg = "Please restart your device to see changes." QMessageBox.information(None, "Success!", "All done! " + msg) update_label("Success!") except Exception as e: show_apply_error(e, update_label) ## RESETTING MOBILE GESTALT def reset_mobilegestalt(self, settings: QSettings, update_label=lambda x: None): # restore to the device update_label("Restoring to device...") try: # remove the saved device model, hardware, and cpu settings.setValue(self.data_singleton.current_device.uuid + "_model", "") settings.setValue(self.data_singleton.current_device.uuid + "_hardware", "") settings.setValue(self.data_singleton.current_device.uuid + "_cpu", "") domain, file_path = self.get_domain_for_path( "/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist", fully_patched=self.get_current_device_patched()) restore_files(files=[FileToRestore( contents=b"", restore_path=file_path, domain=domain )], reboot=self.auto_reboot, lockdown_client=self.data_singleton.current_device.ld) msg = "Your device will now restart." if not self.auto_reboot: msg = "Please restart your device to see changes." QMessageBox.information(None, "Success!", "All done! " + msg) update_label("Success!") except Exception as e: show_apply_error(e)