diff --git a/OpenCore-Patcher.command b/OpenCore-Patcher.command index 458116e41..be77bfc75 100755 --- a/OpenCore-Patcher.command +++ b/OpenCore-Patcher.command @@ -238,7 +238,7 @@ B. Exit response = menu.start() - if getattr(sys, "frozen", False): + if getattr(sys, "frozen", False) and self.constants.recovery_status is False: subprocess.run("""osascript -e 'tell application "Terminal" to close first window' & exit""", shell=True) diff --git a/Resources/Build.py b/Resources/Build.py index 60ddd4fdc..d6dd1aecc 100644 --- a/Resources/Build.py +++ b/Resources/Build.py @@ -35,12 +35,6 @@ class BuildOpenCore: self.config = None self.constants: Constants.Constants = versions - def hexswap(self, input_hex: str): - hex_pairs = [input_hex[i:i + 2] for i in range(0, len(input_hex), 2)] - hex_rev = hex_pairs[::-1] - hex_str = "".join(["".join(x) for x in hex_rev]) - return hex_str.upper() - def build_efi(self): Utilities.cls() if not self.constants.custom_model: @@ -119,8 +113,8 @@ class BuildOpenCore: try: x = 1 for i in storage_devices: - storage_vendor = self.hexswap(binascii.hexlify(i["vendor-id"]).decode()[:4]) - storage_device = self.hexswap(binascii.hexlify(i["device-id"]).decode()[:4]) + storage_vendor = Utilities.hexswap(binascii.hexlify(i["vendor-id"]).decode()[:4]) + storage_device = Utilities.hexswap(binascii.hexlify(i["device-id"]).decode()[:4]) print(f'- Fixing PCIe Storage Controller ({x}) reporting') try: storage_path = [line.strip().split("= ", 1)[1] for line in storage_path_gfx.split("\n") if f'{storage_vendor}:{storage_device}'.lower() in line.strip()][0] @@ -141,8 +135,8 @@ class BuildOpenCore: try: x = 1 for i in nvme_devices: - nvme_vendor = self.hexswap(binascii.hexlify(i["vendor-id"]).decode()[:4]) - nvme_device = self.hexswap(binascii.hexlify(i["device-id"]).decode()[:4]) + nvme_vendor = Utilities.hexswap(binascii.hexlify(i["vendor-id"]).decode()[:4]) + nvme_device = Utilities.hexswap(binascii.hexlify(i["device-id"]).decode()[:4]) print(f'- Found 3rd Party NVMe SSD ({x}): {nvme_vendor}:{nvme_device}') nvme_aspm = i["pci-aspm-default"] try: @@ -435,8 +429,8 @@ class BuildOpenCore: try: x = 1 for i in mp_dgpu_devices: - mp_dgpu_vendor = self.hexswap(binascii.hexlify(i["vendor-id"]).decode()[:4]) - mp_dgpu_device = self.hexswap(binascii.hexlify(i["device-id"]).decode()[:4]) + mp_dgpu_vendor = Utilities.hexswap(binascii.hexlify(i["vendor-id"]).decode()[:4]) + mp_dgpu_device = Utilities.hexswap(binascii.hexlify(i["device-id"]).decode()[:4]) print(f'- Found dGPU ({x}): {mp_dgpu_vendor}:{mp_dgpu_device}') self.config["#Revision"][f"Hardware-MacPro-dGPU-{x}"] = f'{mp_dgpu_vendor}:{mp_dgpu_device}' diff --git a/Resources/Constants.py b/Resources/Constants.py index 8f7370e03..255a9897c 100644 --- a/Resources/Constants.py +++ b/Resources/Constants.py @@ -127,7 +127,11 @@ class Constants: def opencore_zip_source(self): return self.payload_path / Path(f"OpenCore/OpenCore-{self.opencore_build}.zip") @property def plist_template(self): return self.payload_path / Path(f"Config/config.plist") - + + # Mount Location + @property + def payload_mnt1_path(self): return self.payload_path / Path("mnt1") + # ACPI @property def pci_ssdt_path(self): return self.payload_path / Path("ACPI/SSDT-CPBG.aml") @@ -338,18 +342,18 @@ class Constants: def skylight_path(self): return self.payload_apple_private_frameworks_path_accel / Path("SkyLight.framework") csr_values = { - "CSR_ALLOW_UNTRUSTED_KEXTS ": False, # 0x1 - Introduced in El Capitan - "CSR_ALLOW_UNRESTRICTED_FS ": False, # 0x2 - Introduced in El Capitan - "CSR_ALLOW_TASK_FOR_PID ": False, # 0x4 - Introduced in El Capitan - "CSR_ALLOW_KERNEL_DEBUGGER ": False, # 0x8 - Introduced in El Capitan - "CSR_ALLOW_APPLE_INTERNAL ": False, # 0x10 - Introduced in El Capitan - "CSR_ALLOW_UNRESTRICTED_DTRACE ": False, # 0x20 - Introduced in El Capitan - "CSR_ALLOW_UNRESTRICTED_NVRAM ": False, # 0x40 - Introduced in El Capitan - "CSR_ALLOW_DEVICE_CONFIGURATION ": False, # 0x80 - Introduced in El Capitan - "CSR_ALLOW_ANY_RECOVERY_OS ": False, # 0x100 - Introduced in Sierra - "CSR_ALLOW_UNAPPROVED_KEXTS ": False, # 0x200 - Introduced in High Sierra - "CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE": False, # 0x400 - Introduced in Mojave - "CSR_ALLOW_UNAUTHENTICATED_ROOT ": False, # 0x800 - Introduced in Big Sur + "CSR_ALLOW_UNTRUSTED_KEXTS": False, # 0x1 - Introduced in El Capitan # noqa: E241 + "CSR_ALLOW_UNRESTRICTED_FS": False, # 0x2 - Introduced in El Capitan # noqa: E241 + "CSR_ALLOW_TASK_FOR_PID": False, # 0x4 - Introduced in El Capitan # noqa: E241 + "CSR_ALLOW_KERNEL_DEBUGGER": False, # 0x8 - Introduced in El Capitan # noqa: E241 + "CSR_ALLOW_APPLE_INTERNAL": False, # 0x10 - Introduced in El Capitan # noqa: E241 + "CSR_ALLOW_UNRESTRICTED_DTRACE": False, # 0x20 - Introduced in El Capitan # noqa: E241 + "CSR_ALLOW_UNRESTRICTED_NVRAM": False, # 0x40 - Introduced in El Capitan # noqa: E241 + "CSR_ALLOW_DEVICE_CONFIGURATION": False, # 0x80 - Introduced in El Capitan # noqa: E241 + "CSR_ALLOW_ANY_RECOVERY_OS": False, # 0x100 - Introduced in Sierra # noqa: E241 + "CSR_ALLOW_UNAPPROVED_KEXTS": False, # 0x200 - Introduced in High Sierra # noqa: E241 + "CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE": False, # 0x400 - Introduced in Mojave # noqa: E241 + "CSR_ALLOW_UNAUTHENTICATED_ROOT": False, # 0x800 - Introduced in Big Sur # noqa: E241 } sbm_values = [ diff --git a/Resources/DeviceProbe.py b/Resources/DeviceProbe.py index fab8d4b7b..9b25dbe4c 100644 --- a/Resources/DeviceProbe.py +++ b/Resources/DeviceProbe.py @@ -6,18 +6,12 @@ import binascii import plistlib import subprocess -from Resources import Constants +from Resources import Constants, Utilities class pci_probe: def __init__(self): self.constants = Constants.Constants() - def hexswap(self, input_hex: str): - hex_pairs = [input_hex[i:i + 2] for i in range(0, len(input_hex), 2)] - hex_rev = hex_pairs[::-1] - hex_str = "".join(["".join(x) for x in hex_rev]) - return hex_str.upper() - # Converts given device IDs to DeviceProperty pathing, requires ACPI pathing as DeviceProperties shouldn't be used otherwise def deviceproperty_probe(self, vendor_id, device_id, acpi_path): gfxutil_output: str = subprocess.run([self.constants.gfxutil_path] + f"-v".split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode() @@ -56,8 +50,8 @@ class pci_probe: def gpu_probe(self, gpu_type): try: devices = plistlib.loads(subprocess.run(f"ioreg -r -n {gpu_type} -a".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) - vendor_id = self.hexswap(binascii.hexlify(devices[0]["vendor-id"]).decode()[:4]) - device_id = self.hexswap(binascii.hexlify(devices[0]["device-id"]).decode()[:4]) + vendor_id = Utilities.hexswap(binascii.hexlify(devices[0]["vendor-id"]).decode()[:4]) + device_id = Utilities.hexswap(binascii.hexlify(devices[0]["device-id"]).decode()[:4]) try: acpi_path = devices[0]["acpi-path"] acpi_path = self.acpi_strip(acpi_path) @@ -70,14 +64,14 @@ class pci_probe: return "", "", "" except IndexError: print(f"- No IOService entry found for {gpu_type} (I)") - return "", "", "", "" + return "", "", "" def wifi_probe(self): devices = plistlib.loads(subprocess.run("ioreg -c IOPCIDevice -r -d2 -a".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) try: devices = [i for i in devices if i["class-code"] == binascii.unhexlify(self.constants.classcode_wifi)] - vendor_id = self.hexswap(binascii.hexlify(devices[0]["vendor-id"]).decode()[:4]) - device_id = self.hexswap(binascii.hexlify(devices[0]["device-id"]).decode()[:4]) + vendor_id = Utilities.hexswap(binascii.hexlify(devices[0]["vendor-id"]).decode()[:4]) + device_id = Utilities.hexswap(binascii.hexlify(devices[0]["device-id"]).decode()[:4]) ioname = devices[0]["IOName"] try: acpi_path = devices[0]["acpi-path"] @@ -91,4 +85,4 @@ class pci_probe: return "", "", "", "" except IndexError: print(f"- No IOService entry found for Wireless Card (I)") - return "", "", "", "" \ No newline at end of file + return "", "", "", "" diff --git a/Resources/SysPatch.py b/Resources/SysPatch.py index ed68d0e56..5d51a81d4 100644 --- a/Resources/SysPatch.py +++ b/Resources/SysPatch.py @@ -4,31 +4,46 @@ # - Full System/Library Snapshotting (need to research how Apple achieves this) # - Temporary Work-around: sudo bless --mount /System/Volumes/Update/mnt1 --bootefi --last-sealed-snapshot # - Work-around battery throttling on laptops with no battery (IOPlatformPluginFamily.kext/Contents/PlugIns/ACPI_SMC_PlatformPlugin.kext/Contents/Resources/) -from __future__ import print_function +import os import plistlib import shutil import subprocess import zipfile -import os from pathlib import Path +from typing import Any -from Resources import Constants, ModelArray, PCIIDArray, Utilities, DeviceProbe +from Resources import Constants, DeviceProbe, ModelArray, PCIIDArray, Utilities class PatchSysVolume: def __init__(self, model, versions): self.model = model self.constants: Constants.Constants = versions + self.sip_patch_status = True + self.root_mount_path = None + self.sip_status = None - def hexswap(self, input_hex: str): - hex_pairs = [input_hex[i:i + 2] for i in range(0, len(input_hex), 2)] - hex_rev = hex_pairs[::-1] - hex_str = "".join(["".join(x) for x in hex_rev]) - return hex_str.upper() + # TODO: Put this in a better place + if self.constants.recovery_status is True: + if not Path("/Volumes/mnt1").exists: + self.elevated(["mkdir", "/Volumes/mnt1"], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.mount_location = "/Volumes/mnt1" + else: + self.mount_location = "/System/Volumes/Update/mnt1" + self.mount_extensions = f"{self.mount_location}/System/Library/Extensions" + self.mount_frameworks = f"{self.mount_location}/System/Library/Frameworks" + self.mount_lauchd = f"{self.mount_location}/System/Library/LaunchDaemons" + self.mount_private_frameworks = f"{self.mount_location}/System/Library/PrivateFrameworks" - def csr_decode(self, sip_raw, print_status): - sip_int = int.from_bytes(sip_raw, byteorder='little') + def elevated(self, *args, **kwargs) -> subprocess.CompletedProcess[Any]: + if os.getuid() == 0: + return subprocess.run(*args, **kwargs) + else: + return subprocess.run(["sudo"] + [args[0][0]] + args[0][1:], **kwargs) + + def csr_decode(self, print_status): + sip_int = int.from_bytes(self.sip_status, byteorder="little") i = 0 for current_sip_bit in self.constants.csr_values: if sip_int & (1 << i): @@ -39,45 +54,146 @@ class PatchSysVolume: if print_status is True: print(f"- {current_sip_bit}\t {temp}") i = i + 1 - if ((self.constants.csr_values["CSR_ALLOW_UNTRUSTED_KEXTS "] is True) \ - and (self.constants.csr_values["CSR_ALLOW_UNRESTRICTED_FS "] is True) \ - and (self.constants.csr_values["CSR_ALLOW_UNRESTRICTED_DTRACE "] is True) \ - and (self.constants.csr_values["CSR_ALLOW_UNRESTRICTED_NVRAM "] is True) \ - and (self.constants.csr_values["CSR_ALLOW_DEVICE_CONFIGURATION "] is True) \ - and (self.constants.csr_values["CSR_ALLOW_UNAPPROVED_KEXTS "] is True) \ - and (self.constants.csr_values["CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE"] is True) \ - and (self.constants.csr_values["CSR_ALLOW_UNAUTHENTICATED_ROOT "] is True)): + + sip_needs_change = all( + self.constants.csr_values[i] + for i in [ + "CSR_ALLOW_UNTRUSTED_KEXTS", + "CSR_ALLOW_UNRESTRICTED_FS", + "CSR_ALLOW_UNRESTRICTED_DTRACE", + "CSR_ALLOW_UNRESTRICTED_NVRAM", + "CSR_ALLOW_DEVICE_CONFIGURATION", + "CSR_ALLOW_UNAPPROVED_KEXTS", + "CSR_ALLOW_EXECUTABLE_POLICY_OVERRIDE", + "CSR_ALLOW_UNAUTHENTICATED_ROOT", + ] + ) + if sip_needs_change is True: self.sip_patch_status = False else: self.sip_patch_status = True + def recovery_root_mount(self): + def human_fmt(num): + for unit in ["B", "KB", "MB", "GB", "TB", "PB"]: + if abs(num) < 1000.0: + return "%3.1f %s" % (num, unit) + num /= 1000.0 + return "%.1f %s" % (num, "EB") + + print("- Starting Root Volume Picker") + # Planned logic: + # Load "diskutil list -plist" + # Find all APFSVolumes entries where VolumeName is not named Update, VM, Recovery or Preboot + # Omit any VolumeName entries containing "- Data" + # Parse remianing options for macOS 11.x with /Volumes/$disk/System/Library/CoreServices/SystemVersion.plist + # List remaining drives as user options + all_disks = {} + disks = plistlib.loads(subprocess.run("diskutil list -plist".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) + for disk in disks["AllDisksAndPartitions"]: + disk_info = plistlib.loads(subprocess.run(f"diskutil info -plist {disk['DeviceIdentifier']}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) + try: + all_disks[disk["DeviceIdentifier"]] = {"identifier": disk_info["DeviceNode"], "name": disk_info["MediaName"], "size": disk_info["TotalSize"], "partitions": {}} + for partition in disk["Partitions"] + disk.get("APFSVolumes", []): + partition_info = plistlib.loads(subprocess.run(f"diskutil info -plist {partition['DeviceIdentifier']}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) + all_disks[disk["DeviceIdentifier"]]["partitions"][partition["DeviceIdentifier"]] = { + "fs": partition_info.get("FilesystemType", partition_info["Content"]), + "type": partition_info["Content"], + "name": partition_info.get("VolumeName", ""), + "size": partition_info["TotalSize"], + "sealed": partition_info.get("Sealed", "No"), + } + except KeyError: + # Avoid crashing with CDs installed + continue + menu = Utilities.TUIMenu( + ["Select Disk"], + "Please select the disk you would like to patch: ", + in_between=["Missing disks? Ensure they have a macOS Big Sur install present."], + return_number_instead_of_direct_call=True, + loop=True, + ) + for disk in all_disks: + if not any(all_disks[disk]["partitions"][partition]["fs"] == "apfs" for partition in all_disks[disk]["partitions"]): + continue + menu.add_menu_option(f"{disk}: {all_disks[disk]['name']} ({human_fmt(all_disks[disk]['size'])})", key=disk[4:]) + + response = menu.start() + + if response == -1: + return + + disk_identifier = "disk" + response + selected_disk = all_disks[disk_identifier] + + menu = Utilities.TUIMenu( + ["Select Partition"], + "Please select the partition you would like to install OpenCore to: ", + return_number_instead_of_direct_call=True, + loop=True, + in_between=["Missing disks? Ensure they have a macOS Big Sur install present.", "", "* denotes likely candidate."], + ) + # TODO: check if Big Sur, when macOS 12 comes out + for partition in selected_disk["partitions"]: + if selected_disk["partitions"][partition]["fs"] != "apfs": + continue + text = f"{partition}: {selected_disk['partitions'][partition]['name']} ({human_fmt(selected_disk['partitions'][partition]['size'])})" + if selected_disk["partitions"][partition]["sealed"] != "No": + text += " *" + menu.add_menu_option(text, key=partition[len(disk_identifier) + 1 :]) + + response = menu.start() + + if response == -1: + return + else: + return f"{disk_identifier}s{response}" + def find_mount_root_vol(self, patch): - root_partition_info = plistlib.loads(subprocess.run("diskutil info -plist /".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) - self.root_mount_path = root_partition_info["DeviceIdentifier"] - self.mount_location = "/System/Volumes/Update/mnt1" - self.mount_extensions = f"{self.mount_location}/System/Library/Extensions" - self.mount_frameworks = f"{self.mount_location}/System/Library/Frameworks" - self.mount_lauchd = f"{self.mount_location}/System/Library/LaunchDaemons" - self.mount_private_frameworks = f"{self.mount_location}/System/Library/PrivateFrameworks" + if self.constants.recovery_status is True: + print("- Running RecoveryOS logic") + self.root_mount_path = self.recovery_root_mount() + if not self.root_mount_path: + return + print(f"- Root Mount Path: {self.root_mount_path}") + if not Path(self.constants.payload_mnt1_path).exists(): + print("- Creating mnt1 folder") + Path(self.constants.payload_mnt1_path).mkdir() + else: + root_partition_info = plistlib.loads(subprocess.run("diskutil info -plist /".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) + self.root_mount_path = root_partition_info["DeviceIdentifier"] if self.root_mount_path.startswith("disk"): - self.root_mount_path = self.root_mount_path[:-2] if self.root_mount_path.endswith('s1') else self.root_mount_path + if self.constants.recovery_status is False: + self.root_mount_path = self.root_mount_path[:-2] if self.root_mount_path.count("s") > 1 else self.root_mount_path print(f"- Found Root Volume at: {self.root_mount_path}") if Path(self.mount_extensions).exists(): print("- Root Volume is already mounted") if patch is True: self.patch_root_vol() + return True else: self.unpatch_root_vol() + return True else: - print("- Mounting drive as writable") - subprocess.run(["sudo", "mount", "-o", "nobrowse", "-t", "apfs", f"/dev/{self.root_mount_path}", self.mount_location], stdout=subprocess.PIPE).stdout.decode().strip().encode() + if self.constants.recovery_status is True: + print("- Mounting drive as writable in Recovery") + + umount_drive = plistlib.loads(subprocess.run(f"diskutil info -plist {self.root_mount_path}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) + umount_drive = umount_drive["VolumeName"] + self.elevated(["umount", f'/Volumes/{umount_drive}'], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["mount", "-t", "apfs", "-rw", f"/dev/{self.root_mount_path}", self.mount_location], stdout=subprocess.PIPE).stdout.decode().strip().encode() + else: + print("- Mounting drive as writable in OS") + self.elevated(["mount", "-o", "nobrowse", "-t", "apfs", f"/dev/{self.root_mount_path}", self.mount_location], stdout=subprocess.PIPE).stdout.decode().strip().encode() if Path(self.mount_extensions).exists(): print("- Successfully mounted the Root Volume") if patch is True: self.patch_root_vol() + return True else: self.unpatch_root_vol() + return True else: print("- Failed to mount the Root Volume") else: @@ -88,7 +204,7 @@ class PatchSysVolume: delete_path = Path(self.mount_extensions) / Path(delete_current_kext) if Path(delete_path).exists(): print(f"- Deleting {delete_current_kext}") - subprocess.run(["sudo", "rm", "-R", delete_path], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["sudo", "rm", "-R", delete_path], stdout=subprocess.PIPE).stdout.decode().strip().encode() else: print(f"- Couldn't find {delete_current_kext}, skipping") @@ -97,23 +213,23 @@ class PatchSysVolume: existing_path = Path(self.mount_extensions) / Path(add_current_kext) if Path(existing_path).exists(): print(f"- Found conflicting kext, Deleting Root Volume's {add_current_kext}") - subprocess.run(["sudo", "rm", "-R", existing_path], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["rm", "-R", existing_path], stdout=subprocess.PIPE).stdout.decode().strip().encode() print(f"- Adding {add_current_kext}") - subprocess.run(["sudo", "cp", "-R", f"{vendor_location}/{add_current_kext}", self.mount_extensions], stdout=subprocess.PIPE).stdout.decode().strip().encode() - subprocess.run(["sudo", "chmod", "-Rf", "755", f"{self.mount_extensions}/{add_current_kext}"], stdout=subprocess.PIPE).stdout.decode().strip().encode() - subprocess.run(["sudo", "chown", "-Rf", "root:wheel", f"{self.mount_extensions}/{add_current_kext}"], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["cp", "-R", f"{vendor_location}/{add_current_kext}", self.mount_extensions], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["chmod", "-Rf", "755", f"{self.mount_extensions}/{add_current_kext}"], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["chown", "-Rf", "root:wheel", f"{self.mount_extensions}/{add_current_kext}"], stdout=subprocess.PIPE).stdout.decode().strip().encode() def add_brightness_patch(self): print("- Merging legacy Brightness Control Patches") self.delete_old_binaries(ModelArray.DeleteBrightness) self.add_new_binaries(ModelArray.AddBrightness, self.constants.legacy_brightness) - subprocess.run(["sudo", "ditto", self.constants.payload_apple_private_frameworks_path_brightness, self.mount_private_frameworks], stdout=subprocess.PIPE).stdout.decode().strip().encode() - subprocess.run(["sudo", "chmod", "-Rf", "755", f"{self.mount_private_frameworks}/DisplayServices.framework"], stdout=subprocess.PIPE).stdout.decode().strip().encode() - subprocess.run(["sudo", "chown", "-Rf", "root:wheel", f"{self.mount_private_frameworks}/DisplayServices.framework"], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["ditto", self.constants.payload_apple_private_frameworks_path_brightness, self.mount_private_frameworks], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["chmod", "-Rf", "755", f"{self.mount_private_frameworks}/DisplayServices.framework"], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["chown", "-Rf", "root:wheel", f"{self.mount_private_frameworks}/DisplayServices.framework"], stdout=subprocess.PIPE).stdout.decode().strip().encode() def gpu_accel_patches_11(self): - igpu_vendor,igpu_device,igpu_acpi = DeviceProbe.pci_probe().gpu_probe("IGPU") - dgpu_vendor,dgpu_device,dgpu_acpi = DeviceProbe.pci_probe().gpu_probe("GFX0") + igpu_vendor, igpu_device, igpu_acpi = DeviceProbe.pci_probe().gpu_probe("IGPU") + dgpu_vendor, dgpu_device, dgpu_acpi = DeviceProbe.pci_probe().gpu_probe("GFX0") if dgpu_vendor: print(f"- Found GFX0: {dgpu_vendor}:{dgpu_device}") if dgpu_vendor == self.constants.pci_nvidia: @@ -123,7 +239,7 @@ class PatchSysVolume: self.add_new_binaries(ModelArray.AddGeneralAccel, self.constants.legacy_general_path) self.add_new_binaries(ModelArray.AddNvidiaAccel11, self.constants.legacy_nvidia_path) # TODO: Enable below code if macOS 12 drops support - #elif dgpu_device in PCIIDArray.nvidia_ids().kepler_ids and self.constants.detected_os > self.constants.big_sur: + # elif dgpu_device in PCIIDArray.nvidia_ids().kepler_ids and self.constants.detected_os > self.constants.big_sur: # print("- Merging legacy Nvidia Kepler Kexts and Bundles") # self.add_new_binaries(ModelArray.AddNvidiaKeplerAccel11, self.constants.legacy_nvidia_kepler_path) elif dgpu_vendor == self.constants.pci_amd_ati: @@ -162,7 +278,7 @@ class PatchSysVolume: self.add_new_binaries(ModelArray.AddIntelGen2Accel, self.constants.legacy_intel_gen2_path) # TODO: Enable below code if macOS 12 drops support - #elif igpu_device in PCIIDArray.intel_ids().ivy_ids: + # elif igpu_device in PCIIDArray.intel_ids().ivy_ids: # print("- Merging legacy Intel 3rd Gen Kexts and Bundles") # self.add_new_binaries(ModelArray.AddIntelGen3Accel, self.constants.legacy_intel_gen3_path) elif igpu_vendor == self.constants.pci_nvidia: @@ -175,7 +291,7 @@ class PatchSysVolume: # Frameworks print("- Merging legacy Frameworks") - subprocess.run(["sudo", "ditto", self.constants.payload_apple_frameworks_path_accel, self.mount_frameworks], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["ditto", self.constants.payload_apple_frameworks_path_accel, self.mount_frameworks], stdout=subprocess.PIPE).stdout.decode().strip().encode() if self.model in ModelArray.LegacyBrightness: self.add_brightness_patch() @@ -183,20 +299,19 @@ class PatchSysVolume: # LaunchDaemons if Path(self.mount_lauchd / Path("HiddHack.plist")).exists(): print("- Removing legacy HiddHack") - subprocess.run(["sudo", "rm", f"{self.mount_lauchd}/HiddHack.plist"], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["rm", f"{self.mount_lauchd}/HiddHack.plist"], stdout=subprocess.PIPE).stdout.decode().strip().encode() print("- Adding IOHID-Fixup.plist") - subprocess.run(["sudo", "ditto", self.constants.payload_apple_lauchd_path_accel, self.mount_lauchd], stdout=subprocess.PIPE).stdout.decode().strip().encode() - subprocess.run(["sudo", "chmod", "755", f"{self.mount_lauchd}/IOHID-Fixup.plist"], stdout=subprocess.PIPE).stdout.decode().strip().encode() - subprocess.run(["sudo", "chown", "root:wheel", f"{self.mount_lauchd}/IOHID-Fixup.plist"], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["ditto", self.constants.payload_apple_lauchd_path_accel, self.mount_lauchd], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["chmod", "755", f"{self.mount_lauchd}/IOHID-Fixup.plist"], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["chown", "root:wheel", f"{self.mount_lauchd}/IOHID-Fixup.plist"], stdout=subprocess.PIPE).stdout.decode().strip().encode() # PrivateFrameworks print("- Merging legacy PrivateFrameworks") - subprocess.run(["sudo", "ditto", self.constants.payload_apple_private_frameworks_path_accel, self.mount_private_frameworks], stdout=subprocess.PIPE).stdout.decode().strip().encode() - + self.elevated(["ditto", self.constants.payload_apple_private_frameworks_path_accel, self.mount_private_frameworks], stdout=subprocess.PIPE).stdout.decode().strip().encode() # Sets AppKit to Catalina Window Drawing codepath # Disabled upon ASentientBot request - #print("- Enabling NSDefenestratorModeEnabled") - #subprocess.run("defaults write -g NSDefenestratorModeEnabled -bool true".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode() + # print("- Enabling NSDefenestratorModeEnabled") + # subprocess.run("defaults write -g NSDefenestratorModeEnabled -bool true".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode() def patch_root_vol(self): print(f"- Detecting patches for {self.model}") @@ -206,11 +321,20 @@ class PatchSysVolume: # Perhaps a basic py2 script to run in recovery to restore # Ensures no .DS_Stores got in print("- Preparing Files") - subprocess.run(["sudo", "find", self.constants.payload_apple_root_path, "-name", "'.DS_Store'", "-delete"], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["find", self.constants.payload_apple_root_path, "-name", "'.DS_Store'", "-delete"], stdout=subprocess.PIPE).stdout.decode().strip().encode() if self.model in ModelArray.LegacyGPU or self.constants.assume_legacy is True: - dgpu_vendor,dgpu_device,dgpu_acpi = DeviceProbe.pci_probe().gpu_probe("GFX0") - if dgpu_vendor and dgpu_vendor == self.constants.pci_amd_ati and (dgpu_device in PCIIDArray.amd_ids().polaris_ids or dgpu_device in PCIIDArray.amd_ids().vega_ids or dgpu_device in PCIIDArray.amd_ids().navi_ids or dgpu_device in PCIIDArray.amd_ids().legacy_gcn_ids): + dgpu_vendor, dgpu_device, dgpu_acpi = DeviceProbe.pci_probe().gpu_probe("GFX0") + if ( + dgpu_vendor + and dgpu_vendor == self.constants.pci_amd_ati + and ( + dgpu_device in PCIIDArray.amd_ids().polaris_ids + or dgpu_device in PCIIDArray.amd_ids().vega_ids + or dgpu_device in PCIIDArray.amd_ids().navi_ids + or dgpu_device in PCIIDArray.amd_ids().legacy_gcn_ids + ) + ): print("- Detected Metal-based AMD GPU, skipping legacy patches") elif dgpu_vendor and dgpu_vendor == self.constants.pci_nvidia and dgpu_device in PCIIDArray.nvidia_ids().kepler_ids: print("- Detected Metal-based Nvidia GPU, skipping legacy patches") @@ -230,13 +354,13 @@ class PatchSysVolume: def unpatch_root_vol(self): print("- Reverting to last signed APFS snapshot") - subprocess.run(["sudo", "bless", "--mount", self.mount_location, "--bootefi", "--last-sealed-snapshot"], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["bless", "--mount", self.mount_location, "--bootefi", "--last-sealed-snapshot"], stdout=subprocess.PIPE).stdout.decode().strip().encode() def rebuild_snapshot(self): if self.constants.gui_mode is False: input("Press [ENTER] to continue with cache rebuild") print("- Rebuilding Kernel Cache (This may take some time)") - result = subprocess.run(["sudo", "kmutil", "install", "--volume-root", self.mount_location, "--update-all"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + result = self.elevated(["kmutil", "install", "--volume-root", self.mount_location, "--update-all"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: self.success_status = False @@ -251,18 +375,18 @@ class PatchSysVolume: if self.constants.gui_mode is False: input("Press [ENTER] to continue with snapshotting") print("- Creating new APFS snapshot") - subprocess.run(["sudo", "bless", "--folder", f"{self.mount_location}/System/Library/CoreServices", "--bootefi", "--create-snapshot"], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["bless", "--folder", f"{self.mount_location}/System/Library/CoreServices", "--bootefi", "--create-snapshot"], stdout=subprocess.PIPE).stdout.decode().strip().encode() def unmount_drive(self): print("- Unmounting Root Volume (Don't worry if this fails)") - subprocess.run(["sudo", "diskutil", "unmount", self.root_mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode() + self.elevated(["diskutil", "unmount", self.root_mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode() def check_status(self): nvram_dump = plistlib.loads(subprocess.run("nvram -x -p".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) try: self.sip_status = nvram_dump["csr-active-config"] except KeyError: - self.sip_status = b'\x00\x00\x00\x00' + self.sip_status = b"\x00\x00\x00\x00" self.smb_model: str = subprocess.run("nvram 94B73556-2197-4702-82A8-3E1337DAFBFB:HardwareModel ".split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode() if not self.smb_model.startswith("nvram: Error getting variable"): @@ -274,13 +398,14 @@ class PatchSysVolume: else: self.smb_status = False self.fv_status = True - self.fv_status: str = subprocess.run("fdesetup status".split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode() - if self.fv_status.startswith("FileVault is Off"): - self.fv_status = False + if self.constants.recovery_status == False: + self.fv_status: str = subprocess.run("fdesetup status".split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode() + if self.fv_status.startswith("FileVault is Off"): + self.fv_status = False else: - self.fv_status = True - self.sip_patch_status = True - self.csr_decode(self.sip_status, False) + # Assume FileVault is off for Recovery purposes + self.fv_status = False + self.csr_decode(False) def check_files(self): if Path(self.constants.payload_apple_root_path).exists(): @@ -299,7 +424,13 @@ class PatchSysVolume: def download_files(self): Utilities.cls() print("- Downloading Apple binaries") - popen_oclp = subprocess.Popen(["curl", "-S", "-L", f"{self.constants.url_apple_binaries}{self.constants.payload_version}.zip", "--output", self.constants.payload_apple_root_path_zip], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) + popen_oclp = subprocess.Popen( + ["curl", "-S", "-L", f"{self.constants.url_apple_binaries}{self.constants.payload_version}.zip", "--output", self.constants.payload_apple_root_path_zip], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + ) for stdout_line in iter(popen_oclp.stdout.readline, ""): print(stdout_line, end="") popen_oclp.stdout.close() @@ -331,7 +462,7 @@ class PatchSysVolume: elif self.model not in ModelArray.SupportedSMBIOS11 and self.constants.assume_legacy is False: print("Cannot run on this machine, model is unsupported!") elif self.constants.detected_os < self.constants.big_sur: - print(f"Cannot run on this OS, requires macOS 11!") + print("Cannot run on this OS, requires macOS 11!") else: self.check_status() Utilities.cls() @@ -341,7 +472,8 @@ class PatchSysVolume: input("\nPress [ENTER] to continue") self.check_files() if self.constants.payload_apple_root_path.exists(): - self.find_mount_root_vol(True) + if not self.find_mount_root_vol(True): + return self.unmount_drive() print("- Patching complete") if self.success_status is True: @@ -351,7 +483,7 @@ class PatchSysVolume: if self.sip_patch_status is True: print("SIP set incorrectly, cannot patch on this machine!") print("Please disable SIP and SecureBootModel in Patcher Settings") - self.csr_decode(self.sip_status, True) + self.csr_decode(True) print("") if self.smb_status is True: print("SecureBootModel set incorrectly, unable to patch!") @@ -368,7 +500,7 @@ class PatchSysVolume: if self.constants.custom_model is not None: print("Unpatching must be done on target machine!") elif self.constants.detected_os < self.constants.big_sur: - print(f"Cannot run on this OS, requires macOS 11!") + print("Cannot run on this OS, requires macOS 11!") else: self.check_status() Utilities.cls() @@ -376,14 +508,15 @@ class PatchSysVolume: print("- Detected SIP and SecureBootModel are disabled, continuing") if self.constants.gui_mode is False: input("\nPress [ENTER] to continue") - self.find_mount_root_vol(False) + if not self.find_mount_root_vol(False): + return self.unmount_drive() print("- Unpatching complete") print("\nPlease reboot the machine for patches to take effect") if self.sip_patch_status is True: print("SIP set incorrectly, cannot unpatch on this machine!") print("Please disable SIP and SecureBootModel in Patcher Settings") - self.csr_decode(self.sip_status, True) + self.csr_decode(True) print("") if self.smb_status is True: print("SecureBootModel set incorrectly, unable to unpatch!") diff --git a/Resources/Utilities.py b/Resources/Utilities.py index ba602aebc..44732895b 100644 --- a/Resources/Utilities.py +++ b/Resources/Utilities.py @@ -2,36 +2,40 @@ from __future__ import print_function import os -import math as m +import math import plistlib import subprocess + +def hexswap(input_hex: str): + hex_pairs = [input_hex[i : i + 2] for i in range(0, len(input_hex), 2)] + hex_rev = hex_pairs[::-1] + hex_str = "".join(["".join(x) for x in hex_rev]) + return hex_str.upper() + + def header(lines): lines = [i for i in lines if i is not None] total_length = len(max(lines, key=len)) + 4 print("#" * (total_length)) for line in lines: - left_side = m.floor(((total_length - 2 - len(line.strip())) / 2)) + left_side = math.floor(((total_length - 2 - len(line.strip())) / 2)) print("#" + " " * left_side + line.strip() + " " * (total_length - len("#" + " " * left_side + line.strip()) - 1) + "#") print("#" * total_length) + def check_recovery(): root_partition_info = plistlib.loads(subprocess.run("diskutil info -plist /".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) - if root_partition_info["VolumeName"] == "macOS Base System" and \ - root_partition_info["FilesystemType"] == "apfs" and \ - root_partition_info["BusProtocol"] == "Disk Image": + if root_partition_info["VolumeName"] == "macOS Base System" and root_partition_info["FilesystemType"] == "apfs" and root_partition_info["BusProtocol"] == "Disk Image": return True else: return False + def cls(): - # RecoveryOS doesn't support terminal clearing - if check_recovery() == False: - os.system('cls' if os.name == 'nt' else 'clear') - else: - # Default terminal window is 24 lines tall - for i in range(24): - print("") + # We only support macOS, so + #print("\u001Bc") + pass # def menu(title, prompt, menu_options, add_quit=True, auto_number=False, in_between=[], top_level=False): # return_option = ["Q", "Quit", None] if top_level else ["B", "Back", None] @@ -61,7 +65,7 @@ def cls(): # menu_options[keys.index(selected.upper())][2]() if menu_options[keys.index(selected.upper())][2] else None -class TUIMenu(): +class TUIMenu: def __init__(self, title, prompt, options=None, return_number_instead_of_direct_call=False, add_quit=True, auto_number=False, in_between=None, top_level=False, loop=False): self.title = title self.prompt = prompt @@ -80,8 +84,7 @@ class TUIMenu(): def start(self): return_option = ["Q", "Quit"] if self.top_level else ["B", "Back"] if self.add_quit and not self.added_quit: - self.add_menu_option( - return_option[1], function=None, key=return_option[0]) + self.add_menu_option(return_option[1], function=None, key=return_option[0]) self.added_quit = True while True: @@ -120,7 +123,7 @@ class TUIMenu(): return -class TUIOnlyPrint(): +class TUIOnlyPrint: def __init__(self, title, prompt, in_between=None): self.title = title self.prompt = prompt