Files
Nugget/devicemanagement/device_manager.py
2024-10-07 16:32:14 -04:00

292 lines
13 KiB
Python

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__)