diff --git a/.gitignore b/.gitignore index b1580d16d..a9c2880ae 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,6 @@ __pycache__/ /payloads/KDK.dmg *.log /Universal-Binaries.dmg -/payloads/Universal-Binaries_overlay +/payloads/KDKInfo.plist +/payloads/update.sh +/payloads/OpenCore-Patcher.app \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index cd94954c4..57773df44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,26 @@ - Patch currently limited to Ventura and newer - Restore Function Keys on MacBook5,2 and MacBook4,1 - Implementation by [@jazzzny](https://github.com/Jazzzny) +- Backend changes: + - Rename OCLP-Helper to OpenCore-Patcher + - Allows for better identification when displaying prompts + - Reimplement wxPython GUI into modularized system: + - Allows for easier maintenance and future expansion + - Changes include: + - Reworked settings UI + - Unified download UI with time remaining + - Implement in-app update system + - Guides users to update OpenCore and Root Patches once update's installed + - Expand app update checks to include nightly users + - ex. 0.6.6 nightly -> 0.6.6 release + - Implement macOS installer verification after flashing + - Implement proper UI call backs on long processes + - ex. Root patching + - Implement default selections for disks and installers + - Set about and quit items + - Utilize `py-applescript` for authorization prompts + - Avoids displaying prompts with `osascript` in the title + - Due to limitations, only used for installer creation and OpenCore installation - Increment Binaries: - PatcherSupportPkg 1.0.1 - release - OpenCorePkg 0.9.2 - release diff --git a/OpenCore-Patcher-GUI.spec b/OpenCore-Patcher-GUI.spec index a89ec2a1e..e4490eb21 100644 --- a/OpenCore-Patcher-GUI.spec +++ b/OpenCore-Patcher-GUI.spec @@ -49,6 +49,7 @@ app = BUNDLE(coll, icon="payloads/OC-Patcher.icns", bundle_identifier="com.dortania.opencore-legacy-patcher", info_plist={ + "CFBundleName": "OpenCore Legacy Patcher", "CFBundleShortVersionString": constants.Constants().patcher_version, "NSHumanReadableCopyright": constants.Constants().copyright_date, "LSMinimumSystemVersion": "10.10.0", diff --git a/payloads/Tools/OpenCore-Patcher.app/Contents/Info.plist b/payloads/Tools/OpenCore-Patcher.app/Contents/Info.plist new file mode 100644 index 000000000..29f687b78 --- /dev/null +++ b/payloads/Tools/OpenCore-Patcher.app/Contents/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDisplayName + OpenCore-Patcher + CFBundleExecutable + OpenCore-Patcher + CFBundleIconFile + OC-Patcher.icns + CFBundleIdentifier + com.dortania.opencore-legacy-patcher-helper + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + OpenCore-Patcher + CFBundlePackageType + APPL + LSMinimumSystemVersion + 10.10.0 + NSHighResolutionCapable + + NSHumanReadableCopyright + Copyright © 2020-2023 Dortania + LSUIElement + + + diff --git a/payloads/Tools/OCLP-Helper b/payloads/Tools/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher similarity index 100% rename from payloads/Tools/OCLP-Helper rename to payloads/Tools/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher diff --git a/payloads/Tools/OpenCore-Patcher.app/Contents/Resources/OC-Patcher.icns b/payloads/Tools/OpenCore-Patcher.app/Contents/Resources/OC-Patcher.icns new file mode 100644 index 000000000..f023ac743 Binary files /dev/null and b/payloads/Tools/OpenCore-Patcher.app/Contents/Resources/OC-Patcher.icns differ diff --git a/requirements.txt b/requirements.txt index 2f4524324..a79dd44b2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,5 @@ pyobjc wxpython pyinstaller packaging -py_sip_xnu \ No newline at end of file +py_sip_xnu +py-applescript \ No newline at end of file diff --git a/resources/build/build.py b/resources/build/build.py index 92aa16850..cdf31e946 100644 --- a/resources/build/build.py +++ b/resources/build/build.py @@ -153,5 +153,3 @@ class BuildOpenCore: logging.info(f"Your OpenCore EFI for {self.model} has been built at:") logging.info(f" {self.constants.opencore_release_folder}") logging.info("") - if self.constants.gui_mode is False: - input("Press [Enter] to continue\n") diff --git a/resources/build/firmware.py b/resources/build/firmware.py index 61eef6e1d..17281e10a 100644 --- a/resources/build/firmware.py +++ b/resources/build/firmware.py @@ -73,7 +73,7 @@ class BuildFirmware: support.BuildSupport(self.model, self.constants, self.config).enable_kext("AppleIntelCPUPowerManagement.kext", self.constants.aicpupm_version, self.constants.aicpupm_path) support.BuildSupport(self.model, self.constants, self.config).enable_kext("AppleIntelCPUPowerManagementClient.kext", self.constants.aicpupm_version, self.constants.aicpupm_client_path) - if smbios_data.smbios_dictionary[self.model]["CPU Generation"] <= cpu_data.cpu_data.sandy_bridge.value or self.constants.disable_xcpm is True: + if smbios_data.smbios_dictionary[self.model]["CPU Generation"] <= cpu_data.cpu_data.sandy_bridge.value or self.constants.disable_fw_throttle is True: # With macOS 12.3 Beta 1, Apple dropped the 'plugin-type' check within X86PlatformPlugin # Because of this, X86PP will match onto the CPU instead of ACPI_SMC_PlatformPlugin # This causes power management to break on pre-Ivy Bridge CPUs as they don't have correct @@ -81,11 +81,11 @@ class BuildFirmware: # This patch will simply increase ASPP's 'IOProbeScore' to outmatch X86PP logging.info("- Overriding ACPI SMC matching") support.BuildSupport(self.model, self.constants, self.config).enable_kext("ASPP-Override.kext", self.constants.aspp_override_version, self.constants.aspp_override_path) - if self.constants.disable_xcpm is True: + if self.constants.disable_fw_throttle is True: # Only inject on older OSes if user requests support.BuildSupport(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Add"], "BundlePath", "ASPP-Override.kext")["MinKernel"] = "" - if self.constants.disable_msr_power_ctl is True and smbios_data.smbios_dictionary[self.model]["CPU Generation"] >= cpu_data.cpu_data.nehalem.value: + if self.constants.disable_fw_throttle is True and smbios_data.smbios_dictionary[self.model]["CPU Generation"] >= cpu_data.cpu_data.nehalem.value: logging.info("- Disabling Firmware Throttling") # Nehalem and newer systems force firmware throttling via MSR_POWER_CTL support.BuildSupport(self.model, self.constants, self.config).enable_kext("SimpleMSR.kext", self.constants.simplemsr_version, self.constants.simplemsr_path) diff --git a/resources/build/graphics_audio.py b/resources/build/graphics_audio.py index 479f13b68..df0adf263 100644 --- a/resources/build/graphics_audio.py +++ b/resources/build/graphics_audio.py @@ -259,7 +259,7 @@ class BuildGraphicsAudio: "CAIL,CAIL_DisableUVDPowerGating": 1, "CAIL,CAIL_DisableVCEPowerGating": 1, }) - if self.constants.imac_model == "Legacy GCN": + if self.constants.imac_model == "GCN": logging.info("- Adding Legacy GCN Power Gate Patches") self.config["DeviceProperties"]["Add"][backlight_path].update({ "CAIL,CAIL_DisableDrmdmaPowerGating": 1, @@ -274,7 +274,7 @@ class BuildGraphicsAudio: "CAIL,CAIL_DisableUVDPowerGating": 1, "CAIL,CAIL_DisableVCEPowerGating": 1, }) - elif self.constants.imac_model == "AMD Lexa": + elif self.constants.imac_model == "Lexa": logging.info("- Adding Lexa Spoofing Patches") self.config["DeviceProperties"]["Add"][backlight_path].update({ "model": "AMD Radeon Pro WX 3200", @@ -285,7 +285,7 @@ class BuildGraphicsAudio: "model": "AMD Radeon Pro WX 3200", "device-id": binascii.unhexlify("FF67"), }) - elif self.constants.imac_model == "AMD Navi": + elif self.constants.imac_model == "Navi": logging.info("- Adding Navi Spoofing Patches") navi_backlight_path = backlight_path+"/Pci(0x0,0x0)/Pci(0x0,0x0)" self.config["DeviceProperties"]["Add"][navi_backlight_path] = { diff --git a/resources/constants.py b/resources/constants.py index ddde3df55..c5946e969 100644 --- a/resources/constants.py +++ b/resources/constants.py @@ -15,6 +15,7 @@ class Constants: self.patcher_version: str = "0.6.6" # OpenCore-Legacy-Patcher self.patcher_support_pkg_version: str = "1.0.1" # PatcherSupportPkg self.copyright_date: str = "Copyright © 2020-2023 Dortania" + self.patcher_name: str = "OpenCore Legacy Patcher" # URLs self.url_patcher_support_pkg: str = "https://github.com/dortania/PatcherSupportPkg/releases/download/" @@ -132,6 +133,7 @@ class Constants: self.launcher_script: str = None # Determine launch file path (None if PyInstaller) self.booted_oc_disk: str = None # Determine current disk OCLP booted from self.unpack_thread = None # Determine if unpack thread finished (threading.Thread) + self.update_stage: int = 0 # Determine update stage (see gui_support.py) self.commit_info: tuple = (None, None, None) # Commit info (Branch, Commit Date, Commit URL) @@ -206,12 +208,11 @@ class Constants: self.dGPU_switch: bool = False # Set Display GPU Switching for Windows self.force_surplus: bool = False # Force SurPlus patch in newer OSes self.force_latest_psp: bool = False # Force latest PatcherSupportPkg - self.disable_msr_power_ctl: bool = False # Disable MSR Power Control (missing battery throttling) + self.disable_fw_throttle: bool = False # Disable MSR Power Control and XCPM self.software_demux: bool = False # Enable Software Demux patch set self.force_vmm: bool = False # Force VMM patch self.disable_connectdrivers: bool = False # Disable ConnectDrivers (hibernation) self.set_content_caching: bool = False # Set Content Caching - self.disable_xcpm: bool = False # Disable XCPM (X86PlatformPlugin.kext) self.set_vmm_cpuid: bool = False # Set VMM bit inside CPUID self.disable_cat_colorsync: bool = False # Disable the ColorSync patch to regain Display Profiles self.set_alc_usage: bool = True # Set AppleALC usage @@ -624,7 +625,7 @@ class Constants: @property def oclp_helper_path(self): - return self.payload_path / Path("Tools/OCLP-Helper") + return self.payload_path / Path("Tools/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher") @property def rsrrepair_userspace_path(self): diff --git a/resources/gui/gui_help.py b/resources/gui/gui_help.py deleted file mode 100644 index d2499f142..000000000 --- a/resources/gui/gui_help.py +++ /dev/null @@ -1,104 +0,0 @@ -import wx -import webbrowser -from resources import constants -from data import os_data - -class gui_help_menu: - def __init__(self, versions, frame, frame_modal): - self.constants: constants.Constants = versions - self.frame = frame - self.frame_modal = frame_modal - - # Define Window Size - self.WINDOW_WIDTH_MAIN = 300 - - - def reset_frame_modal(self): - if not self.frame_modal: - self.frame_modal = wx.Dialog(self.frame) - else: - self.frame_modal.DestroyChildren() - self.frame_modal.Close() - if self.constants.detected_os >= os_data.os_data.big_sur: - self.frame_modal.ShowWithoutActivating() - - def help_menu(self, event=None): - # Define Menu - # Header: Get help with OpenCore Legacy Patcher - # Subheader: Following resources are available: - # Button: Official Guide - # Button: Official Discord Server - - self.reset_frame_modal() - self.frame_modal.SetSize((self.WINDOW_WIDTH_MAIN, -1)) - - # Header - self.header = wx.StaticText(self.frame_modal, label="Patcher Resources", pos=(10,10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Subheader - self.subheader = wx.StaticText(self.frame_modal, label="Following resources are available:") - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 5 - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - - - # Official Guide - self.guide = wx.Button(self.frame_modal, label="Official Guide", size=(200,30)) - self.guide.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 5 - - ) - ) - self.guide.Bind(wx.EVT_BUTTON, lambda event: webbrowser.open(self.constants.guide_link)) - self.guide.Centre(wx.HORIZONTAL) - - # Official Discord Server - self.discord = wx.Button(self.frame_modal, label="Official Discord Server", size=(200,30)) - self.discord.SetPosition( - wx.Point( - self.guide.GetPosition().x, - self.guide.GetPosition().y + self.guide.GetSize().height - ) - ) - self.discord.Bind(wx.EVT_BUTTON, lambda event: webbrowser.open(self.constants.discord_link)) - self.discord.Centre(wx.HORIZONTAL) - - # Overclock Button - self.overclock = wx.Button(self.frame_modal, label="Official Support Phone", size=(200,30)) - self.overclock.SetPosition( - wx.Point( - self.discord.GetPosition().x, - self.discord.GetPosition().y + self.discord.GetSize().height - ) - ) - self.overclock.Bind(wx.EVT_BUTTON, lambda event: webbrowser.open("https://www.youtube.com/watch?v=dQw4w9WgXcQ")) - self.overclock.Centre(wx.HORIZONTAL) - - - self.return_to_main = wx.Button(self.frame_modal, label="Return to Main Menu", size=(150,30)) - self.return_to_main.SetPosition( - wx.Point( - self.overclock.GetPosition().x, - self.overclock.GetPosition().y + self.overclock.GetSize().height + 5 - ) - ) - self.return_to_main.Bind(wx.EVT_BUTTON, lambda event: self.frame_modal.Close()) - self.return_to_main.Centre(wx.HORIZONTAL) - - # Set Window Size to below Copyright Label - self.frame_modal.SetSize( - ( - -1, - self.return_to_main.GetPosition().y + self.return_to_main.GetSize().height + 40 - ) - ) - self.frame_modal.ShowWindowModal() diff --git a/resources/gui/gui_main.py b/resources/gui/gui_main.py deleted file mode 100644 index 75c09566a..000000000 --- a/resources/gui/gui_main.py +++ /dev/null @@ -1,3963 +0,0 @@ -# Setup GUI -# Implemented using wxPython -# Currently Work in Progress - -import plistlib -from pathlib import Path -from datetime import datetime - -import os -import sys -import subprocess -import threading - -import webbrowser - -import time - -import logging -import tempfile - -import wx -import wx.adv -from wx.lib.agw import hyperlink - -import py_sip_xnu - -from resources import ( - constants, - defaults, - install, - utilities, - generate_smbios, - updates, - integrity_verification, - global_settings, - kdk_handler, - network_handler, - macos_installer_handler -) - -from resources.sys_patch import sys_patch_detect, sys_patch -from resources.build import build -from resources.gui import menu_redirect, gui_help - -from data import model_array, os_data, smbios_data, sip_data, cpu_data - - - -class wx_python_gui: - def __init__(self, versions, frame=None, frame_modal=None): - self.constants: constants.Constants = versions - self.computer = self.constants.computer - self.constants.gui_mode = True - self.walkthrough_mode = False - self.finished_auto_patch = False - self.finished_cim_process = False - self.target_disk = "" - self.pulse_forward = False - self.prepare_result = False - self.non_metal_required = self.use_non_metal_alternative() - self.hyperlink_colour = (25, 179, 231) - - # Backup stdout for usage with wxPython - self.stock_stream = logging.getLogger().handlers[0].stream - - current_uid = os.getuid() - - # Define Window Size - self.WINDOW_WIDTH_MAIN = 350 - self.WINDOW_HEIGHT_MAIN = 220 - self.WINDOW_WIDTH_BUILD = 400 - self.WINDOW_HEIGHT_BUILD = 500 - self.WINDOW_SETTINGS_WIDTH = 250 - self.WINDOW_SETTINGS_HEIGHT = 320 - - # Create Application - self.app = wx.App() - if frame is None: - self.frame = wx.Frame( - None, title=f"OpenCore Legacy Patcher ({self.constants.patcher_version})", - size=(self.WINDOW_WIDTH_MAIN, self.WINDOW_HEIGHT_MAIN), - style = wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX) - ) - self.frame.Centre(~wx.MAXIMIZE_BOX) - self.frame.Show() - self.frame.Bind(wx.EVT_CLOSE, self.OnCloseFrame) - # Create Menubar (allows Cmd+Q usage) - self.menubar = wx.MenuBar() - self.file_menu = wx.Menu() - self.file_menu.Append(wx.ID_EXIT, "Quit", "Quit Application" ) - self.file_menu.Append(wx.ID_REDO, f"Relaunch as Root (UID: {int(current_uid)})", "Relaunch OpenCore Legacy Patcher as Root") - self.menubar.Append(self.file_menu, "File") - self.frame.Bind(wx.EVT_MENU, self.OnCloseFrame, id=wx.ID_EXIT) - self.frame.Bind(wx.EVT_MENU, self.relaunch_as_root, id=wx.ID_REDO) - self.frame.SetMenuBar(self.menubar) - else: - self.frame = frame - - # Modal Frames - self.frame_modal = frame_modal - - if current_uid == 0: - self.file_menu.Enable(wx.ID_REDO, False) - - - def OnCloseFrame(self, event=None): - self.frame.SetTransparent(0) - wx.GetApp().Yield() - self.frame.DestroyChildren() - self.frame.Destroy() - self.app.ExitMainLoop() - sys.exit() - - def reboot_system(self, event=None, message=""): - self.popup = wx.MessageDialog( - self.frame, - message, - "Reboot to apply?", - wx.YES_NO | wx.ICON_INFORMATION - ) - self.popup.SetYesNoLabels("Reboot", "Ignore") - answer = self.popup.ShowModal() - if answer == wx.ID_YES: - # Reboots with Count Down prompt (user can still dismiss if needed) - subprocess.call(['osascript', '-e', 'tell app "loginwindow" to «event aevtrrst»']) - self.OnCloseFrame(event) - - def reset_window(self): - self.frame.DestroyChildren() - self.frame.SetSize(self.WINDOW_WIDTH_MAIN, self.WINDOW_HEIGHT_MAIN) - logging.getLogger().handlers[0].stream = self.stock_stream - self.reset_frame_modal() - - # Re-enable sleep if we failed to do so before returning to the main menu - utilities.enable_sleep_after_running() - - def reset_frame_modal(self): - if not self.frame_modal: - self.frame_modal = wx.Dialog(self.frame) - else: - self.frame_modal.DestroyChildren() - self.frame_modal.Close() - - # This is a hack to fix window sizing issues - # If the previous frame was a modal, the new frame will anchor onto it - # instead of the core frame - # Calling ShowWithoutActivating() resets the frame position - if self.constants.detected_os >= os_data.os_data.big_sur: - self.frame_modal.ShowWithoutActivating() - - def use_non_metal_alternative(self): - if self.constants.detected_os >= os_data.os_data.monterey: - if Path("/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLightOld.dylib").exists(): - if self.constants.host_is_non_metal is True: - return True - return False - - def is_unpack_finished(self): - if not self.constants.unpack_thread.is_alive(): - if Path(self.constants.payload_kexts_path).exists(): - return True - else: - # Raise error to end program - self.popup = wx.MessageDialog( - self.frame, - f"During unpacking of our internal files, we seemed to have encountered an error.\n\nIf you keep seeing this error, please try rebooting and redownloading the application.", - "Internal Error occurred!", - style = wx.OK | wx.ICON_EXCLAMATION - ) - self.popup.ShowModal() - self.frame.Freeze() - self.OnCloseFrame(None) - return False - - def pulse_alternative(self, progress_bar): - if self.non_metal_required is True: - if progress_bar.GetValue() == 0: - self.pulse_forward = True - - elif progress_bar.GetValue() == 100: - self.pulse_forward = False - - if self.pulse_forward: - progress_bar.SetValue(progress_bar.GetValue() + 1) - else: - progress_bar.SetValue(progress_bar.GetValue() - 1) - time.sleep(0.005) - - def preflight_check(self): - if ( - self.constants.computer.build_model != None and - self.constants.computer.build_model != self.constants.computer.real_model and - self.constants.host_is_hackintosh is False - ): - # Notify user they're booting an unsupported configuration - self.constants.start_build_install = True - self.popup = wx.MessageDialog( - self.frame, - f"We found you are currently booting OpenCore built for a different unit: {self.constants.computer.build_model}\n\nWe builds configs to match individual units and cannot be mixed or reused with different Macs.\n\nPlease Build and Install a new OpenCore config, and reboot your Mac.", - "Unsupported Configuration Detected!", - style = wx.OK | wx.ICON_EXCLAMATION - ) - self.popup.ShowModal() - else: - # Spawn thread to check for updates - threading.Thread(target=self.check_for_updates).start() - - def check_for_local_installs(self, event=None): - # Update app in '/Library/Application Support/Dortania' folder - - # Skip if we're running from source - if self.constants.launcher_script: - return False - - # Only performed if application is already installed (ie. we're updating) - application_path = Path("/Library/Application Support/Dortania/OpenCore-Patcher.app") - if not application_path.exists(): - return False - - # Check application version - # If we're older than the installed version, skip - application_plist_path = application_path / "Contents/Info.plist" - if not application_plist_path.exists(): - return False - - application_plist = plistlib.load(application_plist_path.open("rb")) - if not "CFBundleShortVersionString" in application_plist: - return False - - application_version = application_plist["CFBundleShortVersionString"].split(".") - local_version = self.constants.patcher_version.split(".") - - if application_version == local_version: - if "Build Date" not in application_plist: - return False - - # Check build date of installed version - plist_path = self.constants.launcher_binary.replace("MacOS/OpenCore-Patcher", "Info.plist") - if not Path(plist_path).exists(): - return False - - plist = plistlib.load(Path(plist_path).open("rb")) - if "Build Date" not in plist: - return False - - if plist["Build Date"] == application_plist["Build Date"]: - return False - - local_build_date = datetime.strptime(plist["Build Date"], "%Y-%m-%d %H:%M:%S") - installed_build_date = datetime.strptime(application_plist["Build Date"], "%Y-%m-%d %H:%M:%S") - - if local_build_date <= installed_build_date: - return False - - elif updates.CheckBinaryUpdates(self.constants)._check_if_build_newer(local_version, application_version) is False: - return False - - # Ask user if they want to move the application to the Applications folder - self.popup = wx.MessageDialog( - self.frame, - f"We've detected an old version of OpenCore-Patcher.app installed in the Application Support directory.\n\nWould you like to replace it with this version?", - "Move to Applications?", - wx.YES_NO | wx.ICON_INFORMATION - ) - self.popup.SetYesNoLabels("Replace", "Ignore") - answer = self.popup.ShowModal() - if answer != wx.ID_YES: - return False - - path = str(self.constants.launcher_binary).split("/Contents/MacOS/OpenCore-Patcher")[0] - - args = [ - "osascript", - "-e", - f'''do shell script "ditto {path} '/Library/Application Support/Dortania/OpenCore-Patcher.app'"''' - ' with prompt "OpenCore Legacy Patcher needs administrator privileges to copy in."' - " with administrator privileges" - " without altering line endings", - ] - - result = subprocess.run(args,stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if result.returncode != 0: - logging.info("- Failed to move application into /Library/Application Support/Dortania/OpenCore-Patcher.app") - # Notify user we failed to move the application - self.popup = wx.MessageDialog( - self.frame, - f"Failed to move the application to the Applications folder.\n\nThis is likely due to permission errors, you can copy the app manually into '/Library/Application Support/Dortania/OpenCore-Patcher.app' if you continue to see this error.", - "Failed to Move!", - style = wx.OK | wx.ICON_EXCLAMATION - ) - self.popup.ShowModal() - return False - - subprocess.run(["xattr", "-cr", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - subprocess.run(["open", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if "AppTranslocation" not in path: - subprocess.run(["rm", "-R", path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - self.OnCloseFrame() - - def check_for_updates(self, event=None): - if self.constants.has_checked_updates is True: - return - - did_find_update = False - ignore_updates = global_settings.GlobalEnviromentSettings().read_property("IgnoreAppUpdates") - if ignore_updates is not True: - self.constants.ignore_updates = False - self.constants.has_checked_updates = True - dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates() - if dict: - for entry in dict: - version = dict[entry]["Version"] - github_link = dict[entry]["Github Link"] - logging.info(f"New version: {version}") - self.dialog = wx.MessageDialog( - parent=self.frame, - message=f"Current Version: {self.constants.patcher_version}\nNew version: {version}\nWould you like to view?", - caption="Update Available for OpenCore Legacy Patcher!", - style=wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION - ) - self.dialog.SetYesNoCancelLabels("View on Github", "Always Ignore", "Ignore Once") - response = self.dialog.ShowModal() - did_find_update = True - if response == wx.ID_YES: - webbrowser.open(github_link) - elif response == wx.ID_NO: - logging.info("- Setting IgnoreAppUpdates to True") - self.constants.ignore_updates = True - global_settings.GlobalEnviromentSettings().write_property("IgnoreAppUpdates", True) - else: - self.constants.ignore_updates = True - logging.info("- Ignoring App Updates due to defaults") - - # if did_find_update is False: - # self.check_for_local_installs() - - def relaunch_as_root(self, event=None): - - # Add Dialog Box asking if it's ok to relaunch as root - # If yes, relaunch as root - # If no, do nothing - - # Create Dialog Box - self.dialog = wx.MessageDialog( - self.frame, - "OpenCore Legacy Patcher needs to relaunch as admin to continue. You will be prompted to enter your password.", - "Relaunch as root?", - wx.YES_NO | wx.ICON_QUESTION - ) - - # Show Dialog Box - if self.dialog.ShowModal() == wx.ID_YES: - logging.info("Relaunching as root") - - timer_val = 5 - extension = "" - if event: - if event.GetEventObject() != wx.Menu: - try: - if event.GetEventObject().GetLabel() in ["Start Root Patching", "Reinstall Root Patches"]: - extension = " --gui_patch" - elif event.GetEventObject().GetLabel() == "Revert Root Patches": - extension = " --gui_unpatch" - except TypeError: - pass - - if self.constants.launcher_script is None: - args_string = f"'{self.constants.launcher_binary}'{extension}" - else: - args_string = f"{self.constants.launcher_binary} {self.constants.launcher_script}{extension}" - - args = [ - "osascript", - "-e", - f'''do shell script "{args_string}"''' - ' with prompt "OpenCore Legacy Patcher needs administrator privileges to relaunch as admin."' - " with administrator privileges" - " without altering line endings", - ] - - self.frame.DestroyChildren() - self.frame.SetSize(self.WINDOW_WIDTH_MAIN, self.WINDOW_HEIGHT_MAIN) - - # Header - self.header = wx.StaticText(self.frame, label="Relaunching as root") - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Add count down label - self.countdown_label = wx.StaticText(self.frame, label=f"Closing old process in {timer_val} seconds") - self.countdown_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - # Set below header - self.countdown_label.SetPosition( - ( - self.header.GetPosition().x + 3, - self.header.GetPosition().y + self.header.GetSize().height + 3 - ) - ) - self.countdown_label.Centre(wx.HORIZONTAL) - # Label: You can close this window if app finished relaunching - self.countdown_label2 = wx.StaticText(self.frame, label="You can close this window if app finished relaunching") - self.countdown_label2.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - # Set below countdown label - self.countdown_label2.SetPosition( - ( - self.countdown_label.GetPosition().x, - self.countdown_label.GetPosition().y + self.countdown_label.GetSize().height + 3 - ) - ) - self.countdown_label2.Centre(wx.HORIZONTAL) - - # Set frame right below countdown label - self.frame.SetSize( - ( - -1, - self.countdown_label2.GetPosition().y + self.countdown_label2.GetSize().height + 40 - ) - ) - - wx.GetApp().Yield() - subprocess.Popen( - args, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT - ) - while True: - wx.GetApp().Yield() - self.countdown_label.SetLabel(f"Closing old process in {timer_val} seconds") - time.sleep(1) - timer_val -= 1 - if timer_val == 0: - break - # Close Current Application - self.OnCloseFrame(event) - - def not_yet_implemented_menu(self, event=None): - self.frame.DestroyChildren() - self.frame.SetSize(self.WINDOW_WIDTH_MAIN, self.WINDOW_HEIGHT_MAIN) - - # Header - self.header = wx.StaticText(self.frame, label="🚧 Not Yet Implemented") - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Return to main menu - self.return_button = wx.Button(self.frame, label="Return to Main Menu") - self.return_button.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_button.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 10 - ) - ) - self.return_button.Centre(wx.HORIZONTAL) - - def main_menu(self, event=None): - # Define Menu - # - Header: OpenCore Legacy Patcher v{self.constants.patcher_version} - # - Subheader: Model: {self.constants.custom_model or self.computer.real_model} - # - Options: - # - Build and Install OpenCore - # - Post Install Root Patch - # - Create macOS Installer - # - Settings - - # Reset Data in the event of re-run - self.reset_window() - - # Set header text - self.frame.SetTitle(f"OpenCore Legacy Patcher ({self.constants.patcher_version})") - # Header - self.header = wx.StaticText(self.frame, label=f"OpenCore Legacy Patcher v{self.constants.patcher_version}") - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Subheader - self.subheader = wx.StaticText(self.frame, label=f"Model: {self.constants.custom_model or self.computer.real_model}") - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 5 - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - - # Build and Install OpenCore - self.build_install = wx.Button(self.frame, label="Build and Install OpenCore", size=(200,30)) - self.build_install.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 - ) - ) - self.build_install.Bind(wx.EVT_BUTTON, self.build_install_menu) - self.build_install.Centre(wx.HORIZONTAL) - - # Disable button if real_model not in model_array.SupportedSMBIOS - if ( - ( - self.constants.allow_oc_everywhere is False and \ - self.constants.custom_model is None and \ - self.computer.real_model not in model_array.SupportedSMBIOS - ) or ( - self.constants.custom_model is None and \ - self.constants.host_is_hackintosh is True - ) - ): - self.build_install.Disable() - self.build_install.SetToolTip(wx.ToolTip("""If building for a native Mac model, \nselect 'Allow Native Models' in Settings.\nIf building for another Mac, change model in Settings""")) - - # Post Install Root Patch - self.post_install = wx.Button(self.frame, label="Post Install Root Patch", size=(200,30)) - self.post_install.SetPosition( - wx.Point( - self.build_install.GetPosition().x, - self.build_install.GetPosition().y + self.build_install.GetSize().height - ) - ) - self.post_install.Bind(wx.EVT_BUTTON, self.root_patch_menu) - self.post_install.Centre(wx.HORIZONTAL) - if self.constants.detected_os in [os_data.os_data.mojave, os_data.os_data.catalina]: - self.post_install.SetToolTip(wx.ToolTip("""Graphics Acceleration for Mojave and Catalina has been removed in 0.4.4 onwards.\n\nIf you require this feature, use 0.4.3 or older""")) - self.post_install.Disable() - elif self.constants.detected_os < os_data.os_data.mojave: - self.post_install.SetToolTip(wx.ToolTip("""Root Patching is only available for Big Sur and newer.""")) - self.post_install.Disable() - - # Create macOS Installer - self.create_installer = wx.Button(self.frame, label="Create macOS Installer", size=(200,30)) - self.create_installer.SetPosition( - wx.Point( - self.post_install.GetPosition().x, - self.post_install.GetPosition().y + self.post_install.GetSize().height - ) - ) - self.create_installer.Bind(wx.EVT_BUTTON, self.create_macos_menu) - self.create_installer.Centre(wx.HORIZONTAL) - - # Settings - self.settings = wx.Button(self.frame, label="Settings", size=(200,30)) - self.settings.SetPosition( - wx.Point( - self.create_installer.GetPosition().x, - self.create_installer.GetPosition().y + self.create_installer.GetSize().height - ) - ) - self.settings.Bind(wx.EVT_BUTTON, self.settings_menu) - self.settings.Centre(wx.HORIZONTAL) - - # Help Button - self.help_button = wx.Button(self.frame, label="Help", size=(200,30)) - self.help_button.SetPosition( - wx.Point( - self.settings.GetPosition().x, - self.settings.GetPosition().y + self.settings.GetSize().height - ) - ) - self.help_button.Bind(wx.EVT_BUTTON, gui_help.gui_help_menu(self.constants, self.frame, self.frame_modal).help_menu) - self.help_button.Centre(wx.HORIZONTAL) - - - # Copyright Label - self.copyright = wx.StaticText(self.frame, label=self.constants.copyright_date) - self.copyright.SetFont(wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.copyright.SetPosition( - wx.Point( - self.help_button.GetPosition().x, - self.help_button.GetPosition().y + self.help_button.GetSize().height + 5 - ) - ) - self.copyright.Centre(wx.HORIZONTAL) - - # Set Window Size to below Copyright Label - self.frame.SetSize( - ( - -1, - self.copyright.GetPosition().y + self.copyright.GetSize().height + 40 - ) - ) - - self.preflight_check() - if self.finished_auto_patch is False: - if self.constants.start_build_install is True: - self.build_install_menu() - elif "--gui_patch" in sys.argv: - self.patches = sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).detect_patch_set() - self.root_patch_start() - elif "--gui_unpatch" in sys.argv: - self.patches = sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).detect_patch_set() - self.root_patch_revert() - self.finished_auto_patch = True - self.constants.start_build_install = False - - if self.app.MainLoop() is None: - self.app.MainLoop() - - def help_menu(self, event=None): - # Define Menu - # Header: Get help with OpenCore Legacy Patcher - # Subheader: Following resources are available: - # Button: Official Guide - # Button: Official Discord Server - - self.reset_frame_modal() - self.frame_modal.SetSize((self.WINDOW_WIDTH_MAIN - 40,-1)) - - # Header - self.header = wx.StaticText(self.frame_modal, label="Patcher Resources", pos=(10,10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Subheader - self.subheader = wx.StaticText(self.frame_modal, label="Following resources are available:") - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 5 - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - - - # Official Guide - self.guide = wx.Button(self.frame_modal, label="Official Guide", size=(200,30)) - self.guide.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 5 - - ) - ) - self.guide.Bind(wx.EVT_BUTTON, lambda event: webbrowser.open(self.constants.guide_link)) - self.guide.Centre(wx.HORIZONTAL) - - # Official Discord Server - self.discord = wx.Button(self.frame_modal, label="Official Discord Server", size=(200,30)) - self.discord.SetPosition( - wx.Point( - self.guide.GetPosition().x, - self.guide.GetPosition().y + self.guide.GetSize().height - ) - ) - self.discord.Bind(wx.EVT_BUTTON, lambda event: webbrowser.open(self.constants.discord_link)) - self.discord.Centre(wx.HORIZONTAL) - - # Overclock Button - self.overclock = wx.Button(self.frame_modal, label="Official Support Phone", size=(200,30)) - self.overclock.SetPosition( - wx.Point( - self.discord.GetPosition().x, - self.discord.GetPosition().y + self.discord.GetSize().height - ) - ) - self.overclock.Bind(wx.EVT_BUTTON, lambda event: webbrowser.open("https://www.youtube.com/watch?v=dQw4w9WgXcQ")) - self.overclock.Centre(wx.HORIZONTAL) - - - self.return_to_main = wx.Button(self.frame_modal, label="Return to Main Menu", size=(150,30)) - self.return_to_main.SetPosition( - wx.Point( - self.overclock.GetPosition().x, - self.overclock.GetPosition().y + self.overclock.GetSize().height + 5 - ) - ) - self.return_to_main.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main.Centre(wx.HORIZONTAL) - - # Set Window Size to below Copyright Label - self.frame_modal.SetSize( - ( - -1, - self.return_to_main.GetPosition().y + self.return_to_main.GetSize().height + 40 - ) - ) - self.frame_modal.ShowWindowModal() - - def build_install_menu(self, event=None): - # Define Menu - # - Header: Build and Install OpenCore - # - Subheader: Model: {self.constants.custom_model or self.computer.real_model} - # - Button: Build OpenCore - # - Textbox: stdout - # - Button: Return to Main Menu - - self.reset_frame_modal() - self.frame_modal.SetSize(self.WINDOW_WIDTH_BUILD, self.WINDOW_HEIGHT_BUILD + 10) - - # Header - self.header = wx.StaticText(self.frame_modal, label="Build and Install OpenCore", pos=(10,10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Subheader - self.subheader = wx.StaticText(self.frame_modal, label=f"Model: {self.constants.custom_model or self.computer.real_model}") - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 5 - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - - # Build OpenCore - self.build_opencore = wx.Button(self.frame_modal, label="🔨 Build OpenCore", size=(150,30)) - self.build_opencore.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 - ) - ) - self.build_opencore.Bind(wx.EVT_BUTTON, self.build_start) - self.build_opencore.Centre(wx.HORIZONTAL) - - # Textbox - # Redirect stdout to a text box - self.stdout_text = wx.TextCtrl(self.frame_modal, style=wx.TE_MULTILINE | wx.TE_READONLY) - self.stdout_text.SetPosition(wx.Point(self.build_opencore.GetPosition().x, self.build_opencore.GetPosition().y + self.build_opencore.GetSize().height + 10)) - self.stdout_text.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, False, ".AppleSystemUIFont")) - # Set width to same as frame - self.stdout_text.SetSize(self.WINDOW_WIDTH_BUILD, 340) - # Centre the text box to top of window - self.stdout_text.Centre(wx.HORIZONTAL) - self.stdout_text.SetValue("") - - # Set StreamHandler to redirect stdout to textbox - logging.getLogger().handlers[0].stream = menu_redirect.RedirectText(self.stdout_text, False) - - self.return_to_main_menu = wx.Button(self.frame_modal, label="Return to Main Menu") - self.return_to_main_menu.SetPosition( - wx.Point( - self.stdout_text.GetPosition().x, - self.stdout_text.GetPosition().y + self.stdout_text.GetSize().height + 10 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - - self.frame_modal.ShowWindowModal() - - self.build_start() - - def build_start(self, event=None): - self.build_opencore.Disable() - - while self.is_unpack_finished() is False: - time.sleep(0.1) - - build.BuildOpenCore(self.constants.custom_model or self.constants.computer.real_model, self.constants) - # Once finished, change build_opencore button to "Install OpenCore" - self.build_opencore.SetLabel("🔩 Install OpenCore") - self.build_opencore.Bind(wx.EVT_BUTTON, self.install_menu) - - # Reset stdout - logging.getLogger().handlers[0].stream = self.stock_stream - - # Throw popup asking to install OpenCore - self.dialog = wx.MessageDialog( - parent=self.frame_modal, - message=f"Would you like to install OpenCore now?", - caption="Finished building your OpenCore configuration!", - style=wx.YES_NO | wx.ICON_QUESTION - ) - self.dialog.SetYesNoLabels("Install to disk", "View build log") - if self.dialog.ShowModal() == wx.ID_YES: - self.install_menu() - else: - self.build_opencore.Enable() - - def install_menu(self, event=None): - self.frame.DestroyChildren() - self.frame.SetSize(self.WINDOW_WIDTH_BUILD, -1) - i = 0 - - # Header - self.header = wx.StaticText(self.frame, label="Install OpenCore") - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Subheader: Select Disk to install OpenCore onto - self.subheader = wx.StaticText(self.frame, label="Select Disk to install OpenCore onto") - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 5 - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - - # Label: If you're missing disks, ensure they're either FAT32 or formatted as GUI/GPT - self.missing_disks = wx.StaticText(self.frame, label="Loading disks shortly...") - self.missing_disks.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.missing_disks.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 5 - ) - ) - self.missing_disks.Centre(wx.HORIZONTAL) - - self.color_note = wx.StaticText(self.frame, label="Note: Blue represent the disk OpenCore is currently booted from") - self.color_note.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.color_note.SetPosition( - wx.Point( - self.missing_disks.GetPosition().x, - self.missing_disks.GetPosition().y + self.missing_disks.GetSize().height + 5 - ) - ) - self.color_note.Centre(wx.HORIZONTAL) - self.color_note.Hide() - - - # Progress Bar - self.progress_bar = wx.Gauge(self.frame, range=100, style=wx.GA_HORIZONTAL) - self.progress_bar.SetPosition( - wx.Point( - self.missing_disks.GetPosition().x, - self.missing_disks.GetPosition().y + self.missing_disks.GetSize().height + 5 - ) - ) - self.progress_bar.SetSize(wx.Size(self.WINDOW_WIDTH_BUILD - 30, 20)) - self.progress_bar.Centre(wx.HORIZONTAL) - self.progress_bar.SetValue(0) - - self.frame.SetSize(-1, self.progress_bar.GetPosition().y + self.progress_bar.GetSize().height + 40) - - # Request Disks Present - def get_disks(): - self.list_disks = install.tui_disk_installation(self.constants).list_disks() - - thread_disk = threading.Thread(target=get_disks) - thread_disk.start() - self.progress_bar.Pulse() - - - while thread_disk.is_alive(): - self.pulse_alternative(self.progress_bar) - wx.GetApp().Yield() - self.progress_bar.Destroy() - list_disks = self.list_disks - - self.color_note.Show() - self.missing_disks.SetLabel("Missing disks? Ensure they're FAT32 or formatted as GUID/GPT") - self.missing_disks.Centre(wx.HORIZONTAL) - - if list_disks: - if self.constants.booted_oc_disk is not None: - # disk6s1 -> disk6 - disk_root = self.constants.booted_oc_disk.strip("disk") - disk_root = "disk" + disk_root.split("s")[0] - else: - disk_root = None - - for disk in list_disks: - # Create a button for each disk - logging.info(f"{list_disks[disk]['disk']} - {list_disks[disk]['name']} - {list_disks[disk]['size']}") - self.install_button = wx.Button(self.frame, label=disk, size=(300,30)) - self.install_button.SetLabel(f"{list_disks[disk]['disk']} - {list_disks[disk]['name']} - {list_disks[disk]['size']}") - self.install_button.SetPosition( - wx.Point( - self.color_note.GetPosition().x, - self.color_note.GetPosition().y + self.color_note.GetSize().height + 3 + i - ) - ) - self.install_button.Bind(wx.EVT_BUTTON, lambda event, temp=disk: self.install_oc_disk_select(temp, list_disks)) - self.install_button.Centre(wx.HORIZONTAL) - i += self.install_button.GetSize().height + 3 - if disk_root == list_disks[disk]['disk']: - # Set label colour to red - self.install_button.SetForegroundColour(self.hyperlink_colour) - - else: - # Label: No disks found - self.install_button = wx.StaticText(self.frame, label="Failed to find any applicable disks") - self.install_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.install_button.SetPosition( - wx.Point( - self.color_note.GetPosition().x, - self.color_note.GetPosition().y + self.color_note.GetSize().height + 3 - ) - ) - self.install_button.Centre(wx.HORIZONTAL) - - - self.reload_button = wx.Button(self.frame, label="Search for Disks Again", size=(170,-1)) - self.reload_button.SetPosition( - wx.Point( - self.install_button.GetPosition().x, - self.install_button.GetPosition().y + self.install_button.GetSize().height + 10 - ) - ) - self.reload_button.Bind(wx.EVT_BUTTON, self.install_menu) - self.reload_button.Centre(wx.HORIZONTAL) - - self.return_to_main_menu = wx.Button(self.frame, label="Return to Main Menu", size=(170,-1)) - self.return_to_main_menu.SetPosition( - wx.Point( - self.reload_button.GetPosition().x, - self.reload_button.GetPosition().y + self.reload_button.GetSize().height + 8 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - - self.frame.SetSize(self.WINDOW_WIDTH_BUILD, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - - def install_oc_disk_select(self, disk, disk_data): - self.reset_frame_modal() - self.frame_modal.SetSize(self.WINDOW_WIDTH_BUILD - 40, -1) - - i = 0 - - # Header - self.header = wx.StaticText(self.frame_modal, label="Install OpenCore", pos=(10,10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Subheader: Select Partition to install OpenCore onto - self.subheader = wx.StaticText(self.frame_modal, label="Select Partition to install OpenCore onto") - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 5 - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - - list_partitions = install.tui_disk_installation(self.constants).list_partitions(disk, disk_data) - for partition in list_partitions: - logging.info(f"{list_partitions[partition]['partition']} - {list_partitions[partition]['name']} - {list_partitions[partition]['size']}") - self.install_button = wx.Button(self.frame_modal, label=partition, size=(300,30)) - self.install_button.SetLabel(f"{list_partitions[partition]['partition']} - {list_partitions[partition]['name']} - {list_partitions[partition]['size']}") - self.install_button.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 + i - ) - ) - self.install_button.Bind(wx.EVT_BUTTON, lambda event, temp=partition: self.install_oc_process(temp)) - self.install_button.Centre(wx.HORIZONTAL) - i += self.install_button.GetSize().height + 3 - if self.constants.booted_oc_disk == list_partitions[partition]['partition']: - # Set label colour to red - self.install_button.SetForegroundColour(self.hyperlink_colour) - - self.return_to_main_menu = wx.Button(self.frame_modal, label="Return") - self.return_to_main_menu.SetPosition( - wx.Point( - self.install_button.GetPosition().x, - self.install_button.GetPosition().y + self.install_button.GetSize().height + 10 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, lambda event: self.frame_modal.Close()) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - - self.frame_modal.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - self.frame_modal.ShowWindowModal() - - def install_oc_process(self, partition): - logging.info(f"Installing OpenCore to {partition}") - self.reset_frame_modal() - self.frame_modal.SetSize(self.WINDOW_WIDTH_BUILD - 20, self.WINDOW_HEIGHT_BUILD) - - # Header - self.header = wx.StaticText(self.frame_modal, label="Install OpenCore", pos=(10,10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Textbox - # Redirect stdout to a text box - self.stdout_text = wx.TextCtrl(self.frame_modal, style=wx.TE_MULTILINE | wx.TE_READONLY) - self.stdout_text.SetPosition(wx.Point(self.header.GetPosition().x, self.header.GetPosition().y + self.header.GetSize().height + 10)) - self.stdout_text.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, False, ".AppleSystemUIFont")) - # Set width to same as frame - self.stdout_text.SetSize(self.WINDOW_WIDTH_BUILD - 40, 240) - # Centre the text box to top of window - self.stdout_text.Centre(wx.HORIZONTAL) - self.stdout_text.SetValue("") - - # Update frame height to right below - self.frame_modal.SetSize(-1, self.stdout_text.GetPosition().y + self.stdout_text.GetSize().height + 40) - self.frame_modal.ShowWindowModal() - - logging.getLogger().handlers[0].stream = menu_redirect.RedirectText(self.stdout_text, False) - result = install.tui_disk_installation(self.constants).install_opencore(partition) - logging.getLogger().handlers[0].stream = self.stock_stream - - self.return_to_main_menu = wx.Button(self.frame_modal, label="Return to Main Menu") - self.return_to_main_menu.SetPosition( - wx.Point( - self.stdout_text.GetPosition().x, - self.stdout_text.GetPosition().y + self.stdout_text.GetSize().height + 10 - - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - - self.frame_modal.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 20) - - if result is True: - if not self.constants.custom_model: - self.reboot_system(message="OpenCore has finished installing to disk.\n\nYou will need to reboot and hold the Option key and select OpenCore/Boot EFI's option.\n\nWould you like to reboot?") - else: - popup_message = wx.MessageDialog(self.frame,f"OpenCore has finished installing to disk.\n\nYou can eject the drive, insert it into the {self.constants.custom_model}, reboot, hold the Option key and select OpenCore/Boot EFI's option.", "Success", wx.OK) - popup_message.ShowModal() - - def check_if_new_patches_needed(self, patches): - # Check if there's any new patches for the user to install - # Newer users will assume the root patch menu will present missing patches. - # Thus we'll need to see if the exact same OCLP build was used already - if self.constants.commit_info[0] in ["Running from source", "Built from source"]: - return True - - if self.constants.computer.oclp_sys_url != self.constants.commit_info[2]: - # If commits are different, assume patches are as well - return True - - oclp_plist = "/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist" - if not Path(oclp_plist).exists(): - # If it doesn't exist, no patches were ever installed - # ie. all patches applicable - return True - - oclp_plist_data = plistlib.load(open(oclp_plist, "rb")) - for patch in patches: - if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): - # Patches should share the same name as the plist key - # See sys_patch_dict.py for more info - patch_installed = False - for key in oclp_plist_data: - if "Display Name" not in oclp_plist_data[key]: - continue - if oclp_plist_data[key]["Display Name"] == patch: - patch_installed = True - break - - if patch_installed is False: - logging.info(f"- Patch {patch} not installed") - return True - - logging.info("- No new patches detected for system") - return False - - - def root_patch_menu(self, event=None): - # Define Menu - # Header: Post-Install Menu - # Subheader: Available patches for system: - # Label: Placeholder for patch name - # Button: Start Root Patching - # Button: Revert Root Patches - # Button: Return to Main Menu - self.reset_frame_modal() - self.frame_modal.SetSize(self.WINDOW_WIDTH_BUILD - 40, -1) - - # Header - self.header = wx.StaticText(self.frame_modal, label=f"Post-Install Menu", pos=(10,10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Subheader - self.subheader = wx.StaticText(self.frame_modal, label="Available patches for system:") - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 10 - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - - patches = sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).detect_patch_set() - self.patches = patches - can_unpatch = patches["Validation: Unpatching Possible"] - if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches): - logging.info("- No applicable patches available") - patches = [] - - # Check if OCLP has already applied the same patches - no_new_patches = False - if patches: - no_new_patches = not self.check_if_new_patches_needed(patches) - - i = 0 - if patches: - if no_new_patches is False: - for patch in patches: - # Add Label for each patch - if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): - logging.info(f"- Adding patch: {patch} - {patches[patch]}") - self.patch_label = wx.StaticText(self.frame_modal, label=f"- {patch}") - self.patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.patch_label.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 + i - ) - ) - i = i + self.patch_label.GetSize().height + 3 - else: - self.patch_label = wx.StaticText(self.frame_modal, label=f"All applicable patches already installed") - self.patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.patch_label.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 + i - ) - ) - i = i + self.patch_label.GetSize().height + 3 - self.patch_label.Centre(wx.HORIZONTAL) - if patches["Validation: Patching Possible"] is False: - self.patch_label = wx.StaticText(self.frame_modal, label="Cannot Patch due to following reasons:") - self.patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.patch_label.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 + i - ) - ) - self.patch_label.Centre(wx.HORIZONTAL) - i = i + self.patch_label.GetSize().height + 3 - for patch in patches: - if patch == "Validation: Unpatching Possible": - continue - if patch.startswith("Validation") and patches[patch] is True: - logging.info(f"- Adding check: {patch} - {patches[patch]}") - self.patch_label = wx.StaticText(self.frame_modal, label=f"- {patch[12:]}") - self.patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.patch_label.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 + i - ) - ) - i = i + self.patch_label.GetSize().height + 3 - - i += 10 - if self.constants.host_is_hackintosh is False: - self.patch_label = wx.StaticText(self.frame_modal, label="Please run 'Build and Install OpenCore' and reboot") - self.patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.patch_label.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 + i - ) - ) - self.patch_label.Centre(wx.HORIZONTAL) - i = i + self.patch_label.GetSize().height + 3 - - self.patch_label = wx.StaticText(self.frame_modal, label="to remove these errors.") - self.patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.patch_label.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 + i - ) - ) - self.patch_label.Centre(wx.HORIZONTAL) - i = i + self.patch_label.GetSize().height + 3 - - - else: - if self.constants.computer.oclp_sys_version and self.constants.computer.oclp_sys_date: - date = self.constants.computer.oclp_sys_date.split(" @") - if len(date) == 2: - date = date[0] - else: - date = "" - patch_text = f"{self.constants.computer.oclp_sys_version}, {date}" - - self.patch_label = wx.StaticText(self.frame_modal, label="Root Volume last patched:") - self.patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.patch_label.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 + i - ) - ) - self.patch_label.Centre(wx.HORIZONTAL) - i = i + self.patch_label.GetSize().height + 3 - - self.patch_label = wx.StaticText(self.frame_modal, label=patch_text) - self.patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.patch_label.SetPosition( - wx.Point( - self.subheader.GetPosition().x + 20, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 + i - ) - ) - self.patch_label.Centre(wx.HORIZONTAL) - i = i + self.patch_label.GetSize().height + 3 - else: - # Prompt user with no patches found - self.patch_label = wx.StaticText(self.frame_modal, label="No patches needed") - self.patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.patch_label.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 + i - ) - ) - self.patch_label.Centre(wx.HORIZONTAL) - - # Start Root Patching - self.start_root_patching = wx.Button(self.frame_modal, label="Start Root Patching", size=(170, -1)) - if no_new_patches is True: - self.start_root_patching.Label = "Reinstall Root Patches" - self.start_root_patching.SetPosition( - wx.Point( - self.patch_label.GetPosition().x, - self.patch_label.GetPosition().y + self.patch_label.GetSize().height + 10 - ) - ) - - self.start_root_patching.Centre(wx.HORIZONTAL) - if not patches: - self.start_root_patching.Disable() - - # Revert Root Patches - self.revert_root_patches = wx.Button(self.frame_modal, label="Revert Root Patches", size=(170, -1)) - self.revert_root_patches.SetPosition( - wx.Point( - self.start_root_patching.GetPosition().x, - self.start_root_patching.GetPosition().y + self.start_root_patching.GetSize().height + 3 - ) - ) - - self.revert_root_patches.Centre(wx.HORIZONTAL) - if self.constants.detected_os < os_data.os_data.big_sur: - self.revert_root_patches.Disable() - - self.return_to_main_menu = wx.Button(self.frame_modal, label="Return to Main Menu") - self.return_to_main_menu.SetPosition( - wx.Point( - self.revert_root_patches.GetPosition().x, - self.revert_root_patches.GetPosition().y + self.revert_root_patches.GetSize().height + 10 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - - uid = os.geteuid() - if uid == 0: - self.start_root_patching.Bind(wx.EVT_BUTTON, self.root_patch_start) - self.revert_root_patches.Bind(wx.EVT_BUTTON, self.root_patch_revert) - else: - self.start_root_patching.Bind(wx.EVT_BUTTON, self.relaunch_as_root) - self.revert_root_patches.Bind(wx.EVT_BUTTON, self.relaunch_as_root) - - if patches: - if patches["Validation: Patching Possible"] is False: - self.start_root_patching.Disable() - if can_unpatch is False: - self.revert_root_patches.Disable() - - self.frame_modal.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - self.frame_modal.ShowWindowModal() - - def root_patch_start(self, event=None): - self.frame.DestroyChildren() - self.frame.SetSize(self.WINDOW_WIDTH_BUILD, self.WINDOW_HEIGHT_MAIN) - - # Header - self.header = wx.StaticText(self.frame, label="Root Patching", pos=(10, 10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Subheader - self.subheader = wx.StaticText(self.frame, label="Preparing PatcherSupportPkg binaries") - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 10 - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - - self.developer_note = wx.StaticText(self.frame, label="Starting shortly") - self.developer_note.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.developer_note.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 - ) - ) - self.developer_note.Centre(wx.HORIZONTAL) - - self.progress_bar = wx.Gauge(self.frame, range=100, size=(200, 10)) - self.progress_bar.SetPosition( - wx.Point( - self.developer_note.GetPosition().x, - self.developer_note.GetPosition().y + self.developer_note.GetSize().height + 10 - ) - ) - self.progress_bar.SetValue(0) - self.progress_bar.Centre(wx.HORIZONTAL) - self.progress_bar.Pulse() - - self.frame.SetSize(-1, self.progress_bar.GetPosition().y + self.progress_bar.GetSize().height + 60) - self.frame.Show() - while self.is_unpack_finished() is False: - self.pulse_alternative(self.progress_bar) - wx.GetApp().Yield() - - if self.patches["Settings: Kernel Debug Kit missing"] is True: - # Download KDK (if needed) - self.subheader.SetLabel("Downloading Kernel Debug Kit") - self.subheader.Centre(wx.HORIZONTAL) - self.developer_note.SetLabel("Starting shortly") - - wx.GetApp().Yield() - - kdk_result = False - self.kdk_obj = None - def kdk_thread_spawn(): - self.kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version) - - kdk_thread = threading.Thread(target=kdk_thread_spawn) - kdk_thread.start() - - while kdk_thread.is_alive(): - self.pulse_alternative(self.progress_bar) - wx.GetApp().Yield() - - self.progress_bar.Hide() - - if self.kdk_obj.success is True: - kdk_download_obj = self.kdk_obj.retrieve_download() - if not kdk_download_obj: - kdk_result = True - else: - kdk_download_obj.download() - - self.header.SetLabel(f"Downloading KDK Build: {self.kdk_obj.kdk_url_build}") - self.header.Centre(wx.HORIZONTAL) - - self.progress_bar.SetValue(0) - # Set below developer note - self.progress_bar.SetPosition( - wx.Point( - self.developer_note.GetPosition().x, - self.developer_note.GetPosition().y + self.developer_note.GetSize().height + 10 - ) - ) - self.progress_bar.Centre(wx.HORIZONTAL) - self.progress_bar.Show() - - self.frame.SetSize(-1, self.progress_bar.GetPosition().y + self.progress_bar.GetSize().height + 60) - - while kdk_download_obj.is_active(): - self.subheader.SetLabel(f"{utilities.human_fmt(kdk_download_obj.downloaded_file_size)} downloaded of {utilities.human_fmt(kdk_download_obj.total_file_size)} ({kdk_download_obj.get_percent():.2f}%)") - self.subheader.Centre(wx.HORIZONTAL) - self.developer_note.SetLabel( - f"Average download speed: {utilities.human_fmt(kdk_download_obj.get_speed())}/s" - ) - self.developer_note.Centre(wx.HORIZONTAL) - - self.progress_bar.SetValue(int(kdk_download_obj.get_percent())) - - wx.GetApp().Yield() - time.sleep(0.1) - - if kdk_download_obj.download_complete is False: - logging.error("Failed to download KDK") - logging.error(kdk_download_obj.error_msg) - error_msg = kdk_download_obj.error_msg - else: - kdk_result = self.kdk_obj.validate_kdk_checksum() - error_msg = self.kdk_obj.error_msg - else: - logging.error("Failed to download KDK") - logging.error(self.kdk_obj.error_msg) - error_msg = self.kdk_obj.error_msg - - if kdk_result is False: - # Create popup window to inform user of error - self.popup = wx.MessageDialog( - self.frame, - f"A problem occurred trying to download the Kernel Debug Kit:\n\n{error_msg}", - "Kernel Debug Kit", - wx.ICON_ERROR - ) - self.popup.ShowModal() - self.finished_auto_patch = True - self.main_menu() - - self.reset_frame_modal() - self.frame_modal.SetSize(-1, self.WINDOW_HEIGHT_MAIN) - - # Header - self.header = wx.StaticText(self.frame_modal, label="Root Patching", pos=(10, 10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Subheader - self.subheader = wx.StaticText(self.frame_modal, label="Starting root volume patching") - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 10 - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - - self.developer_note = wx.StaticText(self.frame_modal, label="Starting shortly") - self.developer_note.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.developer_note.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 - ) - ) - self.developer_note.Centre(wx.HORIZONTAL) - - # Text Box - self.text_box = wx.TextCtrl(self.frame_modal, style=wx.TE_MULTILINE | wx.TE_READONLY) - self.text_box.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.text_box.SetPosition( - wx.Point( - self.developer_note.GetPosition().x, - self.developer_note.GetPosition().y + self.developer_note.GetSize().height + 3 - ) - ) - self.text_box.SetSize( - wx.Size( - self.frame_modal.GetSize().width - 10, - self.frame_modal.GetSize().height + self.text_box.GetPosition().y + 80 - ) - ) - self.text_box.Centre(wx.HORIZONTAL) - - self.return_to_main_menu = wx.Button(self.frame_modal, label="Return to Main Menu") - self.return_to_main_menu.SetPosition( - wx.Point( - self.text_box.GetPosition().x, - self.text_box.GetPosition().y + self.text_box.GetSize().height + 10 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - self.return_to_main_menu.Disable() - - self.frame_modal.SetSize(self.WINDOW_WIDTH_BUILD, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - - logging.getLogger().handlers[0].stream = menu_redirect.RedirectText(self.text_box, True) - self.frame_modal.ShowWindowModal() - wx.GetApp().Yield() - try: - sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, self.patches).start_patch() - except Exception as e: - self.text_box.AppendText(f"- An internal error occurred while running the Root Patcher:\n{str(e)}") - pass - logging.getLogger().handlers[0].stream = self.stock_stream - if self.constants.root_patcher_succeeded is True: - logging.info("- Root Patcher finished successfully") - if self.constants.needs_to_open_preferences is True: - if self.constants.detected_os >= os_data.os_data.ventura: - self.reboot_system(message="Root Patcher finished successfully!\nIf you were prompted to open System Settings to authorize new kexts, this can be ignored. Your system is ready once restarted.\n\nWould you like to reboot now?") - else: - # Create dialog box to open System Preferences -> Security and Privacy - self.popup = wx.MessageDialog( - self.frame_modal, - "We just finished installing the patches to your Root Volume!\n\nHowever, Apple requires users to manually approve the kernel extensions installed before they can be used next reboot.\n\nWould you like to open System Preferences?", - "Open System Preferences?", - wx.YES_NO | wx.ICON_INFORMATION - ) - self.popup.SetYesNoLabels("Open System Preferences", "Ignore") - answer = self.popup.ShowModal() - if answer == wx.ID_YES: - output =subprocess.run( - [ - "osascript", "-e", - 'tell app "System Preferences" to activate', - "-e", 'tell app "System Preferences" to reveal anchor "General" of pane id "com.apple.preference.security"', - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE - ) - if output.returncode != 0: - # Some form of fallback if unaccelerated state errors out - subprocess.run(["open", "-a", "System Preferences"]) - time.sleep(5) - self.OnCloseFrame(None) - else: - self.reboot_system(message="Root Patcher finished successfully\nWould you like to reboot now?") - self.return_to_main_menu.Enable() - - wx.GetApp().Yield() - - def root_patch_revert(self, event=None): - self.reset_frame_modal() - self.frame_modal.SetSize(self.WINDOW_WIDTH_BUILD, self.WINDOW_HEIGHT_MAIN) - - # Header - self.header = wx.StaticText(self.frame_modal, label="Revert Root Patches", pos=(10, 10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Subheader - if self.constants.detected_os == os_data.os_data.big_sur: - self.subheader = wx.StaticText(self.frame_modal, label="Currently experimental in Big Sur") - else: - self.subheader = wx.StaticText(self.frame_modal, label="Reverting to last sealed snapshot") - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 10 - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - - self.developer_note = wx.StaticText(self.frame_modal, label="Starting shortly") - self.developer_note.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.developer_note.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 3 - ) - ) - self.developer_note.Centre(wx.HORIZONTAL) - - # Text Box - self.text_box = wx.TextCtrl(self.frame_modal, style=wx.TE_MULTILINE | wx.TE_READONLY) - self.text_box.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.text_box.SetPosition( - wx.Point( - self.developer_note.GetPosition().x, - self.developer_note.GetPosition().y + self.developer_note.GetSize().height + 3 - ) - ) - self.text_box.SetSize( - wx.Size( - self.frame_modal.GetSize().width - 10, - self.frame_modal.GetSize().height + self.text_box.GetPosition().y + 10 - ) - ) - self.text_box.Centre(wx.HORIZONTAL) - - self.return_to_main_menu = wx.Button(self.frame_modal, label="Return to Main Menu") - self.return_to_main_menu.SetPosition( - wx.Point( - self.text_box.GetPosition().x, - self.text_box.GetPosition().y + self.text_box.GetSize().height + 10 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - self.return_to_main_menu.Disable() - - self.frame_modal.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - - # Start reverting root patches - logging.getLogger().handlers[0].stream = menu_redirect.RedirectText(self.text_box, True) - wx.GetApp().Yield() - self.frame_modal.ShowWindowModal() - while self.is_unpack_finished() is False: - time.sleep(0.1) - try: - sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, self.patches).start_unpatch() - except Exception as e: - self.text_box.AppendText(f"- An internal error occurred while running the Root Patcher:\n{str(e)}") - pass - logging.getLogger().handlers[0].stream = self.stock_stream - if self.constants.root_patcher_succeeded is True: - logging.info("- Root Patcher finished successfully") - self.reboot_system(message="Root Patcher finished successfully\nWould you like to reboot now?") - self.return_to_main_menu.Enable() - wx.GetApp().Yield() - - def create_macos_menu(self, event=None): - # Define Menu - # Header: Create macOS Installer - # Options: - # - Download macOS Installer - # - Use existing macOS Installer - # - Return to Main Menu - - self.reset_frame_modal() - self.frame_modal.SetSize(self.WINDOW_WIDTH_MAIN - 20 , -1) - - # Header - self.header = wx.StaticText(self.frame_modal, label="Create macOS Installer", pos=wx.Point(10, 10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Button: Download macOS Installer - self.download_macos_installer = wx.Button(self.frame_modal, label="Download macOS Installer", size=(200, 30)) - self.download_macos_installer.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 10 - ) - ) - self.download_macos_installer.Bind(wx.EVT_BUTTON, self.grab_installer_data) - self.download_macos_installer.Centre(wx.HORIZONTAL) - - # Button: Use existing macOS Installer - self.use_existing_macos_installer = wx.Button(self.frame_modal, label="Use existing macOS Installer", size=(200, 30)) - self.use_existing_macos_installer.SetPosition( - wx.Point( - self.download_macos_installer.GetPosition().x, - self.download_macos_installer.GetPosition().y + self.download_macos_installer.GetSize().height - ) - ) - self.use_existing_macos_installer.Bind(wx.EVT_BUTTON, self.flash_installer_menu) - self.use_existing_macos_installer.Centre(wx.HORIZONTAL) - - self.return_to_main_menu = wx.Button(self.frame_modal, label="Return to Main Menu") - self.return_to_main_menu.SetPosition( - wx.Point( - self.use_existing_macos_installer.GetPosition().x, - self.use_existing_macos_installer.GetPosition().y + self.use_existing_macos_installer.GetSize().height + 10 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - - self.frame_modal.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - self.frame_modal.ShowWindowModal() - - def grab_installer_data(self, event=None, ias=None): - self.frame.DestroyChildren() - - # Header - self.header = wx.StaticText(self.frame, label="Pulling installer catalog") - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Label: Download... - self.download_label = wx.StaticText(self.frame, label="Downloading installer catalog...") - self.download_label.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, False, ".AppleSystemUIFont")) - self.download_label.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 15 - ) - ) - self.download_label.Centre(wx.HORIZONTAL) - - # Progress Bar - self.progress_bar = wx.Gauge(self.frame, range=100, size=(200, 30)) - self.progress_bar.SetPosition( - wx.Point( - self.download_label.GetPosition().x, - self.download_label.GetPosition().y + self.download_label.GetSize().height + 10 - ) - ) - self.progress_bar.Centre(wx.HORIZONTAL) - self.progress_bar.Pulse() - - - self.return_to_main_menu = wx.Button(self.frame, label="Return to Main Menu") - self.return_to_main_menu.SetPosition( - wx.Point( - self.progress_bar.GetPosition().x, - self.progress_bar.GetPosition().y + self.progress_bar.GetSize().height + 15 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - - self.frame.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - self.frame.Show() - wx.GetApp().Yield() - - # Download installer catalog - if ias is None: - def ia(): - remote_obj = macos_installer_handler.RemoteInstallerCatalog(seed_override=macos_installer_handler.SeedType.DeveloperSeed) - self.available_installers = remote_obj.available_apps - self.available_installers_latest = remote_obj.available_apps_latest - - logging.info("- Downloading installer catalog...") - thread_ia = threading.Thread(target=ia) - thread_ia.start() - - while thread_ia.is_alive() or self.is_unpack_finished() is False: - self.pulse_alternative(self.progress_bar) - wx.GetApp().Yield() - available_installers = self.available_installers - else: - logging.info("- Using existing installer catalog...") - available_installers = ias - - self.reset_frame_modal() - self.frame_modal.SetSize(self.WINDOW_WIDTH_MAIN - 20, -1) - - # Header - self.header = wx.StaticText(self.frame_modal, label="Download macOS Installer", pos=wx.Point(10, 10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Subheader: - self.subheader = wx.StaticText(self.frame_modal, label="Installers currently available from Apple:") - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 10 - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - - available_installers_backup = available_installers.copy() - - i = -20 - if available_installers: - if ias is None: - available_installers = self.available_installers_latest - for app in available_installers: - logging.info(f"macOS {available_installers[app]['Version']} ({available_installers[app]['Build']}):\n - Size: {utilities.human_fmt(available_installers[app]['Size'])}\n - Source: {available_installers[app]['Source']}\n - Variant: {available_installers[app]['Variant']}\n - Link: {available_installers[app]['Link']}\n") - if available_installers[app]['Variant'] in ["DeveloperSeed" , "PublicSeed"]: - extra = " Beta" - else: - extra = "" - self.install_selection = wx.Button(self.frame_modal, label=f"macOS {available_installers[app]['Version']}{extra} ({available_installers[app]['Build']} - {utilities.human_fmt(available_installers[app]['Size'])})", size=(280, 30)) - i = i + 25 - self.install_selection.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + i - ) - ) - self.install_selection.Bind(wx.EVT_BUTTON, lambda event, temp=app: self.download_macos_click(available_installers[temp])) - self.install_selection.Centre(wx.HORIZONTAL) - else: - self.install_selection = wx.StaticText(self.frame_modal, label="No installers available") - i = i + 25 - self.install_selection.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + i - ) - ) - self.install_selection.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.install_selection.Centre(wx.HORIZONTAL) - - self.load_all_installers = wx.Button(self.frame_modal, label="Show all available installers") - self.load_all_installers.SetPosition( - wx.Point( - self.install_selection.GetPosition().x, - self.install_selection.GetPosition().y + self.install_selection.GetSize().height + 7 - ) - ) - self.load_all_installers.Bind(wx.EVT_BUTTON, lambda event: self.reload_macos_installer_catalog(ias=available_installers_backup)) - self.load_all_installers.Centre(wx.HORIZONTAL) - if ias or not available_installers: - self.load_all_installers.Disable() - - self.return_to_main_menu = wx.Button(self.frame_modal, label="Return to Main Menu") - self.return_to_main_menu.SetPosition( - wx.Point( - self.load_all_installers.GetPosition().x, - self.load_all_installers.GetPosition().y + self.load_all_installers.GetSize().height + 5 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - - self.frame_modal.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - self.frame_modal.ShowWindowModal() - - def reload_macos_installer_catalog(self, event=None, ias=None): - self.grab_installer_data(ias=ias) - - def download_macos_click(self, app_dict): - # Unsupported Models include: - # - USB 1.1 machines (Penryn, MacPro3,1-5,1) - # - Non-Metal GPUs - has_legacy_usb = False - issues_list = "" - model = self.constants.custom_model or self.constants.computer.real_model - if model in ["MacPro3,1", "MacPro4,1", "MacPro5,1"]: - has_legacy_usb = True - issues_list = "- Lack of Keyboard/Mouse in macOS installer without a USB hub\n" - elif model in smbios_data.smbios_dictionary: - if "CPU Generation" in smbios_data.smbios_dictionary[model]: - if smbios_data.smbios_dictionary[model]["CPU Generation"] <= cpu_data.cpu_data.penryn: - has_legacy_usb = True - if model.startswith("MacBook"): - issues_list = "- Lack of internal Keyboard/Trackpad in macOS installer\n" - elif not model.startswith("MacPro"): - issues_list = "- Lack of internal Keyboard/Mouse in macOS installer\n" - - if has_legacy_usb: - try: - app_major = app_dict['Version'].split(".")[0] - if float(app_major) > self.constants.os_support: - # Throw pop up warning OCLP does not support this OS - os = os_data.os_conversion.convert_kernel_to_marketing_name(os_data.os_conversion.os_to_kernel(app_major)) - dlg = wx.MessageDialog(self.frame_modal, f"OpenCore Legacy Patcher may not fully support macOS {os} on your machine ({model}).\n\nThe main issues include:\n{issues_list}\nThe newest version we recommend is macOS {os_data.os_conversion.convert_kernel_to_marketing_name(os_data.os_conversion.os_to_kernel(str(self.constants.os_support)))}. For more information, see the associated Github Issue.\n\nWould you still want to continue downloading macOS {os}?", "Unsupported OS", style=wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION) - dlg.SetYesNoCancelLabels("View Github Issue", "Download Anyways", "Cancel") - result = dlg.ShowModal() - if result == wx.ID_YES: - webbrowser.open("https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1021") - return - elif result == wx.ID_NO: - pass - else: - return - except ValueError: - pass - - # Ensure we have space to both download and extract the installer - host_space = utilities.get_free_space() - needed_space = app_dict['Size'] * 2 - if host_space < needed_space: - dlg = wx.MessageDialog(self.frame_modal, f"You do not have enough free space to download and extract this installer. Please free up some space and try again\n\n{utilities.human_fmt(host_space)} available vs {utilities.human_fmt(needed_space)} required", "Insufficient Space", wx.OK | wx.ICON_WARNING) - dlg.ShowModal() - return - - self.frame.DestroyChildren() - installer_name = f"macOS {app_dict['Version']} ({app_dict['Build']})" - - # Header - self.header = wx.StaticText(self.frame, label=f"Downloading {installer_name}") - self.frame.SetSize(self.header.GetSize().width + 200, -1) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Label: Download... - self.download_label = wx.StaticText(self.frame, label="Starting download shortly...") - self.download_label.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, False, ".AppleSystemUIFont")) - self.download_label.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 10 - ) - ) - self.download_label.Centre(wx.HORIZONTAL) - - self.download_label_2 = wx.StaticText(self.frame, label="") - self.download_label_2.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, ".AppleSystemUIFont")) - self.download_label_2.SetPosition( - wx.Point( - self.download_label.GetPosition().x, - self.download_label.GetPosition().y + self.download_label.GetSize().height + 5 - ) - ) - self.download_label_2.Centre(wx.HORIZONTAL) - - # Progress Bar - self.download_progress = wx.Gauge(self.frame, range=100, size=(self.frame.GetSize().width - 100, 20)) - self.download_progress.SetPosition( - wx.Point( - self.download_label_2.GetPosition().x, - self.download_label_2.GetPosition().y + self.download_label_2.GetSize().height + 5 - ) - ) - self.download_progress.Centre(wx.HORIZONTAL) - - self.return_to_main_menu = wx.Button(self.frame, label="Return to Main Menu") - self.return_to_main_menu.SetPosition( - wx.Point( - self.download_progress.GetPosition().x, - self.download_progress.GetPosition().y + self.download_progress.GetSize().height + 15 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - self.frame.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - wx.GetApp().Yield() - - - ia_download = network_handler.DownloadObject(app_dict['Link'], self.constants.payload_path / "InstallAssistant.pkg") - ia_download.download() - - while ia_download.is_active(): - wx.GetApp().Yield() - self.download_label.SetLabel(f"{utilities.human_fmt(ia_download.downloaded_file_size)} downloaded of {utilities.human_fmt(ia_download.total_file_size)} ({ia_download.get_percent():.2f}%)") - self.download_label.Centre(wx.HORIZONTAL) - self.download_label_2.SetLabel( - f"Average download speed: {utilities.human_fmt(ia_download.get_speed())}/s" - ) - self.download_label_2.Centre(wx.HORIZONTAL) - - self.download_progress.SetValue(int(ia_download.get_percent())) - - wx.GetApp().Yield() - time.sleep(0.1) - - - # Download macOS install data - if ia_download.download_complete is True: - self.download_label.SetLabel(f"Finished Downloading {installer_name}") - self.download_label.Centre(wx.HORIZONTAL) - wx.App.Get().Yield() - self.installer_validation(apple_integrity_file_link=app_dict['integrity']) - else: - self.download_label.SetLabel(f"Failed to download {installer_name}") - self.download_label.Centre(wx.HORIZONTAL) - - - def installer_validation(self, event=None, apple_integrity_file_link=""): - self.frame.DestroyChildren() - - # Header: Verifying InstallAssistant.pkg - self.header = wx.StaticText(self.frame, label="Verifying InstallAssistant.pkg") - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Label: Verifying Chunk 0 of 1200 - self.verifying_chunk_label = wx.StaticText(self.frame, label="Verifying Chunk 0 of 1200") - self.verifying_chunk_label.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, False, ".AppleSystemUIFont")) - self.verifying_chunk_label.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 10 - ) - ) - self.verifying_chunk_label.Centre(wx.HORIZONTAL) - - - # Progress Bar - self.progress_bar = wx.Gauge(self.frame, range=1200, size=(300, 25)) - self.progress_bar.SetPosition( - wx.Point( - self.verifying_chunk_label.GetPosition().x, - self.verifying_chunk_label.GetPosition().y + self.verifying_chunk_label.GetSize().height + 10 - ) - ) - self.progress_bar.Centre(wx.HORIZONTAL) - - - # Button: Return to Main Menu - self.return_to_main_menu = wx.Button(self.frame, label="Return to Main Menu") - self.return_to_main_menu.SetPosition( - wx.Point( - self.progress_bar.GetPosition().x, - self.progress_bar.GetPosition().y + self.progress_bar.GetSize().height + 10 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - - self.frame.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - - wx.App.Get().Yield() - integrity_path = Path(Path(self.constants.payload_path) / Path(apple_integrity_file_link.split("/")[-1])) - - chunklist_stream = network_handler.NetworkUtilities().get(apple_integrity_file_link).content - if chunklist_stream: - # If we're unable to download the integrity file immediately after downloading the IA, there's a legitimate issue - # on Apple's end. - # Fail gracefully and just head to installing the IA. - utilities.disable_sleep_while_running() - chunk_obj = integrity_verification.ChunklistVerification(self.constants.payload_path / Path("InstallAssistant.pkg"), chunklist_stream) - if chunk_obj.chunks: - self.progress_bar.SetValue(chunk_obj.current_chunk) - self.progress_bar.SetRange(chunk_obj.total_chunks) - - wx.App.Get().Yield() - chunk_obj.validate() - - while chunk_obj.status == integrity_verification.ChunklistStatus.IN_PROGRESS: - self.progress_bar.SetValue(chunk_obj.current_chunk) - self.verifying_chunk_label.SetLabel(f"Verifying Chunk {chunk_obj.current_chunk} of {chunk_obj.total_chunks}") - wx.App.Get().Yield() - - if chunk_obj.status == integrity_verification.ChunklistStatus.FAILURE: - self.popup = wx.MessageDialog( - self.frame, - f"We've found that Chunk {chunk_obj.current_chunk} of {chunk_obj.total_chunks} has failed the integrity check.\n\nThis generally happens when downloading on unstable connections such as WiFi or cellular.\n\nPlease try redownloading again on a stable connection (ie. Ethernet)", - "Corrupted Installer!", - style = wx.OK | wx.ICON_EXCLAMATION - ) - self.popup.ShowModal() - self.main_menu() - - logging.info("Integrity check passed!") - else: - logging.info("Invalid integrity file provided") - else: - logging.info("Failed to download integrity file, skipping integrity check.") - - wx.App.Get().Yield() - self.header.SetLabel("Installing InstallAssistant.pkg") - self.header.Centre(wx.HORIZONTAL) - self.verifying_chunk_label.SetLabel("Installing into Applications folder") - self.verifying_chunk_label.Centre(wx.HORIZONTAL) - thread_install = threading.Thread(target=macos_installer_handler.InstallerCreation().install_macOS_installer, args=(self.constants.payload_path,)) - thread_install.start() - self.progress_bar.Pulse() - while thread_install.is_alive(): - wx.App.Get().Yield() - - self.progress_bar.SetValue(self.progress_bar.GetRange()) - self.return_to_main_menu.SetLabel("Flash Installer") - self.verifying_chunk_label.SetLabel("Finished extracting to Applications folder!") - self.verifying_chunk_label.Centre(wx.HORIZONTAL) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.flash_installer_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - utilities.enable_sleep_after_running() - - - def flash_installer_menu(self, event=None): - self.frame.DestroyChildren() - self.frame.SetSize(self.WINDOW_WIDTH_MAIN, self.WINDOW_HEIGHT_MAIN) - # Header - self.header = wx.StaticText(self.frame, label="Select macOS Installer") - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - # Subheader: Installers found in /Applications - self.subheader = wx.StaticText(self.frame, label="Searching for Installers in /Applications") - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - - self.available_installers = None - - # Spawn thread to get list of installers - def get_installers(): - self.available_installers = macos_installer_handler.LocalInstallerCatalog().available_apps - - thread_get_installers = threading.Thread(target=get_installers) - thread_get_installers.start() - - # Progress bar - self.progress_bar = wx.Gauge(self.frame, range=100, size=(self.WINDOW_WIDTH_MAIN - 50, -1)) - self.progress_bar.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 10 - ) - ) - self.progress_bar.Centre(wx.HORIZONTAL) - self.progress_bar.Pulse() - - # Set window size - self.frame.SetSize(-1, self.progress_bar.GetPosition().y + self.progress_bar.GetSize().height + 40) - - while thread_get_installers.is_alive(): - self.pulse_alternative(self.progress_bar) - wx.App.Get().Yield() - - # Remove progress bar - self.progress_bar.Destroy() - - self.subheader.SetLabel("Installers found in Applications folder") - self.subheader.Centre(wx.HORIZONTAL) - - available_installers = self.available_installers - - i = -7 - if available_installers: - logging.info("Installer(s) found:") - for app in available_installers: - logging.info(f"- {available_installers[app]['Short Name']}: {available_installers[app]['Version']} ({available_installers[app]['Build']})") - - app_str = f"{available_installers[app]['Short Name']}" - unsupported: bool = available_installers[app]['Minimum Host OS'] > self.constants.detected_os - - if unsupported: - min_str = os_data.os_conversion.convert_kernel_to_marketing_name(available_installers[app]['Minimum Host OS']) - app_str += f" (Requires {min_str})" - else: - app_str += f": {available_installers[app]['Version']} ({available_installers[app]['Build']})" - - self.install_selection = wx.Button(self.frame, label=app_str, size=(320, 30)) - i = i + 25 - self.install_selection.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + i - ) - ) - self.install_selection.Bind(wx.EVT_BUTTON, lambda event, temp=app: self.format_usb_menu(available_installers[temp]['Short Name'], available_installers[temp]['Path'])) - self.install_selection.Centre(wx.HORIZONTAL) - - if unsupported: - self.install_selection.Disable() - else: - logging.info("No installers found") - # Label: No Installers Found - self.install_selection = wx.StaticText(self.frame, label="No Installers Found in Applications folder") - self.install_selection.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, False, ".AppleSystemUIFont")) - self.install_selection.SetPosition( - # Set Position below header - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 15 - ) - ) - self.install_selection.Centre(wx.HORIZONTAL) - - self.return_to_main_menu = wx.Button(self.frame, label="Return to Main Menu") - self.return_to_main_menu.SetPosition( - wx.Point( - self.install_selection.GetPosition().x, - self.install_selection.GetPosition().y + self.install_selection.GetSize().height + 10 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - - self.frame.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - - def format_usb_menu(self, installer_name, installer_path): - self.frame.DestroyChildren() - logging.info(installer_path) - self.frame.SetSize(370, -1) - - # Header - self.header = wx.StaticText(self.frame, label="Format USB") - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Subheader: Selected USB will be erased, please backup your data - self.subheader = wx.StaticText(self.frame, label="Selected USB will be erased, please backup your data") - self.subheader.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 10 - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - - # Label: Select USB - self.usb_selection_label = wx.StaticText(self.frame, label="Missing drives? Ensure they're 14GB+ and removable") - self.usb_selection_label.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, ".AppleSystemUIFont")) - self.usb_selection_label.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height + 10 - ) - ) - self.usb_selection_label.Centre(wx.HORIZONTAL) - - i = -15 - available_disks = macos_installer_handler.InstallerCreation().list_disk_to_format() - if available_disks: - logging.info("Disks found") - for disk in available_disks: - logging.info(f"{disk}: {available_disks[disk]['name']} - {available_disks[disk]['size']}") - self.usb_selection = wx.Button(self.frame, label=f"{disk} - {available_disks[disk]['name']} - {utilities.human_fmt(available_disks[disk]['size'])}", size=(300, 30)) - i = i + 25 - self.usb_selection.SetPosition( - wx.Point( - self.usb_selection_label.GetPosition().x, - self.usb_selection_label.GetPosition().y + self.usb_selection_label.GetSize().height + i - ) - ) - self.usb_selection.Bind(wx.EVT_BUTTON, lambda event, temp=disk: self.format_usb_progress(available_disks[temp]['identifier'], installer_name, installer_path)) - self.usb_selection.Centre(wx.HORIZONTAL) - else: - logging.info("No disks found") - # Label: No Disks Found - self.usb_selection = wx.StaticText(self.frame, label="No Disks Found") - self.usb_selection.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.BOLD, False, ".AppleSystemUIFont")) - self.usb_selection.SetPosition( - # Set Position below header - wx.Point( - self.usb_selection_label.GetPosition().x, - self.usb_selection_label.GetPosition().y + self.usb_selection_label.GetSize().height + 10 - ) - ) - self.usb_selection.Centre(wx.HORIZONTAL) - - self.return_to_main_menu = wx.Button(self.frame, label="Return to Main Menu") - self.return_to_main_menu.SetPosition( - wx.Point( - self.usb_selection.GetPosition().x, - self.usb_selection.GetPosition().y + self.usb_selection.GetSize().height + 10 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - - self.frame.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - - def format_usb_progress(self, disk, installer_name, installer_path): - self.frame.DestroyChildren() - self.frame.SetSize(520, -1) - # Header - self.header = wx.StaticText(self.frame, label=f"Creating Installer: {installer_name}") - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.Centre(wx.HORIZONTAL) - - # Label: Creating macOS Installer - self.creating_macos_installer_label = wx.StaticText(self.frame, label="Formatting and flashing installer to drive") - self.creating_macos_installer_label.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, ".AppleSystemUIFont")) - self.creating_macos_installer_label.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 10 - ) - ) - self.creating_macos_installer_label.Centre(wx.HORIZONTAL) - - # Label: Developer Note: createinstallmedia output currently not implemented - self.developer_note_label = wx.StaticText(self.frame, label="Developer Note: Creating macOS installers can take 30min+ on slower USB drives.") - self.developer_note_label.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, ".AppleSystemUIFont")) - self.developer_note_label.SetPosition( - wx.Point( - self.creating_macos_installer_label.GetPosition().x, - self.creating_macos_installer_label.GetPosition().y + self.creating_macos_installer_label.GetSize().height + 10 - ) - ) - self.developer_note_label.Centre(wx.HORIZONTAL) - - # We will notify you when it's done. Do not close this window however - self.developer_note_label_2 = wx.StaticText(self.frame, label="We will notify you when it's done, please do not close this window however") - self.developer_note_label_2.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, ".AppleSystemUIFont")) - self.developer_note_label_2.SetPosition( - wx.Point( - self.developer_note_label.GetPosition().x, - self.developer_note_label.GetPosition().y + self.developer_note_label.GetSize().height - ) - ) - self.developer_note_label_2.Centre(wx.HORIZONTAL) - - # Progress Bar - max_file_size = 19000 # Best guess for installer + chainloaded packages - self.progress_bar = wx.Gauge(self.frame, range=max_file_size, size=(-1, 20)) - self.progress_bar.SetPosition( - wx.Point( - self.developer_note_label_2.GetPosition().x, - self.developer_note_label_2.GetPosition().y + self.developer_note_label_2.GetSize().height + 10 - ) - ) - self.progress_bar.SetSize( - self.frame.GetSize().width - 40, - 20 - ) - self.progress_bar.Centre(wx.HORIZONTAL) - - self.progress_label = wx.StaticText(self.frame, label="Preparing files, beginning shortly...") - self.progress_label.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, ".AppleSystemUIFont")) - self.progress_label.SetPosition( - wx.Point( - self.progress_bar.GetPosition().x, - self.progress_bar.GetPosition().y + self.progress_bar.GetSize().height + 10 - ) - ) - self.progress_label.Centre(wx.HORIZONTAL) - - self.return_to_main_menu = wx.Button(self.frame, label="Return to Main Menu") - self.return_to_main_menu.SetPosition( - wx.Point( - self.progress_label.GetPosition().x, - self.progress_label.GetPosition().y + self.progress_label.GetSize().height + 10 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - self.return_to_main_menu.Disable() - - self.frame.Show() - - self.frame.SetSize(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40) - wx.GetApp().Yield() - # Create installer.sh script - logging.info("- Creating installer.sh script") - logging.info(f"- Disk: {disk}") - logging.info(f"- Installer: {installer_path}") - - self.prepare_script_thread = threading.Thread(target=self.prepare_script, args=(installer_path,disk)) - self.prepare_script_thread.start() - self.progress_bar.Pulse() - - while self.prepare_script_thread.is_alive(): - self.pulse_alternative(self.progress_bar) - wx.GetApp().Yield() - - if self.prepare_result is True: - self.progress_label.SetLabel("Bytes Written: 0") - self.progress_label.Centre(wx.HORIZONTAL) - logging.info("- Successfully generated creation script") - logging.info("- Starting creation script as admin") - wx.GetApp().Yield() - time.sleep(1) - disk = disk[5:] - self.target_disk = disk - install_thread = threading.Thread(target=self.start_script) - install_thread.start() - self.download_thread = threading.Thread(target=self.download_and_unzip_pkg) - self.download_thread.start() - default_output = float(utilities.monitor_disk_output(disk)) - self.progress_bar.SetValue(0) - while True: - time.sleep(0.1) - output = float(utilities.monitor_disk_output(disk)) - bytes_written = output - default_output - if install_thread.is_alive(): - self.progress_bar.SetValue(int(bytes_written)) - self.progress_label.SetLabel(f"Bytes Written: {round(bytes_written, 2)}MB") - wx.GetApp().Yield() - else: - break - self.progress_bar.SetValue(max_file_size) - self.progress_label.SetLabel(f"Finished Running Installer Creation Script") - self.progress_label.Centre(wx.HORIZONTAL) - if self.finished_cim_process is True: - self.finished_cim_process = False - # Only prompt user with option to install OC to disk if - # the model is supported. - if ( - ( - self.constants.allow_oc_everywhere is False and \ - self.constants.custom_model is None and \ - self.computer.real_model not in model_array.SupportedSMBIOS - ) or ( - self.constants.custom_model is None and \ - self.constants.host_is_hackintosh is True - ) - ): - popup_message = wx.MessageDialog(self.frame, "Successfully created a macOS installer!", "Success", wx.OK) - popup_message.ShowModal() - else: - self.dialog = wx.MessageDialog( - parent=self.frame, - message="Would you like to continue and Install OpenCore to this disk?", - caption="Successfully created the macOS installer!", - style=wx.YES_NO | wx.ICON_QUESTION - ) - self.dialog.SetYesNoLabels("Install OpenCore to disk", "Skip") - response = self.dialog.ShowModal() - if response == wx.ID_YES: - self.constants.start_build_install = True - self.build_install_menu() - else: - logging.info("- Failed to create installer script") - self.progress_label.SetLabel("Failed to copy files to tmp directory") - self.progress_label.Centre(wx.HORIZONTAL) - popup_message = wx.MessageDialog(self.frame, "Failed to prepare the base files for installer creation.\n\nPlease ensure you have 20GB~ free on-disk before starting to ensure the installer has enough room to work.", "Error", wx.OK) - popup_message.ShowModal() - self.return_to_main_menu.Enable() - - def prepare_script(self, installer_path, disk): - self.prepare_result = macos_installer_handler.InstallerCreation().generate_installer_creation_script(self.constants.payload_path, installer_path, disk) - - def start_script(self): - utilities.disable_sleep_while_running() - args = [self.constants.oclp_helper_path, "/bin/sh", self.constants.installer_sh_path] - result = subprocess.run(args, capture_output=True, text=True) - output = result.stdout - error = result.stderr if result.stderr else "" - - if "Install media now available at" in output: - logging.info("- Successfully created macOS installer") - while self.download_thread.is_alive(): - # wait for download_thread to finish - # though highly unlikely this thread is still alive (flashing an Installer will take a while) - time.sleep(0.1) - logging.info("- Installing Root Patcher to drive") - self.install_installer_pkg(self.target_disk) - self.finished_cim_process = True - else: - logging.info("- Failed to create macOS installer") - popup = wx.MessageDialog(self.frame, f"Failed to create macOS installer\n\nOutput: {output}\n\nError: {error}", "Error", wx.OK | wx.ICON_ERROR) - popup.ShowModal() - utilities.enable_sleep_after_running() - - - def download_and_unzip_pkg(self): - # Function's main goal is to grab the correct AutoPkg-Assets.pkg and unzip it - # Note the following: - # - When running a release build, pull from Github's release page with the same versioning - # - When running from source/unable to find on Github, use the nightly.link variant - # - If nightly also fails, fall back to the manually uploaded variant - link = self.constants.installer_pkg_url - if network_handler.NetworkUtilities(link).validate_link() is False: - logging.info("- Stock Install.pkg is missing on Github, falling back to Nightly") - link = self.constants.installer_pkg_url_nightly - - if link.endswith(".zip"): - path = self.constants.installer_pkg_zip_path - else: - path = self.constants.installer_pkg_path - - autopkg_download = network_handler.DownloadObject(link, path) - autopkg_download.download(spawn_thread=False) - - if autopkg_download.download_complete is False: - logging.warning("- Failed to download Install.pkg") - logging.warning(autopkg_download.error_msg) - return - - # Download thread will re-enable Idle Sleep after downloading - utilities.disable_sleep_while_running() - if not str(path).endswith(".zip"): - return - if Path(self.constants.installer_pkg_path).exists(): - subprocess.run(["rm", self.constants.installer_pkg_path]) - subprocess.run(["ditto", "-V", "-x", "-k", "--sequesterRsrc", "--rsrc", self.constants.installer_pkg_zip_path, self.constants.payload_path]) - - - def _kdk_chainload(self, build: str, version: str, download_dir: str): - """ - Download the correct KDK to be chainloaded in the macOS installer - - Parameters - build (str): The build number of the macOS installer (e.g. 20A5343j) - version (str): The version of the macOS installer (e.g. 11.0.1) - """ - - kdk_dmg_path = Path(download_dir) / "KDK.dmg" - kdk_pkg_path = Path(download_dir) / "KDK.pkg" - - if kdk_dmg_path.exists(): - kdk_dmg_path.unlink() - if kdk_pkg_path.exists(): - kdk_pkg_path.unlink() - - logging.info("- Initiating KDK download") - logging.info(f" - Build: {build}") - logging.info(f" - Version: {version}") - logging.info(f" - Working Directory: {download_dir}") - - kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, build, version, ignore_installed=True) - if kdk_obj.success is False: - logging.info("- Failed to retrieve KDK") - logging.info(kdk_obj.error_msg) - return - - kdk_download_obj = kdk_obj.retrieve_download(override_path=kdk_dmg_path) - if kdk_download_obj is None: - logging.info("- Failed to retrieve KDK") - logging.info(kdk_obj.error_msg) - - # Check remaining disk space before downloading - space = utilities.get_free_space(download_dir) - if space < (kdk_obj.kdk_url_expected_size * 2): - logging.info("- Not enough disk space to download and install KDK") - logging.info(f"- Attempting to download locally first") - if space < kdk_obj.kdk_url_expected_size: - logging.info("- Not enough disk space to install KDK, skipping") - return - # Ideally we'd download the KDK onto the disk to display progress in the UI - # However we'll just download to our temp directory and move it to the target disk - kdk_dmg_path = self.constants.kdk_download_path - - kdk_download_obj.download(spawn_thread=False) - if kdk_download_obj.download_complete is False: - logging.info("- Failed to download KDK") - logging.info(kdk_download_obj.error_msg) - return - - if not kdk_dmg_path.exists(): - logging.info(f"- KDK missing: {kdk_dmg_path}") - return - - # Now that we have a KDK, extract it to get the pkg - with tempfile.TemporaryDirectory() as mount_point: - logging.info("- Mounting KDK") - result = subprocess.run(["hdiutil", "attach", kdk_dmg_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - if result.returncode != 0: - logging.info("- Failed to mount KDK") - logging.info(result.stdout.decode("utf-8")) - return - - logging.info("- Copying KDK") - subprocess.run(["cp", "-r", f"{mount_point}/KernelDebugKit.pkg", kdk_pkg_path]) - - logging.info("- Unmounting KDK") - result = subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - if result.returncode != 0: - logging.info("- Failed to unmount KDK") - logging.info(result.stdout.decode("utf-8")) - return - - logging.info("- Removing KDK Disk Image") - kdk_dmg_path.unlink() - - - def install_installer_pkg(self, disk): - disk = disk + "s2" # ESP sits at 1, and we know macOS will have created the main partition at 2 - - if not Path(self.constants.installer_pkg_path).exists(): - return - - path = utilities.grab_mount_point_from_disk(disk) - if not Path(path + "/System/Library/CoreServices/SystemVersion.plist").exists(): - return - - os_version = plistlib.load(Path(path + "/System/Library/CoreServices/SystemVersion.plist").open("rb")) - kernel_version = os_data.os_conversion.os_to_kernel(os_version["ProductVersion"]) - if int(kernel_version) < os_data.os_data.big_sur: - logging.info("- Installer unsupported, requires Big Sur or newer") - return - - subprocess.run(["mkdir", "-p", f"{path}/Library/Packages/"]) - subprocess.run(["cp", "-r", self.constants.installer_pkg_path, f"{path}/Library/Packages/"]) - - self._kdk_chainload(os_version["ProductBuildVersion"], os_version["ProductVersion"], Path(path + "/Library/Packages/")) - - - def settings_menu(self, event=None): - # Define Menu - # - Header: Settings - # - Dropdown: Model - # - Checkboxes: - # - Verbose - # - Kext Debug - # - OpenCore Debug - # - SIP - # - SecureBootModel - # - Show Boot Picker - # - Buttons: - # - Developer Settings - # - Return to Main Menu - - # Create wxDialog and have Settings menu be WindowModal - - # Create Menu - self.reset_frame_modal() - - self.frame_modal.SetSize(wx.Size(self.WINDOW_SETTINGS_WIDTH, self.WINDOW_SETTINGS_HEIGHT)) - self.frame_modal.SetTitle("Settings") - - # Header - self.header = wx.StaticText(self.frame_modal, label="Settings", pos=wx.Point(10, 10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.SetPosition((-1, 5)) - self.header.Centre(wx.HORIZONTAL) - - # Subheader - self.subheader = wx.StaticText(self.frame_modal, label="Changing settings here require you") - self.subheader.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, ".AppleSystemUIFont")) - self.subheader.SetPosition( - wx.Point( - self.header.GetPosition().x, - self.header.GetPosition().y + self.header.GetSize().height + 10 - ) - ) - self.subheader.Centre(wx.HORIZONTAL) - self.subheader2 = wx.StaticText(self.frame_modal, label="to run 'Build and Install OpenCore'") - self.subheader2.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, ".AppleSystemUIFont")) - self.subheader2.SetPosition( - wx.Point( - self.subheader.GetPosition().x, - self.subheader.GetPosition().y + self.subheader.GetSize().height - ) - ) - self.subheader2.Centre(wx.HORIZONTAL) - self.subheader3 = wx.StaticText(self.frame_modal, label="then reboot for changes to be applied") - self.subheader3.SetFont(wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False, ".AppleSystemUIFont")) - self.subheader3.SetPosition( - wx.Point( - self.subheader2.GetPosition().x, - self.subheader2.GetPosition().y + self.subheader2.GetSize().height - ) - ) - self.subheader3.Centre(wx.HORIZONTAL) - - # Dropdown - self.dropdown_model = wx.Choice(self.frame_modal) - for model in model_array.SupportedSMBIOS: - self.dropdown_model.Append(model) - if self.computer.real_model not in self.dropdown_model.GetItems(): - # In the event an unsupported model is loaded, add it to the dropdown - # Supported situation: If user wants to run on native model - self.dropdown_model.Append(self.computer.real_model) - self.dropdown_model.SetSelection(self.dropdown_model.GetItems().index(self.constants.custom_model or self.computer.real_model)) - self.dropdown_model.SetPosition( - wx.Point( - self.subheader3.GetPosition().x, - self.subheader3.GetPosition().y + self.subheader3.GetSize().height + 10 - ) - ) - # Set size to largest item - self.dropdown_model.SetSize( - wx.Size( - self.dropdown_model.GetBestSize().width, - self.dropdown_model.GetBestSize().height - ) - ) - self.dropdown_model.Bind(wx.EVT_CHOICE, self.model_choice_click) - self.dropdown_model.Centre(wx.HORIZONTAL) - self.dropdown_model.ToolTip = wx.ToolTip("Select the model you want to build for") - - # Checkboxes - # Checkbox: Allow native models - self.checkbox_allow_native_models = wx.CheckBox(self.frame_modal, label="Allow native models") - self.checkbox_allow_native_models.SetValue(self.constants.allow_oc_everywhere) - self.checkbox_allow_native_models.SetPosition(wx.Point(self.dropdown_model.GetPosition().x, self.dropdown_model.GetPosition().y + self.dropdown_model.GetSize().height + 10)) - self.checkbox_allow_native_models.Bind(wx.EVT_CHECKBOX, self.allow_native_models_click) - self.checkbox_allow_native_models.ToolTip = wx.ToolTip("""Select to allow OpenCore to be installed on native models\nGenerally used for enabling OS features Apple locks out of native Macs\nie. AirPlay to Mac, Sidecar.""") - - # Checkbox: Verbose - self.verbose_checkbox = wx.CheckBox(self.frame_modal, label="Verbose") - self.verbose_checkbox.SetValue(self.constants.verbose_debug) - self.verbose_checkbox.SetPosition(wx.Point(self.checkbox_allow_native_models.GetPosition().x, self.checkbox_allow_native_models.GetPosition().y + self.checkbox_allow_native_models.GetSize().height)) - self.verbose_checkbox.Bind(wx.EVT_CHECKBOX, self.verbose_checkbox_click) - self.verbose_checkbox.ToolTip = wx.ToolTip("""Add -v (verbose) to boot-args during build""") - - # Checkbox: Kext Debug - self.kext_checkbox = wx.CheckBox(self.frame_modal, label="Kext Debug") - self.kext_checkbox.SetValue(self.constants.kext_debug) - self.kext_checkbox.SetPosition(wx.Point(self.verbose_checkbox.GetPosition().x , self.verbose_checkbox.GetPosition().y + self.verbose_checkbox.GetSize().height)) - self.kext_checkbox.Bind(wx.EVT_CHECKBOX, self.kext_checkbox_click) - self.kext_checkbox.ToolTip = wx.ToolTip("""Enables additional kext logging, including expanded message buffer""") - - # Checkbox: OpenCore Debug - self.opencore_checkbox = wx.CheckBox(self.frame_modal, label="OpenCore Debug") - self.opencore_checkbox.SetValue(self.constants.opencore_debug) - self.opencore_checkbox.SetPosition(wx.Point(self.kext_checkbox.GetPosition().x , self.kext_checkbox.GetPosition().y + self.kext_checkbox.GetSize().height)) - self.opencore_checkbox.Bind(wx.EVT_CHECKBOX, self.oc_checkbox_click) - self.opencore_checkbox.ToolTip = wx.ToolTip("""Enables OpenCore logging, can heavily impact boot times""") - - # Checkbox: SecureBootModel - self.secureboot_checkbox = wx.CheckBox(self.frame_modal, label="SecureBootModel") - self.secureboot_checkbox.SetValue(self.constants.secure_status) - self.secureboot_checkbox.SetPosition(wx.Point(self.opencore_checkbox.GetPosition().x , self.opencore_checkbox.GetPosition().y + self.opencore_checkbox.GetSize().height)) - self.secureboot_checkbox.Bind(wx.EVT_CHECKBOX, self.secureboot_checkbox_click) - self.secureboot_checkbox.ToolTip = wx.ToolTip("""Sets SecureBootModel, useful for models spoofing T2 Macs to get OTA updates""") - - # Checkbox: Show Boot Picker - self.bootpicker_checkbox = wx.CheckBox(self.frame_modal, label="Show Boot Picker") - self.bootpicker_checkbox.SetValue(self.constants.showpicker) - self.bootpicker_checkbox.SetPosition(wx.Point(self.secureboot_checkbox.GetPosition().x , self.secureboot_checkbox.GetPosition().y + self.secureboot_checkbox.GetSize().height)) - self.bootpicker_checkbox.Bind(wx.EVT_CHECKBOX, self.show_picker_checkbox_click) - self.bootpicker_checkbox.ToolTip = wx.ToolTip("""Shows OpenCore's Boot Picker on machine start\nToggling this off will hide the picker, and only load when holding either Option or Escape""") - - # Buttons - - # Button: SIP Settings - if self.constants.custom_sip_value: - sip_string = "Custom" - elif self.constants.sip_status: - sip_string = "Enabled" - else: - sip_string = "Lowered" - self.sip_button = wx.Button(self.frame_modal, label=f"SIP Settings ({sip_string})", size=(155,30)) - self.sip_button.SetPosition(wx.Point(self.bootpicker_checkbox.GetPosition().x , self.bootpicker_checkbox.GetPosition().y + self.bootpicker_checkbox.GetSize().height + 10)) - self.sip_button.Bind(wx.EVT_BUTTON, self.sip_config_menu) - self.sip_button.Center(wx.HORIZONTAL) - - # Button: SMBIOS Settings - self.smbios_button = wx.Button(self.frame_modal, label="SMBIOS Settings", size=(155,30)) - self.smbios_button.SetPosition(wx.Point(self.sip_button.GetPosition().x , self.sip_button.GetPosition().y + self.sip_button.GetSize().height)) - self.smbios_button.Bind(wx.EVT_BUTTON, self.smbios_settings_menu) - self.smbios_button.Center(wx.HORIZONTAL) - - # Button: Misc Settings - self.misc_button = wx.Button(self.frame_modal, label="Misc Settings", size=(155,30)) - self.misc_button.SetPosition(wx.Point(self.smbios_button.GetPosition().x , self.smbios_button.GetPosition().y + self.smbios_button.GetSize().height)) - self.misc_button.Bind(wx.EVT_BUTTON, self.misc_settings_menu) - self.misc_button.Center(wx.HORIZONTAL) - - # Button: non-Metal Settings - self.nonmetal_button = wx.Button(self.frame_modal, label="Non-Metal Settings", size=(155,30)) - self.nonmetal_button.SetPosition(wx.Point(self.misc_button.GetPosition().x , self.misc_button.GetPosition().y + self.misc_button.GetSize().height)) - self.nonmetal_button.Bind(wx.EVT_BUTTON, self.non_metal_config_menu) - self.nonmetal_button.Center(wx.HORIZONTAL) - - # Button: Developer Settings - self.miscellaneous_button = wx.Button(self.frame_modal, label="Developer Settings", size=(155,30)) - self.miscellaneous_button.SetPosition(wx.Point(self.nonmetal_button.GetPosition().x , self.nonmetal_button.GetPosition().y + self.nonmetal_button.GetSize().height)) - self.miscellaneous_button.Bind(wx.EVT_BUTTON, self.dev_settings_menu) - self.miscellaneous_button.Centre(wx.HORIZONTAL) - - self.return_to_main_menu = wx.Button(self.frame_modal, label="Return to Main Menu", size=(155,30)) - self.return_to_main_menu.SetPosition( - wx.Point( - self.miscellaneous_button.GetPosition().x, - self.miscellaneous_button.GetPosition().y + self.miscellaneous_button.GetSize().height + 10 - ) - ) - self.return_to_main_menu.Bind(wx.EVT_BUTTON, self.main_menu) - self.return_to_main_menu.Centre(wx.HORIZONTAL) - - # Set frame size to below return_to_main_menu button - self.frame_modal.SetSize(wx.Size(-1, self.return_to_main_menu.GetPosition().y + self.return_to_main_menu.GetSize().height + 40)) - self.frame_modal.ShowWindowModal() - - def model_choice_click(self, event=None): - user_choice = self.dropdown_model.GetStringSelection() - if user_choice == self.computer.real_model: - logging.info(f"Using Real Model: {user_choice}") - self.constants.custom_model = None - defaults.GenerateDefaults(self.computer.real_model, True, self.constants) - else: - logging.info(f"Using Custom Model: {user_choice}") - self.constants.custom_model = user_choice - defaults.GenerateDefaults(self.constants.custom_model, False, self.constants) - # Reload Settings - self.settings_menu(None) - - def allow_native_models_click(self, event=None): - if self.checkbox_allow_native_models.GetValue(): - # Throw a prompt warning about this - dlg = wx.MessageDialog(self.frame_modal, "This option should only be used if your Mac natively supports the OSes you wish to run.\n\nIf you are currently running an unsupported OS, this option will break booting. Only toggle for enabling OS features on a native Mac.\n\nAre you certain you want to continue?", "Warning", wx.YES_NO | wx.ICON_WARNING) - if dlg.ShowModal() == wx.ID_NO: - self.checkbox_allow_native_models.SetValue(False) - return - # If the system is running an unsupported OS, throw a second warning - if self.constants.computer.real_model in smbios_data.smbios_dictionary: - if self.constants.detected_os > smbios_data.smbios_dictionary[self.constants.computer.real_model]["Max OS Supported"]: - chassis_type = "aluminum" - if self.constants.computer.real_model in ["MacBook4,1", "MacBook5,2", "MacBook6,1", "MacBook7,1"]: - chassis_type = "plastic" - dlg = wx.MessageDialog(self.frame_modal, f"This model, {self.constants.computer.real_model}, does not natively support macOS {os_data.os_conversion.kernel_to_os(self.constants.detected_os)}, {os_data.os_conversion.convert_kernel_to_marketing_name(self.constants.detected_os)}. The last native OS was macOS {os_data.os_conversion.kernel_to_os(smbios_data.smbios_dictionary[self.constants.computer.real_model]['Max OS Supported'])}, {os_data.os_conversion.convert_kernel_to_marketing_name(smbios_data.smbios_dictionary[self.constants.computer.real_model]['Max OS Supported'])}\n\nToggling this option will break booting on this OS. Are you absolutely certain this is desired?\n\nYou may end up with a nice {chassis_type} brick 🧱", "Are you certain?", wx.YES_NO | wx.ICON_WARNING) - if dlg.ShowModal() == wx.ID_NO: - self.checkbox_allow_native_models.SetValue(False) - return - logging.info("Allow Native Models") - self.constants.allow_oc_everywhere = True - self.constants.serial_settings = "None" - else: - logging.info("Disallow Native Models") - self.constants.allow_oc_everywhere = False - self.constants.serial_settings = "Minimal" - - def verbose_checkbox_click(self, event=None): - if self.verbose_checkbox.GetValue(): - logging.info("Verbose mode enabled") - self.constants.verbose_debug = True - else: - logging.info("Verbose mode disabled") - self.constants.verbose_debug = False - - def kext_checkbox_click(self, event=None): - if self.kext_checkbox.GetValue(): - logging.info("Kext mode enabled") - self.constants.kext_debug = True - self.constants.kext_variant = "DEBUG" - else: - logging.info("Kext mode disabled") - self.constants.kext_debug = False - self.constants.kext_variant = "RELEASE" - - def oc_checkbox_click(self, event=None): - if self.opencore_checkbox.GetValue(): - logging.info("OC mode enabled") - self.constants.opencore_debug = True - self.constants.opencore_build = "DEBUG" - else: - logging.info("OC mode disabled") - self.constants.opencore_debug = False - self.constants.opencore_build = "RELEASE" - - def sip_checkbox_click(self, event=None): - if self.sip_checkbox.GetValue(): - logging.info("SIP mode enabled") - self.constants.sip_status = True - else: - logging.info("SIP mode disabled") - self.constants.sip_status = False - - def secureboot_checkbox_click(self, event=None): - if self.secureboot_checkbox.GetValue(): - logging.info("SecureBoot mode enabled") - self.constants.secure_status = True - else: - logging.info("SecureBoot mode disabled") - self.constants.secure_status = False - - def show_picker_checkbox_click(self, event=None): - if self.bootpicker_checkbox.GetValue(): - logging.info("Show Picker mode enabled") - self.constants.showpicker = True - else: - logging.info("Show Picker mode disabled") - self.constants.showpicker = False - - def dev_settings_menu(self, event=None): - self.reset_frame_modal() - - # Header - self.header = wx.StaticText(self.frame_modal, label="Developer Settings", style=wx.ALIGN_CENTRE, pos=wx.Point(10, 10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.SetPosition(wx.Point(0, 10)) - self.header.SetSize(wx.Size(self.frame_modal.GetSize().width, 30)) - self.header.Centre(wx.HORIZONTAL) - - # Subheader: If unfamiliar with the following settings, please do not change them. - self.subheader = wx.StaticText(self.frame_modal, label="Do not change if unfamiliar", style=wx.ALIGN_CENTRE) - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader.SetPosition(wx.Point(0, self.header.GetPosition().y + self.header.GetSize().height)) - self.subheader.SetSize(wx.Size(self.frame_modal.GetSize().width, 30)) - self.subheader.Centre(wx.HORIZONTAL) - - # Label: Set GPU Model for MXM iMacs - self.label_model = wx.StaticText(self.frame_modal, label="Set GPU Model for MXM iMacs:", style=wx.ALIGN_CENTRE) - self.label_model.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.label_model.SetPosition(wx.Point(0, self.subheader.GetPosition().y + self.subheader.GetSize().height + 2)) - self.label_model.SetSize(wx.Size(self.frame_modal.GetSize().width, 30)) - self.label_model.Centre(wx.HORIZONTAL) - - # Dropdown: GPU Model - self.gpu_dropdown = wx.Choice(self.frame_modal) - for gpu in ["None", "Nvidia Kepler", "AMD GCN", "AMD Polaris", "AMD Navi", "AMD Lexa"]: - self.gpu_dropdown.Append(gpu) - self.gpu_dropdown.SetSelection(0) - self.gpu_dropdown.SetPosition(wx.Point( - self.label_model.GetPosition().x, - int(self.label_model.GetPosition().y + self.label_model.GetSize().height / 1.5))) - self.gpu_dropdown.Bind(wx.EVT_CHOICE, self.gpu_selection_click) - self.gpu_dropdown.Centre(wx.HORIZONTAL) - self.gpu_dropdown.SetToolTip(wx.ToolTip("Configures MXM GPU Vendor logic on pre-built models\nIf you are not using MXM iMacs, please leave this setting as is.")) - models = ["iMac9,1", "iMac10,1", "iMac11,1", "iMac11,2", "iMac11,3", "iMac12,1", "iMac12,2"] - if (not self.constants.custom_model and self.computer.real_model not in models) or (self.constants.custom_model and self.constants.custom_model not in models): - self.gpu_dropdown.Disable() - - # OpenCore Picker Timeout (using wxSpinCtrl) - # Label: Picker Timeout - self.label_timeout = wx.StaticText(self.frame_modal, label="Picker Timeout (seconds):", style=wx.ALIGN_CENTRE) - self.label_timeout.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.label_timeout.SetPosition(wx.Point(0, self.gpu_dropdown.GetPosition().y + self.gpu_dropdown.GetSize().height + 2)) - self.label_timeout.SetSize(wx.Size(self.frame_modal.GetSize().width, 30)) - self.label_timeout.Centre(wx.HORIZONTAL) - - # Picker Timeout - self.timeout_spinner = wx.SpinCtrl(self.frame_modal, value=f"{self.constants.oc_timeout}", min=0, max=60) - self.timeout_spinner.SetPosition(wx.Point( - self.label_timeout.GetPosition().x, - int(self.label_timeout.GetPosition().y + self.label_timeout.GetSize().height / 2))) - self.timeout_spinner.Bind(wx.EVT_SPINCTRL, self.timeout_spinner_click) - self.timeout_spinner.Centre(wx.HORIZONTAL) - - # AMD GOP Injection - self.set_amd_gop_injection = wx.CheckBox(self.frame_modal, label="AMD GOP Injection") - self.set_amd_gop_injection.SetPosition(wx.Point( - 30, - self.timeout_spinner.GetPosition().y + self.timeout_spinner.GetSize().height + 5)) - self.set_amd_gop_injection.SetValue(self.constants.amd_gop_injection) - self.set_amd_gop_injection.Bind(wx.EVT_CHECKBOX, self.amd_gop_injection_checkbox_click) - models = ["iMac9,1", "iMac10,1", "iMac11,1", "iMac11,2", "iMac11,3", "iMac12,1", "iMac12,2", "MacPro3,1", "MacPro4,1", "MacPro5,1", "Xserve2,1", "Xserve3,1"] - if (not self.constants.custom_model and self.computer.real_model not in models) or (self.constants.custom_model and self.constants.custom_model not in models): - self.set_amd_gop_injection.Disable() - - # Nvidia Kepler GOP injection - self.set_nvidia_kepler_gop_injection = wx.CheckBox(self.frame_modal, label="Nvidia Kepler GOP Injection") - self.set_nvidia_kepler_gop_injection.SetPosition(wx.Point( - self.set_amd_gop_injection.GetPosition().x, - self.set_amd_gop_injection.GetPosition().y + self.set_amd_gop_injection.GetSize().height)) - self.set_nvidia_kepler_gop_injection.SetValue(self.constants.nvidia_kepler_gop_injection) - self.set_nvidia_kepler_gop_injection.Bind(wx.EVT_CHECKBOX, self.nvidia_kepler_gop_injection_checkbox_click) - if (not self.constants.custom_model and self.computer.real_model not in models) or (self.constants.custom_model and self.constants.custom_model not in models): - self.set_nvidia_kepler_gop_injection.Disable() - - # Disable Thunderbolt - self.disable_thunderbolt_checkbox = wx.CheckBox(self.frame_modal, label="Disable Thunderbolt") - self.disable_thunderbolt_checkbox.SetValue(self.constants.disable_tb) - self.disable_thunderbolt_checkbox.Bind(wx.EVT_CHECKBOX, self.disable_tb_click) - self.disable_thunderbolt_checkbox.SetPosition(wx.Point( - self.set_nvidia_kepler_gop_injection.GetPosition().x, - self.set_nvidia_kepler_gop_injection.GetPosition().y + self.set_nvidia_kepler_gop_injection.GetSize().height)) - self.disable_thunderbolt_checkbox.SetToolTip(wx.ToolTip("Disables Thunderbolt support on MacBookPro11,x\nMainly applicable for systems that cannot boot with Thunderbolt enabled")) - if not self.constants.custom_model and not self.computer.real_model.startswith("MacBookPro11"): - self.disable_thunderbolt_checkbox.Disable() - # Set TeraScale 2 Accel - self.set_terascale_accel_checkbox = wx.CheckBox(self.frame_modal, label="Set TeraScale 2 Accel") - self.set_terascale_accel_checkbox.SetValue(self.constants.allow_ts2_accel) - self.set_terascale_accel_checkbox.Bind(wx.EVT_CHECKBOX, self.ts2_accel_click) - self.set_terascale_accel_checkbox.SetPosition(wx.Point( - self.disable_thunderbolt_checkbox.GetPosition().x, - self.disable_thunderbolt_checkbox.GetPosition().y + self.disable_thunderbolt_checkbox.GetSize().height)) - self.set_terascale_accel_checkbox.SetToolTip(wx.ToolTip("This option will determine whether TeraScale 2 acceleration is available during Root Volume patching.\nOnly applicable if your system has a AMD TeraScale 2 GPU (ie. MacBookPro8,2/3)")) - if self.computer.real_model not in ["MacBookPro8,2", "MacBookPro8,3"]: - self.set_terascale_accel_checkbox.Disable() - self.set_terascale_accel_checkbox.SetValue(False) - - # Disable ColorSync Downgrade - self.set_colorsync_checkbox = wx.CheckBox(self.frame_modal, label="Disable ColorSync Downgrade") - self.set_colorsync_checkbox.SetValue(self.constants.disable_cat_colorsync) - self.set_colorsync_checkbox.Bind(wx.EVT_CHECKBOX, self.disable_colorsync_click) - self.set_colorsync_checkbox.SetPosition(wx.Point( - self.set_terascale_accel_checkbox.GetPosition().x, - self.set_terascale_accel_checkbox.GetPosition().y + self.set_terascale_accel_checkbox.GetSize().height)) - self.set_colorsync_checkbox.SetToolTip(wx.ToolTip("This option will disable the ColorSync patch used on HD 3000 Macs.\nMainly applicable if you need Display Profile functionality")) - if self.computer.real_model not in ["MacBookAir4,1","MacBookAir4,2","MacBookPro8,1","MacBookPro8,2","MacBookPro8,3","Macmini5,1"]: - self.set_colorsync_checkbox.Disable() - self.set_colorsync_checkbox.SetValue(False) - - # Windows GMUX - self.windows_gmux_checkbox = wx.CheckBox(self.frame_modal, label="Windows GMUX") - self.windows_gmux_checkbox.SetValue(self.constants.dGPU_switch) - self.windows_gmux_checkbox.Bind(wx.EVT_CHECKBOX, self.windows_gmux_click) - self.windows_gmux_checkbox.SetPosition(wx.Point( - self.set_colorsync_checkbox.GetPosition().x, - self.set_colorsync_checkbox.GetPosition().y + self.set_colorsync_checkbox.GetSize().height)) - self.windows_gmux_checkbox.SetToolTip(wx.ToolTip("Enable this option to allow usage of the hardware GMUX to switch between Intel and Nvidia/AMD GPUs in Windows.")) - - # Hibernation Workaround - self.hibernation_checkbox = wx.CheckBox(self.frame_modal, label="Hibernation Workaround") - self.hibernation_checkbox.SetValue(self.constants.disable_connectdrivers) - self.hibernation_checkbox.Bind(wx.EVT_CHECKBOX, self.hibernation_click) - self.hibernation_checkbox.SetPosition(wx.Point( - self.windows_gmux_checkbox.GetPosition().x, - self.windows_gmux_checkbox.GetPosition().y + self.windows_gmux_checkbox.GetSize().height)) - self.hibernation_checkbox.SetToolTip(wx.ToolTip("This will disable the ConnectDrivers in OpenCore\nRecommended to toggle if your machine is having issues with hibernation.\nMainly applicable for MacBookPro9,1 and MacBookPro10,1")) - - # Disable Battery Throttling - self.disable_battery_throttling_checkbox = wx.CheckBox(self.frame_modal, label="Disable Firmware Throttling") - self.disable_battery_throttling_checkbox.SetValue(self.constants.disable_msr_power_ctl) - self.disable_battery_throttling_checkbox.Bind(wx.EVT_CHECKBOX, self.disable_battery_throttling_click) - self.disable_battery_throttling_checkbox.SetPosition(wx.Point( - self.hibernation_checkbox.GetPosition().x, - self.hibernation_checkbox.GetPosition().y + self.hibernation_checkbox.GetSize().height)) - self.disable_battery_throttling_checkbox.SetToolTip(wx.ToolTip("This will forcefully disable MSR Power Control on Arrandale and newer Macs\nMainly applicable for systems with severe throttling due to missing battery or display")) - - # Disable XCPM - self.disable_xcpm_checkbox = wx.CheckBox(self.frame_modal, label="Disable XCPM") - self.disable_xcpm_checkbox.SetValue(self.constants.disable_xcpm) - self.disable_xcpm_checkbox.Bind(wx.EVT_CHECKBOX, self.disable_xcpm_click) - self.disable_xcpm_checkbox.SetPosition(wx.Point( - self.disable_battery_throttling_checkbox.GetPosition().x, - self.disable_battery_throttling_checkbox.GetPosition().y + self.disable_battery_throttling_checkbox.GetSize().height)) - self.disable_xcpm_checkbox.SetToolTip(wx.ToolTip("This will forcefully disable XCPM on Ivy Bridge EP and newer Macs\nMainly applicable for systems with severe throttling due to missing battery or display")) - - # Software Demux - self.software_demux_checkbox = wx.CheckBox(self.frame_modal, label="Software Demux") - self.software_demux_checkbox.SetValue(self.constants.software_demux) - self.software_demux_checkbox.Bind(wx.EVT_CHECKBOX, self.software_demux_click) - self.software_demux_checkbox.SetPosition(wx.Point( - self.disable_xcpm_checkbox.GetPosition().x, - self.disable_xcpm_checkbox.GetPosition().y + self.disable_xcpm_checkbox.GetSize().height)) - self.software_demux_checkbox.SetToolTip(wx.ToolTip("This will force a software based demux on MacBookPro8,2/3 aiding for better battery life\nThis will require the dGPU to be disabled via NVRAM")) - if not self.constants.custom_model and self.computer.real_model not in ["MacBookPro8,2", "MacBookPro8,3"]: - self.software_demux_checkbox.Disable() - - # Disable CPUFriend - self.disable_cpu_friend_checkbox = wx.CheckBox(self.frame_modal, label="Disable CPUFriend") - self.disable_cpu_friend_checkbox.SetValue(self.constants.disallow_cpufriend) - self.disable_cpu_friend_checkbox.Bind(wx.EVT_CHECKBOX, self.disable_cpu_friend_click) - self.disable_cpu_friend_checkbox.SetPosition(wx.Point( - self.software_demux_checkbox.GetPosition().x, - self.software_demux_checkbox.GetPosition().y + self.software_demux_checkbox.GetSize().height)) - self.disable_cpu_friend_checkbox.SetToolTip(wx.ToolTip("This will disable CPUFriend on your system when using Minimal or higher SMBIOS spoofing.\nMainly applicable for older iMacs (2007-9) that wish to disable CPU throttling")) - if self.constants.serial_settings == "None": - self.disable_cpu_friend_checkbox.Disable() - - # AppleALC Usage - self.apple_alc_checkbox = wx.CheckBox(self.frame_modal, label="AppleALC Usage") - self.apple_alc_checkbox.SetValue(self.constants.set_alc_usage) - self.apple_alc_checkbox.Bind(wx.EVT_CHECKBOX, self.apple_alc_click) - self.apple_alc_checkbox.SetPosition(wx.Point( - self.disable_cpu_friend_checkbox.GetPosition().x, - self.disable_cpu_friend_checkbox.GetPosition().y + self.disable_cpu_friend_checkbox.GetSize().height)) - self.apple_alc_checkbox.SetToolTip(wx.ToolTip("This will set whether AppleALC is allowed to be used during config building.\nMainly applicable for MacPro3,1s that do not have boot screen support, thus preventing AppleALC from working.")) - - # Set WriteFlash - self.set_writeflash_checkbox = wx.CheckBox(self.frame_modal, label="Set NVRAM WriteFlash") - self.set_writeflash_checkbox.SetValue(self.constants.nvram_write) - self.set_writeflash_checkbox.Bind(wx.EVT_CHECKBOX, self.set_writeflash_click) - self.set_writeflash_checkbox.SetPosition(wx.Point( - self.apple_alc_checkbox.GetPosition().x, - self.apple_alc_checkbox.GetPosition().y + self.apple_alc_checkbox.GetSize().height)) - self.set_writeflash_checkbox.SetToolTip(wx.ToolTip("This will set whether OpenCore is allowed to write to hardware NVRAM.\nDisable this option if your system has degraded or fragile NVRAM.")) - # Set Enhanced 3rd Party SSD - self.set_enhanced_3rd_party_ssd_checkbox = wx.CheckBox(self.frame_modal, label="Enhanced SSD Support") - self.set_enhanced_3rd_party_ssd_checkbox.SetValue(self.constants.allow_3rd_party_drives) - self.set_enhanced_3rd_party_ssd_checkbox.Bind(wx.EVT_CHECKBOX, self.set_enhanced_3rd_party_ssd_click) - self.set_enhanced_3rd_party_ssd_checkbox.SetPosition(wx.Point( - self.set_writeflash_checkbox.GetPosition().x, - self.set_writeflash_checkbox.GetPosition().y + self.set_writeflash_checkbox.GetSize().height)) - self.set_enhanced_3rd_party_ssd_checkbox.SetToolTip(wx.ToolTip("This will set whether OpenCore is allowed to force Apple Vendor on 3rd Party SATA SSDs\nSome benefits from this patch include better SSD performance, TRIM support and hibernation support.\nDisable this option if your SSD does not support TRIM correctly")) - if self.computer.third_party_sata_ssd is False and not self.constants.custom_model: - self.set_enhanced_3rd_party_ssd_checkbox.Disable() - - # Disable Library Validation - self.disable_library_validation_checkbox = wx.CheckBox(self.frame_modal, label="Disable Library Validation") - self.disable_library_validation_checkbox.SetValue(self.constants.disable_cs_lv) - self.disable_library_validation_checkbox.Bind(wx.EVT_CHECKBOX, self.disable_library_validation_click) - self.disable_library_validation_checkbox.SetPosition(wx.Point( - self.set_enhanced_3rd_party_ssd_checkbox.GetPosition().x, - self.set_enhanced_3rd_party_ssd_checkbox.GetPosition().y + self.set_enhanced_3rd_party_ssd_checkbox.GetSize().height - )) - - # Disable AMFI - self.disable_amfi_checkbox = wx.CheckBox(self.frame_modal, label="Disable AMFI") - self.disable_amfi_checkbox.SetValue(self.constants.disable_amfi) - self.disable_amfi_checkbox.Bind(wx.EVT_CHECKBOX, self.disable_amfi_click) - self.disable_amfi_checkbox.SetPosition(wx.Point( - self.disable_library_validation_checkbox.GetPosition().x, - self.disable_library_validation_checkbox.GetPosition().y + self.disable_library_validation_checkbox.GetSize().height - )) - if self.constants.disable_cs_lv is False: - self.disable_amfi_checkbox.Disable() - - - # Delete Unused KDKs during patching - self.delete_unused_kdks_checkbox = wx.CheckBox(self.frame_modal, label="Delete Unused KDKs") - self.delete_unused_kdks_checkbox.SetValue(self.constants.should_nuke_kdks) - self.delete_unused_kdks_checkbox.Bind(wx.EVT_CHECKBOX, self.delete_unused_kdks_click) - self.delete_unused_kdks_checkbox.SetPosition(wx.Point( - self.disable_amfi_checkbox.GetPosition().x, - self.disable_amfi_checkbox.GetPosition().y + self.disable_amfi_checkbox.GetSize().height - )) - self.delete_unused_kdks_checkbox.SetToolTip(wx.ToolTip("This will delete unused KDKs during root patching.\nThis will save space on your drive, however can be disabled if you wish to keep KDKs installed.")) - - - # Set Ignore App Updates - self.set_ignore_app_updates_checkbox = wx.CheckBox(self.frame_modal, label="Ignore App Updates") - self.set_ignore_app_updates_checkbox.SetValue(self.constants.ignore_updates) - self.set_ignore_app_updates_checkbox.Bind(wx.EVT_CHECKBOX, self.set_ignore_app_updates_click) - self.set_ignore_app_updates_checkbox.SetPosition(wx.Point( - self.delete_unused_kdks_checkbox.GetPosition().x, - self.delete_unused_kdks_checkbox.GetPosition().y + self.delete_unused_kdks_checkbox.GetSize().height)) - self.set_ignore_app_updates_checkbox.SetToolTip(wx.ToolTip("This will set whether OpenCore will ignore App Updates on launch.\nEnable this option if you do not want to be prompted for App Updates")) - - # Set Disable Analytics - res = global_settings.GlobalEnviromentSettings().read_property("DisableCrashAndAnalyticsReporting") - res = False if res is None else res - self.set_disable_analytics_checkbox = wx.CheckBox(self.frame_modal, label="Disable Crash/Analytics") - self.set_disable_analytics_checkbox.SetValue(res) - self.set_disable_analytics_checkbox.Bind(wx.EVT_CHECKBOX, self.set_disable_analytics_click) - self.set_disable_analytics_checkbox.SetPosition(wx.Point( - self.set_ignore_app_updates_checkbox.GetPosition().x, - self.set_ignore_app_updates_checkbox.GetPosition().y + self.set_ignore_app_updates_checkbox.GetSize().height)) - self.set_disable_analytics_checkbox.SetToolTip(wx.ToolTip("Sets whether anonymized analytics are sent to the Dortania team.\nThis is used to help improve the application and is completely optional.")) - - # Button: Developer Debug Info - self.debug_button = wx.Button(self.frame_modal, label="Developer Debug Info") - self.debug_button.Bind(wx.EVT_BUTTON, self.additional_info_menu) - self.debug_button.SetPosition(wx.Point( - self.set_disable_analytics_checkbox.GetPosition().x, - self.set_disable_analytics_checkbox.GetPosition().y + self.set_disable_analytics_checkbox.GetSize().height + 5)) - self.debug_button.Center(wx.HORIZONTAL) - - # Button: return to main menu - self.return_to_main_menu_button = wx.Button(self.frame_modal, label="Return to Settings") - self.return_to_main_menu_button.Bind(wx.EVT_BUTTON, self.settings_menu) - self.return_to_main_menu_button.SetPosition(wx.Point( - self.debug_button.GetPosition().x, - self.debug_button.GetPosition().y + self.debug_button.GetSize().height + 10)) - self.return_to_main_menu_button.Center(wx.HORIZONTAL) - - # set frame_modal size below return to main menu button - - self.frame_modal.SetSize(wx.Size(-1, self.return_to_main_menu_button.GetPosition().y + self.return_to_main_menu_button.GetSize().height + 40)) - self.frame_modal.ShowWindowModal() - - def timeout_spinner_click(self, event): - self.constants.oc_timeout = self.timeout_spinner.GetValue() - - def delete_unused_kdks_click(self, event): - if self.delete_unused_kdks_checkbox.GetValue() is True: - logging.info("Nuke KDKs enabled") - self.constants.should_nuke_kdks = True - else: - logging.info("Nuke KDKs disabled") - self.constants.should_nuke_kdks = False - global_settings.GlobalEnviromentSettings().write_property("ShouldNukeKDKs", self.constants.should_nuke_kdks) - - def disable_library_validation_click(self, event): - if self.disable_library_validation_checkbox.GetValue(): - logging.info("Disable Library Validation") - self.disable_amfi_checkbox.Enable() - self.constants.disable_cs_lv = True - else: - logging.info("Enable Library Validation") - self.disable_amfi_checkbox.Disable() - self.constants.disable_cs_lv = False - - def disable_amfi_click(self, event): - if self.disable_amfi_checkbox.GetValue(): - logging.info("Disable AMFI") - self.constants.disable_amfi = True - else: - logging.info("Enable AMFI") - self.constants.disable_amfi = False - - def set_ignore_app_updates_click(self, event): - self.constants.ignore_updates = self.set_ignore_app_updates_checkbox.GetValue() - if self.constants.ignore_updates is True: - global_settings.GlobalEnviromentSettings().write_property("IgnoreAppUpdates", True) - else: - global_settings.GlobalEnviromentSettings().write_property("IgnoreAppUpdates", False) - - def set_disable_analytics_click(self, event): - global_settings.GlobalEnviromentSettings().write_property("DisableCrashAndAnalyticsReporting", self.set_disable_analytics_checkbox.GetValue()) - - def firewire_click(self, event=None): - if self.firewire_boot_checkbox.GetValue(): - logging.info("Firewire Enabled") - self.constants.firewire_boot = True - else: - logging.info("Firewire Disabled") - self.constants.firewire_boot = False - - def nvme_click(self, event=None): - if self.nvme_boot_checkbox.GetValue(): - logging.info("NVMe Enabled") - self.constants.nvme_boot = True - else: - logging.info("NVMe Disabled") - self.constants.nvme_boot = False - - def nvme_power_management_click(self, event=None): - if self.nvme_power_management_checkbox.GetValue(): - logging.info("NVMe Power Management Enabled") - self.constants.allow_nvme_fixing = True - else: - logging.info("NVMe Power Management Disabled") - self.constants.allow_nvme_fixing = False - - def xhci_click(self, event=None): - if self.xhci_boot_checkbox.GetValue(): - logging.info("XHCI Enabled") - self.constants.xhci_boot = True - else: - logging.info("XHCI Disabled") - self.constants.xhci_boot = False - - def wake_on_wlan_click(self, event=None): - if self.wake_on_wlan_checkbox.GetValue(): - logging.info("Wake on WLAN Enabled") - self.constants.enable_wake_on_wlan = True - else: - logging.info("Wake on WLAN Disabled") - self.constants.enable_wake_on_wlan = False - - def apfs_trim_click(self, event=None): - if self.apfs_trim_checkbox.GetValue(): - logging.info("APFS Trim Enabled") - self.constants.apfs_trim_timeout = True - else: - logging.info("APFS Trim Disabled") - self.constants.apfs_trim_timeout = False - - def content_caching_click(self, event=None): - if self.content_caching_checkbox.GetValue(): - logging.info("Content Caching Enabled") - self.constants.set_content_caching = True - else: - logging.info("Content Caching Disabled") - self.constants.set_content_caching = False - - def amd_gop_injection_checkbox_click(self, event=None): - if self.set_amd_gop_injection.GetValue(): - logging.info("AMD GOP Injection Enabled") - self.constants.amd_gop_injection = True - else: - logging.info("AMD GOP Injection Disabled") - self.constants.amd_gop_injection = False - - def nvidia_kepler_gop_injection_checkbox_click(self, event=None): - if self.set_nvidia_kepler_gop_injection.GetValue(): - logging.info("Nvidia Kepler GOP Injection Enabled") - self.constants.nvidia_kepler_gop_injection = True - else: - logging.info("Nvidia Kepler GOP Injection Disabled") - self.constants.nvidia_kepler_gop_injection = False - - def disable_tb_click(self, event=None): - if self.disable_thunderbolt_checkbox.GetValue(): - logging.info("Disable Thunderbolt Enabled") - self.constants.disable_tb = True - else: - logging.info("Disable Thunderbolt Disabled") - self.constants.disable_tb = False - - def ts2_accel_click(self, event=None): - if self.set_terascale_accel_checkbox.GetValue(): - logging.info("TS2 Acceleration Enabled") - global_settings.GlobalEnviromentSettings().write_property("MacBookPro_TeraScale_2_Accel", True) - self.constants.allow_ts2_accel = True - else: - logging.info("TS2 Acceleration Disabled") - global_settings.GlobalEnviromentSettings().write_property("MacBookPro_TeraScale_2_Accel", False) - self.constants.allow_ts2_accel = False - - def disable_colorsync_click(self, event=None): - if self.set_colorsync_checkbox.GetValue(): - logging.info("ColorSync Patch Disabled") - global_settings.GlobalEnviromentSettings().write_property("Disable_ColorSync_Downgrade", True) - self.constants.disable_cat_colorsync = True - else: - logging.info("ColorSync Patch Enabled") - global_settings.GlobalEnviromentSettings().write_property("Disable_ColorSync_Downgrade", False) - self.constants.disable_cat_colorsync = False - - def force_web_drivers_click(self, event=None): - if self.force_web_drivers_checkbox.GetValue(): - logging.info("Force Web Drivers Enabled") - global_settings.GlobalEnviromentSettings().write_property("Force_Web_Drivers", True) - self.constants.force_nv_web = True - else: - logging.info("Force Web Drivers Disabled") - global_settings.GlobalEnviromentSettings().write_property("Force_Web_Drivers", False) - self.constants.force_nv_web = False - - def windows_gmux_click(self, event=None): - if self.windows_gmux_checkbox.GetValue(): - logging.info("Windows GMUX Enabled") - self.constants.dGPU_switch = True - else: - logging.info("Windows GMUX Disabled") - self.constants.dGPU_switch = False - - def hibernation_click(self, event=None): - if self.hibernation_checkbox.GetValue(): - logging.info("Hibernation Enabled") - self.constants.disable_connectdrivers = True - else: - logging.info("Hibernation Disabled") - self.constants.disable_connectdrivers = False - - def disable_battery_throttling_click(self, event=None): - if self.disable_battery_throttling_checkbox.GetValue(): - logging.info("Disable Battery Throttling Enabled") - self.constants.disable_msr_power_ctl = True - else: - logging.info("Disable Battery Throttling Disabled") - self.constants.disable_msr_power_ctl = False - - def disable_xcpm_click(self, event=None): - if self.disable_xcpm_checkbox.GetValue(): - logging.info("Disable XCPM Enabled") - self.constants.disable_xcpm = True - else: - logging.info("Disable XCPM Disabled") - self.constants.disable_xcpm = False - - def software_demux_click(self, event=None): - if self.software_demux_checkbox.GetValue(): - logging.info("Software Demux Enabled") - self.constants.software_demux = True - else: - logging.info("Software Demux Disabled") - self.constants.software_demux = False - - def disable_cpu_friend_click(self, event=None): - if self.disable_cpu_friend_checkbox.GetValue(): - logging.info("Disable CPUFriend Enabled") - self.constants.disallow_cpufriend = True - else: - logging.info("Disable CPUFriend Disabled") - self.constants.disallow_cpufriend = False - - def apple_alc_click(self, event=None): - if self.apple_alc_checkbox.GetValue(): - logging.info("AppleALC Usage Enabled") - self.constants.set_alc_usage = True - else: - logging.info("AppleALC Usage Disabled") - self.constants.set_alc_usage = False - - def set_enhanced_3rd_party_ssd_click(self, event=None): - if self.set_enhanced_3rd_party_ssd_checkbox.GetValue(): - logging.info("Enhanced 3rd Party SSDs Enabled") - self.constants.allow_3rd_party_drives = True - else: - logging.info("Enhanced 3rd Party SSDs Disabled") - self.constants.allow_3rd_party_drives = False - - def gpu_selection_click(self, event=None): - gpu_choice = self.gpu_dropdown.GetStringSelection() - logging.info(f"GPU Selection: {gpu_choice}") - if "AMD" in gpu_choice: - self.constants.imac_vendor = "AMD" - self.constants.metal_build = True - if "Polaris" in gpu_choice: - self.constants.imac_model = "Polaris" - elif "GCN" in gpu_choice: - self.constants.imac_model = "Legacy GCN" - elif "Lexa" in gpu_choice: - self.constants.imac_model = "AMD Lexa" - elif "Navi" in gpu_choice: - self.constants.imac_model = "AMD Navi" - else: - raise Exception("Unknown GPU Model") - elif "Nvidia" in gpu_choice: - self.constants.imac_vendor = "Nvidia" - self.constants.metal_build = True - if "Kepler" in gpu_choice: - self.constants.imac_model = "Kepler" - elif "GT" in gpu_choice: - self.constants.imac_model = "GT" - else: - raise Exception("Unknown GPU Model") - else: - self.constants.imac_vendor = "None" - self.constants.metal_build = False - - logging.info(f"GPU Vendor: {self.constants.imac_vendor}") - logging.info(f"GPU Model: {self.constants.imac_model}") - - def fu_selection_click(self, event=None): - fu_choice = self.feature_unlock_dropdown.GetStringSelection() - if fu_choice == "Enabled": - self.constants.fu_status = True - self.constants.fu_arguments = None - elif fu_choice == "Partially enabled (No AirPlay/SideCar)": - self.constants.fu_status = True - self.constants.fu_arguments = " -disable_sidecar_mac" - else: - self.constants.fu_status = False - self.constants.fu_arguments = None - - def set_writeflash_click(self, event=None): - if self.set_writeflash_checkbox.GetValue(): - logging.info("Write Flash Enabled") - self.constants.nvram_write = True - else: - logging.info("Write Flash Disabled") - self.constants.nvram_write = False - - def smbios_settings_menu(self, event=None): - self.reset_frame_modal() - - # Header: SMBIOS Settings - self.smbios_settings_header = wx.StaticText(self.frame_modal, label="SMBIOS Settings", pos=wx.Point(10, 10)) - self.smbios_settings_header.SetFont(wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.smbios_settings_header.Center(wx.HORIZONTAL) - - # Label: SMBIOS Spoof Level - self.smbios_spoof_level_label = wx.StaticText(self.frame_modal, label="SMBIOS Spoof Level") - self.smbios_spoof_level_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.smbios_spoof_level_label.SetPosition( - wx.Point(self.smbios_settings_header.GetPosition().x, self.smbios_settings_header.GetPosition().y + self.smbios_settings_header.GetSize().height + 10) - ) - self.smbios_spoof_level_label.Center(wx.HORIZONTAL) - - # Dropdown: SMBIOS Spoof Level - self.smbios_dropdown = wx.Choice(self.frame_modal) - self.smbios_dropdown.SetPosition( - wx.Point(self.smbios_spoof_level_label.GetPosition().x, self.smbios_spoof_level_label.GetPosition().y + self.smbios_spoof_level_label.GetSize().height + 10) - ) - self.smbios_dropdown.AppendItems(["None", "Minimal", "Moderate", "Advanced"]) - self.smbios_dropdown.SetStringSelection(self.constants.serial_settings) - self.smbios_dropdown.Bind(wx.EVT_CHOICE, self.smbios_spoof_level_click) - self.smbios_dropdown.Center(wx.HORIZONTAL) - - # Label: SMBIOS Spoof Model - self.smbios_spoof_model_label = wx.StaticText(self.frame_modal, label="SMBIOS Spoof Model") - self.smbios_spoof_model_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.smbios_spoof_model_label.SetPosition( - wx.Point(self.smbios_dropdown.GetPosition().x, self.smbios_dropdown.GetPosition().y + self.smbios_dropdown.GetSize().height + 10) - ) - self.smbios_spoof_model_label.Center(wx.HORIZONTAL) - - # Dropdown: SMBIOS Spoof Model - self.smbios_model_dropdown = wx.Choice(self.frame_modal) - self.smbios_model_dropdown.SetPosition( - wx.Point(self.smbios_spoof_model_label.GetPosition().x, self.smbios_spoof_model_label.GetPosition().y + self.smbios_spoof_model_label.GetSize().height + 10) - ) - for model in smbios_data.smbios_dictionary: - if "_" not in model and " " not in model: - if smbios_data.smbios_dictionary[model]["Board ID"] is not None: - self.smbios_model_dropdown.Append(model) - self.smbios_model_dropdown.Append("Default") - self.smbios_model_dropdown.SetStringSelection(self.constants.override_smbios) - self.smbios_model_dropdown.Bind(wx.EVT_CHOICE, self.smbios_model_click) - self.smbios_model_dropdown.Center(wx.HORIZONTAL) - if self.smbios_dropdown.GetStringSelection() == "None": - self.smbios_model_dropdown.Disable() - - # Label: Custom Serial Number - self.smbios_serial_label = wx.StaticText(self.frame_modal, label="Custom Serial Number") - self.smbios_serial_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.smbios_serial_label.SetPosition( - wx.Point(self.smbios_model_dropdown.GetPosition().x, self.smbios_model_dropdown.GetPosition().y + self.smbios_model_dropdown.GetSize().height + 10) - ) - self.smbios_serial_label.Center(wx.HORIZONTAL) - - # Textbox: Custom Serial Number - self.smbios_serial_textbox = wx.TextCtrl(self.frame_modal, style=wx.TE_CENTRE) - self.smbios_serial_textbox.SetPosition( - wx.Point(self.smbios_serial_label.GetPosition().x, self.smbios_serial_label.GetPosition().y + self.smbios_serial_label.GetSize().height + 5) - ) - self.smbios_serial_textbox.SetValue(self.constants.custom_serial_number) - self.smbios_serial_textbox.SetSize(wx.Size(200, -1)) - self.smbios_serial_textbox.Bind(wx.EVT_TEXT, self.smbios_serial_click) - self.smbios_serial_textbox.Center(wx.HORIZONTAL) - - # Label: Custom Board Serial Number - self.smbios_board_serial_label = wx.StaticText(self.frame_modal, label="Custom Board Serial Number") - self.smbios_board_serial_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.smbios_board_serial_label.SetPosition( - wx.Point(self.smbios_serial_textbox.GetPosition().x, self.smbios_serial_textbox.GetPosition().y + self.smbios_serial_textbox.GetSize().height + 10) - ) - self.smbios_board_serial_label.Center(wx.HORIZONTAL) - - # Textbox: Custom Board Serial Number - self.smbios_board_serial_textbox = wx.TextCtrl(self.frame_modal, style=wx.TE_CENTRE) - self.smbios_board_serial_textbox.SetPosition( - wx.Point(self.smbios_board_serial_label.GetPosition().x, self.smbios_board_serial_label.GetPosition().y + self.smbios_board_serial_label.GetSize().height + 5) - ) - self.smbios_board_serial_textbox.SetValue(self.constants.custom_board_serial_number) - self.smbios_board_serial_textbox.SetSize(wx.Size(200, -1)) - self.smbios_board_serial_textbox.Bind(wx.EVT_TEXT, self.smbios_board_serial_click) - self.smbios_board_serial_textbox.Center(wx.HORIZONTAL) - - # Button: Generate new serials - self.smbios_generate_button = wx.Button(self.frame_modal, label=f"Generate S/N: {self.constants.custom_model or self.computer.real_model}") - self.smbios_generate_button.SetPosition( - wx.Point(self.smbios_board_serial_textbox.GetPosition().x, self.smbios_board_serial_textbox.GetPosition().y + self.smbios_board_serial_textbox.GetSize().height + 10) - ) - self.smbios_generate_button.Center(wx.HORIZONTAL) - self.smbios_generate_button.Bind(wx.EVT_BUTTON, self.generate_new_serials_clicked) - - if self.constants.allow_oc_everywhere is False and \ - self.constants.custom_model is None and \ - self.computer.real_model not in model_array.SupportedSMBIOS: - self.smbios_board_serial_textbox.Disable() - self.smbios_serial_textbox.Disable() - self.smbios_generate_button.Disable() - - # Checkbox: Allow Native Spoofs - self.native_spoof_checkbox = wx.CheckBox(self.frame_modal, label="Allow Native Spoofs") - self.native_spoof_checkbox.SetValue(self.constants.allow_native_spoofs) - self.native_spoof_checkbox.SetPosition( - wx.Point(self.smbios_generate_button.GetPosition().x, self.smbios_generate_button.GetPosition().y + self.smbios_generate_button.GetSize().height + 10) - ) - self.native_spoof_checkbox.Bind(wx.EVT_CHECKBOX, self.native_spoof_click) - self.native_spoof_checkbox.Center(wx.HORIZONTAL) - self.native_spoof_checkbox.SetToolTip(wx.ToolTip("For native systems that cannot update their firmware, this option will allow OCLP to spoof the SMBIOS.")) - if self.constants.allow_oc_everywhere is False: - self.native_spoof_checkbox.Disable() - - # Button: Return to Main Menu - self.return_to_main_menu_button = wx.Button(self.frame_modal, label="Return to Settings") - self.return_to_main_menu_button.SetPosition( - wx.Point(self.native_spoof_checkbox.GetPosition().x, self.native_spoof_checkbox.GetPosition().y + self.native_spoof_checkbox.GetSize().height + 10) - ) - self.return_to_main_menu_button.Bind(wx.EVT_BUTTON, self.settings_menu) - self.return_to_main_menu_button.Center(wx.HORIZONTAL) - - self.frame_modal.SetSize(wx.Size(-1, self.return_to_main_menu_button.GetPosition().y + self.return_to_main_menu_button.GetSize().height + 40)) - self.frame_modal.ShowWindowModal() - - def smbios_serial_click(self, event): - self.constants.custom_serial_number = self.smbios_serial_textbox.GetValue() - - def smbios_board_serial_click(self, event): - self.constants.custom_board_serial_number = self.smbios_board_serial_textbox.GetValue() - - def generate_new_serials_clicked(self, event): - # Throw pop up warning about misusing this feature - dlg = wx.MessageDialog(self.frame_modal, "Please take caution when using serial spoofing. This should only be used on machines that were legally obtained and require reserialization.\n\nNote: new serials are only overlayed through OpenCore and are not permanently installed into ROM.\n\nMisuse of this setting can break power management and other aspects of the OS if the system does not need spoofing\n\nDortania does not condone the use of our software on stolen devices.\n\nAre you certain you want to continue?", "Warning", wx.YES_NO | wx.ICON_WARNING) - if dlg.ShowModal() == wx.ID_NO: - return - macserial_output = subprocess.run([self.constants.macserial_path] + f"-g -m {self.constants.custom_model or self.computer.real_model} -n 1".split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - macserial_output = macserial_output.stdout.decode().strip().split(" | ") - if len(macserial_output) == 2: - self.smbios_serial_textbox.SetValue(macserial_output[0]) - self.smbios_board_serial_textbox.SetValue(macserial_output[1]) - else: - self.smbios_serial_textbox.SetHint("Unable to generate serials") - self.smbios_board_serial_textbox.SetHint("Unable to generate serials") - - def native_spoof_click(self, event): - if self.native_spoof_checkbox.GetValue(): - logging.info("Allow Native Spoofs Enabled") - self.constants.allow_native_spoofs = True - else: - logging.info("Allow Native Spoofs Disabled") - self.constants.allow_native_spoofs = False - - def smbios_spoof_level_click(self, event=None): - # Throw pop up warning about misusing this feature - selection = self.smbios_dropdown.GetStringSelection() - if selection != "None": - dlg = wx.MessageDialog(self.frame_modal, "This option should only be used when you need to change the machine's SMBIOS data.\n\nMisuse of this option can break OS functionality. Only use if you absolutely understand the need for this setting\n\nAre you certain you want to continue?", "Warning", wx.YES_NO | wx.ICON_WARNING) - if dlg.ShowModal() == wx.ID_NO: - self.smbios_dropdown.SetStringSelection(self.constants.serial_settings) - return - logging.info(f"SMBIOS Spoof Level: {selection}") - self.constants.serial_settings = selection - if selection == "None": - self.smbios_model_dropdown.Disable() - else: - self.smbios_model_dropdown.Enable() - - def smbios_model_click(self, event=None): - selection = self.smbios_model_dropdown.GetStringSelection() - logging.info(f"SMBIOS Spoof Model: {selection}") - self.constants.override_smbios = selection - - def additional_info_menu(self, event=None): - self.reset_frame_modal() - self.frame_modal.SetSize(wx.Size(500, -1)) - - # Header: Additional Info - self.additional_info_header = wx.StaticText(self.frame_modal, label="Developer Debug Info", pos=wx.Point(10, 10)) - self.additional_info_header.SetFont(wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.additional_info_header.Center(wx.HORIZONTAL) - - # Label: Real User ID - self.real_user_id_label = wx.StaticText(self.frame_modal, label=f"Current UID: {os.getuid()} - ({os.geteuid()})") - self.real_user_id_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.real_user_id_label.SetPosition( - wx.Point(self.additional_info_header.GetPosition().x, self.additional_info_header.GetPosition().y + self.additional_info_header.GetSize().height + 10) - ) - self.real_user_id_label.Center(wx.HORIZONTAL) - - - commit_dict = self.constants.commit_info - # Label: Built from Branch: - self.built_from_branch_label = wx.StaticText(self.frame_modal, label=f"Branch: {commit_dict[0]}") - self.built_from_branch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.built_from_branch_label.SetPosition( - wx.Point(self.real_user_id_label.GetPosition().x, self.real_user_id_label.GetPosition().y + self.real_user_id_label.GetSize().height + 10) - ) - self.built_from_branch_label.Center(wx.HORIZONTAL) - - # Label: Built on: (Date) - self.built_on_label = wx.StaticText(self.frame_modal, label=f"Date: {commit_dict[1]}") - self.built_on_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.built_on_label.SetPosition( - wx.Point(self.built_from_branch_label.GetPosition().x, self.built_from_branch_label.GetPosition().y + self.built_from_branch_label.GetSize().height + 10) - ) - - # Label: Commit URL: (hyperlink) - self.commit_url_label = wx.StaticText(self.frame_modal, label=f"URL: ") - self.commit_url_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.commit_url_label.SetPosition( - wx.Point(self.built_on_label.GetPosition().x, self.built_on_label.GetPosition().y + self.built_on_label.GetSize().height + 10) - ) - - # Hyperlink to the right of commit_url_label - if commit_dict[2] != "": - self.commit_url_hyperlink = hyperlink.HyperLinkCtrl(self.frame_modal, id=wx.ID_ANY, label=f"Link", URL=f"{commit_dict[2]}") - self.commit_url_hyperlink.SetPosition( - wx.Point(self.commit_url_label.GetPosition().x + self.commit_url_label.GetSize().width, self.commit_url_label.GetPosition().y) - ) - self.commit_url_hyperlink.SetForegroundColour(self.hyperlink_colour) - self.commit_url_hyperlink.SetColours( - link=self.hyperlink_colour, - visited=self.hyperlink_colour, - rollover=self.hyperlink_colour, - ) - - else: - self.commit_url_label.Label = f"URL: Not applicable" - - # Label: Model Dump - self.model_dump_label = wx.StaticText(self.frame_modal, label="Model Dump") - self.model_dump_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.model_dump_label.SetPosition( - wx.Point(self.commit_url_label.GetPosition().x, self.commit_url_label.GetPosition().y + self.commit_url_label.GetSize().height + 10) - ) - self.model_dump_label.Center(wx.HORIZONTAL) - - # Textbox: Model Dump - self.model_dump_textbox = wx.TextCtrl(self.frame_modal, style=wx.TE_MULTILINE, pos=wx.Point(self.model_dump_label.GetPosition().x, self.model_dump_label.GetPosition().y + self.model_dump_label.GetSize().height + 10)) - self.model_dump_textbox.SetValue(str(self.constants.computer)) - self.model_dump_textbox.SetPosition( - wx.Point(self.model_dump_label.GetPosition().x, self.model_dump_label.GetPosition().y + self.model_dump_label.GetSize().height + 10) - ) - self.model_dump_textbox.SetSize( - wx.Size( - self.frame_modal.GetSize().width - 5, - self.model_dump_textbox.GetSize().height + self.model_dump_textbox.GetSize().height - ) - ) - self.model_dump_textbox.Center(wx.HORIZONTAL) - self.model_dump_textbox.SetEditable(False) - - - - # Label: Launcher Binary - self.launcher_binary_label = wx.StaticText(self.frame_modal, label="Launcher Binary") - self.launcher_binary_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.launcher_binary_label.SetPosition( - wx.Point(self.model_dump_textbox.GetPosition().x, self.model_dump_textbox.GetPosition().y + self.model_dump_textbox.GetSize().height + 10) - ) - self.launcher_binary_label.Center(wx.HORIZONTAL) - - # Textbox: Launcher Binary - self.launcher_binary_textbox = wx.TextCtrl(self.frame_modal, style=wx.TE_MULTILINE, pos=wx.Point(self.launcher_binary_label.GetPosition().x, self.launcher_binary_label.GetPosition().y + self.launcher_binary_label.GetSize().height + 10)) - self.launcher_binary_textbox.SetValue(self.constants.launcher_binary) - self.launcher_binary_textbox.SetPosition( - wx.Point(self.launcher_binary_label.GetPosition().x, self.launcher_binary_label.GetPosition().y + self.launcher_binary_label.GetSize().height + 10) - ) - self.launcher_binary_textbox.SetSize(wx.Size(self.frame_modal.GetSize().width - 5, 50)) - self.launcher_binary_textbox.Center(wx.HORIZONTAL) - self.launcher_binary_textbox.SetEditable(False) - - # Label: Launcher Script - self.launcher_script_label = wx.StaticText(self.frame_modal, label="Payload Location") - self.launcher_script_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.launcher_script_label.SetPosition( - wx.Point(self.launcher_binary_textbox.GetPosition().x, self.launcher_binary_textbox.GetPosition().y + self.launcher_binary_textbox.GetSize().height + 10) - ) - self.launcher_script_label.Center(wx.HORIZONTAL) - - # Textbox: Launcher Script - self.launcher_script_textbox = wx.TextCtrl(self.frame_modal, style=wx.TE_MULTILINE, pos=wx.Point(self.launcher_script_label.GetPosition().x, self.launcher_script_label.GetPosition().y + self.launcher_script_label.GetSize().height + 10)) - self.launcher_script_textbox.SetValue(str(self.constants.payload_path)) - self.launcher_script_textbox.SetPosition( - wx.Point(self.launcher_script_label.GetPosition().x, self.launcher_script_label.GetPosition().y + self.launcher_script_label.GetSize().height + 10) - ) - self.launcher_script_textbox.SetSize(wx.Size(self.frame_modal.GetSize().width - 5, 60)) - self.launcher_script_textbox.Center(wx.HORIZONTAL) - self.launcher_script_textbox.SetEditable(False) - - self.return_to_main_menu_button = wx.Button(self.frame_modal, label="Return to Settings") - self.return_to_main_menu_button.SetPosition( - wx.Point(self.launcher_script_textbox.GetPosition().x, self.launcher_script_textbox.GetPosition().y + self.launcher_script_textbox.GetSize().height + 10) - ) - self.return_to_main_menu_button.Bind(wx.EVT_BUTTON, self.settings_menu) - self.return_to_main_menu_button.Center(wx.HORIZONTAL) - - # Set frame_modal below return to main menu button - self.frame_modal.SetSize(wx.Size(-1, self.return_to_main_menu_button.GetPosition().y + self.return_to_main_menu_button.GetSize().height + 40)) - self.frame_modal.ShowWindowModal() - - - def sip_config_menu(self, event=None): - self.reset_frame_modal() - self.frame_modal.SetSize(wx.Size(400, 600)) - - # Title: Configure SIP - self.configure_sip_title = wx.StaticText(self.frame_modal, label="Configure SIP", pos=wx.Point(10, 10)) - self.configure_sip_title.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.configure_sip_title.Center(wx.HORIZONTAL) - - # Label: Flip individual bits corresponding to XNU's csr.h - # If you're unfamiliar with how SIP works, do not touch this menu - self.sip_label = wx.StaticText(self.frame_modal, label="Flip individual bits corresponding to") - self.sip_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.sip_label.SetPosition( - wx.Point(-1, self.configure_sip_title.GetPosition().y + self.configure_sip_title.GetSize().height + 10) - ) - self.sip_label.Center(wx.HORIZONTAL) - self.sip_label.SetPosition( - wx.Point(self.sip_label.GetPosition().x - 25, -1) - ) - - hyperlink_label = hyperlink.HyperLinkCtrl( - self.frame_modal, - -1, - "XNU's csr.h", - pos=(self.sip_label.GetPosition().x + self.sip_label.GetSize().width, self.sip_label.GetPosition().y), - URL="https://github.com/apple/darwin-xnu/blob/main/bsd/sys/csr.h", - ) - hyperlink_label.SetForegroundColour(self.hyperlink_colour) - hyperlink_label.SetColours( - link=self.hyperlink_colour, - visited=self.hyperlink_colour, - rollover=self.hyperlink_colour, - ) - - if self.constants.custom_sip_value is not None: - self.sip_value = int(self.constants.custom_sip_value, 16) - elif self.constants.sip_status is True: - self.sip_value = 0x00 - else: - self.sip_value = 0x803 - - self.sip_label_2 = wx.StaticText(self.frame_modal, label=f"Currently configured SIP: {hex(self.sip_value)}") - self.sip_label_2.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.sip_label_2.SetPosition( - wx.Point(self.sip_label.GetPosition().x, self.sip_label.GetPosition().y + self.sip_label.GetSize().height + 10) - ) - self.sip_label_2.Center(wx.HORIZONTAL) - - self.sip_label_2_2 = wx.StaticText(self.frame_modal, label=f"Currently Booted SIP: {hex(py_sip_xnu.SipXnu().get_sip_status().value)}") - self.sip_label_2_2.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.sip_label_2_2.SetPosition( - wx.Point(self.sip_label_2.GetPosition().x, self.sip_label_2.GetPosition().y + self.sip_label_2.GetSize().height + 5) - ) - self.sip_label_2_2.Center(wx.HORIZONTAL) - - self.sip_label_3 = wx.StaticText(self.frame_modal, label="For older Macs requiring root patching, we set SIP to\n be partially disabled (0x803) to allow root patching.") - self.sip_label_3.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.sip_label_3.SetPosition( - wx.Point(self.sip_label_2_2.GetPosition().x, self.sip_label_2_2.GetPosition().y + self.sip_label_2_2.GetSize().height + 10) - ) - self.sip_label_3.Center(wx.HORIZONTAL) - - self.sip_label_4 = wx.StaticText(self.frame_modal, label="This value (0x803) corresponds to the following bits in csr.h:") - self.sip_label_4.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.sip_label_4.SetPosition( - wx.Point(self.sip_label_3.GetPosition().x, self.sip_label_3.GetPosition().y + self.sip_label_3.GetSize().height + 5) - ) - self.sip_label_4.Center(wx.HORIZONTAL) - - self.sip_label_5 = wx.StaticText(self.frame_modal, label=" 0x1 - CSR_ALLOW_UNTRUSTED_KEXTS\n 0x2 - CSR_ALLOW_UNRESTRICTED_FS\n 0x800 - CSR_ALLOW_UNAUTHENTICATED_ROOT") - self.sip_label_5.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.sip_label_5.SetPosition( - wx.Point(self.sip_label_4.GetPosition().x, self.sip_label_4.GetPosition().y + self.sip_label_4.GetSize().height + 7) - ) - self.sip_label_5.Center(wx.HORIZONTAL) - - warning_string = """ -OpenCore Legacy Patcher by default knows the most ideal - SIP value for your system. Override this value only if you - understand the consequences. Reckless usage of this - menu can break your installation. -""" - self.sip_label_6 = wx.StaticText(self.frame_modal, label=warning_string) - self.sip_label_6.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.sip_label_6.SetPosition( - wx.Point(self.sip_label_5.GetPosition().x, self.sip_label_5.GetPosition().y + self.sip_label_5.GetSize().height - 10) - ) - self.sip_label_6.Center(wx.HORIZONTAL) - - i = -10 - for sip_bit in sip_data.system_integrity_protection.csr_values_extended: - self.sip_checkbox = wx.CheckBox(self.frame_modal, label=sip_data.system_integrity_protection.csr_values_extended[sip_bit]["name"]) - self.sip_checkbox.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.sip_checkbox.SetToolTip(f'Description: {sip_data.system_integrity_protection.csr_values_extended[sip_bit]["description"]}\nValue: {hex(sip_data.system_integrity_protection.csr_values_extended[sip_bit]["value"])}\nIntroduced in: macOS {sip_data.system_integrity_protection.csr_values_extended[sip_bit]["introduced_friendly"]}') - self.sip_checkbox.SetPosition( - wx.Point(70, self.sip_label_6.GetPosition().y + self.sip_label_6.GetSize().height + i) - ) - i = i + 20 - self.sip_checkbox.Bind(wx.EVT_CHECKBOX, self.update_sip_value) - if self.sip_value & sip_data.system_integrity_protection.csr_values_extended[sip_bit]["value"] == sip_data.system_integrity_protection.csr_values_extended[sip_bit]["value"]: - self.sip_checkbox.SetValue(True) - - # Button: returns to the main menu - self.return_to_main_menu_button = wx.Button(self.frame_modal, label="Return to Settings") - self.return_to_main_menu_button.SetPosition( - wx.Point(self.sip_checkbox.GetPosition().x, self.sip_checkbox.GetPosition().y + self.sip_checkbox.GetSize().height + 15) - ) - self.return_to_main_menu_button.Bind(wx.EVT_BUTTON, self.settings_menu) - self.return_to_main_menu_button.Center(wx.HORIZONTAL) - - # Set the frame_modal size - self.frame_modal.SetSize(wx.Size(-1, self.return_to_main_menu_button.GetPosition().y + self.return_to_main_menu_button.GetSize().height + 40)) - self.frame_modal.ShowWindowModal() - - def update_sip_value(self, event): - dict = sip_data.system_integrity_protection.csr_values_extended[event.GetEventObject().GetLabel()] - if event.GetEventObject().GetValue() is True: - self.sip_value = self.sip_value + dict["value"] - else: - self.sip_value = self.sip_value - dict["value"] - if hex(self.sip_value) == "0x0": - self.constants.custom_sip_value = None - self.constants.sip_status = True - elif hex(self.sip_value) == "0x803": - self.constants.custom_sip_value = None - self.constants.sip_status = False - else: - self.constants.custom_sip_value = hex(self.sip_value) - self.sip_label_2.SetLabel(f"Currently configured SIP: {hex(self.sip_value)}") - self.sip_label_2.Center(wx.HORIZONTAL) - - def misc_settings_menu(self, event): - self.reset_frame_modal() - - # Header - self.header = wx.StaticText(self.frame_modal, label="Misc Settings", style=wx.ALIGN_CENTRE, pos=wx.Point(10, 10)) - self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header.SetPosition(wx.Point(0, 10)) - self.header.SetSize(wx.Size(self.frame_modal.GetSize().width, 30)) - self.header.Centre(wx.HORIZONTAL) - - # Subheader: If unfamiliar with the following settings, please do not change them. - self.subheader = wx.StaticText(self.frame_modal, label="Configure settings", style=wx.ALIGN_CENTRE) - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader.SetPosition(wx.Point(0, self.header.GetPosition().y + self.header.GetSize().height)) - self.subheader.SetSize(wx.Size(self.frame_modal.GetSize().width, 30)) - self.subheader.Centre(wx.HORIZONTAL) - # Subheader: , hover over options more info - self.subheader_2 = wx.StaticText(self.frame_modal, label="Hover over options for more info", style=wx.ALIGN_CENTRE) - self.subheader_2.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader_2.SetPosition(wx.Point(0, self.subheader.GetPosition().y + self.subheader.GetSize().height - 15)) - self.subheader_2.SetSize(wx.Size(self.frame_modal.GetSize().width, 30)) - self.subheader_2.Centre(wx.HORIZONTAL) - - # Label: Set FeatureUnlock status - self.feature_unlock_label = wx.StaticText(self.frame_modal, label="Feature Unlock Status:", style=wx.ALIGN_CENTRE) - self.feature_unlock_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.feature_unlock_label.SetPosition(wx.Point(0, self.subheader_2.GetPosition().y + self.subheader_2.GetSize().height -5)) - self.feature_unlock_label.Centre(wx.HORIZONTAL) - - # Dropdown: Set Feature Unlock status - self.feature_unlock_dropdown = wx.Choice(self.frame_modal) - for entry in ["Enabled", "Partially enabled (No AirPlay/SideCar)", "Disabled"]: - self.feature_unlock_dropdown.Append(entry) - self.feature_unlock_dropdown.SetPosition(wx.Point(0, self.feature_unlock_label.GetPosition().y + self.feature_unlock_label.GetSize().height + 5)) - if self.constants.fu_status is True: - if self.constants.fu_arguments is None: - selection = 0 - else: - selection = 1 - else: - selection = 2 - self.feature_unlock_dropdown.SetSelection(selection) - self.feature_unlock_dropdown.Bind(wx.EVT_CHOICE, self.fu_selection_click) - self.feature_unlock_dropdown.Centre(wx.HORIZONTAL) - self.feature_unlock_dropdown.SetToolTip(wx.ToolTip("Set FeatureUnlock support level\nFor systems experiencing memory instability, lowering this option to disable AirPlay/Sidecar patch sets is recommended.\nFully enabling this option will unlock AirPlay to Mac and Sidecar support")) - - # FireWire Boot - self.firewire_boot_checkbox = wx.CheckBox(self.frame_modal, label="FireWire Boot") - self.firewire_boot_checkbox.SetValue(self.constants.firewire_boot) - self.firewire_boot_checkbox.Bind(wx.EVT_CHECKBOX, self.firewire_click) - self.firewire_boot_checkbox.SetPosition(wx.Point(50, self.feature_unlock_dropdown.GetPosition().y + self.feature_unlock_dropdown.GetSize().height + 5)) - self.firewire_boot_checkbox.SetToolTip(wx.ToolTip("Enable FireWire Boot support in macOS 10.15 and newer.\nMainly applicable for Macs with FireWire or Thunderbolt to FireWire adapters")) - if generate_smbios.check_firewire(self.computer.real_model) is False and not self.constants.custom_model: - self.firewire_boot_checkbox.Disable() - - # XHCI Boot - self.xhci_boot_checkbox = wx.CheckBox(self.frame_modal, label="XHCI Boot") - self.xhci_boot_checkbox.SetValue(self.constants.xhci_boot) - self.xhci_boot_checkbox.Bind(wx.EVT_CHECKBOX, self.xhci_click) - self.xhci_boot_checkbox.SetPosition(wx.Point(self.firewire_boot_checkbox.GetPosition().x, self.firewire_boot_checkbox.GetPosition().y + self.firewire_boot_checkbox.GetSize().height)) - self.xhci_boot_checkbox.SetToolTip(wx.ToolTip("Enables XHCI/USB3.o support in UEFI for non-native systems (ie. pre-Ivy Bridge)\nRequires OpenCore to be stored on a natively bootable volume however")) - - # NVMe Boot - self.nvme_boot_checkbox = wx.CheckBox(self.frame_modal, label="NVMe Boot") - self.nvme_boot_checkbox.SetValue(self.constants.nvme_boot) - self.nvme_boot_checkbox.Bind(wx.EVT_CHECKBOX, self.nvme_click) - self.nvme_boot_checkbox.SetPosition(wx.Point(self.xhci_boot_checkbox.GetPosition().x, self.xhci_boot_checkbox.GetPosition().y + self.xhci_boot_checkbox.GetSize().height)) - self.nvme_boot_checkbox.SetToolTip(wx.ToolTip("Enables NVMe support in UEFI for non-native systems (ie. MacPro3,1)\nRequires OpenCore to be stored on a natively bootable volume however")) - - # NVMe Power Management - self.nvme_power_management_checkbox = wx.CheckBox(self.frame_modal, label="NVMe Power Management") - self.nvme_power_management_checkbox.SetValue(self.constants.allow_nvme_fixing) - self.nvme_power_management_checkbox.Bind(wx.EVT_CHECKBOX, self.nvme_power_management_click) - self.nvme_power_management_checkbox.SetPosition(wx.Point(self.nvme_boot_checkbox.GetPosition().x, self.nvme_boot_checkbox.GetPosition().y + self.nvme_boot_checkbox.GetSize().height)) - self.nvme_power_management_checkbox.SetToolTip(wx.ToolTip("For machines with upgraded NVMe drives, this option allows for better power management support within macOS.\nNote that some NVMe drives don't support macOS's power management settings, and can result in boot issues. Disable this option if you experience IONVMeFamily kernel panics. Mainly applicable for Skylake and newer Macs.")) - - # Wake on WLAN - self.wake_on_wlan_checkbox = wx.CheckBox(self.frame_modal, label="Wake on WLAN") - self.wake_on_wlan_checkbox.SetValue(self.constants.enable_wake_on_wlan) - self.wake_on_wlan_checkbox.Bind(wx.EVT_CHECKBOX, self.wake_on_wlan_click) - self.wake_on_wlan_checkbox.SetPosition(wx.Point(self.nvme_power_management_checkbox.GetPosition().x, self.nvme_power_management_checkbox.GetPosition().y + self.nvme_power_management_checkbox.GetSize().height)) - self.wake_on_wlan_checkbox.SetToolTip(wx.ToolTip("Enables Wake on WLAN for Broadcom Wifi.\nBy default, Wake on WLAN is disabled to work around Apple's wake from sleep bug causing heavily degraded networking performance.\nNote: This option is only applicable for BCM943224, BCM94331, BCM94360 and BCM943602 chipsets")) - - # Content Caching - self.content_caching_checkbox = wx.CheckBox(self.frame_modal, label="Content Caching") - self.content_caching_checkbox.SetValue(self.constants.set_content_caching) - self.content_caching_checkbox.Bind(wx.EVT_CHECKBOX, self.content_caching_click) - self.content_caching_checkbox.SetPosition(wx.Point(self.wake_on_wlan_checkbox.GetPosition().x, self.wake_on_wlan_checkbox.GetPosition().y + self.wake_on_wlan_checkbox.GetSize().height)) - self.content_caching_checkbox.SetToolTip(wx.ToolTip("Enables content caching support in macOS")) - - # APFS Trim - self.apfs_trim_checkbox = wx.CheckBox(self.frame_modal, label="APFS Trim") - self.apfs_trim_checkbox.SetValue(self.constants.apfs_trim_timeout) - self.apfs_trim_checkbox.Bind(wx.EVT_CHECKBOX, self.apfs_trim_click) - self.apfs_trim_checkbox.SetPosition(wx.Point(self.content_caching_checkbox.GetPosition().x, self.content_caching_checkbox.GetPosition().y + self.content_caching_checkbox.GetSize().height)) - self.apfs_trim_checkbox.SetToolTip(wx.ToolTip("Enables APFS Trim support in macOS")) - - # Button: return to main menu - self.return_to_main_menu_button = wx.Button(self.frame_modal, label="Return to Settings") - self.return_to_main_menu_button.Bind(wx.EVT_BUTTON, self.settings_menu) - self.return_to_main_menu_button.SetPosition(wx.Point( - self.apfs_trim_checkbox.GetPosition().x, - self.apfs_trim_checkbox.GetPosition().y + self.apfs_trim_checkbox.GetSize().height + 10)) - self.return_to_main_menu_button.Center(wx.HORIZONTAL) - - # set frame_modal size below return to main menu button - self.frame_modal.SetSize(wx.Size(-1, self.return_to_main_menu_button.GetPosition().y + self.return_to_main_menu_button.GetSize().height + 40)) - self.frame_modal.ShowWindowModal() - - def non_metal_config_menu(self, event=None): - # Configures ASB's Blur settings - # Check Dark Menu Bar: - # defaults read Moraea_DarkMenuBar - # defaults write -g Moraea_DarkMenuBar -bool true - # Check Beta Blur: - # defaults read Moraea_BlurBeta - # defaults write -g Moraea_BlurBeta -bool true - # Check Blur Radius: - # defaults read ASB_BlurOverride - # defaults write -g ASB_BlurOverride -float 30 - - self.reset_frame_modal() - self.frame_modal.SetSize(wx.Size(400, 300)) - - # Header 1: Configure non-Metal Settings - - self.header_1 = wx.StaticText(self.frame_modal, label="Configure non-Metal Settings", pos=wx.Point(10, 10)) - self.header_1.SetFont(wx.Font(16, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) - self.header_1.Centre(wx.HORIZONTAL) - - self.subheader = wx.StaticText(self.frame_modal, label="Below settings apply to systems that have installed") - self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader.SetPosition(wx.Point(0, self.header_1.GetPosition().y + self.header_1.GetSize().height + 5)) - self.subheader.Centre(wx.HORIZONTAL) - - self.subheader_2 = wx.StaticText(self.frame_modal, label="non-metal acceleration patches.") - self.subheader_2.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader_2.SetPosition(wx.Point(0, self.subheader.GetPosition().y + self.subheader.GetSize().height)) - self.subheader_2.Centre(wx.HORIZONTAL) - - # This menu will allow you to enable Beta Blur features resolving some of the UI distortions experienced with non-Metal - self.subheader2_1 = wx.StaticText(self.frame_modal, label="This menu will allow you to enable Beta Blur features resolving") - self.subheader2_1.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader2_1.SetPosition(wx.Point(0, self.subheader_2.GetPosition().y + self.subheader_2.GetSize().height + 5)) - self.subheader2_1.Centre(wx.HORIZONTAL) - - self.subheader2_2 = wx.StaticText(self.frame_modal, label="some of the UI distortions experienced with non-metal GPUs.") - self.subheader2_2.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader2_2.SetPosition(wx.Point(0, self.subheader2_1.GetPosition().y + self.subheader2_1.GetSize().height)) - self.subheader2_2.Centre(wx.HORIZONTAL) - - - self.subheader_4 = wx.StaticText(self.frame_modal, label="Note: Only logout and login is required to apply these settings") - self.subheader_4.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) - self.subheader_4.SetPosition(wx.Point(0, self.subheader2_2.GetPosition().y + self.subheader2_2.GetSize().height+ 5)) - self.subheader_4.Centre(wx.HORIZONTAL) - - is_dark_menu_bar = subprocess.run(["defaults", "read", "-g", "Moraea_DarkMenuBar"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode("utf-8").strip() - if is_dark_menu_bar in ["1", "true"]: - is_dark_menu_bar = True - else: - is_dark_menu_bar = False - - is_blur_enabled = subprocess.run(["defaults", "read", "-g", "Moraea_BlurBeta"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode("utf-8").strip() - if is_blur_enabled in ["1", "true"]: - is_blur_enabled = True - else: - is_blur_enabled = False - - is_rim_disabled = subprocess.run(["defaults", "read", "-g", "Moraea_RimBetaDisabled"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode("utf-8").strip() - if is_rim_disabled in ["1", "true"]: - is_rim_disabled = True - else: - is_rim_disabled = False - - # Checkbox: Dark Menu Bar - self.dark_menu_bar_checkbox = wx.CheckBox(self.frame_modal, label="Dark Menu Bar") - self.dark_menu_bar_checkbox.SetValue(is_dark_menu_bar) - self.dark_menu_bar_checkbox.Bind(wx.EVT_CHECKBOX, self.enable_dark_menubar_click) - self.dark_menu_bar_checkbox.SetPosition(wx.Point(0, self.subheader_4.GetPosition().y + self.subheader_4.GetSize().height + 10)) - self.dark_menu_bar_checkbox.Centre(wx.HORIZONTAL) - - # Checkbox: Enable Beta Blur - self.enable_beta_blur_checkbox = wx.CheckBox(self.frame_modal, label="Enable Beta Blur") - self.enable_beta_blur_checkbox.SetValue(is_blur_enabled) - self.enable_beta_blur_checkbox.Bind(wx.EVT_CHECKBOX, self.enable_beta_blur_click) - self.enable_beta_blur_checkbox.SetPosition(wx.Point(self.dark_menu_bar_checkbox.GetPosition().x, self.dark_menu_bar_checkbox.GetPosition().y + self.dark_menu_bar_checkbox.GetSize().height + 7)) - - # Checkbox: Enable Beta Rim - self.enable_beta_rim_checkbox = wx.CheckBox(self.frame_modal, label="Disable Beta Rim") - self.enable_beta_rim_checkbox.SetValue(is_rim_disabled) - self.enable_beta_rim_checkbox.Bind(wx.EVT_CHECKBOX, self.enable_beta_rim_click) - self.enable_beta_rim_checkbox.SetPosition(wx.Point(self.enable_beta_blur_checkbox.GetPosition().x, self.enable_beta_blur_checkbox.GetPosition().y + self.enable_beta_blur_checkbox.GetSize().height + 7)) - - # Button: Return to Settings - self.return_to_settings_button = wx.Button(self.frame_modal, label="Return to Settings") - self.return_to_settings_button.Bind(wx.EVT_BUTTON, self.settings_menu) - self.return_to_settings_button.SetPosition(wx.Point(0, self.enable_beta_rim_checkbox.GetPosition().y + self.enable_beta_rim_checkbox.GetSize().height + 10)) - self.return_to_settings_button.Center(wx.HORIZONTAL) - - self.frame_modal.SetSize(wx.Size(-1, self.return_to_settings_button.GetPosition().y + self.return_to_settings_button.GetSize().height + 40)) - self.frame_modal.ShowWindowModal() - - def enable_beta_blur_click(self, event=None): - if event.IsChecked(): - subprocess.run(["defaults", "write", "-g", "Moraea_BlurBeta", "-bool", "true"]) - else: - subprocess.run(["defaults", "write", "-g", "Moraea_BlurBeta", "-bool", "false"]) - logging.info("Beta Blur Enabled:", event.IsChecked()) - - def enable_dark_menubar_click(self, event=None): - if event.IsChecked(): - subprocess.run(["defaults", "write", "-g", "Moraea_DarkMenuBar", "-bool", "true"]) - else: - subprocess.run(["defaults", "write", "-g", "Moraea_DarkMenuBar", "-bool", "false"]) - logging.info("Dark Menu Bar Enabled:", event.IsChecked()) - - def enable_beta_rim_click(self, event=None): - if event.IsChecked(): - subprocess.run(["defaults", "write", "-g", "Moraea_RimBetaDisabled", "-bool", "true"]) - else: - subprocess.run(["defaults", "write", "-g", "Moraea_RimBetaDisabled", "-bool", "false"]) - logging.info("Beta Rim Enabled:", event.IsChecked()) diff --git a/resources/gui/menu_redirect.py b/resources/gui/menu_redirect.py deleted file mode 100644 index 70c990f3d..000000000 --- a/resources/gui/menu_redirect.py +++ /dev/null @@ -1,46 +0,0 @@ -import wx -import time - -class RedirectText(object): - def __init__(self,aWxTextCtrl, sleep): - self.out=aWxTextCtrl - self.sleep = sleep - - def write(self,string): - self.out.WriteText(string) - wx.GetApp().Yield() - if self.sleep: - time.sleep(0.01) - - def fileno(self): - return 1 - - def flush(self): - pass - -class RedirectLabel(object): - def __init__(self,aWxTextCtrl): - self.out=aWxTextCtrl - - def write(self,string): - if "MB/s" in string: - self.out.SetLabel(string) - self.out.Centre(wx.HORIZONTAL) - wx.GetApp().Yield() - time.sleep(0.01) - - def fileno(self): - return 1 - - def flush(self): - pass - -class RedirectLabelAll(object): - def __init__(self,aWxTextCtrl): - self.out=aWxTextCtrl - - def write(self,string): - self.out.SetLabel(string) - self.out.Centre(wx.HORIZONTAL) - wx.GetApp().Yield() - time.sleep(0.01) \ No newline at end of file diff --git a/resources/install.py b/resources/install.py index f5906ab17..b2d9d2d3e 100644 --- a/resources/install.py +++ b/resources/install.py @@ -2,15 +2,17 @@ # Usage solely for TUI # Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk +import logging import plistlib import subprocess -import shutil -import os -import logging +import applescript + from pathlib import Path + from resources import utilities, constants from data import os_data + class tui_disk_installation: def __init__(self, versions): self.constants: constants.Constants = versions @@ -75,49 +77,37 @@ class tui_disk_installation: return supported_partitions - def install_opencore(self, full_disk_identifier): - def determine_sd_card(media_name): - # Array filled with common SD Card names - # Note most USB-based SD Card readers generally report as "Storage Device" - # Thus no reliable way to detect further without parsing IOService output (kUSBProductString) - if ( - "SD Card" in media_name or \ - "SD/MMC" in media_name or \ - "SDXC Reader" in media_name or \ - "SD Reader" in media_name or \ - "Card Reader" in media_name - ): - return True - return False + def _determine_sd_card(media_name: str): + # Array filled with common SD Card names + # Note most USB-based SD Card readers generally report as "Storage Device" + # Thus no reliable way to detect further without parsing IOService output (kUSBProductString) + if any(x in media_name for x in ("SD Card", "SD/MMC", "SDXC Reader", "SD Reader", "Card Reader")): + return True + return False + + def install_opencore(self, full_disk_identifier: str): # TODO: Apple Script fails in Yosemite(?) and older - args = [ - "osascript", - "-e", - f'''do shell script "diskutil mount {full_disk_identifier}"''' - ' with prompt "OpenCore Legacy Patcher needs administrator privileges to mount your EFI."' - " with administrator privileges" - " without altering line endings", - ] - + logging.info(f"- Mounting partition: {full_disk_identifier}") if self.constants.detected_os >= os_data.os_data.el_capitan and not self.constants.recovery_status: - result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - else: - result = subprocess.run(f"diskutil mount {full_disk_identifier}".split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - if result.returncode != 0: - if "execution error" in result.stderr.decode() and result.stderr.decode().strip()[-5:-1] == "-128": - # cancelled prompt - return - else: - logging.info("An error occurred!") - logging.info(result.stderr.decode()) - - # Check if we're in Safe Mode, and if so, tell user FAT32 is unsupported + try: + applescript.AppleScript(f'''do shell script "diskutil mount {full_disk_identifier}" with prompt "OpenCore Legacy Patcher needs administrator privileges to mount this volume." with administrator privileges without altering line endings''').run() + except applescript.ScriptError as e: + if "User canceled" in str(e): + logging.info("- Mount cancelled by user") + return + logging.info(f"An error occurred: {e}") if utilities.check_boot_mode() == "safe_boot": logging.info("\nSafe Mode detected. FAT32 is unsupported by macOS in this mode.") logging.info("Please disable Safe Mode and try again.") + return + else: + result = subprocess.run(f"diskutil mount {full_disk_identifier}".split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode != 0: + logging.info("- Mount failed") + logging.info(result.stderr.decode()) return + partition_info = plistlib.loads(subprocess.run(f"diskutil info -plist {full_disk_identifier}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) parent_disk = partition_info["ParentWholeDisk"] drive_host_info = plistlib.loads(subprocess.run(f"diskutil info -plist {parent_disk}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) @@ -128,71 +118,57 @@ class tui_disk_installation: ssd_type = False mount_path = Path(partition_info["MountPoint"]) disk_type = partition_info["BusProtocol"] - utilities.cls() - utilities.header(["Copying OpenCore"]) - if mount_path.exists(): - if (mount_path / Path("EFI/Microsoft")).exists() and self.constants.gui_mode is False: - logging.info("- Found Windows Boot Loader") - logging.info("\nWould you like to continue installing OpenCore?") - logging.info("Installing OpenCore onto this drive may make Windows unbootable until OpenCore") - logging.info("is removed from the partition") - logging.info("We highly recommend users partition 200MB off their drive with Disk Utility") - logging.info(" Name:\t\t OPENCORE") - logging.info(" Format:\t\t FAT32") - logging.info(" Size:\t\t 200MB") - choice = input("\nWould you like to still install OpenCore to this drive?(y/n): ") - if not choice in ["y", "Y", "Yes", "yes"]: - subprocess.run(["diskutil", "umount", mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode() - return False - if (mount_path / Path("EFI/OC")).exists(): - logging.info("- Removing preexisting EFI/OC folder") - shutil.rmtree(mount_path / Path("EFI/OC"), onerror=rmtree_handler) - if (mount_path / Path("System")).exists(): - logging.info("- Removing preexisting System folder") - shutil.rmtree(mount_path / Path("System"), onerror=rmtree_handler) - if (mount_path / Path("boot.efi")).exists(): - logging.info("- Removing preexisting boot.efi") - os.remove(mount_path / Path("boot.efi")) - logging.info("- Copying OpenCore onto EFI partition") - shutil.copytree(self.constants.opencore_release_folder / Path("EFI/OC"), mount_path / Path("EFI/OC")) - shutil.copytree(self.constants.opencore_release_folder / Path("System"), mount_path / Path("System")) - if Path(self.constants.opencore_release_folder / Path("boot.efi")).exists(): - shutil.copy(self.constants.opencore_release_folder / Path("boot.efi"), mount_path / Path("boot.efi")) - if self.constants.boot_efi is True: - logging.info("- Converting Bootstrap to BOOTx64.efi") - if (mount_path / Path("EFI/BOOT")).exists(): - shutil.rmtree(mount_path / Path("EFI/BOOT"), onerror=rmtree_handler) - Path(mount_path / Path("EFI/BOOT")).mkdir() - shutil.move(mount_path / Path("System/Library/CoreServices/boot.efi"), mount_path / Path("EFI/BOOT/BOOTx64.efi")) - shutil.rmtree(mount_path / Path("System"), onerror=rmtree_handler) - if determine_sd_card(sd_type) is True: - logging.info("- Adding SD Card icon") - shutil.copy(self.constants.icon_path_sd, mount_path) - elif ssd_type is True: - logging.info("- Adding SSD icon") - shutil.copy(self.constants.icon_path_ssd, mount_path) - elif disk_type == "USB": - logging.info("- Adding External USB Drive icon") - shutil.copy(self.constants.icon_path_external, mount_path) - else: - logging.info("- Adding Internal Drive icon") - shutil.copy(self.constants.icon_path_internal, mount_path) - - logging.info("- Cleaning install location") - if not self.constants.recovery_status: - logging.info("- Unmounting EFI partition") - subprocess.run(["diskutil", "umount", mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode() - logging.info("- OpenCore transfer complete") - if self.constants.gui_mode is False: - logging.info("\nPress [Enter] to continue.\n") - input() - else: + if not mount_path.exists(): logging.info("EFI failed to mount!") return False - return True -def rmtree_handler(func, path, exc_info): - if exc_info[0] == FileNotFoundError: - return - raise # pylint: disable=misplaced-bare-raise \ No newline at end of file + if (mount_path / Path("EFI/OC")).exists(): + logging.info("- Removing preexisting EFI/OC folder") + subprocess.run(["rm", "-rf", mount_path / Path("EFI/OC")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + if (mount_path / Path("System")).exists(): + logging.info("- Removing preexisting System folder") + subprocess.run(["rm", "-rf", mount_path / Path("System")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + if (mount_path / Path("boot.efi")).exists(): + logging.info("- Removing preexisting boot.efi") + subprocess.run(["rm", mount_path / Path("boot.efi")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + logging.info("- Copying OpenCore onto EFI partition") + subprocess.run(["mkdir", "-p", mount_path / Path("EFI")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + subprocess.run(["cp", "-r", self.constants.opencore_release_folder / Path("EFI/OC"), mount_path / Path("EFI/OC")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + subprocess.run(["cp", "-r", self.constants.opencore_release_folder / Path("System"), mount_path / Path("System")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + if Path(self.constants.opencore_release_folder / Path("boot.efi")).exists(): + subprocess.run(["cp", self.constants.opencore_release_folder / Path("boot.efi"), mount_path / Path("boot.efi")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + if self.constants.boot_efi is True: + logging.info("- Converting Bootstrap to BOOTx64.efi") + if (mount_path / Path("EFI/BOOT")).exists(): + subprocess.run(["rm", "-rf", mount_path / Path("EFI/BOOT")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + Path(mount_path / Path("EFI/BOOT")).mkdir() + subprocess.run(["mv", mount_path / Path("System/Library/CoreServices/boot.efi"), mount_path / Path("EFI/BOOT/BOOTx64.efi")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + subprocess.run(["rm", "-rf", mount_path / Path("System")], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + if self._determine_sd_card(sd_type) is True: + logging.info("- Adding SD Card icon") + subprocess.run(["cp", self.constants.icon_path_sd, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + elif ssd_type is True: + logging.info("- Adding SSD icon") + subprocess.run(["cp", self.constants.icon_path_ssd, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + elif disk_type == "USB": + logging.info("- Adding External USB Drive icon") + subprocess.run(["cp", self.constants.icon_path_external, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + else: + logging.info("- Adding Internal Drive icon") + subprocess.run(["cp", self.constants.icon_path_internal, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + logging.info("- Cleaning install location") + if not self.constants.recovery_status: + logging.info("- Unmounting EFI partition") + subprocess.run(["diskutil", "umount", mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode() + + logging.info("- OpenCore transfer complete") + + return True \ No newline at end of file diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 7e5f1ae09..be91bb8ca 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -538,10 +538,12 @@ class KernelDebugKitObject: logging.info(f"- {msg}") self.error_msg = msg + return False self._remove_unused_kdks() - self.success = True + logging.info("- Kernel Debug Kit checksum verified") + return True class KernelDebugKitUtilities: diff --git a/resources/macos_installer_handler.py b/resources/macos_installer_handler.py index 2632b481d..23b70f622 100644 --- a/resources/macos_installer_handler.py +++ b/resources/macos_installer_handler.py @@ -6,6 +6,7 @@ import subprocess import tempfile import enum import logging +import applescript from data import os_data from resources import network_handler, utilities @@ -53,19 +54,16 @@ class InstallerCreation(): """ logging.info("- Extracting macOS installer from InstallAssistant.pkg\n This may take some time") - args = [ - "osascript", - "-e", - f'''do shell script "installer -pkg {Path(download_path)}/InstallAssistant.pkg -target /"''' - ' with prompt "OpenCore Legacy Patcher needs administrator privileges to add InstallAssistant."' - " with administrator privileges" - " without altering line endings", - ] - - result = subprocess.run(args,stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if result.returncode != 0: + try: + applescript.AppleScript( + f'''do shell script "installer -pkg {Path(download_path)}/InstallAssistant.pkg -target /"''' + ' with prompt "OpenCore Legacy Patcher needs administrator privileges to extract the installer."' + " with administrator privileges" + " without altering line endings", + ).run() + except Exception as e: logging.info("- Failed to install InstallAssistant") - logging.info(f" Error Code: {result.returncode}") + logging.info(f" Error Code: {e}") return False logging.info("- InstallAssistant installed") @@ -400,6 +398,7 @@ class RemoteInstallerCatalog: "integrity": integrity, "Source": "Apple Inc.", "Variant": catalog_url, + "OS": os_data.os_conversion.os_to_kernel(version) } }) @@ -585,6 +584,7 @@ class LocalInstallerCatalog: "Build": app_sdk, "Path": application, "Minimum Host OS": min_required, + "OS": kernel } }) diff --git a/resources/main.py b/resources/main.py index 75daeb699..9797cfcdf 100644 --- a/resources/main.py +++ b/resources/main.py @@ -6,7 +6,7 @@ import logging import threading from pathlib import Path -from resources.gui import gui_main +from resources.wx_gui import gui_entry from resources import ( constants, utilities, @@ -38,7 +38,7 @@ class OpenCoreLegacyPatcher: self._generate_base_data() if utilities.check_cli_args() is None: - gui_main.wx_python_gui(self.constants).main_menu(None) + gui_entry.EntryPoint(self.constants).start() def _generate_base_data(self) -> None: @@ -99,7 +99,7 @@ class OpenCoreLegacyPatcher: logging.info("- Detected arguments, switching to CLI mode") self.constants.gui_mode = True # Assumes no user interaction is required - ignore_args = ["--auto_patch", "--gui_patch", "--gui_unpatch"] + ignore_args = ["--auto_patch", "--gui_patch", "--gui_unpatch", "--update_installed"] if not any(x in sys.argv for x in ignore_args): self.constants.current_path = Path.cwd() self.constants.cli_mode = True diff --git a/resources/network_handler.py b/resources/network_handler.py index f89bcc91d..5efd0be14 100644 --- a/resources/network_handler.py +++ b/resources/network_handler.py @@ -9,6 +9,7 @@ import threading import logging import enum import hashlib +import atexit from pathlib import Path from resources import utilities @@ -340,6 +341,7 @@ class DownloadObject: response = NetworkUtilities().get(self.url, stream=True, timeout=10) with open(self.filepath, 'wb') as file: + atexit.register(self.stop) for i, chunk in enumerate(response.iter_content(1024 * 1024 * 4)): if self.should_stop: raise Exception("Download stopped") @@ -380,7 +382,6 @@ class DownloadObject: """ if self.total_file_size == 0.0: - logging.error("- File size is 0, cannot calculate percent") return -1 return self.downloaded_file_size / self.total_file_size * 100 @@ -405,9 +406,11 @@ class DownloadObject: """ if self.total_file_size == 0.0: - logging.error("- File size is 0, cannot calculate time remaining") return -1 - return (self.total_file_size - self.downloaded_file_size) / self.get_speed() + speed = self.get_speed() + if speed <= 0: + return -1 + return (self.total_file_size - self.downloaded_file_size) / speed def get_file_size(self) -> float: diff --git a/resources/sys_patch/sys_patch.py b/resources/sys_patch/sys_patch.py index 4e04bf531..2a730f274 100644 --- a/resources/sys_patch/sys_patch.py +++ b/resources/sys_patch/sys_patch.py @@ -280,8 +280,6 @@ class PatchSysVolume: if self.needs_kmutil_exemptions is True: logging.info("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) -> bool: @@ -365,8 +363,6 @@ class PatchSysVolume: logging.info(result.stdout.decode()) logging.info("") logging.info("\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: @@ -378,8 +374,6 @@ class PatchSysVolume: logging.info(result.stdout.decode()) logging.info("") logging.info("\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"]: @@ -885,33 +879,18 @@ class PatchSysVolume: self.patch_set_dictionary = sys_patch_generate.GenerateRootPatchSets(self.computer.real_model, self.constants, self.hardware_details).patchset if self.patch_set_dictionary == {}: - change_menu = None logging.info("- 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" - logging.info("- Continuing root patching") - if change_menu in ["y", "Y"]: - logging.info("- Verifying whether Root Patching possible") - if sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).verify_patch_allowed(print_errors=not self.constants.wxpython_variant) is True: - logging.info("- 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: - logging.info("- 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: ") + return + + logging.info("- Verifying whether Root Patching possible") + if sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).verify_patch_allowed(print_errors=not self.constants.wxpython_variant) is True: + logging.info("- Patcher is capable of patching") + if self._check_files(): + if self._mount_root_vol() is True: + self._patch_root_vol() + else: + logging.info("- Recommend rebooting the machine and trying to patch again") - else: - logging.info("- Returning to main menu") def start_unpatch(self) -> None: """ @@ -922,11 +901,5 @@ class PatchSysVolume: if sys_patch_detect.DetectRootPatch(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: logging.info("- 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") diff --git a/resources/sys_patch/sys_patch_auto.py b/resources/sys_patch/sys_patch_auto.py index 545c55a7a..7bea39299 100644 --- a/resources/sys_patch/sys_patch_auto.py +++ b/resources/sys_patch/sys_patch_auto.py @@ -1,14 +1,16 @@ # Copyright (C) 2022, Mykola Grymalyuk +import wx +import logging 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 +from resources.wx_gui import gui_entry class AutomaticSysPatch: @@ -42,6 +44,26 @@ class AutomaticSysPatch: logging.info("- Auto Patch option is not supported on TUI, please use GUI") return + dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates() + if dict: + for key in dict: + version = dict[key]["Version"] + logging.info(f"- Found new version: {version}") + + app = wx.App() + frame = wx.Frame(None, -1, "OpenCore Legacy Patcher") + dialog = wx.MessageDialog( + parent=frame, + 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", "Always Ignore", "Ignore Once") + response = dialog.ShowModal() + if response == wx.ID_YES: + gui_entry.EntryPoint(self.constants).start(entry=gui_entry.SupportedEntryPoints.UPDATE_APP) + return + if utilities.check_seal() is True: logging.info("- Detected Snapshot seal intact, detecting patches") patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() @@ -58,69 +80,44 @@ class AutomaticSysPatch: 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.CheckBinaryUpdates(self.constants).check_binary_updates() - if not dict: - logging.info("- No new binaries found on Github, proceeding with patching") - if self.constants.launcher_script is None: - args_string = f"'{self.constants.launcher_binary}' --gui_patch" - else: - args_string = f"{self.constants.launcher_binary} {self.constants.launcher_script} --gui_patch" - warning_str = "" - if network_handler.NetworkUtilities("https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest").verify_network_connection() 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 {self.constants.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 "{self.constants.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 - ) - return + logging.info("- No new binaries found on Github, proceeding with patching") + logging.info("- No new binaries found on Github, proceeding with patching") + if self.constants.launcher_script is None: + args_string = f"'{self.constants.launcher_binary}' --gui_patch" else: - for key in dict: - version = dict[key]["Version"] - github_link = dict[key]["Github Link"] - logging.info(f"- Found new version: {version}") + args_string = f"{self.constants.launcher_binary} {self.constants.launcher_script} --gui_patch" - # launch osascript 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 + warning_str = "" + if network_handler.NetworkUtilities("https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest").verify_network_connection() 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 {self.constants.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 "{self.constants.app_icon_path}"', + ] + output = subprocess.run( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) + if output.returncode == 0: 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: {self.constants.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 "{self.constants.app_icon_path}"', + 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" ] - output = subprocess.run( + subprocess.run( args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) - if output.returncode == 0: - webbrowser.open(github_link) - - return + return else: logging.info("- No patches detected") else: @@ -170,7 +167,7 @@ class AutomaticSysPatch: if output.returncode == 0: logging.info("- Launching GUI's Build/Install menu") self.constants.start_build_install = True - gui_main.wx_python_gui(self.constants).main_menu(None) + gui_entry.EntryPoint(self.constants).start(entry=gui_entry.SupportedEntryPoints.BUILD_OC) return False @@ -245,7 +242,7 @@ class AutomaticSysPatch: if output.returncode == 0: logging.info("- Launching GUI's Build/Install menu") self.constants.start_build_install = True - gui_main.wx_python_gui(self.constants).main_menu(None) + gui_entry.EntryPoint(self.constants).start(entry=gui_entry.SupportedEntryPoints.BUILD_OC) except KeyError: logging.info("- Unable to determine if boot disk is removable, skipping prompt") diff --git a/resources/updates.py b/resources/updates.py index d5a62dcab..850a1b0e6 100644 --- a/resources/updates.py +++ b/resources/updates.py @@ -2,7 +2,6 @@ # Check whether new updates are available for OpenCore Legacy Patcher binary # 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 requests import logging from resources import network_handler, constants @@ -35,6 +34,12 @@ class CheckBinaryUpdates: if local_version is None: local_version = self.binary_version_array + + if local_version == remote_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) @@ -99,6 +104,9 @@ class CheckBinaryUpdates: response = network_handler.NetworkUtilities().get(REPO_LATEST_RELEASE_URL) data_set = response.json() + if "tag_name" not in data_set: + return None + self.remote_version = data_set["tag_name"] self.remote_version_array = self.remote_version.split(".") diff --git a/resources/utilities.py b/resources/utilities.py index e2b4b903c..63d3fad97 100644 --- a/resources/utilities.py +++ b/resources/utilities.py @@ -48,6 +48,44 @@ def human_fmt(num): return "%.1f %s" % (num, "EB") +def seconds_to_readable_time(seconds) -> str: + """ + Convert seconds to a readable time format + + Parameters: + seconds (int | float | str): Seconds to convert + + Returns: + str: Readable time format + """ + seconds = int(seconds) + time = "" + + if seconds == 0: + return "Almost done" + if seconds < 0: + return "Indeterminate" + + years, seconds = divmod(seconds, 31536000) + days, seconds = divmod(seconds, 86400) + hours, seconds = divmod(seconds, 3600) + minutes, seconds = divmod(seconds, 60) + + if years > 0: + return "Over a year" + if days > 0: + if days > 31: + return "Over a month" + time += f"{days}d " + if hours > 0: + time += f"{hours}h " + if minutes > 0: + time += f"{minutes}m " + if seconds > 0: + time += f"{seconds}s" + return time + + def header(lines): lines = [i for i in lines if i is not None] total_length = len(max(lines, key=len)) + 4 @@ -534,6 +572,7 @@ def check_cli_args(): parser.add_argument("--gui_patch", help="Starts GUI in Root Patcher", action="store_true", required=False) parser.add_argument("--gui_unpatch", help="Starts GUI in Root Unpatcher", action="store_true", required=False) parser.add_argument("--auto_patch", help="Check if patches are needed and prompt user", action="store_true", required=False) + 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): diff --git a/resources/wx_gui/gui_about.py b/resources/wx_gui/gui_about.py new file mode 100644 index 000000000..f73cd29cf --- /dev/null +++ b/resources/wx_gui/gui_about.py @@ -0,0 +1,63 @@ +# About frame, just to sat + +import wx +import wx.adv + +from resources import constants + + +class AboutFrame(wx.Frame): + + def __init__(self, global_constants: constants.Constants) -> None: + if wx.FindWindowByName("About"): + return + + super(AboutFrame, self).__init__(None, title="About", size=(350, 350), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) + self.constants: constants.Constants = global_constants + self.Centre() + self.hyperlink_colour = (25, 179, 231) + + self._generate_elements(self) + + self.Show() + + + def _generate_elements(self, frame: wx.Frame) -> None: + + # Set title + title = wx.StaticText(frame, label="OpenCore Legacy Patcher", pos=(-1, 5)) + title.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title.Centre(wx.HORIZONTAL) + + # Set version + version = wx.StaticText(frame, label=f"Version: {self.constants.patcher_version}", pos=(-1, title.GetPosition()[1] + title.GetSize()[1] + 5)) + version.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + version.Centre(wx.HORIZONTAL) + + # Description + description = [ + "Written by a small group of Mac hobbyists who just", + "want to keep old machines out of the landfill!", + + ] + spacer = 5 + for line in description: + desc = wx.StaticText(frame, label=line, pos=(-1, version.GetPosition()[1] + version.GetSize()[1] + 5 + spacer)) + desc.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + desc.Centre(wx.HORIZONTAL) + + spacer += 20 + + # Set icon + icon_mac = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/com.apple.macbook-unibody-plastic.icns" + icon_mac = wx.StaticBitmap(frame, bitmap=wx.Bitmap(icon_mac, wx.BITMAP_TYPE_ICON), pos=(5, desc.GetPosition()[1] - 15)) + icon_mac.SetSize((160, 160)) + icon_mac.Centre(wx.HORIZONTAL) + + icon_path = str(self.constants.app_icon_path) + icon = wx.StaticBitmap(frame, bitmap=wx.Bitmap(icon_path, wx.BITMAP_TYPE_ICON), pos=(5, desc.GetPosition()[1] + desc.GetSize()[1] + 17)) + icon.SetSize((64, 64)) + icon.Centre(wx.HORIZONTAL) + + # Set frame size + frame.SetSize((-1, icon.GetPosition()[1] + icon.GetSize()[1] + 60)) diff --git a/resources/wx_gui/gui_build.py b/resources/wx_gui/gui_build.py new file mode 100644 index 000000000..2edf7a19b --- /dev/null +++ b/resources/wx_gui/gui_build.py @@ -0,0 +1,158 @@ +# Generate UI for Building OpenCore +import wx +import logging +import threading +import traceback + +from resources import constants +from resources.build import build +from resources.wx_gui import ( + gui_main_menu, + gui_install_oc, + gui_support +) + + +class BuildFrame(wx.Frame): + """ + Create a frame for building OpenCore + Uses a Modal Dialog for smoother transition from other frames + """ + def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None) -> None: + super(BuildFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) + + self.install_button: wx.Button = None + self.text_box: wx.TextCtrl = None + self.frame_modal: wx.Dialog = None + + self.constants: constants.Constants = global_constants + self.title: str = title + self.stock_output = logging.getLogger().handlers[0].stream + + self.frame_modal = wx.Dialog(self, title=title, size=(400, 200)) + + self._generate_elements(self.frame_modal) + + if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: + self.constants.update_stage = gui_support.AutoUpdateStages.BUILDING + + self.SetPosition(screen_location) if screen_location else self.Centre() + self.frame_modal.ShowWindowModal() + + self._invoke_build() + + + def _generate_elements(self, frame: wx.Frame = None) -> None: + """ + Generate UI elements for build frame + + Format: + - Title label: Build and Install OpenCore + - Text: Model: {Build or Host Model} + - Button: Install OpenCore + - Read-only text box: {empty} + - Button: Return to Main Menu + """ + frame = self if not frame else frame + + title_label = wx.StaticText(frame, label="Build and Install OpenCore", pos=(-1,5)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + model_label = wx.StaticText(frame, label=f"Model: {self.constants.custom_model or self.constants.computer.real_model}", pos=(-1,30)) + model_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + model_label.Centre(wx.HORIZONTAL) + + # Button: Install OpenCore + install_button = wx.Button(frame, label="🔩 Install OpenCore", pos=(-1, model_label.GetPosition()[1] + model_label.GetSize()[1]), size=(150, 30)) + install_button.Bind(wx.EVT_BUTTON, self.on_install) + install_button.Centre(wx.HORIZONTAL) + install_button.Disable() + self.install_button = install_button + + # Read-only text box: {empty} + text_box = wx.TextCtrl(frame, value="", pos=(-1, install_button.GetPosition()[1] + install_button.GetSize()[1] + 10), size=(400, 350), style=wx.TE_READONLY | wx.TE_MULTILINE | wx.TE_RICH2) + text_box.Centre(wx.HORIZONTAL) + self.text_box = text_box + + # Button: Return to Main Menu + return_button = wx.Button(frame, label="Return to Main Menu", pos=(-1, text_box.GetPosition()[1] + text_box.GetSize()[1] + 5), size=(200, 30)) + return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) + return_button.Centre(wx.HORIZONTAL) + return_button.Disable() + self.return_button = return_button + + # Adjust window size to fit all elements + frame.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40)) + + + def _invoke_build(self) -> None: + """ + Invokes build function and waits for it to finish + """ + while gui_support.PayloadMount(self.constants, self).is_unpack_finished() is False: + wx.Yield() + + thread = threading.Thread(target=self._build) + thread.start() + + while thread.is_alive(): + wx.Yield() + + self.return_button.Enable() + dialog = wx.MessageDialog( + parent=self, + message=f"Would you like to install OpenCore now?", + caption="Finished building your OpenCore configuration!", + style=wx.YES_NO | wx.ICON_QUESTION + ) + dialog.SetYesNoLabels("Install to disk", "View build log") + + self.on_install() if dialog.ShowModal() == wx.ID_YES else self.install_button.Enable() + + + def _build(self) -> None: + """ + Calls build function and redirects stdout to the text box + """ + logger = logging.getLogger() + logger.addHandler(gui_support.ThreadHandler(self.text_box)) + try: + build.BuildOpenCore(self.constants.custom_model or self.constants.computer.real_model, self.constants) + except: + logging.error("- An internal error occurred while building:\n") + logging.error(traceback.format_exc()) + logger.removeHandler(logger.handlers[2]) + + + def on_return_to_main_menu(self, event: wx.Event = None) -> None: + """ + Return to main menu + """ + self.frame_modal.Hide() + main_menu_frame = gui_main_menu.MainFrame( + None, + title=self.title, + global_constants=self.constants, + screen_location=self.GetScreenPosition() + ) + main_menu_frame.Show() + self.frame_modal.Destroy() + self.Destroy() + + + def on_install(self, event: wx.Event = None) -> None: + """ + Launch install frame + """ + self.frame_modal.Destroy() + self.Destroy() + install_oc_frame = gui_install_oc.InstallOCFrame( + None, + title=self.title, + global_constants=self.constants, + screen_location=self.GetScreenPosition() + ) + install_oc_frame.Show() + + diff --git a/resources/wx_gui/gui_download.py b/resources/wx_gui/gui_download.py new file mode 100644 index 000000000..96b139129 --- /dev/null +++ b/resources/wx_gui/gui_download.py @@ -0,0 +1,98 @@ +# Generate UI for downloading files +import wx + +from resources import ( + constants, + network_handler, + utilities +) + + +class DownloadFrame(wx.Frame): + """ + Update provided frame with download stats + """ + def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, download_obj: network_handler.DownloadObject, item_name: str) -> None: + + self.constants: constants.Constants = global_constants + self.title: str = title + self.parent: wx.Frame = parent + self.download_obj: network_handler.DownloadObject = download_obj + self.item_name: str = item_name + + self.user_cancelled: bool = False + + self.frame_modal = wx.Dialog(parent, title=title, size=(400, 200)) + + self._generate_elements(self.frame_modal) + + + def _generate_elements(self, frame: wx.Dialog = None) -> None: + """ + Generate elements for download frame + """ + + frame = self if not frame else frame + + title_label = wx.StaticText(frame, label=f"Downloading: {self.item_name}", pos=(-1,5)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + label_amount = wx.StaticText(frame, label="0.00 B downloaded of 0.00B (0.00%)", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5)) + label_amount.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + label_amount.Centre(wx.HORIZONTAL) + + label_speed = wx.StaticText(frame, label="Average download speed: Unknown", pos=(-1, label_amount.GetPosition()[1] + label_amount.GetSize()[1] + 5)) + label_speed.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + label_speed.Centre(wx.HORIZONTAL) + + label_est_time = wx.StaticText(frame, label="Estimated time remaining: Unknown", pos=(-1, label_speed.GetPosition()[1] + label_speed.GetSize()[1] + 5)) + label_est_time.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + label_est_time.Centre(wx.HORIZONTAL) + + progress_bar = wx.Gauge(frame, range=100, pos=(-1, label_est_time.GetPosition()[1] + label_est_time.GetSize()[1] + 5), size=(300, 20)) + progress_bar.Centre(wx.HORIZONTAL) + + return_button = wx.Button(frame, label="Return", pos=(-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 5)) + return_button.Bind(wx.EVT_BUTTON, lambda event: self.terminate_download()) + return_button.Centre(wx.HORIZONTAL) + + # Set size of frame + frame.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40)) + frame.ShowWindowModal() + + self.download_obj.download() + while self.download_obj.is_active(): + if self.download_obj.get_percent() == -1: + amount_str = f"{utilities.human_fmt(self.download_obj.downloaded_file_size)} downloaded" + else: + amount_str = f"{utilities.human_fmt(self.download_obj.downloaded_file_size)} downloaded of {utilities.human_fmt(self.download_obj.total_file_size)} ({self.download_obj.get_percent():.2f}%)" + label_amount.SetLabel(amount_str) + label_amount.Centre(wx.HORIZONTAL) + + label_speed.SetLabel( + f"Average download speed: {utilities.human_fmt(self.download_obj.get_speed())}/s" + ) + + label_est_time.SetLabel( + f"Estimated time remaining: {utilities.seconds_to_readable_time(self.download_obj.get_time_remaining())}" + ) + + progress_bar.SetValue(int(self.download_obj.get_percent())) + wx.Yield() + + if self.download_obj.download_complete is False and self.user_cancelled is False: + wx.MessageBox(f"Download failed: \n{self.download_obj.error_msg}", "Error", wx.OK | wx.ICON_ERROR) + + frame.Destroy() + + + def terminate_download(self) -> None: + """ + Terminate download + """ + if wx.MessageBox("Are you sure you want to cancel the download?", "Cancel Download", wx.YES_NO | wx.ICON_QUESTION | wx.NO_DEFAULT) == wx.YES: + self.user_cancelled = True + self.download_obj.stop() + + diff --git a/resources/wx_gui/gui_entry.py b/resources/wx_gui/gui_entry.py new file mode 100644 index 000000000..c8ef7e0f1 --- /dev/null +++ b/resources/wx_gui/gui_entry.py @@ -0,0 +1,91 @@ +# Entry point for the wxPython GUI +import wx +import sys +import atexit +import logging + +from resources import constants +from resources.wx_gui import ( + gui_main_menu, + gui_build, + gui_install_oc, + gui_sys_patch, + gui_support, + gui_update, +) +from resources.sys_patch import sys_patch_detect + + +class SupportedEntryPoints: + """ + Enum for supported entry points + """ + MAIN_MENU = gui_main_menu.MainFrame + BUILD_OC = gui_build.BuildFrame + INSTALL_OC = gui_install_oc.InstallOCFrame + SYS_PATCH = gui_sys_patch.SysPatchFrame + UPDATE_APP = gui_update.UpdateFrame + + +class EntryPoint: + + def __init__(self, global_constants: constants.Constants) -> None: + self.app: wx.App = None + self.main_menu_frame: gui_main_menu.MainFrame = None + self.constants: constants.Constants = global_constants + + self.constants.gui_mode = True + + + def _generate_base_data(self) -> None: + self.app = wx.App() + self.app.SetAppName(self.constants.patcher_name) + + + def start(self, entry: SupportedEntryPoints = gui_main_menu.MainFrame) -> None: + """ + Launches entry point for the wxPython GUI + """ + self._generate_base_data() + + if "--gui_patch" in sys.argv or "--gui_unpatch" in sys.argv: + entry = gui_sys_patch.SysPatchFrame + patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() + + logging.info(f"- Loading wxPython GUI: {entry.__name__}") + self.frame: wx.Frame = entry( + None, + title=f"{self.constants.patcher_name} ({self.constants.patcher_version})", + global_constants=self.constants, + screen_location=None, + **({"patches": patches} if "--gui_patch" in sys.argv or "--gui_unpatch" in sys.argv else {}) + ) + if self.frame: + gui_support.GenerateMenubar(self.frame, self.constants).generate() + + atexit.register(self.OnCloseFrame) + + if "--gui_patch" in sys.argv: + self.frame.start_root_patching(patches) + elif "--gui_unpatch" in sys.argv: + self.frame.revert_root_patching(patches) + + self.app.MainLoop() + + + def OnCloseFrame(self, event: wx.Event = None) -> None: + """ + Closes the wxPython GUI + """ + + if not self.frame: + return + + logging.info("- Cleaning up wxPython GUI") + + self.frame.SetTransparent(0) + wx.Yield() + + self.frame.DestroyChildren() + self.frame.Destroy() + self.app.ExitMainLoop() diff --git a/resources/wx_gui/gui_help.py b/resources/wx_gui/gui_help.py new file mode 100644 index 000000000..b5c880ed0 --- /dev/null +++ b/resources/wx_gui/gui_help.py @@ -0,0 +1,66 @@ +# Generate UI for help menu +import wx +import webbrowser + +from resources import constants + + +class HelpFrame(wx.Frame): + """ + Append to main menu through a modal dialog + """ + def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None) -> None: + + self.dialog = wx.Dialog(parent, title=title, size=(300, 200)) + + self.constants: constants.Constants = global_constants + self.title: str = title + + self._generate_elements(self.dialog) + self.dialog.ShowWindowModal() + + + def _generate_elements(self, frame: wx.Frame = None) -> None: + """ + Format: + - Title: Patcher Resources + - Text: Following resources are available: + - Button: Official Guide + - Button: Community Discord Server + - Button: Official Phone Support + - Button: Return to Main Menu + """ + + frame = self if not frame else frame + + title_label = wx.StaticText(frame, label="Patcher Resources", pos=(-1,5)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + text_label = wx.StaticText(frame, label="Following resources are available:", pos=(-1,30)) + text_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + text_label.Centre(wx.HORIZONTAL) + + buttons = { + "Official Guide": self.constants.guide_link, + "Official Phone Support": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + "Community Discord Server": self.constants.discord_link, + } + + for button in buttons: + help_button = wx.Button(frame, label=button, pos=(-1, text_label.GetPosition()[1] + text_label.GetSize()[1] + (list(buttons.keys()).index(button) * 30)), size=(200, 30)) + help_button.Bind(wx.EVT_BUTTON, lambda event, temp=buttons[button]: webbrowser.open(temp)) + help_button.Centre(wx.HORIZONTAL) + + # Button: Return to Main Menu + return_button = wx.Button(frame, label="Return to Main Menu", pos=(-1, help_button.GetPosition()[1] + help_button.GetSize()[1]), size=(150, 30)) + return_button.Bind(wx.EVT_BUTTON, lambda event: frame.Close()) + return_button.Centre(wx.HORIZONTAL) + + # Set size of frame + frame.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40)) + + + + + diff --git a/resources/wx_gui/gui_install_oc.py b/resources/wx_gui/gui_install_oc.py new file mode 100644 index 000000000..6d0b38669 --- /dev/null +++ b/resources/wx_gui/gui_install_oc.py @@ -0,0 +1,347 @@ +import wx +import threading +import logging +import traceback + +from resources.wx_gui import gui_main_menu, gui_support, gui_sys_patch +from resources import constants, install + + +class InstallOCFrame(wx.Frame): + """ + Create a frame for installing OpenCore to disk + """ + def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None): + super(InstallOCFrame, self).__init__(parent, title=title, size=(300, 120), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) + + self.constants: constants.Constants = global_constants + self.title: str = title + self.result: bool = False + + self.available_disks: dict = None + self.stock_output = logging.getLogger().handlers[0].stream + + self.progress_bar_animation: gui_support.GaugePulseCallback = None + + self.hyperlink_colour = (25, 179, 231) + + self._generate_elements() + + if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: + self.constants.update_stage = gui_support.AutoUpdateStages.INSTALLING + + self.SetPosition(screen_location) if screen_location else self.Centre() + self.Show() + + self._display_disks() + + + def _generate_elements(self) -> None: + """ + Display indeterminate progress bar while collecting disk information + + Format: + - Title label: Install OpenCore + - Text: Fetching information on local disks... + - Progress bar: {indeterminate} + """ + + # Title label: Install OpenCore + title_label = wx.StaticText(self, label="Install OpenCore", pos=(-1,5)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # Text: Parsing local disks... + text_label = wx.StaticText(self, label="Fetching information on local disks...", pos=(-1,30)) + text_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + text_label.Centre(wx.HORIZONTAL) + self.text_label = text_label + + # Progress bar: {indeterminate} + progress_bar = wx.Gauge(self, range=100, pos=(-1, text_label.GetPosition()[1] + text_label.GetSize()[1]), size=(150, 30), style=wx.GA_HORIZONTAL | wx.GA_SMOOTH) + progress_bar.Centre(wx.HORIZONTAL) + + progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar) + progress_bar_animation.start_pulse() + + self.progress_bar_animation = progress_bar_animation + self.progress_bar = progress_bar + + + def _fetch_disks(self) -> None: + """ + Fetch information on local disks + """ + self.available_disks = install.tui_disk_installation(self.constants).list_disks() + + # Need to clean up output on pre-Sierra + # Disk images are mixed in with regular disks (ex. payloads.dmg) + ignore = ["disk image", "read-only", "virtual"] + for disk in self.available_disks.copy(): + if any(string in self.available_disks[disk]['name'].lower() for string in ignore): + del self.available_disks[disk] + + + def _display_disks(self) -> None: + """ + Display disk selection dialog + """ + thread = threading.Thread(target=self._fetch_disks) + thread.start() + + while thread.is_alive(): + wx.Yield() + continue + + self.progress_bar_animation.stop_pulse() + self.progress_bar.Hide() + + # Create wxDialog for disk selection + dialog = wx.Dialog(self, title=self.title, size=(380, -1)) + + # Title label: Install OpenCore + title_label = wx.StaticText(dialog, label="Install OpenCore", pos=(-1,5)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # Text: select disk to install OpenCore onto + text_label = wx.StaticText(dialog, label="Select disk to install OpenCore onto:", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5)) + text_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + text_label.Centre(wx.HORIZONTAL) + + # Add note: "Missing disks? Ensure they're FAT32 or formatted as GUID/GPT" + gpt_note = wx.StaticText(dialog, label="Missing disks? Ensure they're FAT32 or formatted as GUID/GPT", pos=(-1, text_label.GetPosition()[1] + text_label.GetSize()[1] + 5)) + gpt_note.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + gpt_note.Centre(wx.HORIZONTAL) + + # Add buttons for each disk + if self.available_disks: + # Only show booted disk if building for host + disk_root = self.constants.booted_oc_disk if self.constants.custom_model is None else None + if disk_root: + # disk6s1 -> disk6 + disk_root = self.constants.booted_oc_disk.strip("disk") + disk_root = "disk" + disk_root.split("s")[0] + logging.info(f"- Checking if booted disk is present: {disk_root}") + + # Add buttons for each disk + items = len(self.available_disks) + longest_label = max((len(self.available_disks[disk]['disk']) + len(self.available_disks[disk]['name']) + len(str(self.available_disks[disk]['size']))) for disk in self.available_disks) + longest_label = longest_label * 9 + spacer = 0 + for disk in self.available_disks: + # Create a button for each disk + logging.info(f"- {self.available_disks[disk]['disk']} - {self.available_disks[disk]['name']} - {self.available_disks[disk]['size']}") + disk_button = wx.Button(dialog, label=f"{self.available_disks[disk]['disk']} - {self.available_disks[disk]['name']} - {self.available_disks[disk]['size']}", size=(longest_label ,30), pos=(-1, gpt_note.GetPosition()[1] + gpt_note.GetSize()[1] + 5 + spacer)) + disk_button.Centre(wx.HORIZONTAL) + disk_button.Bind(wx.EVT_BUTTON, lambda event, disk=disk: self._display_volumes(disk, self.available_disks)) + if disk_root == self.available_disks[disk]['disk'] or items == 1: + disk_button.SetDefault() + spacer += 25 + + if disk_root: + # Add note: "Note: Blue represent the disk OpenCore is currently booted from" + disk_label = wx.StaticText(dialog, label="Note: Blue represent the disk OpenCore is currently booted from", pos=(-1, disk_button.GetPosition()[1] + disk_button.GetSize()[1] + 5)) + disk_label.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + disk_label.Centre(wx.HORIZONTAL) + else: + disk_label = wx.StaticText(dialog, label="", pos=(-1, disk_button.GetPosition()[1] + 15)) + disk_label.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + else: + # Text: Failed to find any applicable disks + disk_label = wx.StaticText(dialog, label="Failed to find any applicable disks", pos=(-1, gpt_note.GetPosition()[1] + gpt_note.GetSize()[1] + 5)) + disk_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + disk_label.Centre(wx.HORIZONTAL) + + # Add button: Search for disks again + search_button = wx.Button(dialog, label="Search for disks again", size=(160,30), pos=(-1, disk_label.GetPosition()[1] + disk_label.GetSize()[1] + 5)) + search_button.Centre(wx.HORIZONTAL) + search_button.Bind(wx.EVT_BUTTON, self.on_reload_frame) + + # Add button: Return to main menu + return_button = wx.Button(dialog, label="Return to main menu", size=(160,30), pos=(-1, search_button.GetPosition()[1] + 20)) + return_button.Centre(wx.HORIZONTAL) + return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) + + # Set size + dialog.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40)) + dialog.ShowWindowModal() + self.dialog = dialog + + + def _display_volumes(self, disk: str, dataset: dict) -> None: + """ + List volumes on disk + """ + + self.dialog.Close() + + # Create dialog + dialog = wx.Dialog( + self, + title=f"Volumes on {disk}", + style=wx.CAPTION | wx.CLOSE_BOX, + size=(300, 300) + ) + + # Add text: "Volumes on {disk}" + text_label = wx.StaticText(dialog, label=f"Volumes on {disk}", pos=(-1, 10)) + text_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + text_label.Centre(wx.HORIZONTAL) + + partitions = install.tui_disk_installation(self.constants).list_partitions(disk, dataset) + items = len(partitions) + longest_label = max((len(partitions[partition]['partition']) + len(partitions[partition]['name']) + len(str(partitions[partition]['size']))) for partition in partitions) + longest_label = longest_label * 10 + for partition in partitions: + logging.info(f"- {partitions[partition]['partition']} - {partitions[partition]['name']} - {partitions[partition]['size']}") + disk_button = wx.Button(dialog, label=f"{partitions[partition]['partition']} - {partitions[partition]['name']} - {partitions[partition]['size']}", size=(longest_label,30), pos=(-1, text_label.GetPosition()[1] + text_label.GetSize()[1] + 5)) + disk_button.Centre(wx.HORIZONTAL) + disk_button.Bind(wx.EVT_BUTTON, lambda event, partition=partition: self._install_oc_process(partition)) + if items == 1 or self.constants.booted_oc_disk == partitions[partition]['partition']: + disk_button.SetDefault() + + # Add button: Return to main menu + return_button = wx.Button(dialog, label="Return to main menu", size=(150,30), pos=(-1, disk_button.GetPosition()[1] + disk_button.GetSize()[1])) + return_button.Centre(wx.HORIZONTAL) + return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) + + # Set size + dialog.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40)) + + # Show dialog + dialog.ShowWindowModal() + self.dialog = dialog + + + def _install_oc_process(self, partition: dict) -> None: + """ + Install OpenCore to disk + """ + self.dialog.Close() + + # Create dialog + dialog = wx.Dialog( + self, + title=f"Installing OpenCore to {partition}", + style=wx.CAPTION | wx.CLOSE_BOX, + size=(370, 200) + ) + + # Add text: "Installing OpenCore to {partition}" + text_label = wx.StaticText(dialog, label=f"Installing OpenCore to {partition}", pos=(-1, 10)) + text_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + text_label.Centre(wx.HORIZONTAL) + + # Read-only text box: {empty} + text_box = wx.TextCtrl(dialog, value="", pos=(-1, text_label.GetPosition()[1] + text_label.GetSize()[1] + 10), size=(370, 200), style=wx.TE_READONLY | wx.TE_MULTILINE | wx.TE_RICH2) + text_box.Centre(wx.HORIZONTAL) + self.text_box = text_box + + # Add button: Return to main menu + return_button = wx.Button(dialog, label="Return to main menu", size=(200,30), pos=(-1, text_box.GetPosition()[1] + text_box.GetSize()[1] + 10)) + return_button.Centre(wx.HORIZONTAL) + return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) + return_button.Disable() + + # Set size + dialog.SetSize((370, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40)) + + # Show dialog + dialog.ShowWindowModal() + self.dialog = dialog + + # Install OpenCore + self._invoke_install_oc(partition) + return_button.Enable() + + + def _invoke_install_oc(self, partition: dict) -> None: + """ + Invoke OpenCore installation + """ + thread = threading.Thread(target=self._install_oc, args=(partition,)) + thread.start() + + while thread.is_alive(): + wx.Yield() + + if self.result is True: + if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: + self.constants.update_stage = gui_support.AutoUpdateStages.ROOT_PATCHING + popup_message = wx.MessageDialog( + self, + f"OpenCore has finished installing to disk.\n\nWould you like to update your root patches next?", "Success", + wx.YES_NO | wx.YES_DEFAULT + ) + popup_message.ShowModal() + if popup_message.GetReturnCode() == wx.ID_YES: + gui_sys_patch.SysPatchFrame( + parent=None, + title=self.title, + global_constants=self.constants, + screen_location=self.GetPosition() + ) + self.Destroy() + return + + elif not self.constants.custom_model: + gui_support.RestartHost(self).restart(message="OpenCore has finished installing to disk.\n\nYou will need to reboot and hold the Option key and select OpenCore/Boot EFI's option.\n\nWould you like to reboot?") + else: + popup_message = wx.MessageDialog( + self, + f"OpenCore has finished installing to disk.\n\nYou can eject the drive, insert it into the {self.constants.custom_model}, reboot, hold the Option key and select OpenCore/Boot EFI's option.", "Success", + wx.OK + ) + popup_message.ShowModal() + else: + if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: + self.constants.update_stage = gui_support.AutoUpdateStages.FINISHED + + + def _install_oc(self, partition: dict) -> None: + """ + Install OpenCore to disk + """ + logging.info(f"- Installing OpenCore to {partition}") + + logger = logging.getLogger() + logger.addHandler(gui_support.ThreadHandler(self.text_box)) + try: + self.result = install.tui_disk_installation(self.constants).install_opencore(partition) + except: + logging.error("- An internal error occurred while installing:\n") + logging.error(traceback.format_exc()) + logger.removeHandler(logger.handlers[2]) + + + def on_reload_frame(self, event: wx.Event = None) -> None: + """ + Reload frame + """ + self.Destroy() + frame = InstallOCFrame( + None, + title=self.title, + global_constants=self.constants, + screen_location=self.GetScreenPosition() + ) + frame.Show() + + + def on_return_to_main_menu(self, event: wx.Event = None) -> None: + """ + Return to main menu + """ + main_menu_frame = gui_main_menu.MainFrame( + None, + title=self.title, + global_constants=self.constants, + screen_location=self.GetScreenPosition() + ) + main_menu_frame.Show() + self.Destroy() + + + + diff --git a/resources/wx_gui/gui_macos_installer_download.py b/resources/wx_gui/gui_macos_installer_download.py new file mode 100644 index 000000000..35bd4e819 --- /dev/null +++ b/resources/wx_gui/gui_macos_installer_download.py @@ -0,0 +1,379 @@ +import wx +import logging +import threading +import webbrowser + +from pathlib import Path + +from resources.wx_gui import ( + gui_main_menu, + gui_support, + gui_download, + gui_macos_installer_flash +) +from resources import ( + constants, + macos_installer_handler, + utilities, + network_handler, + integrity_verification +) +from data import os_data, smbios_data, cpu_data + + +class macOSInstallerDownloadFrame(wx.Frame): + """ + Create a frame for downloading and creating macOS installers + Uses a Modal Dialog for smoother transition from other frames + Note: Flashing installers is passed to gui_macos_installer_flash.py + """ + def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None): + + self.constants: constants.Constants = global_constants + self.title: str = title + self.parent: wx.Frame = parent + + self.available_installers = None + self.available_installers_latest = None + + self.frame_modal = wx.Dialog(parent, title=title, size=(330, 200)) + + self._generate_elements(self.frame_modal) + self.frame_modal.ShowWindowModal() + + + def _generate_elements(self, frame: wx.Frame = None) -> None: + """ + Format: + - Title: Create macOS Installer + - Button: Download macOS Installer + - Button: Use existing macOS Installer + - Button: Return to Main Menu + """ + + frame = self if not frame else frame + + title_label = wx.StaticText(frame, label="Create macOS Installer", pos=(-1,5)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # Button: Download macOS Installer + download_button = wx.Button(frame, label="Download macOS Installer", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5), size=(200, 30)) + download_button.Bind(wx.EVT_BUTTON, self.on_download) + download_button.Centre(wx.HORIZONTAL) + + # Button: Use existing macOS Installer + existing_button = wx.Button(frame, label="Use existing macOS Installer", pos=(-1, download_button.GetPosition()[1] + download_button.GetSize()[1] - 5), size=(200, 30)) + existing_button.Bind(wx.EVT_BUTTON, self.on_existing) + existing_button.Centre(wx.HORIZONTAL) + + # Button: Return to Main Menu + return_button = wx.Button(frame, label="Return to Main Menu", pos=(-1, existing_button.GetPosition()[1] + existing_button.GetSize()[1] + 5), size=(150, 30)) + return_button.Bind(wx.EVT_BUTTON, self.on_return) + return_button.Centre(wx.HORIZONTAL) + + # Set size of frame + frame.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40)) + + + def _generate_catalog_frame(self) -> None: + """ + Generate frame to display available installers + """ + super(macOSInstallerDownloadFrame, self).__init__(None, title=self.title, size=(300, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) + self.SetPosition((self.parent.GetPosition()[0], self.parent.GetPosition()[1])) + + # Title: Pulling installer catalog + title_label = wx.StaticText(self, label="Pulling installer catalog", pos=(-1,5)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # Progress bar + progress_bar = wx.Gauge(self, range=100, pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5), size=(250, 30)) + progress_bar.Centre(wx.HORIZONTAL) + progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar) + progress_bar_animation.start_pulse() + + # Set size of frame + self.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40)) + + self.Show() + + # Grab installer catalog + def _fetch_installers(): + remote_obj = macos_installer_handler.RemoteInstallerCatalog(seed_override=macos_installer_handler.SeedType.DeveloperSeed) + self.available_installers = remote_obj.available_apps + self.available_installers_latest = remote_obj.available_apps_latest + + thread = threading.Thread(target=_fetch_installers) + thread.start() + + while thread.is_alive(): + wx.Yield() + + progress_bar_animation.stop_pulse() + progress_bar.Hide() + self._display_available_installers() + + + def _display_available_installers(self, event: wx.Event = None, show_full: bool = False) -> None: + """ + Display available installers in frame + """ + self.frame_modal.Destroy() + dialog = wx.Dialog(self, title="Select macOS Installer", size=(300, 200)) + + # Title: Select macOS Installer + title_label = wx.StaticText(dialog, label="Select macOS Installer", pos=(-1,5)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # Subtitle: Installers currently available from Apple: + subtitle_label = wx.StaticText(dialog, label="Installers currently available from Apple:", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5)) + subtitle_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + subtitle_label.Centre(wx.HORIZONTAL) + + # List of installers + installers = self.available_installers_latest if show_full is False else self.available_installers + if installers: + spacer = 0 + for app in installers: + logging.info(f"macOS {installers[app]['Version']} ({installers[app]['Build']}):\n - Size: {utilities.human_fmt(installers[app]['Size'])}\n - Source: {installers[app]['Source']}\n - Variant: {installers[app]['Variant']}\n - Link: {installers[app]['Link']}\n") + extra = " Beta" if installers[app]['Variant'] in ["DeveloperSeed" , "PublicSeed"] else "" + + installer_button = wx.Button(dialog, label=f"macOS {installers[app]['Version']}{extra} ({installers[app]['Build']} - {utilities.human_fmt(installers[app]['Size'])})", pos=(-1, subtitle_label.GetPosition()[1] + subtitle_label.GetSize()[1] + 5 + spacer), size=(270, 30)) + installer_button.Bind(wx.EVT_BUTTON, lambda event, temp=app: self.on_download_installer(installers[temp])) + installer_button.Centre(wx.HORIZONTAL) + spacer += 25 + + # Since installers are sorted by version, set the latest installer as the default button + # Note that on full display, the last installer is generally a beta + if show_full is False and app == list(installers.keys())[-1]: + installer_button.SetDefault() + + # Show all available installers + show_all_button = wx.Button(dialog, label="Show all available installers" if show_full is False else "Show only latest installers", pos=(-1, installer_button.GetPosition()[1] + installer_button.GetSize()[1]), size=(200, 30)) + show_all_button.Bind(wx.EVT_BUTTON, lambda event: self._display_available_installers(event, not show_full)) + show_all_button.Centre(wx.HORIZONTAL) + + # Return to Main Menu + return_button = wx.Button(dialog, label="Return to Main Menu", pos=(-1, show_all_button.GetPosition()[1] + show_all_button.GetSize()[1] - 7), size=(150, 30)) + return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) + return_button.Centre(wx.HORIZONTAL) + + # Set size of frame + dialog.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40)) + dialog.ShowWindowModal() + self.frame_modal = dialog + + + def on_download_installer(self, app: dict) -> None: + """ + Download macOS installer + """ + + # Notify user whether their model is compatible with the selected installer + problems = [] + model = self.constants.custom_model or self.constants.computer.real_model + if model in smbios_data.smbios_dictionary: + if app["OS"] >= os_data.os_data.ventura: + if smbios_data.smbios_dictionary[model]["CPU Generation"] <= cpu_data.cpu_data.penryn or model in ["MacPro4,1", "MacPro5,1", "Xserve3,1"]: + if model.startswith("MacBook"): + problems.append("Lack of internal Keyboard/Trackpad in macOS installer.") + else: + problems.append("Lack of internal Keyboard/Mouse in macOS installer.") + + if problems: + problems = "\n".join(problems) + dlg = wx.MessageDialog(self.frame_modal, f"Your model ({model}) may not be fully supported by this installer. You may encounter the following issues:\n\n{problems}\n\nFor more information, see associated page.", "Potential Issues", wx.YES_NO | wx.CANCEL | wx.ICON_WARNING) + dlg.SetYesNoCancelLabels("View Github Issue", "Download Anyways", "Cancel") + result = dlg.ShowModal() + if result == wx.ID_CANCEL: + return + elif result == wx.ID_YES: + webbrowser.open("https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1021") + return + + host_space = utilities.get_free_space() + needed_space = app['Size'] * 2 + if host_space < needed_space: + dlg = wx.MessageDialog(self.frame_modal, f"You do not have enough free space to download and extract this installer. Please free up some space and try again\n\n{utilities.human_fmt(host_space)} available vs {utilities.human_fmt(needed_space)} required", "Insufficient Space", wx.OK | wx.ICON_WARNING) + dlg.ShowModal() + return + + self.frame_modal.Close() + + download_obj = network_handler.DownloadObject(app['Link'], self.constants.payload_path / "InstallAssistant.pkg") + + gui_download.DownloadFrame( + self, + title=self.title, + global_constants=self.constants, + download_obj=download_obj, + item_name=f"macOS {app['Version']} ({app['Build']})", + ) + + if download_obj.download_complete is False: + self.on_return_to_main_menu() + return + + self._validate_installer(app['integrity']) + + + def _validate_installer(self, chunklist_link: str) -> None: + """ + Validate macOS installer + """ + self.SetSize((300, 200)) + for child in self.GetChildren(): + child.Destroy() + + # Title: Validating macOS Installer + title_label = wx.StaticText(self, label="Validating macOS Installer", pos=(-1,5)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # Label: Validating chunk 0 of 0 + chunk_label = wx.StaticText(self, label="Validating chunk 0 of 0", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5)) + chunk_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + chunk_label.Centre(wx.HORIZONTAL) + + # Progress bar + progress_bar = wx.Gauge(self, range=100, pos=(-1, chunk_label.GetPosition()[1] + chunk_label.GetSize()[1] + 5), size=(270, 30)) + progress_bar.Centre(wx.HORIZONTAL) + + # Set size of frame + self.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40)) + self.Show() + + chunklist_stream = network_handler.NetworkUtilities().get(chunklist_link).content + if chunklist_stream: + utilities.disable_sleep_while_running() + chunk_obj = integrity_verification.ChunklistVerification(self.constants.payload_path / Path("InstallAssistant.pkg"), chunklist_stream) + if chunk_obj.chunks: + progress_bar.SetValue(chunk_obj.current_chunk) + progress_bar.SetRange(chunk_obj.total_chunks) + + wx.App.Get().Yield() + chunk_obj.validate() + + while chunk_obj.status == integrity_verification.ChunklistStatus.IN_PROGRESS: + progress_bar.SetValue(chunk_obj.current_chunk) + chunk_label.SetLabel(f"Validating chunk {chunk_obj.current_chunk} of {chunk_obj.total_chunks}") + chunk_label.Centre(wx.HORIZONTAL) + wx.App.Get().Yield() + + if chunk_obj.status == integrity_verification.ChunklistStatus.FAILURE: + wx.MessageBox("Chunklist validation failed.\n\nThis generally happens when downloading on unstable connections such as WiFi or cellular.\n\nPlease try redownloading again on a stable connection (ie. Ethernet)", "Corrupted Installer!", wx.OK | wx.ICON_ERROR) + self.on_return_to_main_menu() + return + + # Extract installer + title_label.SetLabel("Extracting macOS Installer") + title_label.Centre(wx.HORIZONTAL) + + chunk_label.SetLabel("May take a few minutes...") + chunk_label.Centre(wx.HORIZONTAL) + + progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar) + progress_bar_animation.start_pulse() + + # Start thread to extract installer + self.result = False + def extract_installer(): + self.result = macos_installer_handler.InstallerCreation().install_macOS_installer(self.constants.payload_path) + + thread = threading.Thread(target=extract_installer) + thread.start() + + # Show frame + self.Show() + + # Wait for thread to finish + while thread.is_alive(): + wx.Yield() + + progress_bar_animation.stop_pulse() + progress_bar.Hide() + chunk_label.SetLabel("Successfully extracted macOS installer" if self.result is True else "Failed to extract macOS installer") + chunk_label.Centre(wx.HORIZONTAL) + + # Create macOS Installer button + create_installer_button = wx.Button(self, label="Create macOS Installer", pos=(-1, progress_bar.GetPosition()[1]), size=(170, 30)) + create_installer_button.Bind(wx.EVT_BUTTON, self.on_existing) + create_installer_button.Centre(wx.HORIZONTAL) + if self.result is False: + create_installer_button.Disable() + + # Return to main menu button + return_button = wx.Button(self, label="Return to Main Menu", pos=(-1, create_installer_button.GetPosition()[1] + create_installer_button.GetSize()[1]), size=(150, 30)) + return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) + return_button.Centre(wx.HORIZONTAL) + + # Set size of frame + self.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40)) + + # Show frame + self.Show() + + if self.result is False: + wx.MessageBox("An error occurred while extracting the macOS installer. Could be due to a corrupted installer", "Error", wx.OK | wx.ICON_ERROR) + return + + user_input = wx.MessageBox("Finished extracting the installer, would you like to continue and create a macOS installer?", "Create macOS Installer?", wx.YES_NO | wx.ICON_QUESTION) + if user_input == wx.YES: + self.on_existing() + + + def on_download(self, event: wx.Event) -> None: + """ + Display available macOS versions to download + """ + self.frame_modal.Close() + self.parent.Hide() + self._generate_catalog_frame() + self.parent.Close() + + + def on_existing(self, event: wx.Event = None) -> None: + """ + Display local macOS installers + """ + frames = [self, self.frame_modal, self.parent] + for frame in frames: + if frame: + frame.Close() + gui_macos_installer_flash.macOSInstallerFlashFrame( + None, + title=self.title, + global_constants=self.constants, + **({"screen_location": self.GetScreenPosition()} if self else {}) + ) + for frame in frames: + if frame: + frame.Destroy() + + + def on_return(self, event: wx.Event) -> None: + """ + Return to main menu (dismiss frame) + """ + self.frame_modal.Close() + + + def on_return_to_main_menu(self, event: wx.Event = None) -> None: + """ + Return to main menu + """ + if self.frame_modal: + self.frame_modal.Hide() + main_menu_frame = gui_main_menu.MainFrame( + None, + title=self.title, + global_constants=self.constants, + screen_location=self.GetScreenPosition() + ) + main_menu_frame.Show() + if self.frame_modal: + self.frame_modal.Destroy() + self.Destroy() \ No newline at end of file diff --git a/resources/wx_gui/gui_macos_installer_flash.py b/resources/wx_gui/gui_macos_installer_flash.py new file mode 100644 index 000000000..8e79f95e8 --- /dev/null +++ b/resources/wx_gui/gui_macos_installer_flash.py @@ -0,0 +1,571 @@ +import wx +import time +import logging +import plistlib +import tempfile +import threading +import subprocess + +from pathlib import Path + +from resources.wx_gui import gui_main_menu, gui_build, gui_support +from resources import ( + constants, + macos_installer_handler, + utilities, + network_handler, + kdk_handler, +) +from data import os_data + + +class macOSInstallerFlashFrame(wx.Frame): + + def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None): + super(macOSInstallerFlashFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) + + self.constants: constants.Constants = global_constants + self.title: str = title + + self.available_installers_local: dict = {} + self.available_disks: dict = {} + self.prepare_result: bool = False + + self.progress_bar_animation: gui_support.GaugePulseCallback = None + + self.frame_modal: wx.Dialog = None + + self._generate_elements() + + self.SetPosition(screen_location) if screen_location else self.Centre() + self.Show() + + self._populate_installers() + + + def _generate_elements(self) -> None: + """ + Fetches local macOS Installers for users to select from + """ + + # Title: Fetching local macOS Installers + title_label = wx.StaticText(self, label="Fetching local macOS Installers", pos=(-1,1)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # Progress bar + progress_bar = wx.Gauge(self, range=100, pos=(-1, 30), size=(200, 30)) + progress_bar.Centre(wx.HORIZONTAL) + + progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar) + progress_bar_animation.start_pulse() + self.progress_bar_animation = progress_bar_animation + + # Set size of frame + self.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40)) + + + def _populate_installers(self) -> None: + # Grab installer catalog + def fetch_installers(): + self.available_installers_local = macos_installer_handler.LocalInstallerCatalog().available_apps + + thread = threading.Thread(target=fetch_installers) + thread.start() + + while thread.is_alive(): + wx.Yield() + + frame_modal = wx.Dialog(self, title=self.title, size=(350, 200)) + frame_modal.Centre(wx.HORIZONTAL) + + # Title: Select macOS Installer + title_label = wx.StaticText(frame_modal, label="Select local macOS Installer", pos=(-1,5)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # List of installers + if self.available_installers_local: + logging.info("Installer(s) found:") + spacer = 10 + entries = len(self.available_installers_local) + for app in self.available_installers_local: + logging.info(f"- {self.available_installers_local[app]['Short Name']}: {self.available_installers_local[app]['Version']} ({self.available_installers_local[app]['Build']})") + + app_str = f"{self.available_installers_local[app]['Short Name']}" + unsupported: bool = self.available_installers_local[app]['Minimum Host OS'] > self.constants.detected_os + + if unsupported: + min_str = os_data.os_conversion.convert_kernel_to_marketing_name(self.available_installers_local[app]['Minimum Host OS']) + app_str += f" (Requires {min_str})" + else: + app_str += f": {self.available_installers_local[app]['Version']} ({self.available_installers_local[app]['Build']})" + + installer_button = wx.Button(frame_modal, label=app_str, pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + spacer), size=(300, 30)) + installer_button.Bind(wx.EVT_BUTTON, lambda event, temp=app: self.on_select(self.available_installers_local[temp])) + installer_button.Centre(wx.HORIZONTAL) + spacer += 25 + if unsupported: + installer_button.Disable() + elif entries == 1: + installer_button.SetDefault() + + else: + installer_button = wx.StaticText(frame_modal, label="No installers found in '/Applications'", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5)) + installer_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + installer_button.Centre(wx.HORIZONTAL) + + # Button: Return to Main Menu + cancel_button = wx.Button(frame_modal, label="Return to Main Menu", pos=(-1, installer_button.GetPosition()[1] + installer_button.GetSize()[1]), size=(150, 30)) + cancel_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) + cancel_button.Centre(wx.HORIZONTAL) + + # Set size of frame + frame_modal.SetSize((-1, cancel_button.GetPosition()[1] + cancel_button.GetSize()[1] + 40)) + + self.progress_bar_animation.stop_pulse() + + frame_modal.ShowWindowModal() + self.frame_modal = frame_modal + + + def on_select(self, installer: dict) -> None: + self.frame_modal.Destroy() + + for child in self.GetChildren(): + child.Destroy() + + # Fetching information on local disks + title_label = wx.StaticText(self, label="Fetching information on local disks", pos=(-1,1)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # Progress bar + progress_bar = wx.Gauge(self, range=100, pos=(-1, 30), size=(200, 30)) + progress_bar.Centre(wx.HORIZONTAL) + + progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar) + progress_bar_animation.start_pulse() + + # Set size of frame + self.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40)) + + # Fetch local disks + def _fetch_disks(): + self.available_disks = macos_installer_handler.InstallerCreation().list_disk_to_format() + + # Need to clean up output on pre-Sierra + # Disk images are mixed in with regular disks (ex. payloads.dmg) + ignore = ["disk image", "read-only", "virtual"] + for disk in self.available_disks.copy(): + if any(string in self.available_disks[disk]['name'].lower() for string in ignore): + del self.available_disks[disk] + + + thread = threading.Thread(target=_fetch_disks) + thread.start() + + while thread.is_alive(): + wx.Yield() + + self.frame_modal = wx.Dialog(self, title=self.title, size=(350, 200)) + + # Title: Select local disk + title_label = wx.StaticText(self.frame_modal, label="Select local disk", pos=(-1,5)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # Label: Selected USB will be erased, please backup any data + warning_label = wx.StaticText(self.frame_modal, label="Selected USB will be erased, please backup any data", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5)) + warning_label.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + warning_label.Centre(wx.HORIZONTAL) + + # List of disks + if self.available_disks: + spacer = 5 + entries = len(self.available_disks) + for disk in self.available_disks: + logging.info(f"{disk}: {self.available_disks[disk]['name']} - {utilities.human_fmt(self.available_disks[disk]['size'])}") + disk_button = wx.Button(self.frame_modal, label=f"{disk}: {self.available_disks[disk]['name']} - {utilities.human_fmt(self.available_disks[disk]['size'])}", pos=(-1, warning_label.GetPosition()[1] + warning_label.GetSize()[1] + spacer), size=(300, 30)) + disk_button.Bind(wx.EVT_BUTTON, lambda event, temp=disk: self.on_select_disk(self.available_disks[temp], installer)) + disk_button.Centre(wx.HORIZONTAL) + if entries == 1: + disk_button.SetDefault() + spacer += 25 + else: + disk_button = wx.StaticText(self.frame_modal, label="No disks found", pos=(-1, warning_label.GetPosition()[1] + warning_label.GetSize()[1] + 5)) + disk_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + disk_button.Centre(wx.HORIZONTAL) + + # Search for disks again + search_button = wx.Button(self.frame_modal, label="Search for disks again", pos=(-1, disk_button.GetPosition()[1] + disk_button.GetSize()[1]), size=(160, 30)) + search_button.Bind(wx.EVT_BUTTON, lambda event, temp=installer: self.on_select(temp)) + search_button.Centre(wx.HORIZONTAL) + + # Button: Return to Main Menu + cancel_button = wx.Button(self.frame_modal, label="Return to Main Menu", pos=(-1, search_button.GetPosition()[1] + search_button.GetSize()[1] - 10), size=(160, 30)) + cancel_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) + cancel_button.Centre(wx.HORIZONTAL) + + # Set size of frame + self.frame_modal.SetSize((-1, cancel_button.GetPosition()[1] + cancel_button.GetSize()[1] + 40)) + + progress_bar_animation.stop_pulse() + + self.frame_modal.ShowWindowModal() + + + def on_select_disk(self, disk: dict, installer: dict) -> None: + answer = wx.MessageBox(f"Are you sure you want to erase '{disk['name']}'?\nAll data will be lost, this cannot be undone.", "Confirmation", wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) + if answer != wx.YES: + return + + self.frame_modal.Destroy() + + for child in self.GetChildren(): + child.Destroy() + + self.SetSize((450, -1)) + + # Title: Creating Installer: {installer_name} + title_label = wx.StaticText(self, label=f"Creating Installer: {installer['Short Name']}", pos=(-1,1)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # Label: Creating macOS installers can take 30min+ on slower USB drives. + warning_label = wx.StaticText(self, label="Creating macOS installers can take 30min+ on slower USB drives.", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5)) + warning_label.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + warning_label.Centre(wx.HORIZONTAL) + + # Label: We will notify you when the installer is ready. + warning_label = wx.StaticText(self, label="We will notify you when the installer is ready.", pos=(-1, warning_label.GetPosition()[1] + warning_label.GetSize()[1] + 5)) + warning_label.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + warning_label.Centre(wx.HORIZONTAL) + + # Label: Bytes Written: 0 MB + bytes_written_label = wx.StaticText(self, label="Bytes Written: 0000.0 MB", pos=(-1, warning_label.GetPosition()[1] + warning_label.GetSize()[1] + 5)) + bytes_written_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + bytes_written_label.Centre(wx.HORIZONTAL) + + # Progress bar + progress_bar = wx.Gauge(self, range=100, pos=(-1, bytes_written_label.GetPosition()[1] + bytes_written_label.GetSize()[1] + 5), size=(300, 30)) + progress_bar.Centre(wx.HORIZONTAL) + + progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar) + progress_bar_animation.start_pulse() + + # Set size of frame + self.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40)) + self.Show() + + # Prepare resources + if self._prepare_resources(installer['Path'], disk['identifier']) is False: + wx.MessageBox("Failed to prepare resources, cannot continue.", "Error", wx.OK | wx.ICON_ERROR) + self.on_return_to_main_menu() + return + + # Base Size + estimated_size = 16000 + # AutoPkg (700MB~) + estimated_size += 700 if installer['OS'] >= os_data.os_data.big_sur else 0 + # KDK (700MB~, and overhead for copying to installer) + estimated_size += 700 * 2 if installer['OS'] >= os_data.os_data.ventura else 0 + + progress_bar_animation.stop_pulse() + progress_bar.SetRange(estimated_size) + + root_disk = disk['identifier'][5:] + initial_bytes_written = float(utilities.monitor_disk_output(root_disk)) + self.result = False + def _flash(): + self.result = self._flash_installer(root_disk) + + thread = threading.Thread(target=_flash) + thread.start() + + # Wait for installer to be created + while thread.is_alive(): + try: + total_bytes_written = float(utilities.monitor_disk_output(root_disk)) + except: + pass + bytes_written = total_bytes_written - initial_bytes_written + wx.CallAfter(bytes_written_label.SetLabel, f"Bytes Written: {bytes_written:.2f} MB") + wx.CallAfter(progress_bar.SetValue, int(bytes_written)) + wx.Yield() + + if self.result is False: + self.on_return_to_main_menu() + return + + # Next verify the installer + progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar) + progress_bar_animation.start_pulse() + + bytes_written_label.SetLabel("Validating Installer Integrity...") + error_message = self._validate_installer_pkg(disk['identifier']) + + progress_bar_animation.stop_pulse() + + if error_message != "": + progress_bar.SetValue(0) + wx.MessageBox(f"Failed to validate installer, cannot continue.\n This can generally happen due to a faulty USB drive, as flashing is an intensive process that can trigger hardware faults not normally seen. \n\n{error_message}", "Corrupted Installer!", wx.OK | wx.ICON_ERROR) + self.on_return_to_main_menu() + return + + progress_bar.SetValue(estimated_size) + + if gui_support.CheckProperties(self.constants).host_can_build() is False: + wx.MessageBox("Installer created successfully! If you want to install OpenCore to this USB, you will need to change the Target Model in settings", "Successfully created the macOS installer!", wx.OK | wx.ICON_INFORMATION) + self.on_return_to_main_menu() + return + + answer = wx.MessageBox("Installer created successfully, would you like to continue and Install OpenCore to this disk?", "Successfully created the macOS installer!", wx.YES_NO | wx.ICON_QUESTION) + if answer != wx.YES: + self.on_return_to_main_menu() + return + + # Install OpenCore + self.Hide() + gui_build.BuildFrame( + parent=None, + title=self.title, + global_constants=self.constants, + screen_location=self.GetPosition() + ) + self.Destroy() + + + def _prepare_resources(self, installer_path: str, disk: str) -> None: + + def prepare_script(self, installer_path: str, disk: str, constants: constants.Constants): + self.prepare_result = macos_installer_handler.InstallerCreation().generate_installer_creation_script(constants.payload_path, installer_path, disk) + + thread = threading.Thread(target=prepare_script, args=(self, installer_path, disk, self.constants)) + thread.start() + + while thread.is_alive(): + wx.Yield() + + return self.prepare_result + + + def _flash_installer(self, disk) -> bool: + utilities.disable_sleep_while_running() + logging.info("- Creating macOS installer") + + thread = threading.Thread(target=self._auto_package_handler) + thread.start() + + # print contents of installer.sh + with open(self.constants.installer_sh_path, "r") as f: + logging.info(f"- installer.sh contents:\n{f.read()}") + + args = [self.constants.oclp_helper_path, "/bin/sh", self.constants.installer_sh_path] + result = subprocess.run(args, capture_output=True, text=True) + output = result.stdout + error = result.stderr if result.stderr else "" + + if "Install media now available at" not in output: + logging.info("- Failed to create macOS installer") + popup = wx.MessageDialog(self, f"Failed to create macOS installer\n\nOutput: {output}\n\nError: {error}", "Error", wx.OK | wx.ICON_ERROR) + popup.ShowModal() + return False + + logging.info("- Successfully created macOS installer") + while thread.is_alive(): + # wait for download_thread to finish + # though highly unlikely this thread is still alive (flashing an Installer will take a while) + time.sleep(0.1) + logging.info("- Installing Root Patcher to drive") + self._install_installer_pkg(disk) + + utilities.enable_sleep_after_running() + return True + + + def _auto_package_handler(self): + """ + Function's main goal is to grab the correct AutoPkg-Assets.pkg and unzip it + Note the following: + - When running a release build, pull from Github's release page with the same versioning + - When running from source/unable to find on Github, use the nightly.link variant + - If nightly also fails, fall back to the manually uploaded variant + """ + link = self.constants.installer_pkg_url + if network_handler.NetworkUtilities(link).validate_link() is False: + logging.info("- Stock Install.pkg is missing on Github, falling back to Nightly") + link = self.constants.installer_pkg_url_nightly + + if link.endswith(".zip"): + path = self.constants.installer_pkg_zip_path + else: + path = self.constants.installer_pkg_path + + autopkg_download = network_handler.DownloadObject(link, path) + autopkg_download.download(spawn_thread=False) + + if autopkg_download.download_complete is False: + logging.warning("- Failed to download Install.pkg") + logging.warning(autopkg_download.error_msg) + return + + # Download thread will re-enable Idle Sleep after downloading + utilities.disable_sleep_while_running() + if not str(path).endswith(".zip"): + return + if Path(self.constants.installer_pkg_path).exists(): + subprocess.run(["rm", self.constants.installer_pkg_path]) + subprocess.run(["ditto", "-V", "-x", "-k", "--sequesterRsrc", "--rsrc", self.constants.installer_pkg_zip_path, self.constants.payload_path]) + + + def _install_installer_pkg(self, disk): + disk = disk + "s2" # ESP sits at 1, and we know macOS will have created the main partition at 2 + + if not Path(self.constants.installer_pkg_path).exists(): + return + + path = utilities.grab_mount_point_from_disk(disk) + if not Path(path + "/System/Library/CoreServices/SystemVersion.plist").exists(): + return + + os_version = plistlib.load(Path(path + "/System/Library/CoreServices/SystemVersion.plist").open("rb")) + kernel_version = os_data.os_conversion.os_to_kernel(os_version["ProductVersion"]) + if int(kernel_version) < os_data.os_data.big_sur: + logging.info("- Installer unsupported, requires Big Sur or newer") + return + + subprocess.run(["mkdir", "-p", f"{path}/Library/Packages/"]) + subprocess.run(["cp", "-r", self.constants.installer_pkg_path, f"{path}/Library/Packages/"]) + + self._kdk_chainload(os_version["ProductBuildVersion"], os_version["ProductVersion"], Path(path + "/Library/Packages/")) + + + def _kdk_chainload(self, build: str, version: str, download_dir: str): + """ + Download the correct KDK to be chainloaded in the macOS installer + + Parameters + build (str): The build number of the macOS installer (e.g. 20A5343j) + version (str): The version of the macOS installer (e.g. 11.0.1) + """ + + kdk_dmg_path = Path(download_dir) / "KDK.dmg" + kdk_pkg_path = Path(download_dir) / "KDK.pkg" + + if kdk_dmg_path.exists(): + kdk_dmg_path.unlink() + if kdk_pkg_path.exists(): + kdk_pkg_path.unlink() + + logging.info("- Initiating KDK download") + logging.info(f" - Build: {build}") + logging.info(f" - Version: {version}") + logging.info(f" - Working Directory: {download_dir}") + + kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, build, version, ignore_installed=True) + if kdk_obj.success is False: + logging.info("- Failed to retrieve KDK") + logging.info(kdk_obj.error_msg) + return + + kdk_download_obj = kdk_obj.retrieve_download(override_path=kdk_dmg_path) + if kdk_download_obj is None: + logging.info("- Failed to retrieve KDK") + logging.info(kdk_obj.error_msg) + + # Check remaining disk space before downloading + space = utilities.get_free_space(download_dir) + if space < (kdk_obj.kdk_url_expected_size * 2): + logging.info("- Not enough disk space to download and install KDK") + logging.info(f"- Attempting to download locally first") + if space < kdk_obj.kdk_url_expected_size: + logging.info("- Not enough disk space to install KDK, skipping") + return + # Ideally we'd download the KDK onto the disk to display progress in the UI + # However we'll just download to our temp directory and move it to the target disk + kdk_dmg_path = self.constants.kdk_download_path + + kdk_download_obj.download(spawn_thread=False) + if kdk_download_obj.download_complete is False: + logging.info("- Failed to download KDK") + logging.info(kdk_download_obj.error_msg) + return + + if not kdk_dmg_path.exists(): + logging.info(f"- KDK missing: {kdk_dmg_path}") + return + + # Now that we have a KDK, extract it to get the pkg + with tempfile.TemporaryDirectory() as mount_point: + logging.info("- Mounting KDK") + result = subprocess.run(["hdiutil", "attach", kdk_dmg_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if result.returncode != 0: + logging.info("- Failed to mount KDK") + logging.info(result.stdout.decode("utf-8")) + return + + logging.info("- Copying KDK") + subprocess.run(["cp", "-r", f"{mount_point}/KernelDebugKit.pkg", kdk_pkg_path]) + + logging.info("- Unmounting KDK") + result = subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if result.returncode != 0: + logging.info("- Failed to unmount KDK") + logging.info(result.stdout.decode("utf-8")) + return + + logging.info("- Removing KDK Disk Image") + kdk_dmg_path.unlink() + + def _validate_installer_pkg(self, disk: str) -> bool: + verification_success = False + error_message = "" + def _integrity_check(): + nonlocal error_message + path = utilities.grab_mount_point_from_disk(disk + "s2") + dmg_path = path + f"/{path.split('/')[2]}.app/Contents/SharedSupport/SharedSupport.dmg" + if not Path(dmg_path).exists(): + logging.error(f"Failed to find {dmg_path}") + error_message = f"Failed to find {dmg_path}" + return error_message + result = subprocess.run(["hdiutil", "verify", dmg_path],stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode != 0: + if result.stdout: + logging.error(result.stdout.decode("utf-8")) + error_message = "STDOUT: " + result.stdout.decode("utf-8") + if result.stderr: + logging.error(result.stderr.decode("utf-8")) + error_message += "\n\nSTDERR: " + result.stderr.decode("utf-8") + + + thread = threading.Thread(target=_integrity_check) + thread.start() + while thread.is_alive(): + wx.Yield() + + if verification_success: + return error_message + + logging.error(error_message) + return error_message + + + def on_return_to_main_menu(self, event: wx.Event = None): + if self.frame_modal: + self.frame_modal.Hide() + if self: + if isinstance(self, wx.Frame): + self.Hide() + main_menu_frame = gui_main_menu.MainFrame( + None, + title=self.title, + global_constants=self.constants, + screen_location=self.GetScreenPosition() + ) + main_menu_frame.Show() + if self.frame_modal: + self.frame_modal.Destroy() + if self: + if isinstance(self, wx.Frame): + self.Destroy() \ No newline at end of file diff --git a/resources/wx_gui/gui_main_menu.py b/resources/wx_gui/gui_main_menu.py new file mode 100644 index 000000000..856742a6b --- /dev/null +++ b/resources/wx_gui/gui_main_menu.py @@ -0,0 +1,241 @@ +# Generate GUI for main menu +import wx +import sys +import logging +import threading + +from resources.wx_gui import ( + gui_build, + gui_macos_installer_download, + gui_sys_patch, + gui_support, + gui_help, + gui_settings, + gui_update, +) +from resources import ( + constants, + global_settings, + updates +) +from data import os_data + + +class MainFrame(wx.Frame): + def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None): + super(MainFrame, self).__init__(parent, title=title, size=(350, 300), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) + + self.constants: constants.Constants = global_constants + self.title: str = title + + self.model_label: wx.StaticText = None + self.build_button: wx.Button = None + + self.constants.update_stage = gui_support.AutoUpdateStages.INACTIVE + + self._generate_elements() + + self.SetPosition(screen_location) if screen_location else self.Centre() + self.Show() + + self._preflight_checks() + + + def _generate_elements(self) -> None: + """ + Generate UI elements for the main menu + + Format: + - Title label: OpenCore Legacy Patcher v{X.Y.Z} + - Text: Model: {Build or Host Model} + - Buttons: + - Build and Install OpenCore + - Post-Install Root Patch + - Create macOS Installer + - Settings + - Help + - Text: Copyright + """ + + # 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,1)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # Text: Model: {Build or Host Model} + model_label = wx.StaticText(self, label=f"Model: {self.constants.custom_model or self.constants.computer.real_model}", pos=(-1,30)) + model_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + model_label.Centre(wx.HORIZONTAL) + self.model_label = model_label + + # Buttons: + menu_buttons = { + "Build and Install OpenCore": self.on_build_and_install, + "Post-Install Root Patch": self.on_post_install_root_patch, + "Create macOS Installer": self.on_create_macos_installer, + "Settings": self.on_settings, + "Help": self.on_help + } + button_y = model_label.GetPosition()[1] + 20 + for button_name, button_function in menu_buttons.items(): + button = wx.Button(self, label=button_name, pos=(-1, button_y), size=(200, 30)) + button.Bind(wx.EVT_BUTTON, button_function) + button.Centre(wx.HORIZONTAL) + button_y += 30 + + if button_name == "Build and Install OpenCore": + self.build_button = button + if gui_support.CheckProperties(self.constants).host_can_build() is False: + button.Disable() + elif button_name == "Post-Install Root Patch": + if self.constants.detected_os < os_data.os_data.big_sur: + button.Disable() + + # Text: Copyright + copy_label = wx.StaticText(self, label=self.constants.copyright_date, pos=(-1, button_y + 10)) + copy_label.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + copy_label.Centre(wx.HORIZONTAL) + + # Set window size + self.SetSize((350, copy_label.GetPosition()[1] + 50)) + + + def _preflight_checks(self): + if ( + self.constants.computer.build_model != None and + self.constants.computer.build_model != self.constants.computer.real_model and + self.constants.host_is_hackintosh is False + ): + # Notify user they're booting an unsupported configuration + pop_up = wx.MessageDialog( + self, + f"We found you are currently booting OpenCore built for a different unit: {self.constants.computer.build_model}\n\nWe builds configs to match individual units and cannot be mixed or reused with different Macs.\n\nPlease Build and Install a new OpenCore config, and reboot your Mac.", + "Unsupported Configuration Detected!", + style=wx.OK | wx.ICON_EXCLAMATION + ) + pop_up.ShowModal() + self.on_build_and_install() + return + + if "--update_installed" in sys.argv and self.constants.has_checked_updates is False and gui_support.CheckProperties(self.constants).host_can_build(): + # Notify user that the update has been installed + self.constants.has_checked_updates = True + pop_up = wx.MessageDialog( + self, + f"OpenCore Legacy Patcher has been updated to the latest version: {self.constants.patcher_version}\n\nWould you like to update OpenCore and your root volume patches?", + "Update successful!", + style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_INFORMATION + ) + pop_up.ShowModal() + + if pop_up.GetReturnCode() != wx.ID_YES: + print("- Skipping OpenCore and root volume patch update...") + return + + + print("- Updating OpenCore and root volume patches...") + self.constants.update_stage = gui_support.AutoUpdateStages.CHECKING + self.Hide() + pos = self.GetPosition() + gui_build.BuildFrame( + parent=None, + title=self.title, + global_constants=self.constants, + screen_location=pos + ) + self.Close() + + threading.Thread(target=self._check_for_updates).start() + + + def _check_for_updates(self): + if self.constants.has_checked_updates is True: + return + + ignore_updates = global_settings.GlobalEnviromentSettings().read_property("IgnoreAppUpdates") + if ignore_updates is True: + self.constants.ignore_updates = True + return + + self.constants.ignore_updates = False + self.constants.has_checked_updates = True + dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates() + 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", "Always Ignore", "Ignore Once") + response = dialog.ShowModal() + + if response == wx.ID_YES: + wx.CallAfter(self.on_update, dict[entry]["Link"], version) + elif response == wx.ID_NO: + logging.info("- Setting IgnoreAppUpdates to True") + self.constants.ignore_updates = True + global_settings.GlobalEnviromentSettings().write_property("IgnoreAppUpdates", True) + + + def on_build_and_install(self, event: wx.Event = None): + self.Hide() + gui_build.BuildFrame( + parent=None, + title=self.title, + global_constants=self.constants, + screen_location=self.GetPosition() + ) + self.Destroy() + + + def on_post_install_root_patch(self, event: wx.Event = None): + self.Hide() + gui_sys_patch.SysPatchFrame( + parent=None, + title=self.title, + global_constants=self.constants, + screen_location=self.GetPosition() + ) + self.Destroy() + + + def on_create_macos_installer(self, event: wx.Event = None): + gui_macos_installer_download.macOSInstallerDownloadFrame( + parent=self, + title=self.title, + global_constants=self.constants, + screen_location=self.GetPosition() + ) + + + def on_settings(self, event: wx.Event = None): + gui_settings.SettingsFrame( + parent=self, + title=self.title, + global_constants=self.constants, + screen_location=self.GetPosition() + ) + + def on_help(self, event: wx.Event = None): + gui_help.HelpFrame( + parent=self, + title=self.title, + global_constants=self.constants, + screen_location=self.GetPosition() + ) + + def on_update(self, oclp_url: str, oclp_version: str): + gui_update.UpdateFrame( + parent=self, + title=self.title, + global_constants=self.constants, + screen_location=self.GetPosition(), + url=oclp_url, + version_label=oclp_version + ) \ No newline at end of file diff --git a/resources/wx_gui/gui_settings.py b/resources/wx_gui/gui_settings.py new file mode 100644 index 000000000..a7ef5f53e --- /dev/null +++ b/resources/wx_gui/gui_settings.py @@ -0,0 +1,1230 @@ +import wx +import wx.adv +import pprint +import logging +import py_sip_xnu +import subprocess + +from pathlib import Path + +from resources.wx_gui import ( + gui_support, + gui_update +) +from resources import ( + constants, + global_settings, + defaults, + generate_smbios, + network_handler +) +from data import ( + model_array, + sip_data, + smbios_data, + os_data, + cpu_data +) + + +class SettingsFrame(wx.Frame): + """ + Modal-based Settings Frame + """ + def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None): + self.constants: constants.Constants = global_constants + self.title: str = title + self.parent: wx.Frame = parent + + self.hyperlink_colour = (25, 179, 231) + + self.settings = self._settings() + + self.frame_modal = wx.Dialog(parent, title=title, size=(600, 675)) + + self._generate_elements(self.frame_modal) + self.frame_modal.ShowWindowModal() + + def _generate_elements(self, frame: wx.Frame = None) -> None: + """ + Generates elements for the Settings Frame + Uses wx.Notebook to implement a tabbed interface + and relies on 'self._settings()' for populating + """ + + notebook = wx.Notebook(frame) + notebook.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.AddSpacer(10) + + model_label = wx.StaticText(frame, label="Target Model", pos=(-1, -1)) + model_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + sizer.Add(model_label, 0, wx.ALIGN_CENTER | wx.ALL, 5) + + model_choice = wx.Choice(frame, choices=model_array.SupportedSMBIOS + ["Host Model"], pos=(-1, -1)) + model_choice.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + model_choice.Bind(wx.EVT_CHOICE, lambda event: self.on_model_choice(event, model_choice)) + selection = self.constants.custom_model if self.constants.custom_model else "Host Model" + model_choice.SetSelection(model_choice.FindString(selection)) + sizer.Add(model_choice, 0, wx.ALIGN_CENTER | wx.ALL, 5) + + model_description = wx.StaticText(frame, label="Overrides Mac Model Patcher will build for.", pos=(-1, -1)) + model_description.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + sizer.Add(model_description, 0, wx.ALIGN_CENTER | wx.ALL, 5) + + tabs = list(self.settings.keys()) + if not Path("~/.dortania_developer").expanduser().exists(): + tabs.remove("Developer") + for tab in tabs: + panel = wx.Panel(notebook) + notebook.AddPage(panel, tab) + + sizer.Add(notebook, 1, wx.EXPAND | wx.ALL, 10) + + # Add return button + return_button = wx.Button(frame, label="Return", pos=(-1, -1), size=(100, 30)) + return_button.Bind(wx.EVT_BUTTON, self.on_return) + return_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + sizer.Add(return_button, 0, wx.ALIGN_CENTER | wx.ALL, 10) + + frame.SetSizer(sizer) + + horizontal_center = frame.GetSize()[0] / 2 + for tab in tabs: + if tab not in self.settings: + continue + + stock_height = 0 + stock_width = 20 + + height = stock_height + width = stock_width + + lowest_height_reached = height + highest_height_reached = height + + panel = notebook.GetPage(tabs.index(tab)) + + for setting, setting_info in self.settings[tab].items(): + if setting_info["type"] == "populate": + # execute populate function + if setting_info["args"] == wx.Frame: + setting_info["function"](panel) + else: + raise Exception("Invalid populate function") + continue + + if setting_info["type"] == "title": + stock_height = lowest_height_reached + height = stock_height + width = stock_width + + height += 10 + + # Add title + title = wx.StaticText(panel, label=setting, pos=(-1, -1)) + title.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + + title.SetPosition((int(horizontal_center) - int(title.GetSize()[0] / 2) - 15, height)) + highest_height_reached = height + title.GetSize()[1] + 10 + height += title.GetSize()[1] + 10 + continue + + if setting_info["type"] == "sub_title": + # Add sub-title + sub_title = wx.StaticText(panel, label=setting, pos=(-1, -1)) + sub_title.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + + sub_title.SetPosition((int(horizontal_center) - int(sub_title.GetSize()[0] / 2) - 15, height)) + highest_height_reached = height + sub_title.GetSize()[1] + 10 + height += sub_title.GetSize()[1] + 10 + continue + + if setting_info["type"] == "wrap_around": + height = highest_height_reached + width = 300 if width is stock_width else stock_width + continue + + if setting_info["type"] == "checkbox": + # Add checkbox, and description underneath + checkbox = wx.CheckBox(panel, label=setting, pos=(10 + width, 10 + height), size = (300,-1)) + checkbox.SetValue(setting_info["value"] if setting_info["value"] else False) + checkbox.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + event = lambda event, warning=setting_info["warning"] if "warning" in setting_info else "", override=bool(setting_info["override_function"]) if "override_function" in setting_info else False: self.on_checkbox(event, warning, override) + checkbox.Bind(wx.EVT_CHECKBOX, event) + if "condition" in setting_info: + checkbox.Enable(setting_info["condition"]) + if setting_info["condition"] is False: + checkbox.SetValue(False) + + elif setting_info["type"] == "spinctrl": + # Add spinctrl, and description underneath + spinctrl = wx.SpinCtrl(panel, value=str(setting_info["value"]), pos=(width - 20, 10 + height), min=setting_info["min"], max=setting_info["max"], size = (45,-1)) + spinctrl.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + spinctrl.Bind(wx.EVT_TEXT, lambda event, variable=setting: self.on_spinctrl(event, variable)) + # Add label next to spinctrl + label = wx.StaticText(panel, label=setting, pos=(spinctrl.GetSize()[0] + width - 16, spinctrl.GetPosition()[1])) + label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + elif setting_info["type"] == "combobox": + # Title + title = wx.StaticText(panel, label=setting, pos=(width + 30, 10 + height)) + title.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + height += title.GetSize()[1] + 10 + + # Add combobox, and description underneath + combobox = wx.ComboBox(panel, value=setting_info["value"], pos=(width + 25, 10 + height), choices=setting_info["choices"], size = (130,-1)) + combobox.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + # combobox.Bind(wx.EVT_COMBOBOX, lambda event, variable=setting: self.on_combobox(event, variable)) + if "override_function" in setting_info: + combobox.Bind(wx.EVT_COMBOBOX, lambda event, variable=setting: self.settings[tab][variable]["override_function"](event)) + height += 10 + elif setting_info["type"] == "button": + button = wx.Button(panel, label=setting, pos=(width + 25, 10 + height), size = (200,-1)) + button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + button.Bind(wx.EVT_BUTTON, lambda event, variable=setting: self.settings[tab][variable]["function"](event)) + height += 10 + + else: + raise Exception("Invalid setting type") + + lines = '\n'.join(setting_info["description"]) + description = wx.StaticText(panel, label=lines, pos=(30 + width, 10 + height + 20)) + description.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + height += 40 + + # Check number of lines in description, and adjust spacer accordingly + for i, line in enumerate(lines.split('\n')): + if line == "": + continue + if i == 0: + height += 11 + else: + height += 13 + + if height > lowest_height_reached: + lowest_height_reached = height + + + def _settings(self) -> dict: + """ + Generates a dictionary of settings to be used in the GUI + General format: + { + "Tab Name": { + "type": "title" | "checkbox" | "spinctrl" | "populate" | "wrap_around", + "value": bool | int | str, + "variable": str, (Variable name) + "constants_variable": str, (Constants variable name, if different from "variable") + "description": [str, str, str], (List of strings) + "warning": str, (Optional) (Warning message to be displayed when checkbox is checked) + "override_function": function, (Optional) (Function to be executed when checkbox is checked) + } + } + """ + + models = [model for model in smbios_data.smbios_dictionary if "_" not in model and " " not in model and smbios_data.smbios_dictionary[model]["Board ID"] is not None] + socketed_gpu_models = ["iMac9,1", "iMac10,1", "iMac11,1", "iMac11,2", "iMac11,3", "iMac12,1", "iMac12,2", "MacPro3,1", "MacPro4,1", "MacPro5,1", "Xserve2,1", "Xserve3,1"] + + settings = { + "Build": { + "General": { + "type": "title", + }, + "FireWire Booting": { + "type": "checkbox", + "value": self.constants.firewire_boot, + "variable": "firewire_boot", + "description": [ + "Enable booting macOS from", + "FireWire drives.", + ], + "condition": not (generate_smbios.check_firewire(self.constants.custom_model or self.constants.computer.real_model) is False) + }, + "XHCI Booting": { + "type": "checkbox", + "value": self.constants.xhci_boot, + "variable": "xhci_boot", + "description": [ + "Enable booting macOS from add-in", + "USB 3.0 expansion cards on systems", + "without native support.", + ], + "condition": not gui_support.CheckProperties(self.constants).host_has_cpu_gen(cpu_data.cpu_data.ivy_bridge) # Sandy Bridge and older do not natively support XHCI booting + }, + "NVMe Booting": { + "type": "checkbox", + "value": self.constants.nvme_boot, + "variable": "nvme_boot", + "description": [ + "Enable booting macOS from NVMe", + "drives on systems without native", + "support.", + "Note: Requires Firmware support", + "for OpenCore to load from NVMe.", + ], + "condition": not gui_support.CheckProperties(self.constants).host_has_cpu_gen(cpu_data.cpu_data.ivy_bridge) # Sandy Bridge and older do not natively support NVMe booting + }, + "wrap_around 2": { + "type": "wrap_around", + }, + "Content Caching": { + "type": "checkbox", + "value": self.constants.set_content_caching, + "variable": "set_content_caching", + "description": [ + # "Enable Content Caching.", + ], + }, + "APFS Trim": { + "type": "checkbox", + "value": self.constants.apfs_trim_timeout, + "variable": "apfs_trim_timeout", + "description": [ + "Recommended for all users, however faulty", + "SSDs may benefit from disabling this.", + ], + + }, + "Show OpenCore Boot Picker": { + "type": "checkbox", + "value": self.constants.showpicker, + "variable": "showpicker", + "description": [ + "When disabled, users can hold ESC to", + "show picker in the firmware.", + ], + }, + "Boot Picker Timeout": { + "type": "spinctrl", + "value": self.constants.oc_timeout, + "variable": "oc_timeout", + "description": [ + "Timeout before boot picker selects default", + "entry in seconds.", + "Set to 0 for no timeout.", + ], + + "min": 0, + "max": 60, + }, + "Debug": { + "type": "title", + }, + + "Verbose": { + "type": "checkbox", + "value": self.constants.verbose_debug, + "variable": "verbose_debug", + "description": [ + "Verbose output during boot.", + ], + + }, + "Kext Debugging": { + "type": "checkbox", + "value": self.constants.kext_debug, + "variable": "kext_debug", + "description": [ + "Use DEBUG variants of kexts and", + "enables additional kernel logging.", + ], + }, + "wrap_around 1": { + "type": "wrap_around", + }, + "OpenCore Debugging": { + "type": "checkbox", + "value": self.constants.opencore_debug, + "variable": "opencore_debug", + "description": [ + "Use DEBUG variant of OpenCore", + "and enables additional logging.", + ], + }, + }, + "Extras": { + "General (Continued)": { + "type": "title", + }, + "Wake on WLAN": { + "type": "checkbox", + "value": self.constants.enable_wake_on_wlan, + "variable": "enable_wake_on_wlan", + "description": [ + "Disabled by default due to", + "performance degradation", + "on some systems from wake.", + ], + }, + "Disable Thunderbolt": { + "type": "checkbox", + "value": self.constants.disable_tb, + "variable": "disable_tb", + "description": [ + "For MacBookPro11,x with faulty", + "PCHs that may crash sporadically.", + ], + "condition": (self.constants.custom_model and self.constants.custom_model in ["MacBookPro11,1", "MacBookPro11,2", "MacBookPro11,3"]) or self.constants.computer.real_model in ["MacBookPro11,1", "MacBookPro11,2", "MacBookPro11,3"] + }, + "Windows GMUX": { + "type": "checkbox", + "value": self.constants.dGPU_switch, + "variable": "dGPU_switch", + "description": [ + "Allow iGPU to be exposed in Windows", + "for dGPU-based MacBooks.", + ], + }, + "Disable CPUFriend": { + "type": "checkbox", + "value": self.constants.disallow_cpufriend, + "variable": "disallow_cpufriend", + "description": [ + "Disables power management helper", + "for unsupported models.", + ], + }, + "wrap_around 1": { + "type": "wrap_around", + }, + "Allow AppleALC Audio": { + "type": "checkbox", + "value": self.constants.set_alc_usage, + "variable": "set_alc_usage", + "description": [ + "Allow AppleALC to manage audio", + "if applicable.", + "Only disable if your host lacks", + "a GOP ROM.", + ], + }, + "NVRAM WriteFlash": { + "type": "checkbox", + "value": self.constants.nvram_write, + "variable": "nvram_write", + "description": [ + "Allow OpenCore to write to NVRAM.", + "Disable on systems with faulty or", + "degraded NVRAM.", + ], + }, + + "3rd Party NVMe PM": { + "type": "checkbox", + "value": self.constants.allow_nvme_fixing, + "variable": "allow_nvme_fixing", + "description": [ + "Enable non-stock NVMe power", + "management in macOS.", + ], + }, + "3rd Party SATA PM": { + "type": "checkbox", + "value": self.constants.allow_3rd_party_drives, + "variable": "allow_3rd_party_drives", + "description": [ + "Enable non-stock SATA power", + "management in macOS.", + ], + "condition": not bool(self.constants.computer.third_party_sata_ssd is False and not self.constants.custom_model) + }, + }, + "Advanced": { + "Miscellaneous": { + "type": "title", + }, + "Disable Firmware Throttling": { + "type": "checkbox", + "value": self.constants.disable_fw_throttle, + "variable": "disable_fw_throttle", + "description": [ + "Disables firmware-based throttling", + "caused by missing hardware.", + "Ex. Missing Display, Battery, etc.", + ], + }, + "Software DeMUX": { + "type": "checkbox", + "value": self.constants.software_demux, + "variable": "software_demux", + "description": [ + "Enable software based DeMUX", + "for MacBookPro8,2 and MacBookPro8,3.", + "Prevents faulty dGPU from turning on.", + "Note: Requires associated NVRAM arg:", + "'gpu-power-prefs'.", + ], + "warning": "This settings requires 'gpu-power-prefs' NVRAM argument to be set to '1'.\n\nIf missing and this option is toggled, the system will not boot\n\nFull command:\nnvram FA4CE28D-B62F-4C99-9CC3-6815686E30F9:gpu-power-prefs=%01%00%00%00", + "condition": not bool((not self.constants.custom_model and self.constants.computer.real_model not in ["MacBookPro8,2", "MacBookPro8,3"]) or (self.constants.custom_model and self.constants.custom_model not in ["MacBookPro8,2", "MacBookPro8,3"])) + }, + "wrap_around 1": { + "type": "wrap_around", + }, + "FeatureUnlock": { + "type": "combobox", + "choices": [ + "Enabled", + "Partial", + "Disabled", + ], + "value": "Enabled", + "variable": "", + "description": [ + "Configure FeatureUnlock level.", + "Recommend lowering if your system", + "experiences memory instability.", + ], + }, + "Populate FeatureUnlock Override": { + "type": "populate", + "function": self._populate_fu_override, + "args": wx.Frame, + }, + "Hibernation Work-around": { + "type": "checkbox", + "value": self.constants.disable_connectdrivers, + "variable": "disable_connectdrivers", + "description": [ + "Only load minimum EFI drivers", + "to prevent hibernation issues.", + "Note: This may break booting from", + "external drives.", + ], + }, + "Graphics": { + "type": "title", + }, + "AMD GOP Injection": { + "type": "checkbox", + "value": self.constants.amd_gop_injection, + "variable": "amd_gop_injection", + "description": [ + "Inject AMD GOP for boot screen", + "support on PC GPUs.", + ], + "condition": not bool((not self.constants.custom_model and self.constants.computer.real_model not in socketed_gpu_models) or (self.constants.custom_model and self.constants.custom_model not in socketed_gpu_models)) + }, + "Nvidia GOP Injection": { + "type": "checkbox", + "value": self.constants.nvidia_kepler_gop_injection, + "variable": "nvidia_kepler_gop_injection", + "description": [ + "Inject Nvidia Kepler GOP for boot", + "screen support on PC GPUs.", + ], + "condition": not bool((not self.constants.custom_model and self.constants.computer.real_model not in socketed_gpu_models) or (self.constants.custom_model and self.constants.custom_model not in socketed_gpu_models)) + }, + "wrap_around 2": { + "type": "wrap_around", + }, + "Graphics Override": { + "type": "combobox", + "choices": [ + "None", + "Nvidia Kepler", + "AMD GCN", + "AMD Polaris", + "AMD Lexa", + "AMD Navi", + ], + "value": "None", + "variable": "", + "description": [ + "Override detected/assumed GPU on", + "socketed MXM-based iMacs.", + ], + }, + "Populate Graphics Override": { + "type": "populate", + "function": self._populate_graphics_override, + "args": wx.Frame, + }, + + }, + "Security": { + "Kernel Security": { + "type": "title", + }, + "Disable Library Validation": { + "type": "checkbox", + "value": self.constants.disable_cs_lv, + "variable": "disable_cs_lv", + "description": [ + "Required for loading modified", + "system files from root patching.", + ], + }, + "Disable AMFI": { + "type": "checkbox", + "value": self.constants.disable_amfi, + "variable": "disable_amfi", + "description": [ + "Extended version of 'Disable", + "Library Validation', required", + "for systems with deeper", + "root patches.", + ], + }, + "wrap_around 1": { + "type": "wrap_around", + }, + "Secure Boot Model": { + "type": "checkbox", + "value": self.constants.secure_status, + "variable": "secure_status", + "description": [ + "Set Apple Secure Boot Model Identifier", + "to matching T2 model if spoofing.", + "Note: Incompatible with Root Patching.", + ], + }, + "System Integrity Protection": { + "type": "title", + }, + "Populate SIP": { + "type": "populate", + "function": self._populate_sip_settings, + "args": wx.Frame, + }, + }, + "SMBIOS": { + "Model Spoofing": { + "type": "title", + }, + "SMBIOS Spoof Level": { + "type": "combobox", + "choices": [ + "None", + "Minimal", + "Moderate", + "Advanced", + ], + "value": self.constants.serial_settings, + "variable": "serial_settings", + "description": [ + "Supported Levels:", + " - None: No spoofing.", + " - Minimal: Overrides Board ID.", + " - Moderate: Overrides Model.", + " - Advanced: Overrides Model and serial.", + ], + }, + + "SMBIOS Spoof Model": { + "type": "combobox", + "choices": models + ["Default"], + "value": self.constants.override_smbios, + "variable": "override_smbios", + "description": [ + "Set Mac Model to spoof to.", + ], + + }, + "wrap_around 1": { + "type": "wrap_around", + }, + "Allow spoofing native Macs": { + "type": "checkbox", + "value": self.constants.allow_native_spoofs, + "variable": "allow_native_spoofs", + "description": [ + "Allow OpenCore to spoof natively", + "supported Macs.", + "Primarily used for enabling", + "Universal Control on unsupported Macs", + ], + }, + "Serial Spoofing": { + "type": "title", + }, + "Populate Serial Spoofing": { + "type": "populate", + "function": self._populate_serial_spoofing_settings, + "args": wx.Frame, + }, + }, + "Root Patching": { + "Root Volume Patching": { + "type": "title", + }, + "TeraScale 2 Acceleration": { + "type": "checkbox", + "value": global_settings.GlobalEnviromentSettings().read_property("MacBookPro_TeraScale_2_Accel") or self.constants.allow_ts2_accel, + "variable": "MacBookPro_TeraScale_2_Accel", + "constants_variable": "allow_ts2_accel", + "description": [ + "Enable AMD TeraScale 2 GPU", + "Acceleration on MacBookPro8,2 and", + "MacBookPro8,3.", + "By default this is disabled due to", + "common GPU failures on these models.", + ], + "condition": not bool(self.constants.computer.real_model not in ["MacBookPro8,2", "MacBookPro8,3"]) + }, + "wrap_around 1": { + "type": "wrap_around", + }, + "Disable ColorSync Downgrade": { + "type": "checkbox", + "value": global_settings.GlobalEnviromentSettings().read_property("Disable_ColorSync_Downgrade") or self.constants.disable_cat_colorsync, + "variable": "Disable_ColorSync_Downgrade", + "constants_variable": "disable_cat_colorsync", + "description": [ + "Disable ColorSync downgrade", + "on HD3000 GPUs in Ventura and newer.", + "Note: Disabling can cause UI corruption.", + ], + "condition": not bool(self.constants.computer.real_model not in ["MacBookAir4,1","MacBookAir4,2","MacBookPro8,1","MacBookPro8,2","MacBookPro8,3","Macmini5,1"]) + }, + "Non-Metal Configuration": { + "type": "title", + }, + "Log out required to apply changes to SkyLight": { + "type": "sub_title", + }, + "Dark Menu Bar": { + "type": "checkbox", + "value": self._get_system_settings("Moraea_DarkMenuBar"), + "variable": "Moraea_DarkMenuBar", + "description": [ + # "Enable Dark Menu Bar", + ], + "override_function": self._update_system_defaults, + "condition": gui_support.CheckProperties(self.constants).host_is_non_metal(general_check=True) + }, + "Beta Blur": { + "type": "checkbox", + "value": self._get_system_settings("Moraea_BlurBeta"), + "variable": "Moraea_BlurBeta", + "description": [ + # "Enable Beta Blur", + ], + "override_function": self._update_system_defaults, + "condition": gui_support.CheckProperties(self.constants).host_is_non_metal(general_check=True) + + }, + "wrap_around 2": { + "type": "wrap_around", + }, + "Disable Beta Rim": { + "type": "checkbox", + "value": self._get_system_settings("Moraea_RimBetaDisabled"), + "variable": "Moraea_RimBetaDisabled", + "description": [ + # "Disable Beta Rim", + ], + "override_function": self._update_system_defaults, + "condition": gui_support.CheckProperties(self.constants).host_is_non_metal(general_check=True) + }, + }, + "App": { + "General": { + "type": "title", + }, + "Allow native models": { + "type": "checkbox", + "value": self.constants.allow_oc_everywhere, + "variable": "allow_oc_everywhere", + "description": [ + "Allow OpenCore to be installed", + "on natively supported Macs.", + "Note this will not allow unsupported", + "macOS versions to be installed on", + "your system.", + ], + "warning": "This option should only be used if your Mac natively supports the OSes you wish to run.\n\nIf you are currently running an unsupported OS, this option will break booting. Only toggle for enabling OS features on a native Mac.\n\nAre you certain you want to continue?", + }, + "Ignore App Updates": { + "type": "checkbox", + "value": global_settings.GlobalEnviromentSettings().read_property("IgnoreAppUpdates") or self.constants.ignore_updates, + "variable": "IgnoreAppUpdates", + "constants_variable": "ignore_updates", + "description": [ + # "Ignore app updates", + ], + "override_function": self._update_global_settings, + }, + "wrap_around 1": { + "type": "wrap_around", + }, + "Disable Reporting": { + "type": "checkbox", + "value": global_settings.GlobalEnviromentSettings().read_property("DisableCrashAndAnalyticsReporting"), + "variable": "DisableCrashAndAnalyticsReporting", + "description": [ + "When enabled, patcher will not", + "report any info to Dortania.", + ], + "override_function": self._update_global_settings, + }, + "Remove Unused KDKs": { + "type": "checkbox", + "value": global_settings.GlobalEnviromentSettings().read_property("ShouldNukeKDKs") or self.constants.should_nuke_kdks, + "variable": "ShouldNukeKDKs", + "constants_variable": "should_nuke_kdks", + "description": [ + "When enabled, the app will remove", + "unused Kernel Debug Kits from the system", + "during root patching.", + ], + "override_function": self._update_global_settings, + }, + "Statistics": { + "type": "title", + }, + "Populate Stats": { + "type": "populate", + "function": self._populate_app_stats, + "args": wx.Frame, + }, + }, + "Developer": { + "Install latest nightly build 🧪": { + "type": "button", + "function": self.on_nightly, + "description": [ + "If you're already here, I assume you're ok", + "bricking your system 🧱.", + "Check CHANGELOG before blindly updating.", + ], + }, + "wrap_around 1": { + "type": "wrap_around", + }, + "Export constants": { + "type": "button", + "function": self.on_export_constants, + "description": [ + "Export constants.py values to a txt file.", + ], + }, + }, + } + + return settings + + + def on_model_choice(self, event: wx.Event, model_choice: wx.Choice) -> None: + """ + Sets model to use for patching. + """ + + selection = model_choice.GetStringSelection() + if selection == "Host Model": + selection = self.constants.computer.real_model + self.constants.custom_model = None + logging.info(f"Using Real Model: {self.constants.computer.real_model}") + defaults.GenerateDefaults(self.constants.computer.real_model, True, self.constants) + else: + logging.info(f"Using Custom Model: {selection}") + self.constants.custom_model = selection + defaults.GenerateDefaults(self.constants.custom_model, False, self.constants) + self.parent.build_button.Enable() + + + + self.parent.model_label.SetLabel(f"Model: {selection}") + self.parent.model_label.Centre(wx.HORIZONTAL) + + self.frame_modal.Destroy() + SettingsFrame( + parent=self.parent, + title=self.title, + global_constants=self.constants, + screen_location=self.parent.GetPosition() + ) + + + def _populate_sip_settings(self, panel: wx.Frame) -> None: + + horizontal_spacer = 250 + + # Look for title on frame + sip_title: wx.StaticText = None + for child in panel.GetChildren(): + if child.GetLabel() == "System Integrity Protection": + sip_title = child + break + + + # Label: Flip individual bits corresponding to XNU's csr.h + # If you're unfamiliar with how SIP works, do not touch this menu + sip_label = wx.StaticText(panel, label="Flip individual bits corresponding to", pos=(sip_title.GetPosition()[0] - 20, sip_title.GetPosition()[1] + 30)) + sip_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + + # Hyperlink: csr.h + spacer = 1 if self.constants.detected_os >= os_data.os_data.big_sur else 3 + sip_csr_h = wx.adv.HyperlinkCtrl(panel, id=wx.ID_ANY, label="XNU's csr.h", url="https://github.com/apple-oss-distributions/xnu/blob/xnu-8020.101.4/bsd/sys/csr.h", pos=(sip_label.GetPosition()[0] + sip_label.GetSize()[0] + 4, sip_label.GetPosition()[1] + spacer)) + sip_csr_h.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + sip_csr_h.SetHoverColour(self.hyperlink_colour) + sip_csr_h.SetNormalColour(self.hyperlink_colour) + sip_csr_h.SetVisitedColour(self.hyperlink_colour) + + # Label: SIP Status + if self.constants.custom_sip_value is not None: + self.sip_value = int(self.constants.custom_sip_value, 16) + elif self.constants.sip_status is True: + self.sip_value = 0x00 + else: + self.sip_value = 0x803 + sip_configured_label = wx.StaticText(panel, label=f"Currently configured SIP: {hex(self.sip_value)}", pos=(sip_label.GetPosition()[0] + 35, sip_label.GetPosition()[1] + 20)) + sip_configured_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + self.sip_configured_label = sip_configured_label + + # Label: SIP Status + sip_booted_label = wx.StaticText(panel, label=f"Currently booted SIP: {hex(py_sip_xnu.SipXnu().get_sip_status().value)}", pos=(sip_configured_label.GetPosition()[0], sip_configured_label.GetPosition()[1] + 20)) + sip_booted_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + + + # SIP toggles + entries_per_row = len(sip_data.system_integrity_protection.csr_values) // 2 + horizontal_spacer = 15 + vertical_spacer = 25 + index = 1 + for sip_bit in sip_data.system_integrity_protection.csr_values_extended: + self.sip_checkbox = wx.CheckBox(panel, label=sip_data.system_integrity_protection.csr_values_extended[sip_bit]["name"].split("CSR_")[1], pos = (vertical_spacer, sip_booted_label.GetPosition()[1] + 20 + horizontal_spacer)) + self.sip_checkbox.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + self.sip_checkbox.SetToolTip(f'Description: {sip_data.system_integrity_protection.csr_values_extended[sip_bit]["description"]}\nValue: {hex(sip_data.system_integrity_protection.csr_values_extended[sip_bit]["value"])}\nIntroduced in: macOS {sip_data.system_integrity_protection.csr_values_extended[sip_bit]["introduced_friendly"]}') + + if self.sip_value & sip_data.system_integrity_protection.csr_values_extended[sip_bit]["value"] == sip_data.system_integrity_protection.csr_values_extended[sip_bit]["value"]: + self.sip_checkbox.SetValue(True) + + horizontal_spacer += 20 + if index == entries_per_row: + horizontal_spacer = 20 + vertical_spacer += 250 + + index += 1 + self.sip_checkbox.Bind(wx.EVT_CHECKBOX, self.on_sip_value) + + + def _populate_serial_spoofing_settings(self, panel: wx.Frame) -> None: + title: wx.StaticText = None + for child in panel.GetChildren(): + if child.GetLabel() == "Serial Spoofing": + title = child + break + + # Label: Custom Serial Number + custom_serial_number_label = wx.StaticText(panel, label="Custom Serial Number", pos=(title.GetPosition()[0] - 150, title.GetPosition()[1] + 30)) + custom_serial_number_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + + # Textbox: Custom Serial Number + custom_serial_number_textbox = wx.TextCtrl(panel, pos=(custom_serial_number_label.GetPosition()[0] - 27, custom_serial_number_label.GetPosition()[1] + 20), size=(200, 25)) + custom_serial_number_textbox.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + custom_serial_number_textbox.SetToolTip("Enter a custom serial number here. This will be used for the SMBIOS and iMessage.\n\nNote: This will not be used if the \"Use Custom Serial Number\" checkbox is not checked.") + custom_serial_number_textbox.Bind(wx.EVT_TEXT, self.on_custom_serial_number_textbox) + custom_serial_number_textbox.SetValue(self.constants.custom_serial_number) + self.custom_serial_number_textbox = custom_serial_number_textbox + + # Label: Custom Board Serial Number + custom_board_serial_number_label = wx.StaticText(panel, label="Custom Board Serial Number", pos=(title.GetPosition()[0] + 120, custom_serial_number_label.GetPosition()[1])) + custom_board_serial_number_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + + # Textbox: Custom Board Serial Number + custom_board_serial_number_textbox = wx.TextCtrl(panel, pos=(custom_board_serial_number_label.GetPosition()[0] - 5, custom_serial_number_textbox.GetPosition()[1]), size=(200, 25)) + custom_board_serial_number_textbox.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + custom_board_serial_number_textbox.SetToolTip("Enter a custom board serial number here. This will be used for the SMBIOS and iMessage.\n\nNote: This will not be used if the \"Use Custom Board Serial Number\" checkbox is not checked.") + custom_board_serial_number_textbox.Bind(wx.EVT_TEXT, self.on_custom_board_serial_number_textbox) + custom_board_serial_number_textbox.SetValue(self.constants.custom_board_serial_number) + self.custom_board_serial_number_textbox = custom_board_serial_number_textbox + + # Button: Generate Serial Number (below) + generate_serial_number_button = wx.Button(panel, label=f"Generate S/N: {self.constants.custom_model or self.constants.computer.real_model}", pos=(title.GetPosition()[0] - 30, custom_board_serial_number_label.GetPosition()[1] + 60), size=(200, 25)) + generate_serial_number_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + generate_serial_number_button.Bind(wx.EVT_BUTTON, self.on_generate_serial_number) + + + def _populate_app_stats(self, panel: wx.Frame) -> None: + title: wx.StaticText = None + for child in panel.GetChildren(): + if child.GetLabel() == "Statistics": + title = child + break + + lines = f"""Application Information: + Application Version: {self.constants.patcher_version} + PatcherSupportPkg Version: {self.constants.patcher_support_pkg_version} + Application Path: {self.constants.launcher_binary} + Application Mount: {self.constants.payload_path} + +Commit Information: + Branch: {self.constants.commit_info[0]} + Date: {self.constants.commit_info[1]} + URL: {self.constants.commit_info[2] if self.constants.commit_info[2] != "" else "N/A"} + +Booted Information: + Booted OS: XNU {self.constants.detected_os} ({self.constants.detected_os_version}) + Booted Patcher Version: {self.constants.computer.oclp_version} + Booted OpenCore Version: {self.constants.computer.opencore_version} + Booted OpenCore Disk: {self.constants.booted_oc_disk} + +Hardware Information: + {pprint.pformat(self.constants.computer, indent=4)} +""" + # TextCtrl: properties + self.app_stats = wx.TextCtrl(panel, value=lines, pos=(-1, title.GetPosition()[1] + 30), size=(600, 240), style=wx.TE_READONLY | wx.TE_MULTILINE | wx.TE_RICH2) + self.app_stats.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + + + def on_checkbox(self, event: wx.Event, warning_pop: str = "", override_function: bool = False) -> None: + """ + """ + label = event.GetEventObject().GetLabel() + value = event.GetEventObject().GetValue() + if warning_pop != "" and value is True: + warning = wx.MessageDialog(self.frame_modal, warning_pop, f"Warning: {label}", wx.YES_NO | wx.ICON_WARNING | wx.NO_DEFAULT) + if warning.ShowModal() == wx.ID_NO: + event.GetEventObject().SetValue(not event.GetEventObject().GetValue()) + return + if label == "Allow native models": + if self.constants.computer.real_model in smbios_data.smbios_dictionary: + if self.constants.detected_os > smbios_data.smbios_dictionary[self.constants.computer.real_model]["Max OS Supported"]: + chassis_type = "aluminum" + if self.constants.computer.real_model in ["MacBook4,1", "MacBook5,2", "MacBook6,1", "MacBook7,1"]: + chassis_type = "plastic" + dlg = wx.MessageDialog(self.frame_modal, f"This model, {self.constants.computer.real_model}, does not natively support macOS {os_data.os_conversion.kernel_to_os(self.constants.detected_os)}, {os_data.os_conversion.convert_kernel_to_marketing_name(self.constants.detected_os)}. The last native OS was macOS {os_data.os_conversion.kernel_to_os(smbios_data.smbios_dictionary[self.constants.computer.real_model]['Max OS Supported'])}, {os_data.os_conversion.convert_kernel_to_marketing_name(smbios_data.smbios_dictionary[self.constants.computer.real_model]['Max OS Supported'])}\n\nToggling this option will break booting on this OS. Are you absolutely certain this is desired?\n\nYou may end up with a nice {chassis_type} brick 🧱", "Are you certain?", wx.YES_NO | wx.ICON_WARNING | wx.NO_DEFAULT) + if dlg.ShowModal() == wx.ID_NO: + event.GetEventObject().SetValue(not event.GetEventObject().GetValue()) + return + if override_function is True: + print("Override function") + self.settings[self._find_parent_for_key(label)][label]["override_function"](self.settings[self._find_parent_for_key(label)][label]["variable"], value, self.settings[self._find_parent_for_key(label)][label]["constants_variable"] if "constants_variable" in self.settings[self._find_parent_for_key(label)][label] else None) + return + + self._update_setting(self.settings[self._find_parent_for_key(label)][label]["variable"], value) + if label == "Allow native models": + if gui_support.CheckProperties(self.constants).host_can_build() is True: + self.parent.build_button.Enable() + else: + self.parent.build_button.Disable() + + + def on_spinctrl(self, event: wx.Event, label: str) -> None: + """ + """ + value = event.GetEventObject().GetValue() + self._update_setting(self.settings[self._find_parent_for_key(label)][label]["variable"], value) + + + def _update_setting(self, variable, value): + logging.info(f"Updating Local Setting: {variable} = {value}") + setattr(self.constants, variable, value) + + + def _update_global_settings(self, variable, value, global_setting = None): + logging.info(f"Updating Global Setting: {variable} = {value}") + global_settings.GlobalEnviromentSettings().write_property(variable, value) + if global_setting is not None: + self._update_setting(global_setting, value) + + + def _update_system_defaults(self, variable, value, global_setting = None): + value_type = type(value) + if value_type is str: + value_type = "-string" + elif value_type is int: + value_type = "-int" + elif value_type is bool: + value_type = "-bool" + + logging.info(f"Updating System Defaults: {variable} = {value} ({value_type})") + subprocess.run(["defaults", "write", "-g", variable, value_type, str(value)]) + + + def _find_parent_for_key(self, key: str) -> str: + for parent in self.settings: + if key in self.settings[parent]: + return parent + + + def on_sip_value(self, event: wx.Event) -> None: + """ + """ + dict = sip_data.system_integrity_protection.csr_values_extended[f"CSR_{event.GetEventObject().GetLabel()}"] + + if event.GetEventObject().GetValue() is True: + self.sip_value = self.sip_value + dict["value"] + else: + self.sip_value = self.sip_value - dict["value"] + + if hex(self.sip_value) == "0x0": + self.constants.custom_sip_value = None + self.constants.sip_status = True + elif hex(self.sip_value) == "0x803": + self.constants.custom_sip_value = None + self.constants.sip_status = False + else: + self.constants.custom_sip_value = hex(self.sip_value) + + self.sip_configured_label.SetLabel(f"Currently configured SIP: {hex(self.sip_value)}") + + def on_combobox(self, event: wx.Event, label: str) -> None: + """ + """ + value = event.GetEventObject().GetValue() + self._update_setting(self.settings[self._find_parent_for_key(label)][label]["variable"], value) + + + def on_generate_serial_number(self, event: wx.Event) -> None: + dlg = wx.MessageDialog(self.frame_modal, "Please take caution when using serial spoofing. This should only be used on machines that were legally obtained and require reserialization.\n\nNote: new serials are only overlayed through OpenCore and are not permanently installed into ROM.\n\nMisuse of this setting can break power management and other aspects of the OS if the system does not need spoofing\n\nDortania does not condone the use of our software on stolen devices.\n\nAre you certain you want to continue?", "Warning", wx.YES_NO | wx.ICON_WARNING | wx.NO_DEFAULT) + if dlg.ShowModal() != wx.ID_YES: + return + + macserial_output = subprocess.run([self.constants.macserial_path] + f"-g -m {self.constants.custom_model or self.constants.computer.real_model} -n 1".split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + macserial_output = macserial_output.stdout.decode().strip().split(" | ") + if len(macserial_output) == 2: + self.custom_serial_number_textbox.SetValue(macserial_output[0]) + self.custom_board_serial_number_textbox.SetValue(macserial_output[1]) + else: + wx.MessageBox(f"Failed to generate serial number:\n\n{macserial_output}", "Error", wx.OK | wx.ICON_ERROR) + + + def on_custom_serial_number_textbox(self, event: wx.Event) -> None: + self.constants.custom_serial_number = event.GetEventObject().GetValue() + + + def on_custom_board_serial_number_textbox(self, event: wx.Event) -> None: + self.constants.custom_board_serial_number = event.GetEventObject().GetValue() + + + def _populate_fu_override(self, panel: wx.Panel) -> None: + gpu_combo_box: wx.ComboBox = None + for child in panel.GetChildren(): + if isinstance(child, wx.ComboBox): + gpu_combo_box = child + break + + gpu_combo_box.Bind(wx.EVT_COMBOBOX, self.fu_selection_click) + if self.constants.fu_status is False: + gpu_combo_box.SetStringSelection("Disabled") + elif self.constants.fu_arguments is None: + gpu_combo_box.SetStringSelection("Enabled") + else: + gpu_combo_box.SetStringSelection("Partial") + + + def fu_selection_click(self, event: wx.Event) -> None: + value = event.GetEventObject().GetStringSelection() + if value == "Enabled": + self.constants.fu_status = True + self.constants.fu_arguments = None + return + + if value == "Partial": + self.constants.fu_status = True + self.constants.fu_arguments = " -disable_sidecar_mac" + return + + self.constants.fu_status = False + self.constants.fu_arguments = None + + + def _populate_graphics_override(self, panel: wx.Panel) -> None: + gpu_combo_box: wx.ComboBox = None + index = 0 + for child in panel.GetChildren(): + if isinstance(child, wx.ComboBox): + if index == 0: + index = index + 1 + continue + gpu_combo_box = child + break + + gpu_combo_box.Bind(wx.EVT_COMBOBOX, self.gpu_selection_click) + gpu_combo_box.SetStringSelection(f"{self.constants.imac_vendor} {self.constants.imac_model}") + + socketed_gpu_models = ["iMac9,1", "iMac10,1", "iMac11,1", "iMac11,2", "iMac11,3", "iMac12,1", "iMac12,2"] + if ((not self.constants.custom_model and self.constants.computer.real_model not in socketed_gpu_models) or (self.constants.custom_model and self.constants.custom_model not in socketed_gpu_models)): + gpu_combo_box.Disable() + return + + + def gpu_selection_click(self, event: wx.Event) -> None: + gpu_choice = event.GetEventObject().GetStringSelection() + + logging.info(f"Updating GPU Selection: {gpu_choice}") + if "AMD" in gpu_choice: + self.constants.imac_vendor = "AMD" + self.constants.metal_build = True + if "Polaris" in gpu_choice: + self.constants.imac_model = "Polaris" + elif "GCN" in gpu_choice: + self.constants.imac_model = "GCN" + elif "Lexa" in gpu_choice: + self.constants.imac_model = "Lexa" + elif "Navi" in gpu_choice: + self.constants.imac_model = "Navi" + else: + raise Exception("Unknown GPU Model") + elif "Nvidia" in gpu_choice: + self.constants.imac_vendor = "Nvidia" + self.constants.metal_build = True + if "Kepler" in gpu_choice: + self.constants.imac_model = "Kepler" + elif "GT" in gpu_choice: + self.constants.imac_model = "GT" + else: + raise Exception("Unknown GPU Model") + else: + self.constants.imac_vendor = "None" + self.constants.metal_build = False + + + def _get_system_settings(self, variable) -> bool: + result = subprocess.run(["defaults", "read", "-g", variable], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if result.returncode == 0: + try: + return bool(int(result.stdout.decode().strip())) + except: + return False + return False + + + def on_return(self, event): + self.frame_modal.Destroy() + + + def on_nightly(self, event: wx.Event) -> None: + # Ask prompt for which branch + branches = ["main"] + if self.constants.commit_info[0] not in ["Running from source", "Built from source"]: + branches = [self.constants.commit_info[0].split("/")[-1]] + result = network_handler.NetworkUtilities().get("https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/branches") + if result is not None: + result = result.json() + for branch in result: + if branch["name"] == "gh-pages": + continue + if branch["name"] not in branches: + branches.append(branch["name"]) + + with wx.SingleChoiceDialog(self.parent, "Which branch would you like to download?", "Branch Selection", branches) as dialog: + if dialog.ShowModal() == wx.ID_CANCEL: + return + + branch = dialog.GetStringSelection() + else: + branch = "main" + + gui_update.UpdateFrame( + parent=self.parent, + title=self.title, + global_constants=self.constants, + screen_location=self.parent.GetPosition(), + url=f"https://nightly.link/dortania/OpenCore-Legacy-Patcher/workflows/build-app-wxpython/{branch}/OpenCore-Patcher.app%20%28GUI%29.zip", + version_label="(Nightly)" + ) + + + def on_export_constants(self, event: wx.Event) -> None: + # Throw pop up to get save location + with wx.FileDialog(self.parent, "Save Constants File", wildcard="JSON files (*.txt)|*.txt", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as fileDialog: + if fileDialog.ShowModal() == wx.ID_CANCEL: + return + + # Save the current contents in the file + pathname = fileDialog.GetPath() + with open(pathname, 'w') as file: + file.write(pprint.pformat(vars(self.constants), indent=4)) \ No newline at end of file diff --git a/resources/wx_gui/gui_support.py b/resources/wx_gui/gui_support.py new file mode 100644 index 000000000..5a1f003de --- /dev/null +++ b/resources/wx_gui/gui_support.py @@ -0,0 +1,296 @@ +import wx +import sys +import time +import logging +import threading +import subprocess +import applescript + +from pathlib import Path + +from resources.wx_gui import gui_about +from resources import constants +from data import model_array, os_data, smbios_data + + +class AutoUpdateStages: + INACTIVE = 0 + CHECKING = 1 + BUILDING = 2 + INSTALLING = 3 + ROOT_PATCHING = 4 + FINISHED = 5 + + +class GenerateMenubar: + + def __init__(self, frame: wx.Frame, global_constants: constants.Constants) -> None: + self.frame: wx.Frame = frame + self.constants: constants.Constants = global_constants + + + def generate(self) -> wx.MenuBar: + menubar = wx.MenuBar() + fileMenu = wx.Menu() + + aboutItem = fileMenu.Append(wx.ID_ABOUT, "&About OpenCore Legacy Patcher") + fileMenu.AppendSeparator() + + menubar.Append(fileMenu, "&File") + self.frame.SetMenuBar(menubar) + + self.frame.Bind(wx.EVT_MENU, lambda event: gui_about.AboutFrame(self.constants), aboutItem) + + +class GaugePulseCallback: + """ + Uses an alternative Pulse() method for wx.Gauge() on macOS Monterey+ + Dirty hack, however better to display some form of animation than none at all + """ + + def __init__(self, global_constants: constants.Constants, gauge: wx.Gauge) -> None: + self.gauge: wx.Gauge = gauge + + self.pulse_thread: threading.Thread = None + self.pulse_thread_active: bool = False + + self.gauge_value: int = 0 + self.pulse_forward: bool = True + + self.max_value: int = gauge.GetRange() + + self.non_metal_alternative: bool = CheckProperties(global_constants).host_is_non_metal() + + + def start_pulse(self) -> None: + if self.non_metal_alternative is False: + self.gauge.Pulse() + return + self.pulse_thread_active = True + self.pulse_thread = threading.Thread(target=self._pulse) + self.pulse_thread.start() + + + def stop_pulse(self) -> None: + if self.non_metal_alternative is False: + return + self.pulse_thread_active = False + self.pulse_thread.join() + + + def _pulse(self) -> None: + while self.pulse_thread_active: + if self.gauge_value == 0: + self.pulse_forward = True + + elif self.gauge_value == self.max_value: + self.pulse_forward = False + + if self.pulse_forward: + self.gauge_value += 1 + else: + self.gauge_value -= 1 + + wx.CallAfter(self.gauge.SetValue, self.gauge_value) + time.sleep(0.005) + + +class CheckProperties: + + def __init__(self, global_constants: constants.Constants) -> None: + self.constants: constants.Constants = global_constants + + def host_can_build(self): + """ + Check if host supports building OpenCore configs + """ + if self.constants.custom_model: + return True + if self.constants.host_is_hackintosh is True: + return False + if self.constants.allow_oc_everywhere is True: + return True + if self.constants.computer.real_model in model_array.SupportedSMBIOS: + return True + + return False + + + def host_is_non_metal(self, general_check: bool = False): + """ + Check if host is non-metal + Primarily for wx.Gauge().Pulse() workaround (where animation doesn't work on Monterey+) + """ + + if self.constants.detected_os < os_data.os_data.monterey and general_check is False: + return False + if self.constants.detected_os < os_data.os_data.big_sur and general_check is True: + return False + if not Path("/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLightOld.dylib").exists(): + # SkyLight stubs are only used on non-Metal + return False + + return True + + def host_has_cpu_gen(self, gen: int) -> bool: + """ + Check if host has a CPU generation equal to or greater than the specified generation + """ + model = self.constants.custom_model if self.constants.custom_model else self.constants.computer.real_model + if model in smbios_data.smbios_dictionary: + if smbios_data.smbios_dictionary[model]["CPU Generation"] >= gen: + return True + return False + + +class PayloadMount: + + def __init__(self, global_constants: constants.Constants, frame: wx.Frame) -> None: + self.constants: constants.Constants = global_constants + self.frame: wx.Frame = frame + + + def is_unpack_finished(self): + if self.constants.unpack_thread.is_alive(): + return False + + if Path(self.constants.payload_kexts_path).exists(): + return True + + # Raise error to end program + popup = wx.MessageDialog( + self.frame, + f"During unpacking of our internal files, we seemed to have encountered an error.\n\nIf you keep seeing this error, please try rebooting and redownloading the application.", + "Internal Error occurred!", + style=wx.OK | wx.ICON_EXCLAMATION + ) + popup.ShowModal() + self.frame.Freeze() + sys.exit(1) + + +class ThreadHandler(logging.Handler): + """ + Reroutes logging output to a wx.TextCtrl using UI callbacks + """ + + def __init__(self, text_box: wx.TextCtrl): + logging.Handler.__init__(self) + self.text_box = text_box + + + def emit(self, record: logging.LogRecord): + wx.CallAfter(self.text_box.AppendText, self.format(record) + '\n') + + +class RestartHost: + """ + Restarts the host machine + """ + + def __init__(self, frame: wx.Frame) -> None: + self.frame: wx.Frame = frame + + + def restart(self, event: wx.Event = None, message: str = ""): + self.popup = wx.MessageDialog( + self.frame, + message, + "Reboot to apply?", + wx.YES_NO | wx.ICON_INFORMATION + ) + self.popup.SetYesNoLabels("Reboot", "Ignore") + answer = self.popup.ShowModal() + if answer == wx.ID_YES: + # Reboots with Count Down prompt (user can still dismiss if needed) + self.frame.Hide() + wx.Yield() + try: + applescript.AppleScript('tell app "loginwindow" to «event aevtrrst»').run() + except applescript.ScriptError as e: + logging.error(f"Error while trying to reboot: {e}") + sys.exit(0) + + +class RelaunchApplicationAsRoot: + """ + Relaunches the application as root + """ + + def __init__(self, frame: wx.Frame, global_constants: constants.Constants) -> None: + self.constants = global_constants + self.frame: wx.Frame = frame + + + def relaunch(self, event: wx.Event): + + self.dialog = wx.MessageDialog( + self.frame, + "OpenCore Legacy Patcher needs to relaunch as admin to continue. You will be prompted to enter your password.", + "Relaunch as root?", + wx.YES_NO | wx.ICON_QUESTION + ) + + # Show Dialog Box + if self.dialog.ShowModal() != wx.ID_YES: + logging.info("User cancelled relaunch") + return + + timer: int = 5 + program_arguments: str = "" + + if event: + if event.GetEventObject() != wx.Menu: + try: + if event.GetEventObject().GetLabel() in ["Start Root Patching", "Reinstall Root Patches"]: + program_arguments = " --gui_patch" + elif event.GetEventObject().GetLabel() == "Revert Root Patches": + program_arguments = " --gui_unpatch" + except TypeError: + pass + + if self.constants.launcher_script is None: + program_arguments = f"'{self.constants.launcher_binary}'{program_arguments}" + else: + program_arguments = f"{self.constants.launcher_binary} {self.constants.launcher_script}{program_arguments}" + + # Relaunch as root + args = [ + "osascript", + "-e", + f'''do shell script "{program_arguments}"''' + ' with prompt "OpenCore Legacy Patcher needs administrator privileges to relaunch as admin."' + " with administrator privileges" + " without altering line endings", + ] + + self.frame.DestroyChildren() + self.frame.SetSize(400, 300) + + # Header + header = wx.StaticText(self.frame, label="Relaunching as root") + header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + header.Centre(wx.HORIZONTAL) + + # Add count down label + countdown_label = wx.StaticText(self.frame, label=f"Closing old process in {timer} seconds", pos=(0, header.GetPosition().y + header.GetSize().height + 3)) + countdown_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + countdown_label.Centre(wx.HORIZONTAL) + + # Set size of frame + self.frame.SetSize((-1, countdown_label.GetPosition().y + countdown_label.GetSize().height + 40)) + + wx.Yield() + + logging.info(f"- Relaunching as root with command: {program_arguments}") + subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + while True: + wx.Yield() + countdown_label.SetLabel(f"Closing old process in {timer} seconds") + time.sleep(1) + timer -= 1 + if timer == 0: + break + + sys.exit(0) \ No newline at end of file diff --git a/resources/wx_gui/gui_sys_patch.py b/resources/wx_gui/gui_sys_patch.py new file mode 100644 index 000000000..8942101f8 --- /dev/null +++ b/resources/wx_gui/gui_sys_patch.py @@ -0,0 +1,508 @@ + +import wx +import os +import sys +import time +import logging +import plistlib +import traceback +import threading +import subprocess + +from pathlib import Path + +from resources import ( + constants, + kdk_handler, +) +from resources.sys_patch import ( + sys_patch, + sys_patch_detect +) +from resources.wx_gui import ( + gui_main_menu, + gui_support, + gui_download, +) +from data import os_data + + +class SysPatchFrame(wx.Frame): + """ + Create a frame for root patching + Uses a Modal Dialog for smoother transition from other frames + """ + def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None, patches: dict = {}): + super(SysPatchFrame, self).__init__(parent, title=title, size=(350, 260), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) + + self.title = title + self.constants: constants.Constants = global_constants + self.frame_modal: wx.Dialog = None + self.return_button: wx.Button = None + self.available_patches: bool = False + + self.frame_modal = wx.Dialog(self, title=title, size=(360, 200)) + self.SetPosition(screen_location) if screen_location else self.Centre() + + if patches: + return + + self._generate_elements_display_patches(self.frame_modal, patches) + self.frame_modal.ShowWindowModal() + + if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE: + if self.available_patches is False: + gui_support.RestartHost(self).restart(message="No root patch updates needed!\n\nWould you like to reboot to apply the new OpenCore build?") + + + def _kdk_download(self, frame: wx.Frame = None) -> bool: + frame = self if not frame else frame + + logging.info("KDK missing, generating KDK download frame") + + header = wx.StaticText(frame, label="Downloading Kernel Debug Kit", pos=(-1,5)) + header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + header.Centre(wx.HORIZONTAL) + + subheader = wx.StaticText(frame, label="Fetching KDK database...", pos=(-1, header.GetPosition()[1] + header.GetSize()[1] + 5)) + subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + subheader.Centre(wx.HORIZONTAL) + + progress_bar = wx.Gauge(frame, range=100, pos=(-1, subheader.GetPosition()[1] + subheader.GetSize()[1] + 5), size=(250, 20)) + progress_bar.Centre(wx.HORIZONTAL) + + progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar) + progress_bar_animation.start_pulse() + + # Set size of frame + frame.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 35)) + frame.Show() + + # Generate KDK object + self.kdk_obj: kdk_handler.KernelDebugKitObject = None + def _kdk_thread_spawn(): + self.kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version) + + kdk_thread = threading.Thread(target=_kdk_thread_spawn) + kdk_thread.start() + + while kdk_thread.is_alive(): + wx.Yield() + + if self.kdk_obj.success is False: + progress_bar_animation.stop_pulse() + progress_bar.SetValue(0) + wx.MessageBox(f"KDK download failed: {self.kdk_obj.error_msg}", "Error", wx.OK | wx.ICON_ERROR) + return False + + kdk_download_obj = self.kdk_obj.retrieve_download() + if not kdk_download_obj: + # KDK is already downloaded + return True + + gui_download.DownloadFrame( + self, + title=self.title, + global_constants=self.constants, + download_obj=kdk_download_obj, + item_name=f"KDK Build {self.kdk_obj.kdk_url_build}" + ) + if kdk_download_obj.download_complete is False: + return False + + header.SetLabel(f"Validating KDK: {self.kdk_obj.kdk_url_build}") + header.Centre(wx.HORIZONTAL) + + subheader.SetLabel("Checking if checksum is valid...") + subheader.Centre(wx.HORIZONTAL) + wx.Yield() + + progress_bar_animation.stop_pulse() + + if self.kdk_obj.validate_kdk_checksum() is False: + progress_bar.SetValue(0) + logging.error("KDK checksum validation failed") + logging.error(self.kdk_obj.error_msg) + msg = wx.MessageDialog(frame, f"KDK checksum validation failed: {self.kdk_obj.error_msg}", "Error", wx.OK | wx.ICON_ERROR) + msg.ShowModal() + return False + + progress_bar.SetValue(100) + + logging.info("KDK download complete") + return True + + + def _generate_elements_display_patches(self, frame: wx.Frame = None, patches: dict = {}) -> None: + """ + Generate UI elements for root patching frame + + Format: + - Title label: Post-Install Menu + - Label: Available patches: + - Labels: {patch name} + - Button: Start Root Patching + - Button: Revert Root Patches + - Button: Return to Main Menu + """ + frame = self if not frame else frame + + title_label = wx.StaticText(frame, label="Post-Install Menu", 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) + + # Label: Available patches: + available_label = wx.StaticText(frame, label="Available patches for your system:", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 10)) + available_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + available_label.Centre(wx.HORIZONTAL) + + # Labels: {patch name} + patches: dict = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() if not patches else patches + can_unpatch: bool = patches["Validation: Unpatching Possible"] + + if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches): + logging.info("- No applicable patches available") + patches = [] + + # Check if OCLP has already applied the same patches + no_new_patches = not self._check_if_new_patches_needed(patches) if patches else False + + if not patches: + # Prompt user with no patches found + patch_label = wx.StaticText(frame, label="No patches required", pos=(-1, available_label.GetPosition()[1] + 20)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + patch_label.Centre(wx.HORIZONTAL) + + else: + # Add Label for each patch + i = 0 + if no_new_patches is True: + patch_label = wx.StaticText(frame, label="All applicable patches already installed", pos=(-1, available_label.GetPosition()[1] + 20)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + patch_label.Centre(wx.HORIZONTAL) + i = i + 20 + else: + longest_patch = "" + for patch in patches: + if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): + if len(patch) > len(longest_patch): + longest_patch = patch + anchor = wx.StaticText(frame, label=longest_patch, pos=(-1, available_label.GetPosition()[1] + 20)) + anchor.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + anchor.Centre(wx.HORIZONTAL) + anchor.Hide() + + for patch in patches: + if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): + i = i + 20 + logging.info(f"- Adding patch: {patch} - {patches[patch]}") + patch_label = wx.StaticText(frame, label=f"- {patch}", pos=(anchor.GetPosition()[0], available_label.GetPosition()[1] + i)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + + if i == 20: + patch_label.SetLabel(patch_label.GetLabel().replace("-", "")) + patch_label.Centre(wx.HORIZONTAL) + + if patches["Validation: Patching Possible"] is False: + # Cannot patch due to the following reasons: + patch_label = wx.StaticText(frame, label="Cannot patch due to the following reasons:", pos=(-1, patch_label.GetPosition().y + 25)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + patch_label.Centre(wx.HORIZONTAL) + + for patch in patches: + if not patch.startswith("Validation"): + continue + if patches[patch] is False: + continue + if patch == "Validation: Unpatching Possible": + continue + + patch_label = wx.StaticText(frame, label=f"- {patch.split('Validation: ')[1]}", pos=(available_label.GetPosition().x - 10, patch_label.GetPosition().y + 20)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + else: + if self.constants.computer.oclp_sys_version and self.constants.computer.oclp_sys_date: + date = self.constants.computer.oclp_sys_date.split(" @") + date = date[0] if len(date) == 2 else "" + + patch_text = f"{self.constants.computer.oclp_sys_version}, {date}" + + patch_label = wx.StaticText(frame, label="Root Volume last patched:", pos=(-1, patch_label.GetPosition().y + 25)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + patch_label.Centre(wx.HORIZONTAL) + + patch_label = wx.StaticText(frame, label=patch_text, pos=(available_label.GetPosition().x - 10, patch_label.GetPosition().y + 20)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + patch_label.Centre(wx.HORIZONTAL) + + + # Button: Start Root Patching + start_button = wx.Button(frame, label="Start Root Patching", pos=(10, patch_label.GetPosition().y + 25), size=(170, 30)) + start_button.Bind(wx.EVT_BUTTON, lambda event: self.start_root_patching(frame, patches, no_new_patches)) + start_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + start_button.Centre(wx.HORIZONTAL) + + # Button: Revert Root Patches + revert_button = wx.Button(frame, label="Revert Root Patches", pos=(10, start_button.GetPosition().y + start_button.GetSize().height - 5), size=(170, 30)) + revert_button.Bind(wx.EVT_BUTTON, lambda event: self.revert_root_patching(frame, patches, can_unpatch)) + revert_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + revert_button.Centre(wx.HORIZONTAL) + + # Button: Return to Main Menu + return_button = wx.Button(frame, label="Return to Main Menu", pos=(10, revert_button.GetPosition().y + revert_button.GetSize().height), size=(150, 30)) + return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) + return_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + return_button.Centre(wx.HORIZONTAL) + self.return_button = return_button + + # Disable buttons if unsupported + if not patches: + start_button.Disable() + else: + self.available_patches = True + if patches["Validation: Patching Possible"] is False: + start_button.Disable() + elif no_new_patches is False: + start_button.SetDefault() + if can_unpatch is False: + revert_button.Disable() + + # Relaunch as root if not root + uid = os.geteuid() + if uid != 0: + start_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch) + revert_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch) + + # Set frame size + frame.SetSize((-1, return_button.GetPosition().y + return_button.GetSize().height + 35)) + + + def _generate_modal(self, patches: dict = {}, variant: str = "Root Patching"): + """ + Create UI for root patching/unpatching + """ + supported_variants = ["Root Patching", "Revert Root Patches"] + if variant not in supported_variants: + logging.error(f"Unsupported variant: {variant}") + return + + self.frame_modal.Close() + + dialog = wx.Dialog(self, title=self.title, size=(400, 200)) + + # Title + title = wx.StaticText(dialog, label=variant, pos=(-1, 10)) + title.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title.Centre(wx.HORIZONTAL) + + if variant == "Root Patching": + # Label + label = wx.StaticText(dialog, label="Root Patching will patch the following:", pos=(-1, title.GetPosition()[1] + 30)) + label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + label.Centre(wx.HORIZONTAL) + + + # Get longest patch label, then create anchor for patch labels + longest_patch = "" + for patch in patches: + if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): + if len(patch) > len(longest_patch): + longest_patch = patch + + anchor = wx.StaticText(dialog, label=longest_patch, pos=(label.GetPosition()[0], label.GetPosition()[1] + 20)) + anchor.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + anchor.Centre(wx.HORIZONTAL) + anchor.Hide() + + # Labels + i = 0 + for patch in patches: + if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): + logging.info(f"- Adding patch: {patch} - {patches[patch]}") + patch_label = wx.StaticText(dialog, label=f"- {patch}", pos=(anchor.GetPosition()[0], label.GetPosition()[1] + 20 + i)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + i = i + 20 + + if i == 20: + patch_label.SetLabel(patch_label.GetLabel().replace("-", "")) + patch_label.Centre(wx.HORIZONTAL) + + elif i == 0: + patch_label = wx.StaticText(dialog, label="No patches to apply", pos=(label.GetPosition()[0], label.GetPosition()[1] + 20)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + patch_label.Centre(wx.HORIZONTAL) + else: + patch_label = wx.StaticText(dialog, label="Reverting to last sealed snapshot", pos=(-1, title.GetPosition()[1] + 30)) + patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + patch_label.Centre(wx.HORIZONTAL) + + + # Text box + text_box = wx.TextCtrl(dialog, pos=(10, patch_label.GetPosition()[1] + 30), size=(400, 400), style=wx.TE_READONLY | wx.TE_MULTILINE | wx.TE_RICH2) + text_box.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + text_box.Centre(wx.HORIZONTAL) + self.text_box = text_box + + # Button: Return to Main Menu + return_button = wx.Button(dialog, label="Return to Main Menu", pos=(10, text_box.GetPosition()[1] + text_box.GetSize()[1] + 5), size=(150, 30)) + return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) + return_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + return_button.Centre(wx.HORIZONTAL) + self.return_button = return_button + + # Set frame size + dialog.SetSize((-1, return_button.GetPosition().y + return_button.GetSize().height + 33)) + + dialog.ShowWindowModal() + + + def start_root_patching(self, patches: dict): + logging.info("Starting root patching") + + while gui_support.PayloadMount(self.constants, self).is_unpack_finished() is False: + wx.Yield() + + if patches["Settings: Kernel Debug Kit missing"] is True: + if self._kdk_download(self) is False: + self.on_return_to_main_menu() + return + + self._generate_modal(patches, "Root Patching") + self.return_button.Disable() + + thread = threading.Thread(target=self._start_root_patching, args=(patches,)) + thread.start() + + while thread.is_alive(): + wx.Yield() + + self._post_patch() + self.return_button.Enable() + + + def _start_root_patching(self, patches: dict): + logger = logging.getLogger() + logger.addHandler(gui_support.ThreadHandler(self.text_box)) + try: + sys_patch.PatchSysVolume(self.constants.computer.real_model, self.constants, patches).start_patch() + except: + logging.error("- An internal error occurred while running the Root Patcher:\n") + logging.error(traceback.format_exc()) + logger.removeHandler(logger.handlers[2]) + + + def revert_root_patching(self, patches: dict): + logging.info("Reverting root patches") + self._generate_modal(patches, "Revert Root Patches") + self.return_button.Disable() + + thread = threading.Thread(target=self._revert_root_patching, args=(patches,)) + thread.start() + + while thread.is_alive(): + wx.Yield() + + self._post_patch() + self.return_button.Enable() + + + def _revert_root_patching(self, patches: dict): + logger = logging.getLogger() + logger.addHandler(gui_support.ThreadHandler(self.text_box)) + try: + sys_patch.PatchSysVolume(self.constants.computer.real_model, self.constants, patches).start_unpatch() + except: + logging.error("- An internal error occurred while running the Root Patcher:\n") + logging.error(traceback.format_exc()) + logger.removeHandler(logger.handlers[2]) + + + def on_return_to_main_menu(self, event: wx.Event = None): + self.frame_modal.Hide() + main_menu_frame = gui_main_menu.MainFrame( + None, + title=self.title, + global_constants=self.constants, + screen_location=self.GetScreenPosition() + ) + main_menu_frame.Show() + self.frame_modal.Destroy() + self.Destroy() + + + def _post_patch(self): + if self.constants.root_patcher_succeeded is False: + return + + if self.constants.needs_to_open_preferences is False: + gui_support.RestartHost(self).restart(message="Root Patcher finished successfully!\n\nWould you like to reboot now?") + return + + if self.constants.detected_os >= os_data.os_data.ventura: + gui_support.RestartHost(self).restart(message="Root Patcher finished successfully!\nIf you were prompted to open System Settings to authorize new kexts, this can be ignored. Your system is ready once restarted.\n\nWould you like to reboot now?") + return + + # Create dialog box to open System Preferences -> Security and Privacy + self.popup = wx.MessageDialog( + self.frame_modal, + "We just finished installing the patches to your Root Volume!\n\nHowever, Apple requires users to manually approve the kernel extensions installed before they can be used next reboot.\n\nWould you like to open System Preferences?", + "Open System Preferences?", + wx.YES_NO | wx.ICON_INFORMATION + ) + self.popup.SetYesNoLabels("Open System Preferences", "Ignore") + answer = self.popup.ShowModal() + if answer == wx.ID_YES: + output =subprocess.run( + [ + "osascript", "-e", + 'tell app "System Preferences" to activate', + "-e", 'tell app "System Preferences" to reveal anchor "General" of pane id "com.apple.preference.security"', + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + if output.returncode != 0: + # Some form of fallback if unaccelerated state errors out + subprocess.run(["open", "-a", "System Preferences"]) + time.sleep(5) + sys.exit(0) + + + def _check_if_new_patches_needed(self, patches: dict) -> bool: + """ + Checks if any new patches are needed for the user to install + Newer users will assume the root patch menu will present missing patches. + Thus we'll need to see if the exact same OCLP build was used already + """ + + if self.constants.commit_info[0] in ["Running from source", "Built from source"]: + return True + + if self.constants.computer.oclp_sys_url != self.constants.commit_info[2]: + # If commits are different, assume patches are as well + return True + + oclp_plist = "/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist" + if not Path(oclp_plist).exists(): + # If it doesn't exist, no patches were ever installed + # ie. all patches applicable + return True + + oclp_plist_data = plistlib.load(open(oclp_plist, "rb")) + for patch in patches: + if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True): + # Patches should share the same name as the plist key + # See sys_patch_dict.py for more info + patch_installed = False + for key in oclp_plist_data: + if "Display Name" not in oclp_plist_data[key]: + continue + if oclp_plist_data[key]["Display Name"] == patch: + patch_installed = True + break + + if patch_installed is False: + logging.info(f"- Patch {patch} not installed") + return True + + logging.info("- No new patches detected for system") + return False \ No newline at end of file diff --git a/resources/wx_gui/gui_update.py b/resources/wx_gui/gui_update.py new file mode 100644 index 000000000..844ae5ef4 --- /dev/null +++ b/resources/wx_gui/gui_update.py @@ -0,0 +1,254 @@ +# Generate UI for updating the patcher +import wx +import sys +import time +import logging +import datetime +import threading +import subprocess + +from pathlib import Path + +from resources.wx_gui import gui_download +from resources import ( + constants, + network_handler, + updates +) + + +class UpdateFrame(wx.Frame): + """ + Create a frame for updating the patcher + """ + def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: wx.Point, url: str = "", version_label: str = "") -> None: + if parent: + self.parent: wx.Frame = parent + + for child in self.parent.GetChildren(): + child.Hide() + parent.Hide() + else: + super(UpdateFrame, self).__init__(parent, title=title, size=(350, 300), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) + + self.title: str = title + self.constants: constants.Constants = global_constants + self.application_path = self.constants.payload_path / "OpenCore-Patcher.app" + self.screen_location: wx.Point = screen_location + if parent: + self.parent.Centre() + self.screen_location = parent.GetScreenPosition() + else: + self.Centre() + self.screen_location = self.GetScreenPosition() + + + 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 + + self.version_label = version_label + self.url = url + + self.frame: wx.Frame = wx.Frame( + parent=parent if parent else self, + title=self.title, + size=(350, 130), + pos=self.screen_location, + style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX + ) + + # Title: Preparing update + title_label = wx.StaticText(self.frame, label="Preparing download...", pos=(-1,1)) + title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + title_label.Centre(wx.HORIZONTAL) + + # Progress bar + progress_bar = wx.Gauge(self.frame, range=100, pos=(10, 50), size=(300, 20)) + progress_bar.Centre(wx.HORIZONTAL) + progress_bar.Pulse() + self.progress_bar = progress_bar + + self.frame.Show() + wx.Yield() + + download_obj = network_handler.DownloadObject(url, self.constants.payload_path / "OpenCore-Patcher-GUI.app.zip") + + gui_download.DownloadFrame( + self.frame, + title=self.title, + global_constants=self.constants, + download_obj=download_obj, + item_name=f"OpenCore Patcher {version_label}" + ) + + if download_obj.download_complete is False: + progress_bar.SetValue(0) + wx.MessageBox("Failed to download update. If you continue to have this issue, please manually download OpenCore Legacy Patcher off Github", "Critical Error!", wx.OK | wx.ICON_ERROR) + sys.exit(1) + + # Title: Extracting update + title_label.SetLabel("Extracting update...") + title_label.Centre(wx.HORIZONTAL) + wx.Yield() + + thread = threading.Thread(target=self._extract_update) + thread.start() + + while thread.is_alive(): + wx.Yield() + + # Title: Installing update + title_label.SetLabel("Installing update...") + title_label.Centre(wx.HORIZONTAL) + + thread = threading.Thread(target=self._install_update) + thread.start() + + while thread.is_alive(): + wx.Yield() + + # Title: Update complete + title_label.SetLabel("Update complete!") + title_label.Centre(wx.HORIZONTAL) + + # Progress bar + progress_bar.Hide() + + # Label: 0.6.6 has been installed to: + installed_label = wx.StaticText(self.frame, label=f"{version_label} has been installed:", pos=(-1, progress_bar.GetPosition().y - 15)) + installed_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont")) + installed_label.Centre(wx.HORIZONTAL) + + # Label: '/Library/Application Support/Dortania' + installed_path_label = wx.StaticText(self.frame, label='/Library/Application Support/Dortania', pos=(-1, installed_label.GetPosition().y + 20)) + installed_path_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + installed_path_label.Centre(wx.HORIZONTAL) + + # Label: Launching update shortly... + launch_label = wx.StaticText(self.frame, label="Launching update shortly...", pos=(-1, installed_path_label.GetPosition().y + 30)) + launch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont")) + launch_label.Centre(wx.HORIZONTAL) + + # Adjust frame size + self.frame.SetSize((-1, launch_label.GetPosition().y + 60)) + + thread = threading.Thread(target=self._launch_update) + thread.start() + + while thread.is_alive(): + wx.Yield() + + timer = 5 + while True: + launch_label.SetLabel(f"Closing old process in {timer} seconds") + launch_label.Centre(wx.HORIZONTAL) + wx.Yield() + time.sleep(1) + timer -= 1 + if timer == 0: + break + + sys.exit(0) + + + + def _extract_update(self) -> None: + """ + Extracts the update + """ + logging.info("Extracting update") + if Path(self.application_path).exists(): + subprocess.run(["rm", "-rf", str(self.application_path)]) + + # Some hell spawn at Github decided to double zip our Github Actions artifacts + # So we need to unzip it twice + for i in range(2): + result = subprocess.run( + ["ditto", "-xk", str(self.constants.payload_path / "OpenCore-Patcher-GUI.app.zip"), str(self.constants.payload_path)], capture_output=True + ) + if result.returncode != 0: + wx.CallAfter(self.progress_bar.SetValue, 0) + wx.CallAfter(wx.MessageBox, f"Failed to extract update. Error: {result.stderr.decode('utf-8')}", "Critical Error!", wx.OK | wx.ICON_ERROR) + wx.CallAfter(sys.exit, 1) + break + + if Path(self.application_path).exists(): + break + + if i == 1: + wx.CallAfter(self.progress_bar.SetValue, 0) + wx.CallAfter(wx.MessageBox, "Failed to extract update. Error: Update file does not exist", "Critical Error!", wx.OK | wx.ICON_ERROR) + wx.CallAfter(sys.exit, 1) + break + + + def _install_update(self) -> None: + """ + Installs update to '/Library/Application Support/Dortania/OpenCore-Patcher.app' + """ + logging.info(f"Installing update: {self.application_path}") + + # Create bash script to run as root + script = f"""#!/bin/bash +# Check if '/Library/Application Support/Dortania' exists +if [ ! -d "/Library/Application Support/Dortania" ]; then + mkdir -p "/Library/Application Support/Dortania" +fi + +# Check if '/Library/Application Support/Dortania/OpenCore-Patcher.app' exists +if [ -d "/Library/Application Support/Dortania/OpenCore-Patcher.app" ]; then + rm -rf "/Library/Application Support/Dortania/OpenCore-Patcher.app" +fi + +# Move '/tmp/OpenCore-Patcher.app' to '/Library/Application Support/Dortania' +mv "{str(self.application_path)}" "/Library/Application Support/Dortania/OpenCore-Patcher.app" + +# Check if '/Applications/OpenCore-Patcher.app' exists +if [ ! -d "/Applications/OpenCore-Patcher.app" ]; then + ln -s "/Library/Application Support/Dortania/OpenCore-Patcher.app" "/Applications/OpenCore-Patcher.app" +fi + +# Create update.plist with info about update +cat << EOF > "/Library/Application Support/Dortania/update.plist" + + + + CFBundleShortVersionString + {self.version_label} + CFBundleVersion + {self.version_label} + InstallationDate + {datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")} + InstallationSource + {self.url} + + +EOF +""" + # Write script to file + with open(self.constants.payload_path / "update.sh", "w") as f: + f.write(script) + + # Execute script + args = [self.constants.oclp_helper_path, "/bin/sh", str(self.constants.payload_path / "update.sh")] + result = subprocess.run(args, capture_output=True) + if result.returncode != 0: + wx.CallAfter(self.progress_bar.SetValue, 0) + if "User cancelled" in result.stderr.decode("utf-8"): + wx.CallAfter(wx.MessageBox, "User cancelled update", "Update Cancelled", wx.OK | wx.ICON_INFORMATION) + else: + wx.CallAfter(wx.MessageBox, f"Failed to install update. Error: {result.stderr.decode('utf-8')}", "Critical Error!", wx.OK | wx.ICON_ERROR) + wx.CallAfter(sys.exit, 1) + + + def _launch_update(self) -> None: + """ + Launches newly installed update + """ + logging.info("Launching update: '/Library/Application Support/Dortania/OpenCore-Patcher.app'") + subprocess.Popen(["/Library/Application Support/Dortania/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher", "--update_installed"])