Merge pull request #232 from dortania/recovery-tests

Partial Support Patching from RecoveryOS
This commit is contained in:
Mykola Grymalyuk
2021-05-19 13:36:28 -06:00
committed by GitHub
6 changed files with 253 additions and 125 deletions

View File

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

View File

@@ -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}'

View File

@@ -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 = [

View File

@@ -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 "", "", "", ""
return "", "", "", ""

View File

@@ -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!")

View File

@@ -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