import traceback import plistlib from pathlib import Path from PySide6.QtWidgets import QMessageBox 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.basic_plist_locations import FileLocationsList from Sparserestore.restore import restore_files, FileToRestore def show_error_msg(txt: str): detailsBox = QMessageBox() detailsBox.setIcon(QMessageBox.Critical) detailsBox.setWindowTitle("Error!") detailsBox.setText(txt) detailsBox.setDetailedText(str(traceback.format_exc())) detailsBox.exec() class DeviceManager: ## Class Functions def __init__(self): self.devices: list[Device] = [] self.data_singleton = DataSingleton() self.current_device_index = 0 self.apply_over_wifi = True self.skip_setup = True def get_devices(self): self.devices.clear() connected_devices = usbmux.list_devices() # 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 dev = Device( uuid=device.serial, name=vals['DeviceName'], version=vals['ProductVersion'], build=vals['BuildVersion'], model=vals['ProductType'], locale=ld.locale, ld=ld ) tweaks["RdarFix"].get_rdar_mode(vals['ProductType']) self.devices.append(dev) except Exception as e: print(f"ERROR with lockdown device with UUID {device.serial}") show_error_msg(type(e).__name__) 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 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 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_uuid(self) -> str: if self.data_singleton.current_device == None: return "" else: return self.data_singleton.current_device.uuid 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 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"], "CloudConfigurationUIComplete": True, "IsSupervised": False } 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) -> str: 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/": "SysSharedContainerDomain-.", "/var/containers/Data/SystemGroup/": "SysContainerDomain-." } for mapping in mappings.keys(): if path.startswith(mapping): new_path = path.replace(mapping, "") return mappings[mapping], 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) 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) 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=True, lockdown_client=self.data_singleton.current_device.ld) QMessageBox.information(None, "Success!", "All done! Your device will now restart.") update_label("Success!") except Exception as e: if "Find My" in str(e): detailsBox = QMessageBox() detailsBox.setIcon(QMessageBox.Critical) detailsBox.setWindowTitle("Error!") detailsBox.setText("Find My must be disabled in order to use this tool.") detailsBox.setDetailedText("Disable Find My from Settings (Settings -> [Your Name] -> Find My) and then try again.") detailsBox.exec() else: print(traceback.format_exc()) update_label("Failed to restore") show_error_msg(type(e).__name__) ## RESETTING MOBILE GESTALT def reset_mobilegestalt(self, update_label=lambda x: None): # restore to the device update_label("Restoring to device...") try: domain, file_path = self.get_domain_for_path("/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/com.apple.MobileGestalt.plist") restore_files(files=[FileToRestore( contents=b"", restore_path=file_path, domain=domain )], reboot=True, lockdown_client=self.data_singleton.current_device.ld) QMessageBox.information(None, "Success!", "All done! Your device will now restart.") update_label("Success!") except Exception as e: if "Find My" in str(e): detailsBox = QMessageBox() detailsBox.setIcon(QMessageBox.Critical) detailsBox.setWindowTitle("Error!") detailsBox.setText("Find My must be disabled in order to use this tool.") detailsBox.setDetailedText("Disable Find My from Settings (Settings -> [Your Name] -> Find My) and then try again.") detailsBox.exec() else: print(traceback.format_exc()) update_label("Failed to restore") show_error_msg(type(e).__name__)