From dfdb5b4c68583709758028b1b6bc4440c0341535 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 6 Feb 2023 09:02:46 -0700 Subject: [PATCH 01/61] gui_main.py: Avoid nested logic --- resources/gui/gui_main.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/resources/gui/gui_main.py b/resources/gui/gui_main.py index 521727347..2ff53e6e2 100644 --- a/resources/gui/gui_main.py +++ b/resources/gui/gui_main.py @@ -2327,16 +2327,22 @@ class wx_python_gui: def install_installer_pkg(self, disk): disk = disk + "s2" # ESP sits at 1, and we know macOS will have created the main partition at 2 - if Path(self.constants.installer_pkg_path).exists(): - path = utilities.grab_mount_point_from_disk(disk) - if Path(path + "/System/Library/CoreServices/SystemVersion.plist").exists(): - os_version = plistlib.load(Path(path + "/System/Library/CoreServices/SystemVersion.plist").open("rb")) - kernel_version = os_data.os_conversion.os_to_kernel(os_version["ProductVersion"]) - if int(kernel_version) >= os_data.os_data.big_sur: - subprocess.run(["mkdir", "-p", f"{path}/Library/Packages/"]) - subprocess.run(["cp", "-r", self.constants.installer_pkg_path, f"{path}/Library/Packages/"]) - else: - logging.info("- Installer unsupported, requires Big Sur or newer") + + if not Path(self.constants.installer_pkg_path).exists(): + return + + path = utilities.grab_mount_point_from_disk(disk) + if not Path(path + "/System/Library/CoreServices/SystemVersion.plist").exists(): + return + + os_version = plistlib.load(Path(path + "/System/Library/CoreServices/SystemVersion.plist").open("rb")) + kernel_version = os_data.os_conversion.os_to_kernel(os_version["ProductVersion"]) + if int(kernel_version) < os_data.os_data.big_sur: + logging.info("- Installer unsupported, requires Big Sur or newer") + return + + subprocess.run(["mkdir", "-p", f"{path}/Library/Packages/"]) + subprocess.run(["cp", "-r", self.constants.installer_pkg_path, f"{path}/Library/Packages/"]) def settings_menu(self, event=None): From 6f4c110318e0b5116eae581e8c75f116ee8235f2 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 6 Feb 2023 09:05:43 -0700 Subject: [PATCH 02/61] gui_main.py: Adjust import formatting --- resources/gui/gui_main.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/resources/gui/gui_main.py b/resources/gui/gui_main.py index 2ff53e6e2..f0b668ece 100644 --- a/resources/gui/gui_main.py +++ b/resources/gui/gui_main.py @@ -18,8 +18,22 @@ import hashlib from datetime import datetime import py_sip_xnu import logging +import tempfile -from resources import constants, defaults, install, installer, utilities, run, generate_smbios, updates, integrity_verification, global_settings, kdk_handler, network_handler +from resources import ( + constants, + defaults, + install, + installer, + utilities, + run, + generate_smbios, + updates, + integrity_verification, + global_settings, + kdk_handler, + network_handler +) from resources.sys_patch import sys_patch_detect, sys_patch from resources.build import build from data import model_array, os_data, smbios_data, sip_data, cpu_data From 7fc9f3af7c13cfe12823b3cc9c5b2c19d4a5cb11 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 6 Feb 2023 14:03:25 -0700 Subject: [PATCH 03/61] CI: Use repository secrets for signing --- .github/workflows/build-app-wxpython.yml | 2 +- .github/workflows/build-app.yml | 6 ------ SOURCE.md | 23 ++++++----------------- 3 files changed, 7 insertions(+), 24 deletions(-) diff --git a/.github/workflows/build-app-wxpython.yml b/.github/workflows/build-app-wxpython.yml index 564cdca05..2a847de08 100644 --- a/.github/workflows/build-app-wxpython.yml +++ b/.github/workflows/build-app-wxpython.yml @@ -19,7 +19,7 @@ jobs: - 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 }}" - 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; ditto -c -k --sequesterRsrc --keepParent OpenCore-Patcher.app ../OpenCore-Patcher-wxPython.app.zip - - run: ./../sign-wxpython.sh + - run: xcrun altool --notarize-app --primary-bundle-id "com.dortania.opencore-legacy-patcher-wxpython" --username "${{ secrets.MAC_NOTARIZATION_USERNAME }}" --password "${{ secrets.MAC_NOTARIZATION_PASSWORD }}" --file OpenCore-Patcher-wxPython.app.zip - run: packagesbuild ./payloads/InstallPackage/AutoPkg-Assets-Setup.pkgproj - run: mv ./OpenCore-Patcher-wxPython.app.zip ./OpenCore-Patcher-GUI.app.zip - name: Upload App to Artifacts diff --git a/.github/workflows/build-app.yml b/.github/workflows/build-app.yml index e0202ed58..b6a9dca72 100644 --- a/.github/workflows/build-app.yml +++ b/.github/workflows/build-app.yml @@ -19,12 +19,6 @@ jobs: - 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 - - run: ./../sign-tui.sh - - name: Upload App to Artifacts - uses: actions/upload-artifact@v3 - with: - name: OpenCore-Patcher-TUI.app - path: OpenCore-Patcher-TUI.app.zip - name: Validate OpenCore run: ./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher --validate diff --git a/SOURCE.md b/SOURCE.md index 68ff35f36..88adc6b88 100644 --- a/SOURCE.md +++ b/SOURCE.md @@ -1,11 +1,10 @@ # Build and run from source -OpenCore Legacy Patcher at its core is a python-based TUI/CLI based application. This means that to run the project from source, you simply need to invoke the OpenCore-Patcher.command file via Python. +OpenCore Legacy Patcher at its core is a python-based GUI/CLI based application. This means that to run the project from source, you simply need to invoke the OpenCore-Patcher-GUI.command file via Python. For developers wishing to validate mainline changes, you may use these nightly links: * [GUI (Graphical Based App)](https://nightly.link/dortania/OpenCore-Legacy-Patcher/workflows/build-app-wxpython/main/OpenCore-Patcher.app%20%28GUI%29.zip) -* [TUI (Text Based App)](https://nightly.link/dortania/OpenCore-Legacy-Patcher/workflows/build-app/main/OpenCore-Patcher-TUI.app.zip) **Warning**: These binaries should not be used without first consulting the [CHANGELOG](./CHANGELOG.md). Do not distribute these links in forums, instead direct to this file. @@ -32,34 +31,24 @@ pip3 install -r requirements.txt If you have installation error, see following troubleshooting options: -* Use Python 3.9 - * Currently our build server uses py3.9 for generating binaries used in releases +* Use Python 3.10 + * Currently our build server uses py3.10 for generating binaries used in releases * Use .whl snapshots for installing additional dependencies - * [wxPython 4.1.1 wheel for py3.9](https://files.pythonhosted.org/packages/2c/a8/7027e8ca3ba20dc2ed2acd556e31941cb44097ab87d6f81d646a79de4eab/wxPython-4.1.1-cp39-cp39-macosx_10_10_x86_64.whl) - * [PyObjc 8.5 wheel for py3](https://files.pythonhosted.org/packages/69/3d/786f379dd669a078cf0c4a686e242c9b643071c23367bfbd3d9a7eb589ec/pyobjc-8.5-py3-none-any.whl) - * [Requests 2.27.1 for py2/py3](https://files.pythonhosted.org/packages/2d/61/08076519c80041bc0ffa1a8af0cbd3bf3e2b62af10435d269a9d0f40564d/requests-2.27.1-py2.py3-none-any.whl) - * [pyinstaller 5.3 for py3](https://files.pythonhosted.org/packages/65/70/625e86e5a45cb975a9c32a10a721394d10771275c69207308b80bc6a758e/pyinstaller-5.3-py3-none-macosx_10_13_universal2.whl) - ## Running OpenCore Legacy Patcher To run the project from source, simply invoke via python3: -```sh -# Launch TUI -python3 OpenCore-Patcher.command -``` - ```sh # Launch GUI python3 OpenCore-Patcher-GUI.command ``` -Note that the OpenCore-Patcher.command file can be run as both a TUI and a CLI utility for other programs to call. If no core arguments are passed, the TUI is initialized. Otherwise the CLI will start: +Note that the OpenCore-Patcher-GUI.command file can be run as both a GUI and a CLI utility for other programs to call. If no core arguments are passed, the GUI is initialized. Otherwise the CLI will start: ```sh # Launch CLI -python3 OpenCore-Patcher.command --build --model iMac12,2 --verbose +python3 OpenCore-Patcher-GUI.command --build --model iMac12,2 --verbose ``` See `-h`/`--help` for more information on supported CLI arguments. @@ -79,7 +68,7 @@ pip3 install pyinstaller cd ~/Developer/OpenCore-Legacy-Patcher/ # Create the pyinstaller based Application # Optional Arguments -# '--build_tui': Create TUI vairant +# '--build_tui': Create TUI vairant (deprecated) # '--reset_binaries': Redownload and generate support files python3 Build-Binary.command # Open build folder From d70daaf5a4adcadf0d7371dc49a6511aa86b52b9 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 6 Feb 2023 14:09:36 -0700 Subject: [PATCH 04/61] CI: Expose enviroment variables --- .github/workflows/build-app-wxpython.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-app-wxpython.yml b/.github/workflows/build-app-wxpython.yml index 2a847de08..d4e5ce45c 100644 --- a/.github/workflows/build-app-wxpython.yml +++ b/.github/workflows/build-app-wxpython.yml @@ -10,16 +10,19 @@ jobs: build: 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 }}" - 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; ditto -c -k --sequesterRsrc --keepParent OpenCore-Patcher.app ../OpenCore-Patcher-wxPython.app.zip - - run: xcrun altool --notarize-app --primary-bundle-id "com.dortania.opencore-legacy-patcher-wxpython" --username "${{ secrets.MAC_NOTARIZATION_USERNAME }}" --password "${{ secrets.MAC_NOTARIZATION_PASSWORD }}" --file OpenCore-Patcher-wxPython.app.zip + - run: xcrun altool --notarize-app --primary-bundle-id "com.dortania.opencore-legacy-patcher-wxpython" --username "${{ env.MAC_NOTARIZATION_USERNAME }}" --password env:MAC_NOTARIZATION_PASSWORD --file OpenCore-Patcher-wxPython.app.zip - run: packagesbuild ./payloads/InstallPackage/AutoPkg-Assets-Setup.pkgproj - run: mv ./OpenCore-Patcher-wxPython.app.zip ./OpenCore-Patcher-GUI.app.zip - name: Upload App to Artifacts From bd471df48f4f1775f79ef263beca0736c3337caf Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 6 Feb 2023 14:21:53 -0700 Subject: [PATCH 05/61] CI: Fix Password export --- .github/workflows/build-app-wxpython.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-app-wxpython.yml b/.github/workflows/build-app-wxpython.yml index d4e5ce45c..d6c3dc746 100644 --- a/.github/workflows/build-app-wxpython.yml +++ b/.github/workflows/build-app-wxpython.yml @@ -22,7 +22,7 @@ jobs: - 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 }}" - 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; ditto -c -k --sequesterRsrc --keepParent OpenCore-Patcher.app ../OpenCore-Patcher-wxPython.app.zip - - run: xcrun altool --notarize-app --primary-bundle-id "com.dortania.opencore-legacy-patcher-wxpython" --username "${{ env.MAC_NOTARIZATION_USERNAME }}" --password env:MAC_NOTARIZATION_PASSWORD --file OpenCore-Patcher-wxPython.app.zip + - run: xcrun altool --notarize-app --primary-bundle-id "com.dortania.opencore-legacy-patcher-wxpython" --username "${{ env.MAC_NOTARIZATION_USERNAME }}" --password "${{ env.MAC_NOTARIZATION_PASSWORD }}" --file OpenCore-Patcher-wxPython.app.zip - run: packagesbuild ./payloads/InstallPackage/AutoPkg-Assets-Setup.pkgproj - run: mv ./OpenCore-Patcher-wxPython.app.zip ./OpenCore-Patcher-GUI.app.zip - name: Upload App to Artifacts From 118d6352646b10d9dcbc33a0249373c28a6d5a1f Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 6 Feb 2023 14:33:52 -0700 Subject: [PATCH 06/61] CI: Fix bundle ID --- .github/workflows/build-app-wxpython.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-app-wxpython.yml b/.github/workflows/build-app-wxpython.yml index d6c3dc746..ba9d74b00 100644 --- a/.github/workflows/build-app-wxpython.yml +++ b/.github/workflows/build-app-wxpython.yml @@ -22,7 +22,7 @@ jobs: - 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 }}" - 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; ditto -c -k --sequesterRsrc --keepParent OpenCore-Patcher.app ../OpenCore-Patcher-wxPython.app.zip - - run: xcrun altool --notarize-app --primary-bundle-id "com.dortania.opencore-legacy-patcher-wxpython" --username "${{ env.MAC_NOTARIZATION_USERNAME }}" --password "${{ env.MAC_NOTARIZATION_PASSWORD }}" --file OpenCore-Patcher-wxPython.app.zip + - run: xcrun altool --notarize-app --primary-bundle-id "com.dortania.opencore-legacy-patcher" --username "${{ env.MAC_NOTARIZATION_USERNAME }}" --password "${{ env.MAC_NOTARIZATION_PASSWORD }}" --file OpenCore-Patcher-wxPython.app.zip - run: packagesbuild ./payloads/InstallPackage/AutoPkg-Assets-Setup.pkgproj - run: mv ./OpenCore-Patcher-wxPython.app.zip ./OpenCore-Patcher-GUI.app.zip - name: Upload App to Artifacts From 68af20d2fa5be4e96294dc9b4b2e2b5264d843ef Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 6 Feb 2023 20:10:39 -0700 Subject: [PATCH 07/61] kdk_handler.py: Implement additional OS check --- resources/kdk_handler.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 09a551393..2f4d13737 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -15,6 +15,7 @@ import logging from resources import utilities, network_handler from resources.constants import Constants +from data.os_data import os_data KDK_INSTALL_PATH = "/Library/Developer/KDKs" @@ -121,7 +122,13 @@ class KernelDebugKitObject: host_build = self.host_build host_version = self.host_version - logging.info(f"- Fetching latest KDK for {host_build} ({host_version})") + parsed_version = cast(packaging.version.Version, packaging.version.parse(host_version)) + + if parsed_version.major < os_data.ventura: + self.error_msg = "KDKs are not required for macOS Monterey or older" + logging.warning(f"- {self.error_msg}") + return + self.kdk_installed_path = self._local_kdk_installed_build() if self.kdk_installed_path: logging.info(f"- KDK already installed ({Path(self.kdk_installed_path).name}), skipping") @@ -131,8 +138,6 @@ class KernelDebugKitObject: remote_kdk_version = self._get_available_kdks() - parsed_version = cast(packaging.version.Version, packaging.version.parse(host_version)) - if remote_kdk_version is None: logging.warning("- Failed to fetch KDK list, falling back to local KDK matching") From fe8a2d253a247eb399b5844491815814eac26f7b Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Mon, 6 Feb 2023 21:28:28 -0700 Subject: [PATCH 08/61] gui_main.py: Add support for KDK chainloading --- CHANGELOG.md | 4 + .../AutoPkgInstaller-v1.0.1-DEBUG.zip | Bin 12293 -> 0 bytes .../AutoPkgInstaller-v1.0.1-RELEASE.zip | Bin 10955 -> 0 bytes .../AutoPkgInstaller-v1.0.2-DEBUG.zip | Bin 0 -> 12321 bytes .../AutoPkgInstaller-v1.0.2-RELEASE.zip | Bin 0 -> 11026 bytes resources/constants.py | 2 +- resources/gui/gui_main.py | 118 +++++++++++++++--- 7 files changed, 109 insertions(+), 15 deletions(-) delete mode 100644 payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.1-DEBUG.zip delete mode 100644 payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.1-RELEASE.zip create mode 100644 payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.2-DEBUG.zip create mode 100644 payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.2-RELEASE.zip diff --git a/CHANGELOG.md b/CHANGELOG.md index fd1fee682..f488f3e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ - Ensure `Moraea_BlurBeta` is set on non-Metal systems - Implement proper Root Unpatching verification in GUI - Removes arbitrary patch requirements used against unpatching (ex. network connection) +- Implement Kernel Debug Kit installation during OS installs + - Avoids network requirement for first time installs + - Paired along side AutoPkgInstaller - Backend Changes: - Refactored kdk_handler.py - Prioritizes KdkSupportPkg repository for downloads @@ -30,6 +33,7 @@ - packaging - 23.0 - Increment Binaries: - PatcherSupportPkg 0.8.3 - release + - AutoPkgInstaller 1.0.2 - release ## 0.6.1 - Avoid usage of KDKlessWorkaround on hardware not requiring it diff --git a/payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.1-DEBUG.zip b/payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.1-DEBUG.zip deleted file mode 100644 index fbe4203283e0da6db67169db56e23069ec501440..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12293 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 Ds4D1! diff --git a/payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.1-RELEASE.zip b/payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.1-RELEASE.zip deleted file mode 100644 index c5785d44cbc1ce5af4773c10671e22411956b413..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10955 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 diff --git a/payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.2-DEBUG.zip b/payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.2-DEBUG.zip new file mode 100644 index 0000000000000000000000000000000000000000..8064daa351f5bfbf1450f2b7f528de612ab16505 GIT binary patch literal 12321 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

