mirror of
https://github.com/dortania/OpenCore-Legacy-Patcher.git
synced 2026-04-21 03:04:31 +10:00
sys_patch_helpers.py: Add docstring comments
This commit is contained in:
@@ -340,7 +340,7 @@ class PatchSysVolume:
|
||||
self._remove_file("/private/var/db/SystemPolicyConfiguration/", file)
|
||||
else:
|
||||
# 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")
|
||||
return True
|
||||
@@ -445,7 +445,7 @@ class PatchSysVolume:
|
||||
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):
|
||||
if sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, file_name, self.kdk_path):
|
||||
logging.info("- 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))
|
||||
@@ -576,9 +576,9 @@ class PatchSysVolume:
|
||||
logging.info(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", "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"]):
|
||||
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)
|
||||
|
||||
def _preflight_checks(self, required_patches, source_files_path):
|
||||
@@ -593,7 +593,7 @@ class PatchSysVolume:
|
||||
|
||||
# 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)
|
||||
sys_patch_helpers.SysPatchHelpers(self.constants).snb_board_id_patch(source_files_path)
|
||||
|
||||
for patch in required_patches:
|
||||
# Check if all files are present
|
||||
|
||||
@@ -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
|
||||
|
||||
from pathlib import Path
|
||||
import plistlib
|
||||
import subprocess
|
||||
import webbrowser
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from resources import utilities, updates, global_settings, network_handler, constants
|
||||
from resources.sys_patch import sys_patch_detect
|
||||
from resources.gui import gui_main
|
||||
|
||||
|
||||
class AutomaticSysPatch:
|
||||
"""
|
||||
Library of functions for launch agent, including automatic patching
|
||||
"""
|
||||
|
||||
def __init__(self, global_constants: constants.Constants):
|
||||
self.constants: constants.Constants = global_constants
|
||||
|
||||
|
||||
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")
|
||||
if self.constants.wxpython_variant is False:
|
||||
logging.info("- Auto Patch option is not supported on TUI, please use GUI")
|
||||
@@ -113,26 +126,35 @@ class AutomaticSysPatch:
|
||||
else:
|
||||
logging.info("- Detected Snapshot seal not intact, skipping")
|
||||
|
||||
if self.determine_if_versions_match() is False:
|
||||
self.determine_if_boot_matches()
|
||||
if self._determine_if_versions_match():
|
||||
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")
|
||||
if self.constants.computer.oclp_version is None:
|
||||
logging.info("- Booted version not found")
|
||||
return False
|
||||
return True
|
||||
|
||||
if self.constants.computer.oclp_version == self.constants.patcher_version:
|
||||
logging.info("- Versions match")
|
||||
return False
|
||||
return True
|
||||
|
||||
# Check if installed version is newer than booted version
|
||||
if updates.CheckBinaryUpdates(self.constants)._check_if_build_newer(
|
||||
self.constants.computer.oclp_version.split("."), self.constants.patcher_version.split(".")
|
||||
) is True:
|
||||
logging.info("- Installed version is newer than booted version")
|
||||
return False
|
||||
return True
|
||||
|
||||
args = [
|
||||
"osascript",
|
||||
@@ -150,17 +172,24 @@ class AutomaticSysPatch:
|
||||
self.constants.start_build_install = True
|
||||
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
|
||||
# and ask if they want to install to install to disk
|
||||
def _determine_if_boot_matches(self):
|
||||
"""
|
||||
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")
|
||||
|
||||
should_notify = global_settings.GlobalEnviromentSettings().read_property("AutoPatch_Notify_Mismatched_Disks")
|
||||
if should_notify is False:
|
||||
logging.info("- Skipping due to user preference")
|
||||
@@ -223,9 +252,16 @@ class AutomaticSysPatch:
|
||||
|
||||
|
||||
def install_auto_patcher_launch_agent(self):
|
||||
# Installs the following:
|
||||
# - OpenCore-Patcher.app in /Library/Application Support/Dortania/
|
||||
# - com.dortania.opencore-legacy-patcher.auto-patch.plist in /Library/LaunchAgents/
|
||||
"""
|
||||
Install the Auto Patcher Launch Agent
|
||||
|
||||
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:
|
||||
logging.info("- Skipping Auto Patcher Launch Agent, not supported when running from source")
|
||||
return
|
||||
|
||||
@@ -12,46 +12,75 @@ from data import os_data
|
||||
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):
|
||||
self.constants: constants.Constants = global_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
|
||||
def snb_board_id_patch(self, source_files_path: str):
|
||||
"""
|
||||
Patch AppleIntelSNBGraphicsFB.kext to support unsupported Board IDs
|
||||
|
||||
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)
|
||||
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())
|
||||
reported_board_hex = bytes.fromhex(self.constants.computer.reported_board_id.encode('utf-8').hex())
|
||||
if self.constants.computer.reported_board_id in self.constants.sandy_board_id_stock:
|
||||
return
|
||||
|
||||
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):
|
||||
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!!!")
|
||||
logging.info(f"- Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching")
|
||||
|
||||
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:
|
||||
logging.info(f"- Error: Could not find {path}")
|
||||
raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!")
|
||||
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())
|
||||
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):
|
||||
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_file = f"{source_path}/{file_name}"
|
||||
|
||||
@@ -67,23 +96,35 @@ class sys_patch_helpers:
|
||||
"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 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
|
||||
"""
|
||||
Disable WindowServer's asset caching
|
||||
|
||||
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
|
||||
|
||||
logging.info("- Disabling WindowServer Caching")
|
||||
# Invoke via 'bash -c' to resolve pathing
|
||||
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):
|
||||
# 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
|
||||
"""
|
||||
Remove News Widgets from Notification Centre
|
||||
|
||||
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
|
||||
|
||||
logging.info("- Parsing Notification Centre Widgets")
|
||||
file_path = "~/Library/Containers/com.apple.notificationcenterui/Data/Library/Preferences/com.apple.notificationcenterui.plist"
|
||||
file_path = Path(file_path).expanduser()
|
||||
@@ -111,22 +158,27 @@ class sys_patch_helpers:
|
||||
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
|
||||
logging.info(f" - Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}")
|
||||
data["widgets"]["instances"].remove(widget)
|
||||
did_find = True
|
||||
if "widgets" not in data:
|
||||
return
|
||||
|
||||
if "instances" not in data["widgets"]:
|
||||
return
|
||||
|
||||
for widget in list(data["widgets"]["instances"]):
|
||||
widget_data = bplist.BPListReader(widget).parse()
|
||||
for entry in widget_data:
|
||||
if 'widget' not 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
|
||||
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:
|
||||
with open(file_path, "wb") as f:
|
||||
plistlib.dump(data, f, sort_keys=False)
|
||||
@@ -134,16 +186,23 @@ class sys_patch_helpers:
|
||||
|
||||
|
||||
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,
|
||||
# which forgets to handle Preboot BootKC syncing
|
||||
"""
|
||||
Installs RSRRepair
|
||||
|
||||
# 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
|
||||
RSRRepair is a utility that will sync the SysKC and BootKC in the event of a panic
|
||||
|
||||
# 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+
|
||||
# https://github.com/flagersgit/RSRRepair
|
||||
With macOS 13.2, Apple implemented the Rapid Security Response System
|
||||
However Apple added a half baked snapshot reversion system if seal was broken,
|
||||
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:
|
||||
return
|
||||
|
||||
@@ -117,7 +117,7 @@ class PatcherValidation:
|
||||
raise Exception(f"Failed to find {source_file}")
|
||||
|
||||
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")
|
||||
|
||||
# Remove the plist file after validation
|
||||
@@ -146,7 +146,7 @@ class PatcherValidation:
|
||||
self._validate_root_patch_files(supported_os, i)
|
||||
logging.info("Validating SNB Board ID patcher")
|
||||
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
|
||||
subprocess.run(
|
||||
|
||||
Reference in New Issue
Block a user