diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 0e269cb92..35116f3bb 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -95,7 +95,6 @@ body: description: What variant of our software are you running? options: - GUI (Graphical User Interface) - - TUI (Text User Interface) - CLI (Command Line Interface) - Other/Non-Applicable validations: diff --git a/.github/workflows/build-app-wxpython.yml b/.github/workflows/build-app-wxpython.yml index ba9d74b00..a803e4496 100644 --- a/.github/workflows/build-app-wxpython.yml +++ b/.github/workflows/build-app-wxpython.yml @@ -11,12 +11,14 @@ jobs: name: Build wxPython runs-on: x86_64_mojave if: github.repository_owner == 'dortania' + env: branch: ${{ github.ref }} commiturl: ${{ github.event.head_commit.url }}${{ github.event.release.html_url }} commitdate: ${{ github.event.head_commit.timestamp }}${{ github.event.release.published_at }} MAC_NOTARIZATION_USERNAME: ${{ secrets.MAC_NOTARIZATION_USERNAME }} MAC_NOTARIZATION_PASSWORD: ${{ secrets.MAC_NOTARIZATION_PASSWORD }} + steps: - uses: actions/checkout@v3 - run: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 Build-Binary.command --reset_binaries --branch "${{ env.branch }}" --commit "${{ env.commiturl }}" --commit_date "${{ env.commitdate }}" @@ -30,11 +32,13 @@ jobs: with: name: OpenCore-Patcher.app (GUI) path: OpenCore-Patcher-GUI.app.zip + - name: Upload Package to Artifacts uses: actions/upload-artifact@v3 with: name: AutoPkg-Assets.pkg path: ./dist/AutoPkg-Assets.pkg + - name: Upload Binary to Release if: github.event_name == 'release' uses: svenstaro/upload-release-action@e74ff71f7d8a4c4745b560a485cc5fdb9b5b999d @@ -43,6 +47,7 @@ jobs: file: OpenCore-Patcher-GUI.app.zip tag: ${{ github.ref }} file_glob: true + - name: Upload Package to Release if: github.event_name == 'release' uses: svenstaro/upload-release-action@e74ff71f7d8a4c4745b560a485cc5fdb9b5b999d diff --git a/.github/workflows/build-app.yml b/.github/workflows/build-app.yml deleted file mode 100644 index b6a9dca72..000000000 --- a/.github/workflows/build-app.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: CI - Build TUI - -on: - push: - workflow_dispatch: - release: - types: [published] - -jobs: - build: - name: Build TUI - runs-on: x86_64_mojave - env: - branch: ${{ github.ref }} - commiturl: ${{ github.event.head_commit.url }}${{ github.event.release.html_url }} - commitdate: ${{ github.event.head_commit.timestamp }}${{ github.event.release.published_at }} - steps: - - uses: actions/checkout@v3 - - run: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 Build-Binary.command --build_tui --reset_binaries --branch "${{ env.branch }}" --commit "${{ env.commiturl }}" --commit_date "${{ env.commitdate }}" - - run: 'codesign -s "Developer ID Application: Mykola Grymalyuk (S74BDJXQMD)" -v --force --deep --timestamp --entitlements ./payloads/entitlements.plist -o runtime "dist/OpenCore-Patcher.app"' - - run: cd dist; zip -r ../OpenCore-Patcher-TUI.app.zip OpenCore-Patcher.app - - - name: Validate OpenCore - run: ./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher --validate diff --git a/.github/workflows/build-site.yml b/.github/workflows/build-site.yml index 12ea0c19c..48eac37e6 100644 --- a/.github/workflows/build-site.yml +++ b/.github/workflows/build-site.yml @@ -9,6 +9,7 @@ jobs: build: name: Build Site and Deploy runs-on: ubuntu-latest + if: github.repository_owner == 'dortania' steps: - uses: actions/setup-node@v3 with: diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 000000000..52496e234 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,20 @@ +name: CI - Validation + +on: + push: + workflow_dispatch: + release: + types: [published] + +jobs: + build: + name: Validate + runs-on: x86_64_mojave + if: github.repository_owner == 'dortania' + env: + branch: ${{ github.ref }} + commiturl: ${{ github.event.head_commit.url }}${{ github.event.release.html_url }} + commitdate: ${{ github.event.head_commit.timestamp }}${{ github.event.release.published_at }} + steps: + - uses: actions/checkout@v3 + - run: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 OpenCore-Patcher-GUI.command --validate diff --git a/Build-Binary.command b/Build-Binary.command index a928bdc6f..92c0e055f 100755 --- a/Build-Binary.command +++ b/Build-Binary.command @@ -1,14 +1,7 @@ #!/usr/bin/env python3 -# This script's main purpose is to handle the following: -# - Download PatcherSupportPkg resources -# - Convert payloads directory into DMG (GUI only) -# - Build Binary via Pyinstaller -# - Add Launcher.sh (TUI only) -# - Patch 'LC_VERSION_MIN_MACOSX' to OS X 10.10 -# - Add commit data to Info.plist - -# Copyright (C) 2022 - Mykola Grymalyuk +# Generate stand alone application for OpenCore-Patcher +# Copyright (C) 2022-2023 - Mykola Grymalyuk from pathlib import Path import time @@ -21,26 +14,49 @@ import sys from resources import constants -class create_binary: + +class CreateBinary: + """ + Library for creating OpenCore-Patcher application + + This script's main purpose is to handle the following: + - Download external dependancies (ex. PatcherSupportPkg) + - Convert payloads directory into DMG + - Build Binary via Pyinstaller + - Patch 'LC_VERSION_MIN_MACOSX' to OS X 10.10 + - Add commit data to Info.plist + + """ def __init__(self): start = time.time() print("- Starting build script") - self.set_cwd() - self.args = self.parse_arguments() - self.preflight_processes() - self.build_binary() - self.postflight_processes() + self.args = self._parse_arguments() + + self._set_cwd() + + self._preflight_processes() + self._build_binary() + self._postflight_processes() print(f"- Build script completed in {str(round(time.time() - start, 2))} seconds") - def set_cwd(self): + + def _set_cwd(self): + """ + Initialize current working directory to parent of this script + """ + os.chdir(Path(__file__).resolve().parent) print(f"- Current Working Directory: \n\t{os.getcwd()}") - def parse_arguments(self): + + def _parse_arguments(self): + """ + Parse arguments passed to script + """ + parser = argparse.ArgumentParser(description='Builds OpenCore-Patcher binary') - parser.add_argument('--build_tui', action='store_true', help='Builds TUI binary, if omitted GUI binary is built') parser.add_argument('--branch', type=str, help='Git branch name') parser.add_argument('--commit', type=str, help='Git commit URL') parser.add_argument('--commit_date', type=str, help='Git commit date') @@ -48,7 +64,12 @@ class create_binary: args = parser.parse_args() return args - def setup_pathing(self): + + def _setup_pathing(self): + """ + Initialize pathing for pyinstaller + """ + python_path = sys.executable python_binary = python_path.split("/")[-1] python_bin_dir = python_path.strip(python_binary) @@ -70,25 +91,36 @@ class create_binary: self.pyinstaller_path = pyinstaller_path - def preflight_processes(self): + + def _preflight_processes(self): + """ + Start preflight processes + """ + print("- Starting preflight processes") - self.setup_pathing() - self.delete_extra_binaries() - self.download_resources() - if not self.args.build_tui: - # payloads.dmg is only needed for GUI builds - self.generate_payloads_dmg() + self._setup_pathing() + self._delete_extra_binaries() + self._download_resources() + self._generate_payloads_dmg() + + + def _postflight_processes(self): + """ + Start postflight processes + """ - def postflight_processes(self): print("- Starting postflight processes") - if self.args.build_tui: - self.move_launcher() - self.patch_load_command() - self.add_commit_data() - self.post_flight_cleanup() - self.mini_validate() + self._patch_load_command() + self._add_commit_data() + self._post_flight_cleanup() + self._mini_validate() + + + def _build_binary(self): + """ + Build binary via pyinstaller + """ - def build_binary(self): if Path(f"./dist/OpenCore-Patcher.app").exists(): print("- Found OpenCore-Patcher.app, removing...") rm_output = subprocess.run( @@ -101,12 +133,8 @@ class create_binary: raise Exception("Remove failed") - if self.args.build_tui: - print("- Building TUI binary...") - build_args = [self.pyinstaller_path, "./OpenCore-Patcher.spec", "--noconfirm"] - else: - print("- Building GUI binary...") - build_args = [self.pyinstaller_path, "./OpenCore-Patcher-GUI.spec", "--noconfirm"] + print("- Building GUI binary...") + build_args = [self.pyinstaller_path, "./OpenCore-Patcher-GUI.spec", "--noconfirm"] build_result = subprocess.run(build_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if build_result.returncode != 0: @@ -114,27 +142,52 @@ class create_binary: print(build_result.stderr.decode('utf-8')) raise Exception("Build failed") - def delete_extra_binaries(self): - delete_files = [ - "AutoPkg-Assets.pkg", - "AutoPkg-Assets.pkg.zip", - "InstallAssistant.pkg", - "InstallAssistant.pkg.integrityDataV1", - "KDK.dmg", + + def _delete_extra_binaries(self): + """ + Delete extra binaries from payloads directory + """ + + whitelist_folders = [ + "ACPI", + "Config", + "Drivers", + "Icon", + "InstallPackage", + "Kexts", + "OpenCore", + "Tools", ] + + whitelist_files = [ + "com.dortania.opencore-legacy-patcher.auto-patch.plist", + "entitlements.plist", + "launcher.sh", + "OC-Patcher-TUI.icns", + "OC-Patcher.icns", + "Universal-Binaries.zip", + ] + + print("- Deleting extra binaries...") for file in Path("payloads").glob(pattern="*"): - if file.name in delete_files or file.name.startswith("OpenCore-Legacy-Patcher"): + if file.is_dir(): + if file.name in whitelist_folders: + continue print(f" - Deleting {file.name}") - file.unlink() - elif (Path(file) / Path("Contents/Resources/createinstallmedia")).exists(): - print(f" - Deleting {file}") - subprocess.run(["rm", "-rf", file]) - elif Path(file).is_dir() and file.name == "Universal-Binaries": - print(f" - Deleting {file}") subprocess.run(["rm", "-rf", file]) + else: + if file.name in whitelist_files: + continue + print(f" - Deleting {file.name}") + subprocess.run(["rm", "-f", file]) + + + def _download_resources(self): + """ + Download required dependencies + """ - def download_resources(self): patcher_support_pkg_version = constants.Constants().patcher_support_pkg_version required_resources = [ "Universal-Binaries.zip" @@ -181,21 +234,29 @@ class create_binary: print(mv_output.stderr.decode('utf-8')) raise Exception("Move failed") - def generate_payloads_dmg(self): + + def _generate_payloads_dmg(self): + """ + Generate disk image containing all payloads + Disk image will be password protected due to issues with + Apple's notarization system and inclusion of kernel extensions + """ + if Path("./payloads.dmg").exists(): - if self.args.reset_binaries: - print(" - Removing old payloads.dmg") - rm_output = subprocess.run( - ["rm", "-rf", "./payloads.dmg"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - if rm_output.returncode != 0: - print("- Remove failed") - print(rm_output.stderr.decode('utf-8')) - raise Exception("Remove failed") - else: + if not self.args.reset_binaries: print(" - payloads.dmg already exists, skipping creation") return + + print(" - Removing old payloads.dmg") + rm_output = subprocess.run( + ["rm", "-rf", "./payloads.dmg"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + if rm_output.returncode != 0: + print("- Remove failed") + print(rm_output.stderr.decode('utf-8')) + raise Exception("Remove failed") + print(" - Generating DMG...") dmg_output = subprocess.run([ 'hdiutil', 'create', './payloads.dmg', @@ -213,7 +274,12 @@ class create_binary: print(" - DMG generation complete") - def add_commit_data(self): + + def _add_commit_data(self): + """ + Add commit data to Info.plist + """ + if not self.args.branch and not self.args.commit and not self.args.commit_date: print(" - No commit data provided, adding source info") branch = "Built from source" @@ -233,20 +299,25 @@ class create_binary: } plistlib.dump(plist, Path(plist_path).open("wb"), sort_keys=True) - def patch_load_command(self): - # Patches LC_VERSION_MIN_MACOSX in Load Command to report 10.10 - # - # By default Pyinstaller will create binaries supporting 10.13+ - # However this limitation is entirely arbitrary for our libraries - # and instead we're able to support 10.10 without issues. - # - # To verify set version: - # otool -l ./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher - # - # cmd LC_VERSION_MIN_MACOSX - # cmdsize 16 - # version 10.13 - # sdk 10.9 + + def _patch_load_command(self): + """ + Patch LC_VERSION_MIN_MACOSX in Load Command to report 10.10 + + By default Pyinstaller will create binaries supporting 10.13+ + However this limitation is entirely arbitrary for our libraries + and instead we're able to support 10.10 without issues. + + To verify set version: + otool -l ./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher + + cmd LC_VERSION_MIN_MACOSX + cmdsize 16 + version 10.13 + sdk 10.9 + + """ + print(" - Patching LC_VERSION_MIN_MACOSX") path = './dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher' find = b'\x00\x0D\x0A\x00' # 10.13 (0xA0D) @@ -257,19 +328,12 @@ class create_binary: with open(path, 'wb') as f: f.write(data) - def move_launcher(self): - print(" - Adding TUI launcher") - mv_output = subprocess.run( - ["cp", "./payloads/launcher.sh", "./dist/OpenCore-Patcher.app/Contents/MacOS/Launcher"], - stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - if mv_output.returncode != 0: - print(" - Move failed") - print(mv_output.stderr.decode('utf-8')) - raise Exception("Move failed") - def post_flight_cleanup(self): - # Remove ./dist/OpenCore-Patcher + def _post_flight_cleanup(self): + """ + Post flight cleanup + """ + path = "./dist/OpenCore-Patcher" print(f" - Removing {path}") rm_output = subprocess.run( @@ -281,9 +345,12 @@ class create_binary: print(rm_output.stderr.decode('utf-8')) raise Exception(f"Remove failed: {path}") - def mini_validate(self): - # Ensure binary can start - # Only build a single config, TUI CI will do in-depth validation + + def _mini_validate(self): + """ + Validate generated binary + """ + print(" - Validating binary") validate_output = subprocess.run( ["./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher", "--build", "--model", "MacPro3,1"], @@ -294,5 +361,6 @@ class create_binary: print(validate_output.stderr.decode('utf-8')) raise Exception("Validation failed") + if __name__ == "__main__": - create_binary() \ No newline at end of file + CreateBinary() \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 587bace9b..bd1badd9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,7 +25,10 @@ - Allows for more reliable network calls and downloads - Better supports network timeouts and disconnects - Dramatically less noise in console during downloads - - Removed unused sys_patch_downloader.py module + - Removed unused modules: + - sys_patch_downloader.py + - run.py + - TUI modules - Build Server Changes: - Upgrade Python backend to 3.10.9 - Upgrade Python modules: diff --git a/OpenCore-Patcher-GUI.command b/OpenCore-Patcher-GUI.command index b076e5aed..ad809085a 100755 --- a/OpenCore-Patcher-GUI.command +++ b/OpenCore-Patcher-GUI.command @@ -3,4 +3,4 @@ from resources import main if __name__ == '__main__': - main.OpenCoreLegacyPatcher(True) \ No newline at end of file + main.OpenCoreLegacyPatcher() \ No newline at end of file diff --git a/OpenCore-Patcher.command b/OpenCore-Patcher.command deleted file mode 100755 index ad809085a..000000000 --- a/OpenCore-Patcher.command +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk -from resources import main - -if __name__ == '__main__': - main.OpenCoreLegacyPatcher() \ No newline at end of file diff --git a/OpenCore-Patcher.spec b/OpenCore-Patcher.spec deleted file mode 100644 index 0ec98adb1..000000000 --- a/OpenCore-Patcher.spec +++ /dev/null @@ -1,44 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- -import sys, os -sys.path.append(os.path.abspath(os.getcwd())) -from resources import constants -block_cipher = None - - -a = Analysis(['OpenCore-Patcher.command'], - pathex=['resources', 'data'], - binaries=[], - datas=[('payloads', 'payloads')], - hiddenimports=[], - hookspath=[], - runtime_hooks=[], - excludes=['wxPython', 'wxpython'], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False) -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - a.binaries, - a.zipfiles, - a.datas, - [], - name='OpenCore-Patcher', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - upx_exclude=[], - runtime_tmpdir=None, - console=True ) -app = BUNDLE(exe, - name='OpenCore-Patcher.app', - icon="payloads/OC-Patcher-TUI.icns", - bundle_identifier="com.dortania.opencore-legacy-patcher-tui", - info_plist={ - "CFBundleShortVersionString": constants.Constants().patcher_version, - "CFBundleExecutable": "MacOS/Launcher", - "NSHumanReadableCopyright": constants.Constants().copyright_date, - }) \ No newline at end of file diff --git a/SOURCE.md b/SOURCE.md index 88adc6b88..a9badf10a 100644 --- a/SOURCE.md +++ b/SOURCE.md @@ -55,7 +55,7 @@ See `-h`/`--help` for more information on supported CLI arguments. ## Generating prebuilt binaries -The main goal of generating prebuilt binaries is to strip the requirement of a local python installation for users. For developers, there's very little benefit besides enabling dark mode support in the GUI. For development, simply use the OpenCore-Patcher.command file with a python3 installation. +The main goal of generating prebuilt binaries is to strip the requirement of a local python installation for users. For developers, there's very little benefit besides enabling dark mode support in the GUI. For development, simply use the OpenCore-Patcher-GUI.command file with a python3 installation. * Note that due to PyInstaller's linking mechanism, binaries generated on Catalina and newer are not compatible with High Sierra and older * To ensure the largest compatibility, generate binaries on macOS Mojave. These binaries will be compatible with macOS 10.9 to macOS 12. @@ -68,7 +68,6 @@ pip3 install pyinstaller cd ~/Developer/OpenCore-Legacy-Patcher/ # Create the pyinstaller based Application # Optional Arguments -# '--build_tui': Create TUI vairant (deprecated) # '--reset_binaries': Redownload and generate support files python3 Build-Binary.command # Open build folder diff --git a/data/amfi_data.py b/data/amfi_data.py index d27089f8a..1dbcf2a5a 100644 --- a/data/amfi_data.py +++ b/data/amfi_data.py @@ -6,19 +6,20 @@ # - 0x3 used in 11.0.1 dyld source: # - https://github.com/apple-oss-distributions/dyld/blob/5c9192436bb195e7a8fe61f22a229ee3d30d8222/testing/test-cases/kernel-hello-world.dtest/main.c#L2 -class apple_mobile_file_integrity: +import enum + +class AppleMobileFileIntegrity(enum.IntEnum): # Names set are solely for readability # Internal names are unknown - amfi_values = { - "AMFI_ALLOW_TASK_FOR_PID": False, # 0x1 - Allow Task for PID (alt. amfi_unrestrict_task_for_pid=0x1) - "AMFI_ALLOW_INVALID_SIGNATURE": False, # 0x2 - Reduce sig enforcement (alt. amfi_allow_any_signature=0x1) - "AMFI_LV_ENFORCE_THIRD_PARTY": False, # 0x4 - Don't mark external binaries as platform binaries - "AMFI_UNKNOWN_1": False, # 0x8 - "AMFI_UNKNOWN_2": False, # 0x10 - "AMFI_UNKNOWN_3": False, # 0x20 - "AMFI_UNKNOWN_4": False, # 0x40 - "AMFI_ALLOW_EVERYTHING": False, # 0x80 - Disable sig enforcement and Library Validation (alt. amfi_get_out_of_my_way=0x1) - }, + AMFI_ALLOW_TASK_FOR_PID: int = 0x1 # Allow Task for PID (alt. amfi_unrestrict_task_for_pid=0x1) + AMFI_ALLOW_INVALID_SIGNATURE: int = 0x2 # Reduce sig enforcement (alt. amfi_allow_any_signature=0x1) + AMFI_LV_ENFORCE_THIRD_PARTY: int = 0x4 # Don't mark external binaries as platform binaries + AMFI_UNKNOWN_1: int = 0x8 + AMFI_UNKNOWN_2: int = 0x10 + AMFI_UNKNOWN_3: int = 0x20 + AMFI_UNKNOWN_4: int = 0x40 + AMFI_ALLOW_EVERYTHING: int = 0x80 # Disable sig enforcement and Library Validation (alt. amfi_get_out_of_my_way=0x1) + # Internally within AMFI.kext, Apple references 0x2 and 0x80 as both 'Disable signature enforcement' # However 0x80 is a higher privilege than 0x2, and breaks TCC support in OS (ex. Camera, Microphone, etc prompts) diff --git a/data/mirror_data.py b/data/mirror_data.py deleted file mode 100644 index bd6761307..000000000 --- a/data/mirror_data.py +++ /dev/null @@ -1,11 +0,0 @@ -# Mirrors of Apple's InstallAssistant.pkg -# Currently only listing important Installers no longer on Apple's servers - -Install_macOS_Big_Sur_11_2_3 = { - "Version": "11.2.3", - "Build": "20D91", - "Link": "https://archive.org/download/install-assistant-20D91/InstallAssistant.pkg", - "Size": 12211077798, - "Source": "Archive.org", - "integrity": None, -} \ No newline at end of file diff --git a/docs/MODELS.md b/docs/MODELS.md index b59a3c1fc..e44d0f6cd 100644 --- a/docs/MODELS.md +++ b/docs/MODELS.md @@ -22,7 +22,7 @@ Regarding OS support, see below: | Support Entry | Supported OSes | Description | Comment | | :--- | :--- | :--- | :--- | -| HostOS | macOS 10.9 - macOS 13 | Refers to OSes where running OpenCore-Patcher.app are supported | Supports 10.7+ if [Python 3.9 or higher](https://www.python.org/downloads/) is manually installed, simply run the `OpenCore-Patcher.command` located in the repo | +| HostOS | macOS 10.9 - macOS 13 | Refers to OSes where running OpenCore-Patcher.app are supported | Supports 10.7+ if [Python 3.9 or higher](https://www.python.org/downloads/) is manually installed, simply run the `OpenCore-Patcher-GUI.command` located in the repo | | TargetOS | macOS 11 - macOS 13 | Refers to OSes that can be patched to run with OpenCore | May support 10.4 and newer (in a potentially broken state). No support provided. | ### MacBook diff --git a/docs/POST-INSTALL.md b/docs/POST-INSTALL.md index f60f5daa7..0ee4bfd6b 100644 --- a/docs/POST-INSTALL.md +++ b/docs/POST-INSTALL.md @@ -20,14 +20,11 @@ And voila! No more USB drive required. To do this, run the OpenCore Patcher and head to Patcher Settings: -| GUI Settings | TUI Settings -| :--- | :--- | -|![](../images/OCLP-GUI-Settings-ShowPicker.png) | ![](../images/OCLP-TUI-Settings.png) | +![](../images/OCLP-GUI-Settings-ShowPicker.png) Here you can change different patcher settings, however the main interest is: -* Show Boot Picker (GUI) -* Set ShowPicker Mode (TUI) +* Show Boot Picker Once you've toggled them both off, build your OpenCore EFI once again and install to your desired drive. Now to show the OpenCore selector, you can simply hold down the "ESC" key while clicking on EFI boot, and then you can release the "ESC" key when you see the cursor arrow at the top left. diff --git a/resources/amfi_detect.py b/resources/amfi_detect.py index f13cfa974..0e09fb80c 100644 --- a/resources/amfi_detect.py +++ b/resources/amfi_detect.py @@ -3,6 +3,7 @@ import enum from resources import utilities +from data import amfi_data class AmfiConfigDetectLevel(enum.IntEnum): @@ -67,32 +68,31 @@ class AmfiConfigurationDetection: amfi_value = 0 for arg in self.boot_args: - if arg.startswith("amfi="): - try: - amfi_value = arg.split("=") - if len(amfi_value) != 2: - return - amfi_value = amfi_value[1] - if amfi_value.startswith("0x"): - amfi_value = int(amfi_value, 16) - else: - amfi_value = int(amfi_value) - except: + if not arg.startswith("amfi="): + continue + try: + amfi_value = arg.split("=") + if len(amfi_value) != 2: return - break + amfi_value = amfi_value[1] + if amfi_value.startswith("0x"): + amfi_value = int(amfi_value, 16) + else: + amfi_value = int(amfi_value) + except: + return + break if amfi_value == 0: return - if amfi_value & 0x1: - self.AMFI_ALLOW_TASK_FOR_PID = True - if amfi_value & 0x2: - self.AMFI_ALLOW_INVALID_SIGNATURE = True - if amfi_value & 0x4: - self.AMFI_LV_ENFORCE_THIRD_PARTY = True - if amfi_value & 0x80: - self.AMFI_ALLOW_EVERYTHING = True - self.SKIP_LIBRARY_VALIDATION = True + self.AMFI_ALLOW_TASK_FOR_PID: bool = amfi_value & amfi_data.AppleMobileFileIntegrity.AMFI_ALLOW_TASK_FOR_PID + self.AMFI_ALLOW_INVALID_SIGNATURE: bool = amfi_value & amfi_data.AppleMobileFileIntegrity.AMFI_ALLOW_INVALID_SIGNATURE + self.AMFI_LV_ENFORCE_THIRD_PARTY: bool = amfi_value & amfi_data.AppleMobileFileIntegrity.AMFI_LV_ENFORCE_THIRD_PARTY + + if amfi_value & amfi_data.AppleMobileFileIntegrity.AMFI_ALLOW_EVERYTHING: + self.AMFI_ALLOW_EVERYTHING = True + self.SKIP_LIBRARY_VALIDATION = True self.AMFI_ALLOW_INVALID_SIGNATURE = True diff --git a/resources/arguments.py b/resources/arguments.py index ece02e81d..a0eb71f88 100644 --- a/resources/arguments.py +++ b/resources/arguments.py @@ -1,116 +1,190 @@ -import sys -from resources import defaults, utilities, validation -from resources.sys_patch import sys_patch, sys_patch_auto -from resources.build import build -from data import model_array import threading import time import logging +import sys + +from resources import defaults, utilities, validation, constants +from resources.sys_patch import sys_patch, sys_patch_auto +from resources.build import build +from data import model_array + # Generic building args class arguments: - def __init__(self): + + def __init__(self, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants + self.args = utilities.check_cli_args() - def parse_arguments(self, settings): + self._parse_arguments() + + + def _parse_arguments(self): + """ + Parses arguments passed to the patcher + """ + if self.args.validate: - validation.validate(settings) - elif self.args.build: + self._validation_handler() + return + + if self.args.build: + self._build_handler() + return + + if self.args.patch_sys_vol: + self._sys_patch_handler() + return + + if self.args.unpatch_sys_vol: + self._sys_unpatch_handler() + return + + if self.args.auto_patch: + self._sys_patch_auto_handler() + return + + + def _validation_handler(self): + """ + Enter validation mode + """ + + validation.PatcherValidation(self.constants) + + + def _sys_patch_handler(self): + """ + Start root volume patching + """ + + logging.info("- Set System Volume patching") + if "Library/InstallerSandboxes/" in str(self.constants.payload_path): + logging.info("- Running from Installer Sandbox") + thread = threading.Thread(target=sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, None).start_patch) + thread.start() + while thread.is_alive(): + utilities.block_os_updaters() + time.sleep(1) + else: + sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, None).start_patch() + + + def _sys_unpatch_handler(self): + """ + Start root volume unpatching + """ + logging.info("- Set System Volume unpatching") + sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, None).start_unpatch() + + + def _sys_patch_auto_handler(self): + """ + Start root volume auto patching + """ + + logging.info("- Set Auto patching") + sys_patch_auto.AutomaticSysPatch(self.constants).start_auto_patch() + + + def _build_handler(self): + """ + Start config building process + """ + + if self.args.model: if self.args.model: - if self.args.model: - logging.info(f"- Using custom model: {self.args.model}") - settings.custom_model = self.args.model - defaults.generate_defaults(settings.custom_model, False, settings) - elif settings.computer.real_model not in model_array.SupportedSMBIOS and settings.allow_oc_everywhere is False: - logging.info( - """Your model is not supported by this patcher for running unsupported OSes!" + logging.info(f"- Using custom model: {self.args.model}") + self.constants.custom_model = self.args.model + defaults.GenerateDefaults(self.constants.custom_model, False, self.constants) + elif self.constants.computer.real_model not in model_array.SupportedSMBIOS and self.constants.allow_oc_everywhere is False: + logging.info( + """Your model is not supported by this patcher for running unsupported OSes!" - If you plan to create the USB for another machine, please select the "Change Model" option in the menu.""" - ) - sys.exit(1) - else: - logging.info(f"- Using detected model: {settings.computer.real_model}") - defaults.generate_defaults(settings.custom_model, True, settings) +If you plan to create the USB for another machine, please select the "Change Model" option in the menu.""" + ) + sys.exit(1) + else: + logging.info(f"- Using detected model: {self.constants.computer.real_model}") + defaults.GenerateDefaults(self.constants.custom_model, True, self.constants) - if self.args.disk: - logging.info(f"- Install Disk set: {self.args.disk}") - settings.disk = self.args.disk - if self.args.verbose: - logging.info("- Set verbose configuration") - settings.verbose_debug = True - else: - settings.verbose_debug = False # Override Defaults detected - if self.args.debug_oc: - logging.info("- Set OpenCore DEBUG configuration") - settings.opencore_debug = True - settings.opencore_build = "DEBUG" - if self.args.debug_kext: - logging.info("- Set kext DEBUG configuration") - settings.kext_debug = True - if self.args.hide_picker: - logging.info("- Set HidePicker configuration") - settings.showpicker = False - if self.args.disable_sip: - logging.info("- Set Disable SIP configuration") - settings.sip_status = False - else: - settings.sip_status = True # Override Defaults detected - if self.args.disable_smb: - logging.info("- Set Disable SecureBootModel configuration") - settings.secure_status = False - else: - settings.secure_status = True # Override Defaults detected - if self.args.vault: - logging.info("- Set Vault configuration") - settings.vault = True - if self.args.firewire: - logging.info("- Set FireWire Boot configuration") - settings.firewire_boot = True - if self.args.nvme: - logging.info("- Set NVMe Boot configuration") - settings.nvme_boot = True - if self.args.wlan: - logging.info("- Set Wake on WLAN configuration") - settings.enable_wake_on_wlan = True - if self.args.disable_tb: - logging.info("- Set Disable Thunderbolt configuration") - settings.disable_tb = True - if self.args.force_surplus: - logging.info("- Forcing SurPlus override configuration") - settings.force_surplus = True - if self.args.moderate_smbios: - logging.info("- Set Moderate SMBIOS Patching configuration") - settings.serial_settings = "Moderate" - if self.args.smbios_spoof: - if self.args.smbios_spoof == "Minimal": - settings.serial_settings = "Minimal" - elif self.args.smbios_spoof == "Moderate": - settings.serial_settings = "Moderate" - elif self.args.smbios_spoof == "Advanced": - settings.serial_settings = "Advanced" - else: - logging.info(f"- Unknown SMBIOS arg passed: {self.args.smbios_spoof}") + if self.args.disk: + logging.info(f"- Install Disk set: {self.args.disk}") + self.constants.disk = self.args.disk - if self.args.support_all: - logging.info("- Building for natively supported model") - settings.allow_oc_everywhere = True - settings.serial_settings = "None" - build.build_opencore(settings.custom_model or settings.computer.real_model, settings).build_opencore() - elif self.args.patch_sys_vol: - logging.info("- Set System Volume patching") + if self.args.verbose: + logging.info("- Set verbose configuration") + self.constants.verbose_debug = True + else: + self.constants.verbose_debug = False # Override Defaults detected - if "Library/InstallerSandboxes/" in str(settings.payload_path): - logging.info("- Running from Installer Sandbox") - thread = threading.Thread(target=sys_patch.PatchSysVolume(settings.custom_model or settings.computer.real_model, settings, None).start_patch) - thread.start() - while thread.is_alive(): - utilities.block_os_updaters() - time.sleep(1) + if self.args.debug_oc: + logging.info("- Set OpenCore DEBUG configuration") + self.constants.opencore_debug = True + self.constants.opencore_build = "DEBUG" + + if self.args.debug_kext: + logging.info("- Set kext DEBUG configuration") + self.constants.kext_debug = True + + if self.args.hide_picker: + logging.info("- Set HidePicker configuration") + self.constants.showpicker = False + + if self.args.disable_sip: + logging.info("- Set Disable SIP configuration") + self.constants.sip_status = False + else: + self.constants.sip_status = True # Override Defaults detected + + if self.args.disable_smb: + logging.info("- Set Disable SecureBootModel configuration") + self.constants.secure_status = False + else: + self.constants.secure_status = True # Override Defaults detected + + if self.args.vault: + logging.info("- Set Vault configuration") + self.constants.vault = True + + if self.args.firewire: + logging.info("- Set FireWire Boot configuration") + self.constants.firewire_boot = True + + if self.args.nvme: + logging.info("- Set NVMe Boot configuration") + self.constants.nvme_boot = True + + if self.args.wlan: + logging.info("- Set Wake on WLAN configuration") + self.constants.enable_wake_on_wlan = True + + if self.args.disable_tb: + logging.info("- Set Disable Thunderbolt configuration") + self.constants.disable_tb = True + + if self.args.force_surplus: + logging.info("- Forcing SurPlus override configuration") + self.constants.force_surplus = True + + if self.args.moderate_smbios: + logging.info("- Set Moderate SMBIOS Patching configuration") + self.constants.serial_settings = "Moderate" + + if self.args.smbios_spoof: + if self.args.smbios_spoof == "Minimal": + self.constants.serial_settings = "Minimal" + elif self.args.smbios_spoof == "Moderate": + self.constants.serial_settings = "Moderate" + elif self.args.smbios_spoof == "Advanced": + self.constants.serial_settings = "Advanced" else: - sys_patch.PatchSysVolume(settings.custom_model or settings.computer.real_model, settings, None).start_patch() - elif self.args.unpatch_sys_vol: - logging.info("- Set System Volume unpatching") - sys_patch.PatchSysVolume(settings.custom_model or settings.computer.real_model, settings, None).start_unpatch() - elif self.args.auto_patch: - logging.info("- Set Auto patching") - sys_patch_auto.AutomaticSysPatch(settings).start_auto_patch() \ No newline at end of file + logging.info(f"- Unknown SMBIOS arg passed: {self.args.smbios_spoof}") + + if self.args.support_all: + logging.info("- Building for natively supported model") + self.constants.allow_oc_everywhere = True + self.constants.serial_settings = "None" + + build.build_opencore(self.constants.custom_model or self.constants.computer.real_model, self.constants).build_opencore() diff --git a/resources/cli_menu.py b/resources/cli_menu.py deleted file mode 100644 index a2d5a907a..000000000 --- a/resources/cli_menu.py +++ /dev/null @@ -1,1341 +0,0 @@ -# Handle misc CLI menu options -# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk -import sys -import logging - -from resources import constants, install, utilities, defaults, installer, tui_helpers, global_settings -from resources.sys_patch import sys_patch -from data import cpu_data, smbios_data, model_array, os_data, mirror_data - - -class MenuOptions: - def __init__(self, model, versions): - self.model = model - self.constants: constants.Constants() = versions - - def change_verbose(self): - utilities.cls() - utilities.header(["Set Verbose mode"]) - change_menu = input("Enable Verbose mode(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.verbose_debug = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.verbose_debug = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.change_verbose() - - def change_oc(self): - utilities.cls() - utilities.header(["Set OpenCore DEBUG mode"]) - change_menu = input("Enable OpenCore DEBUG mode(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.opencore_debug = True - self.constants.opencore_build = "DEBUG" - elif change_menu in {"n", "N", "no", "No"}: - self.constants.opencore_debug = False - self.constants.opencore_build = "RELEASE" - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.change_oc() - - def change_kext(self): - utilities.cls() - utilities.header(["Set Kext DEBUG mode"]) - change_menu = input("Enable Kext DEBUG mode(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.kext_debug = True - self.constants.kext_variant = "DEBUG" - elif change_menu in {"n", "N", "no", "No"}: - self.constants.kext_debug = False - self.constants.kext_variant = "RELEASE" - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.change_kext() - - def change_metal(self): - utilities.cls() - utilities.header(["Assume Metal GPU Always in iMac"]) - logging.info( - """This is for iMacs that have upgraded Metal GPUs, otherwise -Patcher assumes based on stock configuration (ie. iMac10,x-12,x) - -Valid Options: - -1. None (stock GPU) -2. Nvidia Kepler -3. AMD Polaris -4. AMD Legacy GCN -Q. Return to previous menu - -Note: Patcher will detect whether hardware has been upgraded regardless, this -option is for those patching on a different machine or OCLP cannot detect. - """ - ) - change_menu = input("Set GPU Patch type(ie. 1): ") - if change_menu == "1": - self.constants.metal_build = False - self.constants.imac_vendor = "None" - self.constants.imac_model = "" - elif change_menu == "2": - self.constants.metal_build = True - self.constants.imac_vendor = "Nvidia" - self.constants.imac_model = "Kepler" - elif change_menu == "3": - self.constants.metal_build = True - self.constants.imac_vendor = "AMD" - self.constants.imac_model = "Polaris" - elif change_menu == "4": - self.constants.metal_build = True - self.constants.imac_vendor = "AMD" - self.constants.imac_model = "Legacy GCN" - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.change_metal() - - def change_serial(self): - utilities.cls() - utilities.header(["Set SMBIOS Spoof Level"]) - logging.info( - """This section is for setting how OpenCore generates the SMBIOS -Recommended for advanced users who want control how serials are handled - -Valid options: - -0. None:\tUse stock SMBIOS (VMM Masking) -1. Minimal:\tUse original serials and minimally update SMBIOS -2. Moderate:\tReplace entire SMBIOS but keep original serials -3. Advanced:\tReplace entire SMBIOS and generate new serials -Q. Return to previous menu - - """ - ) - change_menu = input("Set SMBIOS Spoof Level(ie. 1): ") - if change_menu == "0": - self.constants.serial_settings = "None" - elif change_menu == "1": - self.constants.serial_settings = "Minimal" - elif change_menu == "2": - self.constants.serial_settings = "Moderate" - elif change_menu == "3": - self.constants.serial_settings = "Advanced" - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.change_serial() - - def change_showpicker(self): - utilities.cls() - utilities.header(["Set OpenCore Picker mode"]) - logging.info( - """By default, OpenCore will show its boot picker each time on boot up, -however this can be disabled by default and be shown on command by repeatedly -pressing the "Esc" key - """ - ) - change_menu = input("Show OpenCore Picker by default(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.showpicker = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.showpicker = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.change_showpicker() - - def change_vault(self): - utilities.cls() - utilities.header(["Set OpenCore Vaulting"]) - logging.info( - """By default, this patcher will sign all your files and ensure none of the -contents can be tampered with. However for more advanced users, you may -want to be able to freely edit the config.plist and files. - -Note: For security reasons, OpenShell will be disabled when Vault is set. - - """ - ) - change_menu = input("Enable Vault(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.vault = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.vault = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.change_vault() - - def change_sip(self): - utilities.cls() - utilities.header(["Set System Integrity protection"]) - logging.info( - f"""SIP is used to ensure proper security measures are set, -however to patch the root volume this must be lowered partially. -Only disable is absolutely necessary. SIP value = 0x803 - -Valid options: - -1. Enable SIP -2. Lower SIP partially (allow root volume patching) -3. Set Custom SIP value {self.constants.custom_sip_value} -Q. Return to previous menu - - """ - ) - change_menu = input("Set SIP: ") - if change_menu == "1": - self.constants.sip_status = True - elif change_menu == "2": - self.constants.sip_status = False - elif change_menu == "3": - self.set_custom_sip_value() - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.change_sip() - - def change_sbm(self): - utilities.cls() - utilities.header(["Set SecureBootModel"]) - logging.info( - """SecureBootModel is used to ensure best firmware security, -however to patch the root volume this must be disabled. -Only recommended to enable for users with T2 SMBIOS spoofs. - -Valid options: - -1. Enable SecureBootModel -2. Disable SecureBootModel -Q. Return to previous menu - - """ - ) - change_menu = input("Set SecureBootModel: ") - if change_menu == "1": - self.constants.secure_status = True - elif change_menu == "2": - self.constants.secure_status = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.change_sbm() - - - def bootstrap_setting(self): - utilities.cls() - utilities.header(["Set Bootstrap method"]) - logging.info( - """Sets OpenCore's bootstrap method, currently the patcher supports the -following options. - -Valid options: - -1. System/Library/CoreServices/boot.efi (default) -2. EFI/BOOT/BOOTx64.efi -Q. Return to previous menu - -Note: S*/L*/C*/boot.efi method is only installed to the EFI partition only -and not to macOS itself. - -Recommended to set to BOOTx64.efi in situations where your Mac cannot -see the EFI Boot entry in the boot picker. - - """ - ) - change_menu = input("Set Bootstrap method: ") - if change_menu == "1": - self.constants.boot_efi = False - elif change_menu == "2": - self.constants.boot_efi = True - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.bootstrap_setting() - - def drm_setting(self): - utilities.cls() - utilities.header(["Set DRM preferences"]) - logging.info( - """Sets OpenCore's DRM preferences for iMac13,x and iMac14,x. -In Big Sur, some DRM based content may be broken by -default in AppleTV, Photobooth, etc. - -To resolve, you can opt to disable Intel QuickSync support in -favor of Nvidia's Software rendering. This can aid in DRM however -greatly hampers Video rendering performance in Final Cut Pro and -other programs relying on such features. - -Recommend only disabling if absolutely required. - """ - ) - change_menu = input("Enable Nvidia's Software DRM rendering(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.drm_support = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.drm_support = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.drm_setting() - - def allow_native_models(self): - utilities.cls() - utilities.header(["Allow OpenCore on native Models"]) - logging.info( - """Allows natively supported Macs to use OpenCore. Recommended -for users with 3rd Party NVMe SSDs to achieve improved overall -power usage. - - """ - ) - change_menu = input("Allow OpenCore on all Models(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.allow_oc_everywhere = True - self.constants.serial_settings = "None" - elif change_menu in {"n", "N", "no", "No"}: - self.constants.allow_oc_everywhere = False - self.constants.serial_settings = "Minimal" - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.allow_native_models() - - def custom_cpu(self): - utilities.cls() - utilities.header(["Set custom CPU Model Name"]) - logging.info( - """Change reported CPU Model name in About This Mac -Custom names will report as follows: - -1: Original Name: 2.5 Ghz Dual-Core Intel Core i5 -2. CPU name: Intel(R) Core(TM) i5-3210M CPU @ 2.50Ghz -3. Custom Name: 2.5Ghz Cotton Candy (example) -Q. Return to previous menu - """ - ) - if self.constants.custom_cpu_model_value == "": - if self.constants.custom_cpu_model == 0: - logging.info("Currently using original name") - else: - logging.info("Currently using CPU name") - else: - logging.info(f"Custom CPU name currently: {self.constants.custom_cpu_model_value}") - change_menu = input("Set custom CPU Name(1,2,3): ") - if change_menu == "1": - self.constants.custom_cpu_model = 2 - self.constants.custom_cpu_model_value = "" - elif change_menu == "2": - self.constants.custom_cpu_model = 0 - self.constants.custom_cpu_model_value = "" - elif change_menu == "3": - self.constants.custom_cpu_model = 1 - self.constants.custom_cpu_model_value = input("Enter new CPU Name: ") - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.custom_cpu() - - def disable_cpufriend(self): - utilities.cls() - utilities.header(["Disable CPU Friend?"]) - logging.info( - """Only recommended for advanced users -Disabling CPUFriend forces macOS into using a different -Mac's power profile for CPUs and GPUs, which can harm the -hardware - """ - ) - change_menu = input("Disable CPU Friend?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.disallow_cpufriend = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.disallow_cpufriend = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.disable_cpufriend() - - def set_smbios(self): - utilities.cls() - utilities.header(["Set SMBIOS Spoof Model"]) - logging.info( - """Change model OpenCore spoofs Mac too - -Valid options: -1. Default set by OpenCore (Default) -2. User Override -3. Disable all spoofing (unsupported configuration) -Q. Return to previous menu - """ - ) - - change_menu = input("Set SMBIOS Spoof Model: ") - if change_menu == "1": - logging.info("Setting SMBIOS spoof to default mode") - self.constants.override_smbios = "Default" - elif change_menu == "2": - custom_smbios = input("Set new SMBIOS mode: ") - try: - if smbios_data.smbios_dictionary[custom_smbios]["Board ID"] != None: - self.constants.override_smbios = custom_smbios - else: - logging.info("Non-Intel SMBIOS, reverting to Default setting") - self.constants.override_smbios = "Default" - except KeyError: - logging.info("Unsupported SMBIOS, reverting to Default setting") - self.constants.override_smbios = "Default" - elif change_menu == "3": - logging.info("Disabling SMBIOS spoof") - self.constants.override_smbios = self.model - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.set_smbios() - - def allow_firewire(self): - utilities.cls() - utilities.header(["Allow FireWire Boot Support"]) - logging.info( - """ -In macOS Catalina and newer, Apple restricted -usage of FireWire devices to boot macOS for -security concerns relating to DMA access. - -If you are comfortable lowering the security, -you can re-enable FireWire support for Catalina -and newer. - -Note: MacBook5,x-7,1 don't support FireWire boot - """ - ) - - change_menu = input("Enable FireWire Boot support?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.firewire_boot = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.firewire_boot = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.allow_firewire() - - def allow_nvme(self): - utilities.cls() - utilities.header(["Allow NVMe UEFI Support"]) - logging.info( - """ -For machines not natively supporting NVMe, -this option allows you to see and boot NVMe -drive in OpenCore's picker - -Not required if your machine natively supports NVMe - -Note: You must have OpenCore on a bootable volume -first, ie. USB or SATA drive. Once loaded, -OpenCore will enable NVMe support in it's picker - """ - ) - - change_menu = input("Enable NVMe Boot support?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.nvme_boot = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.nvme_boot = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.allow_nvme() - - def allow_nvme_pwr_mgmt(self): - utilities.cls() - utilities.header(["Allow NVMe Power Management Adjustments"]) - logging.info( - """ -For machines with upgraded NVMe drives, this -option allows for better power management support -within macOS. - -Note 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. - """ - ) - - change_menu = input("Enable NVMe Power Management?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.allow_nvme_fixing = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.allow_nvme_fixing = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.allow_nvme() - - def allow_xhci(self): - utilities.cls() - utilities.header(["Allow NVMe UEFI Support"]) - logging.info( - """ -For machines not natively supporting XHCI/USB 3.o, -this option allows you to see and boot XHCI -drive in OpenCore's picker - -Not required if your machine natively supports USB 3.0 - -Note: You must have OpenCore on a bootable volume -first, ie. USB 2.0 or SATA drive. Once loaded, -OpenCore will enable XHCI support in it's picker - """ - ) - - change_menu = input("Enable XHCI Boot support?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.xhci_boot = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.xhci_boot = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.allow_xhci() - - def allow_wowl(self): - utilities.cls() - utilities.header(["Allow Wake on WLAN"]) - logging.info( - """ -Due to an unfortunate bug in macOS Big Sur+, Wake on WLAN is -disabled by default for BCM943224, BCM94331 and BCM94360/2 chipsets. - -This is due to Wake on WLAN creating network instability and in other cases -halving network speeds. This issue is not replicable across machines however -be prepared if enabling. - """ - ) - - change_menu = input("Allow Wake on WLAN?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.enable_wake_on_wlan = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.enable_wake_on_wlan = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.allow_wowl() - - - def disable_tb(self): - utilities.cls() - utilities.header(["Disable Thunderbolt on 2013-14 MacBook Pros"]) - logging.info( - """ -Some 2013-14 MacBook Pro's have issues with the built-in thunderbolt, -resulting in kernel panics and random shutdowns. - -To alleviate, you can disable the thunderbolt controller on MacBookPro11,x -machines with this option. - -Note: This option only works on MacBookPro11,x, file an issue if you know of -other devices that benefit from this fix. - """ - ) - - change_menu = input("Disable Thunderbolt?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.disable_tb = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.disable_tb = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.disable_tb() - - def terascale_2_accel(self): - utilities.cls() - utilities.header(["Set TeraScale 2 Acceleration"]) - logging.info( - """ -By default this patcher will install TeraScale 2 acceleration, however -for some laptops this may be undesired due to how degraded their dGPU -is. - -Disabling TeraScale 2 acceleration will instead install basic framebuffer -support allowing for basic brightness control and let the HD3000 iGPU -handle acceleration tasks. - """ - ) - - change_menu = input("Allow TeraScale 2 Acceleration?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - global_settings.global_settings().write_property("MacBookPro_TeraScale_2_Accel", True) - self.constants.allow_ts2_accel = True - elif change_menu in {"n", "N", "no", "No"}: - global_settings.global_settings().write_property("MacBookPro_TeraScale_2_Accel", False) - self.constants.allow_ts2_accel = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.terascale_2_accel() - - def dump_hardware(self): - utilities.cls() - utilities.header(["Dumping detected hardware"]) - logging.info("") - logging.info(self.constants.computer) - input("\nPress [ENTER] to exit: ") - - def applealc_support(self): - utilities.cls() - utilities.header(["Set AppleALC usage"]) - logging.info( - """ -By default this patcher will install audio patches in-memory via -AppleALC. However for systems that cannot achieve boot screen support, -this option will allow you to install the legacy AppleHDA patch via -root patching. - -If AppleALC is detected, the Patcher will not install AppleHDA. - """ - ) - - change_menu = input("Set AppleALC usage?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.set_alc_usage = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.set_alc_usage = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.applealc_support() - - def dGPU_switch_support(self): - utilities.cls() - utilities.header(["Set Windows GMUX support"]) - logging.info( - """ -With OCLP, we're able to restore iGPU functionality on iGPU+dGPU -MacBook Pros. However for some this may not be desires, ie. eGPUs -for Windows may prefer to only work with the dGPU and eGPU active. - """ - ) - - change_menu = input("Set Windows GMUX support?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.dGPU_switch = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.dGPU_switch = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.dGPU_switch_support() - - def set_3rd_party_drives(self): - utilities.cls() - utilities.header(["Set enhanced 3rd Party SSD Support"]) - logging.info( - """ -On SATA-based Macs, Apple restricts enhanced OS support to native -drives. Namely hibernation and TRIM. - -This option allows you to disable enhanced support in situations where -TRIM is not ideal. - """ - ) - - change_menu = input("Set enhanced 3rd Party SSD Support?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.allow_3rd_party_drives = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.allow_3rd_party_drives = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.set_3rd_party_drives() - - def set_software_demux(self): - utilities.cls() - utilities.header(["Set Software Demux"]) - logging.info( - """ -For MacBookPro8,2/3 users, it's very common for the dGPU to fail and -thus require the user to disable them via the 'gpu-power-prefs' -nvram argument. - -However this solution still allows the dGPU to pull power (6-7w). Enabling -this option will simulate a demuxed enviroment allowing the dGPU to pull nearly -no power and have the iGPU handle all tasks including brightness control. - -Note: this option requires dGPU to be disabled via NVRAM: -https://dortania.github.io/OpenCore-Legacy-Patcher/ACCEL.html#unable-to-switch-gpus-on-2011-15-and-17-macbook-pros - """ - ) - - change_menu = input("Set Software Demux?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.software_demux = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.software_demux = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.set_software_demux() - - def set_battery_throttle(self): - utilities.cls() - utilities.header(["Disable Firmware Throttling"]) - logging.info( - """ -By default on Nehalem and newer Macs, the firmware will throttle if -the battery or Display is either dead or missing. The firmware will set -'BD PROCHOT' to notify the OS the machine needs to run in an extreme -low power mode. - -Enabling this option will patch 'MSR_POWER_CTL' to be unset allowing -proper CPU behaviour as if hardware is present. Note that this can cause -instability in situations where the CPU is being taxed and pulls more -power than the laptop's PSU can supply. - -Note: Only supported on Nehalem and newer Macs (2010+) - """ - ) - - change_menu = input("Disable Firmware Throttling?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.disable_msr_power_ctl = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.disable_msr_power_ctl = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.set_battery_throttle() - - def set_xcpm(self): - utilities.cls() - utilities.header(["Disable XCPM"]) - logging.info( - """ -By default on Ivy Bridge EP and newer Macs, the system will throttle if -the battery or Display is either dead or missing. Apple's XCPM will set -'BD PROCHOT' to avoid damage to the system. - -Enabling this option will disable Apple's XNU CPU Power Management (XCPM) -and fall back onto the older ACPI_SMC_PlatformPlugin.kext. - -Note: Only supported on Ivy Bridge EP and newer Macs (2013+) - """ - ) - - change_menu = input("Disable XCPM?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.disable_xcpm = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.disable_xcpm = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.set_xcpm() - - def set_surplus(self): - utilities.cls() - utilities.header(["Override SurPlus MaxKernel"]) - logging.info( - """ -By default OCLP will only allow SurPlus to be used on Big Sur and Monterey. -This is for safety reasons in the event newer OSes may break compatibility -and result in boot loops. - -Enabling this option will allow SurPlus to have no MaxKernel set, and -therefore allow it to run on anything newer than 11.2.3. However if you -do toggle this setting, ensure you have a known-good OS to return to in -the event there's issues. - """ - ) - - change_menu = input("Force SurPlus on all newer OSes?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.force_surplus = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.force_surplus = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.set_surplus() - - def set_hibernation_workaround(self): - utilities.cls() - utilities.header(["Set Hibernation Workaround"]) - logging.info( - """ -For users with Hibernation issues, you can flip this option to disable certain -OpenCore settings that may affect the stability of Hibernation. Namely -OpenCore's ConnectDrivers option. - -Flipping this setting will disable automatic loading of additional drives in -OpenCore's boot menu other than what was booted. - -Note: This option should only be flipped under the following circumstances: - - You are experiencing wake failures from hibernation - - You are only using 1 disk in your system for booting (ie. no RAID) - - OpenCore is installed on the same disk as the OS - - Your system has an Intel iGPU and Nvidia dGPU - - You have no need to boot external media through OpenCore - """ - ) - - change_menu = input("Disable ConnectDrivers?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.disable_connectdrivers = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.disable_connectdrivers = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.set_hibernation_workaround() - - def set_custom_sip_value(self): - utilities.cls() - utilities.header(["Set Custom SIP Value"]) - logging.info( - """ -By default OCLP will use the SIP value of 0x00 as the enabled and -0x803 for machines that require root patching. For users who wish -to flip additional bits in SIP may use this option. - -To disable SIP outright, set it to 0xFEF - """ - ) - change_menu = input("Set Custom SIP Value (0xFEF): ") - try: - # Verify whether input is a valid hex value - int(change_menu, 16) - # Convert to binary hex - self.constants.custom_sip_value = change_menu - except ValueError: - logging.info("Invalid input, returning to previous menu") - self.set_custom_sip_value() - - def set_fu_settings(self): - utilities.cls() - utilities.header(["Set FeatureUnlock Settings"]) - logging.info( - """ -By default OCLP will add a kext called FeatureUnlock to enable -features locked out from older models. Including: - -- AirPlay to Mac -- SideCar -- Night Shift - -However for systems experiencing memory instability, disabling this -kext can help. - -Supported Options: - -1. Enable FeatureUnlock and all patches -2. Enable FeatureUnlock and disable AirPlay to Mac and SideCar -3. Disable FeatureUnlock - """ - ) - change_menu = input("Set FeatureUnlock (ie. 1): ") - if change_menu == "1": - self.constants.fu_status = True - self.constants.fu_arguments = None - elif change_menu == "2": - self.constants.fu_status = True - self.constants.fu_arguments = " -disable_sidecar_mac" - elif change_menu == "3": - self.constants.fu_status = False - self.constants.fu_arguments = None - else: - logging.info("Invalid input, returning to previous menu") - self.set_fu_settings() - - def set_allow_native_spoofs(self): - utilities.cls() - utilities.header(["Allow Native Spoofs"]) - logging.info( - """ -By default OCLP will not touch the SMBIOS of native models -to ensure a "stock-like" environment. However for systems that -cannot update their firmware, this option will allow OCLP to -spoof the SMBIOS. - -By default VMM is used to spoof the SMBIOS. Minimal and higher are -available however not officially supported. - """ - ) - change_menu = input("Allow Native Spoofs?(y/n/q): ") - if change_menu in {"y", "Y", "yes", "Yes"}: - self.constants.allow_native_spoofs = True - elif change_menu in {"n", "N", "no", "No"}: - self.constants.allow_native_spoofs = False - elif change_menu in {"q", "Q", "Quit", "quit"}: - logging.info("Returning to previous menu") - else: - self.set_allow_native_spoofs() - - def set_nvram_write(self): - utilities.cls() - utilities.header(["Set NVRAM Write"]) - logging.info( - """ -By default, OpenCore will write NVRAM variables to flash. This is -recommended for majority of systems however for extremely degraded -or fragile systems, you may wish to disable this. - -Supported Options: - -1. Enable NVRAM Write -2. Disable NVRAM Write - """ - ) - change_menu = input("Set NVRAM Write (ie. 1): ") - if change_menu == "1": - self.constants.nvram_write = True - elif change_menu == "2": - self.constants.nvram_write = False - else: - logging.info("Invalid input, returning to previous menu") - self.set_nvram_write() - - def set_cc_support(self): - utilities.cls() - utilities.header(["Set Content Caching Support"]) - logging.info( - """ -On systems spoofing via VMM, Content Caching is disabled by -default by Apple. This option allows you to mask VMM from -AssetCache. - """ - ) - change_menu = input("Set Content Caching Support (y/n/q): ") - if change_menu in ["y", "Y", "yes", "Yes"]: - self.constants.set_content_caching = True - elif change_menu in ["n", "N", "no", "No"]: - self.constants.set_content_caching = False - elif change_menu in ["q", "Q", "Quit", "quit"]: - logging.info("Returning to previous menu") - else: - self.set_cc_support() - - def credits(self): - tui_helpers.TUIOnlyLogging.info( - ["Credits"], - "Press [Enter] to go back.\n", - [ - """Many thanks to the following: - - - Acidanthera:\tOpenCore, kexts and other tools - - DhinakG:\t\tWriting and maintaining this patcher - - Khronokernel:\tWriting and maintaining this patcher - - ASentientBot:\tLegacy Acceleration Patches - - Ausdauersportler:\tLinking fixes for SNBGraphicsFB and AMDX3000 - - Syncretic:\t\tAAAMouSSE, telemetrap, and SurPlus - - cdf:\t\tNightShiftEnabler and Innie - - parrotgeek1:\tVMM Patch Set - -Source Code: -https://github.com/dortania/OpenCore-Legacy-Patcher - """ - ], - ).start() - - def change_model(self): - utilities.cls() - utilities.header(["Select Different Model"]) - logging.info( - """ -Tip: Run the following command on the target machine to find the model identifier: - -system_profiler SPHardwareDataType | grep 'Model Identifier' - """ - ) - self.constants.custom_model = input("Please enter the model identifier of the target machine: ").strip() - if self.constants.custom_model not in model_array.SupportedSMBIOS: - logging.info( - f""" -{self.constants.custom_model} is not a valid SMBIOS Identifier for macOS {self.constants.os_support}! -""" - ) - logging.info_models = input(f"Logging.info list of valid options for macOS {self.constants.os_support}? (y/n)") - if logging.info_models.lower() in {"y", "yes"}: - logging.info("\n".join(model_array.SupportedSMBIOS)) - input("\nPress [ENTER] to continue") - else: - defaults.generate_defaults(self.constants.custom_model, False, self.constants) - - def PatchVolume(self): - utilities.cls() - utilities.header(["Patching System Volume"]) - - no_patch = False - no_unpatch = False - if self.constants.detected_os == os_data.os_data.monterey: - logging.info(MenuOptions.monterey) - elif self.constants.detected_os == os_data.os_data.big_sur: - logging.info(MenuOptions.big_sur) - else: - logging.info(MenuOptions.default) - no_patch = True - no_unpatch = True - change_menu = input("Patch System Volume?: ") - if no_patch is not True and change_menu == "1": - sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, None).start_patch() - elif no_unpatch is not True and change_menu == "2": - sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, None).start_unpatch() - else: - logging.info("Returning to main menu") - - def advanced_patcher_settings(self): - response = None - while not (response and response == -1): - title = ["Adjust Advanced Patcher Settings, for developers ONLY"] - menu = tui_helpers.TUIMenu(title, "Please select an option: ", auto_number=True, top_level=True) - options = [ - [f"Set Metal GPU Status:\t\tCurrently {self.constants.imac_vendor}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).change_metal], - [f"Set DRM Preferences:\t\tCurrently {self.constants.drm_support}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).drm_setting], - [f"Set Generic Bootstrap:\t\tCurrently {self.constants.boot_efi}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).bootstrap_setting], - [ - f"Disable CPU Friend:\t\t\tCurrently {self.constants.disallow_cpufriend}", - MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).disable_cpufriend, - ], - ] - - for option in options: - menu.add_menu_option(option[0], function=option[1]) - - response = menu.start() - - def patcher_settings(self): - response = None - while not (response and response == -1): - title = ["Adjust Patcher Settings"] - menu = tui_helpers.TUIMenu(title, "Please select an option: ", auto_number=True, top_level=True) - options = [ - ["Debug Settings", self.patcher_setting_debug], - ["Security Settings", self.patcher_settings_security], - ["SMBIOS Settings", self.patcher_settings_smbios], - ["Boot Volume Settings", self.patcher_settings_boot], - ["Miscellaneous Settings", self.patcher_settings_misc], - ["Dump detected hardware", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).dump_hardware], - [ - f"Allow OpenCore on native Models:\tCurrently {self.constants.allow_oc_everywhere}", - MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).allow_native_models, - ], - ["Advanced Settings, for developers only", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).advanced_patcher_settings], - ] - - for option in options: - menu.add_menu_option(option[0], function=option[1]) - - response = menu.start() - - def patcher_setting_debug(self): - response = None - while not (response and response == -1): - title = ["Adjust Debug Settings"] - menu = tui_helpers.TUIMenu(title, "Please select an option: ", auto_number=True, top_level=True) - options = [ - [f"Enable Verbose Mode:\tCurrently {self.constants.verbose_debug}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).change_verbose], - [f"Enable OpenCore DEBUG:\tCurrently {self.constants.opencore_debug}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).change_oc], - [f"Enable Kext DEBUG:\t\tCurrently {self.constants.kext_debug}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).change_kext], - ] + ( - [[f"Set SurPlus Settings:\tCurrently {self.constants.force_surplus}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).set_surplus]] - if (smbios_data.smbios_dictionary[self.constants.custom_model or self.constants.computer.real_model]["CPU Generation"] <= cpu_data.cpu_data.sandy_bridge) - else [] - ) - - for option in options: - menu.add_menu_option(option[0], function=option[1]) - - response = menu.start() - - def patcher_settings_security(self): - response = None - while not (response and response == -1): - title = ["Adjust Security Settings"] - menu = tui_helpers.TUIMenu(title, "Please select an option: ", auto_number=True, top_level=True) - options = [ - [ - f"Set System Integrity Protection (SIP):\tCurrently {self.constants.custom_sip_value or self.constants.sip_status}", - MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).change_sip, - ], - [ - f"Set Secure Boot Model (SBM):\t\tCurrently {self.constants.secure_status}", - MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).change_sbm, - ], - [f"Set Vault Mode:\t\t\t\tCurrently {self.constants.vault}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).change_vault], - ] - - for option in options: - menu.add_menu_option(option[0], function=option[1]) - - response = menu.start() - - def patcher_settings_smbios(self): - response = None - while not (response and response == -1): - title = ["Adjust SMBIOS Settings"] - menu = tui_helpers.TUIMenu(title, "Please select an option: ", auto_number=True, top_level=True) - options = [ - [f"Set SMBIOS Spoof Level:\tCurrently {self.constants.serial_settings}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).change_serial], - [f"Set SMBIOS Spoof Model:\tCurrently {self.constants.override_smbios}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).set_smbios], - [f"Allow Native Spoofs:\tCurrently {self.constants.allow_native_spoofs}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).set_allow_native_spoofs], - [f"Set Custom name {self.constants.custom_cpu_model_value}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).custom_cpu], - ] - - for option in options: - menu.add_menu_option(option[0], function=option[1]) - - response = menu.start() - - def patcher_settings_boot(self): - response = None - while not (response and response == -1): - title = ["Adjust Bootable Volume Settings"] - menu = tui_helpers.TUIMenu(title, "Please select an option: ", auto_number=True, top_level=True) - options = [ - [f"Set FireWire Boot:\t\tCurrently {self.constants.firewire_boot}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).allow_firewire], - [f"Set XHCI Boot:\t\tCurrently {self.constants.xhci_boot}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).allow_xhci], - [f"Set NVMe Boot:\t\tCurrently {self.constants.nvme_boot}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).allow_nvme], - [f"Set NVMe Power Management:\tCurrently {self.constants.allow_nvme_fixing}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).allow_nvme_pwr_mgmt], - ] - - for option in options: - menu.add_menu_option(option[0], function=option[1]) - - response = menu.start() - - def patcher_settings_misc(self): - response = None - while not (response and response == -1): - title = ["Adjust Miscellaneous Settings"] - menu = tui_helpers.TUIMenu(title, "Please select an option: ", auto_number=True, top_level=True) - options = [ - [f"Set ShowPicker Mode:\tCurrently {self.constants.showpicker}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).change_showpicker], - [f"Set Wake on WLAN:\t\tCurrently {self.constants.enable_wake_on_wlan}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).allow_wowl], - [ - f"Set TeraScale 2 Accel:\tCurrently {self.constants.allow_ts2_accel}", - MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).terascale_2_accel, - ], - [ - f"Disable Thunderbolt:\tCurrently {self.constants.disable_tb}", - MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).disable_tb, - ], - [f"Set AppleALC Usage:\t\tCurrently {self.constants.set_alc_usage}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).applealc_support], - [ - f"Set Windows GMUX support:\tCurrently {self.constants.dGPU_switch}", - MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).dGPU_switch_support, - ], - [f"Set Hibernation Workaround:\tCurrently {self.constants.disable_connectdrivers}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).set_hibernation_workaround], - [f"Disable Battery Throttling:\tCurrently {self.constants.disable_msr_power_ctl}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).set_battery_throttle], - [f"Disable XCPM:\t\tCurrently {self.constants.disable_xcpm}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).set_xcpm], - [f"Set Software Demux:\tCurrently {self.constants.software_demux}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).set_software_demux], - [f"Set 3rd Party SSD Support:\tCurrently {self.constants.allow_3rd_party_drives}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).set_3rd_party_drives], - [f"Set FeatureUnlock: \tCurrently {self.constants.fu_status}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).set_fu_settings], - [f"Set NVRAM Write:\t\tCurrently {self.constants.nvram_write}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).set_nvram_write], - [f"Set Content Caching:\tCurrently {self.constants.set_content_caching}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).set_cc_support], - ] - - for option in options: - menu.add_menu_option(option[0], function=option[1]) - - response = menu.start() - - def advanced_patcher_settings(self): - response = None - while not (response and response == -1): - title = ["Adjust Advanced Patcher Settings, for developers ONLY"] - menu = tui_helpers.TUIMenu(title, "Please select an option: ", auto_number=True, top_level=True) - options = [ - [f"Set Metal GPU Status:\t\tCurrently {self.constants.imac_vendor}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).change_metal], - [f"Set DRM Preferences:\t\tCurrently {self.constants.drm_support}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).drm_setting], - [f"Set Generic Bootstrap:\t\tCurrently {self.constants.boot_efi}", MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).bootstrap_setting], - [ - f"Disable CPU Friend:\t\t\tCurrently {self.constants.disallow_cpufriend}", - MenuOptions(self.constants.custom_model or self.constants.computer.real_model, self.constants).disable_cpufriend, - ], - ] - - for option in options: - menu.add_menu_option(option[0], function=option[1]) - - response = menu.start() - - def download_macOS(self): - utilities.cls() - utilities.header(["Create macOS installer"]) - logging.info( - """ -This option allows you to download and flash a macOS installer -to your USB drive. - -1. Download macOS Installer -2. Use Existing Installer -B. Exit -""" - ) - change_menu = input("Select an option: ") - if change_menu == "1": - self.download_macOS_installer() - elif change_menu == "2": - self.find_local_installer() - elif change_menu in ["B", "b"]: - return - else: - self.download_macOS() - - def download_install_assistant(self, link): - if installer.download_install_assistant(self.constants.payload_path, link): - installer.install_macOS_installer(self.constants.payload_path) - input("Press any key to continue...") - # To avoid selecting the wrong installer by mistake, let user select the correct one - self.find_local_installer() - else: - logging.info("Failed to start download") - input("Press any key to continue...") - - - def download_macOS_installer(self): - response = None - while not (response and response == -1): - options = [] - title = ["Select the macOS Installer you wish to download"] - menu = tui_helpers.TUIMenu(title, "Please select an option: ", auto_number=True, top_level=True) - available_installers = installer.list_downloadable_macOS_installers(self.constants.payload_path, "DeveloperSeed") - if available_installers: - # Add mirror of 11.2.3 for users who want it - options.append([f"macOS {mirror_data.Install_macOS_Big_Sur_11_2_3['Version']} ({mirror_data.Install_macOS_Big_Sur_11_2_3['Build']} - {utilities.human_fmt(mirror_data.Install_macOS_Big_Sur_11_2_3['Size'])} - {mirror_data.Install_macOS_Big_Sur_11_2_3['Source']})", lambda: self.download_install_assistant(mirror_data.Install_macOS_Big_Sur_11_2_3['Link'])]) - for app in available_installers: - if available_installers[app]['Variant'] in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]: - variant = " Beta" - else: - variant = "" - options.append([f"macOS {available_installers[app]['Version']}{variant} ({available_installers[app]['Build']} - {utilities.human_fmt(available_installers[app]['Size'])} - {available_installers[app]['Source']})", lambda x=app: self.download_install_assistant(available_installers[x]['Link'])]) - for option in options: - menu.add_menu_option(option[0], function=option[1]) - response = menu.start() - - def find_local_installer(self): - response = None - while not (response and response == -1): - options = [] - title = ["Select the macOS Installer you wish to use"] - menu = tui_helpers.TUIMenu(title, "Please select an option: ", auto_number=True, top_level=True) - available_installers = installer.list_local_macOS_installers() - if available_installers: - for app in available_installers: - options.append([f"{available_installers[app]['Short Name']}: {available_installers[app]['Version']} ({available_installers[app]['Build']})", lambda x=app: self.list_disks(available_installers[x]['Path'])]) - for option in options: - menu.add_menu_option(option[0], function=option[1]) - response = menu.start() - - def list_disks(self, installer_path): - disk = installer.select_disk_to_format() - if disk != None: - if installer.format_drive(disk) is True: - # Only install if OC is found - # Allows a user to create a macOS Installer without OCLP if desired - if self.constants.opencore_release_folder.exists() and self.constants.walkthrough is True: - # ESP will always be the first partition when formatted by disk utility - install.tui_disk_installation.install_opencore(self, f"disk{disk}", "1") - if installer.create_installer(installer_path, "OCLP-Installer") is True: - utilities.cls() - utilities.header(["Create macOS installer"]) - logging.info("Installer created successfully.") - input("Press enter to exit.") - if self.constants.walkthrough is True: - self.closing_message() - else: - utilities.cls() - utilities.header(["Create macOS installer"]) - logging.info("Installer creation failed.") - input("Press enter to return to the previous.") - return - else: - if self.constants.walkthrough is True: - sys.exit() - - def closing_message(self): - utilities.cls() - utilities.header(["Create macOS installer"]) - logging.info("Thank you for using OpenCore Legacy Patcher!") - logging.info("Reboot your machine and select EFI Boot to load OpenCore") - logging.info("") - logging.info("If you have any issues, remember to check the guide as well as\nour Discord server:") - logging.info("\n\tGuide: https://dortania.github.io/OpenCore-Legacy-Patcher/") - logging.info("\tDiscord: https://discord.gg/rqdPgH8xSN") - input("\nPress enter to exit: ") - sys.exit() - - big_sur = """Patches Root volume to fix misc issues such as: - -- Non-Metal Graphics Acceleration - - Intel: Ironlake - Sandy Bridge - - Nvidia: Tesla - Fermi (8000-500 series) - - AMD: TeraScale 1 and 2 (2000-6000 series) -- Audio support for iMac7,1 and iMac8,1 - -WARNING: Root Volume Patching is still in active development, please -have all important user data backed up. Note when the system volume -is patched, you can no longer have Delta updates. - -Supported Options: - -1. Patch System Volume -2. Unpatch System Volume (Experimental) -B. Exit - """ - monterey = """Patches Root volume to fix misc issues such as: - -- Metal Graphics Acceleration - - Intel: Ivy Bridge (4000 series iGPUs) - - Nvidia: Kepler (600-700) -- Non-Metal Graphics Accelertation - - Intel: Ironlake - Sandy Bridge - - Nvidia: Tesla - Fermi (8000-500 series) - - AMD: TeraScale 1 and 2 (2000-6000 series) -- Audio support for iMac7,1 and iMac8,1 -- Wifi support for BCM94328, BCM94322 and Atheros cards - -WARNING: Root Volume Patching is still in active development, please -have all important user data backed up. Note when the system volume -is patched, you can no longer have Delta updates. - -Supported Options: - -1. Patch System Volume -2. Unpatch System Volume -B. Exit - """ - mojave_catalina = """Patches Root volume to fix misc issues such as: - -- Non-Metal Graphics Acceleration - - Intel: Ironlake - Sandy Bridge - - Nvidia: Tesla - Fermi (8000-500 series) - - AMD: TeraScale 1 and 2 (2000-6000 series) -- Audio support for iMac7,1 and iMac8,1 - -WARNING: Root Volume Patching is still in active development, please -have all important user data backed up. Note when the system volume -is patched, you can no longer have Delta updates. - -Supported Options: - -1. Patch System Volume -B. Exit - """ - - default = """ -This OS has no root patches available to apply, please ensure you're patching a booted -install that requires root patches such as macOS Big Sur or Monterey - -Supported Options: - -B. Exit - """ diff --git a/resources/commit_info.py b/resources/commit_info.py index 6b1034b2c..43272bbd5 100644 --- a/resources/commit_info.py +++ b/resources/commit_info.py @@ -1,29 +1,40 @@ # Parse Commit Info from binary's info.plist -# App Structure: -# OpenCore-Patcher.app: -# Contents: -# MacOS: -# OpenCore-Patcher -# Info.plist from pathlib import Path import plistlib -class commit_info: +class ParseCommitInfo: + + def __init__(self, binary_path: str): + """ + Parameters: + binary_path (str): Path to binary + """ - def __init__(self, binary_path): self.binary_path = str(binary_path) - self.plist_path = self.convert_binary_path_to_plist_path() + self.plist_path = self._convert_binary_path_to_plist_path() - def convert_binary_path_to_plist_path(self): + def _convert_binary_path_to_plist_path(self): + """ + Resolve Info.plist path from binary path + """ + if Path(self.binary_path).exists(): plist_path = self.binary_path.replace("MacOS/OpenCore-Patcher", "Info.plist") if Path(plist_path).exists() and plist_path.endswith(".plist"): return plist_path return None + def generate_commit_info(self): + """ + Generate commit info from Info.plist + + Returns: + tuple: (Branch, Commit Date, Commit URL) + """ + if self.plist_path: plist_info = plistlib.load(Path(self.plist_path).open("rb")) if "Github" in plist_info: diff --git a/resources/defaults.py b/resources/defaults.py index 0f49b7f89..ccae5a293 100644 --- a/resources/defaults.py +++ b/resources/defaults.py @@ -1,36 +1,52 @@ # Generate Default Data -from resources import utilities, device_probe, generate_smbios, global_settings -from data import smbios_data, cpu_data, os_data import subprocess +from resources import ( + utilities, + device_probe, + generate_smbios, + global_settings, + constants +) +from data import ( + smbios_data, + cpu_data, + os_data +) -class generate_defaults: - def __init__(self, model, host_is_target, settings): - self.model = model - self.constants = settings - self.host_is_target = host_is_target +class GenerateDefaults: + + def __init__(self, model: str, host_is_target: bool, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants + + self.model: str = model + self.host_is_target: str = host_is_target # Reset Variables - self.constants.sip_status = True - self.constants.secure_status = False - self.constants.disable_cs_lv = False - self.constants.disable_amfi = False - self.constants.fu_status = True - self.constants.fu_arguments = None + self.constants.sip_status: bool = True + self.constants.secure_status: bool = False + self.constants.disable_cs_lv: bool = False + self.constants.disable_amfi: bool = False + self.constants.fu_status: bool = True - self.constants.custom_serial_number = "" - self.constants.custom_board_serial_number = "" + self.constants.fu_arguments: str = None - self.general_probe() - self.nvram_probe() - self.gpu_probe() - self.networking_probe() - self.misc_hardwares_probe() - self.smbios_probe() + self.constants.custom_serial_number: str = "" + self.constants.custom_board_serial_number: str = "" + + self._general_probe() + self._nvram_probe() + self._gpu_probe() + self._networking_probe() + self._misc_hardwares_probe() + self._smbios_probe() - def general_probe(self): + def _general_probe(self): + """ + General probe for data + """ if "Book" in self.model: self.constants.set_content_caching = False @@ -40,11 +56,11 @@ class generate_defaults: if self.model in ["MacBookPro8,2", "MacBookPro8,3"]: # Users disabling TS2 most likely have a faulty dGPU # users can override this in settings - ts2_status = global_settings.global_settings().read_property("MacBookPro_TeraScale_2_Accel") + ts2_status = global_settings.GlobalEnviromentSettings().read_property("MacBookPro_TeraScale_2_Accel") if ts2_status is True: self.constants.allow_ts2_accel = True else: - global_settings.global_settings().write_property("MacBookPro_TeraScale_2_Accel", False) + global_settings.GlobalEnviromentSettings().write_property("MacBookPro_TeraScale_2_Accel", False) self.constants.allow_ts2_accel = False if self.model in smbios_data.smbios_dictionary: @@ -61,14 +77,19 @@ class generate_defaults: # Check if running in RecoveryOS self.constants.recovery_status = utilities.check_recovery() - if global_settings.global_settings().read_property("Force_Web_Drivers") is True: + if global_settings.GlobalEnviromentSettings().read_property("Force_Web_Drivers") is True: self.constants.force_nv_web = True - result = global_settings.global_settings().read_property("ShouldNukeKDKs") + result = global_settings.GlobalEnviromentSettings().read_property("ShouldNukeKDKs") if result is False: self.constants.should_nuke_kdks = False - def smbios_probe(self): + + def _smbios_probe(self): + """ + SMBIOS specific probe + """ + if not self.host_is_target: if self.model in ["MacPro4,1", "MacPro5,1"]: # Allow H.265 on AMD @@ -99,7 +120,11 @@ class generate_defaults: self.constants.force_vmm = False - def nvram_probe(self): + def _nvram_probe(self): + """ + NVRAM specific probe + """ + if not self.host_is_target: return @@ -120,7 +145,11 @@ class generate_defaults: self.constants.custom_cpu_model_value = custom_cpu_model_value.split("%00")[0] - def networking_probe(self): + def _networking_probe(self): + """ + Networking specific probe + """ + if self.host_is_target: if not ( ( @@ -157,7 +186,11 @@ class generate_defaults: self.constants.fu_status = True self.constants.fu_arguments = " -disable_sidecar_mac" - def misc_hardwares_probe(self): + + def _misc_hardwares_probe(self): + """ + Misc probe + """ if self.host_is_target: if self.constants.computer.usb_controllers: if self.model in smbios_data.smbios_dictionary: @@ -170,7 +203,11 @@ class generate_defaults: break - def gpu_probe(self): + def _gpu_probe(self): + """ + Graphics specific probe + """ + gpu_dict = [] if self.host_is_target: gpu_dict = self.constants.computer.gpus diff --git a/resources/global_settings.py b/resources/global_settings.py index 36d56b5e9..cdbb84ef8 100644 --- a/resources/global_settings.py +++ b/resources/global_settings.py @@ -9,18 +9,23 @@ import logging import os import subprocess -class global_settings: + +class GlobalEnviromentSettings: + """ + Library for querying and writing global enviroment settings + """ def __init__(self): - self.file_name = ".com.dortania.opencore-legacy-patcher.plist" - self.global_settings_folder = "/Users/Shared" - self.global_settings_plist = f"{self.global_settings_folder}/{self.file_name}" + self.file_name: str = ".com.dortania.opencore-legacy-patcher.plist" + self.global_settings_folder: str = "/Users/Shared" + self.global_settings_plist: str = f"{self.global_settings_folder}/{self.file_name}" + self._generate_settings_file() self._convert_defaults_to_global_settings() self._fix_file_permission() - def read_property(self, property_name): + def read_property(self, property_name: str): """ Reads a property from the global settings file """ @@ -32,7 +37,7 @@ class global_settings: return None - def write_property(self, property_name, property_value): + def write_property(self, property_name: str, property_value): """ Writes a property to the global settings file """ diff --git a/resources/gui/gui_main.py b/resources/gui/gui_main.py index 9f2b1051a..a76c03516 100644 --- a/resources/gui/gui_main.py +++ b/resources/gui/gui_main.py @@ -33,7 +33,6 @@ from resources import ( install, installer, utilities, - run, generate_smbios, updates, integrity_verification, @@ -256,7 +255,7 @@ class wx_python_gui: if local_build_date <= installed_build_date: return False - elif updates.check_binary_updates(self.constants).check_if_build_newer(local_version, application_version) is 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 @@ -307,11 +306,11 @@ class wx_python_gui: return did_find_update = False - ignore_updates = global_settings.global_settings().read_property("IgnoreAppUpdates") + 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.check_binary_updates(self.constants).check_binary_updates() + dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates() if dict: for entry in dict: version = dict[entry]["Version"] @@ -331,7 +330,7 @@ class wx_python_gui: elif response == wx.ID_NO: logging.info("- Setting IgnoreAppUpdates to True") self.constants.ignore_updates = True - global_settings.global_settings().write_property("IgnoreAppUpdates", True) + global_settings.GlobalEnviromentSettings().write_property("IgnoreAppUpdates", True) else: self.constants.ignore_updates = True logging.info("- Ignoring App Updates due to defaults") @@ -589,10 +588,10 @@ class wx_python_gui: if self.constants.start_build_install is True: self.build_install_menu() elif "--gui_patch" in sys.argv: - self.patches = sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).detect_patch_set() + 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.detect_root_patch(self.computer.real_model, self.constants).detect_patch_set() + 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 @@ -1079,7 +1078,7 @@ class wx_python_gui: ) self.subheader.Centre(wx.HORIZONTAL) - patches = sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).detect_patch_set() + 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): @@ -2310,8 +2309,11 @@ class wx_python_gui: def start_script(self): utilities.disable_sleep_while_running() - args = [self.constants.oclp_helper_path, "/bin/sh", self.constants.installer_sh_path] - output, error, returncode = run.Run()._stream_output(comm=args) + 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(): @@ -2645,11 +2647,11 @@ class wx_python_gui: if user_choice == self.computer.real_model: logging.info(f"Using Real Model: {user_choice}") self.constants.custom_model = None - defaults.generate_defaults(self.computer.real_model, True, self.constants) + 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.generate_defaults(self.constants.custom_model, False, self.constants) + defaults.GenerateDefaults(self.constants.custom_model, False, self.constants) # Reload Settings self.settings_menu(None) @@ -2986,7 +2988,7 @@ class wx_python_gui: else: logging.info("Nuke KDKs disabled") self.constants.should_nuke_kdks = False - global_settings.global_settings().write_property("ShouldNukeKDKs", self.constants.should_nuke_kdks) + 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(): @@ -3009,9 +3011,9 @@ class wx_python_gui: 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.global_settings().write_property("IgnoreAppUpdates", True) + global_settings.GlobalEnviromentSettings().write_property("IgnoreAppUpdates", True) else: - global_settings.global_settings().write_property("IgnoreAppUpdates", False) + global_settings.GlobalEnviromentSettings().write_property("IgnoreAppUpdates", False) def firewire_click(self, event=None): if self.firewire_boot_checkbox.GetValue(): @@ -3096,21 +3098,21 @@ class wx_python_gui: def ts2_accel_click(self, event=None): if self.set_terascale_accel_checkbox.GetValue(): logging.info("TS2 Acceleration Enabled") - global_settings.global_settings().write_property("MacBookPro_TeraScale_2_Accel", True) + global_settings.GlobalEnviromentSettings().write_property("MacBookPro_TeraScale_2_Accel", True) self.constants.allow_ts2_accel = True else: logging.info("TS2 Acceleration Disabled") - global_settings.global_settings().write_property("MacBookPro_TeraScale_2_Accel", False) + global_settings.GlobalEnviromentSettings().write_property("MacBookPro_TeraScale_2_Accel", False) self.constants.allow_ts2_accel = False def force_web_drivers_click(self, event=None): if self.force_web_drivers_checkbox.GetValue(): logging.info("Force Web Drivers Enabled") - global_settings.global_settings().write_property("Force_Web_Drivers", True) + global_settings.GlobalEnviromentSettings().write_property("Force_Web_Drivers", True) self.constants.force_nv_web = True else: logging.info("Force Web Drivers Disabled") - global_settings.global_settings().write_property("Force_Web_Drivers", False) + global_settings.GlobalEnviromentSettings().write_property("Force_Web_Drivers", False) self.constants.force_nv_web = False def windows_gmux_click(self, event=None): @@ -3816,19 +3818,19 @@ OpenCore Legacy Patcher by default knows the most ideal 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).stdout.decode("utf-8").strip() + 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).stdout.decode("utf-8").strip() + 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).stdout.decode("utf-8").strip() + 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: diff --git a/resources/install.py b/resources/install.py index 45a2b3cfb..f5906ab17 100644 --- a/resources/install.py +++ b/resources/install.py @@ -8,7 +8,7 @@ import shutil import os import logging from pathlib import Path -from resources import utilities, constants, tui_helpers +from resources import utilities, constants from data import os_data class tui_disk_installation: @@ -75,65 +75,6 @@ class tui_disk_installation: return supported_partitions - def copy_efi(self): - utilities.cls() - utilities.header(["Installing OpenCore to Drive"]) - - if not self.constants.opencore_release_folder.exists(): - tui_helpers.TUIOnlyLogging.info( - ["Installing OpenCore to Drive"], - "Press [Enter] to go back.\n", - [ - """OpenCore folder missing! -Please build OpenCore first!""" - ], - ).start() - return - - logging.info("\nDisk picker is loading...") - - all_disks = self.list_disks() - menu = tui_helpers.TUIMenu( - ["Select Disk"], - "Please select the disk you would like to install OpenCore to: ", - in_between=["Missing disks? Ensure they have an EFI or FAT32 partition."], - return_number_instead_of_direct_call=True, - loop=True, - ) - for disk in all_disks: - menu.add_menu_option(f"{disk}: {all_disks[disk]['name']} ({all_disks[disk]['size']})", key=disk[4:]) - - response = menu.start() - - if response == -1: - return - - disk_identifier = "disk" + response - selected_disk = all_disks[disk_identifier] - - menu = tui_helpers.TUIMenu( - ["Select Partition"], - "Please select the partition you would like to install OpenCore to: ", - return_number_instead_of_direct_call=True, - loop=True, - in_between=["Missing partitions? Ensure they are formatted as an EFI or FAT32.", "", "* denotes likely candidate."], - ) - for partition in selected_disk["partitions"]: - if selected_disk["partitions"][partition]["fs"] not in ("msdos", "EFI"): - continue - text = f"{partition}: {selected_disk['partitions'][partition]['name']} ({utilities.human_fmt(selected_disk['partitions'][partition]['size'])})" - if selected_disk["partitions"][partition]["type"] == "EFI" or ( - selected_disk["partitions"][partition]["type"] == "Microsoft Basic Data" and selected_disk["partitions"][partition]["size"] < 1024 * 1024 * 512 - ): # 512 megabytes: - text += " *" - menu.add_menu_option(text, key=partition[len(disk_identifier) + 1 :]) - - response = menu.start() - - if response == -1: - return - self.install_opencore(f"{disk_identifier}s{response}") - def install_opencore(self, full_disk_identifier): def determine_sd_card(media_name): # Array filled with common SD Card names @@ -169,18 +110,13 @@ Please build OpenCore first!""" # cancelled prompt return else: - if self.constants.gui_mode is False: - tui_helpers.TUIOnlyLogging.info( - ["Copying OpenCore"], "Press [Enter] to go back.\n", ["An error occurred!"] + result.stderr.decode().split("\n") + [""] - ).start() - else: - logging.info("An error occurred!") - logging.info(result.stderr.decode()) + 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 - 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.") + # Check if we're in Safe Mode, and if so, tell user FAT32 is unsupported + 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 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"] @@ -252,10 +188,7 @@ Please build OpenCore first!""" logging.info("\nPress [Enter] to continue.\n") input() else: - if self.constants.gui_mode is False: - tui_helpers.TUIOnlyLogging.info(["Copying OpenCore"], "Press [Enter] to go back.\n", ["EFI failed to mount!"]).start() - else: - logging.info("EFI failed to mount!") + logging.info("EFI failed to mount!") return False return True diff --git a/resources/installer.py b/resources/installer.py index 504918b24..aa5986bed 100644 --- a/resources/installer.py +++ b/resources/installer.py @@ -4,7 +4,7 @@ import plistlib import subprocess import tempfile import logging -from resources import utilities, tui_helpers, network_handler +from resources import utilities, network_handler def list_local_macOS_installers(): # Finds all applicable macOS installers @@ -325,52 +325,6 @@ def format_drive(disk_id): input("\nPress Enter to exit") return False -def select_disk_to_format(): - utilities.cls() - utilities.header(["Installing OpenCore to Drive"]) - - logging.info("\nDisk picker is loading...") - - all_disks = {} - # TODO: AllDisksAndPartitions is not supported in Snow Leopard and older - try: - # High Sierra and newer - disks = plistlib.loads(subprocess.run("diskutil list -plist physical".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) - except ValueError: - # Sierra and older - disks = plistlib.loads(subprocess.run("diskutil list -plist".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) - for disk in disks["AllDisksAndPartitions"]: - disk_info = plistlib.loads(subprocess.run(f"diskutil info -plist {disk['DeviceIdentifier']}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) - try: - all_disks[disk["DeviceIdentifier"]] = {"identifier": disk_info["DeviceNode"], "name": disk_info["MediaName"], "size": disk_info["TotalSize"], "removable": disk_info["Internal"], "partitions": {}} - except KeyError: - # Avoid crashing with CDs installed - continue - menu = tui_helpers.TUIMenu( - ["Select Disk to write the macOS Installer onto"], - "Please select the disk you would like to install OpenCore to: ", - in_between=["Missing drives? Verify they are 14GB+ and external (ie. USB)", "", "Ensure all data is backed up on selected drive, entire drive will be erased!"], - return_number_instead_of_direct_call=True, - loop=True, - ) - for disk in all_disks: - # Strip disks that are under 14GB (15,032,385,536 bytes) - # createinstallmedia isn't great at detecting if a disk has enough space - if not any(all_disks[disk]['size'] > 15032385536 for partition in all_disks[disk]): - continue - # Strip internal disks as well (avoid user formatting their SSD/HDD) - # Ensure user doesn't format their boot drive - if not any(all_disks[disk]['removable'] is False for partition in all_disks[disk]): - continue - menu.add_menu_option(f"{disk}: {all_disks[disk]['name']} ({utilities.human_fmt(all_disks[disk]['size'])})", key=disk[4:]) - - response = menu.start() - - if response == -1: - return None - - return response - def list_disk_to_format(): diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index bfd1be59c..d13985f30 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -15,8 +15,7 @@ import os import logging -from resources import utilities, network_handler -from resources.constants import Constants +from resources import utilities, network_handler, constants from data import os_data KDK_INSTALL_PATH: str = "/Library/Developer/KDKs" @@ -50,8 +49,8 @@ class KernelDebugKitObject: """ - def __init__(self, constants: Constants, host_build: str, host_version: str, ignore_installed: bool = False, passive: bool = False): - self.constants: Constants = constants + def __init__(self, global_constants: constants.Constants, host_build: str, host_version: str, ignore_installed: bool = False, passive: bool = False): + self.constants: constants.Constants = global_constants self.host_build: str = host_build # ex. 20A5384c self.host_version: str = host_version # ex. 11.0.1 @@ -565,6 +564,8 @@ class KernelDebugKitUtilities: logging.info(f"- Installing KDK package: {kdk_path.name}") logging.info(f" - This may take a while...") + # TODO: Check whether enough disk space is available + result = utilities.elevated(["installer", "-pkg", kdk_path, "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.info("- Failed to install KDK:") diff --git a/resources/main.py b/resources/main.py index 4dd962c82..f52dada98 100644 --- a/resources/main.py +++ b/resources/main.py @@ -4,21 +4,16 @@ import sys import time import logging import threading -import subprocess from pathlib import Path -from data import model_array -from resources.build import build +from resources.gui import gui_main from resources import ( - cli_menu, constants, utilities, device_probe, os_probe, defaults, arguments, - install, - tui_helpers, reroute_payloads, commit_info, logging_handler @@ -26,36 +21,50 @@ from resources import ( class OpenCoreLegacyPatcher: - def __init__(self, launch_gui=False): - self.constants = constants.Constants() - self.constants.wxpython_variant = launch_gui + """ + Initial entry point for starting OpenCore Legacy Patcher + """ + + def __init__(self): logging_handler.InitializeLoggingSupport() + self.constants: constants.Constants = constants.Constants() + + self.constants.wxpython_variant: bool = True + logging.info(f"- Loading OpenCore Legacy Patcher v{self.constants.patcher_version}...") - self.generate_base_data() + self._generate_base_data() + if utilities.check_cli_args() is None: - if launch_gui is True: - utilities.disable_cls() - from resources.gui import gui_main - gui_main.wx_python_gui(self.constants).main_menu(None) - else: - self.main_menu() + gui_main.wx_python_gui(self.constants).main_menu(None) - def generate_base_data(self): + def _generate_base_data(self): + """ + Generate base data required for the patcher to run + """ + + # Generate OS data os_data = os_probe.OSProbe() self.constants.detected_os = os_data.detect_kernel_major() self.constants.detected_os_minor = os_data.detect_kernel_minor() self.constants.detected_os_build = os_data.detect_os_build() self.constants.detected_os_version = os_data.detect_os_version() + + # Generate computer data self.constants.computer = device_probe.Computer.probe() - self.constants.recovery_status = utilities.check_recovery() self.computer = self.constants.computer self.constants.booted_oc_disk = utilities.find_disk_off_uuid(utilities.clean_device_path(self.computer.opencore_path)) if self.constants.computer.firmware_vendor: if self.constants.computer.firmware_vendor != "Apple": self.constants.host_is_hackintosh = True + + # Generate environment data + self.constants.recovery_status = utilities.check_recovery() + utilities.disable_cls() + + # Generate binary data launcher_script = None launcher_binary = sys.executable if "python" in launcher_binary: @@ -65,83 +74,40 @@ class OpenCoreLegacyPatcher: launcher_script = launcher_script.replace("/resources/main.py", "/OpenCore-Patcher-GUI.command") self.constants.launcher_binary = launcher_binary self.constants.launcher_script = launcher_script - self.constants.unpack_thread = threading.Thread(target=reroute_payloads.reroute_payloads(self.constants).setup_tmp_disk_image) - self.constants.unpack_thread.start() - self.constants.commit_info = commit_info.commit_info(self.constants.launcher_binary).generate_commit_info() - # Now that we have commit info, update nightly link + # Initialize working directory + self.constants.unpack_thread = threading.Thread(target=reroute_payloads.RoutePayloadDiskImage, args=(self.constants,)) + self.constants.unpack_thread.start() + + # Generate commit info + self.constants.commit_info = commit_info.ParseCommitInfo(self.constants.launcher_binary).generate_commit_info() if self.constants.commit_info[0] not in ["Running from source", "Built from source"]: + # Now that we have commit info, update nightly link branch = self.constants.commit_info[0] branch = branch.replace("refs/heads/", "") self.constants.installer_pkg_url_nightly = self.constants.installer_pkg_url_nightly.replace("main", branch) - defaults.generate_defaults(self.computer.real_model, True, self.constants) + # Generate defaults + defaults.GenerateDefaults(self.computer.real_model, True, self.constants) - if utilities.check_cli_args() is not None: - 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"] - if not any(x in sys.argv for x in ignore_args): - self.constants.current_path = Path.cwd() - self.constants.cli_mode = True - if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): - logging.info("- Rerouting payloads location") - self.constants.payload_path = sys._MEIPASS / Path("payloads") - ignore_args = ignore_args.pop(0) - if not any(x in sys.argv for x in ignore_args): - while self.constants.unpack_thread.is_alive(): - time.sleep(0.1) - arguments.arguments().parse_arguments(self.constants) - else: + if utilities.check_cli_args() is None: logging.info(f"- No arguments present, loading {'GUI' if self.constants.wxpython_variant is True else 'TUI'} mode") + return + logging.info("- Detected arguments, switching to CLI mode") + self.constants.gui_mode = True # Assumes no user interaction is required - def main_menu(self): - response = None - while not (response and response == -1): - title = [ - f"OpenCore Legacy Patcher v{self.constants.patcher_version}", - f"Selected Model: {self.constants.custom_model or self.computer.real_model}", - ] + ignore_args = ["--auto_patch", "--gui_patch", "--gui_unpatch"] + if not any(x in sys.argv for x in ignore_args): + self.constants.current_path = Path.cwd() + self.constants.cli_mode = True + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + logging.info("- Rerouting payloads location") + self.constants.payload_path = sys._MEIPASS / Path("payloads") + ignore_args = ignore_args.pop(0) - if (self.constants.custom_model or self.computer.real_model) not in model_array.SupportedSMBIOS and self.constants.allow_oc_everywhere is False: - in_between = [ - "Your model is not supported by this patcher for running unsupported OSes!", - "", - 'If you plan to create the USB for another machine, please select the \n"Change Model" option in the menu.', - "", - 'If you want to run OCLP on a native Mac, please toggle \n"Allow OpenCore on native Models" in settings', - ] - elif not self.constants.custom_model and self.computer.real_model == "iMac7,1" and "SSE4.1" not in self.computer.cpu.flags: - in_between = [ - "Your model requires a CPU upgrade to a CPU supporting SSE4.1+ to be supported by this patcher!", - "", - f'If you plan to create the USB for another {self.computer.real_model} with SSE4.1+, please select the "Change Model" option in the menu.', - ] - elif self.constants.custom_model == "iMac7,1": - in_between = ["This model is supported", "However please ensure the CPU has been upgraded to support SSE4.1+"] - else: - in_between = ["This model is supported"] + if not any(x in sys.argv for x in ignore_args): + while self.constants.unpack_thread.is_alive(): + time.sleep(0.1) - menu = tui_helpers.TUIMenu(title, "Please select an option: ", in_between=in_between, auto_number=True, top_level=True) - - options = ( - [["Build OpenCore", build.build_opencore(self.constants.custom_model or self.constants.computer.real_model, self.constants).build_opencore]] - if ((self.constants.custom_model or self.computer.real_model) in model_array.SupportedSMBIOS) or self.constants.allow_oc_everywhere is True - else [] - ) + [ - ["Install OpenCore to USB/internal drive", install.tui_disk_installation(self.constants).copy_efi], - ["Post-Install Volume Patch", cli_menu.MenuOptions(self.constants.custom_model or self.computer.real_model, self.constants).PatchVolume], - ["Change Model", cli_menu.MenuOptions(self.constants.custom_model or self.computer.real_model, self.constants).change_model], - ["Patcher Settings", cli_menu.MenuOptions(self.constants.custom_model or self.computer.real_model, self.constants).patcher_settings], - ["Installer Creation", cli_menu.MenuOptions(self.constants.custom_model or self.computer.real_model, self.constants).download_macOS], - ["Credits", cli_menu.MenuOptions(self.constants.custom_model or self.computer.real_model, self.constants).credits], - ] - - for option in options: - menu.add_menu_option(option[0], function=option[1]) - - response = menu.start() - - if getattr(sys, "frozen", False) and self.constants.recovery_status is False: - subprocess.run("""osascript -e 'tell application "Terminal" to close first window' & exit""", shell=True) \ No newline at end of file + arguments.arguments(self.constants) diff --git a/resources/reroute_payloads.py b/resources/reroute_payloads.py index 674914c41..6f3624ec5 100644 --- a/resources/reroute_payloads.py +++ b/resources/reroute_payloads.py @@ -9,21 +9,31 @@ import tempfile import atexit import logging -class reroute_payloads: - def __init__(self, constants): - self.constants = constants +from resources import constants + +class RoutePayloadDiskImage: + + def __init__(self, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants + + self._setup_tmp_disk_image() + + + def _setup_tmp_disk_image(self): + """ + Initialize temp directory and mount payloads.dmg + Create overlay for patcher to write to + + Currently only applicable for GUI variant and not running from source + """ - def setup_tmp_disk_image(self): - # Create a temp directory to mount the payloads.dmg - # Then reroute r/w to this new temp directory - # Currently only applicable for GUI variant if self.constants.wxpython_variant is True and not self.constants.launcher_script: logging.info("- Running in Binary GUI mode, switching to tmp directory") self.temp_dir = tempfile.TemporaryDirectory() logging.info(f"- New payloads location: {self.temp_dir.name}") logging.info("- Creating payloads directory") Path(self.temp_dir.name / Path("payloads")).mkdir(parents=True, exist_ok=True) - self.unmount_active_dmgs(unmount_all_active=False) + self._unmount_active_dmgs(unmount_all_active=False) output = subprocess.run( [ "hdiutil", "attach", "-noverify", f"{self.constants.payload_path}.dmg", @@ -38,16 +48,25 @@ class reroute_payloads: logging.info("- Mounted payloads.dmg") self.constants.current_path = Path(self.temp_dir.name) self.constants.payload_path = Path(self.temp_dir.name) / Path("payloads") - atexit.register(self.unmount_active_dmgs, unmount_all_active=False) + atexit.register(self._unmount_active_dmgs, unmount_all_active=False) else: logging.info("- Failed to mount payloads.dmg") logging.info(f"Output: {output.stdout.decode()}") logging.info(f"Return Code: {output.returncode}") - def unmount_active_dmgs(self, unmount_all_active=True): - # Find all DMGs that are mounted, and forcefully unmount them - # If our disk image was previously mounted, we need to unmount it to use again - # This can happen if we crash during a previous secession, however 'atexit' class should hopefully avoid this + + def _unmount_active_dmgs(self, unmount_all_active=True): + """ + Unmounts disk images associated with OCLP + + Finds all DMGs that are mounted, and forcefully unmount them + If our disk image was previously mounted, we need to unmount it to use again + This can happen if we crash during a previous secession, however 'atexit' class should hopefully avoid this + + Parameters: + unmount_all_active (bool): If True, unmount all active DMGs, otherwise only unmount our own DMG + """ + dmg_info = subprocess.run(["hdiutil", "info", "-plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) dmg_info = plistlib.loads(dmg_info.stdout) diff --git a/resources/run.py b/resources/run.py deleted file mode 100644 index 669e675fb..000000000 --- a/resources/run.py +++ /dev/null @@ -1,152 +0,0 @@ -# Module for running processes with real time output -# Written by CorpNewt -# Source: https://github.com/corpnewt/pymodules/blob/884c3de15b6a2570afde52fe8a14a3e946ffb18a/run.py - -import sys, subprocess, time, threading, shlex, logging -from queue import Queue, Empty - -ON_POSIX = 'posix' in sys.builtin_module_names - -class Run: - - def __init__(self): - return - - def _read_output(self, pipe, q): - try: - for line in iter(lambda: pipe.read(1), b''): - q.put(line) - except ValueError: - pass - pipe.close() - - def _create_thread(self, output): - # Creates a new queue and thread object to watch based on the output pipe sent - q = Queue() - t = threading.Thread(target=self._read_output, args=(output, q)) - t.daemon = True - return (q,t) - - def _stream_output(self, comm, shell = False): - output = error = "" - p = None - try: - if shell and type(comm) is list: - comm = " ".join(shlex.quote(x) for x in comm) - if not shell and type(comm) is str: - comm = shlex.split(comm) - p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0, universal_newlines=True, close_fds=ON_POSIX) - # Setup the stdout thread/queue - q,t = self._create_thread(p.stdout) - qe,te = self._create_thread(p.stderr) - # Start both threads - t.start() - te.start() - - while True: - c = z = "" - try: c = q.get_nowait() - except Empty: pass - else: - sys.stdout.write(c) - output += c - sys.stdout.flush() - try: z = qe.get_nowait() - except Empty: pass - else: - sys.stderr.write(z) - error += z - sys.stderr.flush() - if not c==z=="": continue # Keep going until empty - # No output - see if still running - p.poll() - if p.returncode != None: - # Subprocess ended - break - # No output, but subprocess still running - stall for 20ms - time.sleep(0.02) - - o, e = p.communicate() - return (output+o, error+e, p.returncode) - except: - if p: - try: o, e = p.communicate() - except: o = e = "" - return (output+o, error+e, p.returncode) - return ("", "Command not found!", 1) - - def _decode(self, value, encoding="utf-8", errors="ignore"): - # Helper method to only decode if bytes type - if sys.version_info >= (3,0) and isinstance(value, bytes): - return value.decode(encoding,errors) - return value - - def _run_command(self, comm, shell = False): - c = None - try: - if shell and type(comm) is list: - comm = " ".join(shlex.quote(x) for x in comm) - if not shell and type(comm) is str: - comm = shlex.split(comm) - p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - c = p.communicate() - except: - if c == None: - return ("", "Command not found!", 1) - return (self._decode(c[0]), self._decode(c[1]), p.returncode) - - def run(self, command_list, leave_on_fail = False): - # Command list should be an array of dicts - if type(command_list) is dict: - # We only have one command - command_list = [command_list] - output_list = [] - for comm in command_list: - args = comm.get("args", []) - shell = comm.get("shell", False) - stream = comm.get("stream", False) - sudo = comm.get("sudo", False) - stdout = comm.get("stdout", False) - stderr = comm.get("stderr", False) - mess = comm.get("message", None) - show = comm.get("show", False) - - if not mess == None: - logging.info(mess) - - if not len(args): - # nothing to process - continue - if sudo: - # Check if we have sudo - out = self._run_command(["which", "sudo"]) - if "sudo" in out[0]: - # Can sudo - if type(args) is list: - args.insert(0, out[0].replace("\n", "")) # add to start of list - elif type(args) is str: - args = out[0].replace("\n", "") + " " + args # add to start of string - - if show: - logging.info(" ".join(args)) - - if stream: - # Stream it! - out = self._stream_output(args, shell) - else: - # Just run and gather output - out = self._run_command(args, shell) - if stdout and len(out[0]): - logging.info(out[0]) - if stderr and len(out[1]): - logging.info(out[1]) - # Append output - output_list.append(out) - # Check for errors - if leave_on_fail and out[2] != 0: - # Got an error - leave - break - if len(output_list) == 1: - # We only ran one command - just return that output - return output_list[0] - return output_list \ No newline at end of file diff --git a/resources/sys_patch/sys_patch.py b/resources/sys_patch/sys_patch.py index b232b7ca4..5b2036e13 100644 --- a/resources/sys_patch/sys_patch.py +++ b/resources/sys_patch/sys_patch.py @@ -46,9 +46,9 @@ from data import os_data class PatchSysVolume: - def __init__(self, model, versions, hardware_details=None): + def __init__(self, model: str, global_constants: constants.Constants, hardware_details: list = None): self.model = model - self.constants: constants.Constants() = versions + self.constants: constants.Constants = global_constants self.computer = self.constants.computer self.root_mount_path = None self.root_supports_snapshot = utilities.check_if_root_is_apfs_snapshot() @@ -61,9 +61,9 @@ class PatchSysVolume: # GUI will detect hardware patches before starting PatchSysVolume() # However the TUI will not, so allow for data to be passed in manually avoiding multiple calls if hardware_details is None: - hardware_details = sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).detect_patch_set() + hardware_details = sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).detect_patch_set() self.hardware_details = hardware_details - self.init_pathing(custom_root_mount_path=None, custom_data_mount_path=None) + self._init_pathing(custom_root_mount_path=None, custom_data_mount_path=None) self.skip_root_kmutil_requirement = self.hardware_details["Settings: Supports Auxiliary Cache"] @@ -72,7 +72,15 @@ class PatchSysVolume: if Path(self.constants.payload_local_binaries_root_path).exists(): shutil.rmtree(self.constants.payload_local_binaries_root_path) - def init_pathing(self, custom_root_mount_path=None, custom_data_mount_path=None): + def _init_pathing(self, custom_root_mount_path: Path = None, custom_data_mount_path: Path = None): + """ + Initializes the pathing for root volume patching + + Parameters: + custom_root_mount_path (Path): Custom path to mount the root volume + custom_data_mount_path (Path): Custom path to mount the data volume + + """ if custom_root_mount_path and custom_data_mount_path: self.mount_location = custom_root_mount_path self.data_mount_location = custom_data_mount_path @@ -83,11 +91,12 @@ class PatchSysVolume: else: self.mount_location = "" self.mount_location_data = "" + self.mount_extensions = f"{self.mount_location}/System/Library/Extensions" self.mount_application_support = f"{self.mount_location_data}/Library/Application Support" - def mount_root_vol(self): + def _mount_root_vol(self): # Returns boolean if Root Volume is available self.root_mount_path = utilities.get_disk_path() if self.root_mount_path.startswith("disk"): @@ -113,7 +122,7 @@ class PatchSysVolume: return False - def merge_kdk_with_root(self, save_hid_cs=False): + def _merge_kdk_with_root(self, save_hid_cs=False): if self.skip_root_kmutil_requirement is True: return if self.constants.detected_os < os_data.os_data.ventura: @@ -211,7 +220,7 @@ class PatchSysVolume: utilities.elevated(["rm", "-rf", f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - def unpatch_root_vol(self): + def _unpatch_root_vol(self): if self.constants.detected_os > os_data.os_data.catalina and self.root_supports_snapshot is True: logging.info("- Reverting to last signed APFS snapshot") result = utilities.elevated(["bless", "--mount", self.mount_location, "--bootefi", "--last-sealed-snapshot"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) @@ -221,18 +230,18 @@ class PatchSysVolume: logging.info(result.stdout.decode()) logging.info("- Failed to revert snapshot via Apple's 'bless' command") else: - self.clean_skylight_plugins() - self.delete_nonmetal_enforcement() - self.clean_auxiliary_kc() + self._clean_skylight_plugins() + self._delete_nonmetal_enforcement() + self._clean_auxiliary_kc() self.constants.root_patcher_succeeded = True logging.info("- Unpatching complete") logging.info("\nPlease reboot the machine for patches to take effect") - def rebuild_snapshot(self): - if self.rebuild_kernel_collection() is True: + def _rebuild_snapshot(self): + if self._rebuild_kernel_collection() is True: self.update_preboot_kernel_cache() - self.rebuild_dyld_shared_cache() - if self.create_new_apfs_snapshot() is True: + self._rebuild_dyld_shared_cache() + if self._create_new_apfs_snapshot() is True: logging.info("- Patching complete") logging.info("\nPlease reboot the machine for patches to take effect") if self.needs_kmutil_exemptions is True: @@ -241,7 +250,7 @@ class PatchSysVolume: if self.constants.gui_mode is False: input("\nPress [ENTER] to continue") - def rebuild_kernel_collection(self): + def _rebuild_kernel_collection(self): logging.info("- Rebuilding Kernel Cache (This may take some time)") if self.constants.detected_os > os_data.os_data.catalina: # Base Arguments @@ -328,15 +337,15 @@ class PatchSysVolume: return False for file in ["KextPolicy", "KextPolicy-shm", "KextPolicy-wal"]: - self.remove_file("/private/var/db/SystemPolicyConfiguration/", file) + self._remove_file("/private/var/db/SystemPolicyConfiguration/", file) else: # Install RSRHelper utility to handle desynced KCs - sys_patch_helpers.sys_patch_helpers(self.constants).install_rsr_repair_binary() + sys_patch_helpers.SysPatchHelpers(self.constants).install_rsr_repair_binary() logging.info("- Successfully built new kernel cache") return True - def create_new_apfs_snapshot(self): + def _create_new_apfs_snapshot(self): if self.root_supports_snapshot is True: logging.info("- Creating new APFS snapshot") bless = utilities.elevated( @@ -353,24 +362,25 @@ class PatchSysVolume: if "Can't use last-sealed-snapshot or create-snapshot on non system volume" in bless.stdout.decode(): logging.info("- This is an APFS bug with Monterey and newer! Perform a clean installation to ensure your APFS volume is built correctly") return False - self.unmount_drive() + self._unmount_drive() return True - def unmount_drive(self): + def _unmount_drive(self): logging.info("- Unmounting Root Volume (Don't worry if this fails)") utilities.elevated(["diskutil", "unmount", self.root_mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode() - def rebuild_dyld_shared_cache(self): - if self.constants.detected_os <= os_data.os_data.catalina: - logging.info("- Rebuilding dyld shared cache") - utilities.process_status(utilities.elevated(["update_dyld_shared_cache", "-root", f"{self.mount_location}/"])) + def _rebuild_dyld_shared_cache(self): + if self.constants.detected_os > os_data.os_data.catalina: + return + logging.info("- Rebuilding dyld shared cache") + utilities.process_status(utilities.elevated(["update_dyld_shared_cache", "-root", f"{self.mount_location}/"])) def update_preboot_kernel_cache(self): if self.constants.detected_os == os_data.os_data.catalina: logging.info("- Rebuilding preboot kernel cache") utilities.process_status(utilities.elevated(["kcditto"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - def clean_skylight_plugins(self): + def _clean_skylight_plugins(self): if (Path(self.mount_application_support) / Path("SkyLightPlugins/")).exists(): logging.info("- Found SkylightPlugins folder, removing old plugins") utilities.process_status(utilities.elevated(["rm", "-Rf", f"{self.mount_application_support}/SkyLightPlugins"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) @@ -379,14 +389,14 @@ class PatchSysVolume: logging.info("- Creating SkylightPlugins folder") utilities.process_status(utilities.elevated(["mkdir", "-p", f"{self.mount_application_support}/SkyLightPlugins/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - def delete_nonmetal_enforcement(self): + def _delete_nonmetal_enforcement(self): for arg in ["useMetal", "useIOP"]: result = subprocess.run(["defaults", "read", "/Library/Preferences/com.apple.CoreDisplay", arg], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode("utf-8").strip() if result in ["0", "false", "1", "true"]: logging.info(f"- Removing non-Metal Enforcement Preference: {arg}") utilities.elevated(["defaults", "delete", "/Library/Preferences/com.apple.CoreDisplay", arg]) - def clean_auxiliary_kc(self): + def _clean_auxiliary_kc(self): # When reverting root volume patches, the AuxKC will still retain the UUID # it was built against. Thus when Boot/SysKC are reverted, Aux will break # To resolve this, delete all installed kexts in /L*/E* and rebuild the AuxKC @@ -407,7 +417,7 @@ class PatchSysVolume: for file in oclp_plist_data[key]["Install"][location]: if not file.endswith(".kext"): continue - self.remove_file("/Library/Extensions", file) + self._remove_file("/Library/Extensions", file) # Handle situations where users migrated from older OSes with a lot of garbage in /L*/E* # ex. Nvidia Web Drivers, NetUSB, dosdude1's patches, etc. @@ -431,17 +441,17 @@ class PatchSysVolume: # ex. Symlinks pointing to symlinks pointing to dead files pass - def write_patchset(self, patchset): + def _write_patchset(self, patchset): destination_path = f"{self.mount_location}/System/Library/CoreServices" file_name = "OpenCore-Legacy-Patcher.plist" destination_path_file = f"{destination_path}/{file_name}" - if sys_patch_helpers.sys_patch_helpers(self.constants).generate_patchset_plist(patchset, file_name, self.kdk_path): + if sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, file_name, self.kdk_path): logging.info("- Writing patchset information to Root Volume") if Path(destination_path_file).exists(): utilities.process_status(utilities.elevated(["rm", destination_path_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) utilities.process_status(utilities.elevated(["cp", f"{self.constants.payload_path}/{file_name}", destination_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - def add_auxkc_support(self, install_file, source_folder_path, install_patch_directory, destination_folder_path): + def _add_auxkc_support(self, install_file, source_folder_path, install_patch_directory, destination_folder_path): # In macOS Ventura, KDKs are required to build new Boot and System KCs # However for some patch sets, we're able to use the Auxiliary KCs with '/Library/Extensions' @@ -477,11 +487,11 @@ class PatchSysVolume: plist_data["OSBundleRequired"] = "Auxiliary" plistlib.dump(plist_data, plist_path.open("wb")) - self.check_kexts_needs_authentication(install_file) + self._check_kexts_needs_authentication(install_file) return updated_install_location - def check_kexts_needs_authentication(self, kext_name): + def _check_kexts_needs_authentication(self, kext_name): # Verify whether the user needs to authenticate in System Preferences # Specifically under 'private/var/db/KernelManagement/AuxKC/CurrentAuxKC/com.apple.kcgen.instructions.plist' # ["kextsToBuild"][i]: @@ -503,21 +513,21 @@ class PatchSysVolume: logging.info(f" - {kext_name} requires authentication in System Preferences") self.constants.needs_to_open_preferences = True # Notify in GUI to open System Preferences - def patch_root_vol(self): + def _patch_root_vol(self): logging.info(f"- Running patches for {self.model}") if self.patch_set_dictionary != {}: - self.execute_patchset(self.patch_set_dictionary) + self._execute_patchset(self.patch_set_dictionary) else: - self.execute_patchset(sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).generate_patchset(self.hardware_details)) + self._execute_patchset(sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).generate_patchset(self.hardware_details)) if self.constants.wxpython_variant is True and self.constants.detected_os >= os_data.os_data.big_sur: sys_patch_auto.AutomaticSysPatch(self.constants).install_auto_patcher_launch_agent() - self.rebuild_snapshot() + self._rebuild_snapshot() - def execute_patchset(self, required_patches): + def _execute_patchset(self, required_patches): source_files_path = str(self.constants.payload_local_binaries_root_path) - self.preflight_checks(required_patches, source_files_path) + self._preflight_checks(required_patches, source_files_path) for patch in required_patches: logging.info("- Installing Patchset: " + patch) if "Remove" in required_patches[patch]: @@ -525,7 +535,7 @@ class PatchSysVolume: logging.info("- Remove Files at: " + remove_patch_directory) for remove_patch_file in required_patches[patch]["Remove"][remove_patch_directory]: destination_folder_path = str(self.mount_location) + remove_patch_directory - self.remove_file(destination_folder_path, remove_patch_file) + self._remove_file(destination_folder_path, remove_patch_file) for method_install in ["Install", "Install Non-Root"]: @@ -539,10 +549,10 @@ class PatchSysVolume: else: if install_patch_directory == "/Library/Extensions": self.needs_kmutil_exemptions = True - self.check_kexts_needs_authentication(install_file) + self._check_kexts_needs_authentication(install_file) destination_folder_path = str(self.mount_location_data) + install_patch_directory - updated_destination_folder_path = self.add_auxkc_support(install_file, source_folder_path, install_patch_directory, destination_folder_path) + updated_destination_folder_path = self._add_auxkc_support(install_file, source_folder_path, install_patch_directory, destination_folder_path) if destination_folder_path != updated_destination_folder_path: # Update required_patches to reflect the new destination folder path @@ -553,7 +563,7 @@ class PatchSysVolume: destination_folder_path = updated_destination_folder_path - self.install_new_file(source_folder_path, destination_folder_path, install_file) + self._install_new_file(source_folder_path, destination_folder_path, install_file) if "Processes" in required_patches[patch]: for process in required_patches[patch]["Processes"]: @@ -566,24 +576,24 @@ class PatchSysVolume: logging.info(f"- Running Process:\n{process}") utilities.process_status(subprocess.run(process, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)) if any(x in required_patches for x in ["AMD Legacy GCN", "AMD Legacy Polaris", "AMD Legacy Vega"]): - sys_patch_helpers.sys_patch_helpers(self.constants).disable_window_server_caching() + sys_patch_helpers.SysPatchHelpers(self.constants).disable_window_server_caching() if any(x in required_patches for x in ["Intel Ivy Bridge", "Intel Haswell"]): - sys_patch_helpers.sys_patch_helpers(self.constants).remove_news_widgets() - self.write_patchset(required_patches) + sys_patch_helpers.SysPatchHelpers(self.constants).remove_news_widgets() + self._write_patchset(required_patches) - def preflight_checks(self, required_patches, source_files_path): + def _preflight_checks(self, required_patches, source_files_path): logging.info("- Running Preflight Checks before patching") # Make sure old SkyLight plugins aren't being used - self.clean_skylight_plugins() + self._clean_skylight_plugins() # Make sure non-Metal Enforcement preferences are not present - self.delete_nonmetal_enforcement() + self._delete_nonmetal_enforcement() # Make sure we clean old kexts in /L*/E* that are not in the patchset - self.clean_auxiliary_kc() + self._clean_auxiliary_kc() # Make sure SNB kexts are compatible with the host if "Intel Sandy Bridge" in required_patches: - sys_patch_helpers.sys_patch_helpers(self.constants).snb_board_id_patch(source_files_path) + sys_patch_helpers.SysPatchHelpers(self.constants).snb_board_id_patch(source_files_path) for patch in required_patches: # Check if all files are present @@ -599,11 +609,11 @@ class PatchSysVolume: should_save_cs = False if "Legacy USB 1.1" in required_patches: should_save_cs = True - self.merge_kdk_with_root(save_hid_cs=should_save_cs) + self._merge_kdk_with_root(save_hid_cs=should_save_cs) logging.info("- Finished Preflight, starting patching") - def install_new_file(self, source_folder, destination_folder, file_name): + def _install_new_file(self, source_folder, destination_folder, file_name): # .frameworks are merged # .kexts and .apps are deleted and replaced file_name_str = str(file_name) @@ -616,7 +626,7 @@ class PatchSysVolume: # merge with rsync logging.info(f" - Installing: {file_name}") utilities.elevated(["rsync", "-r", "-i", "-a", f"{source_folder}/{file_name}", f"{destination_folder}/"], stdout=subprocess.PIPE) - self.fix_permissions(destination_folder + "/" + file_name) + self._fix_permissions(destination_folder + "/" + file_name) elif Path(source_folder + "/" + file_name_str).is_dir(): # Applicable for .kext, .app, .plugin, .bundle, all of which are directories if Path(destination_folder + "/" + file_name).exists(): @@ -625,7 +635,7 @@ class PatchSysVolume: else: logging.info(f" - Installing: {file_name}") utilities.process_status(utilities.elevated(["cp", "-R", f"{source_folder}/{file_name}", destination_folder], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - self.fix_permissions(destination_folder + "/" + file_name) + self._fix_permissions(destination_folder + "/" + file_name) else: # Assume it's an individual file, replace as normal if Path(destination_folder + "/" + file_name).exists(): @@ -634,9 +644,9 @@ class PatchSysVolume: else: logging.info(f" - Installing: {file_name}") utilities.process_status(utilities.elevated(["cp", f"{source_folder}/{file_name}", destination_folder], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - self.fix_permissions(destination_folder + "/" + file_name) + self._fix_permissions(destination_folder + "/" + file_name) - def remove_file(self, destination_folder, file_name): + def _remove_file(self, destination_folder, file_name): if Path(destination_folder + "/" + file_name).exists(): logging.info(f" - Removing: {file_name}") if Path(destination_folder + "/" + file_name).is_dir(): @@ -645,7 +655,7 @@ class PatchSysVolume: utilities.process_status(utilities.elevated(["rm", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - def fix_permissions(self, destination_file): + def _fix_permissions(self, destination_file): chmod_args = ["chmod", "-Rf", "755", destination_file] chown_args = ["chown", "-Rf", "root:wheel", destination_file] if not Path(destination_file).is_dir(): @@ -656,7 +666,7 @@ class PatchSysVolume: utilities.process_status(utilities.elevated(chown_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - def check_files(self): + def _check_files(self): if Path(self.constants.payload_local_binaries_root_path).exists(): logging.info("- Local PatcherSupportPkg resources available, continuing...") return True @@ -675,7 +685,7 @@ class PatchSysVolume: def start_patch(self): logging.info("- Starting Patch Process") logging.info(f"- Determining Required Patch set for Darwin {self.constants.detected_os}") - self.patch_set_dictionary = sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).generate_patchset(self.hardware_details) + self.patch_set_dictionary = sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).generate_patchset(self.hardware_details) if self.patch_set_dictionary == {}: change_menu = None @@ -689,11 +699,11 @@ class PatchSysVolume: logging.info("- Continuing root patching") if change_menu in ["y", "Y"]: logging.info("- Verifying whether Root Patching possible") - if sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).verify_patch_allowed(print_errors=not self.constants.wxpython_variant) is True: + 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._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: @@ -708,9 +718,9 @@ class PatchSysVolume: def start_unpatch(self): logging.info("- Starting Unpatch Process") - if sys_patch_detect.detect_root_patch(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 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: diff --git a/resources/sys_patch/sys_patch_auto.py b/resources/sys_patch/sys_patch_auto.py index eb8b11b48..e71d235cc 100644 --- a/resources/sys_patch/sys_patch_auto.py +++ b/resources/sys_patch/sys_patch_auto.py @@ -1,29 +1,42 @@ -# Auto Patching's main purpose is to try and tell the user they're missing root patches -# New users may not realize OS updates remove our patches, so we try and run when nessasary -# Conditions for running: -# - Verify running GUI (TUI users can write their own scripts) -# - Verify the Snapshot Seal is intact (if not, assume user is running patches) -# - Verify this model needs patching (if not, assume user upgraded hardware and OCLP was not removed) -# - Verify there are no updates for OCLP (ensure we have the latest patch sets) -# If all these tests pass, start Root Patcher # Copyright (C) 2022, Mykola Grymalyuk -from pathlib import Path import plistlib import subprocess import webbrowser import logging -from resources import utilities, updates, global_settings, network_handler +from pathlib import Path + +from resources import utilities, updates, global_settings, network_handler, constants from resources.sys_patch import sys_patch_detect from resources.gui import gui_main -class AutomaticSysPatch: - def __init__(self, constants): - self.constants = constants +class AutomaticSysPatch: + """ + Library of functions for launch agent, including automatic patching + """ + + def __init__(self, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants def start_auto_patch(self): + """ + Initiates automatic patching + + Auto Patching's main purpose is to try and tell the user they're missing root patches + New users may not realize OS updates remove our patches, so we try and run when nessasary + + Conditions for running: + - Verify running GUI (TUI users can write their own scripts) + - Verify the Snapshot Seal is intact (if not, assume user is running patches) + - Verify this model needs patching (if not, assume user upgraded hardware and OCLP was not removed) + - Verify there are no updates for OCLP (ensure we have the latest patch sets) + + If all these tests pass, start Root Patcher + + """ + logging.info("- Starting Automatic Patching") if self.constants.wxpython_variant is False: logging.info("- Auto Patch option is not supported on TUI, please use GUI") @@ -31,7 +44,7 @@ class AutomaticSysPatch: if utilities.check_seal() is True: logging.info("- Detected Snapshot seal intact, detecting patches") - patches = sys_patch_detect.detect_root_patch(self.constants.computer.real_model, self.constants).detect_patch_set() + patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches): patches = [] if patches: @@ -46,7 +59,7 @@ class AutomaticSysPatch: 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.check_binary_updates(self.constants).check_binary_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: @@ -113,26 +126,35 @@ class AutomaticSysPatch: else: logging.info("- Detected Snapshot seal not intact, skipping") - if self.determine_if_versions_match() is False: - self.determine_if_boot_matches() + if self._determine_if_versions_match(): + self._determine_if_boot_matches() - def determine_if_versions_match(self): + def _determine_if_versions_match(self): + """ + Determine if the booted version of OCLP matches the installed version + + ie. Installed app is 0.2.0, but EFI version is 0.1.0 + + Returns: + bool: True if versions match, False if not + """ + logging.info("- Checking booted vs installed OCLP Build") if self.constants.computer.oclp_version is None: logging.info("- Booted version not found") - return False + return True if self.constants.computer.oclp_version == self.constants.patcher_version: logging.info("- Versions match") - return False + return True # Check if installed version is newer than booted version - if updates.check_binary_updates(self.constants).check_if_build_newer( + if updates.CheckBinaryUpdates(self.constants)._check_if_build_newer( self.constants.computer.oclp_version.split("."), self.constants.patcher_version.split(".") ) is True: logging.info("- Installed version is newer than booted version") - return False + return True args = [ "osascript", @@ -150,18 +172,25 @@ class AutomaticSysPatch: self.constants.start_build_install = True gui_main.wx_python_gui(self.constants).main_menu(None) - return True + return False - def determine_if_boot_matches(self): - # Goal of this function is to determine whether the user - # is using a USB drive to Boot OpenCore but macOS does not - # reside on the same drive as the USB. - # If we determine them to be mismatched, notify the user - # and ask if they want to install to install to disk + def _determine_if_boot_matches(self): + """ + Determine if the boot drive matches the macOS drive + ie. Booted from USB, but macOS is on internal disk + + Goal of this function is to determine whether the user + is using a USB drive to Boot OpenCore but macOS does not + reside on the same drive as the USB. + + If we determine them to be mismatched, notify the user + and ask if they want to install to install to disk. + """ logging.info("- Determining if macOS drive matches boot drive") - should_notify = global_settings.global_settings().read_property("AutoPatch_Notify_Mismatched_Disks") + + should_notify = global_settings.GlobalEnviromentSettings().read_property("AutoPatch_Notify_Mismatched_Disks") if should_notify is False: logging.info("- Skipping due to user preference") return @@ -223,9 +252,16 @@ class AutomaticSysPatch: def install_auto_patcher_launch_agent(self): - # Installs the following: - # - OpenCore-Patcher.app in /Library/Application Support/Dortania/ - # - com.dortania.opencore-legacy-patcher.auto-patch.plist in /Library/LaunchAgents/ + """ + Install the Auto Patcher Launch Agent + + Installs the following: + - OpenCore-Patcher.app in /Library/Application Support/Dortania/ + - com.dortania.opencore-legacy-patcher.auto-patch.plist in /Library/LaunchAgents/ + + See start_auto_patch() comments for more info + """ + if self.constants.launcher_script is not None: logging.info("- Skipping Auto Patcher Launch Agent, not supported when running from source") return diff --git a/resources/sys_patch/sys_patch_detect.py b/resources/sys_patch/sys_patch_detect.py index d0988b87b..4474a9360 100644 --- a/resources/sys_patch/sys_patch_detect.py +++ b/resources/sys_patch/sys_patch_detect.py @@ -3,19 +3,39 @@ # Used when supplying data to sys_patch.py # Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk -from resources import constants, device_probe, utilities, amfi_detect, network_handler, kdk_handler -from resources.sys_patch import sys_patch_helpers -from data import model_array, os_data, sip_data, sys_patch_dict, smbios_data, cpu_data - -import py_sip_xnu -from pathlib import Path import plistlib import logging +import py_sip_xnu +from pathlib import Path + +from resources import ( + constants, + device_probe, + utilities, + amfi_detect, + network_handler, + kdk_handler +) +from data import ( + model_array, + os_data, + sip_data, + sys_patch_dict, + smbios_data, + cpu_data +) + + +class DetectRootPatch: + """ + Library for querying root volume patches applicable for booted system + """ + + def __init__(self, model: str, global_constants: constants.Constants): + self.model: str = model + + self.constants: constants.Constants = global_constants -class detect_root_patch: - def __init__(self, model, versions): - self.model = model - self.constants: constants.Constants() = versions self.computer = self.constants.computer # GPU Patch Detection @@ -63,7 +83,12 @@ class detect_root_patch: self.missing_nv_web_opengl = False self.missing_nv_compat = False - def detect_gpus(self): + + def _detect_gpus(self): + """ + Query GPUs and set flags for applicable patches + """ + gpus = self.constants.computer.gpus non_metal_os = os_data.os_data.catalina for i, gpu in enumerate(gpus): @@ -75,7 +100,7 @@ class detect_root_patch: self.amfi_must_disable = True if os_data.os_data.ventura in self.constants.legacy_accel_support: self.amfi_shim_bins = True - self.legacy_keyboard_backlight = self.check_legacy_keyboard_backlight() + self.legacy_keyboard_backlight = self._check_legacy_keyboard_backlight() self.requires_root_kc = True elif gpu.arch == device_probe.NVIDIA.Archs.Kepler and self.constants.force_nv_web is False: if self.constants.detected_os > os_data.os_data.big_sur: @@ -165,7 +190,7 @@ class detect_root_patch: self.amfi_must_disable = True if os_data.os_data.ventura in self.constants.legacy_accel_support: self.amfi_shim_bins = True - self.legacy_keyboard_backlight = self.check_legacy_keyboard_backlight() + self.legacy_keyboard_backlight = self._check_legacy_keyboard_backlight() self.requires_root_kc = True elif gpu.arch == device_probe.Intel.Archs.Sandy_Bridge: if self.constants.detected_os > non_metal_os: @@ -173,7 +198,7 @@ class detect_root_patch: self.amfi_must_disable = True if os_data.os_data.ventura in self.constants.legacy_accel_support: self.amfi_shim_bins = True - self.legacy_keyboard_backlight = self.check_legacy_keyboard_backlight() + self.legacy_keyboard_backlight = self._check_legacy_keyboard_backlight() self.requires_root_kc = True elif gpu.arch == device_probe.Intel.Archs.Ivy_Bridge: if self.constants.detected_os > os_data.os_data.big_sur: @@ -220,17 +245,21 @@ class detect_root_patch: self.requires_root_kc = True else: if self.requires_root_kc is True: - self.missing_kdk = not self.check_kdk() + self.missing_kdk = not self._check_kdk() - self.check_networking_support() + self._check_networking_support() - def check_networking_support(self): - # On macOS Ventura, networking support is required to download KDKs. - # However for machines such as BCM94322, BCM94328 and Atheros chipsets, - # users may only have wifi as their only supported network interface. - # Thus we'll allow for KDK-less installs for these machines on first run. - # On subsequent runs, we'll require networking to be enabled. + def _check_networking_support(self): + """ + Query for network requirement, ex. KDK downloading + + On macOS Ventura, networking support is required to download KDKs. + However for machines such as BCM94322, BCM94328 and Atheros chipsets, + users may only have wifi as their only supported network interface. + Thus we'll allow for KDK-less installs for these machines on first run. + On subsequent runs, we'll require networking to be enabled. + """ if self.constants.detected_os < os_data.os_data.ventura: return @@ -272,7 +301,11 @@ class detect_root_patch: self.legacy_keyboard_backlight = False - def check_dgpu_status(self): + def _check_dgpu_status(self): + """ + Query whether system has an active dGPU + """ + dgpu = self.constants.computer.dgpu if dgpu: if dgpu.class_code and dgpu.class_code == 0xFFFFFFFF: @@ -281,25 +314,45 @@ class detect_root_patch: return True return False - def detect_demux(self): + + def _detect_demux(self): + """ + Query whether system has been demuxed (ex. MacBookPro8,2, disabled dGPU) + """ + # If GFX0 is missing, assume machine was demuxed # -wegnoegpu would also trigger this, so ensure arg is not present if not "-wegnoegpu" in (utilities.get_nvram("boot-args", decode=True) or ""): igpu = self.constants.computer.igpu - dgpu = self.check_dgpu_status() + dgpu = self._check_dgpu_status() if igpu and not dgpu: return True return False - def check_legacy_keyboard_backlight(self): + + def _check_legacy_keyboard_backlight(self): + """ + Query whether system has a legacy keyboard backlight + + Returns: + bool: True if legacy keyboard backlight, False otherwise + """ + # iMac12,x+ have an 'ACPI0008' device, but it's not a keyboard backlight # Best to assume laptops will have a keyboard backlight if self.model.startswith("MacBook"): return self.constants.computer.ambient_light_sensor return False - def check_nv_web_nvram(self): - # First check boot-args, then dedicated nvram variable + + def _check_nv_web_nvram(self): + """ + Query for Nvidia Web Driver property: nvda_drv_vrl or nvda_drv + + Returns: + bool: True if property is present, False otherwise + """ + nv_on = utilities.get_nvram("boot-args", decode=True) if nv_on: if "nvda_drv_vrl=" in nv_on: @@ -309,8 +362,17 @@ class detect_root_patch: return True return False - def check_nv_web_opengl(self): - # First check boot-args, then whether property exists on GPU + + def _check_nv_web_opengl(self): + """ + Query for Nvidia Web Driver property: ngfxgl + + Verify Web Drivers will run in OpenGL mode + + Returns: + bool: True if property is present, False otherwise + """ + nv_on = utilities.get_nvram("boot-args", decode=True) if nv_on: if "ngfxgl=" in nv_on: @@ -321,8 +383,17 @@ class detect_root_patch: return True return False - def check_nv_compat(self): - # Check for 'nv_web' in boot-args, then whether property exists on GPU + + def _check_nv_compat(self): + """ + Query for Nvidia Web Driver property: ngfxcompat + + Verify Web Drivers will skip NVDAStartupWeb compatibility check + + Returns: + bool: True if property is present, False otherwise + """ + nv_on = utilities.get_nvram("boot-args", decode=True) if nv_on: if "ngfxcompat=" in nv_on: @@ -333,13 +404,37 @@ class detect_root_patch: return True return False - def check_whatevergreen(self): + + def _check_whatevergreen(self): + """ + Query whether WhateverGreen.kext is loaded + + Returns: + bool: True if loaded, False otherwise + """ + return utilities.check_kext_loaded("WhateverGreen", self.constants.detected_os) - def check_kdk(self): + + def _check_kdk(self): + """ + Query whether Kernel Debug Kit is installed + + Returns: + bool: True if installed, False otherwise + """ + return kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version, passive=True).kdk_already_installed - def check_sip(self): + + def _check_sip(self): + """ + Query System Integrity checks required for patching + + Returns: + tuple: (list, str, str) of SIP values, SIP hex, SIP error message + """ + if self.constants.detected_os > os_data.os_data.catalina: if self.nvidia_web is True: sip = sip_data.system_integrity_protection.root_patch_sip_big_sur_3rd_part_kexts @@ -365,7 +460,15 @@ class detect_root_patch: sip_value = f"For Hackintoshes, please set csr-active-config to '03060000' ({sip_hex})\nFor non-OpenCore Macs, please run 'csrutil disable' in RecoveryOS" return (sip, sip_value, sip_hex) - def check_uhci_ohci(self): + + def _check_uhci_ohci(self): + """ + Query whether host has UHCI/OHCI controllers, and requires USB 1.1 patches + + Returns: + bool: True if UHCI/OHCI patches required, False otherwise + """ + if self.constants.detected_os < os_data.os_data.ventura: return False @@ -399,10 +502,19 @@ class detect_root_patch: return False + + # Entry point for patch set detection def detect_patch_set(self): + """ + Query patch sets required for host + + Returns: + dict: Dictionary of patch sets + """ + self.has_network = network_handler.NetworkUtilities().verify_network_connection() - if self.check_uhci_ohci() is True: + if self._check_uhci_ohci() is True: self.legacy_uhci_ohci = True self.requires_root_kc = True @@ -435,12 +547,12 @@ class detect_root_patch: if self.constants.detected_os > os_data.os_data.high_sierra: if self.model in ["MacBookPro8,2", "MacBookPro8,3"]: # Ref: https://doslabelectronics.com/Demux.html - if self.detect_demux() is True: + if self._detect_demux() is True: self.legacy_gmux = True else: self.legacy_gmux = True - self.detect_gpus() + self._detect_gpus() self.root_patch_dict = { "Graphics: Nvidia Tesla": self.nvidia_tesla, @@ -467,11 +579,11 @@ class detect_root_patch: "Settings: Supports Auxiliary Cache": not self.requires_root_kc, "Settings: Kernel Debug Kit missing": self.missing_kdk if self.constants.detected_os >= os_data.os_data.ventura.value else False, "Validation: Patching Possible": self.verify_patch_allowed(), - "Validation: Unpatching Possible": self.verify_unpatch_allowed(), - f"Validation: SIP is enabled (Required: {self.check_sip()[2]} or higher)": self.sip_enabled, + "Validation: Unpatching Possible": self._verify_unpatch_allowed(), + f"Validation: SIP is enabled (Required: {self._check_sip()[2]} or higher)": self.sip_enabled, f"Validation: Currently Booted SIP: ({hex(py_sip_xnu.SipXnu().get_sip_status().value)})": self.sip_enabled, "Validation: SecureBootModel is enabled": self.sbm_enabled, - f"Validation: {'AMFI' if self.constants.host_is_hackintosh is True or self.get_amfi_level_needed() > 2 else 'Library Validation'} is enabled": self.amfi_enabled if self.amfi_must_disable is True else False, + f"Validation: {'AMFI' if self.constants.host_is_hackintosh is True or self._get_amfi_level_needed() > 2 else 'Library Validation'} is enabled": self.amfi_enabled if self.amfi_must_disable is True else False, "Validation: FileVault is enabled": self.fv_enabled, "Validation: System is dosdude1 patched": self.dosdude_patched, "Validation: WhateverGreen.kext missing": self.missing_whatever_green if self.nvidia_web is True else False, @@ -483,30 +595,53 @@ class detect_root_patch: return self.root_patch_dict - def get_amfi_level_needed(self): - if self.amfi_must_disable is True: - if self.constants.detected_os > os_data.os_data.catalina: - if self.constants.detected_os >= os_data.os_data.ventura: - if self.amfi_shim_bins is True: - # Currently we require AMFI outright disabled - # in Ventura to work with shim'd binaries - return amfi_detect.AmfiConfigDetectLevel.ALLOW_ALL - return amfi_detect.AmfiConfigDetectLevel.LIBRARY_VALIDATION - return amfi_detect.AmfiConfigDetectLevel.NO_CHECK - def verify_patch_allowed(self, print_errors=False): - sip_dict = self.check_sip() + def _get_amfi_level_needed(self): + """ + Query the AMFI level needed for the patcher to work + + Returns: + int: AMFI level needed + """ + + if self.amfi_must_disable is False: + return amfi_detect.AmfiConfigDetectLevel.NO_CHECK + + if self.constants.detected_os < os_data.os_data.big_sur: + return amfi_detect.AmfiConfigDetectLevel.NO_CHECK + + if self.constants.detected_os >= os_data.os_data.ventura: + if self.amfi_shim_bins is True: + # Currently we require AMFI outright disabled + # in Ventura to work with shim'd binaries + return amfi_detect.AmfiConfigDetectLevel.ALLOW_ALL + + return amfi_detect.AmfiConfigDetectLevel.LIBRARY_VALIDATION + + + def verify_patch_allowed(self, print_errors: bool = False): + """ + Validate that the patcher can be run + + Parameters: + print_errors (bool): Print errors to console + + Returns: + bool: True if patching is allowed, False otherwise + """ + + sip_dict = self._check_sip() sip = sip_dict[0] sip_value = sip_dict[1] self.sip_enabled, self.sbm_enabled, self.fv_enabled, self.dosdude_patched = utilities.patching_status(sip, self.constants.detected_os) - self.amfi_enabled = not amfi_detect.AmfiConfigurationDetection().check_config(self.get_amfi_level_needed()) + self.amfi_enabled = not amfi_detect.AmfiConfigurationDetection().check_config(self._get_amfi_level_needed()) if self.nvidia_web is True: - self.missing_nv_web_nvram = not self.check_nv_web_nvram() - self.missing_nv_web_opengl = not self.check_nv_web_opengl() - self.missing_nv_compat = not self.check_nv_compat() - self.missing_whatever_green = not self.check_whatevergreen() + self.missing_nv_web_nvram = not self._check_nv_web_nvram() + self.missing_nv_web_opengl = not self._check_nv_web_opengl() + self.missing_nv_compat = not self._check_nv_compat() + self.missing_whatever_green = not self._check_whatevergreen() if print_errors is True: if self.sip_enabled is True: @@ -574,28 +709,54 @@ class detect_root_patch: ] ): return False - else: - return True - def verify_unpatch_allowed(self, print_errors=False): - # Must be called after verify_patch_allowed + return True + + + def _verify_unpatch_allowed(self): + """ + Validate that the unpatcher can be run + + Preconditions: + Must be called after verify_patch_allowed() + + Returns: + bool: True if unpatching is allowed, False otherwise + """ + return not self.sip_enabled - def generate_patchset(self, hardware_details): - all_hardware_patchset = sys_patch_dict.SystemPatchDictionary(self.constants.detected_os, self.constants.detected_os_minor, self.constants.legacy_accel_support) - required_patches = {} + + def generate_patchset(self, hardware_details: dict): + """ + Generate Patchset dictionary for the current system + + Parameters: + hardware_details (dict): Dictionary of hardware details generated by detect_patch_set() + + Returns: + dict: Dictionary of patches to be applied from sys_patch_dict.py + """ + + all_hardware_patchset: dict = sys_patch_dict.SystemPatchDictionary(self.constants.detected_os, self.constants.detected_os_minor, self.constants.legacy_accel_support) + required_patches: dict = {} + utilities.cls() + logging.info("- The following patches will be applied:") + if hardware_details["Graphics: Intel Ironlake"] is True: required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]}) required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]}) required_patches.update({"Intel Ironlake": all_hardware_patchset["Graphics"]["Intel Ironlake"]}) + if hardware_details["Graphics: Intel Sandy Bridge"] is True: required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]}) required_patches.update({"Non-Metal ColorSync Workaround": all_hardware_patchset["Graphics"]["Non-Metal ColorSync Workaround"]}) required_patches.update({"High Sierra GVA": all_hardware_patchset["Graphics"]["High Sierra GVA"]}) required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]}) required_patches.update({"Intel Sandy Bridge": all_hardware_patchset["Graphics"]["Intel Sandy Bridge"]}) + if hardware_details["Graphics: Intel Ivy Bridge"] is True: required_patches.update({"Metal 3802 Common": all_hardware_patchset["Graphics"]["Metal 3802 Common"]}) required_patches.update({"Catalina GVA": all_hardware_patchset["Graphics"]["Catalina GVA"]}) @@ -603,23 +764,28 @@ class detect_root_patch: required_patches.update({"Big Sur OpenCL": all_hardware_patchset["Graphics"]["Big Sur OpenCL"]}) required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]}) required_patches.update({"Intel Ivy Bridge": all_hardware_patchset["Graphics"]["Intel Ivy Bridge"]}) + if hardware_details["Graphics: Intel Haswell"] is True: required_patches.update({"Metal 3802 Common": all_hardware_patchset["Graphics"]["Metal 3802 Common"]}) required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]}) required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]}) required_patches.update({"Intel Haswell": all_hardware_patchset["Graphics"]["Intel Haswell"]}) + if hardware_details["Graphics: Intel Broadwell"] is True: required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]}) required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]}) required_patches.update({"Intel Broadwell": all_hardware_patchset["Graphics"]["Intel Broadwell"]}) + if hardware_details["Graphics: Intel Skylake"] is True: required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]}) required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]}) required_patches.update({"Intel Skylake": all_hardware_patchset["Graphics"]["Intel Skylake"]}) + if hardware_details["Graphics: Nvidia Tesla"] is True: required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]}) required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]}) required_patches.update({"Nvidia Tesla": all_hardware_patchset["Graphics"]["Nvidia Tesla"]}) + if hardware_details["Graphics: Nvidia Web Drivers"] is True: required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]}) required_patches.update({"Non-Metal IOAccelerator Common": all_hardware_patchset["Graphics"]["Non-Metal IOAccelerator Common"]}) @@ -627,6 +793,7 @@ class detect_root_patch: required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]}) required_patches.update({"Nvidia Web Drivers": all_hardware_patchset["Graphics"]["Nvidia Web Drivers"]}) required_patches.update({"Non-Metal Enforcement": all_hardware_patchset["Graphics"]["Non-Metal Enforcement"]}) + if hardware_details["Graphics: Nvidia Kepler"] is True: required_patches.update({"Revert Metal Downgrade": all_hardware_patchset["Graphics"]["Revert Metal Downgrade"]}) required_patches.update({"Metal 3802 Common": all_hardware_patchset["Graphics"]["Metal 3802 Common"]}) @@ -641,11 +808,13 @@ class detect_root_patch: if "Catalina GVA" in required_patches: del(required_patches["Catalina GVA"]) break + if hardware_details["Graphics: AMD TeraScale 1"] is True: required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]}) required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]}) required_patches.update({"AMD TeraScale Common": all_hardware_patchset["Graphics"]["AMD TeraScale Common"]}) required_patches.update({"AMD TeraScale 1": all_hardware_patchset["Graphics"]["AMD TeraScale 1"]}) + if hardware_details["Graphics: AMD TeraScale 2"] is True: required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]}) required_patches.update({"Non-Metal IOAccelerator Common": all_hardware_patchset["Graphics"]["Non-Metal IOAccelerator Common"]}) @@ -656,6 +825,7 @@ class detect_root_patch: # TeraScale 2 MacBooks with faulty GPUs are highly prone to crashing with AMDRadeonX3000 attached # Additionally, AMDRadeonX3000 requires IOAccelerator downgrade which is not installed without 'Non-Metal IOAccelerator Common' del(required_patches["AMD TeraScale 2"]["Install"]["/System/Library/Extensions"]["AMDRadeonX3000.kext"]) + if hardware_details["Graphics: AMD Legacy GCN"] is True or hardware_details["Graphics: AMD Legacy Polaris"] is True: required_patches.update({"Revert Metal Downgrade": all_hardware_patchset["Graphics"]["Revert Metal Downgrade"]}) required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]}) @@ -666,6 +836,7 @@ class detect_root_patch: required_patches.update({"AMD Legacy Polaris": all_hardware_patchset["Graphics"]["AMD Legacy Polaris"]}) if "AVX2" not in self.constants.computer.cpu.leafs: required_patches.update({"AMD OpenCL": all_hardware_patchset["Graphics"]["AMD OpenCL"]}) + if hardware_details["Graphics: AMD Legacy Vega"] is True: required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]}) required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]}) @@ -673,20 +844,26 @@ class detect_root_patch: required_patches.update({"AMD OpenCL": all_hardware_patchset["Graphics"]["AMD OpenCL"]}) if hardware_details["Graphics: AMD Legacy GCN"] is True: required_patches.update({"AMD Legacy Vega Extended": all_hardware_patchset["Graphics"]["AMD Legacy Vega Extended"]}) + if hardware_details["Brightness: Legacy Backlight Control"] is True: required_patches.update({"Legacy Backlight Control": all_hardware_patchset["Brightness"]["Legacy Backlight Control"]}) + if hardware_details["Audio: Legacy Realtek"] is True: if self.model in ["iMac7,1", "iMac8,1"]: required_patches.update({"Legacy Realtek": all_hardware_patchset["Audio"]["Legacy Realtek"]}) else: required_patches.update({"Legacy Non-GOP": all_hardware_patchset["Audio"]["Legacy Non-GOP"]}) + if hardware_details["Networking: Legacy Wireless"] is True: required_patches.update({"Legacy Wireless": all_hardware_patchset["Networking"]["Legacy Wireless"]}) required_patches.update({"Legacy Wireless Extended": all_hardware_patchset["Networking"]["Legacy Wireless Extended"]}) + if hardware_details["Miscellaneous: Legacy GMUX"] is True: required_patches.update({"Legacy GMUX": all_hardware_patchset["Miscellaneous"]["Legacy GMUX"]}) + if hardware_details["Miscellaneous: Legacy Keyboard Backlight"] is True: required_patches.update({"Legacy Keyboard Backlight": all_hardware_patchset["Miscellaneous"]["Legacy Keyboard Backlight"]}) + if hardware_details["Miscellaneous: Legacy USB 1.1"] is True: required_patches.update({"Legacy USB 1.1": all_hardware_patchset["Miscellaneous"]["Legacy USB 1.1"]}) diff --git a/resources/sys_patch/sys_patch_helpers.py b/resources/sys_patch/sys_patch_helpers.py index 9cbfe043c..ad4e4ef2c 100644 --- a/resources/sys_patch/sys_patch_helpers.py +++ b/resources/sys_patch/sys_patch_helpers.py @@ -1,58 +1,86 @@ # Additional support functions for sys_patch.py -# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk +# Copyright (C) 2020-2023, Dhinak G, Mykola Grymalyuk -import subprocess -import tempfile -from data import os_data -from resources import generate_smbios, utilities -from pathlib import Path -from datetime import datetime import plistlib import os import logging +import subprocess +from pathlib import Path +from datetime import datetime -from resources import kdk_handler, bplist - -class sys_patch_helpers: - - def __init__(self, constants): - self.constants = constants +from data import os_data +from resources import bplist, constants, generate_smbios, utilities - def snb_board_id_patch(self, source_files_path): - # AppleIntelSNBGraphicsFB hard codes the supported Board IDs for Sandy Bridge iGPUs - # Because of this, the kext errors out on unsupported systems - # This function simply patches in a supported Board ID, using 'determine_best_board_id_for_sandy()' - # to supplement the ideal Board ID +class SysPatchHelpers: + """ + Library of helper functions for sys_patch.py and related libraries + """ + + def __init__(self, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants + + + def snb_board_id_patch(self, source_files_path: str): + """ + Patch AppleIntelSNBGraphicsFB.kext to support unsupported Board IDs + + AppleIntelSNBGraphicsFB hard codes the supported Board IDs for Sandy Bridge iGPUs + Because of this, the kext errors out on unsupported systems + This function simply patches in a supported Board ID, using 'determine_best_board_id_for_sandy()' + to supplement the ideal Board ID + + Parameters: + source_files_path (str): Path to the source files + + """ + source_files_path = str(source_files_path) - if self.constants.computer.reported_board_id not in self.constants.sandy_board_id_stock: - logging.info(f"- Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching") - board_to_patch = generate_smbios.determine_best_board_id_for_sandy(self.constants.computer.reported_board_id, self.constants.computer.gpus) - logging.info(f"- Replacing {board_to_patch} with {self.constants.computer.reported_board_id}") - board_to_patch_hex = bytes.fromhex(board_to_patch.encode('utf-8').hex()) - reported_board_hex = bytes.fromhex(self.constants.computer.reported_board_id.encode('utf-8').hex()) + if self.constants.computer.reported_board_id in self.constants.sandy_board_id_stock: + return - if len(board_to_patch_hex) > len(reported_board_hex): - # Pad the reported Board ID with zeros to match the length of the board to patch - reported_board_hex = reported_board_hex + bytes(len(board_to_patch_hex) - len(reported_board_hex)) - elif len(board_to_patch_hex) < len(reported_board_hex): - logging.info(f"- Error: Board ID {self.constants.computer.reported_board_id} is longer than {board_to_patch}") - raise Exception("Host's Board ID is longer than the kext's Board ID, cannot patch!!!") + logging.info(f"- Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching") - path = source_files_path + "/10.13.6/System/Library/Extensions/AppleIntelSNBGraphicsFB.kext/Contents/MacOS/AppleIntelSNBGraphicsFB" - if Path(path).exists(): - with open(path, 'rb') as f: - data = f.read() - data = data.replace(board_to_patch_hex, reported_board_hex) - with open(path, 'wb') as f: - f.write(data) - else: - logging.info(f"- Error: Could not find {path}") - raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!") + board_to_patch = generate_smbios.determine_best_board_id_for_sandy(self.constants.computer.reported_board_id, self.constants.computer.gpus) + logging.info(f"- Replacing {board_to_patch} with {self.constants.computer.reported_board_id}") + + board_to_patch_hex = bytes.fromhex(board_to_patch.encode('utf-8').hex()) + reported_board_hex = bytes.fromhex(self.constants.computer.reported_board_id.encode('utf-8').hex()) + + if len(board_to_patch_hex) > len(reported_board_hex): + # Pad the reported Board ID with zeros to match the length of the board to patch + reported_board_hex = reported_board_hex + bytes(len(board_to_patch_hex) - len(reported_board_hex)) + elif len(board_to_patch_hex) < len(reported_board_hex): + logging.info(f"- Error: Board ID {self.constants.computer.reported_board_id} is longer than {board_to_patch}") + raise Exception("Host's Board ID is longer than the kext's Board ID, cannot patch!!!") + + path = source_files_path + "/10.13.6/System/Library/Extensions/AppleIntelSNBGraphicsFB.kext/Contents/MacOS/AppleIntelSNBGraphicsFB" + if not Path(path).exists(): + logging.info(f"- Error: Could not find {path}") + raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!") + + with open(path, 'rb') as f: + data = f.read() + data = data.replace(board_to_patch_hex, reported_board_hex) + with open(path, 'wb') as f: + f.write(data) - def generate_patchset_plist(self, patchset, file_name, kdk_used): + def generate_patchset_plist(self, patchset: dict, file_name: str, kdk_used: Path): + """ + Generate patchset file for user reference + + Parameters: + patchset (dict): Dictionary of patchset, see sys_patch_detect.py and sys_patch_dict.py + file_name (str): Name of the file to write to + kdk_used (Path): Path to the KDK used, if any + + Returns: + bool: True if successful, False if not + + """ + source_path = f"{self.constants.payload_path}" source_path_file = f"{source_path}/{file_name}" @@ -68,23 +96,35 @@ class sys_patch_helpers: "Kernel Debug Kit Used": f"{kdk_string}", "OS Version": f"{self.constants.detected_os}.{self.constants.detected_os_minor} ({self.constants.detected_os_build})", } + data.update(patchset) + if Path(source_path_file).exists(): os.remove(source_path_file) + # Need to write to a safe location plistlib.dump(data, Path(source_path_file).open("wb"), sort_keys=False) + if Path(source_path_file).exists(): return True + return False def disable_window_server_caching(self): - # On legacy GCN GPUs, the WindowServer cache generated creates - # corrupted Opaque shaders. - # To work-around this, we disable WindowServer caching - # And force macOS into properly generating the Opaque shaders + """ + Disable WindowServer's asset caching + + On legacy GCN GPUs, the WindowServer cache generated creates + corrupted Opaque shaders. + + To work-around this, we disable WindowServer caching + And force macOS into properly generating the Opaque shaders + """ + if self.constants.detected_os < os_data.os_data.ventura: return + logging.info("- Disabling WindowServer Caching") # Invoke via 'bash -c' to resolve pathing utilities.elevated(["bash", "-c", "rm -rf /private/var/folders/*/*/*/WindowServer/com.apple.WindowServer"]) @@ -96,11 +136,17 @@ class sys_patch_helpers: def remove_news_widgets(self): - # On Ivy Bridge and Haswell iGPUs, RenderBox will crash the News Widgets in - # Notification Centre. To ensure users can access Notifications normally, - # we manually remove all News Widgets + """ + Remove News Widgets from Notification Centre + + On Ivy Bridge and Haswell iGPUs, RenderBox will crash the News Widgets in + Notification Centre. To ensure users can access Notifications normally, + we manually remove all News Widgets + """ + if self.constants.detected_os < os_data.os_data.ventura: return + logging.info("- Parsing Notification Centre Widgets") file_path = "~/Library/Containers/com.apple.notificationcenterui/Data/Library/Preferences/com.apple.notificationcenterui.plist" file_path = Path(file_path).expanduser() @@ -112,22 +158,27 @@ class sys_patch_helpers: did_find = False with open(file_path, "rb") as f: data = plistlib.load(f) - if "widgets" in data: - if "instances" in data["widgets"]: - for widget in list(data["widgets"]["instances"]): - widget_data = bplist.BPListReader(widget).parse() - for entry in widget_data: - if not 'widget' in entry: - continue - sub_data = bplist.BPListReader(widget_data[entry]).parse() - for sub_entry in sub_data: - if not '$object' in sub_entry: - continue - if not b'com.apple.news' in sub_data[sub_entry][2]: - continue - logging.info(f" - Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}") - data["widgets"]["instances"].remove(widget) - did_find = True + if "widgets" not in data: + return + + if "instances" not in data["widgets"]: + return + + for widget in list(data["widgets"]["instances"]): + widget_data = bplist.BPListReader(widget).parse() + for entry in widget_data: + if 'widget' not in entry: + continue + sub_data = bplist.BPListReader(widget_data[entry]).parse() + for sub_entry in sub_data: + if not '$object' in sub_entry: + continue + if not b'com.apple.news' in sub_data[sub_entry][2]: + continue + logging.info(f" - Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}") + data["widgets"]["instances"].remove(widget) + did_find = True + if did_find: with open(file_path, "wb") as f: plistlib.dump(data, f, sort_keys=False) @@ -135,16 +186,23 @@ class sys_patch_helpers: def install_rsr_repair_binary(self): - # With macOS 13.2, Apple implemented the Rapid Security Response System - # However Apple added a half baked snapshot reversion system if seal was broken, - # which forgets to handle Preboot BootKC syncing + """ + Installs RSRRepair - # Thus this application will try to re-sync the BootKC with SysKC in the event of a panic - # Reference: https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1019 + RSRRepair is a utility that will sync the SysKC and BootKC in the event of a panic - # This is a (hopefully) temporary work-around, however likely to stay. - # RSRRepair has the added bonus of fixing desynced KCs from 'bless', so useful in Big Sur+ - # https://github.com/flagersgit/RSRRepair + With macOS 13.2, Apple implemented the Rapid Security Response System + However Apple added a half baked snapshot reversion system if seal was broken, + which forgets to handle Preboot BootKC syncing. + + Thus this application will try to re-sync the BootKC with SysKC in the event of a panic + Reference: https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1019 + + This is a (hopefully) temporary work-around, however likely to stay. + RSRRepair has the added bonus of fixing desynced KCs from 'bless', so useful in Big Sur+ + Source: https://github.com/flagersgit/RSRRepair + + """ if self.constants.detected_os < os_data.os_data.big_sur: return diff --git a/resources/tui_helpers.py b/resources/tui_helpers.py deleted file mode 100644 index 4a520c4dd..000000000 --- a/resources/tui_helpers.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk -from resources import utilities - -class TUIMenu: - def __init__(self, title, prompt, options=None, return_number_instead_of_direct_call=False, add_quit=True, auto_number=False, in_between=None, top_level=False, loop=False): - self.title = title - self.prompt = prompt - self.in_between = in_between or [] - self.options = options or [] - self.return_number_instead_of_direct_call = return_number_instead_of_direct_call - self.auto_number = auto_number - self.add_quit = add_quit - self.top_level = top_level - self.loop = loop - self.added_quit = False - - def add_menu_option(self, name, description="", function=None, key=""): - self.options.append([key, name, description, function]) - - def start(self): - return_option = ["Q", "Quit"] if self.top_level else ["B", "Back"] - if self.add_quit and not self.added_quit: - self.add_menu_option(return_option[1], function=None, key=return_option[0]) - self.added_quit = True - - while True: - utilities.cls() - utilities.header(self.title) - print() - - for i in self.in_between: - print(i) - if self.in_between: - print() - - for index, option in enumerate(self.options): - if self.auto_number and not (index == (len(self.options) - 1) and self.add_quit): - option[0] = str((index + 1)) - print(option[0] + ". " + option[1]) - for i in option[2]: - print("\t" + i) - - print() - selected = input(self.prompt) - - keys = [option[0].upper() for option in self.options] - if not selected or selected.upper() not in keys: - if self.loop: - continue - else: - return - if self.add_quit and selected.upper() == return_option[0]: - return -1 - elif self.return_number_instead_of_direct_call: - return self.options[keys.index(selected.upper())][0] - else: - self.options[keys.index(selected.upper())][3]() if self.options[keys.index(selected.upper())][3] else None - if not self.loop: - return - - -class TUIOnlyPrint: - def __init__(self, title, prompt, in_between=None): - self.title = title - self.prompt = prompt - self.in_between = in_between or [] - - def start(self): - utilities.cls() - utilities.header(self.title) - print() - - for i in self.in_between: - print(i) - if self.in_between: - print() - - return input(self.prompt) \ No newline at end of file diff --git a/resources/updates.py b/resources/updates.py index 89807eb42..a532df438 100644 --- a/resources/updates.py +++ b/resources/updates.py @@ -5,21 +5,31 @@ import requests import logging -from resources import network_handler +from resources import network_handler, constants + +REPO_LATEST_RELEASE_URL: str = "https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest" -class check_binary_updates: - def __init__(self, constants): - self.constants = constants - self.binary_version = self.constants.patcher_version - self.binary_version_array = self.binary_version.split(".") - self.binary_version_array = [int(x) for x in self.binary_version_array] - self.binary_url = "https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest" +class CheckBinaryUpdates: + def __init__(self, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants - self.available_binaries = {} + self.binary_version = self.constants.patcher_version + self.binary_version_array = [int(x) for x in self.binary_version.split(".")] - def check_if_build_newer(self, remote_version=None, local_version=None): + def _check_if_build_newer(self, remote_version: list = None, local_version: list = None): + """ + Check if the remote version is newer than the local version + + Parameters: + remote_version (list): Remote version to compare against + local_version (list): Local version to compare against + + Returns: + bool: True if remote version is newer, False if not + """ + if remote_version is None: remote_version = self.remote_version_array if local_version is None: @@ -39,13 +49,32 @@ class check_binary_updates: return False - def determine_local_build_type(self): + + def _determine_local_build_type(self): + """ + Check if the local build is a GUI or TUI build + + Returns: + str: "GUI" or "TUI" + """ + if self.constants.wxpython_variant is True: return "GUI" else: return "TUI" - def determine_remote_type(self, remote_name): + + def _determine_remote_type(self, remote_name: str): + """ + Check if the remote build is a GUI or TUI build + + Parameters: + remote_name (str): Name of the remote build + + Returns: + str: "GUI" or "TUI" + """ + if "TUI" in remote_name: return "TUI" elif "GUI" in remote_name: @@ -53,45 +82,43 @@ class check_binary_updates: else: return "Unknown" + def check_binary_updates(self): - # logging.info("- Checking for updates...") - if network_handler.NetworkUtilities(self.binary_url).verify_network_connection(): - # logging.info("- Network connection functional") - response = requests.get(self.binary_url) - data_set = response.json() - # logging.info("- Retrieved latest version data") - self.remote_version = data_set["tag_name"] - # logging.info(f"- Latest version: {self.remote_version}") - self.remote_version_array = self.remote_version.split(".") - self.remote_version_array = [ - int(x) for x in self.remote_version_array - ] - if self.check_if_build_newer() is True: - # logging.info("- Remote version is newer") - for asset in data_set["assets"]: - logging.info(f"- Found asset: {asset['name']}") - if self.determine_remote_type(asset["name"]) == self.determine_local_build_type(): - # logging.info(f"- Found matching asset: {asset['name']}") - self.available_binaries.update({ - asset['name']: { - "Name": - asset["name"], - "Version": - self.remote_version, - "Link": - asset["browser_download_url"], - "Type": - self.determine_remote_type(asset["name"]), - "Github Link": - f"https://github.com/dortania/OpenCore-Legacy-Patcher/releases/{self.remote_version}" - } - }) - break - if self.available_binaries: - return self.available_binaries - else: - # logging.info("- No matching binaries available") - return None - # else: - # logging.info("- Failed to connect to GitHub API") + """ + Check if any updates are available for the OpenCore Legacy Patcher binary + + Returns: + dict: Dictionary with Link and Version of the latest binary update if available + """ + + available_binaries: list = {} + + if not network_handler.NetworkUtilities(REPO_LATEST_RELEASE_URL).verify_network_connection(): + return None + + response = requests.get(REPO_LATEST_RELEASE_URL) + data_set = response.json() + + self.remote_version = data_set["tag_name"] + + self.remote_version_array = self.remote_version.split(".") + self.remote_version_array = [int(x) for x in self.remote_version_array] + + if self._check_if_build_newer() is False: + return None + + for asset in data_set["assets"]: + logging.info(f"- Found asset: {asset['name']}") + if self._determine_remote_type(asset["name"]) == self._determine_local_build_type(): + available_binaries.update({ + asset['name']: { + "Name": asset["name"], + "Version": self.remote_version, + "Link": asset["browser_download_url"], + "Type": self._determine_remote_type(asset["name"]), + "Github Link": f"https://github.com/dortania/OpenCore-Legacy-Patcher/releases/{self.remote_version}" + } + }) + return available_binaries + return None \ No newline at end of file diff --git a/resources/utilities.py b/resources/utilities.py index 019324615..84d2abf7b 100644 --- a/resources/utilities.py +++ b/resources/utilities.py @@ -9,7 +9,6 @@ import os import binascii import argparse import atexit -import requests import shutil import py_sip_xnu diff --git a/resources/validation.py b/resources/validation.py index 5705f1009..709117972 100644 --- a/resources/validation.py +++ b/resources/validation.py @@ -1,51 +1,70 @@ +import logging import subprocess +from pathlib import Path + from resources.sys_patch import sys_patch_helpers from resources.build import build +from resources import constants from data import example_data, model_array, sys_patch_dict, os_data -from pathlib import Path -import logging -def validate(settings): - # Runs through ocvalidate to check for errors +class PatcherValidation: + """ + Validation class for the patcher - valid_dumps = [ - example_data.MacBookPro.MacBookPro92_Stock, - example_data.MacBookPro.MacBookPro111_Stock, - example_data.MacBookPro.MacBookPro133_Stock, - # example_data.MacBookPro.MacBookPro171_Stock, - example_data.Macmini.Macmini52_Stock, - example_data.Macmini.Macmini61_Stock, - example_data.Macmini.Macmini71_Stock, - # example_data.Macmini.Macmini91_Stock, - example_data.iMac.iMac81_Stock, - example_data.iMac.iMac112_Stock, - example_data.iMac.iMac122_Upgraded, - example_data.iMac.iMac122_Upgraded_Nvidia, - example_data.iMac.iMac151_Stock, - example_data.MacPro.MacPro31_Stock, - example_data.MacPro.MacPro31_Upgrade, - example_data.MacPro.MacPro31_Modern_AMD, - example_data.MacPro.MacPro31_Modern_Kepler, - example_data.MacPro.MacPro41_Upgrade, - example_data.MacPro.MacPro41_Modern_AMD, - example_data.MacPro.MacPro41_51__Flashed_Modern_AMD, - example_data.MacPro.MacPro41_51_Flashed_NVIDIA_WEB_DRIVERS, - ] + Primarily for Continuous Integration + """ - valid_dumps_native = [ - example_data.iMac.iMac201_Stock, - example_data.MacBookPro.MacBookPro141_SSD_Upgrade, - ] + def __init__(self, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants - settings.validate = True + self.constants.validate = True + + self.valid_dumps = [ + example_data.MacBookPro.MacBookPro92_Stock, + example_data.MacBookPro.MacBookPro111_Stock, + example_data.MacBookPro.MacBookPro133_Stock, + + example_data.Macmini.Macmini52_Stock, + example_data.Macmini.Macmini61_Stock, + example_data.Macmini.Macmini71_Stock, + + example_data.iMac.iMac81_Stock, + example_data.iMac.iMac112_Stock, + example_data.iMac.iMac122_Upgraded, + example_data.iMac.iMac122_Upgraded_Nvidia, + example_data.iMac.iMac151_Stock, + + example_data.MacPro.MacPro31_Stock, + example_data.MacPro.MacPro31_Upgrade, + example_data.MacPro.MacPro31_Modern_AMD, + example_data.MacPro.MacPro31_Modern_Kepler, + example_data.MacPro.MacPro41_Upgrade, + example_data.MacPro.MacPro41_Modern_AMD, + example_data.MacPro.MacPro41_51__Flashed_Modern_AMD, + example_data.MacPro.MacPro41_51_Flashed_NVIDIA_WEB_DRIVERS, + ] + + self.valid_dumps_native = [ + example_data.iMac.iMac201_Stock, + example_data.MacBookPro.MacBookPro141_SSD_Upgrade, + ] + + self._validate_configs() + self._validate_sys_patch() + + + def _build_prebuilt(self): + """ + Generate a build for each predefined model + Then validate against ocvalidate + """ - def build_prebuilt(): for model in model_array.SupportedSMBIOS: logging.info(f"Validating predefined model: {model}") - settings.custom_model = model - build.build_opencore(settings.custom_model, settings).build_opencore() - result = subprocess.run([settings.ocvalidate_path, f"{settings.opencore_release_folder}/EFI/OC/config.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + self.constants.custom_model = model + build.build_opencore(self.constants.custom_model, self.constants).build_opencore() + result = subprocess.run([self.constants.ocvalidate_path, f"{self.constants.opencore_release_folder}/EFI/OC/config.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.info("Error on build!") logging.info(result.stdout.decode()) @@ -53,24 +72,35 @@ def validate(settings): else: logging.info(f"Validation succeeded for predefined model: {model}") - def build_dumps(): - for model in valid_dumps: - settings.computer = model - settings.custom_model = "" - logging.info(f"Validating dumped model: {settings.computer.real_model}") - build.build_opencore(settings.computer.real_model, settings).build_opencore() - result = subprocess.run([settings.ocvalidate_path, f"{settings.opencore_release_folder}/EFI/OC/config.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + def _build_dumps(self): + """ + Generate a build for each predefined model + Then validate against ocvalidate + """ + + for model in self.valid_dumps: + self.constants.computer = model + self.constants.custom_model = "" + logging.info(f"Validating dumped model: {self.constants.computer.real_model}") + build.build_opencore(self.constants.computer.real_model, self.constants).build_opencore() + result = subprocess.run([self.constants.ocvalidate_path, f"{self.constants.opencore_release_folder}/EFI/OC/config.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.info("Error on build!") logging.info(result.stdout.decode()) - raise Exception(f"Validation failed for predefined model: {settings.computer.real_model}") + raise Exception(f"Validation failed for predefined model: {self.constants.computer.real_model}") else: - logging.info(f"Validation succeeded for predefined model: {settings.computer.real_model}") + logging.info(f"Validation succeeded for predefined model: {self.constants.computer.real_model}") - def validate_root_patch_files(major_kernel, minor_kernel): - patchset = sys_patch_dict.SystemPatchDictionary(major_kernel, minor_kernel, settings.legacy_accel_support) + def _validate_root_patch_files(self, major_kernel, minor_kernel): + """ + Validate that all files in the patchset are present in the payload + """ + + patchset = sys_patch_dict.SystemPatchDictionary(major_kernel, minor_kernel, self.constants.legacy_accel_support) host_os_float = float(f"{major_kernel}.{minor_kernel}") + for patch_subject in patchset: for patch_core in patchset[patch_subject]: patch_os_min_float = float(f'{patchset[patch_subject][patch_core]["OS Support"]["Minimum OS Support"]["OS Major"]}.{patchset[patch_subject][patch_core]["OS Support"]["Minimum OS Support"]["OS Minor"]}') @@ -81,58 +111,83 @@ def validate(settings): if install_type in patchset[patch_subject][patch_core]: for install_directory in patchset[patch_subject][patch_core][install_type]: for install_file in patchset[patch_subject][patch_core][install_type][install_directory]: - source_file = str(settings.payload_local_binaries_root_path) + "/" + patchset[patch_subject][patch_core][install_type][install_directory][install_file] + install_directory + "/" + install_file + source_file = str(self.constants.payload_local_binaries_root_path) + "/" + patchset[patch_subject][patch_core][install_type][install_directory][install_file] + install_directory + "/" + install_file if not Path(source_file).exists(): logging.info(f"File not found: {source_file}") raise Exception(f"Failed to find {source_file}") logging.info(f"- Validating against Darwin {major_kernel}.{minor_kernel}") - if not sys_patch_helpers.sys_patch_helpers(settings).generate_patchset_plist(patchset, f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist", None): + if not sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist", None): raise Exception("Failed to generate patchset plist") # Remove the plist file after validation - Path(settings.payload_path / f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist").unlink() + Path(self.constants.payload_path / f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist").unlink() - def validate_sys_patch(): - if Path(settings.payload_local_binaries_root_path_zip).exists(): + def _validate_sys_patch(self): + """ + Validates sys_patch modules + """ + + if Path(self.constants.payload_local_binaries_root_path_zip).exists(): logging.info("Validating Root Patch File integrity") - if not Path(settings.payload_local_binaries_root_path).exists(): - subprocess.run(["ditto", "-V", "-x", "-k", "--sequesterRsrc", "--rsrc", settings.payload_local_binaries_root_path_zip, settings.payload_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if not Path(self.constants.payload_local_binaries_root_path).exists(): + subprocess.run( + [ + "ditto", "-V", "-x", "-k", "--sequesterRsrc", "--rsrc", + self.constants.payload_local_binaries_root_path_zip, + self.constants.payload_path + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) for supported_os in [os_data.os_data.big_sur, os_data.os_data.monterey, os_data.os_data.ventura]: for i in range(0, 10): - validate_root_patch_files(supported_os, i) + self._validate_root_patch_files(supported_os, i) logging.info("Validating SNB Board ID patcher") - settings.computer.reported_board_id = "Mac-7BA5B2DFE22DDD8C" - sys_patch_helpers.sys_patch_helpers(settings).snb_board_id_patch(settings.payload_local_binaries_root_path) + self.constants.computer.reported_board_id = "Mac-7BA5B2DFE22DDD8C" + sys_patch_helpers.SysPatchHelpers(self.constants).snb_board_id_patch(self.constants.payload_local_binaries_root_path) + + # Clean up + subprocess.run( + [ + "rm", "-rf", self.constants.payload_local_binaries_root_path + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) else: logging.info("- Skipping Root Patch File integrity validation") - def validate_configs(): + def _validate_configs(self): + """ + Validates build modules + """ + # First run is with default settings - build_prebuilt() - build_dumps() + self._build_prebuilt() + self._build_dumps() + # Second run, flip all settings - settings.verbose_debug = True - settings.opencore_debug = True - settings.opencore_build = "DEBUG" - settings.kext_debug = True - settings.kext_variant = "DEBUG" - settings.kext_debug = True - settings.showpicker = False - settings.sip_status = False - settings.secure_status = True - settings.firewire_boot = True - settings.nvme_boot = True - settings.enable_wake_on_wlan = True - settings.disable_tb = True - settings.force_surplus = True - settings.software_demux = True - settings.serial_settings = "Minimal" - build_prebuilt() - build_dumps() + self.constants.verbose_debug = True + self.constants.opencore_debug = True + self.constants.opencore_build = "DEBUG" + self.constants.kext_debug = True + self.constants.kext_variant = "DEBUG" + self.constants.kext_debug = True + self.constants.showpicker = False + self.constants.sip_status = False + self.constants.secure_status = True + self.constants.firewire_boot = True + self.constants.nvme_boot = True + self.constants.enable_wake_on_wlan = True + self.constants.disable_tb = True + self.constants.force_surplus = True + self.constants.software_demux = True + self.constants.serial_settings = "Minimal" + self._build_prebuilt() + self._build_dumps() - validate_configs() - validate_sys_patch() \ No newline at end of file + subprocess.run(["rm", "-rf", self.constants.build_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) \ No newline at end of file