1<#0uXvxt05KT0Z$6aO_Fo|ob;7#6~`B`1WT zU|)MfhytM^HYFkoww@FxVJILOH~4616bLwX8FOeU3QE@gglGja9NM)Qe5+s9yw8S& z*C(l!kkxy71rT6m%SI)59c0e^~0Wo%V+N@rxnrg7RZhmjD6fBnhlZ( zLExCei zvaNZo4>XkED3NmRQvO#HSn&C34FL_TgdDS~Z(MrUmBl5t>OZ{cpK*Q_`eXUwB442f z@hnWPGo}YGgIR~9D<n32;7B`>r~cAuRCttimI)}5K;tR9z=TK2 zobq5U_$kq)0uS~VO}ymymPDH)$&1xNa!fQIzLE3aw|zlNSXx}4@R6PUv*;Zf9)=c$ zqNb)A07*#?GjsK8T5PBt!C?i-jhr**SpE_$;f_b{$!%&U+#SSnWI(MulCAnG&FsOC z#LLYch$vy~tFk`d-hLR<^TG^ z_axRhYcPP}F3V~&>uMdVp1X3zyA<6*wsLk=?P2+%`rAP2@4L!G&x`8YcfP^sYNbF6 zqD6(`^n8LUS!tD@Hsswr!^j0nMFebuo`R{5Omv5y!wRgSM3lFLN?OaF3-rnNLX>&$ z+6{RL2xK;Kx4yQ0XUjv#>uls`R^@P9zr+tmdRk`oH4~bdaF4D zrv?36RkrVb+d42DjO;U}kVJ3QW#Bw(;toJ3wBaGlwKjT{cS`(7&Z{O3QU&b$fD#F3 zux&E(Np`HD+xz_BkuYc7S-j{S`plO+x82A!QI1JCT$Q35yx+BDA6War}_TT1p2?0yjT`63QAW^2 z?xawuJ*RPrEv5jo^816gjX1sIRc^}ysbBiQBAVIP~k+;O6rrvrQ7Ek~v6R&N!}TZhN}q27C!` z2UdJrWl6;+rWIR!1;?J+feG3gWC!#+eDBmff3EmNsA%n*JsJB1w^L12^XJP2jx3V# z;l~2X^Eh0&l$FjCx%2@~Pm?RTOTJ_ebl!-0R}PX^OBLxnFI-5k08||AE7WJ41>3+G9)v2Qs8IsJ7t5TaRjg!fAd9RVW`L2NqEw9<^w;DMdEJfl|}Sx)am%L)~CG(dv#}TJOL&$yc8% zJGJyHoI?Fta%j}YCOv`{r@89#ask9&M$bG?2|jAq>)HlAt0PX;1ap~mEr%OMMnFvu zNE6Qtn9e1r&P^2)yOvl$G6^|4g=$Ap85YqzQ0a7ulynp{|6)A-$tJQ>Vx{- z(DOfO{ELZ2j!k~$f%+E{)Rmj;XY9&cUHx&=`|o%Mm8M_g>=*$1B z*GyCOuNht0x_-v4j56k5G5V)P=I@L#Kf(Tt+LZ(6XY59lpuzr0?ay`m_f20vVXxBv zTFh7XSN^J>u`5%A_B;GJ+CMq|R?)w!-#;1J|Af3^dIkCGAzt~de#UMDF81Ft{U`Rn z>Aw0E{K~5LtCQnbnTsL_ul+6Gzp!ll4d*M^UpZgdM1Dq5#3S7Q3Fp6ff^b8GzZ5q=fns&W2|-H3Z^|Go(S+cv7HigW!6kpTUwMW3}A I`_ Date: Tue, 7 Feb 2023 09:04:49 -0700 Subject: [PATCH 09/61] data: remove unused dylib_data.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit File was superseded with sys_patch_dict.py’s introduction --- data/dylib_data.py | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 data/dylib_data.py diff --git a/data/dylib_data.py b/data/dylib_data.py deleted file mode 100644 index c5949f019..000000000 --- a/data/dylib_data.py +++ /dev/null @@ -1,14 +0,0 @@ -# Data for SkyLightShim Plugin systems -class shim_list: - shim_pathing = { - "CoreWLAN.dylib": "/System/Library/CoreServices/WiFiAgent.app/Contents/MacOS/WiFiAgent", - "BacklightFixup.dylib": "/System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow", - } - - shim_legacy_accel = [ - "CoreWLAN.dylib", - ] - - shim_legacy_accel_keyboard = [ - "BacklightFixup.dylib", - ] \ No newline at end of file From 6629e9dcef62df5ca9cfc02bd301919c12370f50 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Tue, 7 Feb 2023 14:25:13 -0700 Subject: [PATCH 10/61] kdk_handler.py: Merge similar functions --- resources/kdk_handler.py | 60 ++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 2f4d13737..6c2c6b990 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -76,7 +76,7 @@ class KernelDebugKitObject: self._get_latest_kdk() - def _get_available_kdks(self): + def _get_remote_kdks(self): """ Fetches a list of available KDKs from the KdkSupportPkg API @@ -129,14 +129,14 @@ class KernelDebugKitObject: logging.warning(f"- {self.error_msg}") return - self.kdk_installed_path = self._local_kdk_installed_build() + self.kdk_installed_path = self._local_kdk_installed() if self.kdk_installed_path: logging.info(f"- KDK already installed ({Path(self.kdk_installed_path).name}), skipping") self.kdk_already_installed = True self.success = True return - remote_kdk_version = self._get_available_kdks() + remote_kdk_version = self._get_remote_kdks() if remote_kdk_version is None: logging.warning("- Failed to fetch KDK list, falling back to local KDK matching") @@ -145,7 +145,7 @@ class KernelDebugKitObject: # ex. 13.0.1 vs 13.0 loose_version = f"{parsed_version.major}.{parsed_version.minor}" logging.info(f"- Checking for KDKs loosely matching {loose_version}") - self.kdk_installed_path = self._local_kdk_installed_version(loose_version) + self.kdk_installed_path = self._local_kdk_installed(match=loose_version, check_version=True) if self.kdk_installed_path: logging.info(f"- Found matching KDK: {Path(self.kdk_installed_path).name}") self.success = True @@ -153,7 +153,7 @@ class KernelDebugKitObject: older_version = f"{parsed_version.major}.{parsed_version.minor - 1 if parsed_version.minor > 0 else 0}" logging.info(f"- Checking for KDKs matching {older_version}") - self.kdk_installed_path = self._local_kdk_installed_version(older_version) + self.kdk_installed_path = self._local_kdk_installed(match=older_version, check_version=True) if self.kdk_installed_path: logging.info(f"- Found matching KDK: {Path(self.kdk_installed_path).name}") self.success = True @@ -200,7 +200,7 @@ class KernelDebugKitObject: # Check if this KDK is already installed - self.kdk_installed_path = self._local_kdk_installed_build(self.kdk_url_build) + self.kdk_installed_path = self._local_kdk_installed(match=self.kdk_url_build) if self.kdk_installed_path: logging.info(f"- KDK already installed ({Path(self.kdk_installed_path).name}), skipping") self.kdk_already_installed = True @@ -276,39 +276,14 @@ class KernelDebugKitObject: return True - def _local_kdk_installed_build(self, build: str = None): + def _local_kdk_installed(self, match: str = None, check_version: bool = False): """ Checks if KDK matching build is installed If so, validates it has not been corrupted - Returns: - str: Path to KDK if valid, None if not - """ - - if self.ignore_installed is True: - return None - - if build is None: - build = self.host_build - - if not Path(KDK_INSTALL_PATH).exists(): - return None - - for kdk_folder in Path(KDK_INSTALL_PATH).iterdir(): - if not kdk_folder.is_dir(): - continue - if not kdk_folder.name.endswith(f"{build}.kdk"): - continue - - if self._local_kdk_valid(kdk_folder): - return kdk_folder - - return None - - def _local_kdk_installed_version(self, version: str = None): - """ - Checks if KDK matching version is installed - If so, validates it has not been corrupted + Parameters: + match (str): string to match against (ex. build or version) + check_version (bool): If True, match against version, otherwise match against build Returns: str: Path to KDK if valid, None if not @@ -317,8 +292,11 @@ class KernelDebugKitObject: if self.ignore_installed is True: return None - if version is None: - version = self.host_version + if match is None: + if check_version: + match = self.host_version + else: + match = self.host_build if not Path(KDK_INSTALL_PATH).exists(): return None @@ -326,8 +304,12 @@ class KernelDebugKitObject: for kdk_folder in Path(KDK_INSTALL_PATH).iterdir(): if not kdk_folder.is_dir(): continue - if version not in kdk_folder.name: - continue + if check_version: + if match not in kdk_folder.name: + continue + else: + if not kdk_folder.name.endswith(f"{match}.kdk"): + continue if self._local_kdk_valid(kdk_folder): return kdk_folder From 2eb98b632700560738abbec61ac2a85d135dd684 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Tue, 7 Feb 2023 14:33:13 -0700 Subject: [PATCH 11/61] kdk_handler.py: Fix OS comparison check --- resources/kdk_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 6c2c6b990..75831289b 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -15,7 +15,7 @@ import logging from resources import utilities, network_handler from resources.constants import Constants -from data.os_data import os_data +from data import os_data KDK_INSTALL_PATH = "/Library/Developer/KDKs" @@ -124,7 +124,7 @@ class KernelDebugKitObject: parsed_version = cast(packaging.version.Version, packaging.version.parse(host_version)) - if parsed_version.major < os_data.ventura: + if os_data.os_conversion.os_to_kernel(str(parsed_version.major)) < os_data.os_data.ventura: self.error_msg = "KDKs are not required for macOS Monterey or older" logging.warning(f"- {self.error_msg}") return From b5c613242f292aa1a27e442ea33e61e68a019788 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Tue, 7 Feb 2023 17:29:29 -0700 Subject: [PATCH 12/61] kdk_handler.py: Write KDK info to plist --- resources/kdk_handler.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 75831289b..57601498b 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -4,6 +4,7 @@ import datetime from pathlib import Path from typing import cast +import plistlib import packaging.version import requests @@ -18,6 +19,7 @@ from resources.constants import Constants from data import os_data KDK_INSTALL_PATH = "/Library/Developer/KDKs" +KDK_INFO_PLIST = "KDKInfo.plist" class KernelDebugKitObject: @@ -241,7 +243,33 @@ class KernelDebugKitObject: logging.info(f"- Returning DownloadObject for KDK: {Path(self.kdk_url).name}") self.success = True - return network_handler.DownloadObject(self.kdk_url, self.constants.kdk_download_path if override_path == "" else Path(override_path)) + + kdk_download_path = self.constants.kdk_download_path if override_path == "" else Path(override_path) + kdk_plist_path = Path(f"{kdk_download_path}/{KDK_INFO_PLIST}") if override_path == "" else Path(f"{Path(override_path).parent}/{KDK_INFO_PLIST}") + + self._generate_kdk_info_plist(kdk_plist_path) + return network_handler.DownloadObject(self.kdk_url, kdk_download_path) + + + def _generate_kdk_info_plist(self, plist_path: str): + """ + Generates a KDK Info.plist + + """ + + plist_path = Path(plist_path) + if plist_path.exists(): + plist_path.unlink() + + kdk_dict = { + "Build": self.kdk_url_build, + "Version": self.kdk_url_version, + } + + try: + plistlib.dump(kdk_dict, plist_path.open("wb"), sort_keys=False) + except Exception as e: + logging.error(f"- Failed to generate KDK Info.plist: {e}") def _local_kdk_valid(self, kdk_path: str): From ea35eaca2e7751033f94b3f9855e27cf4ee563ba Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Tue, 7 Feb 2023 22:38:02 -0700 Subject: [PATCH 13/61] gui_main.py: Ensure passed values are integers --- resources/gui/gui_main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/gui/gui_main.py b/resources/gui/gui_main.py index a3472f18a..c53294fdd 100644 --- a/resources/gui/gui_main.py +++ b/resources/gui/gui_main.py @@ -1328,7 +1328,7 @@ class wx_python_gui: ) self.developer_note.Centre(wx.HORIZONTAL) - self.progress_bar.SetValue(kdk_download_obj.get_percent()) + self.progress_bar.SetValue(int(kdk_download_obj.get_percent())) wx.GetApp().Yield() time.sleep(0.1) @@ -1847,7 +1847,7 @@ class wx_python_gui: ) self.download_label_2.Centre(wx.HORIZONTAL) - self.download_progress.SetValue(ia_download.get_percent()) + self.download_progress.SetValue(int(ia_download.get_percent())) wx.GetApp().Yield() time.sleep(0.1) From 7b33e7794777ed839830c431de7f05be16d08a96 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 10:34:45 -0700 Subject: [PATCH 14/61] kdk_handler.py: Cache API result --- resources/kdk_handler.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 57601498b..30f5bbbc0 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -18,8 +18,11 @@ from resources import utilities, network_handler from resources.constants import Constants from data import os_data -KDK_INSTALL_PATH = "/Library/Developer/KDKs" -KDK_INFO_PLIST = "KDKInfo.plist" +KDK_INSTALL_PATH: str = "/Library/Developer/KDKs" +KDK_INFO_PLIST: str = "KDKInfo.plist" +KDK_API_LINK: str = "https://raw.githubusercontent.com/dortania/KdkSupportPkg/gh-pages/manifest.json" + +KDK_ASSET_LIST: list = None class KernelDebugKitObject: @@ -81,14 +84,17 @@ class KernelDebugKitObject: def _get_remote_kdks(self): """ Fetches a list of available KDKs from the KdkSupportPkg API + Additionally caches the list for future use, avoiding extra API calls Returns: list: A list of KDKs, sorted by version and date if available. Returns None if the API is unreachable """ - KDK_API_LINK = "https://raw.githubusercontent.com/dortania/KdkSupportPkg/gh-pages/manifest.json" + global KDK_ASSET_LIST logging.info("- Pulling KDK list from KdkSupportPkg API") + if KDK_ASSET_LIST: + return KDK_ASSET_LIST try: results = network_handler.SESSION.get( @@ -96,7 +102,7 @@ class KernelDebugKitObject: headers={ "User-Agent": f"OCLP/{self.constants.patcher_version}" }, - timeout=10 + timeout=5 ) except (requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.ConnectionError): logging.info("- Could not contact KDK API") @@ -106,7 +112,9 @@ class KernelDebugKitObject: logging.info("- Could not fetch KDK list") return None - return sorted(results.json(), key=lambda x: (packaging.version.parse(x["version"]), datetime.datetime.fromisoformat(x["date"])), reverse=True) + KDK_ASSET_LIST = sorted(results.json(), key=lambda x: (packaging.version.parse(x["version"]), datetime.datetime.fromisoformat(x["date"])), reverse=True) + + return KDK_ASSET_LIST def _get_latest_kdk(self, host_build: str = None, host_version: str = None): From b3ed101ad9087d67f5ce1a9293790027eba5679a Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 11:07:17 -0700 Subject: [PATCH 15/61] kdk_handler.py: Add support for KDK installation --- resources/kdk_handler.py | 141 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 1 deletion(-) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 30f5bbbc0..3a45dfe8c 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -4,6 +4,7 @@ import datetime from pathlib import Path from typing import cast +import tempfile import plistlib import packaging.version @@ -350,6 +351,25 @@ class KernelDebugKitObject: if self._local_kdk_valid(kdk_folder): return kdk_folder + # If we can't find a KDK, next check if there's a backup present + # Check for KDK packages in the same directory as the KDK + for kdk_pkg in Path(KDK_INSTALL_PATH).iterdir(): + if kdk_pkg.is_dir(): + continue + if not kdk_pkg.name.endswith(".pkg"): + continue + if check_version: + if match not in kdk_pkg.name: + continue + else: + if not kdk_pkg.name.endswith(f"{match}.pkg"): + continue + + logging.info(f"- Found KDK backup, restoring: {kdk_pkg.name}") + if KernelDebugKitUtilities().install_kdk_pkg(kdk_pkg): + logging.info("- Successfully restored KDK") + return self._local_kdk_installed(match=match, check_version=check_version) + return None @@ -382,7 +402,6 @@ class KernelDebugKitObject: If None, defaults to host and closest match builds. """ - if exclude_builds is None: exclude_builds = [ self.kdk_url_build, @@ -443,3 +462,123 @@ class KernelDebugKitObject: self._remove_unused_kdks() self.success = True + + +class KernelDebugKitUtilities: + """ + Utilities for KDK handling + + """ + + def __init__(self): + pass + + + def install_kdk_pkg(self, kdk_path: Path): + """ + Installs provided KDK packages + + Parameters: + kdk_path (Path): Path to KDK package + + Returns: + bool: True if successful, False if not + """ + + if os.getuid() != 0: + logging.warning("- Cannot install KDK, not running as root") + return False + + result = utilities.elevated(["installer", "-pkg", kdk_path, "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if result.returncode != 0: + logging.info("- Failed to install KDK:") + logging.info(result.stdout.decode('utf-8')) + if result.stderr: + logging.info(result.stderr.decode('utf-8')) + return False + + return True + + + def install_kdk_dmg(self, kdk_path: Path): + """ + Installs provided KDK disk image + + Parameters: + kdk_path (Path): Path to KDK disk image + + Returns: + bool: True if successful, False if not + """ + + if os.getuid() != 0: + logging.warning("- Cannot install KDK, not running as root") + return False + + logging.info(f"- Installing downloaded KDK (this may take a while)") + with tempfile.TemporaryDirectory() as mount_point: + result = subprocess.run(["hdiutil", "attach", kdk_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if result.returncode != 0: + logging.info("- Failed to mount KDK:") + logging.info(result.stdout.decode('utf-8')) + return False + + kdk_pkg_path = Path(f"{mount_point}/KernelDebugKit.pkg") + + if not kdk_pkg_path.exists(): + logging.warning("- Failed to find KDK package in DMG, likely corrupted!!!") + return False + + if self.install_kdk_pkg(kdk_pkg_path) is False: + return False + + self._create_backup(kdk_pkg_path, Path(f"{kdk_path.parent}/{KDK_INFO_PLIST}")) + + result = subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if result.returncode != 0: + # Non-fatal error + logging.info("- Failed to unmount KDK:") + logging.info(result.stdout.decode('utf-8')) + + logging.info("- Successfully installed KDK") + return True + + + def _create_backup(self, kdk_path: Path, kdk_info_plist: Path): + """ + Creates a backup of the KDK + + Parameters: + kdk_path (Path): Path to KDK + kdk_info_plist (Path): Path to KDK Info.plist + """ + + if not kdk_path.exists(): + logging.warning("- KDK does not exist, cannot create backup") + return + if not kdk_info_plist.exists(): + logging.warning("- KDK Info.plist does not exist, cannot create backup") + return + + kdk_info_dict = plistlib.load(kdk_info_plist.open("rb")) + logging.info("- Creating backup of KDK") + + if 'version' not in kdk_info_dict or 'build' not in kdk_info_dict: + logging.warning("- Malformed KDK Info.plist provided, cannot create backup") + return + + if os.getuid() != 0: + logging.warning("- Cannot create KDK backup, not running as root") + return + + kdk_dst_name = f"KDK_{kdk_info_dict['version']}_{kdk_info_dict['build']}.pkg" + kdk_dst_path = Path(f"{KDK_INSTALL_PATH}/{kdk_dst_name}") + + if kdk_dst_path.exists(): + logging.info("- Backup already exists, skipping") + return + + result = utilities.elevated(["cp", "-R", kdk_path, kdk_dst_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if result.returncode != 0: + logging.info("- Failed to create KDK backup:") + logging.info(result.stdout.decode('utf-8')) \ No newline at end of file From 2a91b2a11c302f03eb7b42722305561342ebe92c Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 11:15:57 -0700 Subject: [PATCH 16/61] kdk_handler.py: Support passive mode --- resources/kdk_handler.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 3a45dfe8c..4ce9ace24 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -50,12 +50,14 @@ class KernelDebugKitObject: """ - def __init__(self, constants: Constants, host_build: str, host_version: str, ignore_installed: bool = False): + def __init__(self, constants: Constants, host_build: str, host_version: str, ignore_installed: bool = False, passive: bool = False): self.constants: Constants = constants self.host_build: str = host_build # ex. 20A5384c self.host_version: str = host_version # ex. 11.0.1 + self.passive: bool = passive # Don't perform actions requiring elevated privileges + self.ignore_installed: bool = ignore_installed # If True, will ignore any installed KDKs and download the latest self.kdk_already_installed: bool = False @@ -365,10 +367,16 @@ class KernelDebugKitObject: if not kdk_pkg.name.endswith(f"{match}.pkg"): continue - logging.info(f"- Found KDK backup, restoring: {kdk_pkg.name}") - if KernelDebugKitUtilities().install_kdk_pkg(kdk_pkg): - logging.info("- Successfully restored KDK") - return self._local_kdk_installed(match=match, check_version=check_version) + logging.info(f"- Found KDK backup: {kdk_pkg.name}") + if self.passive is False: + logging.info("- Attempting KDK restoration") + if KernelDebugKitUtilities().install_kdk_pkg(kdk_pkg): + logging.info("- Successfully restored KDK") + return self._local_kdk_installed(match=match, check_version=check_version) + else: + # When in passive mode, we're just checking if a KDK could be restored + logging.info("- KDK restoration skipped, running in passive mode") + return kdk_pkg return None @@ -381,6 +389,9 @@ class KernelDebugKitObject: kdk_path (str): Path to KDK """ + if self.passive is True: + return + if os.getuid() != 0: logging.warning("- Cannot remove KDK, not running as root") return @@ -402,6 +413,9 @@ class KernelDebugKitObject: If None, defaults to host and closest match builds. """ + if self.passive is True: + return + if exclude_builds is None: exclude_builds = [ self.kdk_url_build, From dd1afd77e4feb4d4a5eda834e339ea4e61ff5aac Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 11:19:19 -0700 Subject: [PATCH 17/61] sys_patch_detect.py: Switch to kdk_handler for KDK detection --- resources/sys_patch/sys_patch_detect.py | 4 +-- resources/sys_patch/sys_patch_helpers.py | 45 +----------------------- 2 files changed, 3 insertions(+), 46 deletions(-) diff --git a/resources/sys_patch/sys_patch_detect.py b/resources/sys_patch/sys_patch_detect.py index 02004513e..c8886d752 100644 --- a/resources/sys_patch/sys_patch_detect.py +++ b/resources/sys_patch/sys_patch_detect.py @@ -3,7 +3,7 @@ # 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 +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 @@ -337,7 +337,7 @@ class detect_root_patch: return utilities.check_kext_loaded("WhateverGreen", self.constants.detected_os) def check_kdk(self): - if sys_patch_helpers.sys_patch_helpers(self.constants).determine_kdk_present() is None: + if kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version, passive=True).kdk_already_installed == "": return False return True diff --git a/resources/sys_patch/sys_patch_helpers.py b/resources/sys_patch/sys_patch_helpers.py index d2e391f25..a987172d2 100644 --- a/resources/sys_patch/sys_patch_helpers.py +++ b/resources/sys_patch/sys_patch_helpers.py @@ -11,7 +11,7 @@ import plistlib import os import logging -from resources import constants, bplist +from resources import kdk_handler, bplist class sys_patch_helpers: @@ -104,49 +104,6 @@ class sys_patch_helpers: logging.info("- Successfully installed KDK") - def determine_kdk_present(self, match_closest=False, override_build=None): - # Check if KDK is present - # If 'match_closest' is True, will provide the closest match to the reported KDK - - kdk_array = [] - - search_build = self.constants.detected_os_build - if override_build: - search_build = override_build - - if not Path("/Library/Developer/KDKs").exists(): - return None - - - for kdk_folder in Path("/Library/Developer/KDKs").iterdir(): - if not kdk_folder.name.endswith(".kdk"): - continue - - # Ensure direct match - if kdk_folder.name.endswith(f"{search_build}.kdk"): - # Verify that the KDK is valid - if (kdk_folder / Path("System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern")).exists(): - return kdk_folder - if match_closest is True: - # ex: KDK_13.0_22A5266r.kdk -> 22A5266r.kdk -> 22A5266r - try: - build = kdk_folder.name.split("_")[2].split(".")[0] - # Don't append if Darwin Major is different - if build.startswith(str(self.constants.detected_os)): - kdk_array.append(build) - except IndexError: - pass - - if match_closest is True: - result = os_data.os_conversion.find_largest_build(kdk_array) - logging.info(f"- Closest KDK match to {search_build}: {result}") - for kdk_folder in Path("/Library/Developer/KDKs").iterdir(): - if kdk_folder.name.endswith(f"{result}.kdk"): - # Verify that the KDK is valid - if (kdk_folder / Path("System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern")).exists(): - return kdk_folder - return None - def disable_window_server_caching(self): # On legacy GCN GPUs, the WindowServer cache generated creates From 4c4cacf114ff6b247f2c1261a62aadc9a941458e Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 11:22:25 -0700 Subject: [PATCH 18/61] gui_main.py: Add indicator for longer kdk_handler init --- resources/gui/gui_main.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/resources/gui/gui_main.py b/resources/gui/gui_main.py index c53294fdd..5cc273b4e 100644 --- a/resources/gui/gui_main.py +++ b/resources/gui/gui_main.py @@ -1287,24 +1287,36 @@ class wx_python_gui: self.pulse_alternative(self.progress_bar) wx.GetApp().Yield() - self.progress_bar.Hide() - if self.patches["Settings: Kernel Debug Kit missing"] is True: # Download KDK (if needed) self.subheader.SetLabel("Downloading Kernel Debug Kit") self.subheader.Centre(wx.HORIZONTAL) self.developer_note.SetLabel("Starting shortly") + wx.GetApp().Yield() + kdk_result = False - kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version) - if kdk_obj.success is True: - kdk_download_obj = kdk_obj.retrieve_download() + self.kdk_obj = None + def kdk_thread_spawn(): + self.kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version) + + kdk_thread = threading.Thread(target=kdk_thread_spawn) + kdk_thread.start() + + while kdk_thread.is_alive(): + self.pulse_alternative(self.progress_bar) + wx.GetApp().Yield() + + self.progress_bar.Hide() + + if self.kdk_obj.success is True: + kdk_download_obj = self.kdk_obj.retrieve_download() if not kdk_download_obj: kdk_result = True else: kdk_download_obj.download() - self.header.SetLabel(f"Downloading KDK Build: {kdk_obj.kdk_url_build}") + self.header.SetLabel(f"Downloading KDK Build: {self.kdk_obj.kdk_url_build}") self.header.Centre(wx.HORIZONTAL) self.progress_bar.SetValue(0) From 0d38bc0edf668290645d9cd8531399cb4db386c2 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 12:23:25 -0700 Subject: [PATCH 19/61] kdk_handler.py: Add extra error handling to failed KDK install --- resources/gui/gui_main.py | 8 +-- resources/kdk_handler.py | 21 ++++-- resources/sys_patch/sys_patch.py | 87 ++++++++++++------------ resources/sys_patch/sys_patch_detect.py | 4 +- resources/sys_patch/sys_patch_helpers.py | 27 -------- 5 files changed, 62 insertions(+), 85 deletions(-) diff --git a/resources/gui/gui_main.py b/resources/gui/gui_main.py index 5cc273b4e..9f2b1051a 100644 --- a/resources/gui/gui_main.py +++ b/resources/gui/gui_main.py @@ -1350,12 +1350,12 @@ class wx_python_gui: logging.error(kdk_download_obj.error_msg) error_msg = kdk_download_obj.error_msg else: - kdk_result = kdk_obj.validate_kdk_checksum() - error_msg = kdk_obj.error_msg + kdk_result = self.kdk_obj.validate_kdk_checksum() + error_msg = self.kdk_obj.error_msg else: logging.error("Failed to download KDK") - logging.error(kdk_obj.error_msg) - error_msg = kdk_obj.error_msg + logging.error(self.kdk_obj.error_msg) + error_msg = self.kdk_obj.error_msg if kdk_result is False: # Create popup window to inform user of error diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 4ce9ace24..3133876e0 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -256,7 +256,7 @@ class KernelDebugKitObject: self.success = True kdk_download_path = self.constants.kdk_download_path if override_path == "" else Path(override_path) - kdk_plist_path = Path(f"{kdk_download_path}/{KDK_INFO_PLIST}") if override_path == "" else Path(f"{Path(override_path).parent}/{KDK_INFO_PLIST}") + kdk_plist_path = Path(f"{kdk_download_path.parent}/{KDK_INFO_PLIST}") if override_path == "" else Path(f"{Path(override_path).parent}/{KDK_INFO_PLIST}") self._generate_kdk_info_plist(kdk_plist_path) return network_handler.DownloadObject(self.kdk_url, kdk_download_path) @@ -278,6 +278,7 @@ class KernelDebugKitObject: } try: + plist_path.touch() plistlib.dump(kdk_dict, plist_path.open("wb"), sort_keys=False) except Exception as e: logging.error(f"- Failed to generate KDK Info.plist: {e}") @@ -503,6 +504,9 @@ class KernelDebugKitUtilities: logging.warning("- Cannot install KDK, not running as root") return False + logging.info(f"- Installing KDK package: {kdk_path.name}") + logging.info(f" - This may take a while...") + result = utilities.elevated(["installer", "-pkg", kdk_path, "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.info("- Failed to install KDK:") @@ -529,7 +533,7 @@ class KernelDebugKitUtilities: logging.warning("- Cannot install KDK, not running as root") return False - logging.info(f"- Installing downloaded KDK (this may take a while)") + logging.info(f"- Extracting downloaded KDK disk image") with tempfile.TemporaryDirectory() as mount_point: result = subprocess.run(["hdiutil", "attach", kdk_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: @@ -541,22 +545,27 @@ class KernelDebugKitUtilities: if not kdk_pkg_path.exists(): logging.warning("- Failed to find KDK package in DMG, likely corrupted!!!") + self._unmount_disk_image(mount_point) return False if self.install_kdk_pkg(kdk_pkg_path) is False: + self._unmount_disk_image(mount_point) return False self._create_backup(kdk_pkg_path, Path(f"{kdk_path.parent}/{KDK_INFO_PLIST}")) result = subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - if result.returncode != 0: - # Non-fatal error - logging.info("- Failed to unmount KDK:") - logging.info(result.stdout.decode('utf-8')) + self._unmount_disk_image(mount_point) logging.info("- Successfully installed KDK") return True + def _unmount_disk_image(self, mount_point): + result = subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if result.returncode != 0: + logging.info("- Failed to unmount KDK:") + logging.info(result.stdout.decode('utf-8')) + def _create_backup(self, kdk_path: Path, kdk_info_plist: Path): """ diff --git a/resources/sys_patch/sys_patch.py b/resources/sys_patch/sys_patch.py index 6fe251afc..4b2c85cb4 100644 --- a/resources/sys_patch/sys_patch.py +++ b/resources/sys_patch/sys_patch.py @@ -113,56 +113,53 @@ class PatchSysVolume: return False - def invoke_kdk_handler(self): - # If we're invoked, there is no KDK installed (or something went wrong) - kdk_result = False - error_msg = "" - - kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version) - - if kdk_obj.success is False: - error_msg = kdk_obj.error_msg - return kdk_result, error_msg, None - - kdk_download_obj = kdk_obj.retrieve_download() - - # We didn't get a download object, something's wrong - if not kdk_download_obj: - if kdk_obj.kdk_already_installed is True: - error_msg = "KDK already installed, function should not have been invoked" - return kdk_result, error_msg, None - else: - error_msg = "Could not retrieve KDK" - return kdk_result, error_msg, None - - # Hold thread until download is complete - kdk_download_obj.download(spawn_thread=False) - - if kdk_download_obj.download_complete is False: - error_msg = kdk_download_obj.error_msg - return kdk_result, error_msg, None - - kdk_result = kdk_obj.validate_kdk_checksum() - downloaded_kdk = self.constants.kdk_download_path - - return kdk_result, error_msg, downloaded_kdk - - 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: return - downloaded_kdk = None - kdk_path = sys_patch_helpers.sys_patch_helpers(self.constants).determine_kdk_present(match_closest=False) - if kdk_path is None: - if not self.constants.kdk_download_path.exists(): - kdk_result, error_msg, downloaded_kdk = self.invoke_kdk_handler() - if kdk_result is False: - raise Exception(f"Unable to download KDK: {error_msg}") - sys_patch_helpers.sys_patch_helpers(self.constants).install_kdk() - kdk_path = sys_patch_helpers.sys_patch_helpers(self.constants).determine_kdk_present(match_closest=True, override_build=downloaded_kdk) + if self.constants.kdk_download_path.exists(): + if kdk_handler.KernelDebugKitUtilities().install_kdk_dmg(self.constants.kdk_download_path) is False: + logging.info("Failed to install KDK") + raise Exception("Failed to install KDK") + + kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version) + if kdk_obj.success is False: + logging.info(f"Unable to get KDK info: {kdk_obj.error_msg}") + raise Exception(f"Unable to get KDK info: {kdk_obj.error_msg}") + + if kdk_obj.kdk_already_installed is False: + + kdk_download_obj = kdk_obj.retrieve_download() + if not kdk_download_obj: + logging.info(f"Could not retrieve KDK: {kdk_obj.error_msg}") + + # Hold thread until download is complete + kdk_download_obj.download(spawn_thread=False) + + if kdk_download_obj.download_complete is False: + error_msg = kdk_download_obj.error_msg + logging.info(f"Could not download KDK: {error_msg}") + raise Exception(f"Could not download KDK: {error_msg}") + + if kdk_obj.validate_kdk_checksum() is False: + logging.info(f"KDK checksum validation failed: {kdk_obj.error_msg}") + raise Exception(f"KDK checksum validation failed: {kdk_obj.error_msg}") + + kdk_handler.KernelDebugKitUtilities().install_kdk_dmg(self.constants.kdk_download_path) + # re-init kdk_obj to get the new kdk_installed_path + kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version) + if kdk_obj.success is False: + logging.info(f"Unable to get KDK info: {kdk_obj.error_msg}") + raise Exception(f"Unable to get KDK info: {kdk_obj.error_msg}") + + if kdk_obj.kdk_already_installed is False: + # We shouldn't get here, but just in case + logging.warning(f"KDK was not installed, but should have been: {kdk_obj.error_msg}") + raise Exception("KDK was not installed, but should have been: {kdk_obj.error_msg}") + + kdk_path = Path(kdk_obj.kdk_installed_path) if kdk_obj.kdk_installed_path != "" else None oclp_plist = Path("/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist") if (Path(self.mount_location) / Path("System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern")).exists() and oclp_plist.exists(): @@ -178,7 +175,7 @@ class PatchSysVolume: pass if kdk_path is None: - logging.info(f"- Unable to find Kernel Debug Kit: {downloaded_kdk}") + logging.info(f"- Unable to find Kernel Debug Kit") raise Exception("Unable to find Kernel Debug Kit") self.kdk_path = kdk_path logging.info(f"- Found KDK at: {kdk_path}") diff --git a/resources/sys_patch/sys_patch_detect.py b/resources/sys_patch/sys_patch_detect.py index c8886d752..a3a28a791 100644 --- a/resources/sys_patch/sys_patch_detect.py +++ b/resources/sys_patch/sys_patch_detect.py @@ -337,9 +337,7 @@ class detect_root_patch: return utilities.check_kext_loaded("WhateverGreen", self.constants.detected_os) def check_kdk(self): - if kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version, passive=True).kdk_already_installed == "": - return False - return True + 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): if self.constants.detected_os > os_data.os_data.catalina: diff --git a/resources/sys_patch/sys_patch_helpers.py b/resources/sys_patch/sys_patch_helpers.py index a987172d2..9cbfe043c 100644 --- a/resources/sys_patch/sys_patch_helpers.py +++ b/resources/sys_patch/sys_patch_helpers.py @@ -77,33 +77,6 @@ class sys_patch_helpers: return True return False - def install_kdk(self): - if not self.constants.kdk_download_path.exists(): - return - - logging.info(f"- Installing downloaded KDK (this may take a while)") - with tempfile.TemporaryDirectory() as mount_point: - utilities.process_status(subprocess.run(["hdiutil", "attach", self.constants.kdk_download_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - # Due to a permissions bug in macOS, sometimes the OS will fail on a Read-only file system error - # We don't actually need to write inside the KDK DMG, however macOS will do whatever it wants - # Thus move the KDK to another location, and run the installer from there - kdk_dst_path = Path(f"{self.constants.payload_path}/KernelDebugKit.pkg") - if kdk_dst_path.exists(): - utilities.process_status(utilities.elevated(["rm", kdk_dst_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - utilities.process_status(subprocess.run(["cp", f"{mount_point}/KernelDebugKit.pkg", self.constants.payload_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - result = utilities.elevated(["installer", "-pkg", kdk_dst_path, "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - if result.returncode != 0: - logging.info("- Failed to install KDK:") - logging.info(result.stdout.decode('utf-8')) - if result.stderr: - logging.info(result.stderr.decode('utf-8')) - utilities.elevated(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - raise Exception("Failed to install KDK") - utilities.process_status(utilities.elevated(["rm", kdk_dst_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) - utilities.elevated(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - logging.info("- Successfully installed KDK") - - def disable_window_server_caching(self): # On legacy GCN GPUs, the WindowServer cache generated creates From 8b18c59d8ad07d3ca210ff9ca0fd5488fc84d931 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 13:15:19 -0700 Subject: [PATCH 20/61] kdk_handler.py: Avoid double unmount --- resources/kdk_handler.py | 17 +++++++++-------- resources/sys_patch/sys_patch.py | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 3133876e0..bb0ea0905 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -273,8 +273,8 @@ class KernelDebugKitObject: plist_path.unlink() kdk_dict = { - "Build": self.kdk_url_build, - "Version": self.kdk_url_version, + "build": self.kdk_url_build, + "version": self.kdk_url_version, } try: @@ -553,18 +553,19 @@ class KernelDebugKitUtilities: return False self._create_backup(kdk_pkg_path, Path(f"{kdk_path.parent}/{KDK_INFO_PLIST}")) - - result = subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) self._unmount_disk_image(mount_point) logging.info("- Successfully installed KDK") return True def _unmount_disk_image(self, mount_point): - result = subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - if result.returncode != 0: - logging.info("- Failed to unmount KDK:") - logging.info(result.stdout.decode('utf-8')) + """ + Unmounts provided disk image silently + + Parameters: + mount_point (Path): Path to mount point + """ + subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) def _create_backup(self, kdk_path: Path, kdk_info_plist: Path): diff --git a/resources/sys_patch/sys_patch.py b/resources/sys_patch/sys_patch.py index 4b2c85cb4..b232b7ca4 100644 --- a/resources/sys_patch/sys_patch.py +++ b/resources/sys_patch/sys_patch.py @@ -187,7 +187,7 @@ class PatchSysVolume: # Note it's a folder, not a file utilities.elevated(["cp", "-r", cs_path, f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - logging.info("- Merging KDK with Root Volume") + logging.info(f"- Merging KDK with Root Volume: {kdk_path.name}") utilities.elevated( # Only merge '/System/Library/Extensions' # 'Kernels' and 'KernelSupport' is wasted space for root patching (we don't care above dev kernels) From 971a2b0d02a33797415564fae0a1d291cf11ae3a Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 13:56:56 -0700 Subject: [PATCH 21/61] kdk_handler.py: Set KDK installed accordingly --- resources/kdk_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index bb0ea0905..0476b00eb 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -161,6 +161,7 @@ class KernelDebugKitObject: self.kdk_installed_path = self._local_kdk_installed(match=loose_version, check_version=True) if self.kdk_installed_path: logging.info(f"- Found matching KDK: {Path(self.kdk_installed_path).name}") + self.kdk_already_installed = True self.success = True return @@ -169,6 +170,7 @@ class KernelDebugKitObject: self.kdk_installed_path = self._local_kdk_installed(match=older_version, check_version=True) if self.kdk_installed_path: logging.info(f"- Found matching KDK: {Path(self.kdk_installed_path).name}") + self.kdk_already_installed = True self.success = True return From c5f72c10ff283fd71cb435f6b514ca86f36bdf64 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 14:31:54 -0700 Subject: [PATCH 22/61] kdk_handler.py: Clear out backup KDKs when unused --- CHANGELOG.md | 2 ++ resources/kdk_handler.py | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f488f3e5d..77c6bc4cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - Implement Kernel Debug Kit installation during OS installs - Avoids network requirement for first time installs - Paired along side AutoPkgInstaller +- Implement Kernel Debug Kit backup system + - Allows for easy restoration of KDKs if OS updates corrupted installed KDKs - Backend Changes: - Refactored kdk_handler.py - Prioritizes KdkSupportPkg repository for downloads diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 0476b00eb..f2b4b8f96 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -399,7 +399,15 @@ class KernelDebugKitObject: logging.warning("- Cannot remove KDK, not running as root") return - result = utilities.elevated(["rm", "-rf", kdk_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if not Path(kdk_path).exists(): + logging.warning(f"- KDK does not exist: {kdk_path}") + return + + rm_args = ["rm", "-f", kdk_path] + if Path(kdk_path).is_dir(): + rm_args = ["rm", "-rf", kdk_path] + + result = utilities.elevated(rm_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: logging.warning(f"- Failed to remove KDK: {kdk_path}") logging.warning(f"- {result.stdout.decode('utf-8')}") @@ -434,10 +442,12 @@ class KernelDebugKitObject: logging.info("- Cleaning unused KDKs") for kdk_folder in Path(KDK_INSTALL_PATH).iterdir(): if kdk_folder.is_dir(): - if kdk_folder.name.endswith(".kdk"): + if kdk_folder.name.endswith(".kdk") or kdk_folder.name.endswith(".pkg"): should_remove = True for build in exclude_builds: - if build != "" and kdk_folder.name.endswith(f"{build}.kdk"): + if build != "": + continue + if kdk_folder.name.endswith(f"{build}.kdk") or kdk_folder.name.endswith(f"{build}.pkg"): should_remove = False break if should_remove is False: From 4ae494db86412fbf447d8f0834ccafffeebbbe07 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 14:44:09 -0700 Subject: [PATCH 23/61] kdk_handler.py: Limit number of removal invocations --- resources/kdk_handler.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index f2b4b8f96..c22dd18eb 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -486,8 +486,6 @@ class KernelDebugKitObject: self.error_msg = msg - self._remove_unused_kdks() - self.success = True From 6b86e64b5bed72ac230a66c3949936a4e9bd3d8c Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 14:47:25 -0700 Subject: [PATCH 24/61] kdk_handler.py: Keep invocation --- resources/kdk_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index c22dd18eb..f2b4b8f96 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -486,6 +486,8 @@ class KernelDebugKitObject: self.error_msg = msg + self._remove_unused_kdks() + self.success = True From 7be168bf14b0859d1acf15c2d7ba017d824deb1d Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 15:07:33 -0700 Subject: [PATCH 25/61] kdk_handler.py: display KDK backup name during creation --- resources/kdk_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index f2b4b8f96..92550f9f4 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -597,7 +597,6 @@ class KernelDebugKitUtilities: return kdk_info_dict = plistlib.load(kdk_info_plist.open("rb")) - logging.info("- Creating backup of KDK") if 'version' not in kdk_info_dict or 'build' not in kdk_info_dict: logging.warning("- Malformed KDK Info.plist provided, cannot create backup") @@ -610,6 +609,7 @@ class KernelDebugKitUtilities: kdk_dst_name = f"KDK_{kdk_info_dict['version']}_{kdk_info_dict['build']}.pkg" kdk_dst_path = Path(f"{KDK_INSTALL_PATH}/{kdk_dst_name}") + logging.info(f"- Creating backup: {kdk_dst_name}") if kdk_dst_path.exists(): logging.info("- Backup already exists, skipping") return From 7ee631859d75fb37f7c8c1c4f80374ca076bac36 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 15:42:12 -0700 Subject: [PATCH 26/61] amfi_detect.py: Adjust to be more Object-oriented --- resources/amfi_detect.py | 99 ++++++++++++++++++------- resources/sys_patch/sys_patch_detect.py | 8 +- 2 files changed, 75 insertions(+), 32 deletions(-) diff --git a/resources/amfi_detect.py b/resources/amfi_detect.py index 6f7a47f3a..20584f966 100644 --- a/resources/amfi_detect.py +++ b/resources/amfi_detect.py @@ -1,27 +1,54 @@ # Determine AppleMobileFileIntegrity's OS configuration +# Copyright (C) 2022-2023, Mykola Grymalyuk +import enum from resources import utilities -class amfi_configuration_detection: + +class AmfiConfigDetectLevel(enum.IntEnum): + """ + Configuration levels used by AmfiConfigurationDetection + """ + + NO_CHECK: int = 0 + LIBRARY_VALIDATION: int = 1 # For Ventura, use LIBRARY_VALIDATION_AND_SIG + LIBRARY_VALIDATION_AND_SIG: int = 2 + ALLOW_ALL: int = 3 + + +class AmfiConfigurationDetection: + """ + Detect AppleMobileFileIntegrity's OS configuration + + Usage: + + >>> from resources.amfi_detect import AmfiConfigurationDetection + >>> can_patch = AmfiConfigurationDetection().check_config(AmfiConfigDetectLevel.AMFI_LEVEL_ALLOW_ALL) + + """ def __init__(self): - self.AMFI_ALLOW_TASK_FOR_PID = False - self.AMFI_ALLOW_INVALID_SIGNATURE = False - self.AMFI_LV_ENFORCE_THIRD_PARTY = False - self.AMFI_ALLOW_EVERYTHING = False - self.SKIP_LIBRARY_VALIDATION = False + self.AMFI_ALLOW_TASK_FOR_PID: bool = False + self.AMFI_ALLOW_INVALID_SIGNATURE: bool = False + self.AMFI_LV_ENFORCE_THIRD_PARTY: bool = False + self.AMFI_ALLOW_EVERYTHING: bool = False + self.SKIP_LIBRARY_VALIDATION: bool = False - self.boot_args = [] - self.oclp_args = [] + self.boot_args: list = [] + self.oclp_args: list = [] - self.init_nvram_dicts() + self._init_nvram_dicts() - self.parse_amfi_bitmask() - self.parse_amfi_boot_args() - self.parse_oclp_configuration() + self._parse_amfi_bitmask() + self._parse_amfi_boot_args() + self._parse_oclp_configuration() - def init_nvram_dicts(self): + def _init_nvram_dicts(self): + """ + Initialize the boot-args and OCLP-Settings NVRAM dictionaries + """ + boot_args = utilities.get_nvram("boot-args", decode=True) oclp_args = utilities.get_nvram("OCLP-Settings", "4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102", decode=True) @@ -32,8 +59,12 @@ class amfi_configuration_detection: self.oclp_args = oclp_args.split(" ") - def parse_amfi_bitmask(self): - # See data/amfi_data.py for more information + def _parse_amfi_bitmask(self): + """ + Parse the AMFI bitmask from boot-args + See data/amfi_data.py for more information + """ + amfi_value = 0 for arg in self.boot_args: if arg.startswith("amfi="): @@ -65,7 +96,11 @@ class amfi_configuration_detection: self.AMFI_ALLOW_INVALID_SIGNATURE = True - def parse_amfi_boot_args(self): + def _parse_amfi_boot_args(self): + """ + Parse the AMFI boot-args + """ + for arg in self.boot_args: if arg.startswith("amfi_unrestrict_task_for_pid"): value = arg.split("=") @@ -86,26 +121,34 @@ class amfi_configuration_detection: self.AMFI_ALLOW_INVALID_SIGNATURE = True - def parse_oclp_configuration(self): + def _parse_oclp_configuration(self): + """ + Parse the OCLP configuration + """ + if "-allow_amfi" in self.oclp_args: self.SKIP_LIBRARY_VALIDATION = True - def check_config(self, level): - # Levels: - # - 0: No checks - # - 1. Library Validation (Monterey and Older) - # - 2. Library Validation and Signature Checks (Ventura and Newer) - # - 3. Disable all AMFI checks + def check_config(self, level: int): + """ + Check the AMFI configuration based on provided AMFI level + See AmfiConfigLevel enum for valid levels - if level == 0: + Parameters: + level (int): The level of AMFI checks to check for + + Returns: + bool: True if the AMFI configuration matches the level, False otherwise + """ + + if level == AmfiConfigDetectLevel.NO_CHECK: return True - - if level == 1: + if level == AmfiConfigDetectLevel.LIBRARY_VALIDATION: return self.SKIP_LIBRARY_VALIDATION - if level == 2: + if level == AmfiConfigDetectLevel.LIBRARY_VALIDATION_AND_SIG: return bool(self.SKIP_LIBRARY_VALIDATION and self.AMFI_ALLOW_INVALID_SIGNATURE) - if level == 3: + if level == AmfiConfigDetectLevel.ALLOW_ALL: return self.AMFI_ALLOW_EVERYTHING return False \ No newline at end of file diff --git a/resources/sys_patch/sys_patch_detect.py b/resources/sys_patch/sys_patch_detect.py index a3a28a791..d0988b87b 100644 --- a/resources/sys_patch/sys_patch_detect.py +++ b/resources/sys_patch/sys_patch_detect.py @@ -490,9 +490,9 @@ class detect_root_patch: if self.amfi_shim_bins is True: # Currently we require AMFI outright disabled # in Ventura to work with shim'd binaries - return 3 - return 1 - return 0 + 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() @@ -500,7 +500,7 @@ class detect_root_patch: 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.amfi_configuration_detection().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() From dd06932fe516669a61e2ef22a80310064ef023f8 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Wed, 8 Feb 2023 15:45:51 -0700 Subject: [PATCH 27/61] amfi_detect.py: Fix comment --- resources/amfi_detect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/amfi_detect.py b/resources/amfi_detect.py index 20584f966..f13cfa974 100644 --- a/resources/amfi_detect.py +++ b/resources/amfi_detect.py @@ -22,8 +22,8 @@ class AmfiConfigurationDetection: Usage: - >>> from resources.amfi_detect import AmfiConfigurationDetection - >>> can_patch = AmfiConfigurationDetection().check_config(AmfiConfigDetectLevel.AMFI_LEVEL_ALLOW_ALL) + >>> import amfi_detect + >>> can_patch = amfi_detect.AmfiConfigurationDetection().check_config(amfi_detect.AmfiConfigDetectLevel.ALLOW_ALL) """ From 3b5e4f10f6f38b6650f61ac94b60a9629c7bbba5 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 08:35:21 -0700 Subject: [PATCH 28/61] kdk_handler.py: Use pkg receipts for KDK validation --- CHANGELOG.md | 1 + resources/kdk_handler.py | 54 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77c6bc4cd..587bace9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Prioritizes KdkSupportPkg repository for downloads - Skips calls to Apple's now defunct Developer Portal API - Support local loose matching when no network connection is available + - Implement pkg receipt verification to validate integrity of KDKs - Implemented logging framework usage for more reliable logging - Logs are stored under `~/OpenCore-Patcher.log` - Subsequent runs are appended to the log, allowing for easy debugging diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 92550f9f4..630677219 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -286,15 +286,63 @@ class KernelDebugKitObject: logging.error(f"- Failed to generate KDK Info.plist: {e}") - def _local_kdk_valid(self, kdk_path: str): + def _local_kdk_valid(self, kdk_path: Path): """ Validates provided KDK, ensure no corruption The reason for this is due to macOS deleting files from the KDK during OS updates, similar to how Install macOS.app is deleted during OS updates + Uses Apple's pkg receipt system to verify the original contents of the KDK + Args: - kdk_path (str): Path to KDK + kdk_path (Path): Path to KDK + + Returns: + bool: True if valid, False if invalid + """ + + if not Path(f"{kdk_path}/System/Library/CoreServices/SystemVersion.plist").exists(): + logging.info(f"- Corrupted KDK found ({kdk_path.name}), removing due to missing SystemVersion.plist") + self._remove_kdk(kdk_path) + return False + + # Get build from KDK + kdk_plist_data = plistlib.load(Path(f"{kdk_path}/System/Library/CoreServices/SystemVersion.plist").open("rb")) + if "ProductBuildVersion" not in kdk_plist_data: + logging.info(f"- Corrupted KDK found ({kdk_path.name}), removing due to missing ProductBuildVersion") + self._remove_kdk(kdk_path) + return False + + kdk_build = kdk_plist_data["ProductBuildVersion"] + + # Check pkg receipts for this build, will give a canonical list if all files that should be present + result = subprocess.run(["pkgutil", "--files", f"com.apple.pkg.KDK.{kdk_build}"], capture_output=True) + if result.returncode != 0: + # If pkg receipt is missing, we'll fallback to legacy validation + logging.info(f"- pkg receipt missing for {kdk_path.name}, falling back to legacy validation") + return self._local_kdk_valid_legacy(kdk_path) + + # Go through each line of the pkg receipt and ensure it exists + for line in result.stdout.decode("utf-8").splitlines(): + if not line.startswith("System/Library/Extensions"): + continue + if not Path(f"{kdk_path}/{line}").exists(): + logging.info(f"- Corrupted KDK found ({kdk_path.name}), removing due to missing file: {line}") + self._remove_kdk(kdk_path) + return False + + return True + + + def _local_kdk_valid_legacy(self, kdk_path: Path): + """ + Legacy variant of validating provided KDK + Uses best guess of files that should be present + This should ideally never be invoked, but used as a fallback + + Parameters: + kdk_path (Path): Path to KDK Returns: bool: True if valid, False if invalid @@ -307,8 +355,6 @@ class KernelDebugKitObject: "AMDRadeonX6000.kext/Contents/MacOS/AMDRadeonX6000", ] - kdk_path = Path(kdk_path) - for kext in KEXT_CATALOG: if not Path(f"{kdk_path}/System/Library/Extensions/{kext}").exists(): logging.info(f"- Corrupted KDK found, removing due to missing: {kdk_path}/System/Library/Extensions/{kext}") From 0671828c9bd5116e078cc923e337d1ec626ea2f7 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 11:04:15 -0700 Subject: [PATCH 29/61] logging_handler.py: Fix file permissions when root --- resources/global_settings.py | 59 +++++++++++++++++++++++++++++------- resources/logging_handler.py | 24 +++++++++++++++ 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/resources/global_settings.py b/resources/global_settings.py index 47511fb6b..36d56b5e9 100644 --- a/resources/global_settings.py +++ b/resources/global_settings.py @@ -6,6 +6,8 @@ from pathlib import Path import plistlib import logging +import os +import subprocess class global_settings: @@ -13,25 +15,28 @@ class global_settings: 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.generate_settings_file() - self.convert_defaults_to_global_settings() + self._generate_settings_file() + self._convert_defaults_to_global_settings() + self._fix_file_permission() - def generate_settings_file(self): - if Path(self.global_settings_plist).exists(): - return - try: - plistlib.dump({"Developed by Dortania": True,}, Path(self.global_settings_plist).open("wb")) - except PermissionError: - logging.info("- Permission error: Unable to write to global settings file") def read_property(self, property_name): + """ + Reads a property from the global settings file + """ + if Path(self.global_settings_plist).exists(): plist = plistlib.load(Path(self.global_settings_plist).open("rb")) if property_name in plist: return plist[property_name] return None + def write_property(self, property_name, property_value): + """ + Writes a property to the global settings file + """ + if Path(self.global_settings_plist).exists(): plist = plistlib.load(Path(self.global_settings_plist).open("rb")) plist[property_name] = property_value @@ -41,7 +46,20 @@ class global_settings: logging.info("- Failed to write to global settings file") - def convert_defaults_to_global_settings(self): + def _generate_settings_file(self): + if Path(self.global_settings_plist).exists(): + return + try: + plistlib.dump({"Developed by Dortania": True,}, Path(self.global_settings_plist).open("wb")) + except PermissionError: + logging.info("- Permission error: Unable to write to global settings file") + + + def _convert_defaults_to_global_settings(self): + """ + Converts legacy defaults to global settings + """ + defaults_path = "~/Library/Preferences/com.dortania.opencore-legacy-patcher.plist" defaults_path = Path(defaults_path).expanduser() @@ -60,4 +78,23 @@ class global_settings: try: Path(defaults_path).unlink() except PermissionError: - logging.info("- Permission error: Unable to delete defaults plist") \ No newline at end of file + logging.info("- Permission error: Unable to delete defaults plist") + + + def _fix_file_permission(self): + """ + Fixes file permission for log file + + If OCLP was invoked as root, file permission will only allow root to write to settings file + This in turn breaks normal OCLP execution to write to settings file + """ + + if os.geteuid() != 0: + return + + # Set file permission to allow any user to write to log file + result = subprocess.run(["chmod", "777", self.global_settings_plist], capture_output=True) + if result.returncode != 0: + logging.warning("- Failed to fix settings file permissions:") + if result.stderr: + logging.warning(result.stderr.decode("utf-8")) \ No newline at end of file diff --git a/resources/logging_handler.py b/resources/logging_handler.py index 89e50f42f..f5a414528 100644 --- a/resources/logging_handler.py +++ b/resources/logging_handler.py @@ -1,6 +1,8 @@ import logging import sys import threading +import os +import subprocess from pathlib import Path @@ -19,6 +21,9 @@ class InitializeLoggingSupport: >>> from resources.logging_handler import InitializeLoggingSupport >>> InitializeLoggingSupport() + FOR DEVELOPERS: + - Do not invoke logging until after '_attempt_initialize_logging_configuration()' has been invoked + """ def __init__(self) -> None: @@ -35,6 +40,7 @@ class InitializeLoggingSupport: self._clean_log_file() self._attempt_initialize_logging_configuration() self._implement_custom_traceback_handler() + self._fix_file_permission() def __del__(self): @@ -81,6 +87,24 @@ class InitializeLoggingSupport: print(f"- Failed to clean log file: {e}") + def _fix_file_permission(self): + """ + Fixes file permission for log file + + If OCLP was invoked as root, file permission will only allow root to write to log file + This in turn breaks normal OCLP execution to write to log file + """ + + if os.geteuid() != 0: + return + + result = subprocess.run(["chmod", "777", self.log_filepath], capture_output=True) + if result.returncode != 0: + print(f"- Failed to fix log file permissions") + if result.stderr: + print(result.stderr.decode("utf-8")) + + def _initialize_logging_configuration(self, log_to_file: bool = True): """ Initialize logging framework configuration From e076260a1a05c3264123560e3f3c82ceddabcd68 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 12:36:16 -0700 Subject: [PATCH 30/61] Sync docstrings comments --- resources/kdk_handler.py | 10 +++++----- resources/logging_handler.py | 8 +++++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 630677219..bfd1be59c 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -124,7 +124,7 @@ class KernelDebugKitObject: """ Fetches the latest KDK for the current macOS version - Args: + Parameters: host_build (str, optional): The build version of the current macOS version. If empty, will use the host_build from the class. Defaults to None. host_version (str, optional): The version of the current macOS version. @@ -295,7 +295,7 @@ class KernelDebugKitObject: Uses Apple's pkg receipt system to verify the original contents of the KDK - Args: + Parameters: kdk_path (Path): Path to KDK Returns: @@ -434,7 +434,7 @@ class KernelDebugKitObject: """ Removes provided KDK - Args: + Parameters: kdk_path (str): Path to KDK """ @@ -465,7 +465,7 @@ class KernelDebugKitObject: """ Removes KDKs that are not in use - Args: + Parameters: exclude_builds (list, optional): Builds to exclude from removal. If None, defaults to host and closest match builds. """ @@ -505,7 +505,7 @@ class KernelDebugKitObject: """ Validates KDK DMG checksum - Args: + Parameters: kdk_dmg_path (str, optional): Path to KDK DMG. Defaults to None. Returns: diff --git a/resources/logging_handler.py b/resources/logging_handler.py index f5a414528..98935024f 100644 --- a/resources/logging_handler.py +++ b/resources/logging_handler.py @@ -26,7 +26,7 @@ class InitializeLoggingSupport: """ - def __init__(self) -> None: + def __init__(self): self.log_filename: str = "OpenCore-Patcher.log" self.log_filepath: Path = None @@ -111,6 +111,10 @@ class InitializeLoggingSupport: StreamHandler's format is used to mimic the default behavior of print() While FileHandler's format is for more in-depth logging + + Parameters: + log_to_file (bool): Whether to log to file or not + """ logging.basicConfig( @@ -125,6 +129,7 @@ class InitializeLoggingSupport: logging.getLogger().handlers[0].setFormatter(logging.Formatter("%(message)s")) logging.getLogger().handlers[1].maxBytes = self.max_file_size + def _attempt_initialize_logging_configuration(self): """ Attempt to initialize logging framework configuration @@ -160,6 +165,7 @@ class InitializeLoggingSupport: sys.excepthook = custom_excepthook threading.excepthook = custom_thread_excepthook + def _restore_original_excepthook(self): """ Restore original traceback handlers From f9c7273106d749c65be283fa8a5c9fd3aaf92e79 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 12:44:26 -0700 Subject: [PATCH 31/61] os_probe.py: Rework to be Object-oriented Minimize repetitive calls to platform.uname() --- resources/main.py | 9 +++-- resources/os_probe.py | 88 ++++++++++++++++++++++++++++++++----------- 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/resources/main.py b/resources/main.py index de1649fda..4dd962c82 100644 --- a/resources/main.py +++ b/resources/main.py @@ -44,10 +44,11 @@ class OpenCoreLegacyPatcher: def generate_base_data(self): - self.constants.detected_os = os_probe.detect_kernel_major() - self.constants.detected_os_minor = os_probe.detect_kernel_minor() - self.constants.detected_os_build = os_probe.detect_os_build() - self.constants.detected_os_version = os_probe.detect_os_version() + 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() self.constants.computer = device_probe.Computer.probe() self.constants.recovery_status = utilities.check_recovery() self.computer = self.constants.computer diff --git a/resources/os_probe.py b/resources/os_probe.py index 657ed662f..a88f32883 100644 --- a/resources/os_probe.py +++ b/resources/os_probe.py @@ -5,31 +5,77 @@ import subprocess import plistlib -def detect_kernel_major(): - # Return Major Kernel Version - # Example Output: 21 (integer) - return int(platform.uname().release.partition(".")[0]) +class OSProbe: + """ + Library for querying OS information specific to macOS + """ + + def __init__(self): + self.uname_data = platform.uname() -def detect_kernel_minor(): - # Return Minor Kernel Version - # Example Output: 1 (integer) - return int(platform.uname().release.partition(".")[2].partition(".")[0]) + def detect_kernel_major(self): + """ + Detect the booted major kernel version + + Returns: + int: Major kernel version (ex. 21, from 21.1.0) + """ + + return int(self.uname_data.release.partition(".")[0]) -def detect_os_version(): - # Return OS version - # Example Output: 12.0 (string) - return subprocess.run("sw_vers -productVersion".split(), stdout=subprocess.PIPE).stdout.decode().strip() + def detect_kernel_minor(self): + """ + Detect the booted minor kernel version + + Returns: + int: Minor kernel version (ex. 1, from 21.1.0) + """ + + return int(self.uname_data.release.partition(".")[2].partition(".")[0]) -def detect_os_build(): - # Return OS build - # Example Output: 21A5522h (string) + def detect_os_version(self): + """ + Detect the booted OS version - # With macOS 13.2, Apple implemented the Rapid Security Response system which - # will change the reported build to the RSR version and not the original host - # To get the proper versions: - # - Host: /System/Library/CoreServices/SystemVersion.plist - # - RSR: /System/Volumes/Preboot/Cryptexes/OS/System/Library/CoreServices/SystemVersion.plist - return plistlib.load(open("/System/Library/CoreServices/SystemVersion.plist", "rb"))["ProductBuildVersion"] + Returns: + str: OS version (ex. 12.0) + """ + + result = subprocess.run(["sw_vers", "-productVersion"], stdout=subprocess.PIPE) + if result.returncode != 0: + raise RuntimeError("Failed to detect OS version") + + return result.stdout.decode().strip() + + + def detect_os_build(self, rsr: bool = False): + """ + Detect the booted OS build + + Implementation note: + With macOS 13.2, Apple implemented the Rapid Security Response system which + will change the reported build to the RSR version and not the original host + + To get the proper versions: + - Host: /System/Library/CoreServices/SystemVersion.plist + - RSR: /System/Volumes/Preboot/Cryptexes/OS/System/Library/CoreServices/SystemVersion.plist + + + Parameters: + rsr (bool): Whether to use the RSR version of the build + + Returns: + str: OS build (ex. 21A5522h) + """ + + file_path = "/System/Library/CoreServices/SystemVersion.plist" + if rsr is True: + file_path = f"/System/Volumes/Preboot/Cryptexes/OS{file_path}" + + try: + return plistlib.load(open(file_path, "rb"))["ProductBuildVersion"] + except Exception as e: + raise RuntimeError(f"Failed to detect OS build: {e}") From 48471ce4d3935ca1d5cf74cd1038a8336ac7ca73 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 16:25:45 -0700 Subject: [PATCH 32/61] validation.py: Rework into object oriented --- resources/validation.py | 205 ++++++++++++++++++++++++---------------- 1 file changed, 124 insertions(+), 81 deletions(-) diff --git a/resources/validation.py b/resources/validation.py index 5705f1009..2546cf77b 100644 --- a/resources/validation.py +++ b/resources/validation.py @@ -1,51 +1,69 @@ +import logging import subprocess +from pathlib import Path + from resources.sys_patch import sys_patch_helpers from resources.build import build 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, constants): + self.constants = constants - settings.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.constants.validate = True + + 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 +71,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 +110,72 @@ 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.sys_patch_helpers(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.sys_patch_helpers(self.constants).snb_board_id_patch(self.constants.payload_local_binaries_root_path) 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" - - validate_configs() - validate_sys_patch() \ No newline at end of file + self._build_prebuilt() + self._build_dumps() \ No newline at end of file From d305515c288d236864372c60bb3712b1ac46941e Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 16:26:47 -0700 Subject: [PATCH 33/61] Remove TUI modules --- OpenCore-Patcher-GUI.command | 2 +- OpenCore-Patcher.command | 6 - OpenCore-Patcher.spec | 44 -- data/mirror_data.py | 11 - resources/cli_menu.py | 1341 ---------------------------------- resources/install.py | 83 +-- resources/installer.py | 48 +- resources/main.py | 132 ++-- resources/tui_helpers.py | 78 -- 9 files changed, 59 insertions(+), 1686 deletions(-) delete mode 100755 OpenCore-Patcher.command delete mode 100644 OpenCore-Patcher.spec delete mode 100644 data/mirror_data.py delete mode 100644 resources/cli_menu.py delete mode 100644 resources/tui_helpers.py 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/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/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/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/main.py b/resources/main.py index 4dd962c82..89cd55b6f 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() + + 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 + + # Initialize working directory 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 + # Generate commit info + self.constants.commit_info = commit_info.commit_info(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) + # Generate defaults defaults.generate_defaults(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/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 From 3aadfe6002160bf888a115625b8e06f6b5e0dc24 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 16:27:18 -0700 Subject: [PATCH 34/61] arguments.py: Rework into object oriented --- resources/arguments.py | 262 ++++++++++++++++++++++++++--------------- 1 file changed, 166 insertions(+), 96 deletions(-) diff --git a/resources/arguments.py b/resources/arguments.py index ece02e81d..5aee6b4dd 100644 --- a/resources/arguments.py +++ b/resources/arguments.py @@ -9,108 +9,178 @@ import logging # Generic building args class arguments: - def __init__(self): + def __init__(self, constants): self.args = utilities.check_cli_args() + self.constants = constants + + self._parse_arguments() + + + def _parse_arguments(self): + """ + Parses arguments passed to the patcher + """ - def parse_arguments(self, settings): 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.generate_defaults(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.generate_defaults(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() From d40d6607b33aa436e7214ad9ec7e50a8e39a5ff0 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 16:40:25 -0700 Subject: [PATCH 35/61] CI: Remove TUI --- .github/workflows/build-app-wxpython.yml | 5 +++++ .github/workflows/build-app.yml | 24 ------------------------ .github/workflows/build-site.yml | 1 + .github/workflows/validate.yml | 20 ++++++++++++++++++++ 4 files changed, 26 insertions(+), 24 deletions(-) delete mode 100644 .github/workflows/build-app.yml create mode 100644 .github/workflows/validate.yml 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 From 925003e3f1ff9f45b085226c0e6abba3d0777ac9 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 16:46:12 -0700 Subject: [PATCH 36/61] reroute_payloads.py: rework into object oriented --- resources/main.py | 2 +- resources/reroute_payloads.py | 39 +++++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/resources/main.py b/resources/main.py index 89cd55b6f..5138296a0 100644 --- a/resources/main.py +++ b/resources/main.py @@ -76,7 +76,7 @@ class OpenCoreLegacyPatcher: self.constants.launcher_script = launcher_script # Initialize working directory - self.constants.unpack_thread = threading.Thread(target=reroute_payloads.reroute_payloads(self.constants).setup_tmp_disk_image) + self.constants.unpack_thread = threading.Thread(target=reroute_payloads.RoutePayloadDiskImage, args=(self.constants,)) self.constants.unpack_thread.start() # Generate commit info diff --git a/resources/reroute_payloads.py b/resources/reroute_payloads.py index 674914c41..2d4223313 100644 --- a/resources/reroute_payloads.py +++ b/resources/reroute_payloads.py @@ -9,21 +9,29 @@ import tempfile import atexit import logging -class reroute_payloads: +class RoutePayloadDiskImage: + def __init__(self, constants): self.constants = constants - 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 + 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 + """ + 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 +46,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) From 4154b01d40c84761e5acc12049ba37f1159b43d2 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 17:04:20 -0700 Subject: [PATCH 37/61] commit_info.py: Rework into object oriented --- resources/commit_info.py | 31 +++++++++++++++++++++---------- resources/main.py | 2 +- 2 files changed, 22 insertions(+), 11 deletions(-) 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/main.py b/resources/main.py index 5138296a0..921e266a3 100644 --- a/resources/main.py +++ b/resources/main.py @@ -80,7 +80,7 @@ class OpenCoreLegacyPatcher: self.constants.unpack_thread.start() # Generate commit info - self.constants.commit_info = commit_info.commit_info(self.constants.launcher_binary).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] From 66a5f5a9ad7ff6ca9c43bf5c8c6ea3ddab620d92 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 17:40:41 -0700 Subject: [PATCH 38/61] Set type suggestions for global Constants() usage --- resources/arguments.py | 21 +++-- resources/defaults.py | 88 ++++++++++++----- resources/gui/gui_main.py | 4 +- resources/main.py | 2 +- resources/reroute_payloads.py | 6 +- resources/sys_patch/sys_patch.py | 115 ++++++++++++----------- resources/sys_patch/sys_patch_auto.py | 6 +- resources/sys_patch/sys_patch_detect.py | 28 ++++-- resources/sys_patch/sys_patch_helpers.py | 19 ++-- resources/updates.py | 19 ++-- resources/utilities.py | 1 - resources/validation.py | 18 +++- 12 files changed, 196 insertions(+), 131 deletions(-) diff --git a/resources/arguments.py b/resources/arguments.py index 5aee6b4dd..8f7d0884d 100644 --- a/resources/arguments.py +++ b/resources/arguments.py @@ -1,17 +1,20 @@ -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, constants): + def __init__(self, global_constants: constants.Constants()): + self.constants: constants.Constants() = global_constants + self.args = utilities.check_cli_args() - self.constants = constants self._parse_arguments() @@ -93,7 +96,7 @@ class arguments: if self.args.model: logging.info(f"- Using custom model: {self.args.model}") self.constants.custom_model = self.args.model - defaults.generate_defaults(self.constants.custom_model, False, self.constants) + 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!" @@ -103,7 +106,7 @@ If you plan to create the USB for another machine, please select the "Change Mod sys.exit(1) else: logging.info(f"- Using detected model: {self.constants.computer.real_model}") - defaults.generate_defaults(self.constants.custom_model, True, self.constants) + defaults.GenerateDefaults(self.constants.custom_model, True, self.constants) if self.args.disk: logging.info(f"- Install Disk set: {self.args.disk}") diff --git a/resources/defaults.py b/resources/defaults.py index 0f49b7f89..d067d01fd 100644 --- a/resources/defaults.py +++ b/resources/defaults.py @@ -1,36 +1,51 @@ # 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 +class GenerateDefaults: + + def __init__(self, model: str, host_is_target: bool, global_constants: constants.Constants()): + self.constants: constants.Constants() = global_constants + + self.model = model self.host_is_target = 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.fu_arguments: bool = None - self.constants.custom_serial_number = "" - self.constants.custom_board_serial_number = "" + 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() + 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 @@ -68,7 +83,12 @@ class generate_defaults: 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 +119,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 +144,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 +185,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 +202,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/gui/gui_main.py b/resources/gui/gui_main.py index 9f2b1051a..0e28eca88 100644 --- a/resources/gui/gui_main.py +++ b/resources/gui/gui_main.py @@ -2645,11 +2645,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) diff --git a/resources/main.py b/resources/main.py index 921e266a3..0435561b9 100644 --- a/resources/main.py +++ b/resources/main.py @@ -88,7 +88,7 @@ class OpenCoreLegacyPatcher: self.constants.installer_pkg_url_nightly = self.constants.installer_pkg_url_nightly.replace("main", branch) # Generate defaults - defaults.generate_defaults(self.computer.real_model, True, self.constants) + defaults.GenerateDefaults(self.computer.real_model, True, self.constants) 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") diff --git a/resources/reroute_payloads.py b/resources/reroute_payloads.py index 2d4223313..f6f35a8de 100644 --- a/resources/reroute_payloads.py +++ b/resources/reroute_payloads.py @@ -9,10 +9,12 @@ import tempfile import atexit import logging +from resources import constants + class RoutePayloadDiskImage: - def __init__(self, constants): - self.constants = constants + def __init__(self, global_constants: constants.Constants()): + self.constants: constants.Constants() = global_constants self._setup_tmp_disk_image() diff --git a/resources/sys_patch/sys_patch.py b/resources/sys_patch/sys_patch.py index b232b7ca4..7f80bbc2a 100644 --- a/resources/sys_patch/sys_patch.py +++ b/resources/sys_patch/sys_patch.py @@ -63,7 +63,7 @@ class PatchSysVolume: if hardware_details is None: hardware_details = sys_patch_detect.detect_root_patch(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,7 @@ 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=None, custom_data_mount_path=None): 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 @@ -87,7 +87,7 @@ class PatchSysVolume: 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 +113,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 +211,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 +221,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 +241,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,7 +328,7 @@ 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() @@ -336,7 +336,7 @@ class PatchSysVolume: 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 +353,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 +380,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 +408,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,7 +432,7 @@ 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}" @@ -441,7 +442,7 @@ class PatchSysVolume: 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 +478,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 +504,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.detect_root_patch(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 +526,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 +540,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 +554,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"]: @@ -569,17 +570,17 @@ class PatchSysVolume: sys_patch_helpers.sys_patch_helpers(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) + 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: @@ -599,11 +600,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 +617,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 +626,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 +635,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 +646,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 +657,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 @@ -691,9 +692,9 @@ class PatchSysVolume: 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: 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: @@ -709,8 +710,8 @@ 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 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..391953d6b 100644 --- a/resources/sys_patch/sys_patch_auto.py +++ b/resources/sys_patch/sys_patch_auto.py @@ -13,14 +13,14 @@ import plistlib import subprocess import webbrowser import logging -from resources import utilities, updates, global_settings, network_handler +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 + def __init__(self, global_constants: constants.Constants()): + self.constants: constants.Constants() = global_constants def start_auto_patch(self): diff --git a/resources/sys_patch/sys_patch_detect.py b/resources/sys_patch/sys_patch_detect.py index d0988b87b..221fa69da 100644 --- a/resources/sys_patch/sys_patch_detect.py +++ b/resources/sys_patch/sys_patch_detect.py @@ -3,17 +3,31 @@ # 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 detect_root_patch: - def __init__(self, model, versions): + def __init__(self, model: str, versions: constants.Constants()): self.model = model self.constants: constants.Constants() = versions self.computer = self.constants.computer diff --git a/resources/sys_patch/sys_patch_helpers.py b/resources/sys_patch/sys_patch_helpers.py index 9cbfe043c..f18b0e2ee 100644 --- a/resources/sys_patch/sys_patch_helpers.py +++ b/resources/sys_patch/sys_patch_helpers.py @@ -1,22 +1,21 @@ # 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 data import os_data +from resources import bplist, constants, generate_smbios, utilities -from resources import kdk_handler, bplist class sys_patch_helpers: - def __init__(self, constants): - self.constants = constants + def __init__(self, global_constants: constants.Constants()): + self.constants: constants.Constants() = global_constants def snb_board_id_patch(self, source_files_path): diff --git a/resources/updates.py b/resources/updates.py index 89807eb42..300d23521 100644 --- a/resources/updates.py +++ b/resources/updates.py @@ -5,16 +5,17 @@ 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" + def __init__(self, global_constants: constants.Constants()): + self.constants: constants.Constants() = global_constants + + self.binary_version = self.constants.patcher_version + self.binary_version_array = [int(x) for x in self.binary_version.split(".")] self.available_binaries = {} @@ -55,9 +56,9 @@ class check_binary_updates: def check_binary_updates(self): # logging.info("- Checking for updates...") - if network_handler.NetworkUtilities(self.binary_url).verify_network_connection(): + if network_handler.NetworkUtilities(REPO_LATEST_RELEASE_URL).verify_network_connection(): # logging.info("- Network connection functional") - response = requests.get(self.binary_url) + response = requests.get(REPO_LATEST_RELEASE_URL) data_set = response.json() # logging.info("- Retrieved latest version data") self.remote_version = data_set["tag_name"] 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 2546cf77b..ad55c36c5 100644 --- a/resources/validation.py +++ b/resources/validation.py @@ -4,6 +4,7 @@ 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 @@ -14,8 +15,10 @@ class PatcherValidation: Primarily for Continuous Integration """ - def __init__(self, constants): - self.constants = constants + def __init__(self, global_constants: constants.Constants()): + self.constants: constants.Constants() = global_constants + + self.constants.validate = True self.valid_dumps = [ example_data.MacBookPro.MacBookPro92_Stock, @@ -47,8 +50,6 @@ class PatcherValidation: example_data.MacBookPro.MacBookPro141_SSD_Upgrade, ] - self.constants.validate = True - self._validate_configs() self._validate_sys_patch() @@ -146,6 +147,15 @@ class PatcherValidation: logging.info("Validating SNB Board ID patcher") self.constants.computer.reported_board_id = "Mac-7BA5B2DFE22DDD8C" sys_patch_helpers.sys_patch_helpers(self.constants).snb_board_id_patch(self.constants.payload_local_binaries_root_path) + + # 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") From e83e260db7820bb06b17558580eef392fe21509a Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 17:59:53 -0700 Subject: [PATCH 39/61] Fix Constants type suggestion --- resources/arguments.py | 5 +++-- resources/defaults.py | 4 ++-- resources/global_settings.py | 11 ++++++----- resources/kdk_handler.py | 7 +++---- resources/main.py | 2 +- resources/reroute_payloads.py | 4 ++-- resources/sys_patch/sys_patch.py | 15 ++++++++++++--- resources/sys_patch/sys_patch_auto.py | 4 ++-- resources/sys_patch/sys_patch_detect.py | 4 ++-- resources/sys_patch/sys_patch_helpers.py | 4 ++-- resources/updates.py | 4 ++-- resources/validation.py | 4 ++-- 12 files changed, 39 insertions(+), 29 deletions(-) diff --git a/resources/arguments.py b/resources/arguments.py index 8f7d0884d..a0eb71f88 100644 --- a/resources/arguments.py +++ b/resources/arguments.py @@ -11,8 +11,9 @@ from data import model_array # Generic building args class arguments: - def __init__(self, global_constants: constants.Constants()): - self.constants: constants.Constants() = global_constants + + def __init__(self, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants self.args = utilities.check_cli_args() diff --git a/resources/defaults.py b/resources/defaults.py index d067d01fd..51dbb7dbc 100644 --- a/resources/defaults.py +++ b/resources/defaults.py @@ -17,8 +17,8 @@ from data import ( class GenerateDefaults: - def __init__(self, model: str, host_is_target: bool, global_constants: constants.Constants()): - self.constants: constants.Constants() = global_constants + def __init__(self, model: str, host_is_target: bool, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants self.model = model self.host_is_target = host_is_target diff --git a/resources/global_settings.py b/resources/global_settings.py index 36d56b5e9..60b4925db 100644 --- a/resources/global_settings.py +++ b/resources/global_settings.py @@ -12,15 +12,16 @@ import subprocess class global_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 +33,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/kdk_handler.py b/resources/kdk_handler.py index bfd1be59c..7d9df9bee 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 diff --git a/resources/main.py b/resources/main.py index 0435561b9..f52dada98 100644 --- a/resources/main.py +++ b/resources/main.py @@ -28,7 +28,7 @@ class OpenCoreLegacyPatcher: def __init__(self): logging_handler.InitializeLoggingSupport() - self.constants: constants = constants.Constants() + self.constants: constants.Constants = constants.Constants() self.constants.wxpython_variant: bool = True diff --git a/resources/reroute_payloads.py b/resources/reroute_payloads.py index f6f35a8de..6f3624ec5 100644 --- a/resources/reroute_payloads.py +++ b/resources/reroute_payloads.py @@ -13,8 +13,8 @@ from resources import constants class RoutePayloadDiskImage: - def __init__(self, global_constants: constants.Constants()): - self.constants: constants.Constants() = global_constants + def __init__(self, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants self._setup_tmp_disk_image() diff --git a/resources/sys_patch/sys_patch.py b/resources/sys_patch/sys_patch.py index 7f80bbc2a..82e2e70b8 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() @@ -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,6 +91,7 @@ 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" diff --git a/resources/sys_patch/sys_patch_auto.py b/resources/sys_patch/sys_patch_auto.py index 391953d6b..52ddfcd10 100644 --- a/resources/sys_patch/sys_patch_auto.py +++ b/resources/sys_patch/sys_patch_auto.py @@ -19,8 +19,8 @@ from resources.gui import gui_main class AutomaticSysPatch: - def __init__(self, global_constants: constants.Constants()): - self.constants: constants.Constants() = global_constants + def __init__(self, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants def start_auto_patch(self): diff --git a/resources/sys_patch/sys_patch_detect.py b/resources/sys_patch/sys_patch_detect.py index 221fa69da..0619bb8d3 100644 --- a/resources/sys_patch/sys_patch_detect.py +++ b/resources/sys_patch/sys_patch_detect.py @@ -27,9 +27,9 @@ from data import ( class detect_root_patch: - def __init__(self, model: str, versions: constants.Constants()): + def __init__(self, model: str, global_constants: constants.Constants): self.model = model - self.constants: constants.Constants() = versions + self.constants: constants.Constants = global_constants self.computer = self.constants.computer # GPU Patch Detection diff --git a/resources/sys_patch/sys_patch_helpers.py b/resources/sys_patch/sys_patch_helpers.py index f18b0e2ee..a256d54a4 100644 --- a/resources/sys_patch/sys_patch_helpers.py +++ b/resources/sys_patch/sys_patch_helpers.py @@ -14,8 +14,8 @@ from resources import bplist, constants, generate_smbios, utilities class sys_patch_helpers: - def __init__(self, global_constants: constants.Constants()): - self.constants: constants.Constants() = global_constants + def __init__(self, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants def snb_board_id_patch(self, source_files_path): diff --git a/resources/updates.py b/resources/updates.py index 300d23521..8ab1fb1c3 100644 --- a/resources/updates.py +++ b/resources/updates.py @@ -11,8 +11,8 @@ REPO_LATEST_RELEASE_URL: str = "https://api.github.com/repos/dortania/OpenCore-L class check_binary_updates: - def __init__(self, global_constants: constants.Constants()): - self.constants: constants.Constants() = global_constants + def __init__(self, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants self.binary_version = self.constants.patcher_version self.binary_version_array = [int(x) for x in self.binary_version.split(".")] diff --git a/resources/validation.py b/resources/validation.py index ad55c36c5..a2e8320a3 100644 --- a/resources/validation.py +++ b/resources/validation.py @@ -15,8 +15,8 @@ class PatcherValidation: Primarily for Continuous Integration """ - def __init__(self, global_constants: constants.Constants()): - self.constants: constants.Constants() = global_constants + def __init__(self, global_constants: constants.Constants): + self.constants: constants.Constants = global_constants self.constants.validate = True From c5eb52ac5e18a89a80ec8c803fe2d1f2c3120a95 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 18:09:02 -0700 Subject: [PATCH 40/61] amfi_detect.py: Use enum --- data/amfi_data.py | 23 +++++++++++----------- resources/amfi_detect.py | 42 ++++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 32 deletions(-) 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/resources/amfi_detect.py b/resources/amfi_detect.py index f13cfa974..dadaa8064 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 From 594f6dcbe5e76ec238f69d435d4364982b2c273d Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 18:16:12 -0700 Subject: [PATCH 41/61] Toolchain: Remove TUI building argument --- Build-Binary.command | 15 +++------------ SOURCE.md | 1 - 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/Build-Binary.command b/Build-Binary.command index a928bdc6f..c6e6de402 100755 --- a/Build-Binary.command +++ b/Build-Binary.command @@ -40,7 +40,6 @@ class create_binary: def parse_arguments(self): 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') @@ -75,14 +74,10 @@ class create_binary: 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.generate_payloads_dmg() 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() @@ -101,12 +96,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: diff --git a/SOURCE.md b/SOURCE.md index 88adc6b88..58184c240 100644 --- a/SOURCE.md +++ b/SOURCE.md @@ -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 From b5b4d84bc9388e3795e684bb27157093fc3e989e Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 18:29:35 -0700 Subject: [PATCH 42/61] Toolchain: Use docstrings --- Build-Binary.command | 207 ++++++++++++++++++++++++++++--------------- 1 file changed, 135 insertions(+), 72 deletions(-) diff --git a/Build-Binary.command b/Build-Binary.command index c6e6de402..1b19510df 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,24 +14,48 @@ import sys from resources import constants + class create_binary: + """ + 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('--branch', type=str, help='Git branch name') parser.add_argument('--commit', type=str, help='Git commit URL') @@ -47,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) @@ -69,21 +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() - 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") - 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( @@ -105,7 +142,12 @@ class create_binary: print(build_result.stderr.decode('utf-8')) raise Exception("Build failed") - def delete_extra_binaries(self): + + def _delete_extra_binaries(self): + """ + Delete extra binaries from payloads directory + """ + delete_files = [ "AutoPkg-Assets.pkg", "AutoPkg-Assets.pkg.zip", @@ -113,6 +155,7 @@ class create_binary: "InstallAssistant.pkg.integrityDataV1", "KDK.dmg", ] + print("- Deleting extra binaries...") for file in Path("payloads").glob(pattern="*"): if file.name in delete_files or file.name.startswith("OpenCore-Legacy-Patcher"): @@ -125,7 +168,12 @@ class create_binary: print(f" - Deleting {file}") subprocess.run(["rm", "-rf", file]) - def download_resources(self): + + def _download_resources(self): + """ + Download required dependencies + """ + patcher_support_pkg_version = constants.Constants().patcher_support_pkg_version required_resources = [ "Universal-Binaries.zip" @@ -172,21 +220,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', @@ -204,7 +260,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" @@ -224,20 +285,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) @@ -248,19 +314,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( @@ -272,9 +331,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"], @@ -285,5 +347,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 From 56efd9743df2d24ef1f6f10bbb248e183c20bef0 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 18:31:30 -0700 Subject: [PATCH 43/61] Docs: Fix .command name --- SOURCE.md | 2 +- docs/MODELS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SOURCE.md b/SOURCE.md index 58184c240..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. 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 From 2f7965440cc98c30320fcca15abeb294a17c395c Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 19:27:25 -0700 Subject: [PATCH 44/61] Remove unused run.py --- CHANGELOG.md | 5 +- resources/gui/gui_main.py | 8 +- resources/run.py | 152 -------------------------------------- 3 files changed, 9 insertions(+), 156 deletions(-) delete mode 100644 resources/run.py 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/resources/gui/gui_main.py b/resources/gui/gui_main.py index 0e28eca88..3fa4ec62e 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, @@ -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(): 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 From cffc463bde8de8827165910fab1c176af10d75ab Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 19:28:15 -0700 Subject: [PATCH 45/61] Toolchain: Implement whitelist system for cleaning --- Build-Binary.command | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/Build-Binary.command b/Build-Binary.command index 1b19510df..92c0e055f 100755 --- a/Build-Binary.command +++ b/Build-Binary.command @@ -15,7 +15,7 @@ import sys from resources import constants -class create_binary: +class CreateBinary: """ Library for creating OpenCore-Patcher application @@ -148,25 +148,39 @@ class create_binary: Delete extra binaries from payloads directory """ - delete_files = [ - "AutoPkg-Assets.pkg", - "AutoPkg-Assets.pkg.zip", - "InstallAssistant.pkg", - "InstallAssistant.pkg.integrityDataV1", - "KDK.dmg", + 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): @@ -349,4 +363,4 @@ class create_binary: if __name__ == "__main__": - create_binary() \ No newline at end of file + CreateBinary() \ No newline at end of file From 520c9c315cd2fffabfae373d3b9b7a41ea483d8f Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 19:43:48 -0700 Subject: [PATCH 46/61] validation.py: Add cleanup to build validation --- resources/validation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/validation.py b/resources/validation.py index a2e8320a3..337152c13 100644 --- a/resources/validation.py +++ b/resources/validation.py @@ -188,4 +188,6 @@ class PatcherValidation: self.constants.serial_settings = "Minimal" self._build_prebuilt() - self._build_dumps() \ No newline at end of file + self._build_dumps() + + subprocess.run(["rm", "-rf", self.constants.build_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) \ No newline at end of file From 418a966081e8e8bf5f2f85e69d9b3a1ffa67eab1 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Feb 2023 20:05:52 -0700 Subject: [PATCH 47/61] updates.py: Adjust private function names --- resources/gui/gui_main.py | 4 +- resources/sys_patch/sys_patch_auto.py | 4 +- resources/updates.py | 116 ++++++++++++++++---------- 3 files changed, 75 insertions(+), 49 deletions(-) diff --git a/resources/gui/gui_main.py b/resources/gui/gui_main.py index 3fa4ec62e..3186916bd 100644 --- a/resources/gui/gui_main.py +++ b/resources/gui/gui_main.py @@ -255,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 @@ -310,7 +310,7 @@ class wx_python_gui: 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"] diff --git a/resources/sys_patch/sys_patch_auto.py b/resources/sys_patch/sys_patch_auto.py index 52ddfcd10..4b93e4441 100644 --- a/resources/sys_patch/sys_patch_auto.py +++ b/resources/sys_patch/sys_patch_auto.py @@ -46,7 +46,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: @@ -128,7 +128,7 @@ class AutomaticSysPatch: return False # 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") diff --git a/resources/updates.py b/resources/updates.py index 8ab1fb1c3..a532df438 100644 --- a/resources/updates.py +++ b/resources/updates.py @@ -10,17 +10,26 @@ 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: +class CheckBinaryUpdates: def __init__(self, global_constants: constants.Constants): self.constants: constants.Constants = global_constants self.binary_version = self.constants.patcher_version self.binary_version_array = [int(x) for x in self.binary_version.split(".")] - self.available_binaries = {} + 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 + """ - def check_if_build_newer(self, remote_version=None, local_version=None): if remote_version is None: remote_version = self.remote_version_array if local_version is None: @@ -40,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: @@ -54,45 +82,43 @@ class check_binary_updates: else: return "Unknown" + def check_binary_updates(self): - # logging.info("- Checking for updates...") - if network_handler.NetworkUtilities(REPO_LATEST_RELEASE_URL).verify_network_connection(): - # logging.info("- Network connection functional") - response = requests.get(REPO_LATEST_RELEASE_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 From b81899092bdeb1b1b671d721c3febd45ee119846 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Fri, 10 Feb 2023 07:24:34 -0700 Subject: [PATCH 48/61] Docs: Remove TUI references --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 - docs/POST-INSTALL.md | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) 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/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. From 2e7afae29b733cac4a0a86a4d24eeb8995317c67 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Fri, 10 Feb 2023 08:02:50 -0700 Subject: [PATCH 49/61] Adjust private function names --- resources/defaults.py | 12 +- resources/global_settings.py | 6 +- resources/gui/gui_main.py | 24 +-- resources/sys_patch/sys_patch.py | 10 +- resources/sys_patch/sys_patch_auto.py | 4 +- resources/sys_patch/sys_patch_detect.py | 253 +++++++++++++++++++----- 6 files changed, 236 insertions(+), 73 deletions(-) diff --git a/resources/defaults.py b/resources/defaults.py index 51dbb7dbc..a4773bd85 100644 --- a/resources/defaults.py +++ b/resources/defaults.py @@ -20,8 +20,8 @@ class GenerateDefaults: def __init__(self, model: str, host_is_target: bool, global_constants: constants.Constants): self.constants: constants.Constants = global_constants - self.model = model - self.host_is_target = host_is_target + self.model: str = model + self.host_is_target: str = host_is_target # Reset Variables self.constants.sip_status: bool = True @@ -55,11 +55,11 @@ class GenerateDefaults: 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: @@ -76,10 +76,10 @@ class GenerateDefaults: # 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 diff --git a/resources/global_settings.py b/resources/global_settings.py index 60b4925db..cdbb84ef8 100644 --- a/resources/global_settings.py +++ b/resources/global_settings.py @@ -9,7 +9,11 @@ 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: str = ".com.dortania.opencore-legacy-patcher.plist" diff --git a/resources/gui/gui_main.py b/resources/gui/gui_main.py index 3186916bd..92957c215 100644 --- a/resources/gui/gui_main.py +++ b/resources/gui/gui_main.py @@ -306,7 +306,7 @@ 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 @@ -330,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") @@ -588,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 @@ -1078,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): @@ -2988,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(): @@ -3011,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(): @@ -3098,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): diff --git a/resources/sys_patch/sys_patch.py b/resources/sys_patch/sys_patch.py index 82e2e70b8..584849375 100644 --- a/resources/sys_patch/sys_patch.py +++ b/resources/sys_patch/sys_patch.py @@ -61,7 +61,7 @@ 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) @@ -518,7 +518,7 @@ class PatchSysVolume: if 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() @@ -685,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 @@ -699,7 +699,7 @@ 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: @@ -718,7 +718,7 @@ 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 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: diff --git a/resources/sys_patch/sys_patch_auto.py b/resources/sys_patch/sys_patch_auto.py index 4b93e4441..364354b4b 100644 --- a/resources/sys_patch/sys_patch_auto.py +++ b/resources/sys_patch/sys_patch_auto.py @@ -31,7 +31,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: @@ -161,7 +161,7 @@ class AutomaticSysPatch: # 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 diff --git a/resources/sys_patch/sys_patch_detect.py b/resources/sys_patch/sys_patch_detect.py index 0619bb8d3..8de338bc8 100644 --- a/resources/sys_patch/sys_patch_detect.py +++ b/resources/sys_patch/sys_patch_detect.py @@ -26,10 +26,16 @@ from data import ( ) -class detect_root_patch: +class DetectRootPatch: + """ + Library for querying root volume patches applicable for booted system + """ + def __init__(self, model: str, global_constants: constants.Constants): - self.model = model + self.model: str = model + self.constants: constants.Constants = global_constants + self.computer = self.constants.computer # GPU Patch Detection @@ -77,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): @@ -89,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: @@ -179,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: @@ -187,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: @@ -234,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 @@ -286,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: @@ -295,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: @@ -323,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: @@ -335,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: @@ -347,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 @@ -379,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 @@ -413,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 @@ -449,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, @@ -481,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, @@ -497,7 +595,15 @@ class detect_root_patch: return self.root_patch_dict - def get_amfi_level_needed(self): + + 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 True: if self.constants.detected_os > os_data.os_data.catalina: if self.constants.detected_os >= os_data.os_data.ventura: @@ -508,19 +614,30 @@ class detect_root_patch: 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 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: @@ -591,25 +708,51 @@ class detect_root_patch: else: return True - def verify_unpatch_allowed(self, print_errors=False): - # Must be called after verify_patch_allowed + + 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"]}) @@ -617,23 +760,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"]}) @@ -641,6 +789,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"]}) @@ -655,11 +804,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"]}) @@ -670,6 +821,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"]}) @@ -680,6 +832,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"]}) @@ -687,20 +840,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"]}) From 040edfdd25c32ee7e244ef9e02ef3edf67645562 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Fri, 10 Feb 2023 08:04:52 -0700 Subject: [PATCH 50/61] sys_patch_detect.py: Adjust return --- resources/sys_patch/sys_patch_detect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/sys_patch/sys_patch_detect.py b/resources/sys_patch/sys_patch_detect.py index 8de338bc8..f97cf519e 100644 --- a/resources/sys_patch/sys_patch_detect.py +++ b/resources/sys_patch/sys_patch_detect.py @@ -705,8 +705,8 @@ class DetectRootPatch: ] ): return False - else: - return True + + return True def _verify_unpatch_allowed(self): From 0f7f079dd8015f5f5fc1376c9bd1aff87d07fa67 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Fri, 10 Feb 2023 08:25:02 -0700 Subject: [PATCH 51/61] sys_patch_detect.py: Avoid nested if statements --- resources/sys_patch/sys_patch_detect.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/resources/sys_patch/sys_patch_detect.py b/resources/sys_patch/sys_patch_detect.py index f97cf519e..4474a9360 100644 --- a/resources/sys_patch/sys_patch_detect.py +++ b/resources/sys_patch/sys_patch_detect.py @@ -604,15 +604,19 @@ class DetectRootPatch: int: AMFI level needed """ - 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 + 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): From 12b7cf7fcddb8c374a055e8edc2611828bccae1e Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Fri, 10 Feb 2023 12:02:23 -0700 Subject: [PATCH 52/61] amfi_detect.py: Resolve regression --- resources/amfi_detect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/amfi_detect.py b/resources/amfi_detect.py index dadaa8064..0e09fb80c 100644 --- a/resources/amfi_detect.py +++ b/resources/amfi_detect.py @@ -68,7 +68,7 @@ class AmfiConfigurationDetection: amfi_value = 0 for arg in self.boot_args: - if not arg.startswith("amfi"): + if not arg.startswith("amfi="): continue try: amfi_value = arg.split("=") From 8806d29a35d7ff7e7652bd9cce1fc62bf17552b8 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Fri, 10 Feb 2023 12:14:06 -0700 Subject: [PATCH 53/61] defaulkts.py: Fix type suggestion --- resources/defaults.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/defaults.py b/resources/defaults.py index a4773bd85..ccae5a293 100644 --- a/resources/defaults.py +++ b/resources/defaults.py @@ -29,7 +29,8 @@ class GenerateDefaults: self.constants.disable_cs_lv: bool = False self.constants.disable_amfi: bool = False self.constants.fu_status: bool = True - self.constants.fu_arguments: bool = None + + self.constants.fu_arguments: str = None self.constants.custom_serial_number: str = "" self.constants.custom_board_serial_number: str = "" From 0d7186236efeee8508b05426a1d239742fe70bbc Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Fri, 10 Feb 2023 12:14:42 -0700 Subject: [PATCH 54/61] gui_main.py: Supress defaults stderr --- resources/gui/gui_main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/gui/gui_main.py b/resources/gui/gui_main.py index 92957c215..a76c03516 100644 --- a/resources/gui/gui_main.py +++ b/resources/gui/gui_main.py @@ -3818,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: From 44369e2faadce48b23d7bb499aa09d5ff2e968ea Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Fri, 10 Feb 2023 12:24:27 -0700 Subject: [PATCH 55/61] kdk_handler.py: Add todo for file size check --- resources/kdk_handler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/kdk_handler.py b/resources/kdk_handler.py index 7d9df9bee..d13985f30 100644 --- a/resources/kdk_handler.py +++ b/resources/kdk_handler.py @@ -564,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:") From 6a3023301a3d40ed54e254d4090c38646907cb61 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Fri, 10 Feb 2023 13:23:40 -0700 Subject: [PATCH 56/61] sys_patch_helpers.py: Add docstring comments --- resources/sys_patch/sys_patch.py | 10 +- resources/sys_patch/sys_patch_auto.py | 86 +++++++---- resources/sys_patch/sys_patch_helpers.py | 179 +++++++++++++++-------- resources/validation.py | 4 +- 4 files changed, 187 insertions(+), 92 deletions(-) diff --git a/resources/sys_patch/sys_patch.py b/resources/sys_patch/sys_patch.py index 584849375..5b2036e13 100644 --- a/resources/sys_patch/sys_patch.py +++ b/resources/sys_patch/sys_patch.py @@ -340,7 +340,7 @@ class PatchSysVolume: self._remove_file("/private/var/db/SystemPolicyConfiguration/", file) else: # Install RSRHelper utility to handle desynced KCs - sys_patch_helpers.sys_patch_helpers(self.constants).install_rsr_repair_binary() + sys_patch_helpers.SysPatchHelpers(self.constants).install_rsr_repair_binary() logging.info("- Successfully built new kernel cache") return True @@ -445,7 +445,7 @@ class PatchSysVolume: destination_path = f"{self.mount_location}/System/Library/CoreServices" file_name = "OpenCore-Legacy-Patcher.plist" destination_path_file = f"{destination_path}/{file_name}" - if sys_patch_helpers.sys_patch_helpers(self.constants).generate_patchset_plist(patchset, file_name, self.kdk_path): + if sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, file_name, self.kdk_path): logging.info("- Writing patchset information to Root Volume") if Path(destination_path_file).exists(): utilities.process_status(utilities.elevated(["rm", destination_path_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) @@ -576,9 +576,9 @@ class PatchSysVolume: logging.info(f"- Running Process:\n{process}") utilities.process_status(subprocess.run(process, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)) if any(x in required_patches for x in ["AMD Legacy GCN", "AMD Legacy Polaris", "AMD Legacy Vega"]): - sys_patch_helpers.sys_patch_helpers(self.constants).disable_window_server_caching() + sys_patch_helpers.SysPatchHelpers(self.constants).disable_window_server_caching() if any(x in required_patches for x in ["Intel Ivy Bridge", "Intel Haswell"]): - sys_patch_helpers.sys_patch_helpers(self.constants).remove_news_widgets() + sys_patch_helpers.SysPatchHelpers(self.constants).remove_news_widgets() self._write_patchset(required_patches) def _preflight_checks(self, required_patches, source_files_path): @@ -593,7 +593,7 @@ class PatchSysVolume: # Make sure SNB kexts are compatible with the host if "Intel Sandy Bridge" in required_patches: - sys_patch_helpers.sys_patch_helpers(self.constants).snb_board_id_patch(source_files_path) + sys_patch_helpers.SysPatchHelpers(self.constants).snb_board_id_patch(source_files_path) for patch in required_patches: # Check if all files are present diff --git a/resources/sys_patch/sys_patch_auto.py b/resources/sys_patch/sys_patch_auto.py index 364354b4b..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 pathlib import Path + from resources import utilities, updates, global_settings, network_handler, constants from resources.sys_patch import sys_patch_detect from resources.gui import gui_main + class AutomaticSysPatch: + """ + Library of functions for launch agent, including automatic patching + """ def __init__(self, global_constants: constants.Constants): self.constants: constants.Constants = global_constants def start_auto_patch(self): + """ + Initiates automatic patching + + Auto Patching's main purpose is to try and tell the user they're missing root patches + New users may not realize OS updates remove our patches, so we try and run when nessasary + + Conditions for running: + - Verify running GUI (TUI users can write their own scripts) + - Verify the Snapshot Seal is intact (if not, assume user is running patches) + - Verify this model needs patching (if not, assume user upgraded hardware and OCLP was not removed) + - Verify there are no updates for OCLP (ensure we have the latest patch sets) + + If all these tests pass, start Root Patcher + + """ + logging.info("- Starting Automatic Patching") if self.constants.wxpython_variant is False: logging.info("- Auto Patch option is not supported on TUI, please use GUI") @@ -113,26 +126,35 @@ class AutomaticSysPatch: else: logging.info("- Detected Snapshot seal not intact, skipping") - if self.determine_if_versions_match() is False: - self.determine_if_boot_matches() + if self._determine_if_versions_match(): + self._determine_if_boot_matches() - def determine_if_versions_match(self): + def _determine_if_versions_match(self): + """ + Determine if the booted version of OCLP matches the installed version + + ie. Installed app is 0.2.0, but EFI version is 0.1.0 + + Returns: + bool: True if versions match, False if not + """ + logging.info("- Checking booted vs installed OCLP Build") if self.constants.computer.oclp_version is None: logging.info("- Booted version not found") - return False + return True if self.constants.computer.oclp_version == self.constants.patcher_version: logging.info("- Versions match") - return False + return True # Check if installed version is newer than booted version if updates.CheckBinaryUpdates(self.constants)._check_if_build_newer( self.constants.computer.oclp_version.split("."), self.constants.patcher_version.split(".") ) is True: logging.info("- Installed version is newer than booted version") - return False + return True args = [ "osascript", @@ -150,17 +172,24 @@ class AutomaticSysPatch: self.constants.start_build_install = True gui_main.wx_python_gui(self.constants).main_menu(None) - return True + return False - def determine_if_boot_matches(self): - # Goal of this function is to determine whether the user - # is using a USB drive to Boot OpenCore but macOS does not - # reside on the same drive as the USB. - # If we determine them to be mismatched, notify the user - # and ask if they want to install to install to disk + def _determine_if_boot_matches(self): + """ + Determine if the boot drive matches the macOS drive + ie. Booted from USB, but macOS is on internal disk + + Goal of this function is to determine whether the user + is using a USB drive to Boot OpenCore but macOS does not + reside on the same drive as the USB. + + If we determine them to be mismatched, notify the user + and ask if they want to install to install to disk. + """ logging.info("- Determining if macOS drive matches boot drive") + should_notify = global_settings.GlobalEnviromentSettings().read_property("AutoPatch_Notify_Mismatched_Disks") if should_notify is False: logging.info("- Skipping due to user preference") @@ -223,9 +252,16 @@ class AutomaticSysPatch: def install_auto_patcher_launch_agent(self): - # Installs the following: - # - OpenCore-Patcher.app in /Library/Application Support/Dortania/ - # - com.dortania.opencore-legacy-patcher.auto-patch.plist in /Library/LaunchAgents/ + """ + Install the Auto Patcher Launch Agent + + Installs the following: + - OpenCore-Patcher.app in /Library/Application Support/Dortania/ + - com.dortania.opencore-legacy-patcher.auto-patch.plist in /Library/LaunchAgents/ + + See start_auto_patch() comments for more info + """ + if self.constants.launcher_script is not None: logging.info("- Skipping Auto Patcher Launch Agent, not supported when running from source") return diff --git a/resources/sys_patch/sys_patch_helpers.py b/resources/sys_patch/sys_patch_helpers.py index a256d54a4..ad4e4ef2c 100644 --- a/resources/sys_patch/sys_patch_helpers.py +++ b/resources/sys_patch/sys_patch_helpers.py @@ -12,46 +12,75 @@ from data import os_data from resources import bplist, constants, generate_smbios, utilities -class sys_patch_helpers: +class SysPatchHelpers: + """ + Library of helper functions for sys_patch.py and related libraries + """ def __init__(self, global_constants: constants.Constants): self.constants: constants.Constants = global_constants - def snb_board_id_patch(self, source_files_path): - # AppleIntelSNBGraphicsFB hard codes the supported Board IDs for Sandy Bridge iGPUs - # Because of this, the kext errors out on unsupported systems - # This function simply patches in a supported Board ID, using 'determine_best_board_id_for_sandy()' - # to supplement the ideal Board ID + def snb_board_id_patch(self, source_files_path: str): + """ + Patch AppleIntelSNBGraphicsFB.kext to support unsupported Board IDs + + AppleIntelSNBGraphicsFB hard codes the supported Board IDs for Sandy Bridge iGPUs + Because of this, the kext errors out on unsupported systems + This function simply patches in a supported Board ID, using 'determine_best_board_id_for_sandy()' + to supplement the ideal Board ID + + Parameters: + source_files_path (str): Path to the source files + + """ + source_files_path = str(source_files_path) - if self.constants.computer.reported_board_id not in self.constants.sandy_board_id_stock: - logging.info(f"- Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching") - board_to_patch = generate_smbios.determine_best_board_id_for_sandy(self.constants.computer.reported_board_id, self.constants.computer.gpus) - logging.info(f"- Replacing {board_to_patch} with {self.constants.computer.reported_board_id}") - board_to_patch_hex = bytes.fromhex(board_to_patch.encode('utf-8').hex()) - reported_board_hex = bytes.fromhex(self.constants.computer.reported_board_id.encode('utf-8').hex()) + if self.constants.computer.reported_board_id in self.constants.sandy_board_id_stock: + return - if len(board_to_patch_hex) > len(reported_board_hex): - # Pad the reported Board ID with zeros to match the length of the board to patch - reported_board_hex = reported_board_hex + bytes(len(board_to_patch_hex) - len(reported_board_hex)) - elif len(board_to_patch_hex) < len(reported_board_hex): - logging.info(f"- Error: Board ID {self.constants.computer.reported_board_id} is longer than {board_to_patch}") - raise Exception("Host's Board ID is longer than the kext's Board ID, cannot patch!!!") + logging.info(f"- Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching") - path = source_files_path + "/10.13.6/System/Library/Extensions/AppleIntelSNBGraphicsFB.kext/Contents/MacOS/AppleIntelSNBGraphicsFB" - if Path(path).exists(): - with open(path, 'rb') as f: - data = f.read() - data = data.replace(board_to_patch_hex, reported_board_hex) - with open(path, 'wb') as f: - f.write(data) - else: - logging.info(f"- Error: Could not find {path}") - raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!") + board_to_patch = generate_smbios.determine_best_board_id_for_sandy(self.constants.computer.reported_board_id, self.constants.computer.gpus) + logging.info(f"- Replacing {board_to_patch} with {self.constants.computer.reported_board_id}") + + board_to_patch_hex = bytes.fromhex(board_to_patch.encode('utf-8').hex()) + reported_board_hex = bytes.fromhex(self.constants.computer.reported_board_id.encode('utf-8').hex()) + + if len(board_to_patch_hex) > len(reported_board_hex): + # Pad the reported Board ID with zeros to match the length of the board to patch + reported_board_hex = reported_board_hex + bytes(len(board_to_patch_hex) - len(reported_board_hex)) + elif len(board_to_patch_hex) < len(reported_board_hex): + logging.info(f"- Error: Board ID {self.constants.computer.reported_board_id} is longer than {board_to_patch}") + raise Exception("Host's Board ID is longer than the kext's Board ID, cannot patch!!!") + + path = source_files_path + "/10.13.6/System/Library/Extensions/AppleIntelSNBGraphicsFB.kext/Contents/MacOS/AppleIntelSNBGraphicsFB" + if not Path(path).exists(): + logging.info(f"- Error: Could not find {path}") + raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!") + + with open(path, 'rb') as f: + data = f.read() + data = data.replace(board_to_patch_hex, reported_board_hex) + with open(path, 'wb') as f: + f.write(data) - def generate_patchset_plist(self, patchset, file_name, kdk_used): + def generate_patchset_plist(self, patchset: dict, file_name: str, kdk_used: Path): + """ + Generate patchset file for user reference + + Parameters: + patchset (dict): Dictionary of patchset, see sys_patch_detect.py and sys_patch_dict.py + file_name (str): Name of the file to write to + kdk_used (Path): Path to the KDK used, if any + + Returns: + bool: True if successful, False if not + + """ + source_path = f"{self.constants.payload_path}" source_path_file = f"{source_path}/{file_name}" @@ -67,23 +96,35 @@ class sys_patch_helpers: "Kernel Debug Kit Used": f"{kdk_string}", "OS Version": f"{self.constants.detected_os}.{self.constants.detected_os_minor} ({self.constants.detected_os_build})", } + data.update(patchset) + if Path(source_path_file).exists(): os.remove(source_path_file) + # Need to write to a safe location plistlib.dump(data, Path(source_path_file).open("wb"), sort_keys=False) + if Path(source_path_file).exists(): return True + return False def disable_window_server_caching(self): - # On legacy GCN GPUs, the WindowServer cache generated creates - # corrupted Opaque shaders. - # To work-around this, we disable WindowServer caching - # And force macOS into properly generating the Opaque shaders + """ + Disable WindowServer's asset caching + + On legacy GCN GPUs, the WindowServer cache generated creates + corrupted Opaque shaders. + + To work-around this, we disable WindowServer caching + And force macOS into properly generating the Opaque shaders + """ + if self.constants.detected_os < os_data.os_data.ventura: return + logging.info("- Disabling WindowServer Caching") # Invoke via 'bash -c' to resolve pathing utilities.elevated(["bash", "-c", "rm -rf /private/var/folders/*/*/*/WindowServer/com.apple.WindowServer"]) @@ -95,11 +136,17 @@ class sys_patch_helpers: def remove_news_widgets(self): - # On Ivy Bridge and Haswell iGPUs, RenderBox will crash the News Widgets in - # Notification Centre. To ensure users can access Notifications normally, - # we manually remove all News Widgets + """ + Remove News Widgets from Notification Centre + + On Ivy Bridge and Haswell iGPUs, RenderBox will crash the News Widgets in + Notification Centre. To ensure users can access Notifications normally, + we manually remove all News Widgets + """ + if self.constants.detected_os < os_data.os_data.ventura: return + logging.info("- Parsing Notification Centre Widgets") file_path = "~/Library/Containers/com.apple.notificationcenterui/Data/Library/Preferences/com.apple.notificationcenterui.plist" file_path = Path(file_path).expanduser() @@ -111,22 +158,27 @@ class sys_patch_helpers: did_find = False with open(file_path, "rb") as f: data = plistlib.load(f) - if "widgets" in data: - if "instances" in data["widgets"]: - for widget in list(data["widgets"]["instances"]): - widget_data = bplist.BPListReader(widget).parse() - for entry in widget_data: - if not 'widget' in entry: - continue - sub_data = bplist.BPListReader(widget_data[entry]).parse() - for sub_entry in sub_data: - if not '$object' in sub_entry: - continue - if not b'com.apple.news' in sub_data[sub_entry][2]: - continue - logging.info(f" - Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}") - data["widgets"]["instances"].remove(widget) - did_find = True + if "widgets" not in data: + return + + if "instances" not in data["widgets"]: + return + + for widget in list(data["widgets"]["instances"]): + widget_data = bplist.BPListReader(widget).parse() + for entry in widget_data: + if 'widget' not in entry: + continue + sub_data = bplist.BPListReader(widget_data[entry]).parse() + for sub_entry in sub_data: + if not '$object' in sub_entry: + continue + if not b'com.apple.news' in sub_data[sub_entry][2]: + continue + logging.info(f" - Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}") + data["widgets"]["instances"].remove(widget) + did_find = True + if did_find: with open(file_path, "wb") as f: plistlib.dump(data, f, sort_keys=False) @@ -134,16 +186,23 @@ class sys_patch_helpers: def install_rsr_repair_binary(self): - # With macOS 13.2, Apple implemented the Rapid Security Response System - # However Apple added a half baked snapshot reversion system if seal was broken, - # which forgets to handle Preboot BootKC syncing + """ + Installs RSRRepair - # Thus this application will try to re-sync the BootKC with SysKC in the event of a panic - # Reference: https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1019 + RSRRepair is a utility that will sync the SysKC and BootKC in the event of a panic - # This is a (hopefully) temporary work-around, however likely to stay. - # RSRRepair has the added bonus of fixing desynced KCs from 'bless', so useful in Big Sur+ - # https://github.com/flagersgit/RSRRepair + With macOS 13.2, Apple implemented the Rapid Security Response System + However Apple added a half baked snapshot reversion system if seal was broken, + which forgets to handle Preboot BootKC syncing. + + Thus this application will try to re-sync the BootKC with SysKC in the event of a panic + Reference: https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1019 + + This is a (hopefully) temporary work-around, however likely to stay. + RSRRepair has the added bonus of fixing desynced KCs from 'bless', so useful in Big Sur+ + Source: https://github.com/flagersgit/RSRRepair + + """ if self.constants.detected_os < os_data.os_data.big_sur: return diff --git a/resources/validation.py b/resources/validation.py index 337152c13..709117972 100644 --- a/resources/validation.py +++ b/resources/validation.py @@ -117,7 +117,7 @@ class PatcherValidation: raise Exception(f"Failed to find {source_file}") logging.info(f"- Validating against Darwin {major_kernel}.{minor_kernel}") - if not sys_patch_helpers.sys_patch_helpers(self.constants).generate_patchset_plist(patchset, f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist", None): + if not sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist", None): raise Exception("Failed to generate patchset plist") # Remove the plist file after validation @@ -146,7 +146,7 @@ class PatcherValidation: self._validate_root_patch_files(supported_os, i) logging.info("Validating SNB Board ID patcher") self.constants.computer.reported_board_id = "Mac-7BA5B2DFE22DDD8C" - sys_patch_helpers.sys_patch_helpers(self.constants).snb_board_id_patch(self.constants.payload_local_binaries_root_path) + sys_patch_helpers.SysPatchHelpers(self.constants).snb_board_id_patch(self.constants.payload_local_binaries_root_path) # Clean up subprocess.run( From ca24aa6ce5a171820a5abb9ae9babf3778102343 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Tue, 7 Mar 2023 17:44:05 -0700 Subject: [PATCH 57/61] Sync PatcherSupportPkg Back from surgery, OCLP development should start to slowly ramp up again! --- CHANGELOG.md | 9 ++++++++- resources/constants.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd1badd9d..59f3ed5a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,13 @@ - Paired along side AutoPkgInstaller - Implement Kernel Debug Kit backup system - Allows for easy restoration of KDKs if OS updates corrupted installed KDKs +- Update Wireless binaries + - Fixed WiFi preferences crash with legacy wifi patches +- Update non-Metal Binaries + - Improved menubar blur saturation + - Fixed System Settings hover effects, including Bluetooth connect button + - Add Books hacks (reimplement cover image generation, disable broken page curl animation) + - Fixed unresponsive buttons - Backend Changes: - Refactored kdk_handler.py - Prioritizes KdkSupportPkg repository for downloads @@ -38,7 +45,7 @@ - pyinstaller - 5.7.0 - packaging - 23.0 - Increment Binaries: - - PatcherSupportPkg 0.8.3 - release + - PatcherSupportPkg 0.8.4 - release - AutoPkgInstaller 1.0.2 - release ## 0.6.1 diff --git a/resources/constants.py b/resources/constants.py index 991bce4de..de11c79de 100644 --- a/resources/constants.py +++ b/resources/constants.py @@ -13,7 +13,7 @@ class Constants: def __init__(self): # Patcher Versioning self.patcher_version = "0.6.2" # OpenCore-Legacy-Patcher - self.patcher_support_pkg_version = "0.8.3" # PatcherSupportPkg + self.patcher_support_pkg_version = "0.8.4" # PatcherSupportPkg self.url_patcher_support_pkg = "https://github.com/dortania/PatcherSupportPkg/releases/download/" self.nightly_url_patcher_support_pkg = "https://nightly.link/dortania/PatcherSupportPkg/workflows/build/master/" self.discord_link = "https://discord.gg/rqdPgH8xSN" From adec8ebd05253498f527071966b6830c160e03e2 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Thu, 9 Mar 2023 08:54:05 -0700 Subject: [PATCH 58/61] sys_patch.py: Add VA driver patch --- data/sys_patch_dict.py | 29 +++++++++++++++++++++++++ resources/constants.py | 2 +- resources/sys_patch/sys_patch_detect.py | 3 +++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/data/sys_patch_dict.py b/data/sys_patch_dict.py index 908411166..63090c6e3 100644 --- a/data/sys_patch_dict.py +++ b/data/sys_patch_dict.py @@ -296,6 +296,29 @@ def SystemPatchDictionary(os_major, os_minor, non_metal_os_support): }, }, + # Primarily for AMD GCN GPUs + "Revert GVA Downgrade": { + "Display Name": "", + "OS Support": { + "Minimum OS Support": { + "OS Major": os_data.os_data.ventura, + "OS Minor": 0 + }, + "Maximum OS Support": { + "OS Major": os_data.os_data.max_os, + "OS Minor": 99 + }, + }, + "Remove": { + "/System/Library/PrivateFrameworks/AppleGVA.framework/Versions/A/": [ + "AppleGVA", + ], + "/System/Library/PrivateFrameworks/AppleGVACore.framework/Versions/A/": [ + "AppleGVACore", + ], + }, + }, + # For GPUs last natively supported in Catalina/Big Sur # Restores DRM support "Catalina GVA": { @@ -657,6 +680,8 @@ def SystemPatchDictionary(os_major, os_minor, non_metal_os_support): "AMDFramebuffer.kext": "12.5", "AMDSupport.kext": "12.5", + "AMDRadeonVADriver.bundle": "12.5", + "AMDRadeonVADriver2.bundle": "12.5", "AMDRadeonX4000GLDriver.bundle": "12.5", "AMDMTLBronzeDriver.bundle": "12.5", "AMDShared.bundle": "12.5", @@ -680,7 +705,9 @@ def SystemPatchDictionary(os_major, os_minor, non_metal_os_support): "Install": { "/System/Library/Extensions": { "AMDRadeonX4000.kext": "12.5", + "AMDRadeonX4000HWServices.kext": "12.5", + "AMDRadeonVADriver2.bundle": "12.5", "AMDRadeonX4000GLDriver.bundle": "12.5", "AMDMTLBronzeDriver.bundle": "12.5", "AMDShared.bundle": "12.5", @@ -702,7 +729,9 @@ def SystemPatchDictionary(os_major, os_minor, non_metal_os_support): "Install": { "/System/Library/Extensions": { "AMDRadeonX5000.kext": "12.5", + "AMDRadeonX5000HWServices.kext": "12.5", + "AMDRadeonVADriver2.bundle": "12.5", "AMDRadeonX5000GLDriver.bundle": "12.5", "AMDRadeonX5000MTLDriver.bundle": "12.5", "AMDRadeonX5000Shared.bundle": "12.5", diff --git a/resources/constants.py b/resources/constants.py index de11c79de..e74ffaca9 100644 --- a/resources/constants.py +++ b/resources/constants.py @@ -13,7 +13,7 @@ class Constants: def __init__(self): # Patcher Versioning self.patcher_version = "0.6.2" # OpenCore-Legacy-Patcher - self.patcher_support_pkg_version = "0.8.4" # PatcherSupportPkg + self.patcher_support_pkg_version = "0.8.5" # PatcherSupportPkg self.url_patcher_support_pkg = "https://github.com/dortania/PatcherSupportPkg/releases/download/" self.nightly_url_patcher_support_pkg = "https://nightly.link/dortania/PatcherSupportPkg/workflows/build/master/" self.discord_link = "https://discord.gg/rqdPgH8xSN" diff --git a/resources/sys_patch/sys_patch_detect.py b/resources/sys_patch/sys_patch_detect.py index 4474a9360..fb150b10e 100644 --- a/resources/sys_patch/sys_patch_detect.py +++ b/resources/sys_patch/sys_patch_detect.py @@ -834,6 +834,7 @@ class DetectRootPatch: required_patches.update({"AMD Legacy GCN": all_hardware_patchset["Graphics"]["AMD Legacy GCN"]}) else: required_patches.update({"AMD Legacy Polaris": all_hardware_patchset["Graphics"]["AMD Legacy Polaris"]}) + required_patches.update({"Revert GVA Downgrade": all_hardware_patchset["Graphics"]["Revert GVA Downgrade"]}) if "AVX2" not in self.constants.computer.cpu.leafs: required_patches.update({"AMD OpenCL": all_hardware_patchset["Graphics"]["AMD OpenCL"]}) @@ -844,6 +845,8 @@ class DetectRootPatch: 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"]}) + else: + required_patches.update({"Revert GVA Downgrade": all_hardware_patchset["Graphics"]["Revert GVA Downgrade"]}) if hardware_details["Brightness: Legacy Backlight Control"] is True: required_patches.update({"Legacy Backlight Control": all_hardware_patchset["Brightness"]["Legacy Backlight Control"]}) From 7b7f68453a4c7ab14c8b51832bb412415c391393 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sat, 11 Mar 2023 08:41:45 -0700 Subject: [PATCH 59/61] macos_installer_handler.py: Reworked from installer.py module --- CHANGELOG.md | 1 + resources/gui/gui_main.py | 18 +- resources/installer.py | 440 -------------------- resources/macos_installer_handler.py | 590 +++++++++++++++++++++++++++ 4 files changed, 601 insertions(+), 448 deletions(-) delete mode 100644 resources/installer.py create mode 100644 resources/macos_installer_handler.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 59f3ed5a4..2c50933a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - Allows for more reliable network calls and downloads - Better supports network timeouts and disconnects - Dramatically less noise in console during downloads + - Implemented new macOS Installer handler - Removed unused modules: - sys_patch_downloader.py - run.py diff --git a/resources/gui/gui_main.py b/resources/gui/gui_main.py index a76c03516..8a4d383d9 100644 --- a/resources/gui/gui_main.py +++ b/resources/gui/gui_main.py @@ -31,14 +31,14 @@ from resources import ( constants, defaults, install, - installer, utilities, generate_smbios, updates, integrity_verification, global_settings, kdk_handler, - network_handler + network_handler, + macos_installer_handler ) from resources.sys_patch import sys_patch_detect, sys_patch @@ -1652,7 +1652,9 @@ class wx_python_gui: # Download installer catalog if ias is None: def ia(): - self.available_installers = installer.list_downloadable_macOS_installers(self.constants.payload_path, "DeveloperSeed") + remote_obj = macos_installer_handler.RemoteInstallerCatalog(seed_override=macos_installer_handler.SeedType.DeveloperSeed) + self.available_installers = remote_obj.available_apps + self.available_installers_latest = remote_obj.available_apps_latest logging.info("- Downloading installer catalog...") thread_ia = threading.Thread(target=ia) @@ -1690,7 +1692,7 @@ class wx_python_gui: i = -20 if available_installers: if ias is None: - available_installers = installer.only_list_newest_installers(available_installers) + available_installers = self.available_installers_latest for app in available_installers: logging.info(f"macOS {available_installers[app]['Version']} ({available_installers[app]['Build']}):\n - Size: {utilities.human_fmt(available_installers[app]['Size'])}\n - Source: {available_installers[app]['Source']}\n - Variant: {available_installers[app]['Variant']}\n - Link: {available_installers[app]['Link']}\n") if available_installers[app]['Variant'] in ["DeveloperSeed" , "PublicSeed"]: @@ -1964,7 +1966,7 @@ class wx_python_gui: self.header.Centre(wx.HORIZONTAL) self.verifying_chunk_label.SetLabel("Installing into Applications folder") self.verifying_chunk_label.Centre(wx.HORIZONTAL) - thread_install = threading.Thread(target=installer.install_macOS_installer, args=(self.constants.payload_path,)) + thread_install = threading.Thread(target=macos_installer_handler.InstallerCreation().install_macOS_installer, args=(self.constants.payload_path,)) thread_install.start() self.progress_bar.Pulse() while thread_install.is_alive(): @@ -2001,7 +2003,7 @@ class wx_python_gui: # Spawn thread to get list of installers def get_installers(): - self.available_installers = installer.list_local_macOS_installers() + self.available_installers = macos_installer_handler.LocalInstallerCatalog().available_apps thread_get_installers = threading.Thread(target=get_installers) thread_get_installers.start() @@ -2105,7 +2107,7 @@ class wx_python_gui: self.usb_selection_label.Centre(wx.HORIZONTAL) i = -15 - available_disks = installer.list_disk_to_format() + available_disks = macos_installer_handler.InstallerCreation().list_disk_to_format() if available_disks: logging.info("Disks found") for disk in available_disks: @@ -2305,7 +2307,7 @@ class wx_python_gui: self.return_to_main_menu.Enable() def prepare_script(self, installer_path, disk): - self.prepare_result = installer.generate_installer_creation_script(self.constants.payload_path, installer_path, disk) + self.prepare_result = macos_installer_handler.InstallerCreation().generate_installer_creation_script(self.constants.payload_path, installer_path, disk) def start_script(self): utilities.disable_sleep_while_running() diff --git a/resources/installer.py b/resources/installer.py deleted file mode 100644 index aa5986bed..000000000 --- a/resources/installer.py +++ /dev/null @@ -1,440 +0,0 @@ -# Creates a macOS Installer -from pathlib import Path -import plistlib -import subprocess -import tempfile -import logging -from resources import utilities, network_handler - -def list_local_macOS_installers(): - # Finds all applicable macOS installers - # within a user's /Applications folder - # Returns a list of installers - application_list = {} - - for application in Path("/Applications").iterdir(): - # Verify whether application has createinstallmedia - try: - if (Path("/Applications") / Path(application) / Path("Contents/Resources/createinstallmedia")).exists(): - plist = plistlib.load((Path("/Applications") / Path(application) / Path("Contents/Info.plist")).open("rb")) - try: - # Doesn't reflect true OS build, but best to report SDK in the event multiple installers are found with same version - app_version = plist["DTPlatformVersion"] - clean_name = plist["CFBundleDisplayName"] - try: - app_sdk = plist["DTSDKBuild"] - except KeyError: - app_sdk = "Unknown" - - # app_version can sometimes report GM instead of the actual version - # This is a workaround to get the actual version - if app_version.startswith("GM"): - try: - app_version = int(app_sdk[:2]) - if app_version < 20: - app_version = f"10.{app_version - 4}" - else: - app_version = f"{app_version - 9}.0" - except ValueError: - app_version = "Unknown" - # Check if App Version is High Sierra or newer - can_add = False - if app_version.startswith("10."): - app_sub_version = app_version.split(".")[1] - if int(app_sub_version) >= 13: - can_add = True - else: - can_add = False - else: - can_add = True - - # Check SharedSupport.dmg's data - results = parse_sharedsupport_version(Path("/Applications") / Path(application)/ Path("Contents/SharedSupport/SharedSupport.dmg")) - if results[0] is not None: - app_sdk = results[0] - if results[1] is not None: - app_version = results[1] - - if can_add is True: - application_list.update({ - application: { - "Short Name": clean_name, - "Version": app_version, - "Build": app_sdk, - "Path": application, - } - }) - except KeyError: - pass - except PermissionError: - pass - # Sort Applications by version - application_list = {k: v for k, v in sorted(application_list.items(), key=lambda item: item[1]["Version"])} - return application_list - -def parse_sharedsupport_version(sharedsupport_path): - detected_build = None - detected_os = None - sharedsupport_path = Path(sharedsupport_path) - - if not sharedsupport_path.exists(): - return (detected_build, detected_os) - - if not sharedsupport_path.name.endswith(".dmg"): - return (detected_build, detected_os) - - - # Create temporary directory to extract SharedSupport.dmg to - with tempfile.TemporaryDirectory() as tmpdir: - output = subprocess.run( - [ - "hdiutil", "attach", "-noverify", sharedsupport_path, - "-mountpoint", tmpdir, - "-nobrowse", - ], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT - ) - if output.returncode != 0: - return (detected_build, detected_os) - - ss_info = Path("SFR/com_apple_MobileAsset_SFRSoftwareUpdate/com_apple_MobileAsset_SFRSoftwareUpdate.xml") - - if Path(tmpdir / ss_info).exists(): - plist = plistlib.load((tmpdir / ss_info).open("rb")) - if "Build" in plist["Assets"][0]: - detected_build = plist["Assets"][0]["Build"] - if "OSVersion" in plist["Assets"][0]: - detected_os = plist["Assets"][0]["OSVersion"] - - # Unmount SharedSupport.dmg - output = subprocess.run(["hdiutil", "detach", tmpdir], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - - return (detected_build, detected_os) - - -def create_installer(installer_path, volume_name): - # Creates a macOS installer - # Takes a path to the installer and the Volume - # Returns boolean on success status - - createinstallmedia_path = Path("/Applications") / Path(installer_path) / Path("Contents/Resources/createinstallmedia") - - # Sanity check in the event the user somehow deleted it between the time we found it and now - if (createinstallmedia_path).exists(): - utilities.cls() - utilities.header(["Starting createinstallmedia"]) - logging.info("This will take some time, recommend making some coffee while you wait\n") - utilities.elevated([createinstallmedia_path, "--volume", f"/Volumes/{volume_name}", "--nointeraction"]) - return True - else: - logging.info("- Failed to find createinstallmedia") - return False - -def download_install_assistant(download_path, ia_link): - # Downloads InstallAssistant.pkg - ia_download = network_handler.DownloadObject(ia_link, (Path(download_path) / Path("InstallAssistant.pkg"))) - ia_download.download(display_progress=True, spawn_thread=False) - - if ia_download.download_complete is True: - return True - return False - -def install_macOS_installer(download_path): - logging.info("- Extracting macOS installer from InstallAssistant.pkg\n This may take some time") - args = [ - "osascript", - "-e", - f'''do shell script "installer -pkg {Path(download_path)}/InstallAssistant.pkg -target /"''' - ' with prompt "OpenCore Legacy Patcher needs administrator privileges to add InstallAssistant."' - " with administrator privileges" - " without altering line endings", - ] - - result = subprocess.run(args,stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if result.returncode == 0: - logging.info("- InstallAssistant installed") - return True - else: - logging.info("- Failed to install InstallAssistant") - logging.info(f" Error Code: {result.returncode}") - return False - -def list_downloadable_macOS_installers(download_path, catalog): - available_apps = {} - if catalog == "DeveloperSeed": - link = "https://swscan.apple.com/content/catalogs/others/index-13seed-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog" - elif catalog == "PublicSeed": - link = "https://swscan.apple.com/content/catalogs/others/index-13beta-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog" - else: - link = "https://swscan.apple.com/content/catalogs/others/index-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog" - - if network_handler.NetworkUtilities(link).verify_network_connection() is True: - try: - catalog_plist = plistlib.loads(network_handler.SESSION.get(link).content) - except plistlib.InvalidFileException: - return available_apps - - for item in catalog_plist["Products"]: - try: - # Check if entry has SharedSupport and BuildManifest - # Ensures only Big Sur and newer Installers are listed - catalog_plist["Products"][item]["ExtendedMetaInfo"]["InstallAssistantPackageIdentifiers"]["SharedSupport"] - catalog_plist["Products"][item]["ExtendedMetaInfo"]["InstallAssistantPackageIdentifiers"]["BuildManifest"] - - for bm_package in catalog_plist["Products"][item]["Packages"]: - if "Info.plist" in bm_package["URL"] and "InstallInfo.plist" not in bm_package["URL"]: - try: - build_plist = plistlib.loads(network_handler.SESSION.get(bm_package["URL"]).content) - except plistlib.InvalidFileException: - continue - # Ensure Apple Silicon specific Installers are not listed - if "VMM-x86_64" not in build_plist["MobileAssetProperties"]["SupportedDeviceModels"]: - continue - version = build_plist["MobileAssetProperties"]["OSVersion"] - build = build_plist["MobileAssetProperties"]["Build"] - try: - catalog_url = build_plist["MobileAssetProperties"]["BridgeVersionInfo"]["CatalogURL"] - if "beta" in catalog_url: - catalog_url = "PublicSeed" - elif "customerseed" in catalog_url: - catalog_url = "CustomerSeed" - elif "seed" in catalog_url: - catalog_url = "DeveloperSeed" - else: - catalog_url = "Public" - except KeyError: - # Assume Public if no catalog URL is found - catalog_url = "Public" - for ia_package in catalog_plist["Products"][item]["Packages"]: - if "InstallAssistant.pkg" in ia_package["URL"]: - download_link = ia_package["URL"] - size = ia_package["Size"] - integrity = ia_package["IntegrityDataURL"] - - available_apps.update({ - item: { - "Version": version, - "Build": build, - "Link": download_link, - "Size": size, - "integrity": integrity, - "Source": "Apple Inc.", - "Variant": catalog_url, - } - }) - except KeyError: - pass - available_apps = {k: v for k, v in sorted(available_apps.items(), key=lambda x: x[1]['Version'])} - return available_apps - -def only_list_newest_installers(available_apps): - # Takes a dictionary of available installers - # Returns a dictionary of only the newest installers - # This is used to avoid overwhelming the user with installer options - - # Only strip OSes that we know are supported - supported_versions = ["10.13", "10.14", "10.15", "11", "12", "13"] - - for version in supported_versions: - remote_version_minor = 0 - remote_version_security = 0 - os_builds = [] - - # First determine the largest version - for ia in available_apps: - if available_apps[ia]["Version"].startswith(version): - if available_apps[ia]["Variant"] not in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]: - remote_version = available_apps[ia]["Version"].split(".") - if remote_version[0] == "10": - remote_version.pop(0) - remote_version.pop(0) - else: - remote_version.pop(0) - if int(remote_version[0]) > remote_version_minor: - remote_version_minor = int(remote_version[0]) - remote_version_security = 0 # Reset as new minor version found - if len(remote_version) > 1: - if int(remote_version[1]) > remote_version_security: - remote_version_security = int(remote_version[1]) - - # Now remove all versions that are not the largest - for ia in list(available_apps): - # Don't use Beta builds to determine latest version - if available_apps[ia]["Variant"] in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]: - continue - - if available_apps[ia]["Version"].startswith(version): - remote_version = available_apps[ia]["Version"].split(".") - if remote_version[0] == "10": - remote_version.pop(0) - remote_version.pop(0) - else: - remote_version.pop(0) - if int(remote_version[0]) < remote_version_minor: - available_apps.pop(ia) - continue - if int(remote_version[0]) == remote_version_minor: - if len(remote_version) > 1: - if int(remote_version[1]) < remote_version_security: - available_apps.pop(ia) - continue - else: - if remote_version_security > 0: - available_apps.pop(ia) - continue - - # Remove duplicate builds - # ex. macOS 12.5.1 has 2 builds in the Software Update Catalog - # ref: https://twitter.com/classicii_mrmac/status/1560357471654379522 - if available_apps[ia]["Build"] in os_builds: - available_apps.pop(ia) - continue - - os_builds.append(available_apps[ia]["Build"]) - - # Final passthrough - # Remove Betas if there's a non-beta version available - for ia in list(available_apps): - if available_apps[ia]["Variant"] in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]: - for ia2 in available_apps: - if available_apps[ia2]["Version"].split(".")[0] == available_apps[ia]["Version"].split(".")[0] and available_apps[ia2]["Variant"] not in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]: - available_apps.pop(ia) - break - - return available_apps - -def format_drive(disk_id): - # Formats a disk for macOS install - # Takes a disk ID - # Returns boolean on success status - header = f"# Formatting disk{disk_id} for macOS installer #" - box_length = len(header) - utilities.cls() - logging.info("#" * box_length) - logging.info(header) - logging.info("#" * box_length) - logging.info("") - #logging.info(f"- Formatting disk{disk_id} for macOS installer") - format_process = utilities.elevated(["diskutil", "eraseDisk", "HFS+", "OCLP-Installer", f"disk{disk_id}"]) - if format_process.returncode == 0: - logging.info("- Disk formatted") - return True - else: - logging.info("- Failed to format disk") - logging.info(f" Error Code: {format_process.returncode}") - input("\nPress Enter to exit") - return False - - - -def list_disk_to_format(): - all_disks = {} - list_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 - 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 - logging.info(f"disk {disk}: {all_disks[disk]['name']} ({utilities.human_fmt(all_disks[disk]['size'])})") - list_disks.update({ - disk: { - "identifier": all_disks[disk]["identifier"], - "name": all_disks[disk]["name"], - "size": all_disks[disk]["size"], - } - }) - return list_disks - -# Create global tmp directory -tmp_dir = tempfile.TemporaryDirectory() - -def generate_installer_creation_script(tmp_location, installer_path, disk): - # Creates installer.sh to be piped to OCLP-Helper and run as admin - # Goals: - # - Format provided disk as HFS+ GPT - # - Run createinstallmedia on provided disk - # Implementing this into a single installer.sh script allows us to only call - # OCLP-Helper once to avoid nagging the user about permissions - - additional_args = "" - script_location = Path(tmp_location) / Path("Installer.sh") - - # Due to a bug in createinstallmedia, running from '/Applications' may sometimes error: - # 'Failed to extract AssetData/boot/Firmware/Manifests/InstallerBoot/*' - # This affects native Macs as well even when manually invoking createinstallmedia - - # To resolve, we'll copy into our temp directory and run from there - - # Create a new tmp directory - # Our current one is a disk image, thus CoW will not work - global tmp_dir - ia_tmp = tmp_dir.name - - logging.info(f"Creating temporary directory at {ia_tmp}") - # Delete all files in tmp_dir - for file in Path(ia_tmp).glob("*"): - subprocess.run(["rm", "-rf", str(file)]) - - # Copy installer to tmp (use CoW to avoid extra disk writes) - args = ["cp", "-cR", installer_path, ia_tmp] - if utilities.check_filesystem_type() != "apfs": - # HFS+ disks do not support CoW - args[1] = "-R" - # Ensure we have enough space for the duplication - space_available = utilities.get_free_space() - space_needed = Path(ia_tmp).stat().st_size - if space_available < space_needed: - logging.info("Not enough free space to create installer.sh") - logging.info(f"{utilities.human_fmt(space_available)} available, {utilities.human_fmt(space_needed)} required") - return False - subprocess.run(args) - - # Adjust installer_path to point to the copied installer - installer_path = Path(ia_tmp) / Path(Path(installer_path).name) - if not Path(installer_path).exists(): - logging.info(f"Failed to copy installer to {ia_tmp}") - return False - - createinstallmedia_path = str(Path(installer_path) / Path("Contents/Resources/createinstallmedia")) - plist_path = str(Path(installer_path) / Path("Contents/Info.plist")) - if Path(plist_path).exists(): - plist = plistlib.load(Path(plist_path).open("rb")) - if "DTPlatformVersion" in plist: - platform_version = plist["DTPlatformVersion"] - platform_version = platform_version.split(".")[0] - if platform_version[0] == "10": - if int(platform_version[1]) < 13: - additional_args = f" --applicationpath '{installer_path}'" - - if script_location.exists(): - script_location.unlink() - script_location.touch() - - with script_location.open("w") as script: - script.write(f'''#!/bin/bash -erase_disk='diskutil eraseDisk HFS+ OCLP-Installer {disk}' -if $erase_disk; then - "{createinstallmedia_path}" --volume /Volumes/OCLP-Installer --nointeraction{additional_args} -fi - ''') - if Path(script_location).exists(): - return True - return False \ No newline at end of file diff --git a/resources/macos_installer_handler.py b/resources/macos_installer_handler.py new file mode 100644 index 000000000..8764b4a6a --- /dev/null +++ b/resources/macos_installer_handler.py @@ -0,0 +1,590 @@ +# Handler for macOS installers, both local and remote + +from pathlib import Path +import plistlib +import subprocess +import tempfile +import enum +import logging + +from data import os_data +from resources import network_handler, utilities + + +APPLICATION_SEARCH_PATH: str = "/Applications" +SFR_SOFTWARE_UPDATE_PATH: str = "SFR/com_apple_MobileAsset_SFRSoftwareUpdate/com_apple_MobileAsset_SFRSoftwareUpdate.xml" + +CATALOG_URL_BASE: str = "https://swscan.apple.com/content/catalogs/others/index" +CATALOG_URL_EXTENSION: str = "13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog" +CATALOG_URL_VERSION: str = "13" + +tmp_dir = tempfile.TemporaryDirectory() + + +class InstallerCreation(): + + def __init__(self): + pass + + + def install_macOS_installer(self, download_path: str): + """ + Installs InstallAssistant.pkg + """ + + logging.info("- Extracting macOS installer from InstallAssistant.pkg\n This may take some time") + args = [ + "osascript", + "-e", + f'''do shell script "installer -pkg {Path(download_path)}/InstallAssistant.pkg -target /"''' + ' with prompt "OpenCore Legacy Patcher needs administrator privileges to add InstallAssistant."' + " with administrator privileges" + " without altering line endings", + ] + + result = subprocess.run(args,stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode != 0: + logging.info("- Failed to install InstallAssistant") + logging.info(f" Error Code: {result.returncode}") + return False + + logging.info("- InstallAssistant installed") + return True + + + def generate_installer_creation_script(self, tmp_location, installer_path, disk): + """ + Creates installer.sh to be piped to OCLP-Helper and run as admin + + Script includes: + - Format provided disk as HFS+ GPT + - Run createinstallmedia on provided disk + + Implementing this into a single installer.sh script allows us to only call + OCLP-Helper once to avoid nagging the user about permissions + + Parameters: + tmp_location (str): Path to temporary directory + installer_path (str): Path to InstallAssistant.pkg + disk (str): Disk to install to + """ + + additional_args = "" + script_location = Path(tmp_location) / Path("Installer.sh") + + # Due to a bug in createinstallmedia, running from '/Applications' may sometimes error: + # 'Failed to extract AssetData/boot/Firmware/Manifests/InstallerBoot/*' + # This affects native Macs as well even when manually invoking createinstallmedia + + # To resolve, we'll copy into our temp directory and run from there + + # Create a new tmp directory + # Our current one is a disk image, thus CoW will not work + global tmp_dir + ia_tmp = tmp_dir.name + + logging.info(f"Creating temporary directory at {ia_tmp}") + # Delete all files in tmp_dir + for file in Path(ia_tmp).glob("*"): + subprocess.run(["rm", "-rf", str(file)]) + + # Copy installer to tmp (use CoW to avoid extra disk writes) + args = ["cp", "-cR", installer_path, ia_tmp] + if utilities.check_filesystem_type() != "apfs": + # HFS+ disks do not support CoW + args[1] = "-R" + + # Ensure we have enough space for the duplication + space_available = utilities.get_free_space() + space_needed = Path(ia_tmp).stat().st_size + if space_available < space_needed: + logging.info("Not enough free space to create installer.sh") + logging.info(f"{utilities.human_fmt(space_available)} available, {utilities.human_fmt(space_needed)} required") + return False + + subprocess.run(args) + + # Adjust installer_path to point to the copied installer + installer_path = Path(ia_tmp) / Path(Path(installer_path).name) + if not Path(installer_path).exists(): + logging.info(f"Failed to copy installer to {ia_tmp}") + return False + + createinstallmedia_path = str(Path(installer_path) / Path("Contents/Resources/createinstallmedia")) + plist_path = str(Path(installer_path) / Path("Contents/Info.plist")) + if Path(plist_path).exists(): + plist = plistlib.load(Path(plist_path).open("rb")) + if "DTPlatformVersion" in plist: + platform_version = plist["DTPlatformVersion"] + platform_version = platform_version.split(".")[0] + if platform_version[0] == "10": + if int(platform_version[1]) < 13: + additional_args = f" --applicationpath '{installer_path}'" + + if script_location.exists(): + script_location.unlink() + script_location.touch() + + with script_location.open("w") as script: + script.write(f'''#!/bin/bash +erase_disk='diskutil eraseDisk HFS+ OCLP-Installer {disk}' +if $erase_disk; then + "{createinstallmedia_path}" --volume /Volumes/OCLP-Installer --nointeraction{additional_args} +fi + ''') + if Path(script_location).exists(): + return True + return False + + def list_disk_to_format(self): + """ + List applicable disks for macOS installer creation + Only lists disks that are: + - 14GB or larger + - External + + Current limitations: + - Does not support PCIe based SD cards readers + + Returns: + dict: Dictionary of disks + """ + + all_disks: dict = {} + list_disks: dict = {} + + # 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 + + 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 + + logging.info(f"disk {disk}: {all_disks[disk]['name']} ({utilities.human_fmt(all_disks[disk]['size'])})") + list_disks.update({ + disk: { + "identifier": all_disks[disk]["identifier"], + "name": all_disks[disk]["name"], + "size": all_disks[disk]["size"], + } + }) + + return list_disks + + +class SeedType(enum.Enum): + """ + Enum for catalog types + """ + DeveloperSeed = 0 + PublicSeed = 1 + CustomerSeed = 2 + PublicRelease = 3 + + +class RemoteInstallerCatalog: + """ + Parses Apple's Software Update catalog and finds all macOS installers. + """ + + def __init__(self, seed_override: SeedType = SeedType.PublicRelease): + + self.catalog_url: str = self._construct_catalog_url(seed_override) + + self.available_apps: dict = self._parse_catalog() + self.available_apps_latest: dict = self._list_newest_installers_only() + + + def _construct_catalog_url(self, seed_type: SeedType): + """ + Constructs the catalog URL based on the seed type + + Args: + seed_type (SeedType): The seed type to use + """ + + + url: str = "" + + if seed_type == SeedType.DeveloperSeed: + url = f"{CATALOG_URL_BASE}-{CATALOG_URL_VERSION}seed-{CATALOG_URL_EXTENSION}" + elif seed_type == SeedType.PublicSeed: + url = f"{CATALOG_URL_BASE}-{CATALOG_URL_VERSION}beta-{CATALOG_URL_EXTENSION}" + elif seed_type == SeedType.CustomerSeed: + url = f"{CATALOG_URL_BASE}-{CATALOG_URL_VERSION}customerseed-{CATALOG_URL_EXTENSION}" + else: + url = f"{CATALOG_URL_BASE}-{CATALOG_URL_EXTENSION}" + + return url + + + def _fetch_catalog(self): + """ + Fetches the catalog from Apple's servers + + Returns: + dict: The catalog as a dictionary + """ + + catalog: dict = {} + + if network_handler.NetworkUtilities(self.catalog_url).verify_network_connection() is False: + return catalog + + try: + catalog = plistlib.loads(network_handler.SESSION.get(self.catalog_url).content) + except plistlib.InvalidFileException: + return {} + + return catalog + + def _parse_catalog(self): + available_apps: dict = {} + + catalog: dict = self._fetch_catalog() + if not catalog: + return available_apps + + if "Products" not in catalog: + return available_apps + + for product in catalog["Products"]: + if "ExtendedMetaInfo" not in catalog["Products"][product]: + continue + if "Packages" not in catalog["Products"][product]: + continue + if "InstallAssistantPackageIdentifiers" not in catalog["Products"][product]["ExtendedMetaInfo"]: + continue + if "SharedSupport" not in catalog["Products"][product]["ExtendedMetaInfo"]["InstallAssistantPackageIdentifiers"]: + continue + if "BuildManifest" not in catalog["Products"][product]["ExtendedMetaInfo"]["InstallAssistantPackageIdentifiers"]: + continue + + for bm_package in catalog["Products"][product]["Packages"]: + if "Info.plist" not in bm_package["URL"]: + continue + if "InstallInfo.plist" in bm_package["URL"]: + continue + + try: + build_plist = plistlib.loads(network_handler.SESSION.get(bm_package["URL"]).content) + except plistlib.InvalidFileException: + continue + + if "MobileAssetProperties" not in build_plist: + continue + if "SupportedDeviceModels" not in build_plist["MobileAssetProperties"]: + continue + if "OSVersion" not in build_plist["MobileAssetProperties"]: + continue + if "Build" not in build_plist["MobileAssetProperties"]: + continue + + # Ensure Apple Silicon specific Installers are not listed + if "VMM-x86_64" not in build_plist["MobileAssetProperties"]["SupportedDeviceModels"]: + continue + + version = build_plist["MobileAssetProperties"]["OSVersion"] + build = build_plist["MobileAssetProperties"]["Build"] + + try: + catalog_url = build_plist["MobileAssetProperties"]["BridgeVersionInfo"]["CatalogURL"] + if "beta" in catalog_url: + catalog_url = "PublicSeed" + elif "customerseed" in catalog_url: + catalog_url = "CustomerSeed" + elif "seed" in catalog_url: + catalog_url = "DeveloperSeed" + else: + catalog_url = "Public" + except KeyError: + # Assume Public if no catalog URL is found + catalog_url = "Public" + + download_link = None + integrity = None + size = None + + for ia_package in catalog["Products"][product]["Packages"]: + if "InstallAssistant.pkg" not in ia_package["URL"]: + continue + if "URL" not in ia_package: + continue + if "IntegrityDataURL" not in ia_package: + continue + if "Size" not in ia_package: + size = 0 + + download_link = ia_package["URL"] + integrity = ia_package["IntegrityDataURL"] + size = ia_package["Size"] + + + if any([version, build, download_link, size, integrity]) is None: + continue + + available_apps.update({ + product: { + "Version": version, + "Build": build, + "Link": download_link, + "Size": size, + "integrity": integrity, + "Source": "Apple Inc.", + "Variant": catalog_url, + } + }) + + available_apps = {k: v for k, v in sorted(available_apps.items(), key=lambda x: x[1]['Version'])} + return available_apps + + + def _list_newest_installers_only(self): + """ + Returns a dictionary of the newest macOS installers only. + Primarily used to avoid overwhelming the user with a list of + installers that are not the newest version. + + Returns: + dict: A dictionary of the newest macOS installers only. + """ + + if self.available_apps is None: + return {} + + newest_apps: dict = self.available_apps.copy() + supported_versions = ["10.13", "10.14", "10.15", "11", "12", "13"] + + + for version in supported_versions: + remote_version_minor = 0 + remote_version_security = 0 + os_builds = [] + + # First determine the largest version + for ia in newest_apps: + if newest_apps[ia]["Version"].startswith(version): + if newest_apps[ia]["Variant"] not in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]: + remote_version = newest_apps[ia]["Version"].split(".") + if remote_version[0] == "10": + remote_version.pop(0) + remote_version.pop(0) + else: + remote_version.pop(0) + if int(remote_version[0]) > remote_version_minor: + remote_version_minor = int(remote_version[0]) + remote_version_security = 0 # Reset as new minor version found + if len(remote_version) > 1: + if int(remote_version[1]) > remote_version_security: + remote_version_security = int(remote_version[1]) + + # Now remove all versions that are not the largest + for ia in list(newest_apps): + # Don't use Beta builds to determine latest version + if newest_apps[ia]["Variant"] in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]: + continue + + if newest_apps[ia]["Version"].startswith(version): + remote_version = newest_apps[ia]["Version"].split(".") + if remote_version[0] == "10": + remote_version.pop(0) + remote_version.pop(0) + else: + remote_version.pop(0) + if int(remote_version[0]) < remote_version_minor: + newest_apps.pop(ia) + continue + if int(remote_version[0]) == remote_version_minor: + if len(remote_version) > 1: + if int(remote_version[1]) < remote_version_security: + newest_apps.pop(ia) + continue + else: + if remote_version_security > 0: + newest_apps.pop(ia) + continue + + # Remove duplicate builds + # ex. macOS 12.5.1 has 2 builds in the Software Update Catalog + # ref: https://twitter.com/classicii_mrmac/status/1560357471654379522 + if newest_apps[ia]["Build"] in os_builds: + newest_apps.pop(ia) + continue + + os_builds.append(newest_apps[ia]["Build"]) + + # Final passthrough + # Remove Betas if there's a non-beta version available + for ia in list(newest_apps): + if newest_apps[ia]["Variant"] in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]: + for ia2 in newest_apps: + if newest_apps[ia2]["Version"].split(".")[0] == newest_apps[ia]["Version"].split(".")[0] and newest_apps[ia2]["Variant"] not in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]: + newest_apps.pop(ia) + break + + return newest_apps + + +class LocalInstallerCatalog: + """ + Finds all macOS installers on the local machine. + """ + + def __init__(self): + self.available_apps: dict = self._list_local_macOS_installers() + + + def _list_local_macOS_installers(self): + """ + Searches for macOS installers in /Applications + + Returns: + dict: A dictionary of macOS installers found on the local machine. + + Example: + "Install macOS Big Sur Beta.app": { + "Short Name": "Big Sur Beta", + "Version": "11.0", + "Build": "20A5343i", + "Path": "/Applications/Install macOS Big Sur Beta.app", + }, + etc... + """ + + application_list: dict = {} + + for application in Path(APPLICATION_SEARCH_PATH).iterdir(): + # Certain Microsoft Applications have strange permissions disabling us from reading them + try: + if not (Path(APPLICATION_SEARCH_PATH) / Path(application) / Path("Contents/Resources/createinstallmedia")).exists(): + continue + + if not (Path(APPLICATION_SEARCH_PATH) / Path(application) / Path("Contents/Info.plist")).exists(): + continue + except PermissionError: + continue + + try: + application_info_plist = plistlib.load((Path(APPLICATION_SEARCH_PATH) / Path(application) / Path("Contents/Info.plist")).open("rb")) + except (PermissionError, TypeError, plistlib.InvalidFileException): + continue + + if "DTPlatformVersion" not in application_info_plist: + continue + if "CFBundleDisplayName" not in application_info_plist: + continue + + app_version = application_info_plist["DTPlatformVersion"] + clean_name = application_info_plist["CFBundleDisplayName"] + + if "DTSDKBuild" in application_info_plist: + app_sdk = application_info_plist["DTSDKBuild"] + else: + app_sdk = "Unknown" + + # app_version can sometimes report GM instead of the actual version + # This is a workaround to get the actual version + if app_version.startswith("GM"): + try: + app_version = int(app_sdk[:2]) + if app_version < 20: + app_version = f"10.{app_version - 4}" + else: + app_version = f"{app_version - 9}.0" + except ValueError: + app_version = "Unknown" + + # Check if App Version is High Sierra or newer + if os_data.os_conversion.os_to_kernel(app_version) < os_data.os_data.high_sierra: + continue + + results = self._parse_sharedsupport_version(Path(APPLICATION_SEARCH_PATH) / Path(application)/ Path("Contents/SharedSupport/SharedSupport.dmg")) + if results[0] is not None: + app_sdk = results[0] + if results[1] is not None: + app_version = results[1] + + application_list.update({ + application: { + "Short Name": clean_name, + "Version": app_version, + "Build": app_sdk, + "Path": application, + } + }) + + # Sort Applications by version + application_list = {k: v for k, v in sorted(application_list.items(), key=lambda item: item[1]["Version"])} + return application_list + + + def _parse_sharedsupport_version(self, sharedsupport_path: Path): + """ + Determine true version of macOS installer by parsing SharedSupport.dmg + This is required due to Info.plist reporting the application version, not the OS version + + Parameters: + sharedsupport_path (Path): Path to SharedSupport.dmg + + Returns: + tuple: Tuple containing the build and OS version + """ + + detected_build: str = None + detected_os: str = None + + if not sharedsupport_path.exists(): + return (detected_build, detected_os) + + if not sharedsupport_path.name.endswith(".dmg"): + return (detected_build, detected_os) + + + # Create temporary directory to extract SharedSupport.dmg to + with tempfile.TemporaryDirectory() as tmpdir: + + output = subprocess.run( + [ + "hdiutil", "attach", "-noverify", sharedsupport_path, + "-mountpoint", tmpdir, + "-nobrowse", + ], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) + + if output.returncode != 0: + return (detected_build, detected_os) + + ss_info = Path(SFR_SOFTWARE_UPDATE_PATH) + + if Path(tmpdir / ss_info).exists(): + plist = plistlib.load((tmpdir / ss_info).open("rb")) + if "Assets" in plist: + if "Build" in plist["Assets"][0]: + detected_build = plist["Assets"][0]["Build"] + if "OSVersion" in plist["Assets"][0]: + detected_os = plist["Assets"][0]["OSVersion"] + + # Unmount SharedSupport.dmg + subprocess.run(["hdiutil", "detach", tmpdir], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + return (detected_build, detected_os) \ No newline at end of file From 2767f35fc5fff8600fbdb566bf211e88b3fa1f06 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sat, 11 Mar 2023 12:31:39 -0700 Subject: [PATCH 60/61] sys_patch_dict.py: Add reference for 3802 GPUs --- data/sys_patch_dict.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/data/sys_patch_dict.py b/data/sys_patch_dict.py index 63090c6e3..6fbe699c0 100644 --- a/data/sys_patch_dict.py +++ b/data/sys_patch_dict.py @@ -296,6 +296,32 @@ def SystemPatchDictionary(os_major, os_minor, non_metal_os_support): }, }, + # Support for 3802 GPUs were broken with 13.3+ + # Downgrades 31001 stack to 13.2.1, however nukes AMFI support + # Extremely fugly, only for reference purposes at this time + "Metal 3802 Common Extended": { + "Display Name": "", + "OS Support": { + "Minimum OS Support": { + "OS Major": os_data.os_data.ventura, + "OS Minor": 4 # 13.3 + }, + "Maximum OS Support": { + "OS Major": os_data.os_data.max_os, + "OS Minor": 99 + }, + }, + "Install Reference": { + "/System/Library/Frameworks": { + "Metal.framework": "13.2.1", + }, + "/System/Library/PrivateFrameworks": { + "MTLCompiler.framework": "13.2.1", + "GPUCompiler.framework": "13.2.1", + }, + }, + }, + # Primarily for AMD GCN GPUs "Revert GVA Downgrade": { "Display Name": "", From a89a7740ca9170a705ad6075819c226a1088f096 Mon Sep 17 00:00:00 2001 From: Mykola Grymalyuk Date: Sat, 11 Mar 2023 12:33:12 -0700 Subject: [PATCH 61/61] Sync Changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c50933a4..8c71da65a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ - Fixed System Settings hover effects, including Bluetooth connect button - Add Books hacks (reimplement cover image generation, disable broken page curl animation) - Fixed unresponsive buttons +- Implement Hardware Encoding support for AMD Polaris and Vega GPUs + - Applicable for pre-Haswell Macs on macOS Ventura + - Resolves DRM playback issues on Netflix, Disney+, etc. - Backend Changes: - Refactored kdk_handler.py - Prioritizes KdkSupportPkg repository for downloads