diff --git a/Build-Binary.command b/Build-Binary.command index 1f8ee9427..618cb1c44 100755 --- a/Build-Binary.command +++ b/Build-Binary.command @@ -241,6 +241,7 @@ class CreateBinary: whitelist_files = [ "com.dortania.opencore-legacy-patcher.auto-patch.plist", "com.dortania.opencore-legacy-patcher.rsr-monitor.plist", + "com.dortania.opencore-legacy-patcher.macos-update.plist", "entitlements.plist", "launcher.sh", "OC-Patcher-TUI.icns", diff --git a/CHANGELOG.md b/CHANGELOG.md index 39c8ef59e..84c15f1b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ - Add support for detecting T1 Security Chips in DFU mode - Update non-Metal Binaries for macOS Sonoma: - Resolve Photos app crash +- Add new Launch Daemon for clean up on macOS updates + - Resolves KDKless Macs failing to boot after updating from 14.0 to 14.x + - `/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.macos-update.plist` +- Remove News Widget removal from Control Centre + - News Widget no longer crashes on 3802-based GPUs +- Resolve i210 NIC support for macOS Sonoma - Increment Binaries: - PatcherSupportPkg 1.4.3 - release diff --git a/payloads/com.dortania.opencore-legacy-patcher.macos-update.plist b/payloads/com.dortania.opencore-legacy-patcher.macos-update.plist new file mode 100644 index 000000000..dfabe32ca --- /dev/null +++ b/payloads/com.dortania.opencore-legacy-patcher.macos-update.plist @@ -0,0 +1,19 @@ + + + + + AssociatedBundleIdentifiers + com.dortania.opencore-legacy-patcher + Label + com.dortania.opencore-legacy-patcher.rsr-monitor + ProgramArguments + + /Library/Application Support/Dortania/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher + --prepare_for_update + + WatchPaths + + /System/Volumes/Update/Update.plist + + + diff --git a/resources/arguments.py b/resources/arguments.py index 9157e35ea..9672dad76 100644 --- a/resources/arguments.py +++ b/resources/arguments.py @@ -1,12 +1,16 @@ -import threading +import sys import time import logging -import sys +import plistlib +import threading +import subprocess -from resources import defaults, utilities, validation, constants -from resources.sys_patch import sys_patch, sys_patch_auto +from pathlib import Path + +from data import model_array, os_data from resources.build import build -from data import model_array +from resources.sys_patch import sys_patch, sys_patch_auto +from resources import defaults, utilities, validation, constants # Generic building args @@ -41,6 +45,11 @@ class arguments: self._sys_unpatch_handler() return + if self.args.prepare_for_update: + logging.info("Preparing host for macOS update") + self._clean_le_handler() + return + if self.args.auto_patch: self._sys_patch_auto_handler() return @@ -88,6 +97,46 @@ class arguments: sys_patch_auto.AutomaticSysPatch(self.constants).start_auto_patch() + def _clean_le_handler(self) -> None: + """ + Check if software update is staged + If so, clean /Library/Extensions + """ + + if self.constants.detected_os < os_data.os_data.sonoma: + logging.info("Host doesn't require cleaning, skipping") + return + + update_config = "/System/Volumes/Update/Update.plist" + if not Path(update_config).exists(): + logging.info("No update staged, skipping") + return + + try: + update_staged = plistlib.load(open(update_config, "rb")) + except Exception as e: + logging.error(f"Failed to load update config: {e}") + return + if "update-asset-attributes" not in update_staged: + logging.info("No update staged, skipping") + return + + logging.info("Update staged, cleaning /Library/Extensions") + + for kext in Path("/Library/Extensions").glob("*.kext"): + if not Path(f"{kext}/Contents/Info.plist").exists(): + continue + try: + kext_plist = plistlib.load(open(f"{kext}/Contents/Info.plist", "rb")) + except Exception as e: + logging.info(f" - Failed to load plist for {kext.name}: {e}") + continue + if "GPUCompanionBundles" not in kext_plist: + continue + logging.info(f" - Removing {kext.name}") + subprocess.run(["rm", "-rf", kext]) + + def _build_handler(self) -> None: """ Start config building process diff --git a/resources/build/networking/wired.py b/resources/build/networking/wired.py index c2de8cc05..9b31fec6e 100644 --- a/resources/build/networking/wired.py +++ b/resources/build/networking/wired.py @@ -35,6 +35,7 @@ class BuildWiredNetworking: # Always enable due to chance of hot-plugging self._usb_ecm_dongles() + self._i210_handling() def _usb_ecm_dongles(self) -> None: @@ -48,6 +49,21 @@ class BuildWiredNetworking: # - Kext: AppleUSBECM.kext support.BuildSupport(self.model, self.constants, self.config).enable_kext("ECM-Override.kext", self.constants.ecm_override_version, self.constants.ecm_override_path) + + def _i210_handling(self) -> None: + """ + PCIe i210 NIC Handling + """ + # i210 NICs are broke in macOS 14 due to driver kit downgrades + # See ECM logic for why it's always enabled + if not self.model in smbios_data.smbios_dictionary: + return + support.BuildSupport(self.model, self.constants, self.config).enable_kext("CatalinaIntelI210Ethernet.kext", self.constants.i210_version, self.constants.i210_path) + # Ivy Bridge and newer natively support DriverKit, so set MinKernel to 23.0.0 + if smbios_data.smbios_dictionary[self.model]["CPU Generation"] >= cpu_data.CPUGen.ivy_bridge.value: + support.BuildSupport(self.model, self.constants, self.config).get_kext_by_bundle_path("CatalinaIntelI210Ethernet.kext")["MinKernel"] = "23.0.0" + + def _on_model(self) -> None: """ On-Model Hardware Detection Handling diff --git a/resources/constants.py b/resources/constants.py index 29594cc4b..c1c3e028d 100644 --- a/resources/constants.py +++ b/resources/constants.py @@ -288,6 +288,10 @@ class Constants: def rsr_monitor_launch_daemon_path(self): return self.payload_path / Path("com.dortania.opencore-legacy-patcher.rsr-monitor.plist") + @property + def update_launch_daemon_path(self): + return self.payload_path / Path("com.dortania.opencore-legacy-patcher.macos-update.plist") + # ACPI @property def pci_ssdt_path(self): diff --git a/resources/logging_handler.py b/resources/logging_handler.py index c6b64ad93..bac812dd8 100644 --- a/resources/logging_handler.py +++ b/resources/logging_handler.py @@ -61,7 +61,7 @@ class InitializeLoggingSupport: """ base_path = Path("~/Library/Logs").expanduser() - if not base_path.exists(): + if not base_path.exists() or str(base_path).startswith("/var/root/"): # Likely in an installer environment, store in /Users/Shared base_path = Path("/Users/Shared") else: @@ -71,7 +71,7 @@ class InitializeLoggingSupport: try: base_path.mkdir() except Exception as e: - logging.error(f"Failed to create Dortania folder: {e}") + print(f"Failed to create Dortania folder: {e}") base_path = Path("/Users/Shared") self.log_filepath = Path(f"{base_path}/{self.log_filename}").expanduser() @@ -194,6 +194,7 @@ class InitializeLoggingSupport: logging.info('#' * str_len) logging.info("Log file set:") + logging.info(f" {self.log_filepath}") # Display relative path to avoid disclosing user's username try: path = self.log_filepath.relative_to(Path.home()) diff --git a/resources/sys_patch/sys_patch.py b/resources/sys_patch/sys_patch.py index 76557690d..0526c7e64 100644 --- a/resources/sys_patch/sys_patch.py +++ b/resources/sys_patch/sys_patch.py @@ -716,8 +716,6 @@ class PatchSysVolume: 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.SysPatchHelpers(self.constants).disable_window_server_caching() - if any(x in required_patches for x in ["Intel Ivy Bridge", "Intel Haswell"]): - sys_patch_helpers.SysPatchHelpers(self.constants).remove_news_widgets() if "Metal 3802 Common Extended" in required_patches: sys_patch_helpers.SysPatchHelpers(self.constants).patch_gpu_compiler_libraries(mount_point=self.mount_location) diff --git a/resources/sys_patch/sys_patch_auto.py b/resources/sys_patch/sys_patch_auto.py index 683ff6035..3a6722f8b 100644 --- a/resources/sys_patch/sys_patch_auto.py +++ b/resources/sys_patch/sys_patch_auto.py @@ -377,36 +377,28 @@ Please check the Github page for more information about this release.""" subprocess.run(["xattr", "-cr", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - # Copy over our launch agent - logging.info("- Copying auto-patch.plist Launch Agent to /Library/LaunchAgents/") - if Path("/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist").exists(): - logging.info("- 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(): - logging.info("- Creating /Library/LaunchAgents/") - utilities.process_status(utilities.elevated(["mkdir", "-p", "/Library/LaunchAgents/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - utilities.process_status(utilities.elevated(["cp", self.constants.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 - logging.info("- 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)) + services = { + self.constants.auto_patch_launch_agent_path: "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist", + self.constants.update_launch_daemon_path: "/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.macos-update.plist", + **({ self.constants.rsr_monitor_launch_daemon_path: "/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.rsr-monitor.plist" } if self._create_rsr_monitor_daemon() else {}), + } - # Copy over our launch daemon - if self._create_rsr_monitor_daemon() is True: - logging.info("- Copying rsr-monitor.plist Launch Daemon to /Library/LaunchDaemons/") - if Path("/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.rsr-monitor.plist").exists(): - logging.info("- Deleting existing rsr-monitor.plist") - utilities.process_status(utilities.elevated(["rm", "/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.rsr-monitor.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - if not Path("/Library/LaunchDaemons/").exists(): - logging.info("- Creating /Library/LaunchDaemons/") - utilities.process_status(utilities.elevated(["mkdir", "-p", "/Library/LaunchDaemons/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - utilities.process_status(utilities.elevated(["cp", self.constants.rsr_monitor_launch_daemon_path, "/Library/LaunchDaemons/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + for service in services: + name = Path(service).name + logging.info(f"- Installing {name}") + if Path(services[service]).exists(): + logging.info(f" - Existing service found, removing") + utilities.process_status(utilities.elevated(["rm", services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + # Create parent directories + if not Path(services[service]).parent.exists(): + logging.info(f" - Creating {Path(services[service]).parent} directory") + utilities.process_status(utilities.elevated(["mkdir", "-p", Path(services[service]).parent], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + utilities.process_status(utilities.elevated(["cp", service, services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - # Set the permissions on the com.dortania.opencore-legacy-patcher.rsr-monitor.plist - logging.info("- Setting permissions on rsr-monitor.plist") - utilities.process_status(utilities.elevated(["chmod", "644", "/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.rsr-monitor.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - utilities.process_status(utilities.elevated(["chown", "root:wheel", "/Library/LaunchDaemons/com.dortania.opencore-legacy-patcher.rsr-monitor.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + # Set the permissions on the service + utilities.process_status(utilities.elevated(["chmod", "644", services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + utilities.process_status(utilities.elevated(["chown", "root:wheel", services[service]], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) # Making app alias # Simply an easy way for users to notice the app diff --git a/resources/utilities.py b/resources/utilities.py index 117a6dca4..ed5cfeceb 100644 --- a/resources/utilities.py +++ b/resources/utilities.py @@ -580,6 +580,7 @@ def check_cli_args(): # sys_patch args parser.add_argument("--patch_sys_vol", help="Patches root volume", action="store_true", required=False) parser.add_argument("--unpatch_sys_vol", help="Unpatches root volume, EXPERIMENTAL", action="store_true", required=False) + parser.add_argument("--prepare_for_update", help="Prepares host for macOS update, ex. clean /Library/Extensions", action="store_true", required=False) # validation args parser.add_argument("--validate", help="Runs Validation Tests for CI", action="store_true", required=False) @@ -591,7 +592,14 @@ def check_cli_args(): parser.add_argument("--update_installed", help="Prompt user to finish updating via GUI", action="store_true", required=False) args = parser.parse_args() - if not (args.build or args.patch_sys_vol or args.unpatch_sys_vol or args.validate or args.auto_patch): + if not ( + args.build or + args.patch_sys_vol or + args.unpatch_sys_vol or + args.validate or + args.auto_patch or + args.prepare_for_update + ): return None else: return args