diff --git a/.gitignore b/.gitignore index a9c2880ae..e8b28230f 100644 --- a/.gitignore +++ b/.gitignore @@ -30,10 +30,12 @@ __pycache__/ /payloads/OpenCore-Legacy-Patcher /payloads/InstallAssistant.pkg.integrityDataV1 /payloads.dmg +/Universal-Binaries.dmg /payloads/OpenCore-Legacy-Patcher-*.plist /payloads/KDK.dmg *.log /Universal-Binaries.dmg /payloads/KDKInfo.plist /payloads/update.sh -/payloads/OpenCore-Patcher.app \ No newline at end of file +/payloads/OpenCore-Patcher.app +/.x86_64_venv diff --git a/Build-Binary.command b/Build-Binary.command index 94b5da9ea..df8ab6fe7 100755 --- a/Build-Binary.command +++ b/Build-Binary.command @@ -20,7 +20,7 @@ class CreateBinary: Library for creating OpenCore-Patcher application This script's main purpose is to handle the following: - - Download external dependancies (ex. PatcherSupportPkg) + - Download external dependencies (ex. PatcherSupportPkg) - Convert payloads directory into DMG - Build Binary via Pyinstaller - Patch 'LC_VERSION_MIN_MACOSX' to OS X 10.10 @@ -275,7 +275,10 @@ class CreateBinary: for resource in required_resources: if Path(f"./{resource}").exists(): if self.args.reset_binaries: - print(f"- Removing old {resource}") + print(f" - Removing old {resource}") + # Just to be safe + assert resource, "Resource cannot be empty" + assert resource not in ("/", "."), "Resource cannot be root" rm_output = subprocess.run( ["rm", "-rf", f"./{resource}"], stdout=subprocess.PIPE, stderr=subprocess.PIPE diff --git a/CHANGELOG.md b/CHANGELOG.md index effd72cbd..4b57913ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Implement reduce transparency Menubar - Resolve Color Profile support and Black Box rendering issues on HD 3000 Macs - Drops ColorSync downgrade configuration option + - Resolves macOS 13.5 booting on HD 3000 Macs - Resolve app not updating in `/Applications` after an update - Work-around users manually copying app to `/Applications` instead of allowing Root Volume Patcher to create a proper alias - Add configuration for mediaanalysisd usage @@ -18,6 +19,11 @@ - Thanks [@jazzzny](https://github.com/jazzzny) - Resolve AMD Vega support on pre-AVX2 Macs in macOS Ventura - Originally caused by regression from 0.6.2 +- Disable non-Metal's Menubar 2 configuration + - Can be manually re-enabled, however application will try to disable to prevent issues +- Remove AppleGVA downgrade on Intel Skylake iGPUs +- Implement AMFIPass system + - Removes need for disabling Library Validation and AMFI outright on all applicable systems - Backend Changes: - device_probe.py: - Add USB device parsing via `IOUSBDevice` class @@ -27,7 +33,7 @@ - utilities.py: - Fix indexing error on Device Paths (thx [@Ausdauersportler](https://github.com/Ausdauersportler)) - Increment Binaries: - - PatcherSupportPkg 1.2.1 - release +- PatcherSupportPkg 1.2.2 - release ## 0.6.7 - Resolve partition buttons overlapping in Install OpenCore UI diff --git a/data/sys_patch_dict.py b/data/sys_patch_dict.py index 79ca78981..63a076d82 100644 --- a/data/sys_patch_dict.py +++ b/data/sys_patch_dict.py @@ -104,7 +104,7 @@ class SystemPatchDictionary(): "SkyLight.framework": f"10.14.6-{self.os_major}", }, "/System/Applications": { - **({ "Photo Booth.app": "11.7.6"} if self.os_major >= os_data.os_data.monterey else {}), + **({ "Photo Booth.app": "11.7.9"} if self.os_major >= os_data.os_data.monterey else {}), }, }, "Remove": { diff --git a/docs/LICENSE.md b/docs/LICENSE.md index 21172e2b4..96a2d189a 100644 --- a/docs/LICENSE.md +++ b/docs/LICENSE.md @@ -18,6 +18,7 @@ This patcher is made of multiple external applications from different people and * [telemetrap](https://forums.macrumors.com/threads/mp3-1-others-sse-4-2-emulation-to-enable-amd-metal-driver.2206682/post-28447707) - Syncretic * [SurPlus](https://github.com/reenigneorcim/SurPlus) - Syncretic * [VMM Patch Set](https://github.com/dortania/OpenCore-Legacy-Patcher/blob/4a8f61a01da72b38a4b2250386cc4b497a31a839/payloads/Config/config.plist#L1222-L1281) - parrotgeek1 + * AMFIPass - Dhinak G * Apple Binaries - Apple Inc. * All other patches - respective authors diff --git a/payloads/Config/config.plist b/payloads/Config/config.plist index 42631326c..8fb3779fb 100644 --- a/payloads/Config/config.plist +++ b/payloads/Config/config.plist @@ -1689,6 +1689,24 @@ PlistPath Contents/Info.plist + + Arch + x86_64 + Comment + AMFIPass + Enabled + + MaxKernel + + MinKernel + 20.0.0 + BundlePath + AMFIPass.kext + ExecutablePath + Contents/MacOS/AMFIPass + PlistPath + Contents/Info.plist + Block diff --git a/payloads/Kexts/Acidanthera/AMFIPass-v1.3.1-RELEASE.zip b/payloads/Kexts/Acidanthera/AMFIPass-v1.3.1-RELEASE.zip new file mode 100644 index 000000000..06a04afcf Binary files /dev/null and b/payloads/Kexts/Acidanthera/AMFIPass-v1.3.1-RELEASE.zip differ diff --git a/resources/build/security.py b/resources/build/security.py index 5a9073aa5..1d9cdcb38 100644 --- a/resources/build/security.py +++ b/resources/build/security.py @@ -76,4 +76,7 @@ class BuildSecurity: if self.constants.secure_status is False: logging.info("- Disabling SecureBootModel") - self.config["Misc"]["Security"]["SecureBootModel"] = "Disabled" \ No newline at end of file + self.config["Misc"]["Security"]["SecureBootModel"] = "Disabled" + + logging.info("- Enabling AMFIPass") + support.BuildSupport(self.model, self.constants, self.config).enable_kext("AMFIPass.kext", self.constants.amfipass_version, self.constants.amfipass_path) diff --git a/resources/constants.py b/resources/constants.py index e6827811e..a23b0a430 100644 --- a/resources/constants.py +++ b/resources/constants.py @@ -4,6 +4,7 @@ from pathlib import Path from typing import Optional +from packaging import version from resources import device_probe from data import os_data @@ -13,7 +14,7 @@ class Constants: def __init__(self) -> None: # Patcher Versioning self.patcher_version: str = "0.6.8" # OpenCore-Legacy-Patcher - self.patcher_support_pkg_version: str = "1.2.1" # PatcherSupportPkg + self.patcher_support_pkg_version: str = "1.2.2" # PatcherSupportPkg self.copyright_date: str = "Copyright © 2020-2023 Dortania" self.patcher_name: str = "OpenCore Legacy Patcher" @@ -82,13 +83,15 @@ class Constants: ## Dortania ## https://github.com/dortania - self.backlight_injector_version: str = "1.1.0" # BacklightInjector - self.backlight_injectorA_version: str = "1.0.0" # BacklightInjector (iMac9,1) - self.smcspoof_version: str = "1.0.0" # SMC-Spoof - self.mce_version: str = "1.0.0" # AppleMCEReporterDisabler - self.btspoof_version: str = "1.0.0" # Bluetooth-Spoof - self.aspp_override_version: str = "1.0.1" # ACPI_SMC_PlatformPlugin Override - self.rsrhelper_version: str = "1.0.0" # RSRHelper + self.backlight_injector_version: str = "1.1.0" # BacklightInjector + self.backlight_injectorA_version: str = "1.0.0" # BacklightInjector (iMac9,1) + self.smcspoof_version: str = "1.0.0" # SMC-Spoof + self.mce_version: str = "1.0.0" # AppleMCEReporterDisabler + self.btspoof_version: str = "1.0.0" # Bluetooth-Spoof + self.aspp_override_version: str = "1.0.1" # ACPI_SMC_PlatformPlugin Override + self.rsrhelper_version: str = "1.0.0" # RSRHelper + self.amfipass_version: str = "1.3.1" # AMFIPass + self.amfipass_compatibility_version: str = "1.2.1" # Minimum AMFIPass version required ## Syncretic ## https://forums.macrumors.com/members/syncretic.1173816/ @@ -235,6 +238,18 @@ class Constants: os_data.os_data.ventura, ] + @property + def special_build(self): + """ + Special builds are used for testing. They do not get updates through the updater + """ + + try: + version.parse(self.patcher_version) + return False + except version.InvalidVersion: + return True + # Payload Location # Support Disk Images @@ -503,6 +518,11 @@ class Constants: def rsrhelper_path(self): return self.payload_kexts_path / Path(f"Acidanthera/RSRHelper-v{self.rsrhelper_version}-{self.kext_variant}.zip") + @property + def amfipass_path(self): + # AMFIPass is release only + return self.payload_kexts_path / Path(f"Acidanthera/AMFIPass-v{self.amfipass_version}-RELEASE.zip") + @property def innie_path(self): return self.payload_kexts_path / Path(f"Misc/Innie-v{self.innie_version}-{self.kext_variant}.zip") diff --git a/resources/defaults.py b/resources/defaults.py index 1cdb06c50..1a8f87881 100644 --- a/resources/defaults.py +++ b/resources/defaults.py @@ -49,7 +49,7 @@ class GenerateDefaults: self._networking_probe() self._misc_hardwares_probe() self._smbios_probe() - + self._check_amfipass_supported() def _general_probe(self) -> None: """ @@ -327,8 +327,38 @@ class GenerateDefaults: # Only disable AMFI if we officially support Ventura self.constants.disable_amfi = True - for key in ["Moraea_BlurBeta", "Amy.MenuBar2Beta"]: + for key in ["Moraea_BlurBeta"]: # Enable BetaBlur if user hasn't disabled it is_key_enabled = subprocess.run(["defaults", "read", "-g", key], stdout=subprocess.PIPE).stdout.decode("utf-8").strip() if is_key_enabled not in ["false", "0"]: - subprocess.run(["defaults", "write", "-g", key, "-bool", "true"]) \ No newline at end of file + subprocess.run(["defaults", "write", "-g", key, "-bool", "true"]) + + subprocess.run(["defaults", "write", "-g", "Amy.MenuBar2Beta", "-bool", "false"]) + + def _check_amfipass_supported(self) -> None: + """ + Check if root volume supports AMFIPass + + The basic requirements of this function are: + - The host is the target + - Root volume doesn't have adhoc signed binaries + + If all of these conditions are met, it is safe to disable AMFI and CS_LV. Otherwise, for safety, leave it be. + """ + + if not self.host_is_target: + # Unknown whether the host is using old binaries + # Rebuild it once you are on the host + return + + # Check for adhoc signed binaries + if self.constants.computer.oclp_sys_signed is False: + # Root patch with new binaries, then reboot + return + + # Note: simply checking the authority is not enough, as the authority can be spoofed + # (but do we really care? this is just a simple check) + # Note: the cert will change + + self.constants.disable_amfi = False + self.constants.disable_cs_lv = False diff --git a/resources/device_probe.py b/resources/device_probe.py index f533bc4a8..e2cd4188c 100644 --- a/resources/device_probe.py +++ b/resources/device_probe.py @@ -636,6 +636,7 @@ class Computer: oclp_sys_version: Optional[str] = None oclp_sys_date: Optional[str] = None oclp_sys_url: Optional[str] = None + oclp_sys_signed: Optional[bool] = False firmware_vendor: Optional[str] = None rosetta_active: Optional[bool] = False @@ -956,15 +957,19 @@ class Computer: def oclp_sys_patch_probe(self): path = Path("/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist") - if path.exists(): - sys_plist = plistlib.load(path.open("rb")) - if sys_plist: - if "OpenCore Legacy Patcher" in sys_plist: - self.oclp_sys_version = sys_plist["OpenCore Legacy Patcher"] - if "Time Patched" in sys_plist: - self.oclp_sys_date = sys_plist["Time Patched"] - if "Commit URL" in sys_plist: - self.oclp_sys_url = sys_plist["Commit URL"] + if not path.exists(): + self.oclp_sys_signed = True # No plist, so assume root is valid + return + sys_plist = plistlib.load(path.open("rb")) + if sys_plist: + if "OpenCore Legacy Patcher" in sys_plist: + self.oclp_sys_version = sys_plist["OpenCore Legacy Patcher"] + if "Time Patched" in sys_plist: + self.oclp_sys_date = sys_plist["Time Patched"] + if "Commit URL" in sys_plist: + self.oclp_sys_url = sys_plist["Commit URL"] + if "Custom Signature" in sys_plist: + self.oclp_sys_signed = sys_plist["Custom Signature"] def check_rosetta(self): result = subprocess.run("sysctl -in sysctl.proc_translated".split(), stdout=subprocess.PIPE).stdout.decode() diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 0c59bf13d..fd31b27e2 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -20,7 +20,7 @@ from data import os_data KDK_INSTALL_PATH: str = "/Library/Developer/KDKs" KDK_INFO_PLIST: str = "KDKInfo.plist" -KDK_API_LINK: str = "https://raw.githubusercontent.com/dortania/KdkSupportPkg/gh-pages/manifest.json" +KDK_API_LINK: str = "https://dortania.github.io/KdkSupportPkg/manifest.json" KDK_ASSET_LIST: list = None diff --git a/resources/sys_patch/sys_patch.py b/resources/sys_patch/sys_patch.py index 04fc28525..52c82746d 100644 --- a/resources/sys_patch/sys_patch.py +++ b/resources/sys_patch/sys_patch.py @@ -68,7 +68,6 @@ class PatchSysVolume: self.skip_root_kmutil_requirement = self.hardware_details["Settings: Supports Auxiliary Cache"] - def _init_pathing(self, custom_root_mount_path: Path = None, custom_data_mount_path: Path = None) -> None: """ Initializes the pathing for root volume patching @@ -494,7 +493,7 @@ class PatchSysVolume: oclp_plist_data = plistlib.load(Path(oclp_path).open("rb")) for key in oclp_plist_data: if isinstance(oclp_plist_data[key], (bool, int)): - continue + continue if "Install" not in oclp_plist_data[key]: continue for location in oclp_plist_data[key]["Install"]: diff --git a/resources/sys_patch/sys_patch_auto.py b/resources/sys_patch/sys_patch_auto.py index 1c66f9670..0c275919a 100644 --- a/resources/sys_patch/sys_patch_auto.py +++ b/resources/sys_patch/sys_patch_auto.py @@ -47,8 +47,7 @@ class AutomaticSysPatch: dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates() if dict: - for key in dict: - version = dict[key]["Version"] + version = dict["Version"] logging.info(f"- Found new version: {version}") app = wx.App() @@ -64,7 +63,7 @@ class AutomaticSysPatch: if response == wx.ID_YES: gui_entry.EntryPoint(self.constants).start(entry=gui_entry.SupportedEntryPoints.UPDATE_APP) elif response == wx.ID_NO: - webbrowser.open(dict[key]["Github Link"]) + webbrowser.open(dict["Github Link"]) return if utilities.check_seal() is True: @@ -148,17 +147,21 @@ class AutomaticSysPatch: logging.info("- Versions match") return True + if self.constants.special_build is True: + # Version doesn't match and we're on a special build + # Special builds don't have good ways to compare versions + logging.info("- Special build detected, assuming installed is older") + return False + # 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: + if updates.CheckBinaryUpdates(self.constants).check_if_newer(self.constants.computer.oclp_version): logging.info("- Installed version is newer than booted version") return True args = [ "osascript", "-e", - f"""display dialog "OpenCore Legacy Patcher has detected that you are booting an outdated OpenCore build\n- Booted: {self.constants.computer.oclp_version}\n- Installed: {self.constants.patcher_version}\n\nWould you like to update the OpenCore bootloader?" """ + f"""display dialog "OpenCore Legacy Patcher has detected that you are booting {'a different' if self.constants.special_build else 'an outdated'} OpenCore build\n- Booted: {self.constants.computer.oclp_version}\n- Installed: {self.constants.patcher_version}\n\nWould you like to update the OpenCore bootloader?" """ f'with icon POSIX file "{self.constants.app_icon_path}"', ] output = subprocess.run( diff --git a/resources/sys_patch/sys_patch_detect.py b/resources/sys_patch/sys_patch_detect.py index aba656489..f59f3ec04 100644 --- a/resources/sys_patch/sys_patch_detect.py +++ b/resources/sys_patch/sys_patch_detect.py @@ -3,26 +3,16 @@ # Used when supplying data to sys_patch.py # Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk -import plistlib import logging -import py_sip_xnu +import plistlib from pathlib import Path -from resources import ( - constants, - device_probe, - utilities, - amfi_detect, - network_handler, - kdk_handler -) -from data import ( - model_array, - os_data, - sip_data, - smbios_data, - cpu_data -) +import packaging.version +import py_sip_xnu + +from data import cpu_data, model_array, os_data, sip_data, smbios_data +from resources import (amfi_detect, constants, device_probe, kdk_handler, + network_handler, utilities) class DetectRootPatch: @@ -78,6 +68,7 @@ class DetectRootPatch: self.dosdude_patched = False self.missing_kdk = False self.has_network = False + self.unsupported_os = False self.missing_whatever_green = False self.missing_nv_web_nvram = False @@ -441,7 +432,18 @@ class DetectRootPatch: bool: True if loaded, False otherwise """ - return utilities.check_kext_loaded("WhateverGreen", self.constants.detected_os) + return utilities.check_kext_loaded("as.vit9696.WhateverGreen") + + + def _check_os_compat(self) -> bool: + """ + Base check to ensure patcher is compatible with host OS + """ + min_os = os_data.os_data.big_sur + max_os = os_data.os_data.ventura + if self.constants.detected_os < min_os or self.constants.detected_os > max_os: + return False + return True def _check_kdk(self): @@ -546,7 +548,7 @@ class DetectRootPatch: 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): + if self.model in ["iMac7,1", "iMac8,1"] or (self.model in model_array.LegacyAudio and utilities.check_kext_loaded("as.vit9696.AppleALC") 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: @@ -618,6 +620,7 @@ class DetectRootPatch: "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(), "Validation: Unpatching Possible": self._verify_unpatch_allowed(), + f"Validation: Unsupported Host OS": self.unsupported_os, f"Validation: SIP is enabled (Required: {self._check_sip()[2]} or higher)": self.sip_enabled, f"Validation: Currently Booted SIP: ({hex(py_sip_xnu.SipXnu().get_sip_status().value)})": self.sip_enabled, "Validation: SecureBootModel is enabled": self.sbm_enabled, @@ -648,6 +651,12 @@ class DetectRootPatch: if self.constants.detected_os < os_data.os_data.big_sur: return amfi_detect.AmfiConfigDetectLevel.NO_CHECK + amfipass_version = utilities.check_kext_loaded("com.dhinakg.AMFIPass") + if amfipass_version: + if packaging.version.parse(amfipass_version) >= packaging.version.parse(self.constants.amfipass_compatibility_version): + # If AMFIPass is loaded, our binaries will work + return amfi_detect.AmfiConfigDetectLevel.NO_CHECK + if self.constants.detected_os >= os_data.os_data.ventura: if self.amfi_shim_bins is True: # Currently we require AMFI outright disabled @@ -675,6 +684,8 @@ class DetectRootPatch: 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.AmfiConfigurationDetection().check_config(self._get_amfi_level_needed()) + self.unsupported_os = not self._check_os_compat() + 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() @@ -728,10 +739,14 @@ class DetectRootPatch: logging.info("\nCannot patch! Network Connection Required") logging.info("Please ensure you have an active internet connection") + if self.unsupported_os is True: + logging.info("\nCannot patch! Unsupported Host OS") + logging.info("Please ensure you are running a patcher-supported OS") + if any( [ # General patch checks - self.sip_enabled, self.sbm_enabled, self.fv_enabled, self.dosdude_patched, + self.sip_enabled, self.sbm_enabled, self.fv_enabled, self.dosdude_patched, self.unsupported_os, # non-Metal specific self.amfi_enabled if self.amfi_must_disable is True else False, diff --git a/resources/sys_patch/sys_patch_generate.py b/resources/sys_patch/sys_patch_generate.py index 40e35d78d..228bfa79c 100644 --- a/resources/sys_patch/sys_patch_generate.py +++ b/resources/sys_patch/sys_patch_generate.py @@ -81,7 +81,7 @@ class GenerateRootPatchSets: required_patches.update({"Sonoma Legacy Metal Extended": all_hardware_patchset["Graphics"]["Sonoma Legacy Metal Extended"]}) if self.hardware_details["Graphics: Intel Skylake"] is True: - required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]}) + required_patches.update({"Revert GVA Downgrade": all_hardware_patchset["Graphics"]["Revert GVA Downgrade"]}) required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]}) required_patches.update({"Intel Skylake": all_hardware_patchset["Graphics"]["Intel Skylake"]}) required_patches.update({"Sonoma Legacy Metal Extended": all_hardware_patchset["Graphics"]["Sonoma Legacy Metal Extended"]}) @@ -133,7 +133,9 @@ class GenerateRootPatchSets: del(required_patches["AMD TeraScale 2"]["Install"]["/System/Library/Extensions"]["AMDRadeonX3000.kext"]) if self.hardware_details["Graphics: AMD Legacy GCN"] is True or self.hardware_details["Graphics: AMD Legacy Polaris"] is True: - required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]}) + if self.hardware_details["Graphics: Intel Skylake"] is False: + # GVA downgrade not required if Skylake is present + required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]}) required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]}) if self.hardware_details["Graphics: AMD Legacy GCN"] is True: required_patches.update({"AMD Legacy GCN": all_hardware_patchset["Graphics"]["AMD Legacy GCN"]}) diff --git a/resources/updates.py b/resources/updates.py index 8b0df26c8..9b12df530 100644 --- a/resources/updates.py +++ b/resources/updates.py @@ -3,8 +3,11 @@ # Call check_binary_updates() to determine if any updates are available # Returns dict with Link and Version of the latest binary update if available import logging +from typing import Optional, Union -from resources import network_handler, constants +from packaging import version + +from resources import constants, network_handler REPO_LATEST_RELEASE_URL: str = "https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest" @@ -12,48 +15,62 @@ REPO_LATEST_RELEASE_URL: str = "https://api.github.com/repos/dortania/OpenCore-L class CheckBinaryUpdates: def __init__(self, global_constants: constants.Constants) -> None: self.constants: constants.Constants = global_constants + try: + self.binary_version = version.parse(self.constants.patcher_version) + except version.InvalidVersion: + assert self.constants.special_build is True, "Invalid version number for binary" + # Special builds will not have a proper version number + self.binary_version = version.parse("0.0.0") - self.binary_version = self.constants.patcher_version - self.binary_version_array = [int(x) for x in self.binary_version.split(".")] + self.latest_details = None - - def _check_if_build_newer(self, remote_version: list = None, local_version: list = None) -> bool: + def check_if_newer(self, version: Union[str, version.Version]) -> bool: """ - Check if the remote version is newer than the local version + Check if the provided version is newer than the local version Parameters: - remote_version (list): Remote version to compare against - local_version (list): Local version to compare against + version (str): Version to compare against Returns: - bool: True if remote version is newer, False if not + bool: True if the provided version is newer, False if not + """ + if self.constants.special_build is True: + return False + + return self._check_if_build_newer(version, self.binary_version) + + def _check_if_build_newer(self, first_version: Union[str, version.Version], second_version: Union[str, version.Version]) -> bool: + """ + Check if the first version is newer than the second version + + Parameters: + first_version_str (str): First version to compare against (generally local) + second_version_str (str): Second version to compare against (generally remote) + + Returns: + bool: True if first version is newer, False if not """ - if remote_version is None: - remote_version = self.remote_version_array - if local_version is None: - local_version = self.binary_version_array + if not isinstance(first_version, version.Version): + try: + first_version = version.parse(first_version) + except version.InvalidVersion: + # Special build > release build: assume special build is newer + return True + if not isinstance(second_version, version.Version): + try: + second_version = version.parse(second_version) + except version.InvalidVersion: + # Release build > special build: assume special build is newer + return False - if local_version == remote_version: + if first_version == second_version: if not self.constants.commit_info[0].startswith("refs/tags"): # Check for nightly builds return True - # Pad version numbers to match length (ie. 0.1.0 vs 0.1.0.1) - while len(remote_version) > len(local_version): - local_version.append(0) - while len(remote_version) < len(local_version): - remote_version.append(0) - - for i in range(0, len(remote_version)): - if int(remote_version[i]) < int(local_version[i]): - break - elif int(remote_version[i]) > int(local_version[i]): - return True - - return False - + return first_version > second_version def _determine_local_build_type(self) -> str: """ @@ -63,11 +80,7 @@ class CheckBinaryUpdates: str: "GUI" or "TUI" """ - if self.constants.wxpython_variant is True: - return "GUI" - else: - return "TUI" - + return "GUI" if self.constants.wxpython_variant else "TUI" def _determine_remote_type(self, remote_name: str) -> str: """ @@ -87,8 +100,7 @@ class CheckBinaryUpdates: else: return "Unknown" - - def check_binary_updates(self) -> dict: + def check_binary_updates(self) -> Optional[dict]: """ Check if any updates are available for the OpenCore Legacy Patcher binary @@ -96,7 +108,13 @@ class CheckBinaryUpdates: dict: Dictionary with Link and Version of the latest binary update if available """ - available_binaries: list = {} + if self.constants.special_build is True: + # Special builds do not get updates through the updater + return None + + if self.latest_details: + # We already checked + return self.latest_details if not network_handler.NetworkUtilities(REPO_LATEST_RELEASE_URL).verify_network_connection(): return None @@ -107,26 +125,26 @@ class CheckBinaryUpdates: if "tag_name" not in data_set: return None - self.remote_version = data_set["tag_name"] + # The release marked as latest will always be stable, and thus, have a proper version number + # But if not, let's not crash the program + try: + latest_remote_version = version.parse(data_set["tag_name"]) + except version.InvalidVersion: + return None - self.remote_version_array = self.remote_version.split(".") - self.remote_version_array = [int(x) for x in self.remote_version_array] - - if self._check_if_build_newer() is False: + if not self._check_if_build_newer(latest_remote_version, self.binary_version): return None for asset in data_set["assets"]: logging.info(f"Found asset: {asset['name']}") if self._determine_remote_type(asset["name"]) == self._determine_local_build_type(): - available_binaries.update({ - asset['name']: { - "Name": asset["name"], - "Version": self.remote_version, - "Link": asset["browser_download_url"], - "Type": self._determine_remote_type(asset["name"]), - "Github Link": f"https://github.com/dortania/OpenCore-Legacy-Patcher/releases/{self.remote_version}" - } - }) - return available_binaries + self.latest_details = { + "Name": asset["name"], + "Version": latest_remote_version, + "Link": asset["browser_download_url"], + "Type": self._determine_remote_type(asset["name"]), + "Github Link": f"https://github.com/dortania/OpenCore-Legacy-Patcher/releases/{latest_remote_version}", + } + return self.latest_details - return None \ No newline at end of file + return None diff --git a/resources/utilities.py b/resources/utilities.py index c5676a391..d83810f85 100644 --- a/resources/utilities.py +++ b/resources/utilities.py @@ -1,21 +1,20 @@ # Copyright (C) 2020-2023, Dhinak G, Mykola Grymalyuk +import argparse +import atexit +import binascii +import logging import math import os import plistlib +import re +import shutil import subprocess from pathlib import Path -import os -import binascii -import argparse -import atexit -import shutil import py_sip_xnu -import logging - +from data import os_data, sip_data from resources import constants, ioreg -from data import sip_data, os_data def hexswap(input_hex: str): @@ -173,15 +172,35 @@ def enable_sleep_after_running(): sleep_process = None -def check_kext_loaded(kext_name, os_version): - if os_version > os_data.os_data.catalina: - kext_loaded = subprocess.run(["kmutil", "showloaded", "--list-only", "--variant-suffix", "release"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - else: - kext_loaded = subprocess.run(["kextstat", "-l"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - if kext_name in kext_loaded.stdout.decode(): - return True - else: - return False +def check_kext_loaded(bundle_id: str) -> str: + """ + Checks if a kext is loaded + + Parameters: + bundle_id (str): The bundle ID of the kext to check + + Returns: + str: The version of the kext if it is loaded, or "" if it is not loaded + """ + # Name (Version) UUID + # no UUID for kextstat + pattern = re.compile(re.escape(bundle_id) + r"\s+\((?P.+)\)") + + args = ["kextstat", "-l", "-b", bundle_id] + + if Path("/usr/bin/kmutil").exists(): + args = ["kmutil", "showloaded", "--list-only", "--variant-suffix", "release", "--optional-identifier", bundle_id] + + kext_loaded = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if kext_loaded.returncode != 0: + return "" + output = kext_loaded.stdout.decode() + if not output.strip(): + return "" + match = pattern.search(output) + if match: + return match.group("version") + return "" def check_oclp_boot(): diff --git a/resources/wx_gui/gui_main_menu.py b/resources/wx_gui/gui_main_menu.py index 64103ba68..66266ef04 100644 --- a/resources/wx_gui/gui_main_menu.py +++ b/resources/wx_gui/gui_main_menu.py @@ -65,7 +65,7 @@ class MainFrame(wx.Frame): """ # Title label: OpenCore Legacy Patcher v{X.Y.Z} - title_label = wx.StaticText(self, label=f"OpenCore Legacy Patcher v{self.constants.patcher_version}", pos=(-1,10)) + title_label = wx.StaticText(self, label=f"OpenCore Legacy Patcher {'' if self.constants.special_build else 'v'}{self.constants.patcher_version}", pos=(-1, 10)) title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) title_label.Centre(wx.HORIZONTAL) @@ -282,22 +282,21 @@ class MainFrame(wx.Frame): if not dict: return - for entry in dict: - version = dict[entry]["Version"] - logging.info(f"New version: {version}") - dialog = wx.MessageDialog( - parent=self, - message=f"Current Version: {self.constants.patcher_version}{' (Nightly)' if not self.constants.commit_info[0].startswith('refs/tags') else ''}\nNew version: {version}\nWould you like to update?", - caption="Update Available for OpenCore Legacy Patcher!", - style=wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION - ) - dialog.SetYesNoCancelLabels("Download and install", "Ignore", "View on Github") - response = dialog.ShowModal() + version = dict["Version"] + logging.info(f"New version: {version}") + dialog = wx.MessageDialog( + parent=self, + message=f"Current Version: {self.constants.patcher_version}{' (Nightly)' if not self.constants.commit_info[0].startswith('refs/tags') else ''}\nNew version: {version}\nWould you like to update?", + caption="Update Available for OpenCore Legacy Patcher!", + style=wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION + ) + dialog.SetYesNoCancelLabels("Download and install", "Ignore", "View on Github") + response = dialog.ShowModal() - if response == wx.ID_YES: - wx.CallAfter(self.on_update, dict[entry]["Link"], version) - elif response == wx.ID_CANCEL: - webbrowser.open(dict[entry]["Github Link"]) + if response == wx.ID_YES: + wx.CallAfter(self.on_update, dict["Link"], version) + elif response == wx.ID_CANCEL: + webbrowser.open(dict["Github Link"]) def on_build_and_install(self, event: wx.Event = None): diff --git a/resources/wx_gui/gui_update.py b/resources/wx_gui/gui_update.py index f8d8b2b32..1fc60b29c 100644 --- a/resources/wx_gui/gui_update.py +++ b/resources/wx_gui/gui_update.py @@ -48,10 +48,8 @@ class UpdateFrame(wx.Frame): if url == "" or version_label == "": dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates() if dict: - for key in dict: - version_label = dict[key]["Version"] - url = dict[key]["Link"] - break + version_label = dict["Version"] + url = dict["Link"] else: wx.MessageBox("Failed to get update info", "Critical Error") sys.exit(1)