sys_patch_helpers.py: Add docstring comments

This commit is contained in:
Mykola Grymalyuk
2023-02-10 13:23:40 -07:00
parent 44369e2faa
commit 6a3023301a
4 changed files with 187 additions and 92 deletions
+5 -5
View File
@@ -340,7 +340,7 @@ class PatchSysVolume:
self._remove_file("/private/var/db/SystemPolicyConfiguration/", file) self._remove_file("/private/var/db/SystemPolicyConfiguration/", file)
else: else:
# Install RSRHelper utility to handle desynced KCs # Install RSRHelper utility to handle desynced KCs
sys_patch_helpers.sys_patch_helpers(self.constants).install_rsr_repair_binary() sys_patch_helpers.SysPatchHelpers(self.constants).install_rsr_repair_binary()
logging.info("- Successfully built new kernel cache") logging.info("- Successfully built new kernel cache")
return True return True
@@ -445,7 +445,7 @@ class PatchSysVolume:
destination_path = f"{self.mount_location}/System/Library/CoreServices" destination_path = f"{self.mount_location}/System/Library/CoreServices"
file_name = "OpenCore-Legacy-Patcher.plist" file_name = "OpenCore-Legacy-Patcher.plist"
destination_path_file = f"{destination_path}/{file_name}" 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): if sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, file_name, self.kdk_path):
logging.info("- Writing patchset information to Root Volume") logging.info("- Writing patchset information to Root Volume")
if Path(destination_path_file).exists(): 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(["rm", destination_path_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
@@ -576,9 +576,9 @@ class PatchSysVolume:
logging.info(f"- Running Process:\n{process}") logging.info(f"- Running Process:\n{process}")
utilities.process_status(subprocess.run(process, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)) 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", "AMD Legacy Vega"]): if any(x in required_patches for x in ["AMD Legacy GCN", "AMD Legacy Polaris", "AMD Legacy Vega"]):
sys_patch_helpers.sys_patch_helpers(self.constants).disable_window_server_caching() sys_patch_helpers.SysPatchHelpers(self.constants).disable_window_server_caching()
if any(x in required_patches for x in ["Intel Ivy Bridge", "Intel Haswell"]): 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() sys_patch_helpers.SysPatchHelpers(self.constants).remove_news_widgets()
self._write_patchset(required_patches) self._write_patchset(required_patches)
def _preflight_checks(self, required_patches, source_files_path): def _preflight_checks(self, required_patches, source_files_path):
@@ -593,7 +593,7 @@ class PatchSysVolume:
# Make sure SNB kexts are compatible with the host # Make sure SNB kexts are compatible with the host
if "Intel Sandy Bridge" in required_patches: if "Intel Sandy Bridge" in required_patches:
sys_patch_helpers.sys_patch_helpers(self.constants).snb_board_id_patch(source_files_path) sys_patch_helpers.SysPatchHelpers(self.constants).snb_board_id_patch(source_files_path)
for patch in required_patches: for patch in required_patches:
# Check if all files are present # Check if all files are present
+61 -25
View File
@@ -1,29 +1,42 @@
# 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 # Copyright (C) 2022, Mykola Grymalyuk
from pathlib import Path
import plistlib import plistlib
import subprocess import subprocess
import webbrowser import webbrowser
import logging import logging
from pathlib import Path
from resources import utilities, updates, global_settings, network_handler, constants from resources import utilities, updates, global_settings, network_handler, constants
from resources.sys_patch import sys_patch_detect from resources.sys_patch import sys_patch_detect
from resources.gui import gui_main from resources.gui import gui_main
class AutomaticSysPatch: class AutomaticSysPatch:
"""
Library of functions for launch agent, including automatic patching
"""
def __init__(self, global_constants: constants.Constants): def __init__(self, global_constants: constants.Constants):
self.constants: constants.Constants = global_constants self.constants: constants.Constants = global_constants
def start_auto_patch(self): def start_auto_patch(self):
"""
Initiates automatic patching
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
"""
logging.info("- Starting Automatic Patching") logging.info("- Starting Automatic Patching")
if self.constants.wxpython_variant is False: if self.constants.wxpython_variant is False:
logging.info("- Auto Patch option is not supported on TUI, please use GUI") logging.info("- Auto Patch option is not supported on TUI, please use GUI")
@@ -113,26 +126,35 @@ class AutomaticSysPatch:
else: else:
logging.info("- Detected Snapshot seal not intact, skipping") logging.info("- Detected Snapshot seal not intact, skipping")
if self.determine_if_versions_match() is False: if self._determine_if_versions_match():
self.determine_if_boot_matches() self._determine_if_boot_matches()
def determine_if_versions_match(self): def _determine_if_versions_match(self):
"""
Determine if the booted version of OCLP matches the installed version
ie. Installed app is 0.2.0, but EFI version is 0.1.0
Returns:
bool: True if versions match, False if not
"""
logging.info("- Checking booted vs installed OCLP Build") logging.info("- Checking booted vs installed OCLP Build")
if self.constants.computer.oclp_version is None: if self.constants.computer.oclp_version is None:
logging.info("- Booted version not found") logging.info("- Booted version not found")
return False return True
if self.constants.computer.oclp_version == self.constants.patcher_version: if self.constants.computer.oclp_version == self.constants.patcher_version:
logging.info("- Versions match") logging.info("- Versions match")
return False return True
# Check if installed version is newer than booted version # Check if installed version is newer than booted version
if updates.CheckBinaryUpdates(self.constants)._check_if_build_newer( if updates.CheckBinaryUpdates(self.constants)._check_if_build_newer(
self.constants.computer.oclp_version.split("."), self.constants.patcher_version.split(".") self.constants.computer.oclp_version.split("."), self.constants.patcher_version.split(".")
) is True: ) is True:
logging.info("- Installed version is newer than booted version") logging.info("- Installed version is newer than booted version")
return False return True
args = [ args = [
"osascript", "osascript",
@@ -150,17 +172,24 @@ class AutomaticSysPatch:
self.constants.start_build_install = True self.constants.start_build_install = True
gui_main.wx_python_gui(self.constants).main_menu(None) gui_main.wx_python_gui(self.constants).main_menu(None)
return True return False
def determine_if_boot_matches(self):
# 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 def _determine_if_boot_matches(self):
# and ask if they want to install to install to disk """
Determine if the boot drive matches the macOS drive
ie. Booted from USB, but macOS is on internal disk
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.
"""
logging.info("- Determining if macOS drive matches boot drive") logging.info("- Determining if macOS drive matches boot drive")
should_notify = global_settings.GlobalEnviromentSettings().read_property("AutoPatch_Notify_Mismatched_Disks") should_notify = global_settings.GlobalEnviromentSettings().read_property("AutoPatch_Notify_Mismatched_Disks")
if should_notify is False: if should_notify is False:
logging.info("- Skipping due to user preference") logging.info("- Skipping due to user preference")
@@ -223,9 +252,16 @@ class AutomaticSysPatch:
def install_auto_patcher_launch_agent(self): def install_auto_patcher_launch_agent(self):
# Installs the following: """
# - OpenCore-Patcher.app in /Library/Application Support/Dortania/ Install the Auto Patcher Launch Agent
# - com.dortania.opencore-legacy-patcher.auto-patch.plist in /Library/LaunchAgents/
Installs the following:
- OpenCore-Patcher.app in /Library/Application Support/Dortania/
- com.dortania.opencore-legacy-patcher.auto-patch.plist in /Library/LaunchAgents/
See start_auto_patch() comments for more info
"""
if self.constants.launcher_script is not None: if self.constants.launcher_script is not None:
logging.info("- Skipping Auto Patcher Launch Agent, not supported when running from source") logging.info("- Skipping Auto Patcher Launch Agent, not supported when running from source")
return return
+119 -60
View File
@@ -12,46 +12,75 @@ from data import os_data
from resources import bplist, constants, generate_smbios, utilities from resources import bplist, constants, generate_smbios, utilities
class sys_patch_helpers: class SysPatchHelpers:
"""
Library of helper functions for sys_patch.py and related libraries
"""
def __init__(self, global_constants: constants.Constants): def __init__(self, global_constants: constants.Constants):
self.constants: constants.Constants = global_constants self.constants: constants.Constants = global_constants
def snb_board_id_patch(self, source_files_path): def snb_board_id_patch(self, source_files_path: str):
# AppleIntelSNBGraphicsFB hard codes the supported Board IDs for Sandy Bridge iGPUs """
# Because of this, the kext errors out on unsupported systems Patch AppleIntelSNBGraphicsFB.kext to support unsupported Board IDs
# This function simply patches in a supported Board ID, using 'determine_best_board_id_for_sandy()'
# to supplement the ideal Board ID 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
Parameters:
source_files_path (str): Path to the source files
"""
source_files_path = str(source_files_path) source_files_path = str(source_files_path)
if self.constants.computer.reported_board_id not in self.constants.sandy_board_id_stock:
logging.info(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)
logging.info(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()) if self.constants.computer.reported_board_id in self.constants.sandy_board_id_stock:
reported_board_hex = bytes.fromhex(self.constants.computer.reported_board_id.encode('utf-8').hex()) return
if len(board_to_patch_hex) > len(reported_board_hex): logging.info(f"- Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching")
# 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):
logging.info(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" board_to_patch = generate_smbios.determine_best_board_id_for_sandy(self.constants.computer.reported_board_id, self.constants.computer.gpus)
if Path(path).exists(): logging.info(f"- Replacing {board_to_patch} with {self.constants.computer.reported_board_id}")
with open(path, 'rb') as f:
data = f.read() board_to_patch_hex = bytes.fromhex(board_to_patch.encode('utf-8').hex())
data = data.replace(board_to_patch_hex, reported_board_hex) reported_board_hex = bytes.fromhex(self.constants.computer.reported_board_id.encode('utf-8').hex())
with open(path, 'wb') as f:
f.write(data) if len(board_to_patch_hex) > len(reported_board_hex):
else: # Pad the reported Board ID with zeros to match the length of the board to patch
logging.info(f"- Error: Could not find {path}") reported_board_hex = reported_board_hex + bytes(len(board_to_patch_hex) - len(reported_board_hex))
raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!") elif len(board_to_patch_hex) < len(reported_board_hex):
logging.info(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 not Path(path).exists():
logging.info(f"- Error: Could not find {path}")
raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!")
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)
def generate_patchset_plist(self, patchset, file_name, kdk_used): def generate_patchset_plist(self, patchset: dict, file_name: str, kdk_used: Path):
"""
Generate patchset file for user reference
Parameters:
patchset (dict): Dictionary of patchset, see sys_patch_detect.py and sys_patch_dict.py
file_name (str): Name of the file to write to
kdk_used (Path): Path to the KDK used, if any
Returns:
bool: True if successful, False if not
"""
source_path = f"{self.constants.payload_path}" source_path = f"{self.constants.payload_path}"
source_path_file = f"{source_path}/{file_name}" source_path_file = f"{source_path}/{file_name}"
@@ -67,23 +96,35 @@ class sys_patch_helpers:
"Kernel Debug Kit Used": f"{kdk_string}", "Kernel Debug Kit Used": f"{kdk_string}",
"OS Version": f"{self.constants.detected_os}.{self.constants.detected_os_minor} ({self.constants.detected_os_build})", "OS Version": f"{self.constants.detected_os}.{self.constants.detected_os_minor} ({self.constants.detected_os_build})",
} }
data.update(patchset) data.update(patchset)
if Path(source_path_file).exists(): if Path(source_path_file).exists():
os.remove(source_path_file) os.remove(source_path_file)
# Need to write to a safe location # Need to write to a safe location
plistlib.dump(data, Path(source_path_file).open("wb"), sort_keys=False) plistlib.dump(data, Path(source_path_file).open("wb"), sort_keys=False)
if Path(source_path_file).exists(): if Path(source_path_file).exists():
return True return True
return False return False
def disable_window_server_caching(self): def disable_window_server_caching(self):
# On legacy GCN GPUs, the WindowServer cache generated creates """
# corrupted Opaque shaders. Disable WindowServer's asset caching
# To work-around this, we disable WindowServer caching
# And force macOS into properly generating the Opaque shaders 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: if self.constants.detected_os < os_data.os_data.ventura:
return return
logging.info("- Disabling WindowServer Caching") logging.info("- Disabling WindowServer Caching")
# Invoke via 'bash -c' to resolve pathing # Invoke via 'bash -c' to resolve pathing
utilities.elevated(["bash", "-c", "rm -rf /private/var/folders/*/*/*/WindowServer/com.apple.WindowServer"]) utilities.elevated(["bash", "-c", "rm -rf /private/var/folders/*/*/*/WindowServer/com.apple.WindowServer"])
@@ -95,11 +136,17 @@ class sys_patch_helpers:
def remove_news_widgets(self): 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, Remove News Widgets from Notification Centre
# we manually remove all News Widgets
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: if self.constants.detected_os < os_data.os_data.ventura:
return return
logging.info("- Parsing Notification Centre Widgets") logging.info("- Parsing Notification Centre Widgets")
file_path = "~/Library/Containers/com.apple.notificationcenterui/Data/Library/Preferences/com.apple.notificationcenterui.plist" file_path = "~/Library/Containers/com.apple.notificationcenterui/Data/Library/Preferences/com.apple.notificationcenterui.plist"
file_path = Path(file_path).expanduser() file_path = Path(file_path).expanduser()
@@ -111,22 +158,27 @@ class sys_patch_helpers:
did_find = False did_find = False
with open(file_path, "rb") as f: with open(file_path, "rb") as f:
data = plistlib.load(f) data = plistlib.load(f)
if "widgets" in data: if "widgets" not in data:
if "instances" in data["widgets"]: return
for widget in list(data["widgets"]["instances"]):
widget_data = bplist.BPListReader(widget).parse() if "instances" not in data["widgets"]:
for entry in widget_data: return
if not 'widget' in entry:
continue for widget in list(data["widgets"]["instances"]):
sub_data = bplist.BPListReader(widget_data[entry]).parse() widget_data = bplist.BPListReader(widget).parse()
for sub_entry in sub_data: for entry in widget_data:
if not '$object' in sub_entry: if 'widget' not in entry:
continue continue
if not b'com.apple.news' in sub_data[sub_entry][2]: sub_data = bplist.BPListReader(widget_data[entry]).parse()
continue for sub_entry in sub_data:
logging.info(f" - Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}") if not '$object' in sub_entry:
data["widgets"]["instances"].remove(widget) continue
did_find = True if not b'com.apple.news' in sub_data[sub_entry][2]:
continue
logging.info(f" - Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}")
data["widgets"]["instances"].remove(widget)
did_find = True
if did_find: if did_find:
with open(file_path, "wb") as f: with open(file_path, "wb") as f:
plistlib.dump(data, f, sort_keys=False) plistlib.dump(data, f, sort_keys=False)
@@ -134,16 +186,23 @@ class sys_patch_helpers:
def install_rsr_repair_binary(self): def install_rsr_repair_binary(self):
# With macOS 13.2, Apple implemented the Rapid Security Response System """
# However Apple added a half baked snapshot reversion system if seal was broken, Installs RSRRepair
# which forgets to handle Preboot BootKC syncing
# Thus this application will try to re-sync the BootKC with SysKC in the event of a panic RSRRepair is a utility that will sync the SysKC and BootKC in the event of a panic
# Reference: https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1019
# This is a (hopefully) temporary work-around, however likely to stay. With macOS 13.2, Apple implemented the Rapid Security Response System
# RSRRepair has the added bonus of fixing desynced KCs from 'bless', so useful in Big Sur+ However Apple added a half baked snapshot reversion system if seal was broken,
# https://github.com/flagersgit/RSRRepair which forgets to handle Preboot BootKC syncing.
Thus this application will try to re-sync the BootKC with SysKC in the event of a panic
Reference: https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1019
This is a (hopefully) temporary work-around, however likely to stay.
RSRRepair has the added bonus of fixing desynced KCs from 'bless', so useful in Big Sur+
Source: https://github.com/flagersgit/RSRRepair
"""
if self.constants.detected_os < os_data.os_data.big_sur: if self.constants.detected_os < os_data.os_data.big_sur:
return return
+2 -2
View File
@@ -117,7 +117,7 @@ class PatcherValidation:
raise Exception(f"Failed to find {source_file}") raise Exception(f"Failed to find {source_file}")
logging.info(f"- Validating against Darwin {major_kernel}.{minor_kernel}") logging.info(f"- Validating against Darwin {major_kernel}.{minor_kernel}")
if not sys_patch_helpers.sys_patch_helpers(self.constants).generate_patchset_plist(patchset, f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist", None): if not sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist", None):
raise Exception("Failed to generate patchset plist") raise Exception("Failed to generate patchset plist")
# Remove the plist file after validation # Remove the plist file after validation
@@ -146,7 +146,7 @@ class PatcherValidation:
self._validate_root_patch_files(supported_os, i) self._validate_root_patch_files(supported_os, i)
logging.info("Validating SNB Board ID patcher") logging.info("Validating SNB Board ID patcher")
self.constants.computer.reported_board_id = "Mac-7BA5B2DFE22DDD8C" self.constants.computer.reported_board_id = "Mac-7BA5B2DFE22DDD8C"
sys_patch_helpers.sys_patch_helpers(self.constants).snb_board_id_patch(self.constants.payload_local_binaries_root_path) sys_patch_helpers.SysPatchHelpers(self.constants).snb_board_id_patch(self.constants.payload_local_binaries_root_path)
# Clean up # Clean up
subprocess.run( subprocess.run(