Nest sys_patch libraries

This commit is contained in:
Mykola Grymalyuk
2022-11-13 19:25:55 -07:00
parent 48de65dd30
commit 7543606a1e
9 changed files with 14 additions and 8 deletions
+690
View File
@@ -0,0 +1,690 @@
# Framework for mounting and patching macOS root volume
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
# System based off of Apple's Kernel Debug Kit (KDK)
# - https://developer.apple.com/download/all/
# The system relies on mounting the APFS volume as a live read/write volume
# We perform our required edits, then create a new snapshot for the system boot
# The manual process is as follows:
# 1. Find the Root Volume
# 'diskutil info / | grep "Device Node:"'
# 2. Convert Snapshot Device Node to Root Volume Device Node
# /dev/disk3s1s1 -> /dev/disk3s1 (strip last 's1')
# 3. Mount the APFS volume as a read/write volume
# 'sudo mount -o nobrowse -t apfs /dev/disk5s5 /System/Volumes/Update/mnt1'
# 4. Perform edits to the system (ie. create new KernelCollection)
# 'sudo kmutil install --volume-root /System/Volumes/Update/mnt1/ --update-all'
# 5. Create a new snapshot for the system boot
# 'sudo bless --folder /System/Volumes/Update/mnt1/System/Library/CoreServices --bootefi --create-snapshot'
# Additionally Apple's APFS snapshot system supports system rollbacks:
# 'sudo bless --mount /System/Volumes/Update/mnt1 --bootefi --last-sealed-snapshot'
# Note: root volume rollbacks are unstable in Big Sur due to quickly discarding the original snapshot
# - Generally within 2~ boots, the original snapshot is discarded
# - Monterey always preserves the original snapshot allowing for reliable rollbacks
# Alternative to mounting via 'mount', Apple's update system uses 'mount_apfs' directly
# '/sbin/mount_apfs -R /dev/disk5s5 /System/Volumes/Update/mnt1'
# With macOS Ventura, you will also need to install the KDK onto root if you plan to use kmutil
# This is because Apple removed on-disk binaries (ref: https://github.com/dortania/OpenCore-Legacy-Patcher/issues/998)
# 'sudo ditto /Library/Developer/KDKs/<KDK Version>/System /System/Volumes/Update/mnt1/System'
import plistlib
import shutil
import subprocess
from pathlib import Path
from datetime import datetime
from resources import constants, utilities, kdk_handler
from resources.sys_patch import sys_patch_download, sys_patch_detect, sys_patch_auto, sys_patch_helpers
from data import os_data
class PatchSysVolume:
def __init__(self, model, versions, hardware_details=None):
self.model = model
self.constants: constants.Constants() = versions
self.computer = self.constants.computer
self.root_mount_path = None
self.root_supports_snapshot = utilities.check_if_root_is_apfs_snapshot()
self.constants.root_patcher_succeeded = False # Reset Variable each time we start
self.constants.needs_to_open_preferences = False
self.patch_set_dictionary = {}
self.needs_kmutil_exemptions = False # For '/Library/Extensions' rebuilds
self.kdk_path = None
# GUI will detect hardware patches before starting PatchSysVolume()
# However the TUI will not, so allow for data to be passed in manually avoiding multiple calls
if hardware_details is None:
hardware_details = sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).detect_patch_set()
self.hardware_details = hardware_details
self.init_pathing(custom_root_mount_path=None, custom_data_mount_path=None)
self.skip_root_kmutil_requirement = self.hardware_details["Settings: Supports Auxiliary Cache"]
def __del__(self):
# Ensures that each time we're patching, we're using a clean repository
if Path(self.constants.payload_local_binaries_root_path).exists():
shutil.rmtree(self.constants.payload_local_binaries_root_path)
def init_pathing(self, custom_root_mount_path=None, custom_data_mount_path=None):
if custom_root_mount_path and custom_data_mount_path:
self.mount_location = custom_root_mount_path
self.data_mount_location = custom_data_mount_path
elif self.root_supports_snapshot is True:
# Big Sur and newer use APFS snapshots
self.mount_location = "/System/Volumes/Update/mnt1"
self.mount_location_data = ""
else:
self.mount_location = ""
self.mount_location_data = ""
self.mount_extensions = f"{self.mount_location}/System/Library/Extensions"
self.mount_application_support = f"{self.mount_location_data}/Library/Application Support"
def mount_root_vol(self):
# Returns boolean if Root Volume is available
self.root_mount_path = utilities.get_disk_path()
if self.root_mount_path.startswith("disk"):
print(f"- Found Root Volume at: {self.root_mount_path}")
if Path(self.mount_extensions).exists():
print("- Root Volume is already mounted")
return True
else:
if self.root_supports_snapshot is True:
print("- Mounting APFS Snapshot as writable")
result = utilities.elevated(["mount", "-o", "nobrowse", "-t", "apfs", f"/dev/{self.root_mount_path}", self.mount_location], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode == 0:
print(f"- Mounted APFS Snapshot as writable at: {self.mount_location}")
if Path(self.mount_extensions).exists():
print("- Successfully mounted the Root Volume")
return True
else:
print("- Root Volume appears to have unmounted unexpectedly")
else:
print("- Unable to mount APFS Snapshot as writable")
print("Reason for mount failure:")
print(result.stdout.decode().strip())
return False
def merge_kdk_with_root(self):
if self.skip_root_kmutil_requirement is True:
return
if self.constants.detected_os < os_data.os_data.ventura:
return
downloaded_kdk = None
kdk_path = sys_patch_helpers.sys_patch_helpers(self.constants).determine_kdk_present(match_closest=False)
if kdk_path is None:
if not self.constants.kdk_download_path.exists():
kdk_result, error_msg, downloaded_kdk = kdk_handler.kernel_debug_kit_handler(self.constants).download_kdk(self.constants.detected_os_version, self.constants.detected_os_build)
if kdk_result is False:
raise Exception(f"Unable to download KDK: {error_msg}")
sys_patch_helpers.sys_patch_helpers(self.constants).install_kdk()
kdk_path = sys_patch_helpers.sys_patch_helpers(self.constants).determine_kdk_present(match_closest=True, override_build=downloaded_kdk)
oclp_plist = Path("/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist")
if (Path(self.mount_location) / Path("System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern")).exists() and oclp_plist.exists():
# KDK was already merged, check if the KDK used is the same as the one we're using
# If not, we'll rsync over with the new KDK
try:
oclp_plist_data = plistlib.load(open(oclp_plist, "rb"))
if "Kernel Debug Kit Used" in oclp_plist_data:
if oclp_plist_data["Kernel Debug Kit Used"] == str(kdk_path):
print("- Matching KDK determined to already be merged, skipping")
return
except:
pass
if kdk_path is None:
print(f"- Unable to find Kernel Debug Kit: {downloaded_kdk}")
raise Exception("Unable to find Kernel Debug Kit")
self.kdk_path = kdk_path
print(f"- Found KDK at: {kdk_path}")
print("- Merging KDK with Root Volume")
utilities.elevated(
# Only merge '/System/Library/Extensions'
# 'Kernels' and 'KernelSupport' is wasted space for root patching (we don't care above dev kernels)
["rsync", "-r", "-i", "-a", f"{kdk_path}/System/Library/Extensions/", f"{self.mount_location}/System/Library/Extensions"],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
# During reversing, we found that kmutil uses this path to determine whether the KDK was successfully merged
# Best to verify now before we cause any damage
if not (Path(self.mount_location) / Path("System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern")).exists():
print("- Failed to merge KDK with Root Volume")
raise Exception("Failed to merge KDK with Root Volume")
print("- Successfully merged KDK with Root Volume")
def unpatch_root_vol(self):
if self.constants.detected_os > os_data.os_data.catalina and self.root_supports_snapshot is True:
print("- Reverting to last signed APFS snapshot")
result = utilities.elevated(["bless", "--mount", self.mount_location, "--bootefi", "--last-sealed-snapshot"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0:
print("- Unable to revert root volume patches")
print("Reason for unpatch Failure:")
print(result.stdout.decode())
print("- Failed to revert snapshot via Apple's 'bless' command")
else:
self.clean_skylight_plugins()
self.delete_nonmetal_enforcement()
self.clean_auxiliary_kc()
self.constants.root_patcher_succeeded = True
print("- Unpatching complete")
print("\nPlease reboot the machine for patches to take effect")
def rebuild_snapshot(self):
if self.rebuild_kernel_collection() is True:
self.update_preboot_kernel_cache()
self.rebuild_dyld_shared_cache()
if self.create_new_apfs_snapshot() is True:
print("- Patching complete")
print("\nPlease reboot the machine for patches to take effect")
if self.needs_kmutil_exemptions is True:
print("Note: Apple will require you to open System Preferences -> Security to allow the new kernel extensions to be loaded")
self.constants.root_patcher_succeeded = True
if self.constants.gui_mode is False:
input("\nPress [ENTER] to continue")
def rebuild_kernel_collection(self):
print("- Rebuilding Kernel Cache (This may take some time)")
if self.constants.detected_os > os_data.os_data.catalina:
# Base Arguments
args = ["kmutil", "install"]
if self.skip_root_kmutil_requirement is True:
# Only rebuild the Auxiliary Kernel Collection
args.append("--new")
args.append("aux")
args.append("--boot-path")
args.append(f"{self.mount_location}/System/Library/KernelCollections/BootKernelExtensions.kc")
args.append("--system-path")
args.append(f"{self.mount_location}/System/Library/KernelCollections/SystemKernelExtensions.kc")
else:
# Rebuild Boot, System and Auxiliary Kernel Collections
args.append("--volume-root")
args.append(self.mount_location)
# Build Boot, Sys and Aux KC
args.append("--update-all")
# If multiple kernels found, only build release KCs
args.append("--variant-suffix")
args.append("release")
if self.constants.detected_os >= os_data.os_data.ventura:
# With Ventura, we're required to provide a KDK in some form
# to rebuild the Kernel Cache
#
# However since we already merged the KDK onto root with 'ditto',
# We can add '--allow-missing-kdk' to skip parsing the KDK
#
# This allows us to only delete/overwrite kexts inside of
# /System/Library/Extensions and not the entire KDK
args.append("--allow-missing-kdk")
# 'install' and '--update-all' cannot be used together in Ventura.
# kmutil will request the usage of 'create' instead:
# Warning: kmutil install's usage of --update-all is deprecated.
# Use kmutil create --update-install instead'
args[1] = "create"
if self.needs_kmutil_exemptions is True:
# When installing to '/Library/Extensions', following args skip kext consent
# prompt in System Preferences when SIP's disabled
print(" (You will get a prompt by System Preferences, ignore for now)")
args.append("--no-authentication")
args.append("--no-authorization")
else:
args = ["kextcache", "-i", f"{self.mount_location}/"]
result = utilities.elevated(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# kextcache notes:
# - kextcache always returns 0, even if it fails
# - Check the output for 'KernelCache ID' to see if the cache was successfully rebuilt
# kmutil notes:
# - will return 71 on failure to build KCs
# - will return 31 on 'No binaries or codeless kexts were provided'
# - will return -10 if the volume is missing (ie. unmounted by another process)
if result.returncode != 0 or (self.constants.detected_os < os_data.os_data.catalina and "KernelCache ID" not in result.stdout.decode()):
print("- Unable to build new kernel cache")
print(f"\nReason for Patch Failure ({result.returncode}):")
print(result.stdout.decode())
print("")
print("\nPlease reboot the machine to avoid potential issues rerunning the patcher")
if self.constants.gui_mode is False:
input("Press [ENTER] to continue")
return False
if self.skip_root_kmutil_requirement is True:
# Force rebuild the Auxiliary KC
result = utilities.elevated(["killall", "syspolicyd", "kernelmanagerd"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0:
print("- Unable to remove kernel extension policy files")
print(f"\nReason for Patch Failure ({result.returncode}):")
print(result.stdout.decode())
print("")
print("\nPlease reboot the machine to avoid potential issues rerunning the patcher")
if self.constants.gui_mode is False:
input("Press [ENTER] to continue")
return False
for file in ["KextPolicy", "KextPolicy-shm", "KextPolicy-wal"]:
self.remove_file("/private/var/db/SystemPolicyConfiguration/", file)
print("- Successfully built new kernel cache")
return True
def create_new_apfs_snapshot(self):
if self.root_supports_snapshot is True:
print("- Creating new APFS snapshot")
bless = utilities.elevated(
[
"bless",
"--folder", f"{self.mount_location}/System/Library/CoreServices",
"--bootefi", "--create-snapshot"
], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
if bless.returncode != 0:
print("- Unable to create new snapshot")
print("Reason for snapshot failure:")
print(bless.stdout.decode())
if "Can't use last-sealed-snapshot or create-snapshot on non system volume" in bless.stdout.decode():
print("- This is an APFS bug with Monterey and newer! Perform a clean installation to ensure your APFS volume is built correctly")
return False
self.unmount_drive()
return True
def unmount_drive(self):
print("- Unmounting Root Volume (Don't worry if this fails)")
utilities.elevated(["diskutil", "unmount", self.root_mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode()
def rebuild_dyld_shared_cache(self):
if self.constants.detected_os <= os_data.os_data.catalina:
print("- Rebuilding dyld shared cache")
utilities.process_status(utilities.elevated(["update_dyld_shared_cache", "-root", f"{self.mount_location}/"]))
def update_preboot_kernel_cache(self):
if self.constants.detected_os == os_data.os_data.catalina:
print("- Rebuilding preboot kernel cache")
utilities.process_status(utilities.elevated(["kcditto"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
def clean_skylight_plugins(self):
if (Path(self.mount_application_support) / Path("SkyLightPlugins/")).exists():
print("- Found SkylightPlugins folder, removing old plugins")
utilities.process_status(utilities.elevated(["rm", "-Rf", f"{self.mount_application_support}/SkyLightPlugins"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
utilities.process_status(utilities.elevated(["mkdir", f"{self.mount_application_support}/SkyLightPlugins"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
else:
print("- Creating SkylightPlugins folder")
utilities.process_status(utilities.elevated(["mkdir", "-p", f"{self.mount_application_support}/SkyLightPlugins/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
def delete_nonmetal_enforcement(self):
for arg in ["useMetal", "useIOP"]:
result = subprocess.run(["defaults", "read", "/Library/Preferences/com.apple.CoreDisplay", arg], stdout=subprocess.PIPE).stdout.decode("utf-8").strip()
if result in ["0", "false", "1", "true"]:
print(f"- Removing non-Metal Enforcement Preference: {arg}")
utilities.elevated(["defaults", "delete", "/Library/Preferences/com.apple.CoreDisplay", arg])
def clean_auxiliary_kc(self):
# When reverting root volume patches, the AuxKC will still retain the UUID
# it was built against. Thus when Boot/SysKC are reverted, Aux will break
# To resolve this, delete all installed kexts in /L*/E* and rebuild the AuxKC
# We can verify our binaries based off the OpenCore-Legacy-Patcher.plist file
if self.constants.detected_os < os_data.os_data.big_sur:
return
oclp_path = "/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist"
if not Path(oclp_path).exists():
return
print("- Cleaning Auxiliary Kernel Collection")
oclp_plist_data = plistlib.load(Path(oclp_path).open("rb"))
for key in oclp_plist_data:
if "Install" not in oclp_plist_data[key]:
continue
for location in oclp_plist_data[key]["Install"]:
if not location.endswith("Extensions"):
continue
for file in oclp_plist_data[key]["Install"][location]:
if not file.endswith(".kext"):
continue
self.remove_file("/Library/Extensions", file)
# Handle situations where users migrated from older OSes with a lot of garbage in /L*/E*
# ex. Nvidia Web Drivers, NetUSB, dosdude1's patches, etc.
# Move if file's age is older than October 2021 (year before Ventura)
if self.constants.detected_os < os_data.os_data.ventura:
return
relocation_path = "/Library/Relocated Extensions"
if not Path(relocation_path).exists():
utilities.elevated(["mkdir", relocation_path])
for file in Path("/Library/Extensions").glob("*.kext"):
try:
if datetime.fromtimestamp(file.stat().st_mtime) < datetime(2021, 10, 1):
print(f" - Relocating {file.name} kext to {relocation_path}")
if Path(relocation_path) / Path(file.name).exists():
utilities.elevated(["rm", "-Rf", relocation_path / Path(file.name)])
utilities.elevated(["mv", file, relocation_path])
except:
# Some users have the most cursed /L*/E* folders
# ex. Symlinks pointing to symlinks pointing to dead files
pass
def write_patchset(self, patchset):
destination_path = f"{self.mount_location}/System/Library/CoreServices"
file_name = "OpenCore-Legacy-Patcher.plist"
destination_path_file = f"{destination_path}/{file_name}"
if sys_patch_helpers.sys_patch_helpers(self.constants).generate_patchset_plist(patchset, file_name, self.kdk_path):
print("- Writing patchset information to Root Volume")
if Path(destination_path_file).exists():
utilities.process_status(utilities.elevated(["rm", destination_path_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
utilities.process_status(utilities.elevated(["cp", f"{self.constants.payload_path}/{file_name}", destination_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
def add_auxkc_support(self, install_file, source_folder_path, install_patch_directory, destination_folder_path):
# In macOS Ventura, KDKs are required to build new Boot and System KCs
# However for some patch sets, we're able to use the Auxiliary KCs with '/Library/Extensions'
# kernelmanagerd determines which kext is installed by their 'OSBundleRequired' entry
# If a kext is labeled as 'OSBundleRequired: Root' or 'OSBundleRequired: Safe Boot',
# kernelmanagerd will require the kext to be installed in the Boot/SysKC
# Additionally, kexts starting with 'com.apple.' are not natively allowed to be installed
# in the AuxKC. So we need to explicitly set our 'OSBundleRequired' to 'Auxiliary'
if self.skip_root_kmutil_requirement is False:
return destination_folder_path
if not install_file.endswith(".kext"):
return destination_folder_path
if install_patch_directory != "/System/Library/Extensions":
return destination_folder_path
if self.constants.detected_os < os_data.os_data.ventura:
return destination_folder_path
updated_install_location = str(self.mount_location_data) + "/Library/Extensions"
print(f" - Adding AuxKC support to {install_file}")
plist_path = Path(Path(source_folder_path) / Path(install_file) / Path("Contents/Info.plist"))
plist_data = plistlib.load((plist_path).open("rb"))
# Check if we need to update the 'OSBundleRequired' entry
if not plist_data["CFBundleIdentifier"].startswith("com.apple."):
return updated_install_location
if "OSBundleRequired" in plist_data:
if plist_data["OSBundleRequired"] == "Auxiliary":
return updated_install_location
plist_data["OSBundleRequired"] = "Auxiliary"
plistlib.dump(plist_data, plist_path.open("wb"))
self.check_kexts_needs_authentication(install_file)
return updated_install_location
def check_kexts_needs_authentication(self, kext_name):
# Verify whether the user needs to authenticate in System Preferences
# Specifically under 'private/var/db/KernelManagement/AuxKC/CurrentAuxKC/com.apple.kcgen.instructions.plist'
# ["kextsToBuild"][i]:
# ["bundlePathMainOS"] = /Library/Extensions/Test.kext
# ["cdHash"] = Bundle's CDHash (random on ad-hoc signed, static on dev signed)
# ["teamID"] = Team ID (blank on ad-hoc signed)
# To grab the CDHash of a kext, run 'codesign -dvvv <kext_path>'
try:
aux_cache_path = Path(self.mount_location_data) / Path("/private/var/db/KernelExtensionManagement/AuxKC/CurrentAuxKC/com.apple.kcgen.instructions.plist")
if aux_cache_path.exists():
aux_cache_data = plistlib.load((aux_cache_path).open("rb"))
for kext in aux_cache_data["kextsToBuild"]:
if "bundlePathMainOS" in aux_cache_data["kextsToBuild"][kext]:
if aux_cache_data["kextsToBuild"][kext]["bundlePathMainOS"] == f"/Library/Extensions/{kext_name}":
return
except PermissionError:
pass
print(f" - {kext_name} requires authentication in System Preferences")
self.constants.needs_to_open_preferences = True # Notify in GUI to open System Preferences
def patch_root_vol(self):
print(f"- Running patches for {self.model}")
if self.patch_set_dictionary != {}:
self.execute_patchset(self.patch_set_dictionary)
else:
self.execute_patchset(sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).generate_patchset(self.hardware_details))
if self.constants.wxpython_variant is True and self.constants.detected_os >= os_data.os_data.big_sur:
sys_patch_auto.AutomaticSysPatch.install_auto_patcher_launch_agent(self.constants)
self.rebuild_snapshot()
def execute_patchset(self, required_patches):
source_files_path = str(self.constants.payload_local_binaries_root_path)
self.preflight_checks(required_patches, source_files_path)
for patch in required_patches:
print("- Installing Patchset: " + patch)
if "Remove" in required_patches[patch]:
for remove_patch_directory in required_patches[patch]["Remove"]:
print("- Remove Files at: " + remove_patch_directory)
for remove_patch_file in required_patches[patch]["Remove"][remove_patch_directory]:
destination_folder_path = str(self.mount_location) + remove_patch_directory
self.remove_file(destination_folder_path, remove_patch_file)
for method_install in ["Install", "Install Non-Root"]:
if method_install in required_patches[patch]:
for install_patch_directory in list(required_patches[patch][method_install]):
print(f"- Handling Installs in: {install_patch_directory}")
for install_file in list(required_patches[patch][method_install][install_patch_directory]):
source_folder_path = source_files_path + "/" + required_patches[patch][method_install][install_patch_directory][install_file] + install_patch_directory
if method_install == "Install":
destination_folder_path = str(self.mount_location) + install_patch_directory
else:
if install_patch_directory == "/Library/Extensions":
self.needs_kmutil_exemptions = True
self.check_kexts_needs_authentication(install_file)
destination_folder_path = str(self.mount_location_data) + install_patch_directory
updated_destination_folder_path = self.add_auxkc_support(install_file, source_folder_path, install_patch_directory, destination_folder_path)
if destination_folder_path != updated_destination_folder_path:
# Update required_patches to reflect the new destination folder path
if updated_destination_folder_path not in required_patches[patch][method_install]:
required_patches[patch][method_install].update({updated_destination_folder_path: {}})
required_patches[patch][method_install][updated_destination_folder_path].update({install_file: required_patches[patch][method_install][install_patch_directory][install_file]})
required_patches[patch][method_install][install_patch_directory].pop(install_file)
destination_folder_path = updated_destination_folder_path
self.install_new_file(source_folder_path, destination_folder_path, install_file)
if "Processes" in required_patches[patch]:
for process in required_patches[patch]["Processes"]:
# Some processes need sudo, however we cannot directly call sudo in some scenarios
# Instead, call elevated funtion if string's boolean is True
if required_patches[patch]["Processes"][process] is True:
print(f"- Running Process as Root:\n{process}")
utilities.process_status(utilities.elevated(process.split(" "), stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
else:
print(f"- Running Process:\n{process}")
utilities.process_status(subprocess.run(process, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True))
if any(x in required_patches for x in ["AMD Legacy GCN", "AMD Legacy Polaris"]):
sys_patch_helpers.sys_patch_helpers(self.constants).disable_window_server_caching()
if any(x in required_patches for x in ["Intel Ivy Bridge", "Intel Haswell"]):
sys_patch_helpers.sys_patch_helpers(self.constants).remove_news_widgets()
self.write_patchset(required_patches)
def preflight_checks(self, required_patches, source_files_path):
print("- Running Preflight Checks before patching")
# Make sure old SkyLight plugins aren't being used
self.clean_skylight_plugins()
# Make sure non-Metal Enforcement preferences are not present
self.delete_nonmetal_enforcement()
# Make sure we clean old kexts in /L*/E* that are not in the patchset
self.clean_auxiliary_kc()
# Make sure SNB kexts are compatible with the host
if "Intel Sandy Bridge" in required_patches:
sys_patch_helpers.sys_patch_helpers(self.constants).snb_board_id_patch(source_files_path)
for patch in required_patches:
# Check if all files are present
for method_type in ["Install", "Install Non-Root"]:
if method_type in required_patches[patch]:
for install_patch_directory in required_patches[patch][method_type]:
for install_file in required_patches[patch][method_type][install_patch_directory]:
source_file = source_files_path + "/" + required_patches[patch][method_type][install_patch_directory][install_file] + install_patch_directory + "/" + install_file
if not Path(source_file).exists():
raise Exception(f"Failed to find {source_file}")
# Ensure KDK is properly installed
self.merge_kdk_with_root()
print("- Finished Preflight, starting patching")
def install_new_file(self, source_folder, destination_folder, file_name):
# .frameworks are merged
# .kexts and .apps are deleted and replaced
file_name_str = str(file_name)
if not Path(destination_folder).exists():
print(f" - Skipping {file_name}, cannot locate {source_folder}")
return
if file_name_str.endswith(".framework"):
# merge with rsync
print(f" - Installing: {file_name}")
utilities.elevated(["rsync", "-r", "-i", "-a", f"{source_folder}/{file_name}", f"{destination_folder}/"], stdout=subprocess.PIPE)
self.fix_permissions(destination_folder + "/" + file_name)
elif Path(source_folder + "/" + file_name_str).is_dir():
# Applicable for .kext, .app, .plugin, .bundle, all of which are directories
if Path(destination_folder + "/" + file_name).exists():
print(f" - Found existing {file_name}, overwriting...")
utilities.process_status(utilities.elevated(["rm", "-R", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
else:
print(f" - Installing: {file_name}")
utilities.process_status(utilities.elevated(["cp", "-R", f"{source_folder}/{file_name}", destination_folder], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
self.fix_permissions(destination_folder + "/" + file_name)
else:
# Assume it's an individual file, replace as normal
if Path(destination_folder + "/" + file_name).exists():
print(f" - Found existing {file_name}, overwriting...")
utilities.process_status(utilities.elevated(["rm", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
else:
print(f" - Installing: {file_name}")
utilities.process_status(utilities.elevated(["cp", f"{source_folder}/{file_name}", destination_folder], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
self.fix_permissions(destination_folder + "/" + file_name)
def remove_file(self, destination_folder, file_name):
if Path(destination_folder + "/" + file_name).exists():
print(f" - Removing: {file_name}")
if Path(destination_folder + "/" + file_name).is_dir():
utilities.process_status(utilities.elevated(["rm", "-R", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
else:
utilities.process_status(utilities.elevated(["rm", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
def fix_permissions(self, destination_file):
chmod_args = ["chmod", "-Rf", "755", destination_file]
chown_args = ["chown", "-Rf", "root:wheel", destination_file]
if not Path(destination_file).is_dir():
# Strip recursive arguments
chmod_args.pop(1)
chown_args.pop(1)
utilities.process_status(utilities.elevated(chmod_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
utilities.process_status(utilities.elevated(chown_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
def check_files(self):
if Path(self.constants.payload_local_binaries_root_path).exists():
print("- Found local Apple Binaries")
if self.constants.gui_mode is False:
patch_input = input("Would you like to redownload?(y/n): ")
if patch_input in {"y", "Y", "yes", "Yes"}:
shutil.rmtree(Path(self.constants.payload_local_binaries_root_path))
output = self.download_files()
else:
output = True
else:
output = self.download_files()
else:
output = self.download_files()
return output
def download_files(self):
if self.constants.cli_mode is True:
download_result, link = sys_patch_download.grab_patcher_support_pkg(self.constants).download_files()
else:
download_result = True
link = sys_patch_download.grab_patcher_support_pkg(self.constants).generate_pkg_link()
if download_result and self.constants.payload_local_binaries_root_path_zip.exists():
print("- Unzipping binaries...")
utilities.process_status(subprocess.run(["ditto", "-V", "-x", "-k", "--sequesterRsrc", "--rsrc", self.constants.payload_local_binaries_root_path_zip, self.constants.payload_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
print("- Binaries downloaded to:")
print(self.constants.payload_path)
return self.constants.payload_local_binaries_root_path
else:
if self.constants.gui_mode is True:
print("- Download failed, please verify the below link work:")
print(link)
print("\nIf you continue to have issues, try using the Offline builds")
print("located on Github next to the other builds")
else:
input("\nPress enter to continue")
return None
# Entry Function
def start_patch(self):
print("- Starting Patch Process")
print(f"- Determining Required Patch set for Darwin {self.constants.detected_os}")
self.patch_set_dictionary = sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).generate_patchset(self.hardware_details)
if self.patch_set_dictionary == {}:
change_menu = None
print("- No Root Patches required for your machine!")
if self.constants.gui_mode is False:
input("\nPress [ENTER] to return to the main menu: ")
elif self.constants.gui_mode is False:
change_menu = input("Would you like to continue with Root Volume Patching?(y/n): ")
else:
change_menu = "y"
print("- Continuing root patching")
if change_menu in ["y", "Y"]:
print("- Verifying whether Root Patching possible")
if sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).verify_patch_allowed(print_errors=not self.constants.wxpython_variant) is True:
print("- Patcher is capable of patching")
if self.check_files():
if self.mount_root_vol() is True:
self.patch_root_vol()
if self.constants.gui_mode is False:
input("\nPress [ENTER] to return to the main menu")
else:
print("- Recommend rebooting the machine and trying to patch again")
if self.constants.gui_mode is False:
input("- Press [ENTER] to exit: ")
elif self.constants.gui_mode is False:
input("\nPress [ENTER] to return to the main menu: ")
else:
print("- Returning to main menu")
def start_unpatch(self):
print("- Starting Unpatch Process")
if sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).verify_patch_allowed(print_errors=True) is True:
if self.mount_root_vol() is True:
self.unpatch_root_vol()
if self.constants.gui_mode is False:
input("\nPress [ENTER] to return to the main menu")
else:
print("- Recommend rebooting the machine and trying to patch again")
if self.constants.gui_mode is False:
input("- Press [ENTER] to exit: ")
elif self.constants.gui_mode is False:
input("\nPress [ENTER] to return to the main menu")
+225
View File
@@ -0,0 +1,225 @@
# Auto Patching's main purpose is to try and tell the user they're missing root patches
# New users may not realize OS updates remove our patches, so we try and run when nessasary
# Conditions for running:
# - Verify running GUI (TUI users can write their own scripts)
# - Verify the Snapshot Seal is intact (if not, assume user is running patches)
# - Verify this model needs patching (if not, assume user upgraded hardware and OCLP was not removed)
# - Verify there are no updates for OCLP (ensure we have the latest patch sets)
# If all these tests pass, start Root Patcher
# Copyright (C) 2022, Mykola Grymalyuk
from pathlib import Path
import plistlib
import subprocess
import webbrowser
from resources import utilities, updates, global_settings
from resources.sys_patch import sys_patch_detect
from gui import gui_main
class AutomaticSysPatch:
def start_auto_patch(settings):
print("- Starting Automatic Patching")
if settings.wxpython_variant is True:
if utilities.check_seal() is True:
print("- Detected Snapshot seal intact, detecting patches")
patches = sys_patch_detect.detect_root_patch(settings.computer.real_model, settings).detect_patch_set()
if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches):
patches = []
if patches:
print("- Detected applicable patches, determining whether possible to patch")
if patches["Validation: Patching Possible"] is True:
print("- Determined patching is possible, checking for OCLP updates")
patch_string = ""
for patch in patches:
if patches[patch] is True and not patch.startswith("Settings") and not patch.startswith("Validation"):
patch_string += f"- {patch}\n"
# Check for updates
dict = updates.check_binary_updates(settings).check_binary_updates()
if not dict:
print("- No new binaries found on Github, proceeding with patching")
if settings.launcher_script is None:
args_string = f"'{settings.launcher_binary}' --gui_patch"
else:
args_string = f"{settings.launcher_binary} {settings.launcher_script} --gui_patch"
warning_str = ""
if utilities.verify_network_connection("https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest") is False:
warning_str = f"""\n\nWARNING: We're unable to verify whether there are any new releases of OpenCore Legacy Patcher on Github. Be aware that you may be using an outdated version for this OS. If you're unsure, verify on Github that OpenCore Legacy Patcher {settings.patcher_version} is the latest official release"""
args = [
"osascript",
"-e",
f"""display dialog "OpenCore Legacy Patcher has detected you're running without Root Patches, and would like to install them.\n\nmacOS wipes all root patches during OS installs and updates, so they need to be reinstalled.\n\nFollowing Patches have been detected for your system: \n{patch_string}\nWould you like to apply these patches?{warning_str}" """
f'with icon POSIX file "{settings.app_icon_path}"',
]
output = subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
if output.returncode == 0:
args = [
"osascript",
"-e",
f'''do shell script "{args_string}"'''
f' with prompt "OpenCore Legacy Patcher would like to patch your root volume"'
" with administrator privileges"
" without altering line endings"
]
subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
else:
version = dict[0]["Version"]
github_link = dict[0]["Github Link"]
print(f"- Found new version: {version}")
# launch oascript to ask user if they want to apply the update
# if yes, open the link in the default browser
# we never want to run the root patcher if there are updates available
args = [
"osascript",
"-e",
f"""display dialog "OpenCore Legacy Patcher has detected you're running without Root Patches, and would like to install them.\n\nHowever we've detected a new version of OCLP on Github. Would you like to view this?\n\nCurrent Version: {settings.patcher_version}\nLatest Version: {version}\n\nNote: After downloading the latest OCLP version, open the app and run the 'Post Install Root Patcher' from the main menu." """
f'with icon POSIX file "{settings.app_icon_path}"',
]
output = subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
if output.returncode == 0:
webbrowser.open(github_link)
else:
print("- Cannot run patching")
else:
print("- No patches detected")
AutomaticSysPatch.determine_if_boot_matches(settings)
else:
print("- Detected Snapshot seal not intact, skipping")
AutomaticSysPatch.determine_if_boot_matches(settings)
else:
print("- Auto Patch option is not supported on TUI, please use GUI")
def determine_if_boot_matches(settings):
# Goal of this function is to determine whether the user
# is using a USB drive to Boot OpenCore but macOS does not
# reside on the same drive as the USB.
# If we determine them to be mismatched, notify the user
# and ask if they want to install to install to disk
print("- Determining if macOS drive matches boot drive")
should_notify = global_settings.global_settings().read_property("AutoPatch_Notify_Mismatched_Disks")
if should_notify is False:
print("- Skipping due to user preference")
elif settings.host_is_hackintosh is True:
print("- Skipping due to hackintosh")
else:
if settings.booted_oc_disk:
root_disk = settings.booted_oc_disk.strip("disk")
root_disk = "disk" + root_disk.split("s")[0]
print(f" - Boot Drive: {settings.booted_oc_disk} ({root_disk})")
macOS_disk = utilities.get_disk_path()
print(f" - macOS Drive: {macOS_disk}")
physical_stores = utilities.find_apfs_physical_volume(macOS_disk)
print(f" - APFS Physical Stores: {physical_stores}")
disk_match = False
for disk in physical_stores:
if root_disk in disk:
print(f"- Boot drive matches macOS drive ({disk})")
disk_match = True
break
if disk_match is False:
# Check if OpenCore is on a USB drive
print("- Boot Drive does not match macOS drive, checking if OpenCore is on a USB drive")
disk_info = plistlib.loads(subprocess.run(["diskutil", "info", "-plist", root_disk], stdout=subprocess.PIPE).stdout)
try:
if disk_info["Ejectable"] is True:
print("- Boot Disk is ejectable, prompting user to install to internal")
args = [
"osascript",
"-e",
f"""display dialog "OpenCore Legacy Patcher has detected that you are booting OpenCore from an USB or External drive.\n\nIf you would like to boot your Mac normally without a USB drive plugged in, you can install OpenCore to the internal hard drive.\n\nWould you like to launch OpenCore Legacy Patcher and install to disk?" """
f'with icon POSIX file "{settings.app_icon_path}"',
]
output = subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
if output.returncode == 0:
print("- Launching GUI's Build/Install menu")
settings.start_build_install = True
gui_main.wx_python_gui(settings).main_menu(None)
else:
print("- Boot Disk is not removable, skipping prompt")
except KeyError:
print("- Unable to determine if boot disk is removable, skipping prompt")
else:
print("- Failed to find disk OpenCore launched from")
def install_auto_patcher_launch_agent(settings):
# Installs the following:
# - OpenCore-Patcher.app in /Library/Application Support/Dortania/
# - com.dortania.opencore-legacy-patcher.auto-patch.plist in /Library/LaunchAgents/
if settings.launcher_script is None:
# Verify our binary isn't located in '/Library/Application Support/Dortania/'
# As we'd simply be duplicating ourselves
if not settings.launcher_binary.startswith("/Library/Application Support/Dortania/"):
print("- Installing Auto Patcher Launch Agent")
if not Path("Library/Application Support/Dortania").exists():
print("- Creating /Library/Application Support/Dortania/")
utilities.process_status(utilities.elevated(["mkdir", "-p", "/Library/Application Support/Dortania"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
print("- Copying OpenCore Patcher to /Library/Application Support/Dortania/")
if Path("/Library/Application Support/Dortania/OpenCore-Patcher.app").exists():
print("- Deleting existing OpenCore-Patcher")
utilities.process_status(utilities.elevated(["rm", "-R", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
# Strip everything after OpenCore-Patcher.app
path = str(settings.launcher_binary).split("/Contents/MacOS/OpenCore-Patcher")[0]
print(f"- Copying {path} to /Library/Application Support/Dortania/")
utilities.process_status(utilities.elevated(["ditto", path, "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
if not Path("/Library/Application Support/Dortania/OpenCore-Patcher.app").exists():
# Sometimes the binary the user launches may have a suffix (ie. OpenCore-Patcher 3.app)
# We'll want to rename it to OpenCore-Patcher.app
path = path.split("/")[-1]
print(f"- Renaming {path} to OpenCore-Patcher.app")
utilities.process_status(utilities.elevated(["mv", f"/Library/Application Support/Dortania/{path}", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
# Copy over our launch agent
print("- Copying auto-patch.plist Launch Agent to /Library/LaunchAgents/")
if Path("/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist").exists():
print("- Deleting existing auto-patch.plist")
utilities.process_status(utilities.elevated(["rm", "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
if not Path("/Library/LaunchAgents/").exists():
print("- Creating /Library/LaunchAgents/")
utilities.process_status(utilities.elevated(["mkdir", "-p", "/Library/LaunchAgents/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
utilities.process_status(utilities.elevated(["cp", settings.auto_patch_launch_agent_path, "/Library/LaunchAgents/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
# Set the permissions on the com.dortania.opencore-legacy-patcher.auto-patch.plist
print("- Setting permissions on auto-patch.plist")
utilities.process_status(utilities.elevated(["chmod", "644", "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
utilities.process_status(utilities.elevated(["chown", "root:wheel", "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
# Making app alias
# Simply an easy way for users to notice the app
# If there's already an alias or exiting app, skip
if not Path("/Applications/OpenCore-Patcher.app").exists():
print("- Making app alias")
utilities.process_status(utilities.elevated(["ln", "-s", "/Library/Application Support/Dortania/OpenCore-Patcher.app", "/Applications/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
else:
print("- Skipping Auto Patcher Launch Agent, not supported when running from source")
+576
View File
@@ -0,0 +1,576 @@
# Hardware Detection Logic for Root Patching
# Returns a dictionary of patches with boolean values
# Used when supplying data to sys_patch.py
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
from resources import constants, device_probe, utilities, amfi_detect
from resources.sys_patch import sys_patch_helpers
from data import model_array, os_data, sip_data, sys_patch_dict
class detect_root_patch:
def __init__(self, model, versions):
self.model = model
self.constants: constants.Constants() = versions
self.computer = self.constants.computer
# GPU Patch Detection
self.nvidia_tesla = False
self.kepler_gpu = False
self.nvidia_web = False
self.amd_ts1 = False
self.amd_ts2 = False
self.iron_gpu = False
self.sandy_gpu = False
self.ivy_gpu = False
self.haswell_gpu = False
self.broadwell_gpu = False
self.skylake_gpu = False
self.legacy_gcn = False
self.legacy_polaris = False
# Misc Patch Detection
self.brightness_legacy = False
self.legacy_audio = False
self.legacy_wifi = False
self.legacy_gmux = False
self.legacy_keyboard_backlight = False
# Patch Requirements
self.amfi_must_disable = False
self.amfi_shim_bins = False
self.supports_metal = False
self.needs_nv_web_checks = False
self.requires_root_kc = False
# Validation Checks
self.sip_enabled = False
self.sbm_enabled = False
self.amfi_enabled = False
self.fv_enabled = False
self.dosdude_patched = False
self.missing_kdk = False
self.missing_whatever_green = False
self.missing_nv_web_nvram = False
self.missing_nv_web_opengl = False
self.missing_nv_compat = False
def detect_gpus(self):
gpus = self.constants.computer.gpus
non_metal_os = os_data.os_data.catalina
for i, gpu in enumerate(gpus):
if gpu.class_code and gpu.class_code != 0xFFFFFFFF:
print(f"- Found GPU ({i}): {utilities.friendly_hex(gpu.vendor_id)}:{utilities.friendly_hex(gpu.device_id)}")
if gpu.arch in [device_probe.NVIDIA.Archs.Tesla] and self.constants.force_nv_web is False:
if self.constants.detected_os > non_metal_os:
self.nvidia_tesla = True
self.amfi_must_disable = True
if os_data.os_data.ventura in self.constants.legacy_accel_support:
self.amfi_shim_bins = True
self.legacy_keyboard_backlight = self.check_legacy_keyboard_backlight()
self.requires_root_kc = True
elif gpu.arch == device_probe.NVIDIA.Archs.Kepler and self.constants.force_nv_web is False:
if self.constants.detected_os > os_data.os_data.big_sur:
# Kepler drivers were dropped with Beta 7
# 12.0 Beta 5: 21.0.0 - 21A5304g
# 12.0 Beta 6: 21.1.0 - 21A5506j
# 12.0 Beta 7: 21.1.0 - 21A5522h
if (
self.constants.detected_os >= os_data.os_data.ventura or
(
"21A5506j" not in self.constants.detected_os_build and
self.constants.detected_os == os_data.os_data.monterey and
self.constants.detected_os_minor > 0
)
):
self.kepler_gpu = True
self.supports_metal = True
if self.constants.detected_os >= os_data.os_data.ventura:
self.amfi_must_disable = True
elif gpu.arch in [
device_probe.NVIDIA.Archs.Fermi,
device_probe.NVIDIA.Archs.Kepler,
device_probe.NVIDIA.Archs.Maxwell,
device_probe.NVIDIA.Archs.Pascal,
]:
if self.constants.detected_os > os_data.os_data.mojave:
self.nvidia_web = True
self.amfi_must_disable = True
if os_data.os_data.ventura in self.constants.legacy_accel_support:
self.amfi_shim_bins = True
self.needs_nv_web_checks = True
self.requires_root_kc = True
elif gpu.arch == device_probe.AMD.Archs.TeraScale_1:
if self.constants.detected_os > non_metal_os:
self.amd_ts1 = True
self.amfi_must_disable = True
if os_data.os_data.ventura in self.constants.legacy_accel_support:
self.amfi_shim_bins = True
self.requires_root_kc = True
elif gpu.arch == device_probe.AMD.Archs.TeraScale_2:
if self.constants.detected_os > non_metal_os:
self.amd_ts2 = True
self.amfi_must_disable = True
if os_data.os_data.ventura in self.constants.legacy_accel_support:
self.amfi_shim_bins = True
self.requires_root_kc = True
elif gpu.arch in [
device_probe.AMD.Archs.Legacy_GCN_7000,
device_probe.AMD.Archs.Legacy_GCN_8000,
device_probe.AMD.Archs.Legacy_GCN_9000,
device_probe.AMD.Archs.Polaris,
]:
if self.constants.detected_os > os_data.os_data.monterey:
if self.constants.computer.rosetta_active is True:
continue
if gpu.arch == device_probe.AMD.Archs.Polaris:
# Check if host supports AVX2.0
# If not, enable legacy GCN patch
# MacBookPro13,3 does include an unsupported framebuffer, thus we'll patch to ensure
# full compatibility (namely power states, etc)
# Reference: https://github.com/dortania/bugtracker/issues/292
# TODO: Probe framebuffer families further
if self.model != "MacBookPro13,3":
if "AVX2" in self.constants.computer.cpu.leafs:
continue
self.legacy_polaris = True
else:
self.legacy_gcn = True
else:
self.legacy_gcn = True
self.supports_metal = True
self.requires_root_kc = True
self.amfi_must_disable = True
elif gpu.arch == device_probe.Intel.Archs.Iron_Lake:
if self.constants.detected_os > non_metal_os:
self.iron_gpu = True
self.amfi_must_disable = True
if os_data.os_data.ventura in self.constants.legacy_accel_support:
self.amfi_shim_bins = True
self.legacy_keyboard_backlight = self.check_legacy_keyboard_backlight()
self.requires_root_kc = True
elif gpu.arch == device_probe.Intel.Archs.Sandy_Bridge:
if self.constants.detected_os > non_metal_os:
self.sandy_gpu = True
self.amfi_must_disable = True
if os_data.os_data.ventura in self.constants.legacy_accel_support:
self.amfi_shim_bins = True
self.legacy_keyboard_backlight = self.check_legacy_keyboard_backlight()
self.requires_root_kc = True
elif gpu.arch == device_probe.Intel.Archs.Ivy_Bridge:
if self.constants.detected_os > os_data.os_data.big_sur:
self.ivy_gpu = True
if self.constants.detected_os >= os_data.os_data.ventura:
self.amfi_must_disable = True
self.supports_metal = True
elif gpu.arch == device_probe.Intel.Archs.Haswell:
if self.constants.detected_os > os_data.os_data.monterey:
self.haswell_gpu = True
self.amfi_must_disable = True
self.supports_metal = True
elif gpu.arch == device_probe.Intel.Archs.Broadwell:
if self.constants.detected_os > os_data.os_data.monterey:
self.broadwell_gpu = True
self.amfi_must_disable = True
self.supports_metal = True
elif gpu.arch == device_probe.Intel.Archs.Skylake:
if self.constants.detected_os > os_data.os_data.monterey:
self.skylake_gpu = True
self.amfi_must_disable = True
self.supports_metal = True
if self.supports_metal is True:
# Avoid patching Metal and non-Metal GPUs if both present, prioritize Metal GPU
# Main concerns are for iMac12,x with Sandy iGPU and Kepler dGPU
self.nvidia_tesla = False
self.nvidia_web = False
self.amd_ts1 = False
self.amd_ts2 = False
self.iron_gpu = False
self.sandy_gpu = False
self.legacy_keyboard_backlight = False
if self.legacy_polaris is True and self.legacy_gcn is True:
# We can only support one or the other due to the nature of relying
# on portions of the native AMD stack for Polaris
# Thus we'll prioritize legacy GCN due to being the internal card
# ex. MacPro6,1 and MacBookPro11,5 with eGPUs
self.legacy_polaris = False
if self.constants.detected_os <= os_data.os_data.monterey:
# Always assume Root KC requirement on Monterey and older
self.requires_root_kc = True
else:
if self.requires_root_kc is True:
self.missing_kdk = not self.check_kdk()
def check_dgpu_status(self):
dgpu = self.constants.computer.dgpu
if dgpu:
if dgpu.class_code and dgpu.class_code == 0xFFFFFFFF:
# If dGPU is disabled via class-codes, assume demuxed
return False
return True
return False
def detect_demux(self):
# If GFX0 is missing, assume machine was demuxed
# -wegnoegpu would also trigger this, so ensure arg is not present
if not "-wegnoegpu" in (utilities.get_nvram("boot-args", decode=True) or ""):
igpu = self.constants.computer.igpu
dgpu = self.check_dgpu_status()
if igpu and not dgpu:
return True
return False
def check_legacy_keyboard_backlight(self):
# iMac12,x+ have an 'ACPI0008' device, but it's not a keyboard backlight
# Best to assume laptops will have a keyboard backlight
if self.model.startswith("MacBook"):
return self.constants.computer.ambient_light_sensor
return False
def check_nv_web_nvram(self):
# First check boot-args, then dedicated nvram variable
nv_on = utilities.get_nvram("boot-args", decode=True)
if nv_on:
if "nvda_drv_vrl=" in nv_on:
return True
nv_on = utilities.get_nvram("nvda_drv")
if nv_on:
return True
return False
def check_nv_web_opengl(self):
# First check boot-args, then whether property exists on GPU
nv_on = utilities.get_nvram("boot-args", decode=True)
if nv_on:
if "ngfxgl=" in nv_on:
return True
for gpu in self.constants.computer.gpus:
if isinstance(gpu, device_probe.NVIDIA):
if gpu.disable_metal is True:
return True
return False
def check_nv_compat(self):
# Check for 'nv_web' in boot-args, then whether property exists on GPU
nv_on = utilities.get_nvram("boot-args", decode=True)
if nv_on:
if "ngfxcompat=" in nv_on:
return True
for gpu in self.constants.computer.gpus:
if isinstance(gpu, device_probe.NVIDIA):
if gpu.force_compatible is True:
return True
return False
def check_whatevergreen(self):
return utilities.check_kext_loaded("WhateverGreen", self.constants.detected_os)
def check_kdk(self):
if sys_patch_helpers.sys_patch_helpers(self.constants).determine_kdk_present() is None:
return False
return True
def check_sip(self):
if self.constants.detected_os > os_data.os_data.catalina:
if self.nvidia_web is True:
sip = sip_data.system_integrity_protection.root_patch_sip_big_sur_3rd_part_kexts
sip_hex = "0xA03"
sip_value = (
f"For Hackintoshes, please set csr-active-config to '030A0000' ({sip_hex})\nFor non-OpenCore Macs, please run 'csrutil disable' and \n'csrutil authenticated-root disable' in RecoveryOS"
)
elif self.constants.detected_os >= os_data.os_data.ventura:
sip = sip_data.system_integrity_protection.root_patch_sip_ventura
sip_hex = "0x803"
sip_value = (
f"For Hackintoshes, please set csr-active-config to '03080000' ({sip_hex})\nFor non-OpenCore Macs, please run 'csrutil disable' and \n'csrutil authenticated-root disable' in RecoveryOS"
)
else:
sip = sip_data.system_integrity_protection.root_patch_sip_big_sur
sip_hex = "0x802"
sip_value = (
f"For Hackintoshes, please set csr-active-config to '02080000' ({sip_hex})\nFor non-OpenCore Macs, please run 'csrutil disable' and \n'csrutil authenticated-root disable' in RecoveryOS"
)
else:
sip = sip_data.system_integrity_protection.root_patch_sip_mojave
sip_hex = "0x603"
sip_value = f"For Hackintoshes, please set csr-active-config to '03060000' ({sip_hex})\nFor non-OpenCore Macs, please run 'csrutil disable' in RecoveryOS"
return (sip, sip_value, sip_hex)
def detect_patch_set(self):
self.detect_gpus()
if self.model in model_array.LegacyBrightness:
if self.constants.detected_os > os_data.os_data.catalina:
self.brightness_legacy = True
if self.model in ["iMac7,1", "iMac8,1"] or (self.model in model_array.LegacyAudio and utilities.check_kext_loaded("AppleALC", self.constants.detected_os) is False):
# Special hack for systems with botched GOPs
# TL;DR: No Boot Screen breaks Lilu, therefore breaking audio
if self.constants.detected_os > os_data.os_data.catalina:
self.legacy_audio = True
if (
isinstance(self.constants.computer.wifi, device_probe.Broadcom)
and self.constants.computer.wifi.chipset in [device_probe.Broadcom.Chipsets.AirPortBrcm4331, device_probe.Broadcom.Chipsets.AirPortBrcm43224]
) or (isinstance(self.constants.computer.wifi, device_probe.Atheros) and self.constants.computer.wifi.chipset == device_probe.Atheros.Chipsets.AirPortAtheros40):
if os_data.os_data.ventura > self.constants.detected_os > os_data.os_data.big_sur:
self.legacy_wifi = True
# if self.model in ["MacBookPro5,1", "MacBookPro5,2", "MacBookPro5,3", "MacBookPro8,2", "MacBookPro8,3"]:
if self.model in ["MacBookPro8,2", "MacBookPro8,3"]:
# Sierra uses a legacy GMUX control method needed for dGPU switching on MacBookPro5,x
# Same method is also used for demuxed machines
# Note that MacBookPro5,x machines are extremely unstable with this patch set, so disabled until investigated further
# Ref: https://github.com/dortania/OpenCore-Legacy-Patcher/files/7360909/KP-b10-030.txt
if self.constants.detected_os > os_data.os_data.high_sierra:
if self.model in ["MacBookPro8,2", "MacBookPro8,3"]:
# Ref: https://doslabelectronics.com/Demux.html
if self.detect_demux() is True:
self.legacy_gmux = True
else:
self.legacy_gmux = True
self.root_patch_dict = {
"Graphics: Nvidia Tesla": self.nvidia_tesla,
"Graphics: Nvidia Kepler": self.kepler_gpu,
"Graphics: Nvidia Web Drivers": self.nvidia_web,
"Graphics: AMD TeraScale 1": self.amd_ts1,
"Graphics: AMD TeraScale 2": self.amd_ts2,
"Graphics: AMD Legacy GCN": self.legacy_gcn,
"Graphics: AMD Legacy Polaris": self.legacy_polaris,
"Graphics: Intel Ironlake": self.iron_gpu,
"Graphics: Intel Sandy Bridge": self.sandy_gpu,
"Graphics: Intel Ivy Bridge": self.ivy_gpu,
"Graphics: Intel Haswell": self.haswell_gpu,
"Graphics: Intel Broadwell": self.broadwell_gpu,
"Graphics: Intel Skylake": self.skylake_gpu,
"Brightness: Legacy Backlight Control": self.brightness_legacy,
"Audio: Legacy Realtek": self.legacy_audio,
"Networking: Legacy Wireless": self.legacy_wifi,
"Miscellaneous: Legacy GMUX": self.legacy_gmux,
"Miscellaneous: Legacy Keyboard Backlight": self.legacy_keyboard_backlight,
"Settings: Requires AMFI exemption": self.amfi_must_disable,
"Settings: Supports Auxiliary Cache": not self.requires_root_kc,
"Settings: Kernel Debug Kit missing": self.missing_kdk if self.constants.detected_os >= os_data.os_data.ventura.value else False,
"Validation: Patching Possible": self.verify_patch_allowed(),
f"Validation: SIP is enabled (Required: {self.check_sip()[2]} or higher)": self.sip_enabled,
f"Validation: Currently Booted SIP: ({hex(utilities.csr_dump())})": self.sip_enabled,
"Validation: SecureBootModel is enabled": self.sbm_enabled,
f"Validation: {'AMFI' if self.constants.host_is_hackintosh is True or self.get_amfi_level_needed() > 2 else 'Library Validation'} is enabled": self.amfi_enabled if self.amfi_must_disable is True else False,
"Validation: FileVault is enabled": self.fv_enabled,
"Validation: System is dosdude1 patched": self.dosdude_patched,
"Validation: WhateverGreen.kext missing": self.missing_whatever_green if self.nvidia_web is True else False,
"Validation: Force OpenGL property missing": self.missing_nv_web_opengl if self.nvidia_web is True else False,
"Validation: Force compat property missing": self.missing_nv_compat if self.nvidia_web is True else False,
"Validation: nvda_drv(_vrl) variable missing": self.missing_nv_web_nvram if self.nvidia_web is True else False,
}
return self.root_patch_dict
def get_amfi_level_needed(self):
if self.amfi_must_disable is True:
if self.constants.detected_os > os_data.os_data.catalina:
if self.constants.detected_os >= os_data.os_data.ventura:
if self.amfi_shim_bins is True:
# Currently we require AMFI outright disabled
# in Ventura to work with shim'd binaries
return 3
return 1
return 0
def verify_patch_allowed(self, print_errors=False):
sip_dict = self.check_sip()
sip = sip_dict[0]
sip_value = sip_dict[1]
self.sip_enabled, self.sbm_enabled, self.fv_enabled, self.dosdude_patched = utilities.patching_status(sip, self.constants.detected_os)
self.amfi_enabled = not amfi_detect.amfi_configuration_detection().check_config(self.get_amfi_level_needed())
if self.nvidia_web is True:
self.missing_nv_web_nvram = not self.check_nv_web_nvram()
self.missing_nv_web_opengl = not self.check_nv_web_opengl()
self.missing_nv_compat = not self.check_nv_compat()
self.missing_whatever_green = not self.check_whatevergreen()
if print_errors is True:
if self.sip_enabled is True:
print("\nCannot patch! Please disable System Integrity Protection (SIP).")
print("Disable SIP in Patcher Settings and Rebuild OpenCore\n")
print("Ensure the following bits are set for csr-active-config:")
print("\n".join(sip))
print(sip_value)
if self.sbm_enabled is True:
print("\nCannot patch! Please disable Apple Secure Boot.")
print("Disable SecureBootModel in Patcher Settings and Rebuild OpenCore")
print("For Hackintoshes, set SecureBootModel to Disabled")
if self.fv_enabled is True:
print("\nCannot patch! Please disable FileVault.")
print("For OCLP Macs, please rebuild your config with 0.2.5 or newer")
print("For others, Go to System Preferences -> Security and disable FileVault")
if self.amfi_enabled is True and self.amfi_must_disable is True:
print("\nCannot patch! Please disable AMFI.")
print("For Hackintoshes, please add amfi_get_out_of_my_way=1 to boot-args")
if self.dosdude_patched is True:
print("\nCannot patch! Detected machine has already been patched by another patcher")
print("Please ensure your install is either clean or patched with OpenCore Legacy Patcher")
if self.nvidia_web is True:
if self.missing_nv_web_opengl is True:
print("\nCannot patch! Force OpenGL property missing")
print("Please ensure ngfxgl=1 is set in boot-args")
if self.missing_nv_compat is True:
print("\nCannot patch! Force Nvidia compatibility property missing")
print("Please ensure ngfxcompat=1 is set in boot-args")
if self.missing_nv_web_nvram is True:
print("\nCannot patch! nvda_drv(_vrl) variable missing")
print("Please ensure nvda_drv_vrl=1 is set in boot-args")
if self.missing_whatever_green is True:
print("\nCannot patch! WhateverGreen.kext missing")
print("Please ensure WhateverGreen.kext is installed")
if any(
[
# General patch checks
self.sip_enabled, self.sbm_enabled, self.fv_enabled, self.dosdude_patched,
# non-Metal specific
self.amfi_enabled if self.amfi_must_disable is True else False,
# Web Driver specific
self.missing_nv_web_nvram if self.nvidia_web is True else False,
self.missing_nv_web_opengl if self.nvidia_web is True else False,
self.missing_nv_compat if self.nvidia_web is True else False,
self.missing_whatever_green if self.nvidia_web is True else False,
]
):
return False
else:
return True
def generate_patchset(self, hardware_details):
all_hardware_patchset = sys_patch_dict.SystemPatchDictionary(self.constants.detected_os, self.constants.detected_os_minor, self.constants.legacy_accel_support)
required_patches = {}
utilities.cls()
print("- The following patches will be applied:")
if hardware_details["Graphics: Intel Ironlake"] is True:
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]})
required_patches.update({"Intel Ironlake": all_hardware_patchset["Graphics"]["Intel Ironlake"]})
if hardware_details["Graphics: Intel Sandy Bridge"] is True:
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
required_patches.update({"High Sierra GVA": all_hardware_patchset["Graphics"]["High Sierra GVA"]})
required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]})
required_patches.update({"Intel Sandy Bridge": all_hardware_patchset["Graphics"]["Intel Sandy Bridge"]})
if hardware_details["Graphics: Intel Ivy Bridge"] is True:
required_patches.update({"Metal 3802 Common": all_hardware_patchset["Graphics"]["Metal 3802 Common"]})
required_patches.update({"Catalina GVA": all_hardware_patchset["Graphics"]["Catalina GVA"]})
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
required_patches.update({"Big Sur OpenCL": all_hardware_patchset["Graphics"]["Big Sur OpenCL"]})
required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]})
required_patches.update({"Intel Ivy Bridge": all_hardware_patchset["Graphics"]["Intel Ivy Bridge"]})
if hardware_details["Graphics: Intel Haswell"] is True:
required_patches.update({"Metal 3802 Common": all_hardware_patchset["Graphics"]["Metal 3802 Common"]})
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
required_patches.update({"Intel Haswell": all_hardware_patchset["Graphics"]["Intel Haswell"]})
if hardware_details["Graphics: Intel Broadwell"] is True:
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
required_patches.update({"Intel Broadwell": all_hardware_patchset["Graphics"]["Intel Broadwell"]})
if hardware_details["Graphics: Intel Skylake"] is True:
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
required_patches.update({"Intel Skylake": all_hardware_patchset["Graphics"]["Intel Skylake"]})
if hardware_details["Graphics: Nvidia Tesla"] is True:
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]})
required_patches.update({"Nvidia Tesla": all_hardware_patchset["Graphics"]["Nvidia Tesla"]})
if hardware_details["Graphics: Nvidia Web Drivers"] is True:
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
required_patches.update({"Non-Metal IOAccelerator Common": all_hardware_patchset["Graphics"]["Non-Metal IOAccelerator Common"]})
required_patches.update({"Non-Metal CoreDisplay Common": all_hardware_patchset["Graphics"]["Non-Metal CoreDisplay Common"]})
required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]})
required_patches.update({"Nvidia Web Drivers": all_hardware_patchset["Graphics"]["Nvidia Web Drivers"]})
required_patches.update({"Non-Metal Enforcement": all_hardware_patchset["Graphics"]["Non-Metal Enforcement"]})
if hardware_details["Graphics: Nvidia Kepler"] is True:
required_patches.update({"Revert Metal Downgrade": all_hardware_patchset["Graphics"]["Revert Metal Downgrade"]})
required_patches.update({"Metal 3802 Common": all_hardware_patchset["Graphics"]["Metal 3802 Common"]})
required_patches.update({"Catalina GVA": all_hardware_patchset["Graphics"]["Catalina GVA"]})
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
required_patches.update({"Big Sur OpenCL": all_hardware_patchset["Graphics"]["Big Sur OpenCL"]})
required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]})
required_patches.update({"Nvidia Kepler": all_hardware_patchset["Graphics"]["Nvidia Kepler"]})
for gpu in self.constants.computer.gpus:
# Handle mixed GPU situations (ie. MacBookPro11,3: Haswell iGPU + Kepler dGPU)
if gpu.arch == device_probe.Intel.Archs.Haswell:
if "Catalina GVA" in required_patches:
del(required_patches["Catalina GVA"])
break
if hardware_details["Graphics: AMD TeraScale 1"] is True:
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]})
required_patches.update({"AMD TeraScale Common": all_hardware_patchset["Graphics"]["AMD TeraScale Common"]})
required_patches.update({"AMD TeraScale 1": all_hardware_patchset["Graphics"]["AMD TeraScale 1"]})
if hardware_details["Graphics: AMD TeraScale 2"] is True:
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
required_patches.update({"Non-Metal IOAccelerator Common": all_hardware_patchset["Graphics"]["Non-Metal IOAccelerator Common"]})
required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]})
required_patches.update({"AMD TeraScale Common": all_hardware_patchset["Graphics"]["AMD TeraScale Common"]})
required_patches.update({"AMD TeraScale 2": all_hardware_patchset["Graphics"]["AMD TeraScale 2"]})
if self.constants.allow_ts2_accel is False or self.constants.detected_os not in self.constants.legacy_accel_support:
# TeraScale 2 MacBooks with faulty GPUs are highly prone to crashing with AMDRadeonX3000 attached
# Additionally, AMDRadeonX3000 requires IOAccelerator downgrade which is not installed without 'Non-Metal IOAccelerator Common'
del(required_patches["AMD TeraScale 2"]["Install"]["/System/Library/Extensions"]["AMDRadeonX3000.kext"])
if hardware_details["Graphics: AMD Legacy GCN"] is True or hardware_details["Graphics: AMD Legacy Polaris"] is True:
required_patches.update({"Revert Metal Downgrade": all_hardware_patchset["Graphics"]["Revert Metal Downgrade"]})
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
if hardware_details["Graphics: AMD Legacy GCN"] is True:
required_patches.update({"AMD Legacy GCN": all_hardware_patchset["Graphics"]["AMD Legacy GCN"]})
else:
required_patches.update({"AMD Legacy Polaris": all_hardware_patchset["Graphics"]["AMD Legacy Polaris"]})
if "AVX2" not in self.constants.computer.cpu.leafs:
required_patches.update({"AMD OpenCL": all_hardware_patchset["Graphics"]["AMD OpenCL"]})
if hardware_details["Brightness: Legacy Backlight Control"] is True:
required_patches.update({"Legacy Backlight Control": all_hardware_patchset["Brightness"]["Legacy Backlight Control"]})
if hardware_details["Audio: Legacy Realtek"] is True:
if self.model in ["iMac7,1", "iMac8,1"]:
required_patches.update({"Legacy Realtek": all_hardware_patchset["Audio"]["Legacy Realtek"]})
else:
required_patches.update({"Legacy Non-GOP": all_hardware_patchset["Audio"]["Legacy Non-GOP"]})
if hardware_details["Networking: Legacy Wireless"] is True:
required_patches.update({"Legacy Wireless": all_hardware_patchset["Networking"]["Legacy Wireless"]})
if hardware_details["Miscellaneous: Legacy GMUX"] is True:
required_patches.update({"Legacy GMUX": all_hardware_patchset["Miscellaneous"]["Legacy GMUX"]})
if hardware_details["Miscellaneous: Legacy Keyboard Backlight"] is True:
required_patches.update({"Legacy Keyboard Backlight": all_hardware_patchset["Miscellaneous"]["Legacy Keyboard Backlight"]})
if required_patches:
host_os_float = float(f"{self.constants.detected_os}.{self.constants.detected_os_minor}")
# Prioritize Monterey GVA patches
if "Catalina GVA" in required_patches and "Monterey GVA" in required_patches:
del(required_patches["Catalina GVA"])
for patch_name in list(required_patches):
patch_os_min_float = float(f'{required_patches[patch_name]["OS Support"]["Minimum OS Support"]["OS Major"]}.{required_patches[patch_name]["OS Support"]["Minimum OS Support"]["OS Minor"]}')
patch_os_max_float = float(f'{required_patches[patch_name]["OS Support"]["Maximum OS Support"]["OS Major"]}.{required_patches[patch_name]["OS Support"]["Maximum OS Support"]["OS Minor"]}')
if (host_os_float < patch_os_min_float or host_os_float > patch_os_max_float):
del(required_patches[patch_name])
else:
if required_patches[patch_name]["Display Name"]:
print(f" - {required_patches[patch_name]['Display Name']}")
else:
print(" - No patch sets found for booted model")
return required_patches
+32
View File
@@ -0,0 +1,32 @@
# Download PatcherSupportPkg for usage with Root Patching
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
from resources import utilities
from pathlib import Path
import shutil
class grab_patcher_support_pkg:
def __init__(self, constants):
self.constants = constants
def generate_pkg_link(self):
link = f"{self.constants.url_patcher_support_pkg}{self.constants.patcher_support_pkg_version}/Universal-Binaries.zip"
return link
def download_files(self):
link = self.generate_pkg_link()
if Path(self.constants.payload_local_binaries_root_path).exists():
print("- Removing old Root Patcher Payload folder")
# Delete folder
shutil.rmtree(self.constants.payload_local_binaries_root_path)
download_result = None
if Path(self.constants.payload_local_binaries_root_path_zip).exists():
print(f"- Found local Universal-Binaries.zip, skipping download")
download_result = True
else:
print(f"- No local version found, downloading...")
download_result = utilities.download_file(link, self.constants.payload_local_binaries_root_path_zip)
return download_result, link
+186
View File
@@ -0,0 +1,186 @@
# Additional support functions for sys_patch.py
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
import subprocess
import tempfile
from data import os_data
from resources import generate_smbios, utilities
from pathlib import Path
from datetime import datetime
import plistlib
import os
from resources import constants, bplist
class sys_patch_helpers:
def __init__(self, constants):
self.constants = constants
def snb_board_id_patch(self, source_files_path):
# AppleIntelSNBGraphicsFB hard codes the supported Board IDs for Sandy Bridge iGPUs
# Because of this, the kext errors out on unsupported systems
# This function simply patches in a supported Board ID, using 'determine_best_board_id_for_sandy()'
# to supplement the ideal Board ID
source_files_path = str(source_files_path)
if self.constants.computer.reported_board_id not in self.constants.sandy_board_id_stock:
print(f"- Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching")
board_to_patch = generate_smbios.determine_best_board_id_for_sandy(self.constants.computer.reported_board_id, self.constants.computer.gpus)
print(f"- Replacing {board_to_patch} with {self.constants.computer.reported_board_id}")
board_to_patch_hex = bytes.fromhex(board_to_patch.encode('utf-8').hex())
reported_board_hex = bytes.fromhex(self.constants.computer.reported_board_id.encode('utf-8').hex())
if len(board_to_patch_hex) > len(reported_board_hex):
# Pad the reported Board ID with zeros to match the length of the board to patch
reported_board_hex = reported_board_hex + bytes(len(board_to_patch_hex) - len(reported_board_hex))
elif len(board_to_patch_hex) < len(reported_board_hex):
print(f"- Error: Board ID {self.constants.computer.reported_board_id} is longer than {board_to_patch}")
raise Exception("Host's Board ID is longer than the kext's Board ID, cannot patch!!!")
path = source_files_path + "/10.13.6/System/Library/Extensions/AppleIntelSNBGraphicsFB.kext/Contents/MacOS/AppleIntelSNBGraphicsFB"
if Path(path).exists():
with open(path, 'rb') as f:
data = f.read()
data = data.replace(board_to_patch_hex, reported_board_hex)
with open(path, 'wb') as f:
f.write(data)
else:
print(f"- Error: Could not find {path}")
raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!")
def generate_patchset_plist(self, patchset, file_name, kdk_used):
source_path = f"{self.constants.payload_path}"
source_path_file = f"{source_path}/{file_name}"
kdk_string = "Not applicable"
if kdk_used:
kdk_string = kdk_used
data = {
"OpenCore Legacy Patcher": f"v{self.constants.patcher_version}",
"PatcherSupportPkg": f"v{self.constants.patcher_support_pkg_version}",
"Time Patched": f"{datetime.now().strftime('%B %d, %Y @ %H:%M:%S')}",
"Commit URL": f"{self.constants.commit_info[2]}",
"Kernel Debug Kit Used": f"{kdk_string}",
"OS Version": f"{self.constants.detected_os}.{self.constants.detected_os_minor} ({self.constants.detected_os_build})",
}
data.update(patchset)
if Path(source_path_file).exists():
os.remove(source_path_file)
# Need to write to a safe location
plistlib.dump(data, Path(source_path_file).open("wb"), sort_keys=False)
if Path(source_path_file).exists():
return True
return False
def install_kdk(self):
if not self.constants.kdk_download_path.exists():
return
print(f"- Installing downloaded KDK (this may take a while)")
with tempfile.TemporaryDirectory() as mount_point:
utilities.process_status(subprocess.run(["hdiutil", "attach", self.constants.kdk_download_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
# Install the KDK
utilities.process_status(utilities.elevated(["installer", "-pkg", f"{mount_point}/KernelDebugKit.pkg", "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # Do not really care if this fails
print("- Successfully installed KDK")
def determine_kdk_present(self, match_closest=False, override_build=None):
# Check if KDK is present
# If 'match_closest' is True, will provide the closest match to the reported KDK
kdk_array = []
search_build = self.constants.detected_os_build
if override_build:
search_build = override_build
if not Path("/Library/Developer/KDKs").exists():
return None
for kdk_folder in Path("/Library/Developer/KDKs").iterdir():
if not kdk_folder.name.endswith(".kdk"):
continue
# Ensure direct match
if kdk_folder.name.endswith(f"{search_build}.kdk"):
# Verify that the KDK is valid
if (kdk_folder / Path("System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern")).exists():
return kdk_folder
if match_closest is True:
# ex: KDK_13.0_22A5266r.kdk -> 22A5266r.kdk -> 22A5266r
try:
build = kdk_folder.name.split("_")[2].split(".")[0]
# Don't append if Darwin Major is different
if build.startswith(str(self.constants.detected_os)):
kdk_array.append(build)
except IndexError:
pass
if match_closest is True:
result = os_data.os_conversion.find_largest_build(kdk_array)
print(f"- Closest KDK match to {search_build}: {result}")
for kdk_folder in Path("/Library/Developer/KDKs").iterdir():
if kdk_folder.name.endswith(f"{result}.kdk"):
# Verify that the KDK is valid
if (kdk_folder / Path("System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern")).exists():
return kdk_folder
return None
def disable_window_server_caching(self):
# On legacy GCN GPUs, the WindowServer cache generated creates
# corrupted Opaque shaders.
# To work-around this, we disable WindowServer caching
# And force macOS into properly generating the Opaque shaders
if self.constants.detected_os < os_data.os_data.ventura:
return
print("- Disabling WindowServer Caching")
# Invoke via 'bash -c' to resolve pathing
utilities.elevated(["bash", "-c", "rm -rf /private/var/folders/*/*/*/WindowServer/com.apple.WindowServer"])
# Disable writing to WindowServer folder
utilities.elevated(["bash", "-c", "chflags uchg /private/var/folders/*/*/*/WindowServer"])
def remove_news_widgets(self):
# On Ivy Bridge and Haswell iGPUs, RenderBox will crash the News Widgets in
# Notification Centre. To ensure users can access Notifications normally,
# we manually remove all News Widgets
if self.constants.detected_os < os_data.os_data.ventura:
return
print("- Parsing Notification Centre Widgets")
file_path = "~/Library/Containers/com.apple.notificationcenterui/Data/Library/Preferences/com.apple.notificationcenterui.plist"
file_path = Path(file_path).expanduser()
if not file_path.exists():
print(" - Defaults file not found, skipping")
return
did_find = False
with open(file_path, "rb") as f:
data = plistlib.load(f)
if "widgets" in data:
if "instances" in data["widgets"]:
for widget in list(data["widgets"]["instances"]):
widget_data = bplist.BPListReader(widget).parse()
for entry in widget_data:
if not 'widget' in entry:
continue
sub_data = bplist.BPListReader(widget_data[entry]).parse()
for sub_entry in sub_data:
if not '$object' in sub_entry:
continue
if not b'com.apple.news' in sub_data[sub_entry][2]:
continue
print(f" - Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}")
data["widgets"]["instances"].remove(widget)
did_find = True
if did_find:
with open(file_path, "wb") as f:
plistlib.dump(data, f, sort_keys=False)
subprocess.run(["killall", "NotificationCenter"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)