Compare commits

..

153 Commits
0.6.1 ... 0.6.2

Author SHA1 Message Date
Mykola Grymalyuk
29d3e98b81 kdk_handler.py: Fix pkg detection 2023-03-25 10:36:23 -06:00
Mykola Grymalyuk
462fff9d66 Sync PatcherSupportPkg 2023-03-23 19:31:47 -06:00
Mykola Grymalyuk
158f6f2da9 sys_patch_detect.py: Avoid reverting Metal patch on 3802 2023-03-23 18:21:46 -06:00
Mykola Grymalyuk
a57c29d35e gui_main.py: Add tip to rebuild OC for root patch errors 2023-03-23 16:19:23 -06:00
Mykola Grymalyuk
bc18bdce03 Sync CHANGELOG 2023-03-23 11:21:59 -06:00
Mykola Grymalyuk
ef9b5bb64a Merge pull request #1034 from dortania/3802-experiment
sys_patch: Add experimental Metal patches for 3802-based GPUs
2023-03-23 11:18:46 -06:00
Mykola Grymalyuk
c225b77400 utilities.py: Fix volume size detection 2023-03-23 10:03:42 -06:00
Mykola Grymalyuk
b69cf09556 Merge branch 'main' into 3802-experiment 2023-03-22 09:38:59 -06:00
Mykola Grymalyuk
85203aef5b Sync CHANGELOG 2023-03-22 09:36:50 -06:00
Mykola Grymalyuk
ec80afdd17 sys_patch_dict.py: Rework into class 2023-03-21 09:45:45 -06:00
Mykola Grymalyuk
e288e27b7b gui_main.py: Clean chunk check 2023-03-20 06:43:02 -06:00
Mykola Grymalyuk
961c9c7a68 sys_patch_detect.py: Only install ColorSync patch on iGPU-primary Macs 2023-03-19 11:35:14 -06:00
Mykola Grymalyuk
55abbab145 amfi_detect.py: Adjust indentation 2023-03-19 11:24:24 -06:00
Mykola Grymalyuk
2986aea152 macos_installer_handler.py: Add Seed type information 2023-03-18 16:26:18 -06:00
Mykola Grymalyuk
0135d6cccf Resources: Add return typing suggestion 2023-03-17 20:04:46 -06:00
Mykola Grymalyuk
fd2f58da84 constants.py: Remove unused variables 2023-03-16 19:48:38 -06:00
Mykola Grymalyuk
efb1d81231 Merge branch 'main' into 3802-experiment 2023-03-15 10:34:59 -06:00
Mykola Grymalyuk
7fe0432a47 constants.py: Reorganize variables 2023-03-15 10:33:26 -06:00
Mykola Grymalyuk
88fa7a4b7a arguments.py: Remove unused variable 2023-03-15 10:31:35 -06:00
Mykola Grymalyuk
e0e74ee494 Merge branch 'main' into 3802-experiment 2023-03-14 21:08:31 -06:00
Mykola Grymalyuk
e579199246 constants.py: Sync OpenCorePkg version 2023-03-14 21:07:02 -06:00
Mykola Grymalyuk
1108d248e5 Sync binaries 2023-03-14 20:59:36 -06:00
Mykola Grymalyuk
6013eaffe9 Implement Kext Updating script 2023-03-14 20:57:57 -06:00
Mykola Grymalyuk
ea96c546ca build: Avoid overwritting DeviceProperties on MXM iMacs 2023-03-14 08:57:10 -06:00
Mykola Grymalyuk
531fc8ab18 build: Don’t set IOName property for iMac12,x iGPUs 2023-03-13 11:54:11 -06:00
Mykola Grymalyuk
35a6f5183c Merge branch 'main' into 3802-experiment 2023-03-13 11:50:55 -06:00
Mykola Grymalyuk
afd1b5d2a7 device_probe.py: Fix missing pci check 2023-03-13 11:44:05 -06:00
Mykola Grymalyuk
d5ffa9a8cf device_probe.py: Fix string assumption for IOName 2023-03-13 11:42:36 -06:00
Mykola Grymalyuk
0c68618ddf sys_patch: Add experimental 3802 patches
Not final, highly fragile patches
2023-03-13 11:29:27 -06:00
Mykola Grymalyuk
ea80d41ff6 build: Better display AMFI status 2023-03-12 14:43:39 -06:00
Mykola Grymalyuk
483819caef defaults.py: Fix whitespace 2023-03-12 12:06:30 -06:00
Mykola Grymalyuk
0ca8ccd3dc gui_main.py: Adjust installer button label 2023-03-12 12:06:05 -06:00
Mykola Grymalyuk
a1f1da25b5 Sync FeatureUnlock 2023-03-12 10:36:32 -06:00
Mykola Grymalyuk
e105d6077e Sync changelog 2023-03-11 20:49:09 -07:00
Mykola Grymalyuk
eedd4d771a Merge pull request #1033 from dortania/pullrequests/Ausdauersportler/main
Ausdauersportler: Add RX5500XT and Lexa MXM support (reworked)
2023-03-11 20:39:06 -07:00
Mykola Grymalyuk
3f2aadc016 Merge branch 'main' into pullrequests/Ausdauersportler/main 2023-03-11 20:37:38 -07:00
Mykola Grymalyuk
e980054a5d device_probe.py: Add unspoof variable 2023-03-11 13:48:11 -07:00
Mykola Grymalyuk
a89a7740ca Sync Changelog 2023-03-11 12:33:12 -07:00
Mykola Grymalyuk
2767f35fc5 sys_patch_dict.py: Add reference for 3802 GPUs 2023-03-11 12:31:39 -07:00
Mykola Grymalyuk
48cf258366 Merge pull request #1031 from dortania/va-test
sys_patch.py: Add VA driver patch for Polaris and Vega
2023-03-11 12:26:31 -07:00
Mykola Grymalyuk
8e7706fb12 build: Fix off model crash 2023-03-11 11:32:41 -07:00
Mykola Grymalyuk
2eb6542931 payloads: Switch to .patch WEG 2023-03-11 11:28:03 -07:00
Mykola Grymalyuk
e66e851933 build: Skip string conversion for check 2023-03-11 11:12:19 -07:00
Mykola Grymalyuk
9bfcf78ff9 detect: Set new class for Lexa GPUs
As these cards require spoofing, OCLP needs to treat them differently. Primarily due to root volume patching checking against the same class, and prevent users from believing the root patches failed (when their card’s actually unsupported by their Device ID)
2023-03-11 11:11:24 -07:00
Mykola Grymalyuk
8d3ab82ddd payloads: Add Navi patch file 2023-03-11 10:55:22 -07:00
Mykola Grymalyuk
7b7f68453a macos_installer_handler.py: Reworked from installer.py module 2023-03-11 08:41:45 -07:00
Mykola Grymalyuk
adec8ebd05 sys_patch.py: Add VA driver patch 2023-03-09 08:54:05 -07:00
Mykola Grymalyuk
ca24aa6ce5 Sync PatcherSupportPkg
Back from surgery, OCLP development should start to slowly ramp up again!
2023-03-07 17:44:05 -07:00
Mykola Grymalyuk
e8f44e4eaa Merge pull request #1029 from dortania/oop-rework
Rework additional core libraries
2023-02-10 15:33:58 -07:00
Mykola Grymalyuk
6a3023301a sys_patch_helpers.py: Add docstring comments 2023-02-10 13:23:40 -07:00
Mykola Grymalyuk
44369e2faa kdk_handler.py: Add todo for file size check 2023-02-10 12:24:27 -07:00
Mykola Grymalyuk
0d7186236e gui_main.py: Supress defaults stderr 2023-02-10 12:14:42 -07:00
Mykola Grymalyuk
8806d29a35 defaulkts.py: Fix type suggestion 2023-02-10 12:14:06 -07:00
Mykola Grymalyuk
12b7cf7fcd amfi_detect.py: Resolve regression 2023-02-10 12:02:23 -07:00
Mykola Grymalyuk
0f7f079dd8 sys_patch_detect.py: Avoid nested if statements 2023-02-10 08:25:02 -07:00
Mykola Grymalyuk
040edfdd25 sys_patch_detect.py: Adjust return 2023-02-10 08:04:52 -07:00
Mykola Grymalyuk
2e7afae29b Adjust private function names 2023-02-10 08:02:50 -07:00
Mykola Grymalyuk
b81899092b Docs: Remove TUI references 2023-02-10 07:24:34 -07:00
Mykola Grymalyuk
418a966081 updates.py: Adjust private function names 2023-02-09 20:05:52 -07:00
Mykola Grymalyuk
520c9c315c validation.py: Add cleanup to build validation 2023-02-09 19:43:48 -07:00
Mykola Grymalyuk
cffc463bde Toolchain: Implement whitelist system for cleaning 2023-02-09 19:28:15 -07:00
Mykola Grymalyuk
2f7965440c Remove unused run.py 2023-02-09 19:27:25 -07:00
Mykola Grymalyuk
56efd9743d Docs: Fix .command name 2023-02-09 18:31:30 -07:00
Mykola Grymalyuk
b5b4d84bc9 Toolchain: Use docstrings 2023-02-09 18:29:35 -07:00
Mykola Grymalyuk
594f6dcbe5 Toolchain: Remove TUI building argument 2023-02-09 18:16:12 -07:00
Mykola Grymalyuk
c5eb52ac5e amfi_detect.py: Use enum 2023-02-09 18:09:02 -07:00
Mykola Grymalyuk
e83e260db7 Fix Constants type suggestion 2023-02-09 17:59:53 -07:00
Mykola Grymalyuk
66a5f5a9ad Set type suggestions for global Constants() usage 2023-02-09 17:40:41 -07:00
Mykola Grymalyuk
4154b01d40 commit_info.py: Rework into object oriented 2023-02-09 17:04:20 -07:00
Mykola Grymalyuk
925003e3f1 reroute_payloads.py: rework into object oriented 2023-02-09 16:46:12 -07:00
Mykola Grymalyuk
d40d6607b3 CI: Remove TUI 2023-02-09 16:40:25 -07:00
Mykola Grymalyuk
3aadfe6002 arguments.py: Rework into object oriented 2023-02-09 16:27:18 -07:00
Mykola Grymalyuk
d305515c28 Remove TUI modules 2023-02-09 16:26:47 -07:00
Mykola Grymalyuk
48471ce4d3 validation.py: Rework into object oriented 2023-02-09 16:25:45 -07:00
Mykola Grymalyuk
f9c7273106 os_probe.py: Rework to be Object-oriented
Minimize repetitive calls to platform.uname()
2023-02-09 12:44:26 -07:00
Mykola Grymalyuk
e076260a1a Sync docstrings comments 2023-02-09 12:36:16 -07:00
Mykola Grymalyuk
0671828c9b logging_handler.py: Fix file permissions when root 2023-02-09 11:04:15 -07:00
Wolf Dieter Brandt
61e5ff1c83 adding RX5500XT and Lexa MXM support 2023-02-09 18:15:17 +01:00
Mykola Grymalyuk
3b5e4f10f6 kdk_handler.py: Use pkg receipts for KDK validation 2023-02-09 08:35:21 -07:00
Mykola Grymalyuk
dd06932fe5 amfi_detect.py: Fix comment 2023-02-08 15:45:51 -07:00
Mykola Grymalyuk
7ee631859d amfi_detect.py: Adjust to be more Object-oriented 2023-02-08 15:42:12 -07:00
Mykola Grymalyuk
7be168bf14 kdk_handler.py: display KDK backup name during creation 2023-02-08 15:07:33 -07:00
Mykola Grymalyuk
6b86e64b5b kdk_handler.py: Keep invocation 2023-02-08 14:47:25 -07:00
Mykola Grymalyuk
4ae494db86 kdk_handler.py: Limit number of removal invocations 2023-02-08 14:44:09 -07:00
Mykola Grymalyuk
c5f72c10ff kdk_handler.py: Clear out backup KDKs when unused 2023-02-08 14:31:54 -07:00
Mykola Grymalyuk
971a2b0d02 kdk_handler.py: Set KDK installed accordingly 2023-02-08 13:56:56 -07:00
Mykola Grymalyuk
8b18c59d8a kdk_handler.py: Avoid double unmount 2023-02-08 13:15:19 -07:00
Mykola Grymalyuk
0d38bc0edf kdk_handler.py: Add extra error handling to failed KDK install 2023-02-08 12:23:25 -07:00
Mykola Grymalyuk
4c4cacf114 gui_main.py: Add indicator for longer kdk_handler init 2023-02-08 11:22:25 -07:00
Mykola Grymalyuk
dd1afd77e4 sys_patch_detect.py: Switch to kdk_handler for KDK detection 2023-02-08 11:19:19 -07:00
Mykola Grymalyuk
2a91b2a11c kdk_handler.py: Support passive mode 2023-02-08 11:15:57 -07:00
Mykola Grymalyuk
b3ed101ad9 kdk_handler.py: Add support for KDK installation 2023-02-08 11:07:17 -07:00
Mykola Grymalyuk
7b33e77947 kdk_handler.py: Cache API result 2023-02-08 10:34:45 -07:00
Mykola Grymalyuk
ea35eaca2e gui_main.py: Ensure passed values are integers 2023-02-07 22:38:02 -07:00
Mykola Grymalyuk
b5c613242f kdk_handler.py: Write KDK info to plist 2023-02-07 17:29:29 -07:00
Mykola Grymalyuk
2eb98b6327 kdk_handler.py: Fix OS comparison check 2023-02-07 14:33:13 -07:00
Mykola Grymalyuk
6629e9dcef kdk_handler.py: Merge similar functions 2023-02-07 14:25:13 -07:00
Mykola Grymalyuk
6c441e835c data: remove unused dylib_data.py
File was superseded with sys_patch_dict.py’s introduction
2023-02-07 09:04:49 -07:00
Mykola Grymalyuk
fe8a2d253a gui_main.py: Add support for KDK chainloading 2023-02-06 21:28:28 -07:00
Mykola Grymalyuk
68af20d2fa kdk_handler.py: Implement additional OS check 2023-02-06 20:10:39 -07:00
Mykola Grymalyuk
625da17f1e Merge pull request #1028 from dortania/ci-keys
CI: Rework build system
2023-02-06 18:44:51 -05:00
Mykola Grymalyuk
118d635264 CI: Fix bundle ID 2023-02-06 14:33:52 -07:00
Mykola Grymalyuk
bd471df48f CI: Fix Password export 2023-02-06 14:21:53 -07:00
Mykola Grymalyuk
d70daaf5a4 CI: Expose enviroment variables 2023-02-06 14:09:36 -07:00
Mykola Grymalyuk
7fc9f3af7c CI: Use repository secrets for signing 2023-02-06 14:03:25 -07:00
Mykola Grymalyuk
6f4c110318 gui_main.py: Adjust import formatting 2023-02-06 09:05:43 -07:00
Mykola Grymalyuk
dfdb5b4c68 gui_main.py: Avoid nested logic 2023-02-06 09:02:46 -07:00
Mykola Grymalyuk
5ac3343205 gui_main.py: Set expected install size as variable 2023-02-06 08:56:59 -07:00
Mykola Grymalyuk
42dddfdccf kdk_handler.py: Publish expected file size 2023-02-06 08:55:22 -07:00
Mykola Grymalyuk
ace93b8a3e network_handler.py: Match formatting and check download path space 2023-02-06 08:54:55 -07:00
Mykola Grymalyuk
6081ec0d73 kdk_handler.py: Allow path overriding 2023-02-06 08:16:35 -07:00
Mykola Grymalyuk
fc85789f86 Merge pull request #1027 from dortania/py10
Upgrade internal build system dependancies
2023-02-05 22:42:30 -05:00
Mykola Grymalyuk
52fbe23a16 Upgrade internal build system dependancies 2023-02-05 20:11:47 -07:00
Mykola Grymalyuk
99968dbb57 Merge pull request #1024 from dortania/logging
Rework core libraries
2023-02-05 20:19:42 -05:00
Mykola Grymalyuk
b141452005 logging_handler.py: Set StreamHandler to Standard Out 2023-02-05 16:31:49 -07:00
Mykola Grymalyuk
b97a3bc4d8 Docs: Update for Ventura 2023-02-05 11:17:45 -07:00
Mykola Grymalyuk
f4ed623c76 logging_handler.py: Add additional 2023-02-05 11:17:09 -07:00
Mykola Grymalyuk
ad9ae96c7f logging_handler.py: adjust formatting 2023-02-05 10:35:14 -07:00
Mykola Grymalyuk
bece5d4b3b logging_handler.py: Avoid file size check if missing 2023-02-05 09:58:00 -07:00
Mykola Grymalyuk
f915199b92 logging init: Move to dedicated module 2023-02-05 09:41:41 -07:00
Mykola Grymalyuk
cba9d1e224 main.py: Add traceback logging in spawned threads 2023-02-04 21:52:24 -07:00
Mykola Grymalyuk
4782ccdab1 network_handler.py: Adjust time elasped format 2023-02-04 17:43:30 -07:00
Mykola Grymalyuk
1ab2409176 sys_patch.py: Fix PSP handling 2023-02-04 17:37:32 -07:00
Mykola Grymalyuk
3d8bae1142 gui_main.py: Remove double download invocation 2023-02-04 17:29:55 -07:00
Mykola Grymalyuk
baf25319d7 main.py: Don’t double print traceback 2023-02-04 17:25:00 -07:00
Mykola Grymalyuk
43ef3e18ec main.py: Route traceback after logging init 2023-02-04 16:32:25 -07:00
Mykola Grymalyuk
08710bc47b network_handler: Add link validation 2023-02-04 14:46:25 -07:00
Mykola Grymalyuk
6c294902c2 Finish migration to network_handler.py usage 2023-02-04 13:11:18 -07:00
Mykola Grymalyuk
976f14eeb3 logging: Adjust file handling path 2023-02-04 10:26:36 -07:00
Mykola Grymalyuk
eb1e29f95b network_handler.py: Ensure stop() holds thread 2023-02-03 21:18:13 -07:00
Mykola Grymalyuk
4d89c220bf network_handler.py: Set 10s timeout 2023-02-03 09:08:41 -07:00
Mykola Grymalyuk
571f297906 network_handler.py: Use enums for status 2023-02-03 08:45:37 -07:00
Mykola Grymalyuk
7bf4fd0150 network_handler.py: Set file path in Object init 2023-02-03 08:33:47 -07:00
Mykola Grymalyuk
d4f42c8b32 network_handler.py: Add docstrings 2023-02-03 06:59:29 -07:00
Mykola Grymalyuk
664972fd2e network_handler.py: Adjust naming scheme to PEP 2023-02-02 22:13:00 -07:00
Mykola Grymalyuk
f33cc2d5bb Adjust remaining functions to logging 2023-02-02 17:52:46 -07:00
Mykola Grymalyuk
da45a964cb Merge branch 'main' into logging
# Conflicts:
#	resources/kdk_handler.py
#	resources/utilities.py
2023-02-02 17:41:59 -07:00
Mykola Grymalyuk
6c555db237 Sync changelog 2023-02-02 11:15:09 -07:00
Mykola Grymalyuk
c093f400a6 Merge pull request #1026 from dortania/kdk-updates
kdk_handler.py: Switch to KdkSupportPkg as primary source
2023-02-02 13:13:35 -05:00
Mykola Grymalyuk
92d57cb6d5 Merge pull request #1025 from dortania/update-models
Update models table for OCLP 0.6.x
2023-02-02 12:51:33 -05:00
Mykola Grymalyuk
3a6f87c9c8 network_handler.py: Initial implementation 2023-02-02 10:50:29 -07:00
Dhinak G
08a67e5d37 Remove Apple Developer portal downloading
It's dead, Jim
2023-01-30 17:14:29 -05:00
Dhinak G
6ed55ff462 Verify checksum while downloading instead of after
Should speed up checksum calculations by not looping over the file twice
2023-01-30 17:10:17 -05:00
Dhinak G
ef912277b6 Remove repetitive OHCI/usb info 2023-01-28 17:18:20 -05:00
Dhinak G
cef860dbf1 Minor changes
- MacBook4,1 is unsupported
- OCHI -> OHCI
- Update Xserve section
2023-01-28 17:16:14 -05:00
Michael
e5b89e9c6b fix missing asterisk 2023-01-28 17:16:14 -05:00
Michael
7ebdb83af7 OCLP 0.6.x updates 2023-01-28 17:16:14 -05:00
Mykola Grymalyuk
da2fc5c4c3 Merge branch 'main' into logging 2023-01-26 11:32:09 -07:00
Mykola Grymalyuk
94cfeabdfd sys_patch: Add unpatch validation 2023-01-26 11:29:09 -07:00
Mykola Grymalyuk
e7727adcc6 logging: Use generic saving 2023-01-26 10:33:01 -07:00
Mykola Grymalyuk
8becb554fc Implement logging library 2023-01-25 20:50:53 -07:00
Mykola Grymalyuk
97024361cd sys_patch: Add ColorSync downgrade 2023-01-25 12:15:06 -07:00
Mykola Grymalyuk
71ca6731fa Increment Build 2023-01-23 21:12:10 -07:00
98 changed files with 9444 additions and 5472 deletions

View File

@@ -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:

View File

@@ -10,16 +10,21 @@ 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.9/bin/python3 Build-Binary.command --reset_binaries --branch "${{ env.branch }}" --commit "${{ env.commiturl }}" --commit_date "${{ env.commitdate }}"
- 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" --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
@@ -27,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
@@ -40,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

View File

@@ -1,30 +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.9/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

View File

@@ -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:

20
.github/workflows/validate.yml vendored Normal file
View File

@@ -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

1
.gitignore vendored
View File

@@ -32,3 +32,4 @@ __pycache__/
/payloads.dmg
/payloads/OpenCore-Legacy-Patcher-*.plist
/payloads/KDK.dmg
*.log

View File

@@ -1,14 +1,7 @@
#!/usr/bin/env python3
# This script's main purpose is to handle the following:
# - Download PatcherSupportPkg resources
# - Convert payloads directory into DMG (GUI only)
# - Build Binary via Pyinstaller
# - Add Launcher.sh (TUI only)
# - Patch 'LC_VERSION_MIN_MACOSX' to OS X 10.10
# - Add commit data to Info.plist
# Copyright (C) 2022 - Mykola Grymalyuk
# Generate stand alone application for OpenCore-Patcher
# Copyright (C) 2022-2023 - Mykola Grymalyuk
from pathlib import Path
import time
@@ -21,26 +14,49 @@ import sys
from resources import constants
class create_binary:
class CreateBinary:
"""
Library for creating OpenCore-Patcher application
This script's main purpose is to handle the following:
- Download external dependancies (ex. PatcherSupportPkg)
- Convert payloads directory into DMG
- Build Binary via Pyinstaller
- Patch 'LC_VERSION_MIN_MACOSX' to OS X 10.10
- Add commit data to Info.plist
"""
def __init__(self):
start = time.time()
print("- Starting build script")
self.set_cwd()
self.args = self.parse_arguments()
self.preflight_processes()
self.build_binary()
self.postflight_processes()
self.args = self._parse_arguments()
self._set_cwd()
self._preflight_processes()
self._build_binary()
self._postflight_processes()
print(f"- Build script completed in {str(round(time.time() - start, 2))} seconds")
def set_cwd(self):
def _set_cwd(self):
"""
Initialize current working directory to parent of this script
"""
os.chdir(Path(__file__).resolve().parent)
print(f"- Current Working Directory: \n\t{os.getcwd()}")
def parse_arguments(self):
def _parse_arguments(self):
"""
Parse arguments passed to script
"""
parser = argparse.ArgumentParser(description='Builds OpenCore-Patcher binary')
parser.add_argument('--build_tui', action='store_true', help='Builds TUI binary, if omitted GUI binary is built')
parser.add_argument('--branch', type=str, help='Git branch name')
parser.add_argument('--commit', type=str, help='Git commit URL')
parser.add_argument('--commit_date', type=str, help='Git commit date')
@@ -48,7 +64,12 @@ class create_binary:
args = parser.parse_args()
return args
def setup_pathing(self):
def _setup_pathing(self):
"""
Initialize pathing for pyinstaller
"""
python_path = sys.executable
python_binary = python_path.split("/")[-1]
python_bin_dir = python_path.strip(python_binary)
@@ -70,25 +91,36 @@ class create_binary:
self.pyinstaller_path = pyinstaller_path
def preflight_processes(self):
def _preflight_processes(self):
"""
Start preflight processes
"""
print("- Starting preflight processes")
self.setup_pathing()
self.delete_extra_binaries()
self.download_resources()
if not self.args.build_tui:
# payloads.dmg is only needed for GUI builds
self.generate_payloads_dmg()
self._setup_pathing()
self._delete_extra_binaries()
self._download_resources()
self._generate_payloads_dmg()
def _postflight_processes(self):
"""
Start postflight processes
"""
def postflight_processes(self):
print("- Starting postflight processes")
if self.args.build_tui:
self.move_launcher()
self.patch_load_command()
self.add_commit_data()
self.post_flight_cleanup()
self.mini_validate()
self._patch_load_command()
self._add_commit_data()
self._post_flight_cleanup()
self._mini_validate()
def _build_binary(self):
"""
Build binary via pyinstaller
"""
def build_binary(self):
if Path(f"./dist/OpenCore-Patcher.app").exists():
print("- Found OpenCore-Patcher.app, removing...")
rm_output = subprocess.run(
@@ -101,12 +133,8 @@ class create_binary:
raise Exception("Remove failed")
if self.args.build_tui:
print("- Building TUI binary...")
build_args = [self.pyinstaller_path, "./OpenCore-Patcher.spec", "--noconfirm"]
else:
print("- Building GUI binary...")
build_args = [self.pyinstaller_path, "./OpenCore-Patcher-GUI.spec", "--noconfirm"]
print("- Building GUI binary...")
build_args = [self.pyinstaller_path, "./OpenCore-Patcher-GUI.spec", "--noconfirm"]
build_result = subprocess.run(build_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if build_result.returncode != 0:
@@ -114,27 +142,52 @@ class create_binary:
print(build_result.stderr.decode('utf-8'))
raise Exception("Build failed")
def delete_extra_binaries(self):
delete_files = [
"AutoPkg-Assets.pkg",
"AutoPkg-Assets.pkg.zip",
"InstallAssistant.pkg",
"InstallAssistant.pkg.integrityDataV1",
"KDK.dmg",
def _delete_extra_binaries(self):
"""
Delete extra binaries from payloads directory
"""
whitelist_folders = [
"ACPI",
"Config",
"Drivers",
"Icon",
"InstallPackage",
"Kexts",
"OpenCore",
"Tools",
]
whitelist_files = [
"com.dortania.opencore-legacy-patcher.auto-patch.plist",
"entitlements.plist",
"launcher.sh",
"OC-Patcher-TUI.icns",
"OC-Patcher.icns",
"Universal-Binaries.zip",
]
print("- Deleting extra binaries...")
for file in Path("payloads").glob(pattern="*"):
if file.name in delete_files or file.name.startswith("OpenCore-Legacy-Patcher"):
if file.is_dir():
if file.name in whitelist_folders:
continue
print(f" - Deleting {file.name}")
file.unlink()
elif (Path(file) / Path("Contents/Resources/createinstallmedia")).exists():
print(f" - Deleting {file}")
subprocess.run(["rm", "-rf", file])
elif Path(file).is_dir() and file.name == "Universal-Binaries":
print(f" - Deleting {file}")
subprocess.run(["rm", "-rf", file])
else:
if file.name in whitelist_files:
continue
print(f" - Deleting {file.name}")
subprocess.run(["rm", "-f", file])
def _download_resources(self):
"""
Download required dependencies
"""
def download_resources(self):
patcher_support_pkg_version = constants.Constants().patcher_support_pkg_version
required_resources = [
"Universal-Binaries.zip"
@@ -181,21 +234,29 @@ class create_binary:
print(mv_output.stderr.decode('utf-8'))
raise Exception("Move failed")
def generate_payloads_dmg(self):
def _generate_payloads_dmg(self):
"""
Generate disk image containing all payloads
Disk image will be password protected due to issues with
Apple's notarization system and inclusion of kernel extensions
"""
if Path("./payloads.dmg").exists():
if self.args.reset_binaries:
print(" - Removing old payloads.dmg")
rm_output = subprocess.run(
["rm", "-rf", "./payloads.dmg"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
if rm_output.returncode != 0:
print("- Remove failed")
print(rm_output.stderr.decode('utf-8'))
raise Exception("Remove failed")
else:
if not self.args.reset_binaries:
print(" - payloads.dmg already exists, skipping creation")
return
print(" - Removing old payloads.dmg")
rm_output = subprocess.run(
["rm", "-rf", "./payloads.dmg"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
if rm_output.returncode != 0:
print("- Remove failed")
print(rm_output.stderr.decode('utf-8'))
raise Exception("Remove failed")
print(" - Generating DMG...")
dmg_output = subprocess.run([
'hdiutil', 'create', './payloads.dmg',
@@ -213,7 +274,12 @@ class create_binary:
print(" - DMG generation complete")
def add_commit_data(self):
def _add_commit_data(self):
"""
Add commit data to Info.plist
"""
if not self.args.branch and not self.args.commit and not self.args.commit_date:
print(" - No commit data provided, adding source info")
branch = "Built from source"
@@ -233,20 +299,25 @@ class create_binary:
}
plistlib.dump(plist, Path(plist_path).open("wb"), sort_keys=True)
def patch_load_command(self):
# Patches LC_VERSION_MIN_MACOSX in Load Command to report 10.10
#
# By default Pyinstaller will create binaries supporting 10.13+
# However this limitation is entirely arbitrary for our libraries
# and instead we're able to support 10.10 without issues.
#
# To verify set version:
# otool -l ./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher
#
# cmd LC_VERSION_MIN_MACOSX
# cmdsize 16
# version 10.13
# sdk 10.9
def _patch_load_command(self):
"""
Patch LC_VERSION_MIN_MACOSX in Load Command to report 10.10
By default Pyinstaller will create binaries supporting 10.13+
However this limitation is entirely arbitrary for our libraries
and instead we're able to support 10.10 without issues.
To verify set version:
otool -l ./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher
cmd LC_VERSION_MIN_MACOSX
cmdsize 16
version 10.13
sdk 10.9
"""
print(" - Patching LC_VERSION_MIN_MACOSX")
path = './dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher'
find = b'\x00\x0D\x0A\x00' # 10.13 (0xA0D)
@@ -257,19 +328,12 @@ class create_binary:
with open(path, 'wb') as f:
f.write(data)
def move_launcher(self):
print(" - Adding TUI launcher")
mv_output = subprocess.run(
["cp", "./payloads/launcher.sh", "./dist/OpenCore-Patcher.app/Contents/MacOS/Launcher"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
if mv_output.returncode != 0:
print(" - Move failed")
print(mv_output.stderr.decode('utf-8'))
raise Exception("Move failed")
def post_flight_cleanup(self):
# Remove ./dist/OpenCore-Patcher
def _post_flight_cleanup(self):
"""
Post flight cleanup
"""
path = "./dist/OpenCore-Patcher"
print(f" - Removing {path}")
rm_output = subprocess.run(
@@ -281,9 +345,12 @@ class create_binary:
print(rm_output.stderr.decode('utf-8'))
raise Exception(f"Remove failed: {path}")
def mini_validate(self):
# Ensure binary can start
# Only build a single config, TUI CI will do in-depth validation
def _mini_validate(self):
"""
Validate generated binary
"""
print(" - Validating binary")
validate_output = subprocess.run(
["./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher", "--build", "--model", "MacPro3,1"],
@@ -294,5 +361,6 @@ class create_binary:
print(validate_output.stderr.decode('utf-8'))
raise Exception("Validation failed")
if __name__ == "__main__":
create_binary()
CreateBinary()

View File

@@ -1,5 +1,75 @@
# OpenCore Legacy Patcher changelog
## 0.6.2
- Work around Black Box rendering issues on certain Display Color Profiles
- Limited to Ventura currently due to limitations with other color profiles
- Applicable for HD3000-based machines (ex. MacBookAir4,x, MacBookPro8,x, Macmini5,x)
- 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
- 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
- Implement Hardware Encoding support for AMD GCN 1-3, Polaris and Vega GPUs
- Applicable for pre-Haswell Macs on macOS Ventura
- Resolves DRM playback issues on Netflix, Disney+, etc.
- Note: GCN 1-3 DRM is functional, however hardware video encoding is still experimental
- AppleTV+ may be unstable due to this
- Implement support for AMD Navi and Lexa MXM GPUs in 2009-2011 iMacs
- Primarily applicable for MXM 3.0 variants of AMD WX3200 (0x6981) and AMD RX5500XT (0x7340)
- Credit to [Ausdauersportler](https://github.com/Ausdauersportler) for implementation
- Implement Continuity Camera Unlocking for pre-Kaby Lake CPUs
- Applicable for all legacy Macs in macOS Ventura
- Resolve boot support for 3802-based GPUs with macOS 13.3
- Applicable for following GPUs:
- Intel Ivy Bridge and Haswell iGPUs
- Nvidia Kepler dGPUs
- Note: patchset now requires AMFI to be disabled, patchset still in active development to remove this requirement
- Backend Changes:
- Refactored kdk_handler.py
- 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 `~/Library/Logs/OpenCore-Patcher.log`
- Subsequent runs are appended to the log, allowing for easy debugging
- Implemented new network_handler.py module
- 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
- TUI modules
- Build Server Changes:
- Upgrade Python backend to 3.10.9
- Upgrade Python modules:
- requests - 2.28.2
- pyobjc - 9.0.1
- wxpython - 4.2.0
- pyinstaller - 5.7.0
- packaging - 23.0
- Increment Binaries:
- PatcherSupportPkg 0.8.7 - release
- AutoPkgInstaller 1.0.2 - release
- FeatureUnlock 1.1.4 - rolling (0e8d87f)
- Lilu 1.6.4 - release
- WhateverGreen 1.6.4 - release
- NVMeFix 1.1.0 - release
- Innie 1.3.1 - release
- OpenCorePkg 0.9.0 - release
## 0.6.1
- Avoid usage of KDKlessWorkaround on hardware not requiring it
- Resolves AMD Graphics Regression from 0.5.3

View File

@@ -3,4 +3,4 @@
from resources import main
if __name__ == '__main__':
main.OpenCoreLegacyPatcher(True)
main.OpenCoreLegacyPatcher()

View File

@@ -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()

View File

@@ -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,
})

View File

@@ -15,7 +15,7 @@ Our project's main goal is to breath new life to Macs no longer supported by App
Noteworthy features of OpenCore Legacy Patcher:
* Support for macOS Big Sur and Monterey
* Support for macOS Big Sur, Monterey and Ventura
* Native Over the Air(OTA) System Updates
* Supports Penryn and newer Macs
* Full support for WPA Wifi and Personal Hotspot on BCM943224 and newer chipsets
@@ -32,7 +32,7 @@ Note: Only clean-installs and upgrades are supported, macOS Big Sur installs alr
* You can however reinstall macOS with this patcher and retain your original data
Note 2: Currently OpenCore Legacy Patcher officially supports patching to run macOS Big Sur and Monterey installs. For older OSes, OpenCore may function however support is currently not provided from Dortania.
Note 2: Currently OpenCore Legacy Patcher officially supports patching to run macOS Big Sur through Ventura installs. For older OSes, OpenCore may function however support is currently not provided from Dortania.
* For macOS Mojave and Catalina support, we recommend the use of [dosdude1's patchers](http://dosdude1.com)

View File

@@ -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,41 +31,31 @@ 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.
## 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.
@@ -79,7 +68,6 @@ pip3 install pyinstaller
cd ~/Developer/OpenCore-Legacy-Patcher/
# Create the pyinstaller based Application
# Optional Arguments
# '--build_tui': Create TUI vairant
# '--reset_binaries': Redownload and generate support files
python3 Build-Binary.command
# Open build folder

View File

@@ -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)

View File

@@ -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",
]

View File

@@ -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,
}

View File

@@ -249,6 +249,9 @@ IntelNvidiaDRM = [
# Mac Pro and Xserve
MacPro = ["MacPro3,1", "MacPro4,1", "MacPro5,1", "Xserve2,1", "Xserve3,1", "Dortania1,1"]
# MXM iMac
MXMiMac = ["iMac11,1", "iMac11,2", "iMac11,3", "iMac10,1", "iMac12,1", "iMac12,2", "Dortania1,1"]
NoAGPMSupport = ["MacBook4,1", "MacBookPro4,1", "iMac7,1", "iMac8,1", "MacPro3,1", "Xserve2,1", "Dortania1,1"]
AGDPSupport = [
@@ -350,4 +353,4 @@ Missing_USB_Map_Ventura = [
"iMac16,1",
"iMac16,2",
"iMac17,1",
]
]

View File

@@ -796,6 +796,11 @@ class amd_ids:
0x67CF, # Unknown
]
polaris_spoof_ids = [
# Polaris 12 (Lexa)
0x6981, # Lexa XT [Radeon PRO WX 3200]
]
vega_ids = [
# GCN v5
# AMDRadeonX5000

File diff suppressed because it is too large Load Diff

11
data/video_bios_data.py Normal file

File diff suppressed because one or more lines are too long

View File

@@ -15,7 +15,7 @@
* [Black Boxes on HD3000 iGPUs](#black-boxes-on-hd3000-igpus)
* [Cannot Pair Bluetooth Devices](#cannot-pair-bluetooth-devices)
The below page is for users experiencing issues with their overall usage of macOS Big Sur / macOS Monterey and the Legacy Graphics Acceleration patches. Note that the following GPUs currently do not have acceleration support in Big Sur / Monterey:
The below page is for users experiencing issues with their overall usage of macOS Big Sur / Monterey / Ventura and the Legacy Graphics Acceleration patches. Note that the following GPUs currently do not have acceleration support in Big Sur / Monterey / Ventura:
* Intel 3rd and 4th Gen - GMA series
@@ -23,7 +23,7 @@ For those unfamiliar with what is considered a non-Metal GPU, see the chart belo
::: details macOS GPU Chart
Metal is Apple's in-house graphics API that acts as a replacement for OpenGL/OpenCL, introduced in 2015. With the release of macOS Mojave, every system without a Metal-capable GPU was dropped.
Metal is Apple's in-house graphics API that acts as a replacement for OpenGL/OpenCL, introduced in 2015. With the release of macOS Mojave, every system without a Metal-capable GPU was dropped.
| Graphics Vendor | Architecture | Series | Supports Metal |
| :--- | :--- | :--- | :--- |
@@ -31,7 +31,7 @@ Metal is Apple's in-house graphics API that acts as a replacement for OpenGL/Ope
| ^^ | TeraScale 2 | HD5000 - HD6000 | ^^ |
| AMD | GCN (and newer) | HD7000+ | <span style="color:green">Yes</span> |
| NVIDIA | Tesla | 8000GT - GT300 | <span style="color:red">No</span> |
| ^^ | Fermi | GT400 - GT500 | ^^ |
| ^^ | Fermi | GT400 - GT500 | ^^ |
| ^^ | Kepler | GT600 - GT700 | <span style="color:green">Yes</span> |
| Intel | GMA | GMA900 - GMA3000 | <span style="color:red">No</span> |
| ^^ | Iron Lake | HD series | ^^ |
@@ -102,7 +102,7 @@ Due to the Metal Backend, the enhanced color output of these apps seems to heavi
## Cannot press "Done" when editing a Sidebar Widget
Workaround: Press some combination of Tab, or Tab and then Shift-Tab, or just Shift-Tab until the "Done" button is highlighted. Then press spacebar to activate the button, the same as in any other dialog with a highlighted button halo.
Workaround: Press some combination of Tab, or Tab and then Shift-Tab, or just Shift-Tab until the "Done" button is highlighted. Then press spacebar to activate the button, the same as in any other dialog with a highlighted button halo.
## Wake from sleep heavily distorted on AMD/ATI in macOS 11.3 and newer

View File

@@ -18,11 +18,11 @@ The below table will list all supported and unsupported functions of the patcher
Regarding OS support, see below:
* Machines listing `YES - Monterey and older` means they cannot run macOS Ventura at this time. Machines with only `YES` can run all of the supported macOS versions offered by OpenCore Legacy Patcher.
* Machines listing `YES - Ventura and older` means they cannot run macOS Ventura at this time. Machines with only `YES` can run all of the supported macOS versions offered by OpenCore Legacy Patcher.
| 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
@@ -32,12 +32,12 @@ Regarding OS support, see below:
| MacBook1,1 | Mid-2006 | <span style="color:red"> NO </span> | 32-Bit CPU limitation |
| MacBook2,1 | Late 2006 | ^^ | 32-Bit Firmware limitation |
| MacBook3,1 | Late 2007 | ^^ | ^^ |
| MacBook4,1 | Early 2008 | <span style="color:#30BCD5"> YES - Monterey and older</span> | - No GPU Acceleration in Mavericks and newer<br/>- No Keyboard and Trackpad<br/>- No USB |
| MacBook5,1 | Late 2008 | ^^ | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))<br/> - Supports macOS Monterey and older ([Ventura support in development](./VENTURA-DROP.md))|
| MacBook4,1 | Early 2008 | ^^ | - No GPU Acceleration in Mavericks and newer<br/>- No Keyboard and Trackpad<br/>- No USB |
| MacBook5,1 | Late 2008 | <span style="color:#30BCD5"> YES </span> | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))<br/> - UHCI/OHCI support in Public Beta, see current issues ([#1021](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1021))|
| MacBook5,2 | Early 2009 | ^^ | ^^ |
| MacBook6,1 | Late 2009 | ^^ | ^^ |
| MacBook7,1 | Mid-2010 | ^^ | ^^ |
| MacBook8,1 | Mid-2015 | <span style="color:#30BCD5"> YES </span> | <span style="color:green"> Everything is supported</span> |
| MacBook8,1 | Mid-2015 | ^^ | <span style="color:green"> Everything is supported</span> |
| MacBook9,1 | Early 2016 | ^^ | ^^ |
### MacBook Air
@@ -45,12 +45,12 @@ Regarding OS support, see below:
| SMBIOS | Year | Supported | Comment |
| :--- | :--- | :--- | :--- |
| MacBookAir1,1 | Early 2008 | <span style="color:red"> NO </span> | Requires SSE4.1 CPU |
| MacBookAir2,1 | Late 2008 | <span style="color:#30BCD5"> YES - Monterey and older</span> | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))<br/> - Supports macOS Monterey and older ([Ventura support in development](./VENTURA-DROP.md))|
| MacBookAir2,1 | Late 2008 | <span style="color:#30BCD5"> YES </span> | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))<br/> - UHCI/OHCI support in Public Beta, see current issues ([#1021](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1021))|
| MacBookAir3,1 | Late 2010 | ^^ | ^^ |
| MacBookAir3,2 | ^^ | ^^ | ^^ |
| MacBookAir4,1 | Mid-2011 | ^^ | ^^ |
| MacBookAir4,1 | Mid-2011 | ^^ | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))|
| MacBookAir4,2 | ^^ | ^^ | ^^ |
| MacBookAir5,1 | Mid-2012 | <span style="color:#30BCD5"> YES </span> | <span style="color:green"> Everything is supported</span> |
| MacBookAir5,1 | Mid-2012 | ^^ | <span style="color:green"> Everything is supported</span> |
| MacBookAir5,2 | ^^ | ^^ | ^^ |
| MacBookAir6,1 | Mid-2013, Early 2014 | ^^ | ^^ |
| MacBookAir6,2 | ^^ | ^^ | ^^ |
@@ -66,19 +66,19 @@ Regarding OS support, see below:
| MacBookPro2,1 | Late 2006 | ^^ | 32-Bit Firmware limitation |
| MacBookPro2,2 | Late 2006 | ^^ | ^^ |
| MacBookPro3,1 | Mid-2007 | ^^ | Requires SSE4.1 CPU |
| MacBookPro4,1 | Early 2008 | <span style="color:#30BCD5"> YES - Monterey and older</span> | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))<br/> - Supports macOS Monterey and older ([Ventura support in development](./VENTURA-DROP.md))|
| MacBookPro4,1 | Early 2008 | <span style="color:#30BCD5"> YES </span> | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))<br/> - UHCI/OHCI support in Public Beta, see current issues ([#1021](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1021))|
| MacBookPro5,1 | Late 2008 | ^^ | ^^ |
| MacBookPro5,2 | Early 2009 | ^^ | ^^ |
| MacBookPro5,3 | Mid-2009 | ^^ | ^^ |
| MacBookPro5,4 | ^^ | ^^ | ^^ |
| MacBookPro5,5 | ^^ | ^^ | ^^ |
| MacBookPro6,1 | Mid-2010 | ^^ | ^^ |
| MacBookPro6,1 | Mid-2010 | ^^ | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))|
| MacBookPro6,2 | ^^ | ^^ | ^^ |
| MacBookPro7,1 | ^^ | ^^ | ^^ |
| MacBookPro8,1 | Early 2011 | ^^ | ^^ |
| MacBookPro7,1 | ^^ | ^^ | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))<br/> - UHCI/OHCI support in Public Beta, see current issues ([#1021](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1021))|
| MacBookPro8,1 | Early 2011 | ^^ | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))|
| MacBookPro8,2 | ^^ | ^^ | ^^ |
| MacBookPro8,3 | ^^ | ^^ | ^^ |
| MacBookPro9,1 | Mid-2012 | <span style="color:#30BCD5"> YES </span> | <span style="color:green"> Everything is supported</span> |
| MacBookPro9,1 | Mid-2012 | ^^ | <span style="color:green"> Everything is supported</span> |
| MacBookPro9,2 | ^^ | ^^ | ^^ |
| MacBookPro10,1 | Mid-2012, Early 2013 | ^^ | ^^ |
| MacBookPro10,2 | Late 2012, Early 2013 | ^^ | ^^ |
@@ -98,12 +98,12 @@ Regarding OS support, see below:
| :--- | :--- | :--- | :--- |
| Macmini1,1 | Early 2006 | <span style="color:red"> NO </span> | 32-Bit CPU limitation |
| Macmini2,1 | Mid-2007 | ^^ | 32-Bit Firmware limitation |
| Macmini3,1 | Early 2009 | <span style="color:#30BCD5"> YES - Monterey and older</span> | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108)) <br/> - Supports macOS Monterey and older ([Ventura support in development](./VENTURA-DROP.md)) |
| Macmini3,1 | Early 2009 | <span style="color:#30BCD5"> YES </span> | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))<br/> - UHCI/OHCI support in Public Beta, see current issues ([#1021](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1021))|
| Macmini4,1 | Mid-2010 | ^^ | ^^ |
| Macmini5,1 | Mid-2011 | ^^ | ^^ |
| Macmini5,1 | Mid-2011 | ^^ | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))|
| Macmini5,2 | ^^ | ^^ | ^^ |
| Macmini5,3 | ^^ | ^^ | ^^ |
| Macmini6,1 | Late 2012 | <span style="color:#30BCD5"> YES </span> | <span style="color:green"> Everything is supported</span> |
| Macmini6,1 | Late 2012 | ^^ | <span style="color:green"> Everything is supported</span> |
| Macmini6,2 | ^^ | ^^ | ^^ |
| Macmini7,1 | Mid-2014 | ^^ | ^^ |
@@ -116,16 +116,16 @@ Regarding OS support, see below:
| iMac5,1 | Late 2006 | ^^ | 32-Bit Firmware limitation |
| iMac5,2 | ^^ | ^^ | ^^ |
| iMac6,1 | ^^ | ^^ | ^^ |
| iMac7,1 | Mid-2007 | <span style="color:#30BCD5"> YES - Monterey and older</span> | - Requires an [SSE4.1 CPU Upgrade](https://lowendmac.com/2018/penryn-t9300-9500-cpu-upgrades-for-the-2007-imac/)<br/>- GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))<br/>- Stock Bluetooth 2.0 card non-functional<br/> - Supports macOS Monterey and older ([Ventura support in development](./VENTURA-DROP.md)) |
| iMac8,1 | Early 2008 | ^^ | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108)))<br/> - Supports macOS Monterey and older ([Ventura support in development](./VENTURA-DROP.md))|
| iMac7,1 | Mid-2007 | <span style="color:#30BCD5"> YES </span> | - Requires an [SSE4.1 CPU Upgrade](https://lowendmac.com/2018/penryn-t9300-9500-cpu-upgrades-for-the-2007-imac/)<br/>- GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))<br/>- Stock Bluetooth 2.0 card non-functional<br/> - UHCI/OHCI support in Public Beta, see current issues ([#1021](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1021))|
| iMac8,1 | Early 2008 | ^^ | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))<br/> - UHCI/OHCI support in Public Beta, see current issues ([#1021](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1021))|
| iMac9,1 | Early 2009 | ^^ | ^^ |
| iMac10,1 | Late 2009 | ^^ | - GPU is socketed, [recommend upgrading to Metal GPU](https://forums.macrumors.com/threads/2011-imac-graphics-card-upgrade.1596614/?post=17425857#post-17425857) <br/>- GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108)) <br/> - Supports macOS Monterey and older ([Ventura support in development](./VENTURA-DROP.md))|
| iMac11,1 | ^^ | ^^ | ^^ |
| iMac10,1 | Late 2009 | ^^ | - GPU is socketed, [recommend upgrading to Metal GPU](https://forums.macrumors.com/threads/2011-imac-graphics-card-upgrade.1596614/?post=17425857#post-17425857) <br/>- GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108)) <br/> - UHCI/OHCI support in Public Beta, see current issues ([#1021](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1021))|
| iMac11,1 | ^^ | ^^ | - GPU Acceleration in Public Beta, see current issues ([#108](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/108))|
| iMac11,2 | Mid-2010 | ^^ | ^^ |
| iMac11,3 | ^^ | ^^ | ^^ |
| iMac12,1 | Mid-2011 | ^^ | ^^ |
| iMac12,2 | ^^ | ^^ | ^^ |
| iMac13,1 | Late 2012 | <span style="color:#30BCD5"> YES </span> | <span style="color:green"> Everything is supported</span> |
| iMac13,1 | Late 2012 | ^^ | <span style="color:green"> Everything is supported</span> |
| iMac13,2 | ^^ | ^^ | ^^ |
| iMac13,3 | ^^ | ^^ | ^^ |
| iMac14,1 | Late 2013 | ^^ | ^^ |
@@ -145,8 +145,8 @@ Regarding OS support, see below:
| :--- | :--- | :--- | :--- |
| MacPro1,1 | Mid-2006 | <span style="color:red"> NO </span> | 32-Bit Firmware limitation |
| MacPro2,1 | Mid-2007 | ^^ | ^^ |
| MacPro3,1 | Early 2008 | <span style="color:#30BCD5"> YES - Monterey and older</span> | - Potential boot issues with built-in USB 1.1 ports (recommend using a USB 2.0 hub or dedicated USB PCIe controller) <br/>- Potential boot issues with stock Bluetooth card, recommend removing to avoid kernel panics |
| MacPro4,1 | Early 2009 | ^^ | - Everything is supported as long as GPU is Metal capable <br/> - Supports macOS Monterey and older ([Ventura support in development](./VENTURA-DROP.md)) |
| MacPro3,1 | Early 2008 | <span style="color:#30BCD5"> YES </span> | - UHCI/OHCI support in Public Beta, see current issues ([#1021](https://github.com/dortania/OpenCore-Legacy-Patcher/issesues/1021)) <br/>- Potential boot issues with stock Bluetooth card, recommend removing to avoid kernel panics |
| MacPro4,1 | Early 2009 | ^^ | - Everything is supported as long as GPU is Metal capable <br/> - UHCI/OHCI support in Public Beta, see current issues ([#1021](https://github.com/dortania/OpenCore-Legacy-Patcher/issesues/1021))|
| MacPro5,1 | Mid-2010, Mid-2012 | ^^ | ^^ |
| MacPro6,1 | Late 2013 | ^^ | - CPU Power Management currently unsupported<br/> - No DRM support |
@@ -155,7 +155,7 @@ Regarding OS support, see below:
| SMBIOS | Year | Supported | Comment |
| :--- | :--- | :--- | :--- |
| Xserve1,1 | Mid-2006 | <span style="color:red"> NO </span> | 32-Bit Firmware limitation |
| Xserve2,1 | Early 2008 | <span style="color:#30BCD5"> YES - Monterey and older</span> | - Everything is supported as long as GPU is Metal capable <br/> - Supports macOS Monterey and older ([Ventura support in development](./VENTURA-DROP.md))|
| Xserve2,1 | Early 2008 | <span style="color:#30BCD5"> YES </span> | - UHCI/OHCI support in Public Beta, see current issues ([#1021](https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1021))|
| Xserve3,1 | Early 2009 | ^^ | ^^ |
# Once you've verified your hardware is supported, head to [Download and build macOS Installers](./INSTALLER.md)

View File

@@ -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.

View File

@@ -83,7 +83,7 @@ With OpenCore Legacy Patcher v0.6.0, basic support has been implemented via Root
MacPro3,1 suffers from Ethernet driver dying after returning from sleep, current workaround is to use a USB Ethernet adapter or disable sleep.
::: details Legacy Wireless Support (Resolved in v0.6.0)
::: details Legacy Wireless Support (Resolved in v0.6.0 and newer)
### Legacy Wireless Support
@@ -109,7 +109,7 @@ Currently BCM943224, BCM94331, BCM94360 and BCM943602 are still fully supported
:::
::: details Non-Metal Graphics Acceleration (Resolved in 0.6.0 and newer)
::: details Non-Metal Graphics Acceleration (Resolved in v0.6.0 and newer)
### Non-Metal Graphics Acceleration

View File

@@ -2699,6 +2699,8 @@
<true/>
<key>ResizeGpuBars</key>
<integer>-1</integer>
<key>ResizeUsePciRbIo</key>
<false/>
<key>TscSyncTimeout</key>
<integer>0</integer>
<key>UnblockFsConnect</key>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,2 @@
Following patch required for iMacs with Navi MXM GPUs
Source: https://github.com/kingo132/a51m-r2-5700m-hackintosh/issues/13

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,253 @@
#!/usr/bin/env python3
import os
import subprocess
from pathlib import Path
import requests
import packaging.version
import tempfile
# For kexts with basic handling requirements
KEXT_DICTIONARY = {
"Acidanthera": {
"AirportBrcmFixup": {
"Repository": "https://github.com/acidanthera/AirportBrcmFixup",
"Constants Variable": "self.airportbcrmfixup_version",
},
# Due to issues with legacy Macs, don't update
# "AppleALC": {
# "Repository": "https://github.com/acidanthera/AppleALC",
# "Constants Variable": "self.applealc_version",
# },
"BlueToolFixup": {
"Repository": "https://github.com/acidanthera/BrcmPatchRAM",
"Constants Variable": "self.bluetoolfixup_version",
"Override": "BrcmPatchRAM",
},
"CPUFriend": {
"Repository": "https://github.com/acidanthera/CPUFriend",
"Constants Variable": "self.cpufriend_version",
},
"CryptexFixup": {
"Repository": "https://github.com/acidanthera/CryptexFixup",
"Constants Variable": "self.cryptexfixup_version",
},
"DebugEnhancer": {
"Repository": "https://github.com/acidanthera/DebugEnhancer",
"Constants Variable": "self.debugenhancer_version",
},
"FeatureUnlock": {
"Repository": "https://github.com/acidanthera/FeatureUnlock",
"Constants Variable": "self.featureunlock_version",
},
"Lilu": {
"Repository": "https://github.com/acidanthera/Lilu",
"Constants Variable": "self.lilu_version",
},
"NVMeFix": {
"Repository": "https://github.com/acidanthera/NVMeFix",
"Constants Variable": "self.nvmefix_version",
},
"RestrictEvents": {
"Repository": "https://github.com/acidanthera/RestrictEvents",
"Constants Variable": "self.restrictevents_version",
},
"RSRHelper": {
"Repository": "https://github.com/khronokernel/RSRHelper",
"Constants Variable": "self.rsrhelper_version",
},
"WhateverGreen": {
"Repository": "https://github.com/acidanthera/WhateverGreen",
"Constants Variable": "self.whatevergreen_version",
},
},
"Misc": {
"Innie": {
"Repository": "https://github.com/cdf/Innie",
"Constants Variable": "self.innie_version",
},
},
}
class GenerateKexts:
def __init__(self):
self.weg_version = None
self.weg_old = None
self.lilu_version = None
self._set_cwd()
self._iterate_over_kexts()
self._special_kext_handling()
def _set_cwd(self):
# Set working directory to script location
script_path = Path(__file__).parent.absolute()
os.chdir(script_path)
def _special_kext_handling(self):
# Generate custom WhateverGreen
if self.weg_version is None or self.lilu_version is None or self.weg_old is None:
raise Exception("Unable to find latest WEG version!")
if packaging.version.parse(self.weg_version) <= packaging.version.parse(self.weg_old):
print(" WEG is up to date!")
return
# WhateverGreen
print("Building modified WhateverGreen...")
# We have to compile WEG ourselves
weg_source_url = f"https://github.com/acidanthera/WhateverGreen/archive/refs/tags/{self.weg_version}.zip"
lilu_url = f"https://github.com/acidanthera/Lilu/releases/download/{self.lilu_version}/Lilu-{self.lilu_version}-DEBUG.zip"
with tempfile.TemporaryDirectory() as temp_dir:
# Download source
weg_source_zip = f"{temp_dir}/WhateverGreen-{self.weg_version}.zip"
subprocess.run(["curl", "-L", weg_source_url, "-o", weg_source_zip], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# Unzip source
subprocess.run(["unzip", weg_source_zip, "-d", temp_dir], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# Git clone MacKernelSDK into source
subprocess.run(["git", "clone", "https://github.com/acidanthera/MacKernelSDK", f"{temp_dir}/WhateverGreen-{self.weg_version}/MacKernelSDK"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# Grab latest Lilu release, debug version
lilu_zip = f"{temp_dir}/Lilu-{self.lilu_version}-DEBUG.zip"
subprocess.run(["curl", "-L", lilu_url, "-o", lilu_zip], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# Unzip Lilu into WEG source
subprocess.run(["unzip", lilu_zip, "-d", f"{temp_dir}/WhateverGreen-{self.weg_version}"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# Apply patch
patch_path = Path("./Acidanthera/WhateverGreen-Navi-Backlight.patch").absolute()
subprocess.run(["git", "apply", patch_path], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=f"{temp_dir}/WhateverGreen-{self.weg_version}")
# Build WEG
for variant in ["Release", "Debug"]:
subprocess.run(["xcodebuild", "-configuration", variant], cwd=f"{temp_dir}/WhateverGreen-{self.weg_version}", check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# Zip Release
for variant in ["RELEASE", "DEBUG"]:
dst_path = Path(f"./Acidanthera/WhateverGreen-v{self.weg_version}-Navi-{variant}.zip").absolute()
subprocess.run(["zip", "-r", dst_path, "WhateverGreen.kext"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, cwd=f"{temp_dir}/WhateverGreen-{self.weg_version}/build/{'Release' if variant == 'RELEASE' else 'Debug'}")
if Path(f"./Acidanthera/WhateverGreen-v{self.weg_old}-Navi-{variant}.zip").exists():
subprocess.run(["rm", f"./Acidanthera/WhateverGreen-v{self.weg_old}-Navi-{variant}.zip"], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
self._update_constants_file("self.whatevergreen_navi_version", f"{self.weg_old}-Navi", f"{self.weg_version}-Navi")
def _iterate_over_kexts(self):
for kext_folder in KEXT_DICTIONARY:
for kext_name in KEXT_DICTIONARY[kext_folder]:
print(f"Checking {kext_name}...")
if "Override" in KEXT_DICTIONARY[kext_folder][kext_name]:
self._get_latest_release(kext_folder, kext_name, override_kext_zip_name=KEXT_DICTIONARY[kext_folder][kext_name]["Override"])
else:
self._get_latest_release(kext_folder, kext_name)
def _get_latest_release(self, kext_folder, kext_name, override_kext_zip_name=None):
# Get latest release from GitHub API
repo_url = KEXT_DICTIONARY[kext_folder][kext_name]["Repository"].replace("https://github.com", "https://api.github.com/repos")
latest_release = requests.get(f"{repo_url}/releases/latest").json()
for variant in ["RELEASE", "DEBUG"]:
if "tag_name" not in latest_release:
print(f" Error: {latest_release['message']}")
continue
remote_version = latest_release["tag_name"]
if remote_version.startswith("v"):
remote_version = remote_version[1:]
if kext_name == "WhateverGreen":
self.weg_version = remote_version
elif kext_name == "Lilu":
self.lilu_version = remote_version
local_version = self._get_local_version(kext_folder, kext_name, variant)
if kext_name == "WhateverGreen":
self.weg_old = local_version
if packaging.version.parse(remote_version) <= packaging.version.parse(local_version):
print(f" {kext_name} {variant} is up to date: v{local_version}")
continue
for asset in latest_release["assets"]:
if not asset["name"].endswith(f"{variant}.zip"):
continue
print(f" Downloading {kext_name} {variant}: v{remote_version}...")
zip_name = f"{override_kext_zip_name}-v{remote_version}-{variant}.zip" if override_kext_zip_name else f"{kext_name}-v{remote_version}-{variant}.zip"
self._download_file(asset["browser_download_url"], f"./{kext_folder}/{zip_name}", f"{kext_name}.kext")
if Path(f"./{kext_folder}/{zip_name}").exists():
subprocess.run(["rm", "-rf", f"./{kext_folder}/{zip_name.replace(f'v{remote_version}', f'v{local_version}')}"])
self._update_constants_file(KEXT_DICTIONARY[kext_folder][kext_name]["Constants Variable"], local_version, remote_version)
if override_kext_zip_name:
# rename zip file
os.rename(f"./{kext_folder}/{zip_name}", f"./{kext_folder}/{kext_name}-v{remote_version}-{variant}.zip")
subprocess.run(["rm", "-rf", f"./{kext_folder}/{kext_name}-v{local_version}-{variant}.zip"])
def _get_local_version(self, kext_folder, kext_name, variant):
loose_name_start = f"{kext_name}-v"
loose_name_end = f"-{variant}.zip"
for file in Path(f"./{kext_folder}").iterdir():
if file.name.startswith(loose_name_start) and file.name.endswith(loose_name_end):
local_version = file.name.replace(loose_name_start, "").replace(loose_name_end, "")
if local_version.startswith("v"):
local_version = local_version[1:]
return local_version[:5]
raise Exception(f"Could not find local version for {kext_name} {variant}")
def _download_file(self, url, file_path, file):
# Download file
if Path(file_path).exists():
os.remove(file_path)
with tempfile.TemporaryDirectory() as temp_dir:
download = requests.get(url)
with open(f"{temp_dir}/temp.zip", "wb") as f:
f.write(download.content)
# Unzip file
subprocess.run(["unzip", "-q", f"{temp_dir}/temp.zip", "-d", f"{temp_dir}"], check=True)
print(f" Moving {file} to {file_path}...")
# Zip file
subprocess.run(["zip", "-q", "-r", Path(file_path).name, file], cwd=f"{temp_dir}", check=True)
# Move file
subprocess.run(["mv", f"{temp_dir}/{Path(file_path).name}", file_path], check=True)
def _update_constants_file(self, variable_name, old_version, new_version):
print(f" Updating {variable_name} to {new_version}...")
constants_file = Path("../../resources/constants.py")
if not constants_file.exists():
raise Exception("Constants file does not exist")
constants_file_contents = constants_file.read_text()
# Replace version
for line in constants_file_contents.splitlines():
if variable_name in line:
constants_file_contents = constants_file_contents.replace(line, line.replace(old_version, new_version))
break
# Write file
constants_file.write_text(constants_file_contents)
if __name__ == '__main__':
GenerateKexts()

Binary file not shown.

Binary file not shown.

View File

@@ -1,27 +1,55 @@
# Determine AppleMobileFileIntegrity's OS configuration
# Copyright (C) 2022-2023, Mykola Grymalyuk
import enum
from resources import utilities
class amfi_configuration_detection:
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.boot_args = []
self.oclp_args = []
self.init_nvram_dicts()
self.parse_amfi_bitmask()
self.parse_amfi_boot_args()
self.parse_oclp_configuration()
from data import amfi_data
def init_nvram_dicts(self):
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:
>>> import amfi_detect
>>> can_patch = amfi_detect.AmfiConfigurationDetection().check_config(amfi_detect.AmfiConfigDetectLevel.ALLOW_ALL)
"""
def __init__(self) -> None:
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: list = []
self.oclp_args: list = []
self._init_nvram_dicts()
self._parse_amfi_bitmask()
self._parse_amfi_boot_args()
self._parse_oclp_configuration()
def _init_nvram_dicts(self) -> None:
"""
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,40 +60,47 @@ 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) -> None:
"""
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="):
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
def parse_amfi_boot_args(self):
def _parse_amfi_boot_args(self) -> None:
"""
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) -> None:
"""
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) -> bool:
"""
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

View File

@@ -1,115 +1,186 @@
import threading
import time
import logging
import sys
from resources import defaults, utilities, validation
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
import threading
import time
# Generic building args
class arguments:
def __init__(self):
def __init__(self, global_constants: constants.Constants) -> None:
self.constants: constants.Constants = global_constants
self.args = utilities.check_cli_args()
def parse_arguments(self, settings):
self._parse_arguments()
def _parse_arguments(self) -> None:
"""
Parses arguments passed to the patcher
"""
if self.args.validate:
validation.validate(settings)
elif self.args.build:
self._validation_handler()
return
if self.args.build:
self._build_handler()
return
if self.args.patch_sys_vol:
self._sys_patch_handler()
return
if self.args.unpatch_sys_vol:
self._sys_unpatch_handler()
return
if self.args.auto_patch:
self._sys_patch_auto_handler()
return
def _validation_handler(self) -> None:
"""
Enter validation mode
"""
validation.PatcherValidation(self.constants)
def _sys_patch_handler(self) -> None:
"""
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) -> None:
"""
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) -> None:
"""
Start root volume auto patching
"""
logging.info("- Set Auto patching")
sys_patch_auto.AutomaticSysPatch(self.constants).start_auto_patch()
def _build_handler(self) -> None:
"""
Start config building process
"""
if self.args.model:
if self.args.model:
if self.args.model:
print(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:
print(
"""Your model is not supported by this patcher for running unsupported OSes!"
logging.info(f"- Using custom model: {self.args.model}")
self.constants.custom_model = self.args.model
defaults.GenerateDefaults(self.constants.custom_model, False, self.constants)
elif self.constants.computer.real_model not in model_array.SupportedSMBIOS and self.constants.allow_oc_everywhere is False:
logging.info(
"""Your model is not supported by this patcher for running unsupported OSes!"
If you plan to create the USB for another machine, please select the "Change Model" option in the menu."""
)
sys.exit(1)
else:
print(f"- Using detected model: {settings.computer.real_model}")
defaults.generate_defaults(settings.custom_model, True, settings)
If you plan to create the USB for another machine, please select the "Change Model" option in the menu."""
)
sys.exit(1)
else:
logging.info(f"- Using detected model: {self.constants.computer.real_model}")
defaults.GenerateDefaults(self.constants.custom_model, True, self.constants)
if self.args.disk:
print(f"- Install Disk set: {self.args.disk}")
settings.disk = self.args.disk
if self.args.verbose:
print("- Set verbose configuration")
settings.verbose_debug = True
else:
settings.verbose_debug = False # Override Defaults detected
if self.args.debug_oc:
print("- Set OpenCore DEBUG configuration")
settings.opencore_debug = True
settings.opencore_build = "DEBUG"
if self.args.debug_kext:
print("- Set kext DEBUG configuration")
settings.kext_debug = True
if self.args.hide_picker:
print("- Set HidePicker configuration")
settings.showpicker = False
if self.args.disable_sip:
print("- Set Disable SIP configuration")
settings.sip_status = False
else:
settings.sip_status = True # Override Defaults detected
if self.args.disable_smb:
print("- Set Disable SecureBootModel configuration")
settings.secure_status = False
else:
settings.secure_status = True # Override Defaults detected
if self.args.vault:
print("- Set Vault configuration")
settings.vault = True
if self.args.firewire:
print("- Set FireWire Boot configuration")
settings.firewire_boot = True
if self.args.nvme:
print("- Set NVMe Boot configuration")
settings.nvme_boot = True
if self.args.wlan:
print("- Set Wake on WLAN configuration")
settings.enable_wake_on_wlan = True
if self.args.disable_tb:
print("- Set Disable Thunderbolt configuration")
settings.disable_tb = True
if self.args.force_surplus:
print("- Forcing SurPlus override configuration")
settings.force_surplus = True
if self.args.moderate_smbios:
print("- 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:
print(f"- Unknown SMBIOS arg passed: {self.args.smbios_spoof}")
if self.args.verbose:
logging.info("- Set verbose configuration")
self.constants.verbose_debug = True
else:
self.constants.verbose_debug = False # Override Defaults detected
if self.args.support_all:
print("- 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:
print("- Set System Volume patching")
if self.args.debug_oc:
logging.info("- Set OpenCore DEBUG configuration")
self.constants.opencore_debug = True
self.constants.opencore_build = "DEBUG"
if "Library/InstallerSandboxes/" in str(settings.payload_path):
print("- 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_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:
print("- Set System Volume unpatching")
sys_patch.PatchSysVolume(settings.custom_model or settings.computer.real_model, settings, None).start_unpatch()
elif self.args.auto_patch:
print("- Set Auto patching")
sys_patch_auto.AutomaticSysPatch(settings).start_auto_patch()
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()

View File

@@ -5,6 +5,8 @@ from resources import constants, device_probe
from resources.build import support
from data import smbios_data, bluetooth_data
import logging
class build_bluetooth:
def __init__(self, model, versions, config):
@@ -24,7 +26,7 @@ class build_bluetooth:
def on_model(self):
if self.computer.bluetooth_chipset in ["BRCM2070 Hub", "BRCM2046 Hub"]:
print("- Fixing Legacy Bluetooth for macOS Monterey")
logging.info("- Fixing Legacy Bluetooth for macOS Monterey")
support.build_support(self.model, self.constants, self.config).enable_kext("BlueToolFixup.kext", self.constants.bluetool_version, self.constants.bluetool_path)
support.build_support(self.model, self.constants, self.config).enable_kext("Bluetooth-Spoof.kext", self.constants.btspoof_version, self.constants.btspoof_path)
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " -btlfxallowanyaddr"
@@ -34,12 +36,12 @@ class build_bluetooth:
# Due to this, BlueToolFixup is required to resolve Firmware Uploading on legacy chipsets
if self.computer.wifi:
if self.computer.wifi.chipset == device_probe.Broadcom.Chipsets.AirPortBrcm4360:
print("- Fixing Legacy Bluetooth for macOS Monterey")
logging.info("- Fixing Legacy Bluetooth for macOS Monterey")
support.build_support(self.model, self.constants, self.config).enable_kext("BlueToolFixup.kext", self.constants.bluetool_version, self.constants.bluetool_path)
elif self.computer.bluetooth_chipset == "3rd Party Bluetooth 4.0 Hub":
print("- Detected 3rd Party Bluetooth Chipset")
logging.info("- Detected 3rd Party Bluetooth Chipset")
support.build_support(self.model, self.constants, self.config).enable_kext("BlueToolFixup.kext", self.constants.bluetool_version, self.constants.bluetool_path)
print("- Enabling Bluetooth FeatureFlags")
logging.info("- Enabling Bluetooth FeatureFlags")
self.config["Kernel"]["Quirks"]["ExtendBTFeatureFlags"] = True
@@ -50,7 +52,7 @@ class build_bluetooth:
return
if smbios_data.smbios_dictionary[self.model]["Bluetooth Model"] <= bluetooth_data.bluetooth_data.BRCM20702_v1.value:
print("- Fixing Legacy Bluetooth for macOS Monterey")
logging.info("- Fixing Legacy Bluetooth for macOS Monterey")
support.build_support(self.model, self.constants, self.config).enable_kext("BlueToolFixup.kext", self.constants.bluetool_version, self.constants.bluetool_path)
if smbios_data.smbios_dictionary[self.model]["Bluetooth Model"] <= bluetooth_data.bluetooth_data.BRCM2070.value:
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " -btlfxallowanyaddr"

View File

@@ -8,6 +8,7 @@ import shutil
import zipfile
from pathlib import Path
from datetime import date
import logging
from resources import constants, utilities
from resources.build import bluetooth, firmware, graphics_audio, support, storage, smbios, security, misc
@@ -30,9 +31,9 @@ class build_opencore:
def build_efi(self):
utilities.cls()
if not self.constants.custom_model:
print(f"Building Configuration on model: {self.model}")
logging.info(f"Building Configuration on model: {self.model}")
else:
print(f"Building Configuration for external model: {self.model}")
logging.info(f"Building Configuration for external model: {self.model}")
self.generate_base()
self.set_revision()
@@ -54,31 +55,32 @@ class build_opencore:
# Work-around ocvalidate
if self.constants.validate is False:
print("- Adding bootmgfw.efi BlessOverride")
logging.info("- Adding bootmgfw.efi BlessOverride")
self.config["Misc"]["BlessOverride"] += ["\\EFI\\Microsoft\\Boot\\bootmgfw.efi"]
def generate_base(self):
# Generate OpenCore base folder and config
if not Path(self.constants.build_path).exists():
print("Creating build folder")
logging.info("Creating build folder")
Path(self.constants.build_path).mkdir()
else:
print("Build folder already present, skipping")
logging.info("Build folder already present, skipping")
if Path(self.constants.opencore_zip_copied).exists():
print("Deleting old copy of OpenCore zip")
logging.info("Deleting old copy of OpenCore zip")
Path(self.constants.opencore_zip_copied).unlink()
if Path(self.constants.opencore_release_folder).exists():
print("Deleting old copy of OpenCore folder")
logging.info("Deleting old copy of OpenCore folder")
shutil.rmtree(self.constants.opencore_release_folder, onerror=rmtree_handler, ignore_errors=True)
print(f"\n- Adding OpenCore v{self.constants.opencore_version} {self.constants.opencore_build}")
logging.info("")
logging.info(f"- Adding OpenCore v{self.constants.opencore_version} {self.constants.opencore_build}")
shutil.copy(self.constants.opencore_zip_source, self.constants.build_path)
zipfile.ZipFile(self.constants.opencore_zip_copied).extractall(self.constants.build_path)
# Setup config.plist for editing
print("- Adding config.plist for OpenCore")
logging.info("- Adding config.plist for OpenCore")
shutil.copy(self.constants.plist_template, self.constants.oc_folder)
self.config = plistlib.load(Path(self.constants.plist_path).open("rb"))
@@ -115,9 +117,9 @@ class build_opencore:
support.build_support(self.model, self.constants, self.config).sign_files()
support.build_support(self.model, self.constants, self.config).validate_pathing()
print("")
print(f"Your OpenCore EFI for {self.model} has been built at:")
print(f" {self.constants.opencore_release_folder}")
print("")
logging.info("")
logging.info(f"Your OpenCore EFI for {self.model} has been built at:")
logging.info(f" {self.constants.opencore_release_folder}")
logging.info("")
if self.constants.gui_mode is False:
input("Press [Enter] to continue\n")

View File

@@ -5,7 +5,7 @@ from resources import constants, generate_smbios
from resources.build import support
from data import smbios_data, cpu_data
import binascii, shutil
import binascii, shutil, logging
from pathlib import Path
class build_firmware:
@@ -50,7 +50,7 @@ class build_firmware:
# This breaks AppleIntelCPUPowerManagement.kext matching as it no longer matches against the correct criteria
#
# To resolve, we patched AICPUPM to attach regardless of the value of 'intel_cpupm_matching'
print("- Enabling legacy power management support")
logging.info("- Enabling legacy power management support")
support.build_support(self.model, self.constants, self.config).enable_kext("AppleIntelCPUPowerManagement.kext", self.constants.aicpupm_version, self.constants.aicpupm_path)
support.build_support(self.model, self.constants, self.config).enable_kext("AppleIntelCPUPowerManagementClient.kext", self.constants.aicpupm_version, self.constants.aicpupm_client_path)
@@ -60,14 +60,14 @@ class build_firmware:
# This causes power management to break on pre-Ivy Bridge CPUs as they don't have correct
# power management tables provided.
# This patch will simply increase ASPP's 'IOProbeScore' to outmatch X86PP
print("- Overriding ACPI SMC matching")
logging.info("- Overriding ACPI SMC matching")
support.build_support(self.model, self.constants, self.config).enable_kext("ASPP-Override.kext", self.constants.aspp_override_version, self.constants.aspp_override_path)
if self.constants.disable_xcpm is True:
# Only inject on older OSes if user requests
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Add"], "BundlePath", "ASPP-Override.kext")["MinKernel"] = ""
if self.constants.disable_msr_power_ctl is True and smbios_data.smbios_dictionary[self.model]["CPU Generation"] >= cpu_data.cpu_data.nehalem.value:
print("- Disabling Firmware Throttling")
logging.info("- Disabling Firmware Throttling")
# Nehalem and newer systems force firmware throttling via MSR_POWER_CTL
support.build_support(self.model, self.constants, self.config).enable_kext("SimpleMSR.kext", self.constants.simplemsr_version, self.constants.simplemsr_path)
@@ -82,14 +82,14 @@ class build_firmware:
# CPBG device in ACPI is a Co-Processor Bridge Device, which is not actually physically present
# IOPCIFamily will error when enumerating this device, thus we'll power it off via _STA (has no effect in older OSes)
if smbios_data.smbios_dictionary[self.model]["CPU Generation"] == cpu_data.cpu_data.nehalem.value and not (self.model.startswith("MacPro") or self.model.startswith("Xserve")):
print("- Adding SSDT-CPBG.aml")
logging.info("- Adding SSDT-CPBG.aml")
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["ACPI"]["Add"], "Path", "SSDT-CPBG.aml")["Enabled"] = True
shutil.copy(self.constants.pci_ssdt_path, self.constants.acpi_path)
if cpu_data.cpu_data.sandy_bridge <= smbios_data.smbios_dictionary[self.model]["CPU Generation"] <= cpu_data.cpu_data.ivy_bridge.value and self.model != "MacPro6,1":
# Based on: https://egpu.io/forums/pc-setup/fix-dsdt-override-to-correct-error-12/
# Applicable for Sandy and Ivy Bridge Macs
print("- Enabling Windows 10 UEFI Audio support")
logging.info("- Enabling Windows 10 UEFI Audio support")
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["ACPI"]["Add"], "Path", "SSDT-PCI.aml")["Enabled"] = True
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["ACPI"]["Patch"], "Comment", "BUF0 to BUF1")["Enabled"] = True
shutil.copy(self.constants.windows_ssdt_path, self.constants.acpi_path)
@@ -110,7 +110,7 @@ class build_firmware:
# Force Rosetta Cryptex installation in macOS Ventura
# Restores support for CPUs lacking AVX2.0 support
if smbios_data.smbios_dictionary[self.model]["CPU Generation"] <= cpu_data.cpu_data.ivy_bridge.value:
print("- Enabling Rosetta Cryptex support in Ventura")
logging.info("- Enabling Rosetta Cryptex support in Ventura")
support.build_support(self.model, self.constants, self.config).enable_kext("CryptexFixup.kext", self.constants.cryptexfixup_version, self.constants.cryptexfixup_path)
# i3 Ivy Bridge iMacs don't support RDRAND
@@ -119,13 +119,13 @@ class build_firmware:
(smbios_data.smbios_dictionary[self.model]["CPU Generation"] <= cpu_data.cpu_data.sandy_bridge.value):
# Ref: https://github.com/reenigneorcim/SurPlus
# Enable for all systems missing RDRAND support
print("- Adding SurPlus Patch for Race Condition")
logging.info("- Adding SurPlus Patch for Race Condition")
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "SurPlus v1 - PART 1 of 2 - Patch read_erandom (inlined in _early_random)")["Enabled"] = True
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "SurPlus v1 - PART 2 of 2 - Patch register_and_init_prng")["Enabled"] = True
if self.constants.force_surplus is True:
# Syncretic forces SurPlus to only run on Beta 7 and older by default for saftey reasons
# If users desires, allow forcing in newer OSes
print("- Allowing SurPlus on all newer OSes")
logging.info("- Allowing SurPlus on all newer OSes")
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "SurPlus v1 - PART 1 of 2 - Patch read_erandom (inlined in _early_random)")["MaxKernel"] = ""
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "SurPlus v1 - PART 2 of 2 - Patch register_and_init_prng")["MaxKernel"] = ""
@@ -143,7 +143,7 @@ class build_firmware:
# HID patches
if smbios_data.smbios_dictionary[self.model]["CPU Generation"] <= cpu_data.cpu_data.penryn.value:
print("- Adding IOHIDFamily patch")
logging.info("- Adding IOHIDFamily patch")
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Identifier", "com.apple.iokit.IOHIDFamily")["Enabled"] = True
@@ -157,20 +157,20 @@ class build_firmware:
# Exfat check
if smbios_data.smbios_dictionary[self.model]["CPU Generation"] < cpu_data.cpu_data.sandy_bridge.value:
# Sandy Bridge and newer Macs natively support ExFat
print("- Adding ExFatDxeLegacy.efi")
logging.info("- Adding ExFatDxeLegacy.efi")
shutil.copy(self.constants.exfat_legacy_driver_path, self.constants.drivers_path)
support.build_support(self.model, self.constants, self.config).get_efi_binary_by_path("ExFatDxeLegacy.efi", "UEFI", "Drivers")["Enabled"] = True
# NVMe check
if self.constants.nvme_boot is True:
print("- Enabling NVMe boot support")
logging.info("- Enabling NVMe boot support")
shutil.copy(self.constants.nvme_driver_path, self.constants.drivers_path)
support.build_support(self.model, self.constants, self.config).get_efi_binary_by_path("NvmExpressDxe.efi", "UEFI", "Drivers")["Enabled"] = True
# USB check
if self.constants.xhci_boot is True:
print("- Adding USB 3.0 Controller Patch")
print("- Adding XhciDxe.efi and UsbBusDxe.efi")
logging.info("- Adding USB 3.0 Controller Patch")
logging.info("- Adding XhciDxe.efi and UsbBusDxe.efi")
shutil.copy(self.constants.xhci_driver_path, self.constants.drivers_path)
shutil.copy(self.constants.usb_bus_driver_path, self.constants.drivers_path)
support.build_support(self.model, self.constants, self.config).get_efi_binary_by_path("XhciDxe.efi", "UEFI", "Drivers")["Enabled"] = True
@@ -178,7 +178,7 @@ class build_firmware:
# PCIe Link Rate check
if self.model == "MacPro3,1":
print("- Adding PCIe Link Rate Patch")
logging.info("- Adding PCIe Link Rate Patch")
shutil.copy(self.constants.link_rate_driver_path, self.constants.drivers_path)
support.build_support(self.model, self.constants, self.config).get_efi_binary_by_path("FixPCIeLinkRate.efi", "UEFI", "Drivers")["Enabled"] = True
@@ -193,7 +193,7 @@ class build_firmware:
# Waiting for XNU source to be released to fix this properly
# Ref: https://forums.macrumors.com/threads/opencore-on-the-mac-pro.2207814/
if self.model in ["MacPro6,1", "iMac7,1", "iMac8,1", "MacBookPro4,1"] or self.constants.set_vmm_cpuid is True:
print("- Enabling VMM patch")
logging.info("- Enabling VMM patch")
self.config["Kernel"]["Emulate"]["Cpuid1Data"] = binascii.unhexlify("00000000000000000000008000000000")
self.config["Kernel"]["Emulate"]["Cpuid1Mask"] = binascii.unhexlify("00000000000000000000008000000000")
self.config["Kernel"]["Emulate"]["MinKernel"] = "22.0.0"
@@ -207,17 +207,17 @@ class build_firmware:
):
# Fix Virtual Machine support for non-macOS OSes
# Haswell and Broadwell MacBooks lock out the VMX bit if booting UEFI Windows
print("- Enabling VMX Bit for non-macOS OSes")
logging.info("- Enabling VMX Bit for non-macOS OSes")
self.config["UEFI"]["Quirks"]["EnableVmx"] = True
# Works-around Hibernation bug where connecting all firmware drivers breaks the transition from S4
# Mainly applicable for MacBookPro9,1
if self.constants.disable_connectdrivers is True:
print("- Disabling ConnectDrivers")
logging.info("- Disabling ConnectDrivers")
self.config["UEFI"]["ConnectDrivers"] = False
if self.constants.nvram_write is False:
print("- Disabling Hardware NVRAM Write")
logging.info("- Disabling Hardware NVRAM Write")
self.config["NVRAM"]["WriteFlash"] = False
if self.constants.serial_settings != "None":
@@ -247,7 +247,7 @@ class build_firmware:
if "5K Display" not in smbios_data.smbios_dictionary[self.model]:
return
print("- Adding 5K Display Patch")
logging.info("- Adding 5K Display Patch")
# Set LauncherPath to '/boot.efi'
# This is to ensure that only the Mac's firmware presents the boot option, but not OpenCore
# https://github.com/acidanthera/OpenCorePkg/blob/0.7.6/Library/OcAppleBootPolicyLib/OcAppleBootPolicyLib.c#L50-L73

View File

@@ -1,13 +1,14 @@
# Class for handling Graphics and Audio Patches, invocation from build.py
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
from resources import constants, device_probe, utilities
from resources.build import support
from data import smbios_data, model_array, os_data, cpu_data
import shutil, binascii, logging
from pathlib import Path
import shutil, binascii
from resources import constants, device_probe, utilities
from resources.build import support
from data import smbios_data, model_array, os_data, cpu_data, video_bios_data
class build_graphics_audio:
@@ -21,58 +22,59 @@ class build_graphics_audio:
def build(self):
self.imac_mxm_patching()
self.graphics_handling()
self.audio_handling()
self.firmware_handling()
self.spoof_handling()
self.imac_mxm_patching()
self.ioaccel_workaround()
def graphics_handling(self):
if self.constants.allow_oc_everywhere is False and self.constants.serial_settings != "None":
support.build_support(self.model, self.constants, self.config).enable_kext("WhateverGreen.kext", self.constants.whatevergreen_version, self.constants.whatevergreen_path)
if not support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("WhateverGreen.kext")["Enabled"] is True:
support.build_support(self.model, self.constants, self.config).enable_kext("WhateverGreen.kext", self.constants.whatevergreen_version, self.constants.whatevergreen_path)
# Mac Pro handling
if self.model in model_array.MacPro:
if not self.constants.custom_model:
for i, device in enumerate(self.computer.gpus):
print(f"- Found dGPU ({i + 1}): {utilities.friendly_hex(device.vendor_id)}:{utilities.friendly_hex(device.device_id)}")
logging.info(f"- Found dGPU ({i + 1}): {utilities.friendly_hex(device.vendor_id)}:{utilities.friendly_hex(device.device_id)}")
self.config["#Revision"][f"Hardware-MacPro-dGPU-{i + 1}"] = f"{utilities.friendly_hex(device.vendor_id)}:{utilities.friendly_hex(device.device_id)}"
if device.pci_path and device.acpi_path:
print(f"- Found dGPU ({i + 1}) at {device.pci_path}")
logging.info(f"- Found dGPU ({i + 1}) at {device.pci_path}")
if isinstance(device, device_probe.AMD):
print("- Adding Mac Pro, Xserve DRM patches")
logging.info("- Adding Mac Pro, Xserve DRM patches")
self.config["DeviceProperties"]["Add"][device.pci_path] = {"shikigva": 128, "unfairgva": 1, "rebuild-device-tree": 1, "agdpmod": "pikera", "enable-gva-support": 1}
elif isinstance(device, device_probe.NVIDIA):
print("- Enabling Nvidia Output Patch")
logging.info("- Enabling Nvidia Output Patch")
self.config["DeviceProperties"]["Add"][device.pci_path] = {"rebuild-device-tree": 1, "agdpmod": "vit9696"}
self.config["UEFI"]["Quirks"]["ForgeUefiSupport"] = True
self.config["UEFI"]["Quirks"]["ReloadOptionRoms"] = True
else:
print(f"- Failed to find Device path for dGPU {i + 1}")
logging.info(f"- Failed to find Device path for dGPU {i + 1}")
if isinstance(device, device_probe.AMD):
print("- Adding Mac Pro, Xserve DRM patches")
logging.info("- Adding Mac Pro, Xserve DRM patches")
if "shikigva=128 unfairgva=1" not in self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"]:
print("- Falling back to boot-args")
logging.info("- Falling back to boot-args")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " shikigva=128 unfairgva=1 agdpmod=pikera radgva=1" + (
" -wegtree" if "-wegtree" not in self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] else ""
)
elif isinstance(device, device_probe.NVIDIA):
print("- Enabling Nvidia Output Patch")
logging.info("- Enabling Nvidia Output Patch")
if "-wegtree agdpmod=vit9696" not in self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"]:
print("- Falling back to boot-args")
logging.info("- Falling back to boot-args")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " -wegtree agdpmod=vit9696"
self.config["UEFI"]["Quirks"]["ForgeUefiSupport"] = True
self.config["UEFI"]["Quirks"]["ReloadOptionRoms"] = True
if not self.computer.gpus:
print("- No socketed dGPU found")
logging.info("- No socketed dGPU found")
else:
print("- Adding Mac Pro, Xserve DRM patches")
logging.info("- Adding Mac Pro, Xserve DRM patches")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " shikigva=128 unfairgva=1 -wegtree"
if not support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("WhateverGreen.kext")["Enabled"] is True:
@@ -86,7 +88,7 @@ class build_graphics_audio:
device.arch in [device_probe.NVIDIA.Archs.Fermi, device_probe.NVIDIA.Archs.Maxwell, device_probe.NVIDIA.Archs.Pascal] or
(self.constants.force_nv_web is True and device.arch in [device_probe.NVIDIA.Archs.Tesla, device_probe.NVIDIA.Archs.Kepler])
):
print(f"- Enabling Web Driver Patches for GPU ({i + 1}): {utilities.friendly_hex(device.vendor_id)}:{utilities.friendly_hex(device.device_id)}")
logging.info(f"- Enabling Web Driver Patches for GPU ({i + 1}): {utilities.friendly_hex(device.vendor_id)}:{utilities.friendly_hex(device.device_id)}")
if device.pci_path and device.acpi_path:
if device.pci_path in self.config["DeviceProperties"]["Add"]:
self.config["DeviceProperties"]["Add"][device.pci_path].update({"disable-metal": 1, "force-compat": 1})
@@ -104,14 +106,36 @@ class build_graphics_audio:
if "nvda_drv" not in self.config["NVRAM"]["Delete"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]:
self.config["NVRAM"]["Delete"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"] += ["nvda_drv"]
def backlight_path_detection(self):
# self.constants.custom_model: iMac has been modded with new dGPU
# self.computer.dgpu: dGPU has been found using the GFX0 path
# self.computer.dgpu.pci_path:
if not self.constants.custom_model and self.computer.dgpu and self.computer.dgpu.pci_path:
self.gfx0_path = self.computer.dgpu.pci_path
print(f"- Found GFX0 Device Path: {self.gfx0_path}")
for i, device in enumerate(self.computer.gpus):
logging.info(f"- Found dGPU ({i + 1}): {utilities.friendly_hex(device.vendor_id)}:{utilities.friendly_hex(device.device_id)}")
self.config["#Revision"][f"Hardware-iMac-dGPU-{i + 1}"] = f"{utilities.friendly_hex(device.vendor_id)}:{utilities.friendly_hex(device.device_id)}"
if device.pci_path != self.computer.dgpu.pci_path:
logging.info("- device path and GFX0 Device path are different")
self.gfx0_path = device.pci_path
logging.info(f"- Set GFX0 Device Path: {self.gfx0_path}")
self.computer.dgpu.device_id = device.device_id
self.device_id = device.device_id
logging.info(f"- Found GPU Arch: {device.arch}")
if device.arch in [device_probe.AMD.Archs.Navi]:
self.computer.dgpu.arch = device.arch
# self.computer.dgpu.vendor_id = device.vendor_id
# self.vendor_id = device.vendor_id
else:
self.gfx0_path = self.computer.dgpu.pci_path
logging.info(f"- Found GFX0 Device Path: {self.gfx0_path}")
logging.info(f"- Found GPU Arch: {self.computer.dgpu.arch}")
else:
if not self.constants.custom_model:
print("- Failed to find GFX0 Device path, falling back on known logic")
logging.info("- Failed to find GFX0 Device path, falling back on known logic")
if self.model in ["iMac11,1", "iMac11,3"]:
self.gfx0_path = "PciRoot(0x0)/Pci(0x3,0x0)/Pci(0x0,0x0)"
elif self.model == "iMac10,1":
@@ -123,9 +147,9 @@ class build_graphics_audio:
def nvidia_mxm_patch(self, backlight_path):
if not support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("WhateverGreen.kext")["Enabled"] is True:
# Ensure WEG is enabled as we need if for Backlight patching
support.build_support(self.model, self.constants, self.config).enable_kext("WhateverGreen.kext", self.constants.whatevergreen_version, self.constants.whatevergreen_path)
support.build_support(self.model, self.constants, self.config).enable_kext("WhateverGreen.kext", self.constants.whatevergreen_navi_version, self.constants.whatevergreen_navi_path)
if self.model in ["iMac11,1", "iMac11,2", "iMac11,3", "iMac10,1"]:
print("- Adding Nvidia Brightness Control and DRM patches")
logging.info("- Adding Nvidia Brightness Control and DRM patches")
self.config["DeviceProperties"]["Add"][backlight_path] = {
"applbkl": binascii.unhexlify("01000000"),
"@0,backlight-control": binascii.unhexlify("01000000"),
@@ -144,7 +168,7 @@ class build_graphics_audio:
"agdpmod": "vit9696",
}
elif self.model in ["iMac12,1", "iMac12,2"]:
print("- Adding Nvidia Brightness Control and DRM patches")
logging.info("- Adding Nvidia Brightness Control and DRM patches")
self.config["DeviceProperties"]["Add"][backlight_path] = {
"applbkl": binascii.unhexlify("01000000"),
"@0,backlight-control": binascii.unhexlify("01000000"),
@@ -152,10 +176,9 @@ class build_graphics_audio:
"shikigva": 256,
"agdpmod": "vit9696",
}
print("- Disabling unsupported iGPU")
logging.info("- Disabling unsupported iGPU")
self.config["DeviceProperties"]["Add"]["PciRoot(0x0)/Pci(0x2,0x0)"] = {
"name": binascii.unhexlify("23646973706C6179"),
"IOName": "#display",
"class-code": binascii.unhexlify("FFFFFFFF"),
}
shutil.copy(self.constants.backlight_injector_path, self.constants.kexts_path)
@@ -165,27 +188,40 @@ class build_graphics_audio:
def amd_mxm_patch(self, backlight_path):
print("- Adding AMD DRM patches")
logging.info("- Adding AMD DRM patches")
if not support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("WhateverGreen.kext")["Enabled"] is True:
# Ensure WEG is enabled as we need if for Backlight patching
support.build_support(self.model, self.constants, self.config).enable_kext("WhateverGreen.kext", self.constants.whatevergreen_version, self.constants.whatevergreen_path)
self.config["DeviceProperties"]["Add"][backlight_path] = {"shikigva": 128, "unfairgva": 1, "agdpmod": "pikera", "rebuild-device-tree": 1, "enable-gva-support": 1}
support.build_support(self.model, self.constants, self.config).enable_kext("WhateverGreen.kext", self.constants.whatevergreen_navi_version, self.constants.whatevergreen_navi_path)
if not self.constants.custom_model:
if self.computer.dgpu.device_id == 0x7340:
logging.info(f"- Adding AMD RX5500XT vBIOS injection")
self.config["DeviceProperties"]["Add"][backlight_path] = {"shikigva": 128, "unfairgva": 1, "agdpmod": "pikera", "rebuild-device-tree": 1, "enable-gva-support": 1, "ATY,bin_image": binascii.unhexlify(video_bios_data.RX5500XT_64K) }
logging.info(f"- Adding AMD RX5500XT boot-args")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " agdpmod=pikera applbkl=3"
elif self.computer.dgpu.device_id_unspoofed == 0x6981:
logging.info(f"- Adding AMD WX3200 device spoofing")
self.config["DeviceProperties"]["Add"][backlight_path] = {"shikigva": 128, "unfairgva": 1, "agdpmod": "pikera", "rebuild-device-tree": 1, "enable-gva-support": 1, "model": "AMD Radeon Pro WX 3200", "device-id": binascii.unhexlify("FF67")}
else:
self.config["DeviceProperties"]["Add"][backlight_path] = {"shikigva": 128, "unfairgva": 1, "agdpmod": "pikera", "rebuild-device-tree": 1, "enable-gva-support": 1}
else:
self.config["DeviceProperties"]["Add"][backlight_path] = {"shikigva": 128, "unfairgva": 1, "agdpmod": "pikera", "rebuild-device-tree": 1, "enable-gva-support": 1}
if self.constants.custom_model and self.model == "iMac11,2":
# iMac11,2 can have either PciRoot(0x0)/Pci(0x3,0x0)/Pci(0x0,0x0) or PciRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x0)
# Set both properties when we cannot run hardware detection
self.config["DeviceProperties"]["Add"]["PciRoot(0x0)/Pci(0x3,0x0)/Pci(0x0,0x0)"] = {"shikigva": 128, "unfairgva": 1, "agdpmod": "pikera", "rebuild-device-tree": 1, "enable-gva-support": 1}
if self.model in ["iMac12,1", "iMac12,2"]:
print("- Disabling unsupported iGPU")
logging.info("- Disabling unsupported iGPU")
self.config["DeviceProperties"]["Add"]["PciRoot(0x0)/Pci(0x2,0x0)"] = {
"name": binascii.unhexlify("23646973706C6179"),
"IOName": "#display",
"class-code": binascii.unhexlify("FFFFFFFF"),
}
elif self.model == "iMac10,1":
support.build_support(self.model, self.constants, self.config).enable_kext("AAAMouSSE.kext", self.constants.mousse_version, self.constants.mousse_path)
if self.computer and self.computer.dgpu:
if self.computer.dgpu.arch == device_probe.AMD.Archs.Legacy_GCN_7000:
print("- Adding Legacy GCN Power Gate Patches")
logging.info("- Adding Legacy GCN Power Gate Patches")
self.config["DeviceProperties"]["Add"][backlight_path].update({
"CAIL,CAIL_DisableDrmdmaPowerGating": 1,
"CAIL,CAIL_DisableGfxCGPowerGating": 1,
@@ -193,7 +229,7 @@ class build_graphics_audio:
"CAIL,CAIL_DisableVCEPowerGating": 1,
})
if self.constants.imac_model == "Legacy GCN":
print("- Adding Legacy GCN Power Gate Patches")
logging.info("- Adding Legacy GCN Power Gate Patches")
self.config["DeviceProperties"]["Add"][backlight_path].update({
"CAIL,CAIL_DisableDrmdmaPowerGating": 1,
"CAIL,CAIL_DisableGfxCGPowerGating": 1,
@@ -207,7 +243,27 @@ class build_graphics_audio:
"CAIL,CAIL_DisableUVDPowerGating": 1,
"CAIL,CAIL_DisableVCEPowerGating": 1,
})
elif self.constants.imac_model == "AMD Lexa":
logging.info("- Adding Lexa Spoofing Patches")
self.config["DeviceProperties"]["Add"][backlight_path].update({
"model": "AMD Radeon Pro WX 3200",
"device-id": binascii.unhexlify("FF67"),
})
if self.model == "iMac11,2":
self.config["DeviceProperties"]["Add"]["PciRoot(0x0)/Pci(0x3,0x0)/Pci(0x0,0x0)"].update({
"model": "AMD Radeon Pro WX 3200",
"device-id": binascii.unhexlify("FF67"),
})
elif self.constants.imac_model == "AMD Navi":
logging.info("- Adding Navi Spoofing Patches")
navi_backlight_path = backlight_path+"/Pci(0x0,0x0)/Pci(0x0,0x0)"
self.config["DeviceProperties"]["Add"][navi_backlight_path] = {
"ATY,bin_image": binascii.unhexlify(video_bios_data.RX5500XT_64K),
"shikigva": 128,
"unfairgva": 1,
"rebuild-device-tree": 1,
"enable-gva-support": 1
}
def audio_handling(self):
if (self.model in model_array.LegacyAudio or self.model in model_array.MacPro) and self.constants.set_alc_usage is True:
@@ -249,12 +305,12 @@ class build_graphics_audio:
def firmware_handling(self):
# Add UGA to GOP layer
if "UGA Graphics" in smbios_data.smbios_dictionary[self.model]:
print("- Adding UGA to GOP Patch")
logging.info("- Adding UGA to GOP Patch")
self.config["UEFI"]["Output"]["GopPassThrough"] = "Apple"
# GMUX handling
if self.constants.software_demux is True and self.model in ["MacBookPro8,2", "MacBookPro8,3"]:
print("- Enabling software demux")
logging.info("- Enabling software demux")
# Add ACPI patches
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["ACPI"]["Add"], "Path", "SSDT-DGPU.aml")["Enabled"] = True
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["ACPI"]["Patch"], "Comment", "_INI to XINI")["Enabled"] = True
@@ -272,24 +328,24 @@ class build_graphics_audio:
support.build_support(self.model, self.constants, self.config).enable_kext("AMDGPUWakeHandler.kext", self.constants.gpu_wake_version, self.constants.gpu_wake_path)
if self.constants.dGPU_switch is True and "Switchable GPUs" in smbios_data.smbios_dictionary[self.model]:
print("- Allowing GMUX switching in Windows")
logging.info("- Allowing GMUX switching in Windows")
self.config["Booter"]["Quirks"]["SignalAppleOS"] = True
# Force Output support PC VBIOS on Mac Pros
if self.constants.force_output_support is True:
print("- Forcing GOP Support")
logging.info("- Forcing GOP Support")
self.config["UEFI"]["Quirks"]["ForgeUefiSupport"] = True
self.config["UEFI"]["Quirks"]["ReloadOptionRoms"] = True
# AMD GOP VBIOS injection for AMD GCN 1-4 GPUs
if self.constants.amd_gop_injection is True:
print("- Adding AMDGOP.efi")
logging.info("- Adding AMDGOP.efi")
shutil.copy(self.constants.amd_gop_driver_path, self.constants.drivers_path)
support.build_support(self.model, self.constants, self.config).get_efi_binary_by_path("AMDGOP.efi", "UEFI", "Drivers")["Enabled"] = True
# Nvidia Kepler GOP VBIOS injection
if self.constants.nvidia_kepler_gop_injection is True:
print("- Adding NVGOP_GK.efi")
logging.info("- Adding NVGOP_GK.efi")
shutil.copy(self.constants.nvidia_kepler_gop_driver_path, self.constants.drivers_path)
support.build_support(self.model, self.constants, self.config).get_efi_binary_by_path("NVGOP_GK.efi", "UEFI", "Drivers")["Enabled"] = True
@@ -300,7 +356,7 @@ class build_graphics_audio:
# AppleMuxControl Override
if self.model == "MacBookPro9,1":
print("- Adding AppleMuxControl Override")
logging.info("- Adding AppleMuxControl Override")
amc_map_path = Path(self.constants.plist_folder_path) / Path("AppleMuxControl/Info.plist")
self.config["DeviceProperties"]["Add"]["PciRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x0)"] = {"agdpmod": "vit9696"}
Path(self.constants.amc_kext_folder).mkdir()
@@ -309,7 +365,7 @@ class build_graphics_audio:
support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("AMC-Override.kext")["Enabled"] = True
if self.model not in model_array.NoAGPMSupport:
print("- Adding AppleGraphicsPowerManagement Override")
logging.info("- Adding AppleGraphicsPowerManagement Override")
agpm_map_path = Path(self.constants.plist_folder_path) / Path("AppleGraphicsPowerManagement/Info.plist")
Path(self.constants.agpm_kext_folder).mkdir()
Path(self.constants.agpm_contents_folder).mkdir()
@@ -317,7 +373,7 @@ class build_graphics_audio:
support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("AGPM-Override.kext")["Enabled"] = True
if self.model in model_array.AGDPSupport:
print("- Adding AppleGraphicsDevicePolicy Override")
logging.info("- Adding AppleGraphicsDevicePolicy Override")
agdp_map_path = Path(self.constants.plist_folder_path) / Path("AppleGraphicsDevicePolicy/Info.plist")
Path(self.constants.agdp_kext_folder).mkdir()
Path(self.constants.agdp_contents_folder).mkdir()
@@ -326,17 +382,17 @@ class build_graphics_audio:
# AGPM Patch
if self.model in model_array.DualGPUPatch:
print("- Adding dual GPU patch")
logging.info("- Adding dual GPU patch")
if not self.constants.custom_model and self.computer.dgpu and self.computer.dgpu.pci_path:
self.gfx0_path = self.computer.dgpu.pci_path
print(f"- Found GFX0 Device Path: {self.gfx0_path}")
logging.info(f"- Found GFX0 Device Path: {self.gfx0_path}")
else:
if not self.constants.custom_model:
print("- Failed to find GFX0 Device path, falling back on known logic")
logging.info("- Failed to find GFX0 Device path, falling back on known logic")
self.gfx0_path = "PciRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x0)"
if self.model in model_array.IntelNvidiaDRM and self.constants.drm_support is True:
print("- Prioritizing DRM support over Intel QuickSync")
logging.info("- Prioritizing DRM support over Intel QuickSync")
self.config["DeviceProperties"]["Add"][self.gfx0_path] = {"agdpmod": "vit9696", "shikigva": 256}
self.config["DeviceProperties"]["Add"]["PciRoot(0x0)/Pci(0x2,0x0)"] = {
"name": binascii.unhexlify("23646973706C6179"),
@@ -344,7 +400,8 @@ class build_graphics_audio:
"class-code": binascii.unhexlify("FFFFFFFF"),
}
elif self.constants.serial_settings != "None":
self.config["DeviceProperties"]["Add"][self.gfx0_path] = {"agdpmod": "vit9696"}
if self.gfx0_path not in self.config["DeviceProperties"]["Add"] or "agdpmod" not in self.config["DeviceProperties"]["Add"][self.gfx0_path]:
self.config["DeviceProperties"]["Add"][self.gfx0_path] = {"agdpmod": "vit9696"}
if self.model.startswith("iMac14,1"):
# Ensure that agdpmod is applied to iMac14,x with iGPU only
@@ -352,30 +409,29 @@ class build_graphics_audio:
def imac_mxm_patching(self):
self.backlight_path_detection()
# Check GPU Vendor
if self.constants.metal_build is True:
self.backlight_path_detection()
print("- Adding Metal GPU patches on request")
logging.info("- Adding Metal GPU patches on request")
if self.constants.imac_vendor == "AMD":
self.amd_mxm_patch(self.gfx0_path)
elif self.constants.imac_vendor == "Nvidia":
self.nvidia_mxm_patch(self.gfx0_path)
else:
print("- Failed to find vendor")
logging.info("- Failed to find vendor")
elif not self.constants.custom_model and self.model in model_array.LegacyGPU and self.computer.dgpu:
print(f"- Detected dGPU: {utilities.friendly_hex(self.computer.dgpu.vendor_id)}:{utilities.friendly_hex(self.computer.dgpu.device_id)}")
logging.info(f"- Detected dGPU: {utilities.friendly_hex(self.computer.dgpu.vendor_id)}:{utilities.friendly_hex(self.computer.dgpu.device_id)}")
if self.computer.dgpu.arch in [
device_probe.AMD.Archs.Legacy_GCN_7000,
device_probe.AMD.Archs.Legacy_GCN_8000,
device_probe.AMD.Archs.Legacy_GCN_9000,
device_probe.AMD.Archs.Polaris,
device_probe.AMD.Archs.Polaris_Spoof,
device_probe.AMD.Archs.Vega,
device_probe.AMD.Archs.Navi,
]:
self.backlight_path_detection()
self.amd_mxm_patch(self.gfx0_path)
elif self.computer.dgpu.arch == device_probe.NVIDIA.Archs.Kepler:
self.backlight_path_detection()
self.nvidia_mxm_patch(self.gfx0_path)
def ioaccel_workaround(self):
@@ -430,6 +486,7 @@ class build_graphics_audio:
if gpu in [
# Metal KDK (pre-AVX2.0)
device_probe.AMD.Archs.Polaris,
device_probe.AMD.Archs.Polaris_Spoof,
device_probe.AMD.Archs.Vega,
device_probe.AMD.Archs.Navi,
]:
@@ -454,8 +511,9 @@ class build_graphics_audio:
gpu = gpu.arch
if gpu in [
device_probe.AMD.Archs.Polaris,
device_probe.AMD.Archs.Polaris_Spoof,
device_probe.AMD.Archs.Vega,
device_probe.AMD.Archs.Navi,
]:
support.build_support(self.model, self.constants, self.config).enable_kext("KDKlessWorkaround.kext", self.constants.kdkless_version, self.constants.kdkless_path)
return
return

View File

@@ -5,7 +5,7 @@ from resources import constants, device_probe, generate_smbios, utilities
from resources.build import support
from data import model_array, smbios_data, cpu_data
import binascii, shutil
import binascii, shutil, logging
from pathlib import Path
@@ -38,7 +38,7 @@ class build_misc:
if self.constants.fu_status is True:
support.build_support(self.model, self.constants, self.config).enable_kext("FeatureUnlock.kext", self.constants.featureunlock_version, self.constants.featureunlock_path)
if self.constants.fu_arguments is not None:
print(f"- Adding additional FeatureUnlock args: {self.constants.fu_arguments}")
logging.info(f"- Adding additional FeatureUnlock args: {self.constants.fu_arguments}")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += self.constants.fu_arguments
def restrict_events_handling(self):
@@ -49,7 +49,7 @@ class build_misc:
if self.model in ["MacBookPro6,1", "MacBookPro6,2", "MacBookPro9,1", "MacBookPro10,1"]:
block_args += "gmux,"
if self.model in model_array.MacPro:
print("- Disabling memory error reporting")
logging.info("- Disabling memory error reporting")
block_args += "pcie,"
gpu_dict = []
if not self.constants.custom_model:
@@ -65,20 +65,20 @@ class build_misc:
device_probe.Intel.Archs.Haswell,
device_probe.NVIDIA.Archs.Kepler,
]:
print("- Disabling mediaanalysisd")
logging.info("- Disabling mediaanalysisd")
block_args += "media,"
break
if block_args.endswith(","):
block_args = block_args[:-1]
if block_args != "":
print(f"- Setting RestrictEvents block arguments: {block_args}")
logging.info(f"- Setting RestrictEvents block arguments: {block_args}")
support.build_support(self.model, self.constants, self.config).enable_kext("RestrictEvents.kext", self.constants.restrictevents_version, self.constants.restrictevents_path)
self.config["NVRAM"]["Add"]["4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102"]["revblock"] = block_args
patch_args = ""
if support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "Reroute kern.hv_vmm_present patch (1)")["Enabled"] is True and self.constants.set_content_caching is True:
print("- Fixing Content Caching support")
logging.info("- Fixing Content Caching support")
patch_args += "asset,"
if patch_args.endswith(","):
@@ -89,17 +89,17 @@ class build_misc:
patch_args = "none"
if patch_args != "":
print(f"- Setting RestrictEvents patch arguments: {patch_args}")
logging.info(f"- Setting RestrictEvents patch arguments: {patch_args}")
support.build_support(self.model, self.constants, self.config).enable_kext("RestrictEvents.kext", self.constants.restrictevents_version, self.constants.restrictevents_path)
self.config["NVRAM"]["Add"]["4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102"]["revpatch"] = patch_args
if self.constants.custom_cpu_model == 0 or self.constants.custom_cpu_model == 1:
self.config["NVRAM"]["Add"]["4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102"]["revcpu"] = self.constants.custom_cpu_model
if self.constants.custom_cpu_model_value != "":
print(f"- Adding custom CPU Name: {self.constants.custom_cpu_model_value}")
logging.info(f"- Adding custom CPU Name: {self.constants.custom_cpu_model_value}")
self.config["NVRAM"]["Add"]["4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102"]["revcpuname"] = self.constants.custom_cpu_model_value
else:
print("- Adding CPU Name Patch")
logging.info("- Adding CPU Name Patch")
support.build_support(self.model, self.constants, self.config).enable_kext("RestrictEvents.kext", self.constants.restrictevents_version, self.constants.restrictevents_path)
if support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("RestrictEvents.kext")["Enabled"] is False:
@@ -125,7 +125,7 @@ class build_misc:
if self.constants.firewire_boot is True and generate_smbios.check_firewire(self.model) is True:
# Enable FireWire Boot Support
# Applicable for both native FireWire and Thunderbolt to FireWire adapters
print("- Enabling FireWire Boot Support")
logging.info("- Enabling FireWire Boot Support")
support.build_support(self.model, self.constants, self.config).enable_kext("IOFireWireFamily.kext", self.constants.fw_kext, self.constants.fw_family_path)
support.build_support(self.model, self.constants, self.config).enable_kext("IOFireWireSBP2.kext", self.constants.fw_kext, self.constants.fw_sbp2_path)
support.build_support(self.model, self.constants, self.config).enable_kext("IOFireWireSerialBusProtocolTransport.kext", self.constants.fw_kext, self.constants.fw_bus_path)
@@ -148,7 +148,7 @@ class build_misc:
def thunderbolt_handling(self):
if self.constants.disable_tb is True and self.model in ["MacBookPro11,1", "MacBookPro11,2", "MacBookPro11,3", "MacBookPro11,4", "MacBookPro11,5"]:
print("- Disabling 2013-2014 laptop Thunderbolt Controller")
logging.info("- Disabling 2013-2014 laptop Thunderbolt Controller")
if self.model in ["MacBookPro11,3", "MacBookPro11,5"]:
# 15" dGPU models: IOACPIPlane:/_SB/PCI0@0/PEG1@10001/UPSB@0/DSB0@0/NHI0@0
tb_device_path = "PciRoot(0x0)/Pci(0x1,0x1)/Pci(0x0,0x0)/Pci(0x0,0x0)/Pci(0x0,0x0)"
@@ -175,7 +175,7 @@ class build_misc:
(self.model in model_array.Missing_USB_Map or self.model in model_array.Missing_USB_Map_Ventura)
or self.constants.serial_settings in ["Moderate", "Advanced"])
):
print("- Adding USB-Map.kext")
logging.info("- Adding USB-Map.kext")
Path(self.constants.map_kext_folder).mkdir()
Path(self.constants.map_contents_folder).mkdir()
shutil.copy(usb_map_path, self.constants.map_contents_folder)
@@ -196,7 +196,7 @@ class build_misc:
smbios_data.smbios_dictionary[self.model]["CPU Generation"] <= cpu_data.cpu_data.penryn.value or \
self.model in ["MacPro4,1", "MacPro5,1"]
):
print("- Adding UHCI/OHCI USB support")
logging.info("- Adding UHCI/OHCI USB support")
shutil.copy(self.constants.apple_usb_11_injector_path, self.constants.kexts_path)
support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("USB1.1-Injector.kext/Contents/PlugIns/AppleUSBOHCI.kext")["Enabled"] = True
support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("USB1.1-Injector.kext/Contents/PlugIns/AppleUSBOHCIPCI.kext")["Enabled"] = True
@@ -207,11 +207,11 @@ class build_misc:
# DEBUG Settings (OpenCorePkg and Kernel Space)
if self.constants.verbose_debug is True:
print("- Enabling Verbose boot")
logging.info("- Enabling Verbose boot")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " -v"
if self.constants.kext_debug is True:
print("- Enabling DEBUG Kexts")
logging.info("- Enabling DEBUG Kexts")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " -liludbgall liludump=90"
# Disabled due to macOS Monterey crashing shortly after kernel init
# Use DebugEnhancer.kext instead
@@ -219,7 +219,7 @@ class build_misc:
support.build_support(self.model, self.constants, self.config).enable_kext("DebugEnhancer.kext", self.constants.debugenhancer_version, self.constants.debugenhancer_path)
if self.constants.opencore_debug is True:
print("- Enabling DEBUG OpenCore")
logging.info("- Enabling DEBUG OpenCore")
self.config["Misc"]["Debug"]["Target"] = 0x43
self.config["Misc"]["Debug"]["DisplayLevel"] = 0x80000042
@@ -227,7 +227,7 @@ class build_misc:
# OpenCorePkg Settings
# OpenCanopy Settings (GUI)
print("- Adding OpenCanopy GUI")
logging.info("- Adding OpenCanopy GUI")
shutil.rmtree(self.constants.resources_path, onerror=self.rmtree_handler)
shutil.copy(self.constants.gui_path, self.constants.oc_folder)
support.build_support(self.model, self.constants, self.config).get_efi_binary_by_path("OpenCanopy.efi", "UEFI", "Drivers")["Enabled"] = True
@@ -236,14 +236,14 @@ class build_misc:
support.build_support(self.model, self.constants, self.config).get_efi_binary_by_path("ResetNvramEntry.efi", "UEFI", "Drivers")["Enabled"] = True
if self.constants.showpicker is False:
print("- Hiding OpenCore picker")
logging.info("- Hiding OpenCore picker")
self.config["Misc"]["Boot"]["ShowPicker"] = False
if self.constants.oc_timeout != 5:
print(f"- Setting custom OpenCore picker timeout to {self.constants.oc_timeout} seconds")
logging.info(f"- Setting custom OpenCore picker timeout to {self.constants.oc_timeout} seconds")
self.config["Misc"]["Boot"]["Timeout"] = self.constants.oc_timeout
if self.constants.vault is True and utilities.check_command_line_tools() is True:
print("- Setting Vault configuration")
logging.info("- Setting Vault configuration")
self.config["Misc"]["Security"]["Vault"] = "Secure"
support.build_support(self.model, self.constants, self.config).get_efi_binary_by_path("OpenShell.efi", "Misc", "Tools")["Enabled"] = False

View File

@@ -5,6 +5,8 @@ from resources import constants, device_probe, utilities
from resources.build import support
from data import smbios_data
import logging
class build_wireless:
def __init__(self, model, versions, config):
@@ -24,22 +26,22 @@ class build_wireless:
def on_model(self):
print(f"- Found Wireless Device {utilities.friendly_hex(self.computer.wifi.vendor_id)}:{utilities.friendly_hex(self.computer.wifi.device_id)}")
logging.info(f"- Found Wireless Device {utilities.friendly_hex(self.computer.wifi.vendor_id)}:{utilities.friendly_hex(self.computer.wifi.device_id)}")
self.config["#Revision"]["Hardware-Wifi"] = f"{utilities.friendly_hex(self.computer.wifi.vendor_id)}:{utilities.friendly_hex(self.computer.wifi.device_id)}"
if isinstance(self.computer.wifi, device_probe.Broadcom):
# This works around OCLP spoofing the Wifi card and therefore unable to actually detect the correct device
if self.computer.wifi.chipset == device_probe.Broadcom.Chipsets.AirportBrcmNIC and self.constants.validate is False and self.computer.wifi.country_code:
support.build_support(self.model, self.constants, self.config).enable_kext("AirportBrcmFixup.kext", self.constants.airportbcrmfixup_version, self.constants.airportbcrmfixup_path)
print(f"- Setting Wireless Card's Country Code: {self.computer.wifi.country_code}")
logging.info(f"- Setting Wireless Card's Country Code: {self.computer.wifi.country_code}")
if self.computer.wifi.pci_path:
arpt_path = self.computer.wifi.pci_path
print(f"- Found ARPT device at {arpt_path}")
logging.info(f"- Found ARPT device at {arpt_path}")
self.config["DeviceProperties"]["Add"][arpt_path] = {"brcmfx-country": self.computer.wifi.country_code}
else:
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += f" brcmfx-country={self.computer.wifi.country_code}"
if self.constants.enable_wake_on_wlan is True:
print("- Enabling Wake on WLAN support")
logging.info("- Enabling Wake on WLAN support")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += f" -brcmfxwowl"
elif self.computer.wifi.chipset == device_probe.Broadcom.Chipsets.AirPortBrcm4360:
self.wifi_fake_id()
@@ -63,20 +65,20 @@ class build_wireless:
if not "Wireless Model" in smbios_data.smbios_dictionary[self.model]:
return
if smbios_data.smbios_dictionary[self.model]["Wireless Model"] == device_probe.Broadcom.Chipsets.AirPortBrcm4360:
print("- Enabling BCM943224 and BCM94331 Networking Support")
logging.info("- Enabling BCM943224 and BCM94331 Networking Support")
self.wifi_fake_id()
elif smbios_data.smbios_dictionary[self.model]["Wireless Model"] == device_probe.Broadcom.Chipsets.AirPortBrcm4331:
print("- Enabling BCM94328 Networking Support")
logging.info("- Enabling BCM94328 Networking Support")
support.build_support(self.model, self.constants, self.config).enable_kext("corecaptureElCap.kext", self.constants.corecaptureelcap_version, self.constants.corecaptureelcap_path)
support.build_support(self.model, self.constants, self.config).enable_kext("IO80211ElCap.kext", self.constants.io80211elcap_version, self.constants.io80211elcap_path)
support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("IO80211ElCap.kext/Contents/PlugIns/AirPortBrcm4331.kext")["Enabled"] = True
elif smbios_data.smbios_dictionary[self.model]["Wireless Model"] == device_probe.Broadcom.Chipsets.AirPortBrcm43224:
print("- Enabling BCM94328 Networking Support")
logging.info("- Enabling BCM94328 Networking Support")
support.build_support(self.model, self.constants, self.config).enable_kext("corecaptureElCap.kext", self.constants.corecaptureelcap_version, self.constants.corecaptureelcap_path)
support.build_support(self.model, self.constants, self.config).enable_kext("IO80211ElCap.kext", self.constants.io80211elcap_version, self.constants.io80211elcap_path)
support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("IO80211ElCap.kext/Contents/PlugIns/AppleAirPortBrcm43224.kext")["Enabled"] = True
elif smbios_data.smbios_dictionary[self.model]["Wireless Model"] == device_probe.Atheros.Chipsets.AirPortAtheros40:
print("- Enabling Atheros Networking Support")
logging.info("- Enabling Atheros Networking Support")
support.build_support(self.model, self.constants, self.config).enable_kext("corecaptureElCap.kext", self.constants.corecaptureelcap_version, self.constants.corecaptureelcap_path)
support.build_support(self.model, self.constants, self.config).enable_kext("IO80211ElCap.kext", self.constants.io80211elcap_version, self.constants.io80211elcap_path)
support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("IO80211ElCap.kext/Contents/PlugIns/AirPortAtheros40.kext")["Enabled"] = True
@@ -92,7 +94,7 @@ class build_wireless:
if support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("AirportBrcmFixup.kext")["Enabled"] is False:
return
print("- Enabling Wake on WLAN support")
logging.info("- Enabling Wake on WLAN support")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += f" -brcmfxwowl"
@@ -103,10 +105,10 @@ class build_wireless:
support.build_support(self.model, self.constants, self.config).get_kext_by_bundle_path("AirportBrcmFixup.kext/Contents/PlugIns/AirPortBrcmNIC_Injector.kext")["Enabled"] = True
if not self.constants.custom_model and self.computer.wifi and self.computer.wifi.pci_path:
arpt_path = self.computer.wifi.pci_path
print(f"- Found ARPT device at {arpt_path}")
logging.info(f"- Found ARPT device at {arpt_path}")
else:
if not self.model in smbios_data.smbios_dictionary:
print("No known PCI pathing for this model")
logging.info("No known PCI pathing for this model")
return
if "nForce Chipset" in smbios_data.smbios_dictionary[self.model]:
# Nvidia chipsets all have the same path to ARPT
@@ -122,8 +124,8 @@ class build_wireless:
# Assumes we have a laptop with Intel chipset
# iMac11,x-12,x also apply
arpt_path = "PciRoot(0x0)/Pci(0x1C,0x1)/Pci(0x0,0x0)"
print(f"- Using known ARPT Path: {arpt_path}")
logging.info(f"- Using known ARPT Path: {arpt_path}")
if not self.constants.custom_model and self.computer.wifi and self.constants.validate is False and self.computer.wifi.country_code:
print(f"- Applying fake ID for WiFi, setting Country Code: {self.computer.wifi.country_code}")
logging.info(f"- Applying fake ID for WiFi, setting Country Code: {self.computer.wifi.country_code}")
self.config["DeviceProperties"]["Add"][arpt_path] = {"brcmfx-country": self.computer.wifi.country_code}

View File

@@ -5,6 +5,7 @@ from resources import constants, utilities
from resources.build import support
import binascii
import logging
class build_security:
@@ -20,51 +21,53 @@ class build_security:
if self.constants.sip_status is False or self.constants.custom_sip_value:
# Work-around 12.3 bug where Electron apps no longer launch with SIP lowered
# Unknown whether this is intended behavior or not, revisit with 12.4
print("- Adding ipc_control_port_options=0 to boot-args")
logging.info("- Adding ipc_control_port_options=0 to boot-args")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " ipc_control_port_options=0"
# Adds AutoPkgInstaller for Automatic OpenCore-Patcher installation
# Only install if running the GUI (AutoPkg-Assets.pkg requires the GUI)
if self.constants.wxpython_variant is True:
support.build_support(self.model, self.constants, self.config).enable_kext("AutoPkgInstaller.kext", self.constants.autopkg_version, self.constants.autopkg_path)
if self.constants.custom_sip_value:
print(f"- Setting SIP value to: {self.constants.custom_sip_value}")
logging.info(f"- Setting SIP value to: {self.constants.custom_sip_value}")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["csr-active-config"] = utilities.string_to_hex(self.constants.custom_sip_value.lstrip("0x"))
elif self.constants.sip_status is False:
print("- Set SIP to allow Root Volume patching")
logging.info("- Set SIP to allow Root Volume patching")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["csr-active-config"] = binascii.unhexlify("03080000")
# apfs.kext has an undocumented boot-arg that allows FileVault usage on broken APFS seals (-arv_allow_fv)
# This is however hidden behind kern.development, thus we patch _apfs_filevault_allowed to always return true
# Note this function was added in 11.3 (20E232, 20.4), older builds do not support this (ie. 11.2.3)
print("- Allowing FileVault on Root Patched systems")
logging.info("- Allowing FileVault on Root Patched systems")
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "Force FileVault on Broken Seal")["Enabled"] = True
# Lets us check in sys_patch.py if config supports FileVault
self.config["NVRAM"]["Add"]["4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102"]["OCLP-Settings"] += " -allow_fv"
# Patch KC UUID panics due to RSR installation
# - Ref: https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1019
print("- Enabling KC UUID mismatch patch")
logging.info("- Enabling KC UUID mismatch patch")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " -nokcmismatchpanic"
support.build_support(self.model, self.constants, self.config).enable_kext("RSRHelper.kext", self.constants.rsrhelper_version, self.constants.rsrhelper_path)
if self.constants.disable_cs_lv is True:
print("- Disabling Library Validation")
# In Ventura, LV patch broke. For now, add AMFI arg
# Before merging into mainline, this needs to be resolved
if self.constants.disable_amfi is True:
logging.info("- Disabling AMFI")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " amfi=0x80"
else:
logging.info("- Disabling Library Validation")
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "Disable Library Validation Enforcement")["Enabled"] = True
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "Disable _csr_check() in _vnode_check_signature")["Enabled"] = True
if self.constants.disable_amfi is True:
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " amfi=0x80"
self.config["NVRAM"]["Add"]["4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102"]["OCLP-Settings"] += " -allow_amfi"
# CSLVFixup simply patches out __RESTRICT and __restrict out of the Music.app Binary
# Ref: https://pewpewthespells.com/blog/blocking_code_injection_on_ios_and_os_x.html
support.build_support(self.model, self.constants, self.config).enable_kext("CSLVFixup.kext", self.constants.cslvfixup_version, self.constants.cslvfixup_path)
if self.constants.secure_status is False:
print("- Disabling SecureBootModel")
logging.info("- Disabling SecureBootModel")
self.config["Misc"]["Security"]["SecureBootModel"] = "Disabled"
if self.constants.force_vmm is True:
print("- Forcing VMM patchset to support OTA updates")
logging.info("- Forcing VMM patchset to support OTA updates")
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "Reroute kern.hv_vmm_present patch (1)")["Enabled"] = True
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "Reroute kern.hv_vmm_present patch (2) Legacy")["Enabled"] = True
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "Reroute kern.hv_vmm_present patch (2) Ventura")["Enabled"] = True

View File

@@ -5,7 +5,7 @@ from resources import constants, utilities, generate_smbios
from resources.build import support
from data import smbios_data, cpu_data, model_array
import subprocess, plistlib, binascii, uuid, ast
import subprocess, plistlib, binascii, uuid, ast, logging
from pathlib import Path
class build_smbios:
@@ -19,26 +19,26 @@ class build_smbios:
if self.constants.allow_oc_everywhere is False or self.constants.allow_native_spoofs is True:
if self.constants.serial_settings == "None":
# Credit to Parrotgeek1 for boot.efi and hv_vmm_present patch sets
print("- Enabling Board ID exemption patch")
logging.info("- Enabling Board ID exemption patch")
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Booter"]["Patch"], "Comment", "Skip Board ID check")["Enabled"] = True
print("- Enabling VMM exemption patch")
logging.info("- Enabling VMM exemption patch")
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "Reroute kern.hv_vmm_present patch (1)")["Enabled"] = True
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "Reroute kern.hv_vmm_present patch (2) Legacy")["Enabled"] = True
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "Reroute kern.hv_vmm_present patch (2) Ventura")["Enabled"] = True
else:
print("- Enabling SMC exemption patch")
logging.info("- Enabling SMC exemption patch")
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Identifier", "com.apple.driver.AppleSMC")["Enabled"] = True
support.build_support(self.model, self.constants, self.config).enable_kext("SMC-Spoof.kext", self.constants.smcspoof_version, self.constants.smcspoof_path)
if self.constants.serial_settings in ["Moderate", "Advanced"]:
print("- Enabling USB Rename Patches")
logging.info("- Enabling USB Rename Patches")
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["ACPI"]["Patch"], "Comment", "XHC1 to SHC1")["Enabled"] = True
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["ACPI"]["Patch"], "Comment", "EHC1 to EH01")["Enabled"] = True
support.build_support(self.model, self.constants, self.config).get_item_by_kv(self.config["ACPI"]["Patch"], "Comment", "EHC2 to EH02")["Enabled"] = True
if self.model == self.constants.override_smbios:
print("- Adding -no_compat_check")
logging.info("- Adding -no_compat_check")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " -no_compat_check"
@@ -47,20 +47,20 @@ class build_smbios:
if self.constants.override_smbios == "Default":
if self.constants.serial_settings != "None":
print("- Setting macOS Monterey Supported SMBIOS")
logging.info("- Setting macOS Monterey Supported SMBIOS")
if self.constants.allow_native_spoofs is True:
spoofed_model = self.model
else:
spoofed_model = generate_smbios.set_smbios_model_spoof(self.model)
else:
spoofed_model = self.constants.override_smbios
print(f"- Using Model ID: {spoofed_model}")
logging.info(f"- Using Model ID: {spoofed_model}")
spoofed_board = ""
if spoofed_model in smbios_data.smbios_dictionary:
if "Board ID" in smbios_data.smbios_dictionary[spoofed_model]:
spoofed_board = smbios_data.smbios_dictionary[spoofed_model]["Board ID"]
print(f"- Using Board ID: {spoofed_board}")
logging.info(f"- Using Board ID: {spoofed_board}")
self.spoofed_model = spoofed_model
self.spoofed_board = spoofed_board
@@ -69,13 +69,13 @@ class build_smbios:
self.config["#Revision"]["Spoofed-Model"] = f"{self.spoofed_model} - {self.constants.serial_settings}"
if self.constants.serial_settings == "Moderate":
print("- Using Moderate SMBIOS patching")
logging.info("- Using Moderate SMBIOS patching")
self.moderate_serial_patch()
elif self.constants.serial_settings == "Advanced":
print("- Using Advanced SMBIOS patching")
logging.info("- Using Advanced SMBIOS patching")
self.advanced_serial_patch()
elif self.constants.serial_settings == "Minimal":
print("- Using Minimal SMBIOS patching")
logging.info("- Using Minimal SMBIOS patching")
self.spoofed_model = self.model
self.minimal_serial_patch()
else:
@@ -87,12 +87,12 @@ class build_smbios:
# Note 1: Only apply if system is UEFI 1.2, this is generally Ivy Bridge and older
# Note 2: Flipping 'UEFI -> ProtocolOverrides -> DataHub' will break hibernation
if (smbios_data.smbios_dictionary[self.model]["CPU Generation"] <= cpu_data.cpu_data.ivy_bridge.value and self.model):
print("- Detected UEFI 1.2 or older Mac, updating BoardProduct")
logging.info("- Detected UEFI 1.2 or older Mac, updating BoardProduct")
self.config["PlatformInfo"]["DataHub"]["BoardProduct"] = self.spoofed_board
self.config["PlatformInfo"]["UpdateDataHub"] = True
if self.constants.custom_serial_number != "" and self.constants.custom_board_serial_number != "":
print("- Adding custom serial numbers")
logging.info("- Adding custom serial numbers")
self.config["PlatformInfo"]["Automatic"] = True
self.config["PlatformInfo"]["UpdateDataHub"] = True
self.config["PlatformInfo"]["UpdateNVRAM"] = True
@@ -156,7 +156,7 @@ class build_smbios:
if self.model == "MacBookPro6,2":
# Force G State to not exceed moderate state
# Ref: https://github.com/fabioiop/MBP-2010-GPU-Panic-fix
print("- Patching G State for MacBookPro6,2")
logging.info("- Patching G State for MacBookPro6,2")
for gpu in ["Vendor10deDevice0a34", "Vendor10deDevice0a29"]:
agpm_config["IOKitPersonalities"]["AGPM"]["Machines"][self.spoofed_board][gpu]["BoostPState"] = [2, 2, 2, 2]
agpm_config["IOKitPersonalities"]["AGPM"]["Machines"][self.spoofed_board][gpu]["BoostTime"] = [2, 2, 2, 2]
@@ -183,7 +183,7 @@ class build_smbios:
fw_feature = generate_smbios.generate_fw_features(self.model, self.constants.custom_model)
# fw_feature = self.patch_firmware_feature()
fw_feature = hex(fw_feature).lstrip("0x").rstrip("L").strip()
print(f"- Setting Firmware Feature: {fw_feature}")
logging.info(f"- Setting Firmware Feature: {fw_feature}")
fw_feature = utilities.string_to_hex(fw_feature)
# FirmwareFeatures
@@ -216,7 +216,7 @@ class build_smbios:
self.config["PlatformInfo"]["UpdateDataHub"] = True
if self.constants.custom_serial_number != "" and self.constants.custom_board_serial_number != "":
print("- Adding custom serial numbers")
logging.info("- Adding custom serial numbers")
sn = self.constants.custom_serial_number
mlb = self.constants.custom_board_serial_number
@@ -237,7 +237,7 @@ class build_smbios:
if self.constants.custom_cpu_model == 0 or self.constants.custom_cpu_model == 1:
self.config["PlatformInfo"]["Generic"]["ProcessorType"] = 1537
if self.constants.custom_serial_number != "" and self.constants.custom_board_serial_number != "":
print("- Adding custom serial numbers")
logging.info("- Adding custom serial numbers")
self.config["PlatformInfo"]["Generic"]["SystemSerialNumber"] = self.constants.custom_serial_number
self.config["PlatformInfo"]["Generic"]["MLB"] = self.constants.custom_board_serial_number
self.config["NVRAM"]["Add"]["4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102"]["OCLP-Spoofed-SN"] = self.constants.custom_serial_number

View File

@@ -5,6 +5,7 @@ from resources import constants, device_probe, utilities
from resources.build import support
from data import model_array, smbios_data, cpu_data
import logging
class build_storage:
@@ -31,11 +32,11 @@ class build_storage:
for controller in sata_devices:
# https://linux-hardware.org/?id=pci:1179-010b-1b4b-9183
if controller.vendor_id == 0x1179 and controller.device_id == 0x010b:
print("- Enabling AHCI SSD patch")
logging.info("- Enabling AHCI SSD patch")
support.build_support(self.model, self.constants, self.config).enable_kext("MonteAHCIPort.kext", self.constants.monterey_ahci_version, self.constants.monterey_ahci_path)
break
elif self.model in ["MacBookAir6,1", "MacBookAir6,2"]:
print("- Enabling AHCI SSD patch")
logging.info("- Enabling AHCI SSD patch")
support.build_support(self.model, self.constants, self.config).enable_kext("MonteAHCIPort.kext", self.constants.monterey_ahci_version, self.constants.monterey_ahci_path)
# ThirdPartyDrives Check
@@ -48,11 +49,11 @@ class build_storage:
if drive in smbios_data.smbios_dictionary[self.model]["Stock Storage"]:
if not self.constants.custom_model:
if self.computer.third_party_sata_ssd is True:
print("- Adding SATA Hibernation Patch")
logging.info("- Adding SATA Hibernation Patch")
self.config["Kernel"]["Quirks"]["ThirdPartyDrives"] = True
break
else:
print("- Adding SATA Hibernation Patch")
logging.info("- Adding SATA Hibernation Patch")
self.config["Kernel"]["Quirks"]["ThirdPartyDrives"] = True
break
@@ -73,29 +74,29 @@ class build_storage:
# Use Innie's same logic:
# https://github.com/cdf/Innie/blob/v1.3.0/Innie/Innie.cpp#L90-L97
for i, controller in enumerate(self.computer.storage):
print(f"- Fixing PCIe Storage Controller ({i + 1}) reporting")
logging.info(f"- Fixing PCIe Storage Controller ({i + 1}) reporting")
if controller.pci_path:
self.config["DeviceProperties"]["Add"][controller.pci_path] = {"built-in": 1}
else:
print(f"- Failed to find Device path for PCIe Storage Controller {i}, falling back to Innie")
logging.info(f"- Failed to find Device path for PCIe Storage Controller {i}, falling back to Innie")
support.build_support(self.model, self.constants, self.config).enable_kext("Innie.kext", self.constants.innie_version, self.constants.innie_path)
if not self.constants.custom_model and self.constants.allow_nvme_fixing is True:
nvme_devices = [i for i in self.computer.storage if isinstance(i, device_probe.NVMeController)]
for i, controller in enumerate(nvme_devices):
print(f"- Found 3rd Party NVMe SSD ({i + 1}): {utilities.friendly_hex(controller.vendor_id)}:{utilities.friendly_hex(controller.device_id)}")
logging.info(f"- Found 3rd Party NVMe SSD ({i + 1}): {utilities.friendly_hex(controller.vendor_id)}:{utilities.friendly_hex(controller.device_id)}")
self.config["#Revision"][f"Hardware-NVMe-{i}"] = f"{utilities.friendly_hex(controller.vendor_id)}:{utilities.friendly_hex(controller.device_id)}"
# Disable Bit 0 (L0s), enable Bit 1 (L1)
nvme_aspm = (controller.aspm & (~0b11)) | 0b10
if controller.pci_path:
print(f"- Found NVMe ({i}) at {controller.pci_path}")
logging.info(f"- Found NVMe ({i}) at {controller.pci_path}")
self.config["DeviceProperties"]["Add"].setdefault(controller.pci_path, {})["pci-aspm-default"] = nvme_aspm
self.config["DeviceProperties"]["Add"][controller.pci_path.rpartition("/")[0]] = {"pci-aspm-default": nvme_aspm}
else:
if "-nvmefaspm" not in self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"]:
print("- Falling back to -nvmefaspm")
logging.info("- Falling back to -nvmefaspm")
self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " -nvmefaspm"
if (controller.vendor_id != 0x144D and controller.device_id != 0xA804):
@@ -132,5 +133,5 @@ class build_storage:
def trim_handling(self):
if self.constants.apfs_trim_timeout is False:
print(f"- Disabling APFS TRIM timeout")
logging.info(f"- Disabling APFS TRIM timeout")
self.config["Kernel"]["Quirks"]["SetApfsTrimTimeout"] = 0

View File

@@ -5,6 +5,7 @@ from resources import constants, utilities
from pathlib import Path
import shutil, plistlib, subprocess, zipfile
import logging
class build_support:
@@ -27,7 +28,7 @@ class build_support:
def get_kext_by_bundle_path(self, bundle_path):
kext = self.get_item_by_kv(self.config["Kernel"]["Add"], "BundlePath", bundle_path)
if not kext:
print(f"- Could not find kext {bundle_path}!")
logging.info(f"- Could not find kext {bundle_path}!")
raise IndexError
return kext
@@ -35,7 +36,7 @@ class build_support:
def get_efi_binary_by_path(self, bundle_path, entry_location, efi_type):
efi_binary = self.get_item_by_kv(self.config[entry_location][efi_type], "Path", bundle_path)
if not efi_binary:
print(f"- Could not find {efi_type}: {bundle_path}!")
logging.info(f"- Could not find {efi_type}: {bundle_path}!")
raise IndexError
return efi_binary
@@ -50,7 +51,7 @@ class build_support:
if kext["Enabled"] is True:
return
print(f"- Adding {kext_name} {kext_version}")
logging.info(f"- Adding {kext_name} {kext_version}")
shutil.copy(kext_path, self.constants.kexts_path)
kext["Enabled"] = True
@@ -63,27 +64,27 @@ class build_support:
# sign.command checks for the existence of '/usr/bin/strings' however does not verify whether it's executable
# sign.command will continue to run and create an unbootable OpenCore.efi due to the missing strings binary
# macOS has dummy binaries that just reroute to the actual binaries after you install Xcode's Command Line Tools
print("- Missing Command Line tools, skipping Vault for saftey reasons")
print("- Install via 'xcode-select --install' and rerun OCLP if you wish to vault this config")
logging.info("- Missing Command Line tools, skipping Vault for saftey reasons")
logging.info("- Install via 'xcode-select --install' and rerun OCLP if you wish to vault this config")
return
print("- Vaulting EFI")
logging.info("- Vaulting EFI")
subprocess.run([str(self.constants.vault_path), f"{self.constants.oc_folder}/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
def validate_pathing(self):
# Verify whether all files are accounted for on-disk
# This ensures that OpenCore won't hit a critical error and fail to boot
print("- Validating generated config")
logging.info("- Validating generated config")
if not Path(self.constants.opencore_release_folder / Path("EFI/OC/config.plist")):
print("- OpenCore config file missing!!!")
logging.info("- OpenCore config file missing!!!")
raise Exception("OpenCore config file missing")
config_plist = plistlib.load(Path(self.constants.opencore_release_folder / Path("EFI/OC/config.plist")).open("rb"))
for acpi in config_plist["ACPI"]["Add"]:
if not Path(self.constants.opencore_release_folder / Path("EFI/OC/ACPI") / Path(acpi["Path"])).exists():
print(f" - Missing ACPI Table: {acpi['Path']}")
logging.info(f" - Missing ACPI Table: {acpi['Path']}")
raise Exception(f"Missing ACPI Table: {acpi['Path']}")
for kext in config_plist["Kernel"]["Add"]:
@@ -91,40 +92,40 @@ class build_support:
kext_binary_path = Path(kext_path / Path(kext["ExecutablePath"]))
kext_plist_path = Path(kext_path / Path(kext["PlistPath"]))
if not kext_path.exists():
print(f"- Missing kext: {kext_path}")
logging.info(f"- Missing kext: {kext_path}")
raise Exception(f"Missing {kext_path}")
if not kext_binary_path.exists():
print(f"- Missing {kext['BundlePath']}'s binary: {kext_binary_path}")
logging.info(f"- Missing {kext['BundlePath']}'s binary: {kext_binary_path}")
raise Exception(f"Missing {kext_binary_path}")
if not kext_plist_path.exists():
print(f"- Missing {kext['BundlePath']}'s plist: {kext_plist_path}")
logging.info(f"- Missing {kext['BundlePath']}'s plist: {kext_plist_path}")
raise Exception(f"Missing {kext_plist_path}")
for tool in config_plist["Misc"]["Tools"]:
if not Path(self.constants.opencore_release_folder / Path("EFI/OC/Tools") / Path(tool["Path"])).exists():
print(f" - Missing tool: {tool['Path']}")
logging.info(f" - Missing tool: {tool['Path']}")
raise Exception(f"Missing tool: {tool['Path']}")
for driver in config_plist["UEFI"]["Drivers"]:
if not Path(self.constants.opencore_release_folder / Path("EFI/OC/Drivers") / Path(driver["Path"])).exists():
print(f" - Missing driver: {driver['Path']}")
logging.info(f" - Missing driver: {driver['Path']}")
raise Exception(f"Missing driver: {driver['Path']}")
# Validating local files
# Report if they have no associated config.plist entry (i.e. they're not being used)
for tool_files in Path(self.constants.opencore_release_folder / Path("EFI/OC/Tools")).glob("*"):
if tool_files.name not in [x["Path"] for x in config_plist["Misc"]["Tools"]]:
print(f" - Missing tool from config: {tool_files.name}")
logging.info(f" - Missing tool from config: {tool_files.name}")
raise Exception(f"Missing tool from config: {tool_files.name}")
for driver_file in Path(self.constants.opencore_release_folder / Path("EFI/OC/Drivers")).glob("*"):
if driver_file.name not in [x["Path"] for x in config_plist["UEFI"]["Drivers"]]:
print(f"- Found extra driver: {driver_file.name}")
logging.info(f"- Found extra driver: {driver_file.name}")
raise Exception(f"Found extra driver: {driver_file.name}")
def cleanup(self):
print("- Cleaning up files")
logging.info("- Cleaning up files")
# Remove unused entries
entries_to_clean = {
"ACPI": ["Add", "Delete", "Patch"],

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,41 @@
# 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) -> None:
"""
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) -> str or None:
"""
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):
def generate_commit_info(self) -> tuple:
"""
Generate commit info from Info.plist
Returns:
tuple: (Branch, Commit Date, Commit URL)
"""
if self.plist_path:
# print(self.plist_path)
plist_info = plistlib.load(Path(self.plist_path).open("rb"))
if "Github" in plist_info:
return (

View File

@@ -1,5 +1,5 @@
# pylint: disable=multiple-statements
# Define Files
# Defines versioning, file paths and other settings for the patcher
# Copyright (C) 2020-2023, Dhinak G, Mykola Grymalyuk
from pathlib import Path
@@ -10,210 +10,216 @@ from data import os_data
class Constants:
def __init__(self):
def __init__(self) -> None:
# Patcher Versioning
self.patcher_version = "0.6.1" # OpenCore-Legacy-Patcher
self.patcher_support_pkg_version = "0.8.2" # 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"
self.guide_link = "https://dortania.github.io/OpenCore-Legacy-Patcher/"
self.repo_link = "https://github.com/dortania/OpenCore-Legacy-Patcher"
self.repo_link_latest = f"{self.repo_link}/releases/tag/{self.patcher_version}"
self.copyright_date = "Copyright © 2020-2023 Dortania"
self.installer_pkg_url = f"{self.repo_link}/releases/download/{self.patcher_version}/AutoPkg-Assets.pkg"
self.installer_pkg_url_nightly = "http://nightly.link/dortania/OpenCore-Legacy-Patcher/workflows/build-app-wxpython/main/AutoPkg-Assets.pkg.zip"
self.patcher_version: str = "0.6.2" # OpenCore-Legacy-Patcher
self.patcher_support_pkg_version: str = "0.8.7" # PatcherSupportPkg
self.copyright_date: str = "Copyright © 2020-2023 Dortania"
# URLs
self.url_patcher_support_pkg: str = "https://github.com/dortania/PatcherSupportPkg/releases/download/"
self.discord_link: str = "https://discord.gg/rqdPgH8xSN"
self.guide_link: str = "https://dortania.github.io/OpenCore-Legacy-Patcher/"
self.repo_link: str = "https://github.com/dortania/OpenCore-Legacy-Patcher"
self.installer_pkg_url: str = f"{self.repo_link}/releases/download/{self.patcher_version}/AutoPkg-Assets.pkg"
self.installer_pkg_url_nightly: str = "http://nightly.link/dortania/OpenCore-Legacy-Patcher/workflows/build-app-wxpython/main/AutoPkg-Assets.pkg.zip"
# OpenCore Versioning
# https://github.com/acidanthera/OpenCorePkg
self.opencore_commit = "a753334 - 01-02-2023"
self.opencore_version = "0.8.8"
self.opencore_commit: str = "e4f0ba1 - 03-06-2023"
self.opencore_version: str = "0.9.0"
# Kext Versioning
## Acidanthera
## https://github.com/acidanthera
self.lilu_version = "1.6.3" # Lilu
self.whatevergreen_version = "1.6.3" # WhateverGreen
self.airportbcrmfixup_version = "2.1.6" # AirPortBrcmFixup
self.nvmefix_version = "1.0.9" # NVMeFix
self.applealc_version = "1.6.3" # AppleALC
self.restrictevents_version = "1.0.9" # RestrictEvents
self.featureunlock_version = "1.1.2" # FeatureUnlock
self.debugenhancer_version = "1.0.7" # DebugEnhancer
self.cpufriend_version = "1.2.6" # CPUFriend
self.bluetool_version = "2.6.4" # BlueToolFixup (BrcmPatchRAM)
self.cslvfixup_version = "2.6.1" # CSLVFixup
self.autopkg_version = "1.0.1" # AutoPkgInstaller
self.cryptexfixup_version = "1.0.1" # CryptexFixup
self.lilu_version: str = "1.6.4" # Lilu
self.whatevergreen_version: str = "1.6.4" # WhateverGreen
self.whatevergreen_navi_version: str = "1.6.4-Navi" # WhateverGreen (Navi Patch)
self.airportbcrmfixup_version: str = "2.1.6" # AirPortBrcmFixup
self.nvmefix_version: str = "1.1.0" # NVMeFix
self.applealc_version: str = "1.6.3" # AppleALC
self.restrictevents_version: str = "1.0.9" # RestrictEvents
self.featureunlock_version: str = "1.1.4" # FeatureUnlock
self.debugenhancer_version: str = "1.0.7" # DebugEnhancer
self.cpufriend_version: str = "1.2.6" # CPUFriend
self.bluetool_version: str = "2.6.4" # BlueToolFixup (BrcmPatchRAM)
self.cslvfixup_version: str = "2.6.1" # CSLVFixup
self.autopkg_version: str = "1.0.2" # AutoPkgInstaller
self.cryptexfixup_version: str = "1.0.1" # CryptexFixup
## Apple
## https://www.apple.com
self.marvel_version = "1.0.1" # MarvelYukonEthernet
self.nforce_version = "1.0.1" # nForceEthernet
self.piixata_version = "1.0.1" # AppleIntelPIIXATA
self.fw_kext = "1.0.1" # IOFireWireFamily
self.apple_trackpad = "1.0.1" # AppleUSBTrackpad
self.apple_isight_version = "1.0.0" # AppleiSight
self.apple_raid_version = "1.0.0" # AppleRAIDCard
self.apfs_zlib_version = "12.3.1" # NoAVXFSCompressionTypeZlib
self.apfs_zlib_v2_version = "12.6" # NoAVXFSCompressionTypeZlib (patched with AVXpel)
self.multitouch_version = "1.0.0" # AppleUSBMultitouch
self.topcase_version = "1.0.0" # AppleUSBTopCase
self.intel_82574l_version = "1.0.0" # Intel82574L
self.intel_8254x_version = "1.0.0" # AppleIntel8254XEthernet
self.apple_usb_11_injector = "1.0.0" # AppleUSBUHCI/OHCI
self.aicpupm_version = "1.0.0" # AppleIntelCPUPowerManagement/Client
self.marvel_version: str = "1.0.1" # MarvelYukonEthernet
self.nforce_version: str = "1.0.1" # nForceEthernet
self.piixata_version: str = "1.0.1" # AppleIntelPIIXATA
self.fw_kext: str = "1.0.1" # IOFireWireFamily
self.apple_trackpad: str = "1.0.1" # AppleUSBTrackpad
self.apple_isight_version: str = "1.0.0" # AppleiSight
self.apple_raid_version: str = "1.0.0" # AppleRAIDCard
self.apfs_zlib_version: str = "12.3.1" # NoAVXFSCompressionTypeZlib
self.apfs_zlib_v2_version: str = "12.6" # NoAVXFSCompressionTypeZlib (patched with AVXpel)
self.multitouch_version: str = "1.0.0" # AppleUSBMultitouch
self.topcase_version: str = "1.0.0" # AppleUSBTopCase
self.intel_82574l_version: str = "1.0.0" # Intel82574L
self.intel_8254x_version: str = "1.0.0" # AppleIntel8254XEthernet
self.apple_usb_11_injector: str = "1.0.0" # AppleUSBUHCI/OHCI
self.aicpupm_version: str = "1.0.0" # AppleIntelCPUPowerManagement/Client
## Apple - Dortania Modified
self.bcm570_version = "1.0.2" # CatalinaBCM5701Ethernet
self.i210_version = "1.0.0" # CatalinaIntelI210Ethernet
self.corecaptureelcap_version = "1.0.1" # corecaptureElCap
self.io80211elcap_version = "2.0.0" # IO80211ElCap
self.bigsursdxc_version = "1.0.0" # BigSurSDXC
self.monterey_ahci_version = "1.0.0" # CatalinaAHCI
self.bcm570_version: str = "1.0.2" # CatalinaBCM5701Ethernet
self.i210_version: str = "1.0.0" # CatalinaIntelI210Ethernet
self.corecaptureelcap_version: str = "1.0.1" # corecaptureElCap
self.io80211elcap_version: str = "2.0.0" # IO80211ElCap
self.bigsursdxc_version: str = "1.0.0" # BigSurSDXC
self.monterey_ahci_version: str = "1.0.0" # CatalinaAHCI
## Dortania
## https://github.com/dortania
self.backlight_injector_version = "1.1.0" # BacklightInjector
self.smcspoof_version = "1.0.0" # SMC-Spoof
self.mce_version = "1.0.0" # AppleMCEReporterDisabler
self.btspoof_version = "1.0.0" # Bluetooth-Spoof
self.aspp_override_version = "1.0.1" # ACPI_SMC_PlatformPlugin Override
self.rsrhelper_version = "1.0.0" # RSRHelper
self.backlight_injector_version: str = "1.1.0" # BacklightInjector
self.smcspoof_version: str = "1.0.0" # SMC-Spoof
self.mce_version: str = "1.0.0" # AppleMCEReporterDisabler
self.btspoof_version: str = "1.0.0" # Bluetooth-Spoof
self.aspp_override_version: str = "1.0.1" # ACPI_SMC_PlatformPlugin Override
self.rsrhelper_version: str = "1.0.0" # RSRHelper
## Syncretic
## https://forums.macrumors.com/members/syncretic.1173816/
## https://github.com/reenigneorcim/latebloom
self.mousse_version = "0.95-Dortania" # MouSSE
self.telemetrap_version = "1.0.0" # telemetrap
self.mousse_version: str = "0.95-Dortania" # MouSSE
self.telemetrap_version: str = "1.0.0" # telemetrap
## cdf
## https://github.com/cdf/Innie
self.innie_version = "1.3.0" # Innie
self.innie_version: str = "1.3.1" # Innie
## arter97
## https://github.com/arter97/SimpleMSR/
self.simplemsr_version = "1.0.0" # SimpleMSR
self.simplemsr_version: str = "1.0.0" # SimpleMSR
## blackgate
## https://github.com/blackgate/AMDGPUWakeHandler
self.gpu_wake_version = "1.0.0"
self.gpu_wake_version: str = "1.0.0"
## flagersgit
## https://github.com/flagersgit/KDKlessWorkaround
self.kdkless_version = "1.0.0"
self.kdkless_version: str = "1.0.0"
# Get resource path
self.current_path = Path(__file__).parent.parent.resolve()
self.payload_path = self.current_path / Path("payloads")
self.current_path: Path = Path(__file__).parent.parent.resolve()
self.payload_path: Path = self.current_path / Path("payloads")
# Patcher Settings
self.allow_oc_everywhere = False # Set whether Patcher can be run on unsupported Macs
self.gui_mode = False # Determine whether running in a GUI or TUI
self.disk = "" # Set installation ESP
self.patch_disk = "" # Set Root Volume to patch
self.validate = False # Enable validation testing for CI
self.recovery_status = False # Detect if booted into RecoveryOS
self.launcher_binary = None # Determine launch binary (ie. Python vs PyInstaller)
self.launcher_script = None # Determine launch file (if run via Python)
self.ignore_updates = False # Ignore OCLP updates
self.wxpython_variant = False # Determine if using wxPython variant
self.unpack_thread = None # Determine if unpack thread finished
self.cli_mode = False # Determine if running in CLI mode
self.should_nuke_kdks = True # Determine if KDKs should be nuked if unused in /L*/D*/KDKs
self.has_checked_updates = False # Determine if check for updates has been run
## Internal settings
self.allow_oc_everywhere: bool = False # Set whether Patcher can be run on unsupported Macs
self.gui_mode: bool = False # Determine whether running in a GUI or TUI
self.cli_mode: bool = False # Determine if running in CLI mode
self.validate: bool = False # Enable validation testing for CI
self.recovery_status: bool = False # Detect if booted into RecoveryOS
self.ignore_updates: bool = False # Ignore OCLP updates
self.wxpython_variant: bool = False # Determine if using wxPython variant
self.has_checked_updates: bool = False # Determine if check for updates has been run
self.root_patcher_succeeded: bool = False # Determine if root patcher succeeded
self.start_build_install: bool = False # Determine if build install should be started
self.host_is_non_metal: bool = False # Determine if host is non-metal (ie. enable UI hacks)
self.needs_to_open_preferences: bool = False # Determine if preferences need to be opened
self.host_is_hackintosh: bool = False # Determine if host is Hackintosh
self.should_nuke_kdks: bool = True # Determine if KDKs should be nuked if unused in /L*/D*/KDKs
self.launcher_binary: str = None # Determine launch binary path (ie. Python vs PyInstaller)
self.launcher_script: str = None # Determine launch file path (None if PyInstaller)
self.booted_oc_disk: str = None # Determine current disk OCLP booted from
self.unpack_thread = None # Determine if unpack thread finished (threading.Thread)
self.commit_info: tuple = (None, None, None) # Commit info (Branch, Commit Date, Commit URL)
## Hardware
self.computer: device_probe.Computer = None # type: ignore
self.custom_model: Optional[str] = None
self.custom_model: Optional[str] = None
## OpenCore Settings
self.opencore_debug = False
self.opencore_build = "RELEASE"
self.showpicker = True # Show or Hide OpenCore's Boot Picker
self.boot_efi = False # Use EFI/BOOT/BOOTx64.efi bootstrap
self.nvram_write = True # Write to hardware NVRAM
self.opencore_debug: bool = False # Enable OpenCore debug
self.boot_efi: bool = False # Use EFI/BOOT/BOOTx64.efi vs boot.efi bootstrap
self.showpicker: bool = True # Show or Hide OpenCore's Boot Picker
self.nvram_write: bool = True # Write to hardware NVRAM
self.oc_timeout: int = 5 # Set OpenCore timeout
self.opencore_build: str = "RELEASE"
## Kext Settings
self.kext_debug = False # Enables Lilu debug and DebugEnhancer
self.kext_variant = "RELEASE"
self.kext_debug: bool = False # Enables Lilu debug and DebugEnhancer
self.kext_variant: str = "RELEASE"
## NVRAM Settings
self.verbose_debug = False # -v
self.verbose_debug: bool = False # -v
## SMBIOS Settings
self.custom_cpu_model = 2 # Patch type value
self.custom_cpu_model_value = "" # New CPU name within About This Mac
self.serial_settings = "None" # Set SMBIOS level used
self.override_smbios = "Default" # Set SMBIOS model used
self.allow_native_spoofs = False # Allow native models to recieve spoofs
self.custom_serial_number = "" # Set SMBIOS serial number
self.custom_board_serial_number = "" # Set SMBIOS board serial number
self.serial_settings: str = "None" # Set SMBIOS level used
self.override_smbios: str = "Default" # Set SMBIOS model used
self.allow_native_spoofs: bool = False # Allow native models to recieve spoofs
### RestrictEvents CPU renaming
self.custom_cpu_model: int = 2 # Patch type value
self.custom_cpu_model_value: str = "" # New CPU name within About This Mac
### Serial Number Overrides
self.custom_serial_number: str = "" # Set SMBIOS serial number
self.custom_board_serial_number: str = "" # Set SMBIOS board serial number
## FeatureUnlock Settings
self.fu_status = True # Enable FeatureUnlock
self.fu_arguments = None # Set FeatureUnlock arguments
self.fu_status: bool = True # Enable FeatureUnlock
self.fu_arguments: str = None # Set FeatureUnlock arguments
## Security Settings
self.apecid_support = False # ApECID
self.sip_status = True # System Integrity Protection
self.secure_status = False # Secure Boot Model
self.vault = False # EFI Vault
self.disable_cs_lv = False # Disable Library validation
self.disable_amfi = False # Disable AMFI
self.sip_status: bool = True # System Integrity Protection
self.secure_status: bool = False # Secure Boot Model
self.vault: bool = False # EFI Vault
self.disable_cs_lv: bool = False # Disable Library validation
self.disable_amfi: bool = False # Disable AMFI
## OS Settings
self.os_support = 12.0
self.detected_os = 0 # Major Kernel Version
self.detected_os_minor = 0 # Minor Kernel Version
self.detected_os_build = "" # OS Build
self.detected_os_version = "" # OS Version
self.os_support: float = 12.0
self.detected_os: int = 0 # Major Kernel Version
self.detected_os_minor: int = 0 # Minor Kernel Version
self.detected_os_build: str = "" # OS Build
self.detected_os_version: str = "" # OS Version
## Boot Volume Settings
self.firewire_boot = False # Allow macOS FireWire Boot
self.nvme_boot = False # Allow UEFI NVMe Boot
self.xhci_boot = False
self.firewire_boot: bool = False # Allow macOS FireWire Boot (kernel)
self.nvme_boot: bool = False # Allow UEFI NVMe Boot
self.xhci_boot: bool = False # Allow UEFI XHCI Boot
## Graphics Settings
self.metal_build = False # Set MXM Build support
self.imac_vendor = "None" # Set MXM GPU vendor
self.imac_model = "" # Set MXM GPU model
self.drm_support = False # Set iMac14,x DRM support
self.allow_ts2_accel = True # Set TeraScale 2 Acceleration support
self.force_nv_web = False # Force Nvidia Web Drivers on Tesla and Kepler
self.force_output_support = False # Force Output support for Mac Pros with PC VBIOS
self.amd_gop_injection = False # Set GOP Injection support
self.nvidia_kepler_gop_injection = False # Set Kepler GOP Injection support
self.allow_ts2_accel: bool = True # Set TeraScale 2 Acceleration support
self.drm_support: bool = False # Set iMac14,x DRM support
self.force_nv_web: bool = False # Force Nvidia Web Drivers on Tesla and Kepler
self.force_output_support: bool = False # Force Output support for Mac Pros with PC VBIOS
self.amd_gop_injection: bool = False # Set GOP Injection support
self.nvidia_kepler_gop_injection: bool = False # Set Kepler GOP Injection support
## Miscellaneous
self.disallow_cpufriend = False # Disable CPUFriend
self.enable_wake_on_wlan = False # Allow Wake on WLAN for modern Broadcom
self.disable_tb = False # Disable Thunderbolt Controller
self.set_alc_usage = True # Set AppleALC usage
self.dGPU_switch = False # Set Display GPU Switching for Windows
self.force_surplus = False # Force SurPlus patch in newer OSes
self.force_latest_psp = False # Force latest PatcherSupportPkg
self.disable_msr_power_ctl = False # Disable MSR Power Control (missing battery throttling)
self.software_demux = False # Enable Software Demux patch set
self.force_vmm = False # Force VMM patch
self.custom_sip_value = None # Set custom SIP value
self.walkthrough = False # Enable Walkthrough
self.disable_connectdrivers = False # Disable ConnectDrivers (hibernation)
self.allow_3rd_party_drives = True # Allow ThridPartyDrives quirk
self.set_content_caching = False # Set Content Caching
self.allow_nvme_fixing = True # Allow NVMe Kernel Space Patches
self.disable_xcpm = False # Disable XCPM (X86PlatformPlugin.kext)
self.root_patcher_succeeded = False # Determine if root patcher succeeded
self.booted_oc_disk = None # Determine current disk OCLP booted from
self.start_build_install = False # Determine if build install should be started
self.host_is_non_metal = False # Determine if host is non-metal (ie. enable UI hacks)
self.needs_to_open_preferences = False # Determine if preferences need to be opened
self.host_is_hackintosh = False # Determine if host is Hackintosh
self.commit_info = (None, None, None)
self.set_vmm_cpuid = False # Set VMM bit inside CPUID
self.oc_timeout = 5 # Set OpenCore timeout
self.apfs_trim_timeout = True # Set APFS Trim timeout
### MXM GPU Support
self.metal_build: bool = False # Set MXM Build support
self.imac_vendor: str = "None" # Set MXM GPU vendor
self.imac_model: str = "" # Set MXM GPU model
## Miscellaneous build settings
self.disallow_cpufriend: bool = False # Disable CPUFriend
self.enable_wake_on_wlan: bool = False # Allow Wake on WLAN for modern Broadcom
self.disable_tb: bool = False # Disable Thunderbolt Controller
self.dGPU_switch: bool = False # Set Display GPU Switching for Windows
self.force_surplus: bool = False # Force SurPlus patch in newer OSes
self.force_latest_psp: bool = False # Force latest PatcherSupportPkg
self.disable_msr_power_ctl: bool = False # Disable MSR Power Control (missing battery throttling)
self.software_demux: bool = False # Enable Software Demux patch set
self.force_vmm: bool = False # Force VMM patch
self.disable_connectdrivers: bool = False # Disable ConnectDrivers (hibernation)
self.set_content_caching: bool = False # Set Content Caching
self.disable_xcpm: bool = False # Disable XCPM (X86PlatformPlugin.kext)
self.set_vmm_cpuid: bool = False # Set VMM bit inside CPUID
self.set_alc_usage: bool = True # Set AppleALC usage
self.allow_3rd_party_drives: bool = True # Allow ThridPartyDrives quirk
self.allow_nvme_fixing: bool = True # Allow NVMe Kernel Space Patches
self.apfs_trim_timeout: bool = True # Set APFS Trim timeout
self.custom_sip_value: int = None # Set custom SIP value
## Non-Metal OS support
self.legacy_accel_support = [
os_data.os_data.big_sur,
os_data.os_data.monterey,
@@ -281,10 +287,6 @@ class Constants:
def link_rate_driver_path(self):
return self.payload_path / Path("Drivers/FixPCIeLinkRate.efi")
@property
def list_txt_path(self):
return self.payload_path / Path("List.txt")
@property
def installer_sh_path(self):
return self.payload_path / Path("Installer.sh")
@@ -302,6 +304,10 @@ class Constants:
def whatevergreen_path(self):
return self.payload_kexts_path / Path(f"Acidanthera/WhateverGreen-v{self.whatevergreen_version}-{self.kext_variant}.zip")
@property
def whatevergreen_navi_path(self):
return self.payload_kexts_path / Path(f"Acidanthera/WhateverGreen-v{self.whatevergreen_navi_version}-{self.kext_variant}.zip")
@property
def airportbcrmfixup_path(self):
return self.payload_kexts_path / Path(f"Acidanthera/AirportBrcmFixup-v{self.airportbcrmfixup_version}-{self.kext_variant}.zip")
@@ -456,7 +462,7 @@ class Constants:
@property
def innie_path(self):
return self.payload_kexts_path / Path(f"Misc/Innie-v{self.innie_version}.zip")
return self.payload_kexts_path / Path(f"Misc/Innie-v{self.innie_version}-{self.kext_variant}.zip")
@property
def simplemsr_path(self):

View File

@@ -1,36 +1,60 @@
# Generate Default Data
from resources import utilities, device_probe, generate_smbios, global_settings
from data import smbios_data, cpu_data, os_data
import subprocess
from resources import (
utilities,
device_probe,
generate_smbios,
global_settings,
constants
)
from data import (
smbios_data,
cpu_data,
os_data
)
class generate_defaults:
def __init__(self, model, host_is_target, settings):
self.model = model
self.constants = settings
self.host_is_target = host_is_target
class GenerateDefaults:
def __init__(self, model: str, host_is_target: bool, global_constants: constants.Constants) -> None:
self.constants: constants.Constants = global_constants
self.model: str = model
self.host_is_target: bool = host_is_target
# Reset Variables
self.constants.sip_status = True
self.constants.secure_status = False
self.constants.disable_cs_lv = False
self.constants.disable_amfi = False
self.constants.fu_status = True
self.constants.fu_arguments = None
self.constants.sip_status: bool = True
self.constants.secure_status: bool = False
self.constants.disable_cs_lv: bool = False
self.constants.disable_amfi: bool = False
self.constants.fu_status: bool = True
self.constants.custom_serial_number = ""
self.constants.custom_board_serial_number = ""
self.constants.fu_arguments: str = None
self.general_probe()
self.nvram_probe()
self.gpu_probe()
self.networking_probe()
self.misc_hardwares_probe()
self.smbios_probe()
self.constants.custom_serial_number: str = ""
self.constants.custom_board_serial_number: str = ""
if self.host_is_target is True:
for gpu in self.constants.computer.gpus:
if gpu.device_id_unspoofed == -1:
gpu.device_id_unspoofed = gpu.device_id
if gpu.vendor_id_unspoofed == -1:
gpu.vendor_id_unspoofed = gpu.vendor_id
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) -> None:
"""
General probe for data
"""
if "Book" in self.model:
self.constants.set_content_caching = False
@@ -40,11 +64,11 @@ class generate_defaults:
if self.model in ["MacBookPro8,2", "MacBookPro8,3"]:
# Users disabling TS2 most likely have a faulty dGPU
# users can override this in settings
ts2_status = global_settings.global_settings().read_property("MacBookPro_TeraScale_2_Accel")
ts2_status = global_settings.GlobalEnviromentSettings().read_property("MacBookPro_TeraScale_2_Accel")
if ts2_status is True:
self.constants.allow_ts2_accel = True
else:
global_settings.global_settings().write_property("MacBookPro_TeraScale_2_Accel", False)
global_settings.GlobalEnviromentSettings().write_property("MacBookPro_TeraScale_2_Accel", False)
self.constants.allow_ts2_accel = False
if self.model in smbios_data.smbios_dictionary:
@@ -61,14 +85,19 @@ class generate_defaults:
# Check if running in RecoveryOS
self.constants.recovery_status = utilities.check_recovery()
if global_settings.global_settings().read_property("Force_Web_Drivers") is True:
if global_settings.GlobalEnviromentSettings().read_property("Force_Web_Drivers") is True:
self.constants.force_nv_web = True
result = global_settings.global_settings().read_property("ShouldNukeKDKs")
result = global_settings.GlobalEnviromentSettings().read_property("ShouldNukeKDKs")
if result is False:
self.constants.should_nuke_kdks = False
def smbios_probe(self):
def _smbios_probe(self) -> None:
"""
SMBIOS specific probe
"""
if not self.host_is_target:
if self.model in ["MacPro4,1", "MacPro5,1"]:
# Allow H.265 on AMD
@@ -99,7 +128,11 @@ class generate_defaults:
self.constants.force_vmm = False
def nvram_probe(self):
def _nvram_probe(self) -> None:
"""
NVRAM specific probe
"""
if not self.host_is_target:
return
@@ -120,7 +153,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) -> None:
"""
Networking specific probe
"""
if self.host_is_target:
if not (
(
@@ -157,7 +194,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) -> None:
"""
Misc probe
"""
if self.host_is_target:
if self.constants.computer.usb_controllers:
if self.model in smbios_data.smbios_dictionary:
@@ -170,7 +211,11 @@ class generate_defaults:
break
def gpu_probe(self):
def _gpu_probe(self) -> None:
"""
Graphics specific probe
"""
gpu_dict = []
if self.host_is_target:
gpu_dict = self.constants.computer.gpus
@@ -196,14 +241,23 @@ class generate_defaults:
device_probe.AMD.Archs.Legacy_GCN_8000,
device_probe.AMD.Archs.Legacy_GCN_9000,
device_probe.AMD.Archs.Polaris,
device_probe.AMD.Archs.Polaris_Spoof,
device_probe.AMD.Archs.Vega,
device_probe.AMD.Archs.Navi,
]:
if gpu in [
device_probe.Intel.Archs.Ivy_Bridge,
device_probe.Intel.Archs.Haswell,
device_probe.NVIDIA.Archs.Kepler,
]:
self.constants.disable_amfi = True
if gpu in [
device_probe.AMD.Archs.Legacy_GCN_7000,
device_probe.AMD.Archs.Legacy_GCN_8000,
device_probe.AMD.Archs.Legacy_GCN_9000,
device_probe.AMD.Archs.Polaris,
device_probe.AMD.Archs.Polaris_Spoof,
device_probe.AMD.Archs.Vega,
device_probe.AMD.Archs.Navi,
]:
@@ -221,6 +275,7 @@ class generate_defaults:
# See if system can use the native AMD stack in Ventura
if gpu in [
device_probe.AMD.Archs.Polaris,
device_probe.AMD.Archs.Polaris_Spoof,
device_probe.AMD.Archs.Vega,
device_probe.AMD.Archs.Navi,
]:
@@ -252,4 +307,9 @@ class generate_defaults:
self.constants.disable_cs_lv = True
if os_data.os_data.ventura in self.constants.legacy_accel_support:
# Only disable AMFI if we officially support Ventura
self.constants.disable_amfi = True
self.constants.disable_amfi = True
# Enable BetaBlur if user hasn't disabled it
is_blur_enabled = subprocess.run(["defaults", "read", "-g", "Moraea_BlurBeta"], stdout=subprocess.PIPE).stdout.decode("utf-8").strip()
if is_blur_enabled in ["false", "0"]:
subprocess.run(["defaults", "write", "-g", "Moraea_BlurBeta", "-bool", "true"])

View File

@@ -25,25 +25,46 @@ class CPU:
class PCIDevice:
VENDOR_ID: ClassVar[int] # Default vendor id, for subclasses.
vendor_id: int # The vendor ID of this PCI device
device_id: int # The device ID of this PCI device
vendor_id: int # The vendor ID of this PCI device
device_id: int # The device ID of this PCI device
class_code: int # The class code of this PCI device - https://pci-ids.ucw.cz/read/PD
name: Optional[str] = None # Name of IORegistryEntry
model: Optional[str] = None # model property
acpi_path: Optional[str] = None # ACPI Device Path
pci_path: Optional[str] = None # PCI Device Path
disable_metal: Optional[bool] = False # 'disable-metal' property
force_compatible: Optional[bool] = False # 'force-compat' property
name: Optional[str] = None # Name of IORegistryEntry
model: Optional[str] = None # model property
acpi_path: Optional[str] = None # ACPI Device Path
pci_path: Optional[str] = None # PCI Device Path
disable_metal: Optional[bool] = False # 'disable-metal' property
force_compatible: Optional[bool] = False # 'force-compat' property
vendor_id_unspoofed: Optional[int] = -1 # Unspoofed vendor ID of this PCI device
device_id_unspoofed: Optional[int] = -1 # Unspoofed device ID of this PCI device
@classmethod
def from_ioregistry(cls, entry: ioreg.io_registry_entry_t, anti_spoof=False):
properties: dict = ioreg.corefoundation_to_native(ioreg.IORegistryEntryCreateCFProperties(entry, None, ioreg.kCFAllocatorDefault, ioreg.kNilOptions)[1]) # type: ignore
if anti_spoof and "IOName" in properties:
vendor_id, device_id = (int(i, 16) for i in properties["IOName"][3:].split(","))
else:
vendor_id = None
device_id = None
vendor_id_unspoofed = None
device_id_unspoofed = None
if "IOName" in properties:
ioname = properties["IOName"]
if type(ioname) is bytes:
ioname = ioname.strip(b"\0").decode()
if ioname.startswith("pci"):
vendor_id_unspoofed, device_id_unspoofed = (int(i, 16) for i in ioname[3:].split(","))
if anti_spoof:
vendor_id = vendor_id_unspoofed
device_id = device_id_unspoofed
if vendor_id is None and device_id is None:
vendor_id, device_id = [int.from_bytes(properties[i][:4], byteorder="little") for i in ["vendor-id", "device-id"]]
if vendor_id_unspoofed is None and device_id_unspoofed is None:
vendor_id_unspoofed = vendor_id
device_id_unspoofed = device_id
device = cls(vendor_id, device_id, int.from_bytes(properties["class-code"][:6], byteorder="little"), name=ioreg.io_name_t_to_str(ioreg.IORegistryEntryGetName(entry, None)[1]))
if "model" in properties:
model = properties["model"]
@@ -56,6 +77,9 @@ class PCIDevice:
device.disable_metal = True
if "force-compat" in properties:
device.force_compatible = True
device.vendor_id_unspoofed = vendor_id_unspoofed
device.device_id_unspoofed = device_id_unspoofed
device.populate_pci_path(entry)
return device
@@ -242,6 +266,7 @@ class AMD(GPU):
Legacy_GCN_8000 = "Legacy GCN v2"
Legacy_GCN_9000 = "Legacy GCN v3"
Polaris = "Polaris"
Polaris_Spoof = "Polaris (Spoofed)"
Vega = "Vega"
Navi = "Navi"
Unknown = "Unknown"
@@ -263,6 +288,8 @@ class AMD(GPU):
self.arch = AMD.Archs.TeraScale_2
elif self.device_id in pci_data.amd_ids.polaris_ids:
self.arch = AMD.Archs.Polaris
elif self.device_id in pci_data.amd_ids.polaris_spoof_ids:
self.arch = AMD.Archs.Polaris_Spoof
elif self.device_id in pci_data.amd_ids.vega_ids:
self.arch = AMD.Archs.Vega
elif self.device_id in pci_data.amd_ids.navi_ids:

View File

@@ -1,6 +1,7 @@
from data import smbios_data, os_data, cpu_data
from resources import utilities
import logging
def set_smbios_model_spoof(model):
try:
@@ -60,7 +61,7 @@ def generate_fw_features(model, custom):
if not custom:
firmwarefeature = utilities.get_rom("firmware-features")
if not firmwarefeature:
print("- Failed to find FirmwareFeatures, falling back on defaults")
logging.info("- Failed to find FirmwareFeatures, falling back on defaults")
if smbios_data.smbios_dictionary[model]["FirmwareFeatures"] is None:
firmwarefeature = 0
else:

View File

@@ -5,42 +5,66 @@
from pathlib import Path
import plistlib
import logging
import os
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.generate_settings_file()
self.convert_defaults_to_global_settings()
class GlobalEnviromentSettings:
"""
Library for querying and writing global enviroment settings
"""
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:
print("- Permission error: Unable to write to global settings file")
def __init__(self) -> None:
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: str) -> str or None:
"""
Reads a property from the global settings file
"""
def read_property(self, property_name):
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):
def write_property(self, property_name: str, property_value) -> None:
"""
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
try:
plistlib.dump(plist, Path(self.global_settings_plist).open("wb"))
except PermissionError:
print("- Failed to write to global settings file")
logging.info("- Failed to write to global settings file")
def convert_defaults_to_global_settings(self):
def _generate_settings_file(self) -> None:
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) -> None:
"""
Converts legacy defaults to global settings
"""
defaults_path = "~/Library/Preferences/com.dortania.opencore-legacy-patcher.plist"
defaults_path = Path(defaults_path).expanduser()
@@ -52,11 +76,30 @@ class global_settings:
try:
plistlib.dump(global_settings_plist, Path(self.global_settings_plist).open("wb"))
except PermissionError:
print("- Permission error: Unable to write to global settings file")
logging.info("- Permission error: Unable to write to global settings file")
return
# delete defaults plist
try:
Path(defaults_path).unlink()
except PermissionError:
print("- Permission error: Unable to delete defaults plist")
logging.info("- Permission error: Unable to delete defaults plist")
def _fix_file_permission(self) -> None:
"""
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"))

File diff suppressed because it is too large Load Diff

View File

@@ -23,12 +23,15 @@ class RedirectLabel(object):
self.out=aWxTextCtrl
def write(self,string):
if string.endswith("MB/s"):
if "MB/s" in string:
self.out.SetLabel(string)
self.out.Centre(wx.HORIZONTAL)
wx.GetApp().Yield()
time.sleep(0.01)
def fileno(self):
return 1
def flush(self):
pass

View File

@@ -6,8 +6,9 @@ import plistlib
import subprocess
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:
@@ -74,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.TUIOnlyPrint(
["Installing OpenCore to Drive"],
"Press [Enter] to go back.\n",
[
"""OpenCore folder missing!
Please build OpenCore first!"""
],
).start()
return
print("\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
@@ -168,18 +110,13 @@ Please build OpenCore first!"""
# cancelled prompt
return
else:
if self.constants.gui_mode is False:
tui_helpers.TUIOnlyPrint(
["Copying OpenCore"], "Press [Enter] to go back.\n", ["An error occurred!"] + result.stderr.decode().split("\n") + [""]
).start()
else:
print("An error occurred!")
print(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":
print("\nSafe Mode detected. FAT32 is unsupported by macOS in this mode.")
print("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"]
@@ -196,65 +133,62 @@ Please build OpenCore first!"""
if mount_path.exists():
if (mount_path / Path("EFI/Microsoft")).exists() and self.constants.gui_mode is False:
print("- Found Windows Boot Loader")
print("\nWould you like to continue installing OpenCore?")
print("Installing OpenCore onto this drive may make Windows unbootable until OpenCore")
print("is removed from the partition")
print("We highly recommend users partition 200MB off their drive with Disk Utility")
print(" Name:\t\t OPENCORE")
print(" Format:\t\t FAT32")
print(" Size:\t\t 200MB")
logging.info("- Found Windows Boot Loader")
logging.info("\nWould you like to continue installing OpenCore?")
logging.info("Installing OpenCore onto this drive may make Windows unbootable until OpenCore")
logging.info("is removed from the partition")
logging.info("We highly recommend users partition 200MB off their drive with Disk Utility")
logging.info(" Name:\t\t OPENCORE")
logging.info(" Format:\t\t FAT32")
logging.info(" Size:\t\t 200MB")
choice = input("\nWould you like to still install OpenCore to this drive?(y/n): ")
if not choice in ["y", "Y", "Yes", "yes"]:
subprocess.run(["diskutil", "umount", mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode()
return False
if (mount_path / Path("EFI/OC")).exists():
print("- Removing preexisting EFI/OC folder")
logging.info("- Removing preexisting EFI/OC folder")
shutil.rmtree(mount_path / Path("EFI/OC"), onerror=rmtree_handler)
if (mount_path / Path("System")).exists():
print("- Removing preexisting System folder")
logging.info("- Removing preexisting System folder")
shutil.rmtree(mount_path / Path("System"), onerror=rmtree_handler)
if (mount_path / Path("boot.efi")).exists():
print("- Removing preexisting boot.efi")
logging.info("- Removing preexisting boot.efi")
os.remove(mount_path / Path("boot.efi"))
print("- Copying OpenCore onto EFI partition")
logging.info("- Copying OpenCore onto EFI partition")
shutil.copytree(self.constants.opencore_release_folder / Path("EFI/OC"), mount_path / Path("EFI/OC"))
shutil.copytree(self.constants.opencore_release_folder / Path("System"), mount_path / Path("System"))
if Path(self.constants.opencore_release_folder / Path("boot.efi")).exists():
shutil.copy(self.constants.opencore_release_folder / Path("boot.efi"), mount_path / Path("boot.efi"))
if self.constants.boot_efi is True:
print("- Converting Bootstrap to BOOTx64.efi")
logging.info("- Converting Bootstrap to BOOTx64.efi")
if (mount_path / Path("EFI/BOOT")).exists():
shutil.rmtree(mount_path / Path("EFI/BOOT"), onerror=rmtree_handler)
Path(mount_path / Path("EFI/BOOT")).mkdir()
shutil.move(mount_path / Path("System/Library/CoreServices/boot.efi"), mount_path / Path("EFI/BOOT/BOOTx64.efi"))
shutil.rmtree(mount_path / Path("System"), onerror=rmtree_handler)
if determine_sd_card(sd_type) is True:
print("- Adding SD Card icon")
logging.info("- Adding SD Card icon")
shutil.copy(self.constants.icon_path_sd, mount_path)
elif ssd_type is True:
print("- Adding SSD icon")
logging.info("- Adding SSD icon")
shutil.copy(self.constants.icon_path_ssd, mount_path)
elif disk_type == "USB":
print("- Adding External USB Drive icon")
logging.info("- Adding External USB Drive icon")
shutil.copy(self.constants.icon_path_external, mount_path)
else:
print("- Adding Internal Drive icon")
logging.info("- Adding Internal Drive icon")
shutil.copy(self.constants.icon_path_internal, mount_path)
print("- Cleaning install location")
logging.info("- Cleaning install location")
if not self.constants.recovery_status:
print("- Unmounting EFI partition")
logging.info("- Unmounting EFI partition")
subprocess.run(["diskutil", "umount", mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode()
print("- OpenCore transfer complete")
logging.info("- OpenCore transfer complete")
if self.constants.gui_mode is False:
print("\nPress [Enter] to continue.\n")
logging.info("\nPress [Enter] to continue.\n")
input()
else:
if self.constants.gui_mode is False:
tui_helpers.TUIOnlyPrint(["Copying OpenCore"], "Press [Enter] to go back.\n", ["EFI failed to mount!"]).start()
else:
print("EFI failed to mount!")
logging.info("EFI failed to mount!")
return False
return True

View File

@@ -1,483 +0,0 @@
# Creates a macOS Installer
from pathlib import Path
import plistlib
import subprocess
import requests
import tempfile
from resources import utilities, tui_helpers
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"])
print("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:
print("- Failed to find createinstallmedia")
return False
def download_install_assistant(download_path, ia_link):
# Downloads InstallAssistant.pkg
if utilities.download_file(ia_link, (Path(download_path) / Path("InstallAssistant.pkg"))):
return True
return False
def install_macOS_installer(download_path):
print("- 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:
print("- InstallAssistant installed")
return True
else:
print("- Failed to install InstallAssistant")
print(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 utilities.verify_network_connection(link) is True:
try:
catalog_plist = plistlib.loads(utilities.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(utilities.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()
print("#" * box_length)
print(header)
print("#" * box_length)
print("")
#print(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:
print("- Disk formatted")
return True
else:
print("- Failed to format disk")
print(f" Error Code: {format_process.returncode}")
input("\nPress Enter to exit")
return False
def select_disk_to_format():
utilities.cls()
utilities.header(["Installing OpenCore to Drive"])
print("\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():
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
print(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
print(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:
print("Not enough free space to create installer.sh")
print(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():
print(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

View File

@@ -5,6 +5,7 @@
import binascii
import hashlib
import logging
from pathlib import Path
CHUNK_LENGTH = 4 + 32
@@ -42,10 +43,10 @@ def chunk(file_path, chunklist, verbose):
for chunk in chunks:
status = hashlib.sha256(f.read(chunk["length"])).digest()
if not status == chunk["checksum"]:
print(
logging.info(
f"Chunk {chunks.index(chunk) + 1} checksum status FAIL: chunk sum {binascii.hexlify(chunk['checksum']).decode()}, calculated sum {binascii.hexlify(status).decode()}")
return False
elif verbose:
print(
logging.info(
f"Chunk {chunks.index(chunk) + 1} checksum status success")
return True

View File

@@ -1,292 +1,665 @@
# Kernel Debug Kit downloader
# Module for parsing and determining best Kernel Debug Kit for host OS
# Copyright (C) 2022-2023, Dhinak G, Mykola Grymalyuk
import datetime
import re
import urllib.parse
from pathlib import Path
from typing import cast
import tempfile
import plistlib
import packaging.version
import requests
import subprocess
import os
from resources import utilities
from resources.constants import Constants
import logging
from resources import utilities, network_handler, constants
from data import os_data
KDK_INSTALL_PATH: str = "/Library/Developer/KDKs"
KDK_INFO_PLIST: str = "KDKInfo.plist"
KDK_API_LINK: str = "https://raw.githubusercontent.com/dortania/KdkSupportPkg/gh-pages/manifest.json"
KDK_ASSET_LIST: list = None
class kernel_debug_kit_handler:
def __init__(self, constants: Constants):
self.constants = constants
class KernelDebugKitObject:
"""
Library for querying and downloading Kernel Debug Kits (KDK) for macOS
def get_available_kdks(self):
KDK_API_LINK = "https://kdk-api.dhinak.net/v1"
Usage:
>>> kdk_object = KernelDebugKitObject(constants, host_build, host_version)
print("- Fetching available KDKs")
>>> if kdk_object.success:
>>> # Query whether a KDK is already installed
>>> if kdk_object.kdk_already_installed:
>>> # Use the installed KDK
>>> kdk_path = kdk_object.kdk_installed_path
>>> else:
>>> # Get DownloadObject for the KDK
>>> # See network_handler.py's DownloadObject documentation for usage
>>> kdk_download_object = kdk_object.retrieve_download()
>>> # Once downloaded, recommend verifying KDK's checksum
>>> valid = kdk_object.validate_kdk_checksum()
"""
def __init__(self, global_constants: constants.Constants, host_build: str, host_version: str, ignore_installed: bool = False, passive: bool = False) -> None:
self.constants: constants.Constants = global_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
self.kdk_installed_path: str = ""
self.kdk_url: str = ""
self.kdk_url_build: str = ""
self.kdk_url_version: str = ""
self.kdk_url_expected_size: int = 0
self.kdk_url_is_exactly_match: bool = False
self.kdk_closest_match_url: str = ""
self.kdk_closest_match_url_build: str = ""
self.kdk_closest_match_url_version: str = ""
self.kdk_closest_match_url_expected_size: int = 0
self.success: bool = False
self.error_msg: str = ""
self._get_latest_kdk()
def _get_remote_kdks(self) -> list or None:
"""
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
"""
global KDK_ASSET_LIST
logging.info("- Pulling KDK list from KdkSupportPkg API")
if KDK_ASSET_LIST:
return KDK_ASSET_LIST
try:
results = utilities.SESSION.get(KDK_API_LINK, headers={"User-Agent": f"OCLP/{self.constants.patcher_version}"}, timeout=10)
results = network_handler.SESSION.get(
KDK_API_LINK,
headers={
"User-Agent": f"OCLP/{self.constants.patcher_version}"
},
timeout=5
)
except (requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.ConnectionError):
print("- Could not contact KDK API")
logging.info("- Could not contact KDK API")
return None
if results.status_code != 200:
print("- Could not fetch KDK list")
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)
def get_closest_match_legacy(self, host_version: str, host_build: str):
# Get the closest match to the provided version
# KDKs are generally a few days late, so we'll rely on N-1 matching
return KDK_ASSET_LIST
# Note: AppleDB is manually updated, so this is not a perfect solution
OS_DATABASE_LINK = "https://api.appledb.dev/main.json"
VERSION_PATTERN = re.compile(r"\d+\.\d+(\.\d+)?")
def _get_latest_kdk(self, host_build: str = None, host_version: str = None) -> None:
"""
Fetches the latest KDK for the current macOS version
parsed_host_version = cast(packaging.version.Version, packaging.version.parse(host_version))
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.
If empty, will use the host_version from the class. Defaults to None.
"""
print(f"- Checking closest match for: {host_version} build {host_build}")
if host_build is None and host_version is None:
host_build = self.host_build
host_version = self.host_version
try:
results = utilities.SESSION.get(OS_DATABASE_LINK)
except (requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.ConnectionError):
print("- Could not contact AppleDB")
return None, "", ""
parsed_version = cast(packaging.version.Version, packaging.version.parse(host_version))
if results.status_code != 200:
print("- Could not fetch database")
return None, "", ""
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
macos_builds = [i for i in results.json()["ios"] if i["osType"] == "macOS"]
# If the version is borked, put it at the bottom of the list
# Would omit it, but can't do that in this lambda
macos_builds.sort(key=lambda x: (packaging.version.parse(VERSION_PATTERN.match(x["version"]).group() if VERSION_PATTERN.match(x["version"]) else "0.0.0"), datetime.datetime.fromisoformat(x["released"] if x["released"] != "" else "1984-01-01")), reverse=True) # type: ignore
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
# Iterate through, find build that is closest to the host version
# Use date to determine which is closest
for build_info in macos_builds:
if build_info["osType"] == "macOS":
raw_version = VERSION_PATTERN.match(build_info["version"])
if not raw_version:
# Skip if version is borked
continue
version = cast(packaging.version.Version, packaging.version.parse(raw_version.group()))
build = build_info["build"]
if build == host_build:
# Skip, as we want the next closest match
continue
elif version <= parsed_host_version and version.major == parsed_host_version.major and version.minor == parsed_host_version.minor:
# The KDK list is already sorted by date then version, so the first match is the closest
print(f"- Closest match: {version} build {build}")
return self.generate_kdk_link(str(version), build), str(version), build
remote_kdk_version = self._get_remote_kdks()
print("- Could not find a match")
return None, "", ""
if remote_kdk_version is None:
logging.warning("- Failed to fetch KDK list, falling back to local KDK matching")
def generate_kdk_link(self, version: str, build: str):
return f"https://download.developer.apple.com/macOS/Kernel_Debug_Kit_{version}_build_{build}/Kernel_Debug_Kit_{version}_build_{build}.dmg"
# First check if a KDK matching the current macOS version is installed
# 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(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
def verify_apple_developer_portal(self, link):
# Determine whether Apple Developer Portal is up
# and if the requested file is available
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(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
# Returns following:
# 0: Portal is up and file is available
# 1: Portal is up but file is not available
# 2: Portal is down
# 3: Network error
logging.warning(f"- Couldn't find KDK matching {host_version} or {older_version}, please install one manually")
if utilities.verify_network_connection("https://developerservices2.apple.com/services/download") is False:
print("- Could not connect to the network")
return 3
self.error_msg = f"Could not contact KdkSupportPkg API, and no KDK matching {host_version} ({host_build}) or {older_version} was installed.\nPlease ensure you have a network connection or manually install a KDK."
TOKEN_URL_BASE = "https://developerservices2.apple.com/services/download"
remote_path = urllib.parse.urlparse(link).path
token_url = urllib.parse.urlunparse(urllib.parse.urlparse(TOKEN_URL_BASE)._replace(query=urllib.parse.urlencode({"path": remote_path})))
return
try:
response = utilities.SESSION.get(token_url, timeout=5)
except (requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.ConnectionError):
print("- Could not contact Apple download servers")
return 2
for kdk in remote_kdk_version:
kdk_version = cast(packaging.version.Version, packaging.version.parse(kdk["version"]))
if (kdk["build"] == host_build):
self.kdk_url = kdk["url"]
self.kdk_url_build = kdk["build"]
self.kdk_url_version = kdk["version"]
self.kdk_url_expected_size = kdk["fileSize"]
self.kdk_url_is_exactly_match = True
break
if kdk_version <= parsed_version and kdk_version.major == parsed_version.major and (kdk_version.minor in range(parsed_version.minor - 1, parsed_version.minor + 1)):
# The KDK list is already sorted by version then date, so the first match is the closest
self.kdk_closest_match_url = kdk["url"]
self.kdk_closest_match_url_build = kdk["build"]
self.kdk_closest_match_url_version = kdk["version"]
self.kdk_closest_match_url_expected_size = kdk["fileSize"]
self.kdk_url_is_exactly_match = False
break
try:
response.raise_for_status()
except requests.exceptions.HTTPError:
if response.status_code == 400 and "The path specified is invalid" in response.text:
print("- File does not exist on Apple download servers")
return 1
else:
print("- Could not request download authorization from Apple download servers")
return 2
return 0
if self.kdk_url == "":
if self.kdk_closest_match_url == "":
logging.warning(f"- No KDKs found for {host_build} ({host_version})")
self.error_msg = f"No KDKs found for {host_build} ({host_version})"
return
logging.info(f"- No direct match found for {host_build}, falling back to closest match")
logging.info(f"- Closest Match: {self.kdk_closest_match_url_build} ({self.kdk_closest_match_url_version})")
def download_kdk(self, version: str, build: str):
detected_build = build
if self.is_kdk_installed(detected_build) is True:
print("- KDK is already installed")
self.remove_unused_kdks(exclude_builds=[detected_build])
return True, "", detected_build
download_link = None
closest_match_download_link = None
closest_version = ""
closest_build = ""
kdk_list = self.get_available_kdks()
parsed_version = cast(packaging.version.Version, packaging.version.parse(version))
if kdk_list:
for kdk in kdk_list:
kdk_version = cast(packaging.version.Version, packaging.version.parse(kdk["version"]))
if kdk["build"] == build:
download_link = kdk["url"]
elif not closest_match_download_link and kdk_version <= parsed_version and kdk_version.major == parsed_version.major and (kdk_version.minor == parsed_version.minor or kdk_version.minor == parsed_version.minor - 1):
# The KDK list is already sorted by date then version, so the first match is the closest
closest_match_download_link = kdk["url"]
closest_version = kdk["version"]
closest_build = kdk["build"]
self.kdk_url = self.kdk_closest_match_url
self.kdk_url_build = self.kdk_closest_match_url_build
self.kdk_url_version = self.kdk_closest_match_url_version
self.kdk_url_expected_size = self.kdk_closest_match_url_expected_size
else:
print("- Could not fetch KDK list, falling back to brute force")
download_link = self.generate_kdk_link(version, build)
closest_match_download_link, closest_version, closest_build = self.get_closest_match_legacy(version, build)
logging.info(f"- Direct match found for {host_build} ({host_version})")
print(f"- Checking for KDK matching macOS {version} build {build}")
# download_link is None if no matching KDK is found, so we'll fall back to the closest match
result = self.verify_apple_developer_portal(download_link) if download_link else 1
if result == 0:
print("- Downloading KDK")
elif result == 1:
print("- Could not find KDK, finding closest match")
if self.is_kdk_installed(closest_build) is True:
print(f"- Closet Build ({closest_build}) already installed")
self.remove_unused_kdks(exclude_builds=[detected_build, closest_build])
return True, "", closest_build
# Check if this KDK is already installed
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
self.success = True
return
if closest_match_download_link is None:
msg = "Could not find KDK for host, nor closest match"
print(f"- {msg}")
return False, msg, ""
logging.info("- Following KDK is recommended:")
logging.info(f"- KDK Build: {self.kdk_url_build}")
logging.info(f"- KDK Version: {self.kdk_url_version}")
logging.info(f"- KDK URL: {self.kdk_url}")
print(f"- Closest match: {closest_version} build {closest_build}")
result = self.verify_apple_developer_portal(closest_match_download_link)
self.success = True
if result == 0:
print("- Downloading KDK")
download_link = closest_match_download_link
elif result == 1:
msg = "Could not find KDK for host on Apple's servers, nor closest match"
print(f"- {msg}")
return False, msg, ""
elif result == 2:
msg = "Could not contact Apple download servers"
download_link = self.kdk_backup_site(closest_build)
if download_link is None:
msg += " and could not find a backup copy online"
print(f"- {msg}")
return False, msg, ""
else:
msg = "Unknown error"
print(f"- {msg}")
return False, msg, ""
elif result == 2:
msg = "Could not contact Apple download servers"
download_link = self.kdk_backup_site(build)
if download_link is None:
msg += " and could not find a backup copy online"
print(f"- {msg}")
return False, msg, ""
elif result == 3:
msg = "Failed to connect to the internet"
print(f"- {msg}")
return False, msg, ""
if "github" in download_link:
result = utilities.download_file(download_link, self.constants.kdk_download_path)
else:
result = utilities.download_apple_developer_portal(download_link, self.constants.kdk_download_path)
def retrieve_download(self, override_path: str = "") -> network_handler.DownloadObject or None:
"""
Returns a DownloadObject for the KDK
if result:
result = subprocess.run(["hdiutil", "verify", self.constants.kdk_download_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
print(f"Error: Kernel Debug Kit checksum verification failed!")
print(f"Output: {result.stderr}")
msg = "Kernel Debug Kit checksum verification failed, please try again.\n\nIf this continues to fail, ensure you're downloading on a stable network connection (ie. Ethernet)"
print(f"- {msg}")
return False, msg, ""
self.remove_unused_kdks(exclude_builds=[detected_build, closest_build])
return True, "", detected_build
msg = "Failed to download KDK"
print(f"- {msg}")
return False, msg, ""
Parameters:
override_path (str): Override the default download path
def is_kdk_installed(self, build):
kexts_to_check = [
Returns:
DownloadObject: DownloadObject for the KDK, None if no download required
"""
self.success = False
self.error_msg = ""
if self.kdk_already_installed:
logging.info("- No download required, KDK already installed")
self.success = True
return None
if self.kdk_url == "":
self.error_msg = "Could not retrieve KDK catalog, no KDK to download"
logging.error(self.error_msg)
return None
logging.info(f"- Returning DownloadObject for KDK: {Path(self.kdk_url).name}")
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.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)
def _generate_kdk_info_plist(self, plist_path: str) -> None:
"""
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:
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}")
def _local_kdk_valid(self, kdk_path: Path) -> bool:
"""
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
Parameters:
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) -> bool:
"""
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
"""
KEXT_CATALOG = [
"System.kext/PlugIns/Libkern.kext/Libkern",
"apfs.kext/Contents/MacOS/apfs",
"IOUSBHostFamily.kext/Contents/MacOS/IOUSBHostFamily",
"AMDRadeonX6000.kext/Contents/MacOS/AMDRadeonX6000",
]
if Path("/Library/Developer/KDKs").exists():
for file in Path("/Library/Developer/KDKs").iterdir():
if file.is_dir():
if file.name.endswith(f"{build}.kdk"):
for kext in kexts_to_check:
if not Path(f"{file}/System/Library/Extensions/{kext}").exists():
print(f"- Corrupted KDK found, removing due to missing: {file}/System/Library/Extensions/{kext}")
utilities.elevated(["rm", "-rf", file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
return False
return True
return False
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}")
self._remove_kdk(kdk_path)
return False
return True
def _local_kdk_installed(self, match: str = None, check_version: bool = False) -> str or None:
"""
Checks if KDK matching build 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
"""
if self.ignore_installed is True:
return None
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
for kdk_folder in Path(KDK_INSTALL_PATH).iterdir():
if not kdk_folder.is_dir():
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
# 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: {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
def _remove_kdk(self, kdk_path: str) -> None:
"""
Removes provided KDK
Parameters:
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
if not Path(kdk_path).exists():
logging.warning(f"- KDK does not exist: {kdk_path}")
return
rm_args = ["rm", "-rf" if Path(kdk_path).is_dir() else "-f", 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')}")
return
logging.info(f"- Successfully removed KDK: {kdk_path}")
def _remove_unused_kdks(self, exclude_builds: list = None) -> None:
"""
Removes KDKs that are not in use
Parameters:
exclude_builds (list, optional): Builds to exclude from removal.
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,
self.kdk_closest_match_url_build,
]
def remove_unused_kdks(self, exclude_builds=[]):
if self.constants.should_nuke_kdks is False:
return
if not Path("/Library/Developer/KDKs").exists():
if not Path(KDK_INSTALL_PATH).exists():
return
if exclude_builds == []:
return
print("- Cleaning unused KDKs")
for kdk_folder in Path("/Library/Developer/KDKs").iterdir():
if kdk_folder.is_dir():
if kdk_folder.name.endswith(".kdk"):
should_remove = True
for build in exclude_builds:
if build != "" and kdk_folder.name.endswith(f"{build}.kdk"):
should_remove = False
break
if should_remove is False:
logging.info("- Cleaning unused KDKs")
for kdk_folder in Path(KDK_INSTALL_PATH).iterdir():
if kdk_folder.name.endswith(".kdk") or kdk_folder.name.endswith(".pkg"):
should_remove = True
for build in exclude_builds:
if build != "":
continue
print(f" - Removing {kdk_folder.name}")
utilities.elevated(["rm", "-rf", kdk_folder], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
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:
continue
self._remove_kdk(kdk_folder)
def kdk_backup_site(self, build):
KDK_MIRROR_REPOSITORY = "https://api.github.com/repos/dortania/KdkSupportPkg/releases"
def validate_kdk_checksum(self, kdk_dmg_path: str = None) -> bool:
"""
Validates KDK DMG checksum
# Check if tag exists
catalog = requests.get(KDK_MIRROR_REPOSITORY)
if catalog.status_code != 200:
print(f"- Could not contact KDK mirror repository")
return None
Parameters:
kdk_dmg_path (str, optional): Path to KDK DMG. Defaults to None.
catalog = catalog.json()
Returns:
bool: True if valid, False if invalid
"""
for release in catalog:
if release["tag_name"] == build:
print(f"- Found KDK mirror for build: {build}")
for asset in release["assets"]:
if asset["name"].endswith(".dmg"):
return asset["browser_download_url"]
self.success = False
self.error_msg = ""
print(f"- Could not find KDK mirror for build {build}")
return None
if kdk_dmg_path is None:
kdk_dmg_path = self.constants.kdk_download_path
if not Path(kdk_dmg_path).exists():
logging.error(f"KDK DMG does not exist: {kdk_dmg_path}")
return False
# TODO: should we use the checksum from the API?
result = subprocess.run(["hdiutil", "verify", self.constants.kdk_download_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
logging.info("- Error: Kernel Debug Kit checksum verification failed!")
logging.info(f"- Output: {result.stderr.decode('utf-8')}")
msg = "Kernel Debug Kit checksum verification failed, please try again.\n\nIf this continues to fail, ensure you're downloading on a stable network connection (ie. Ethernet)"
logging.info(f"- {msg}")
self.error_msg = msg
self._remove_unused_kdks()
self.success = True
class KernelDebugKitUtilities:
"""
Utilities for KDK handling
"""
def __init__(self) -> None:
pass
def install_kdk_pkg(self, kdk_path: Path) -> bool:
"""
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
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:")
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) -> bool:
"""
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"- 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:
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!!!")
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}"))
self._unmount_disk_image(mount_point)
logging.info("- Successfully installed KDK")
return True
def _unmount_disk_image(self, mount_point) -> None:
"""
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) -> None:
"""
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"))
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}")
logging.info(f"- Creating backup: {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'))

View File

@@ -0,0 +1,175 @@
import logging
import sys
import threading
import os
import subprocess
from pathlib import Path
class InitializeLoggingSupport:
"""
Initialize logging framework for program
Primary responsibilities:
- Determine where to store log file
- Clean log file if it's near the max file size
- Initialize logging framework configuration
- Implement custom traceback handler
- Implement error handling for file write
Usage:
>>> 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:
self.log_filename: str = "OpenCore-Patcher.log"
self.log_filepath: Path = None
self.original_excepthook: sys = sys.excepthook
self.original_thread_excepthook: threading = threading.excepthook
self.max_file_size: int = 1024 * 1024 * 10 # 10 MB
self.file_size_redline: int = 1024 * 1024 * 9 # 9 MB, when to start cleaning log file
self._initialize_logging_path()
self._clean_log_file()
self._attempt_initialize_logging_configuration()
self._implement_custom_traceback_handler()
self._fix_file_permission()
def __del__(self) -> None:
self._restore_original_excepthook()
def _initialize_logging_path(self) -> None:
"""
Initialize logging framework storage path
"""
self.log_filepath = Path(f"~/Library/Logs/{self.log_filename}").expanduser()
if not self.log_filepath.parent.exists():
# Likely in an installer environment, store in /Users/Shared
self.log_filepath = Path("/Users/Shared") / self.log_filename
print("- Initializing logging framework...")
print(f" - Log file: {self.log_filepath}")
def _clean_log_file(self) -> None:
"""
Determine if log file should be cleaned
We check if we're near the max file size, and if so, we clean the log file
"""
if not self.log_filepath.exists():
return
if self.log_filepath.stat().st_size < self.file_size_redline:
return
# Check if backup log file exists
backup_log_filepath = self.log_filepath.with_suffix(".old.log")
try:
if backup_log_filepath.exists():
backup_log_filepath.unlink()
# Rename current log file to backup log file
self.log_filepath.rename(backup_log_filepath)
except Exception as e:
print(f"- Failed to clean log file: {e}")
def _fix_file_permission(self) -> None:
"""
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) -> None:
"""
Initialize logging framework configuration
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(
level=logging.NOTSET,
format="%(asctime)s - %(filename)s (%(lineno)d): %(message)s",
handlers=[
logging.StreamHandler(stream = sys.stdout),
logging.FileHandler(self.log_filepath) if log_to_file is True else logging.NullHandler()
],
)
logging.getLogger().setLevel(logging.INFO)
logging.getLogger().handlers[0].setFormatter(logging.Formatter("%(message)s"))
logging.getLogger().handlers[1].maxBytes = self.max_file_size
def _attempt_initialize_logging_configuration(self) -> None:
"""
Attempt to initialize logging framework configuration
If we fail to initialize the logging framework, we will disable logging to file
"""
try:
self._initialize_logging_configuration()
except Exception as e:
print(f"- Failed to initialize logging framework: {e}")
print("- Retrying without logging to file...")
self._initialize_logging_configuration(log_to_file=False)
def _implement_custom_traceback_handler(self) -> None:
"""
Reroute traceback to logging module
"""
def custom_excepthook(type, value, tb) -> None:
"""
Reroute traceback in main thread to logging module
"""
logging.error("Uncaught exception in main thread", exc_info=(type, value, tb))
def custom_thread_excepthook(args) -> None:
"""
Reroute traceback in spawned thread to logging module
"""
logging.error("Uncaught exception in spawned thread", exc_info=(args))
sys.excepthook = custom_excepthook
threading.excepthook = custom_thread_excepthook
def _restore_original_excepthook(self) -> None:
"""
Restore original traceback handlers
"""
sys.excepthook = self.original_excepthook
threading.excepthook = self.original_thread_excepthook

View File

@@ -0,0 +1,615 @@
# 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) -> None:
pass
def install_macOS_installer(self, download_path: str) -> bool:
"""
Installs InstallAssistant.pkg
Parameters:
download_path (str): Path to InstallAssistant.pkg
Returns:
bool: True if successful, False otherwise
"""
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: str, installer_path: str, disk: str) -> bool:
"""
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
Returns:
bool: True if successful, False otherwise
"""
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) -> dict:
"""
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.IntEnum):
"""
Enum for catalog types
Variants:
DeveloperSeed: Developer Beta (Part of the Apple Developer Program)
PublicSeed: Public Beta
CustomerSeed: AppleSeed Program (Generally mirrors DeveloperSeed)
PublicRelease: Public Release
"""
DeveloperSeed: int = 0
PublicSeed: int = 1
CustomerSeed: int = 2
PublicRelease: int = 3
class RemoteInstallerCatalog:
"""
Parses Apple's Software Update catalog and finds all macOS installers.
"""
def __init__(self, seed_override: SeedType = SeedType.PublicRelease) -> None:
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) -> str:
"""
Constructs the catalog URL based on the seed type
Parameters:
seed_type (SeedType): The seed type to use
Returns:
str: The catalog URL
"""
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) -> dict:
"""
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) -> dict:
"""
Parses the catalog and returns a dictionary of available installers
Returns:
dict: Dictionary of available installers
"""
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) -> dict:
"""
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) -> None:
self.available_apps: dict = self._list_local_macOS_installers()
def _list_local_macOS_installers(self) -> dict:
"""
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) -> tuple:
"""
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)

View File

@@ -1,41 +1,70 @@
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
import subprocess
import sys
from pathlib import Path
import time
import logging
import threading
from pathlib import Path
from resources.gui import gui_main
from resources import (
constants,
utilities,
device_probe,
os_probe,
defaults,
arguments,
reroute_payloads,
commit_info,
logging_handler
)
from resources import cli_menu, constants, utilities, device_probe, os_probe, defaults, arguments, install, tui_helpers, reroute_payloads, commit_info
from resources.build import build
from data import model_array
class OpenCoreLegacyPatcher:
def __init__(self, launch_gui=False):
print("- Loading...")
self.constants = constants.Constants()
self.constants.wxpython_variant = launch_gui
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()
"""
Initial entry point for starting OpenCore Legacy Patcher
"""
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()
def __init__(self) -> None:
logging_handler.InitializeLoggingSupport()
self.constants: constants.Constants = constants.Constants()
self.constants.wxpython_variant: bool = True
logging.info(f"- Loading OpenCore Legacy Patcher v{self.constants.patcher_version}...")
self._generate_base_data()
if utilities.check_cli_args() is None:
gui_main.wx_python_gui(self.constants).main_menu(None)
def _generate_base_data(self) -> None:
"""
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:
@@ -45,83 +74,40 @@ class OpenCoreLegacyPatcher:
launcher_script = launcher_script.replace("/resources/main.py", "/OpenCore-Patcher-GUI.command")
self.constants.launcher_binary = launcher_binary
self.constants.launcher_script = launcher_script
self.constants.unpack_thread = threading.Thread(target=reroute_payloads.reroute_payloads(self.constants).setup_tmp_disk_image)
self.constants.unpack_thread.start()
self.constants.commit_info = commit_info.commit_info(self.constants.launcher_binary).generate_commit_info()
# Now that we have commit info, update nightly link
# Initialize working directory
self.constants.unpack_thread = threading.Thread(target=reroute_payloads.RoutePayloadDiskImage, args=(self.constants,))
self.constants.unpack_thread.start()
# Generate commit info
self.constants.commit_info = commit_info.ParseCommitInfo(self.constants.launcher_binary).generate_commit_info()
if self.constants.commit_info[0] not in ["Running from source", "Built from source"]:
# Now that we have commit info, update nightly link
branch = self.constants.commit_info[0]
branch = branch.replace("refs/heads/", "")
self.constants.installer_pkg_url_nightly = self.constants.installer_pkg_url_nightly.replace("main", branch)
defaults.generate_defaults(self.computer.real_model, True, self.constants)
# Generate defaults
defaults.GenerateDefaults(self.computer.real_model, True, self.constants)
if utilities.check_cli_args() is not None:
print("- 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"):
print("- 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:
print(f"- No arguments present, loading {'GUI' if self.constants.wxpython_variant is True else 'TUI'} mode")
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)
arguments.arguments(self.constants)

View File

@@ -0,0 +1,388 @@
# Library dedicated to Network Handling tasks including downloading files
# Primarily based around the DownloadObject class, which provides a simple
# object for libraries to query download progress and status
# Copyright (C) 2023, Mykola Grymalyuk
import time
import requests
import threading
import logging
import enum
import hashlib
from pathlib import Path
from resources import utilities
SESSION = requests.Session()
class DownloadStatus(enum.Enum):
"""
Enum for download status
"""
INACTIVE: str = "Inactive"
DOWNLOADING: str = "Downloading"
ERROR: str = "Error"
COMPLETE: str = "Complete"
class NetworkUtilities:
"""
Utilities for network related tasks, primarily used for downloading files
"""
def __init__(self, url: str = None) -> None:
self.url: str = url
if self.url is None:
self.url = "https://github.com"
def verify_network_connection(self) -> bool:
"""
Verifies that the network is available
Returns:
bool: True if network is available, False otherwise
"""
try:
requests.head(self.url, timeout=5, allow_redirects=True)
return True
except (
requests.exceptions.Timeout,
requests.exceptions.TooManyRedirects,
requests.exceptions.ConnectionError,
requests.exceptions.HTTPError
):
return False
def validate_link(self) -> bool:
"""
Check for 404 error
Returns:
bool: True if link is valid, False otherwise
"""
try:
response = SESSION.head(self.url, timeout=5, allow_redirects=True)
if response.status_code == 404:
return False
else:
return True
except (
requests.exceptions.Timeout,
requests.exceptions.TooManyRedirects,
requests.exceptions.ConnectionError,
requests.exceptions.HTTPError
):
return False
class DownloadObject:
"""
Object for downloading files from the network
Usage:
>>> download_object = DownloadObject(url, path)
>>> download_object.download(display_progress=True)
>>> if download_object.is_active():
>>> print(download_object.get_percent())
>>> if not download_object.download_complete:
>>> print("Download failed")
>>> print("Download complete"")
"""
def __init__(self, url: str, path: str) -> None:
self.url: str = url
self.status: str = DownloadStatus.INACTIVE
self.error_msg: str = ""
self.filename: str = self._get_filename()
self.filepath: Path = Path(path)
self.total_file_size: float = 0.0
self.downloaded_file_size: float = 0.0
self.start_time: float = time.time()
self.error: bool = False
self.should_stop: bool = False
self.download_complete: bool = False
self.has_network: bool = NetworkUtilities(self.url).verify_network_connection()
self.active_thread: threading.Thread = None
self.should_checksum: bool = False
self.checksum = None
self._checksum_storage: hash = None
if self.has_network:
self._populate_file_size()
def __del__(self) -> None:
self.stop()
def download(self, display_progress: bool = False, spawn_thread: bool = True, verify_checksum: bool = False) -> None:
"""
Download the file
Spawns a thread to download the file, so that the main thread can continue
Note sleep is disabled while the download is active
Parameters:
display_progress (bool): Display progress in console
spawn_thread (bool): Spawn a thread to download the file, otherwise download in the current thread
verify_checksum (bool): Calculate checksum of downloaded file if True
"""
self.status = DownloadStatus.DOWNLOADING
logging.info(f"- Starting download: {self.filename}")
if spawn_thread:
if self.active_thread:
logging.error("- Download already in progress")
return
self.should_checksum = verify_checksum
self.active_thread = threading.Thread(target=self._download, args=(display_progress,))
self.active_thread.start()
return
self.should_checksum = verify_checksum
self._download(display_progress)
def download_simple(self, verify_checksum: bool = False) -> str or bool:
"""
Alternative to download(), mimics utilities.py's old download_file() function
Parameters:
verify_checksum (bool): Return checksum of downloaded file if True
Returns:
If verify_checksum is True, returns the checksum of the downloaded file
Otherwise, returns True if download was successful, False otherwise
"""
if verify_checksum:
self.should_checksum = True
self.checksum = hashlib.sha256()
self.download(spawn_thread=False)
if not self.download_complete:
return False
return self.checksum.hexdigest() if self.checksum else True
def _get_filename(self) -> str:
"""
Get the filename from the URL
Returns:
str: Filename
"""
return Path(self.url).name
def _populate_file_size(self) -> None:
"""
Get the file size of the file to be downloaded
If unable to get file size, set to zero
"""
try:
result = SESSION.head(self.url, allow_redirects=True, timeout=5)
if 'Content-Length' in result.headers:
self.total_file_size = float(result.headers['Content-Length'])
else:
raise Exception("Content-Length missing from headers")
except Exception as e:
logging.error(f"- Error determining file size {self.url}: {str(e)}")
logging.error("- Assuming file size is 0")
self.total_file_size = 0.0
def _update_checksum(self, chunk: bytes) -> None:
"""
Update checksum with new chunk
Parameters:
chunk (bytes): Chunk to update checksum with
"""
self._checksum_storage.update(chunk)
def _prepare_working_directory(self, path: Path) -> bool:
"""
Validates working enviroment, including free space and removing existing files
Parameters:
path (str): Path to the file
Returns:
bool: True if successful, False if not
"""
try:
if Path(path).exists():
logging.info(f"- Deleting existing file: {path}")
Path(path).unlink()
return True
if not Path(path).parent.exists():
logging.info(f"- Creating directory: {Path(path).parent}")
Path(path).parent.mkdir(parents=True, exist_ok=True)
available_space = utilities.get_free_space(Path(path).parent)
if self.total_file_size > available_space:
msg = f"- Not enough free space to download {self.filename}, need {utilities.human_fmt(self.total_file_size)}, have {utilities.human_fmt(available_space)}"
logging.error(msg)
raise Exception(msg)
except Exception as e:
self.error = True
self.error_msg = str(e)
self.status = DownloadStatus.ERROR
logging.error(f"- Error preparing working directory {path}: {self.error_msg}")
return False
return True
def _download(self, display_progress: bool = False) -> None:
"""
Download the file
Libraries should invoke download() instead of this method
Parameters:
display_progress (bool): Display progress in console
"""
utilities.disable_sleep_while_running()
try:
if not self.has_network:
raise Exception("No network connection")
if self._prepare_working_directory(self.filepath) is False:
raise Exception(self.error_msg)
response = SESSION.get(self.url, stream=True, timeout=10)
with open(self.filepath, 'wb') as file:
for i, chunk in enumerate(response.iter_content(1024 * 1024 * 4)):
if self.should_stop:
raise Exception("Download stopped")
if chunk:
file.write(chunk)
self.downloaded_file_size += len(chunk)
if self.should_checksum:
self._update_checksum(chunk)
if display_progress and i % 100:
# Don't use logging here, as we'll be spamming the log file
if self.total_file_size == 0.0:
print(f"- Downloaded {utilities.human_fmt(self.downloaded_file_size)} of {self.filename}")
else:
print(f"- Downloaded {self.get_percent():.2f}% of {self.filename} ({utilities.human_fmt(self.get_speed())}/s) ({self.get_time_remaining():.2f} seconds remaining)")
self.download_complete = True
logging.info(f"- Download complete: {self.filename}")
logging.info("- Stats:")
logging.info(f"- Downloaded size: {utilities.human_fmt(self.downloaded_file_size)}")
logging.info(f"- Time elapsed: {(time.time() - self.start_time):.2f} seconds")
logging.info(f"- Speed: {utilities.human_fmt(self.downloaded_file_size / (time.time() - self.start_time))}/s")
logging.info(f"- Location: {self.filepath}")
except Exception as e:
self.error = True
self.error_msg = str(e)
self.status = DownloadStatus.ERROR
logging.error(f"- Error downloading {self.url}: {self.error_msg}")
self.status = DownloadStatus.COMPLETE
utilities.enable_sleep_after_running()
def get_percent(self) -> float:
"""
Query the download percent
Returns:
float: The download percent, or -1 if unknown
"""
if self.total_file_size == 0.0:
logging.error("- File size is 0, cannot calculate percent")
return -1
return self.downloaded_file_size / self.total_file_size * 100
def get_speed(self) -> float:
"""
Query the download speed
Returns:
float: The download speed in bytes per second
"""
return self.downloaded_file_size / (time.time() - self.start_time)
def get_time_remaining(self) -> float:
"""
Query the time remaining for the download
Returns:
float: The time remaining in seconds, or -1 if unknown
"""
if self.total_file_size == 0.0:
logging.error("- File size is 0, cannot calculate time remaining")
return -1
return (self.total_file_size - self.downloaded_file_size) / self.get_speed()
def get_file_size(self) -> float:
"""
Query the file size of the file to be downloaded
Returns:
float: The file size in bytes, or 0.0 if unknown
"""
return self.total_file_size
def is_active(self) -> bool:
"""
Query if the download is active
Returns:
boolean: True if active, False if completed, failed, stopped, or inactive
"""
if self.status == DownloadStatus.DOWNLOADING:
return True
return False
def stop(self) -> None:
"""
Stop the download
If the download is active, this function will hold the thread until stopped
"""
self.should_stop = True
if self.active_thread:
while self.active_thread.is_alive():
time.sleep(1)

View File

@@ -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) -> None:
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) -> int:
"""
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) -> int:
"""
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) -> str:
"""
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) -> str:
"""
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}")

View File

@@ -7,22 +7,33 @@ from pathlib import Path
import subprocess
import tempfile
import atexit
import logging
class reroute_payloads:
def __init__(self, constants):
self.constants = constants
from resources import constants
class RoutePayloadDiskImage:
def __init__(self, global_constants: constants.Constants) -> None:
self.constants: constants.Constants = global_constants
self._setup_tmp_disk_image()
def _setup_tmp_disk_image(self) -> None:
"""
Initialize temp directory and mount payloads.dmg
Create overlay for patcher to write to
Currently only applicable for GUI variant and not running from source
"""
def setup_tmp_disk_image(self):
# Create a temp directory to mount the payloads.dmg
# Then reroute r/w to this new temp directory
# Currently only applicable for GUI variant
if self.constants.wxpython_variant is True and not self.constants.launcher_script:
print("- Running in Binary GUI mode, switching to tmp directory")
logging.info("- Running in Binary GUI mode, switching to tmp directory")
self.temp_dir = tempfile.TemporaryDirectory()
print(f"- New payloads location: {self.temp_dir.name}")
print("- Creating payloads directory")
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",
@@ -34,19 +45,28 @@ class reroute_payloads:
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
if output.returncode == 0:
print("- Mounted payloads.dmg")
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:
print("- Failed to mount payloads.dmg")
print(f"Output: {output.stdout.decode()}")
print(f"Return Code: {output.returncode}")
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) -> None:
"""
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
"""
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
dmg_info = subprocess.run(["hdiutil", "info", "-plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
dmg_info = plistlib.loads(dmg_info.stdout)
@@ -56,13 +76,13 @@ class reroute_payloads:
# Check that only our personal payloads.dmg is unmounted
if "shadow-path" in image:
if self.temp_dir.name in image["shadow-path"]:
print("- Unmounting personal payloads.dmg")
logging.info("- Unmounting personal payloads.dmg")
subprocess.run(
["hdiutil", "detach", image["system-entities"][0]["dev-entry"], "-force"],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
else:
print(f"- Unmounting payloads.dmg at: {image['system-entities'][0]['dev-entry']}")
logging.info(f"- Unmounting payloads.dmg at: {image['system-entities'][0]['dev-entry']}")
subprocess.run(
["hdiutil", "detach", image["system-entities"][0]["dev-entry"], "-force"],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT

View File

@@ -1,155 +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
try:
from Queue import Queue, Empty
except:
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:
print(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:
print(" ".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]):
print(out[0])
if stderr and len(out[1]):
print(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

View File

@@ -37,17 +37,18 @@ import shutil
import subprocess
from pathlib import Path
from datetime import datetime
import logging
from resources import constants, utilities, kdk_handler
from resources.sys_patch import sys_patch_download, sys_patch_detect, sys_patch_auto, sys_patch_helpers
from resources.sys_patch import sys_patch_detect, sys_patch_auto, sys_patch_helpers
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()
@@ -60,9 +61,9 @@ class PatchSysVolume:
# GUI will detect hardware patches before starting PatchSysVolume()
# However the TUI will not, so allow for data to be passed in manually avoiding multiple calls
if hardware_details is None:
hardware_details = sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).detect_patch_set()
hardware_details = sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).detect_patch_set()
self.hardware_details = hardware_details
self.init_pathing(custom_root_mount_path=None, custom_data_mount_path=None)
self._init_pathing(custom_root_mount_path=None, custom_data_mount_path=None)
self.skip_root_kmutil_requirement = self.hardware_details["Settings: Supports Auxiliary Cache"]
@@ -71,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
@@ -82,50 +91,84 @@ class PatchSysVolume:
else:
self.mount_location = ""
self.mount_location_data = ""
self.mount_extensions = f"{self.mount_location}/System/Library/Extensions"
self.mount_application_support = f"{self.mount_location_data}/Library/Application Support"
def mount_root_vol(self):
def _mount_root_vol(self):
# Returns boolean if Root Volume is available
self.root_mount_path = utilities.get_disk_path()
if self.root_mount_path.startswith("disk"):
print(f"- Found Root Volume at: {self.root_mount_path}")
logging.info(f"- Found Root Volume at: {self.root_mount_path}")
if Path(self.mount_extensions).exists():
print("- Root Volume is already mounted")
logging.info("- Root Volume is already mounted")
return True
else:
if self.root_supports_snapshot is True:
print("- Mounting APFS Snapshot as writable")
logging.info("- Mounting APFS Snapshot as writable")
result = utilities.elevated(["mount", "-o", "nobrowse", "-t", "apfs", f"/dev/{self.root_mount_path}", self.mount_location], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode == 0:
print(f"- Mounted APFS Snapshot as writable at: {self.mount_location}")
logging.info(f"- Mounted APFS Snapshot as writable at: {self.mount_location}")
if Path(self.mount_extensions).exists():
print("- Successfully mounted the Root Volume")
logging.info("- Successfully mounted the Root Volume")
return True
else:
print("- Root Volume appears to have unmounted unexpectedly")
logging.info("- Root Volume appears to have unmounted unexpectedly")
else:
print("- Unable to mount APFS Snapshot as writable")
print("Reason for mount failure:")
print(result.stdout.decode().strip())
logging.info("- Unable to mount APFS Snapshot as writable")
logging.info("Reason for mount failure:")
logging.info(result.stdout.decode().strip())
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:
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 = kdk_handler.kernel_debug_kit_handler(self.constants).download_kdk(self.constants.detected_os_version, self.constants.detected_os_build)
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():
@@ -135,25 +178,25 @@ class PatchSysVolume:
oclp_plist_data = plistlib.load(open(oclp_plist, "rb"))
if "Kernel Debug Kit Used" in oclp_plist_data:
if oclp_plist_data["Kernel Debug Kit Used"] == str(kdk_path):
print("- Matching KDK determined to already be merged, skipping")
logging.info("- Matching KDK determined to already be merged, skipping")
return
except:
pass
if kdk_path is None:
print(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
print(f"- Found KDK at: {kdk_path}")
logging.info(f"- Found KDK at: {kdk_path}")
# Due to some IOHIDFamily oddities, we need to ensure their CodeSignature is retained
cs_path = Path(self.mount_location) / Path("System/Library/Extensions/IOHIDFamily.kext/Contents/PlugIns/IOHIDEventDriver.kext/Contents/_CodeSignature")
if save_hid_cs is True and cs_path.exists():
print("- Backing up IOHIDEventDriver CodeSignature")
logging.info("- Backing up IOHIDEventDriver CodeSignature")
# 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)
print("- 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)
@@ -163,52 +206,52 @@ class PatchSysVolume:
# During reversing, we found that kmutil uses this path to determine whether the KDK was successfully merged
# Best to verify now before we cause any damage
if not (Path(self.mount_location) / Path("System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern")).exists():
print("- Failed to merge KDK with Root Volume")
logging.info("- Failed to merge KDK with Root Volume")
raise Exception("Failed to merge KDK with Root Volume")
print("- Successfully merged KDK with Root Volume")
logging.info("- Successfully merged KDK with Root Volume")
# Restore IOHIDEventDriver CodeSignature
if save_hid_cs is True and Path(f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak").exists():
print("- Restoring IOHIDEventDriver CodeSignature")
logging.info("- Restoring IOHIDEventDriver CodeSignature")
if not cs_path.exists():
print(" - CodeSignature folder missing, creating")
logging.info(" - CodeSignature folder missing, creating")
utilities.elevated(["mkdir", "-p", cs_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
utilities.elevated(["cp", "-r", f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak", cs_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
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:
print("- Reverting to last signed APFS snapshot")
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)
if result.returncode != 0:
print("- Unable to revert root volume patches")
print("Reason for unpatch Failure:")
print(result.stdout.decode())
print("- Failed to revert snapshot via Apple's 'bless' command")
logging.info("- Unable to revert root volume patches")
logging.info("Reason for unpatch Failure:")
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
print("- Unpatching complete")
print("\nPlease reboot the machine for patches to take effect")
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:
print("- Patching complete")
print("\nPlease reboot the machine for patches to take effect")
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:
print("Note: Apple will require you to open System Preferences -> Security to allow the new kernel extensions to be loaded")
logging.info("Note: Apple will require you to open System Preferences -> Security to allow the new kernel extensions to be loaded")
self.constants.root_patcher_succeeded = True
if self.constants.gui_mode is False:
input("\nPress [ENTER] to continue")
def rebuild_kernel_collection(self):
print("- Rebuilding Kernel Cache (This may take some time)")
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
args = ["kmutil", "install"]
@@ -255,7 +298,7 @@ class PatchSysVolume:
if self.needs_kmutil_exemptions is True:
# When installing to '/Library/Extensions', following args skip kext consent
# prompt in System Preferences when SIP's disabled
print(" (You will get a prompt by System Preferences, ignore for now)")
logging.info(" (You will get a prompt by System Preferences, ignore for now)")
args.append("--no-authentication")
args.append("--no-authorization")
else:
@@ -271,11 +314,11 @@ class PatchSysVolume:
# - will return 31 on 'No binaries or codeless kexts were provided'
# - will return -10 if the volume is missing (ie. unmounted by another process)
if result.returncode != 0 or (self.constants.detected_os < os_data.os_data.catalina and "KernelCache ID" not in result.stdout.decode()):
print("- Unable to build new kernel cache")
print(f"\nReason for Patch Failure ({result.returncode}):")
print(result.stdout.decode())
print("")
print("\nPlease reboot the machine to avoid potential issues rerunning the patcher")
logging.info("- Unable to build new kernel cache")
logging.info(f"\nReason for Patch Failure ({result.returncode}):")
logging.info(result.stdout.decode())
logging.info("")
logging.info("\nPlease reboot the machine to avoid potential issues rerunning the patcher")
if self.constants.gui_mode is False:
input("Press [ENTER] to continue")
return False
@@ -284,27 +327,27 @@ class PatchSysVolume:
# Force rebuild the Auxiliary KC
result = utilities.elevated(["killall", "syspolicyd", "kernelmanagerd"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0:
print("- Unable to remove kernel extension policy files")
print(f"\nReason for Patch Failure ({result.returncode}):")
print(result.stdout.decode())
print("")
print("\nPlease reboot the machine to avoid potential issues rerunning the patcher")
logging.info("- Unable to remove kernel extension policy files")
logging.info(f"\nReason for Patch Failure ({result.returncode}):")
logging.info(result.stdout.decode())
logging.info("")
logging.info("\nPlease reboot the machine to avoid potential issues rerunning the patcher")
if self.constants.gui_mode is False:
input("Press [ENTER] to continue")
return False
for file in ["KextPolicy", "KextPolicy-shm", "KextPolicy-wal"]:
self.remove_file("/private/var/db/SystemPolicyConfiguration/", file)
self._remove_file("/private/var/db/SystemPolicyConfiguration/", file)
else:
# Install RSRHelper utility to handle desynced KCs
sys_patch_helpers.sys_patch_helpers(self.constants).install_rsr_repair_binary()
sys_patch_helpers.SysPatchHelpers(self.constants).install_rsr_repair_binary()
print("- Successfully built new kernel cache")
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:
print("- Creating new APFS snapshot")
logging.info("- Creating new APFS snapshot")
bless = utilities.elevated(
[
"bless",
@@ -313,46 +356,47 @@ class PatchSysVolume:
], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
if bless.returncode != 0:
print("- Unable to create new snapshot")
print("Reason for snapshot failure:")
print(bless.stdout.decode())
logging.info("- Unable to create new snapshot")
logging.info("Reason for snapshot failure:")
logging.info(bless.stdout.decode())
if "Can't use last-sealed-snapshot or create-snapshot on non system volume" in bless.stdout.decode():
print("- This is an APFS bug with Monterey and newer! Perform a clean installation to ensure your APFS volume is built correctly")
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):
print("- Unmounting Root Volume (Don't worry if this fails)")
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:
print("- 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:
print("- Rebuilding preboot kernel cache")
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():
print("- Found SkylightPlugins folder, removing old plugins")
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))
utilities.process_status(utilities.elevated(["mkdir", f"{self.mount_application_support}/SkyLightPlugins"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
else:
print("- Creating SkylightPlugins folder")
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).stdout.decode("utf-8").strip()
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"]:
print(f"- Removing non-Metal Enforcement Preference: {arg}")
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
@@ -360,7 +404,7 @@ class PatchSysVolume:
if self.constants.detected_os < os_data.os_data.big_sur:
return
print("- Cleaning Auxiliary Kernel Collection")
logging.info("- Cleaning Auxiliary Kernel Collection")
oclp_path = "/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist"
if Path(oclp_path).exists():
oclp_plist_data = plistlib.load(Path(oclp_path).open("rb"))
@@ -373,7 +417,7 @@ class PatchSysVolume:
for file in oclp_plist_data[key]["Install"][location]:
if not file.endswith(".kext"):
continue
self.remove_file("/Library/Extensions", file)
self._remove_file("/Library/Extensions", file)
# Handle situations where users migrated from older OSes with a lot of garbage in /L*/E*
# ex. Nvidia Web Drivers, NetUSB, dosdude1's patches, etc.
@@ -388,7 +432,7 @@ class PatchSysVolume:
for file in Path("/Library/Extensions").glob("*.kext"):
try:
if datetime.fromtimestamp(file.stat().st_mtime) < datetime(2021, 10, 1):
print(f" - Relocating {file.name} kext to {relocation_path}")
logging.info(f" - Relocating {file.name} kext to {relocation_path}")
if Path(relocation_path) / Path(file.name).exists():
utilities.elevated(["rm", "-Rf", relocation_path / Path(file.name)])
utilities.elevated(["mv", file, relocation_path])
@@ -397,17 +441,17 @@ class PatchSysVolume:
# ex. Symlinks pointing to symlinks pointing to dead files
pass
def write_patchset(self, patchset):
def _write_patchset(self, patchset):
destination_path = f"{self.mount_location}/System/Library/CoreServices"
file_name = "OpenCore-Legacy-Patcher.plist"
destination_path_file = f"{destination_path}/{file_name}"
if sys_patch_helpers.sys_patch_helpers(self.constants).generate_patchset_plist(patchset, file_name, self.kdk_path):
print("- Writing patchset information to Root Volume")
if sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, file_name, self.kdk_path):
logging.info("- Writing patchset information to Root Volume")
if Path(destination_path_file).exists():
utilities.process_status(utilities.elevated(["rm", destination_path_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
utilities.process_status(utilities.elevated(["cp", f"{self.constants.payload_path}/{file_name}", destination_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
def add_auxkc_support(self, install_file, source_folder_path, install_patch_directory, destination_folder_path):
def _add_auxkc_support(self, install_file, source_folder_path, install_patch_directory, destination_folder_path):
# In macOS Ventura, KDKs are required to build new Boot and System KCs
# However for some patch sets, we're able to use the Auxiliary KCs with '/Library/Extensions'
@@ -429,7 +473,7 @@ class PatchSysVolume:
updated_install_location = str(self.mount_location_data) + "/Library/Extensions"
print(f" - Adding AuxKC support to {install_file}")
logging.info(f" - Adding AuxKC support to {install_file}")
plist_path = Path(Path(source_folder_path) / Path(install_file) / Path("Contents/Info.plist"))
plist_data = plistlib.load((plist_path).open("rb"))
@@ -443,11 +487,11 @@ class PatchSysVolume:
plist_data["OSBundleRequired"] = "Auxiliary"
plistlib.dump(plist_data, plist_path.open("wb"))
self.check_kexts_needs_authentication(install_file)
self._check_kexts_needs_authentication(install_file)
return updated_install_location
def check_kexts_needs_authentication(self, kext_name):
def _check_kexts_needs_authentication(self, kext_name):
# Verify whether the user needs to authenticate in System Preferences
# Specifically under 'private/var/db/KernelManagement/AuxKC/CurrentAuxKC/com.apple.kcgen.instructions.plist'
# ["kextsToBuild"][i]:
@@ -466,38 +510,38 @@ class PatchSysVolume:
except PermissionError:
pass
print(f" - {kext_name} requires authentication in System Preferences")
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):
print(f"- Running patches for {self.model}")
def _patch_root_vol(self):
logging.info(f"- Running patches for {self.model}")
if self.patch_set_dictionary != {}:
self.execute_patchset(self.patch_set_dictionary)
self._execute_patchset(self.patch_set_dictionary)
else:
self.execute_patchset(sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).generate_patchset(self.hardware_details))
self._execute_patchset(sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).generate_patchset(self.hardware_details))
if self.constants.wxpython_variant is True and self.constants.detected_os >= os_data.os_data.big_sur:
sys_patch_auto.AutomaticSysPatch(self.constants).install_auto_patcher_launch_agent()
self.rebuild_snapshot()
self._rebuild_snapshot()
def execute_patchset(self, required_patches):
def _execute_patchset(self, required_patches):
source_files_path = str(self.constants.payload_local_binaries_root_path)
self.preflight_checks(required_patches, source_files_path)
self._preflight_checks(required_patches, source_files_path)
for patch in required_patches:
print("- Installing Patchset: " + patch)
logging.info("- Installing Patchset: " + patch)
if "Remove" in required_patches[patch]:
for remove_patch_directory in required_patches[patch]["Remove"]:
print("- Remove Files at: " + remove_patch_directory)
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"]:
if method_install in required_patches[patch]:
for install_patch_directory in list(required_patches[patch][method_install]):
print(f"- Handling Installs in: {install_patch_directory}")
logging.info(f"- Handling Installs in: {install_patch_directory}")
for install_file in list(required_patches[patch][method_install][install_patch_directory]):
source_folder_path = source_files_path + "/" + required_patches[patch][method_install][install_patch_directory][install_file] + install_patch_directory
if method_install == "Install":
@@ -505,10 +549,10 @@ class PatchSysVolume:
else:
if install_patch_directory == "/Library/Extensions":
self.needs_kmutil_exemptions = True
self.check_kexts_needs_authentication(install_file)
self._check_kexts_needs_authentication(install_file)
destination_folder_path = str(self.mount_location_data) + install_patch_directory
updated_destination_folder_path = self.add_auxkc_support(install_file, source_folder_path, install_patch_directory, destination_folder_path)
updated_destination_folder_path = self._add_auxkc_support(install_file, source_folder_path, install_patch_directory, destination_folder_path)
if destination_folder_path != updated_destination_folder_path:
# Update required_patches to reflect the new destination folder path
@@ -519,37 +563,37 @@ 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"]:
# Some processes need sudo, however we cannot directly call sudo in some scenarios
# Instead, call elevated funtion if string's boolean is True
if required_patches[patch]["Processes"][process] is True:
print(f"- Running Process as Root:\n{process}")
logging.info(f"- Running Process as Root:\n{process}")
utilities.process_status(utilities.elevated(process.split(" "), stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
else:
print(f"- Running Process:\n{process}")
logging.info(f"- Running Process:\n{process}")
utilities.process_status(subprocess.run(process, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True))
if any(x in required_patches for x in ["AMD Legacy GCN", "AMD Legacy Polaris", "AMD Legacy Vega"]):
sys_patch_helpers.sys_patch_helpers(self.constants).disable_window_server_caching()
sys_patch_helpers.SysPatchHelpers(self.constants).disable_window_server_caching()
if any(x in required_patches for x in ["Intel Ivy Bridge", "Intel Haswell"]):
sys_patch_helpers.sys_patch_helpers(self.constants).remove_news_widgets()
self.write_patchset(required_patches)
sys_patch_helpers.SysPatchHelpers(self.constants).remove_news_widgets()
self._write_patchset(required_patches)
def preflight_checks(self, required_patches, source_files_path):
print("- Running Preflight Checks before patching")
def _preflight_checks(self, required_patches, source_files_path):
logging.info("- Running Preflight Checks before patching")
# Make sure old SkyLight plugins aren't being used
self.clean_skylight_plugins()
self._clean_skylight_plugins()
# Make sure non-Metal Enforcement preferences are not present
self.delete_nonmetal_enforcement()
self._delete_nonmetal_enforcement()
# Make sure we clean old kexts in /L*/E* that are not in the patchset
self.clean_auxiliary_kc()
self._clean_auxiliary_kc()
# Make sure SNB kexts are compatible with the host
if "Intel Sandy Bridge" in required_patches:
sys_patch_helpers.sys_patch_helpers(self.constants).snb_board_id_patch(source_files_path)
sys_patch_helpers.SysPatchHelpers(self.constants).snb_board_id_patch(source_files_path)
for patch in required_patches:
# Check if all files are present
@@ -565,53 +609,53 @@ 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)
print("- Finished Preflight, starting patching")
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)
if not Path(destination_folder).exists():
print(f" - Skipping {file_name}, cannot locate {source_folder}")
logging.info(f" - Skipping {file_name}, cannot locate {source_folder}")
return
if file_name_str.endswith(".framework"):
# merge with rsync
print(f" - Installing: {file_name}")
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():
print(f" - Found existing {file_name}, overwriting...")
logging.info(f" - Found existing {file_name}, overwriting...")
utilities.process_status(utilities.elevated(["rm", "-R", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
else:
print(f" - Installing: {file_name}")
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():
print(f" - Found existing {file_name}, overwriting...")
logging.info(f" - Found existing {file_name}, overwriting...")
utilities.process_status(utilities.elevated(["rm", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
else:
print(f" - Installing: {file_name}")
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():
print(f" - Removing: {file_name}")
logging.info(f" - Removing: {file_name}")
if Path(destination_folder + "/" + file_name).is_dir():
utilities.process_status(utilities.elevated(["rm", "-R", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
else:
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():
@@ -622,89 +666,65 @@ 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():
print("- Found local Apple Binaries")
if self.constants.gui_mode is False:
patch_input = input("Would you like to redownload?(y/n): ")
if patch_input in {"y", "Y", "yes", "Yes"}:
shutil.rmtree(Path(self.constants.payload_local_binaries_root_path))
output = self.download_files()
else:
output = True
else:
output = self.download_files()
else:
output = self.download_files()
return output
logging.info("- Local PatcherSupportPkg resources available, continuing...")
return True
def download_files(self):
if self.constants.cli_mode is True:
download_result, link = sys_patch_download.grab_patcher_support_pkg(self.constants).download_files()
else:
download_result = True
link = sys_patch_download.grab_patcher_support_pkg(self.constants).generate_pkg_link()
if download_result and self.constants.payload_local_binaries_root_path_zip.exists():
print("- Unzipping binaries...")
if Path(self.constants.payload_local_binaries_root_path_zip).exists():
logging.info("- Local PatcherSupportPkg resources available, unzipping...")
logging.info("- Unzipping binaries...")
utilities.process_status(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))
print("- Binaries downloaded to:")
print(self.constants.payload_path)
return self.constants.payload_local_binaries_root_path
else:
if self.constants.gui_mode is True:
print("- Download failed, please verify the below link work:")
print(link)
print("\nIf you continue to have issues, try using the Offline builds")
print("located on Github next to the other builds")
else:
input("\nPress enter to continue")
return None
return True
logging.info("- PatcherSupportPkg resources missing, Patcher likely corrupted!!!")
return False
# Entry Function
def start_patch(self):
print("- Starting Patch Process")
print(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)
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.DetectRootPatch(self.computer.real_model, self.constants).generate_patchset(self.hardware_details)
if self.patch_set_dictionary == {}:
change_menu = None
print("- No Root Patches required for your machine!")
logging.info("- No Root Patches required for your machine!")
if self.constants.gui_mode is False:
input("\nPress [ENTER] to return to the main menu: ")
elif self.constants.gui_mode is False:
change_menu = input("Would you like to continue with Root Volume Patching?(y/n): ")
else:
change_menu = "y"
print("- Continuing root patching")
logging.info("- Continuing root patching")
if change_menu in ["y", "Y"]:
print("- 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:
print("- Patcher is capable of patching")
if self.check_files():
if self.mount_root_vol() is True:
self.patch_root_vol()
logging.info("- Verifying whether Root Patching possible")
if sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).verify_patch_allowed(print_errors=not self.constants.wxpython_variant) is True:
logging.info("- Patcher is capable of patching")
if self._check_files():
if self._mount_root_vol() is True:
self._patch_root_vol()
if self.constants.gui_mode is False:
input("\nPress [ENTER] to return to the main menu")
else:
print("- Recommend rebooting the machine and trying to patch again")
logging.info("- Recommend rebooting the machine and trying to patch again")
if self.constants.gui_mode is False:
input("- Press [ENTER] to exit: ")
elif self.constants.gui_mode is False:
input("\nPress [ENTER] to return to the main menu: ")
else:
print("- Returning to main menu")
logging.info("- Returning to main menu")
def start_unpatch(self):
print("- 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()
logging.info("- Starting Unpatch Process")
if sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).verify_patch_allowed(print_errors=True) is True:
if self._mount_root_vol() is True:
self._unpatch_root_vol()
if self.constants.gui_mode is False:
input("\nPress [ENTER] to return to the main menu")
else:
print("- Recommend rebooting the machine and trying to patch again")
logging.info("- Recommend rebooting the machine and trying to patch again")
if self.constants.gui_mode is False:
input("- Press [ENTER] to exit: ")
elif self.constants.gui_mode is False:

View File

@@ -1,60 +1,74 @@
# 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
from resources import utilities, updates, global_settings
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:
def __init__(self, constants):
self.constants = constants
class AutomaticSysPatch:
"""
Library of functions for launch agent, including automatic patching
"""
def __init__(self, global_constants: constants.Constants):
self.constants: constants.Constants = global_constants
def start_auto_patch(self):
print("- Starting Automatic Patching")
"""
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:
print("- Auto Patch option is not supported on TUI, please use GUI")
logging.info("- Auto Patch option is not supported on TUI, please use GUI")
return
if utilities.check_seal() is True:
print("- Detected Snapshot seal intact, detecting patches")
patches = sys_patch_detect.detect_root_patch(self.constants.computer.real_model, self.constants).detect_patch_set()
logging.info("- Detected Snapshot seal intact, detecting patches")
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:
print("- Detected applicable patches, determining whether possible to patch")
logging.info("- Detected applicable patches, determining whether possible to patch")
if patches["Validation: Patching Possible"] is False:
print("- Cannot run patching")
logging.info("- Cannot run patching")
return
print("- Determined patching is possible, checking for OCLP updates")
logging.info("- Determined patching is possible, checking for OCLP updates")
patch_string = ""
for patch in patches:
if patches[patch] is True and not patch.startswith("Settings") and not patch.startswith("Validation"):
patch_string += f"- {patch}\n"
# Check for updates
dict = updates.check_binary_updates(self.constants).check_binary_updates()
dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates()
if not dict:
print("- No new binaries found on Github, proceeding with patching")
logging.info("- No new binaries found on Github, proceeding with patching")
if self.constants.launcher_script is None:
args_string = f"'{self.constants.launcher_binary}' --gui_patch"
else:
args_string = f"{self.constants.launcher_binary} {self.constants.launcher_script} --gui_patch"
warning_str = ""
if utilities.verify_network_connection("https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest") is False:
if network_handler.NetworkUtilities("https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest").verify_network_connection() is False:
warning_str = f"""\n\nWARNING: We're unable to verify whether there are any new releases of OpenCore Legacy Patcher on Github. Be aware that you may be using an outdated version for this OS. If you're unsure, verify on Github that OpenCore Legacy Patcher {self.constants.patcher_version} is the latest official release"""
args = [
@@ -87,7 +101,7 @@ class AutomaticSysPatch:
for key in dict:
version = dict[key]["Version"]
github_link = dict[key]["Github Link"]
print(f"- Found new version: {version}")
logging.info(f"- Found new version: {version}")
# launch osascript to ask user if they want to apply the update
# if yes, open the link in the default browser
@@ -108,30 +122,39 @@ class AutomaticSysPatch:
return
else:
print("- No patches detected")
logging.info("- No patches detected")
else:
print("- Detected Snapshot seal not intact, skipping")
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):
print("- Checking booted vs installed OCLP Build")
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:
print("- Booted version not found")
return False
logging.info("- Booted version not found")
return True
if self.constants.computer.oclp_version == self.constants.patcher_version:
print("- Versions match")
return False
logging.info("- Versions match")
return True
# Check if installed version is newer than booted version
if updates.check_binary_updates(self.constants).check_if_build_newer(
if updates.CheckBinaryUpdates(self.constants)._check_if_build_newer(
self.constants.computer.oclp_version.split("."), self.constants.patcher_version.split(".")
) is True:
print("- Installed version is newer than booted version")
return False
logging.info("- Installed version is newer than booted version")
return True
args = [
"osascript",
@@ -145,45 +168,52 @@ class AutomaticSysPatch:
stderr=subprocess.STDOUT
)
if output.returncode == 0:
print("- Launching GUI's Build/Install menu")
logging.info("- Launching GUI's Build/Install menu")
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
print("- Determining if macOS drive matches boot drive")
should_notify = global_settings.global_settings().read_property("AutoPatch_Notify_Mismatched_Disks")
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:
print("- Skipping due to user preference")
logging.info("- Skipping due to user preference")
return
if self.constants.host_is_hackintosh is True:
print("- Skipping due to hackintosh")
logging.info("- Skipping due to hackintosh")
return
if not self.constants.booted_oc_disk:
print("- Failed to find disk OpenCore launched from")
logging.info("- Failed to find disk OpenCore launched from")
return
root_disk = self.constants.booted_oc_disk.strip("disk")
root_disk = "disk" + root_disk.split("s")[0]
print(f" - Boot Drive: {self.constants.booted_oc_disk} ({root_disk})")
logging.info(f" - Boot Drive: {self.constants.booted_oc_disk} ({root_disk})")
macOS_disk = utilities.get_disk_path()
print(f" - macOS Drive: {macOS_disk}")
logging.info(f" - macOS Drive: {macOS_disk}")
physical_stores = utilities.find_apfs_physical_volume(macOS_disk)
print(f" - APFS Physical Stores: {physical_stores}")
logging.info(f" - APFS Physical Stores: {physical_stores}")
disk_match = False
for disk in physical_stores:
if root_disk in disk:
print(f"- Boot drive matches macOS drive ({disk})")
logging.info(f"- Boot drive matches macOS drive ({disk})")
disk_match = True
break
@@ -191,15 +221,15 @@ class AutomaticSysPatch:
return
# Check if OpenCore is on a USB drive
print("- Boot Drive does not match macOS drive, checking if OpenCore is on a USB drive")
logging.info("- Boot Drive does not match macOS drive, checking if OpenCore is on a USB drive")
disk_info = plistlib.loads(subprocess.run(["diskutil", "info", "-plist", root_disk], stdout=subprocess.PIPE).stdout)
try:
if disk_info["Ejectable"] is False:
print("- Boot Disk is not removable, skipping prompt")
logging.info("- Boot Disk is not removable, skipping prompt")
return
print("- Boot Disk is ejectable, prompting user to install to internal")
logging.info("- Boot Disk is ejectable, prompting user to install to internal")
args = [
"osascript",
@@ -213,65 +243,72 @@ class AutomaticSysPatch:
stderr=subprocess.STDOUT
)
if output.returncode == 0:
print("- Launching GUI's Build/Install menu")
logging.info("- Launching GUI's Build/Install menu")
self.constants.start_build_install = True
gui_main.wx_python_gui(self.constants).main_menu(None)
except KeyError:
print("- Unable to determine if boot disk is removable, skipping prompt")
logging.info("- Unable to determine if boot disk is removable, skipping prompt")
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:
print("- Skipping Auto Patcher Launch Agent, not supported when running from source")
logging.info("- Skipping Auto Patcher Launch Agent, not supported when running from source")
return
if self.constants.launcher_binary.startswith("/Library/Application Support/Dortania/"):
print("- Skipping Auto Patcher Launch Agent, already installed")
logging.info("- Skipping Auto Patcher Launch Agent, already installed")
return
# Verify our binary isn't located in '/Library/Application Support/Dortania/'
# As we'd simply be duplicating ourselves
print("- Installing Auto Patcher Launch Agent")
logging.info("- Installing Auto Patcher Launch Agent")
if not Path("Library/Application Support/Dortania").exists():
print("- Creating /Library/Application Support/Dortania/")
logging.info("- Creating /Library/Application Support/Dortania/")
utilities.process_status(utilities.elevated(["mkdir", "-p", "/Library/Application Support/Dortania"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
print("- Copying OpenCore Patcher to /Library/Application Support/Dortania/")
logging.info("- Copying OpenCore Patcher to /Library/Application Support/Dortania/")
if Path("/Library/Application Support/Dortania/OpenCore-Patcher.app").exists():
print("- Deleting existing OpenCore-Patcher")
logging.info("- Deleting existing OpenCore-Patcher")
utilities.process_status(utilities.elevated(["rm", "-R", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
# Strip everything after OpenCore-Patcher.app
path = str(self.constants.launcher_binary).split("/Contents/MacOS/OpenCore-Patcher")[0]
print(f"- Copying {path} to /Library/Application Support/Dortania/")
logging.info(f"- Copying {path} to /Library/Application Support/Dortania/")
utilities.process_status(utilities.elevated(["ditto", path, "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
if not Path("/Library/Application Support/Dortania/OpenCore-Patcher.app").exists():
# Sometimes the binary the user launches may have a suffix (ie. OpenCore-Patcher 3.app)
# We'll want to rename it to OpenCore-Patcher.app
path = path.split("/")[-1]
print(f"- Renaming {path} to OpenCore-Patcher.app")
logging.info(f"- Renaming {path} to OpenCore-Patcher.app")
utilities.process_status(utilities.elevated(["mv", f"/Library/Application Support/Dortania/{path}", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
subprocess.run(["xattr", "-cr", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# Copy over our launch agent
print("- Copying auto-patch.plist Launch Agent to /Library/LaunchAgents/")
logging.info("- Copying auto-patch.plist Launch Agent to /Library/LaunchAgents/")
if Path("/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist").exists():
print("- Deleting existing auto-patch.plist")
logging.info("- Deleting existing auto-patch.plist")
utilities.process_status(utilities.elevated(["rm", "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
if not Path("/Library/LaunchAgents/").exists():
print("- Creating /Library/LaunchAgents/")
logging.info("- Creating /Library/LaunchAgents/")
utilities.process_status(utilities.elevated(["mkdir", "-p", "/Library/LaunchAgents/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
utilities.process_status(utilities.elevated(["cp", self.constants.auto_patch_launch_agent_path, "/Library/LaunchAgents/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
# Set the permissions on the com.dortania.opencore-legacy-patcher.auto-patch.plist
print("- Setting permissions on auto-patch.plist")
logging.info("- Setting permissions on auto-patch.plist")
utilities.process_status(utilities.elevated(["chmod", "644", "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
utilities.process_status(utilities.elevated(["chown", "root:wheel", "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
@@ -279,5 +316,5 @@ class AutomaticSysPatch:
# Simply an easy way for users to notice the app
# If there's already an alias or exiting app, skip
if not Path("/Applications/OpenCore-Patcher.app").exists():
print("- Making app alias")
logging.info("- Making app alias")
utilities.process_status(utilities.elevated(["ln", "-s", "/Library/Application Support/Dortania/OpenCore-Patcher.app", "/Applications/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))

View File

@@ -3,18 +3,39 @@
# Used when supplying data to sys_patch.py
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
from resources import constants, device_probe, utilities, amfi_detect
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 plistlib
import logging
import py_sip_xnu
from pathlib import Path
import plistlib
class detect_root_patch:
def __init__(self, model, versions):
self.model = model
self.constants: constants.Constants() = versions
from resources import (
constants,
device_probe,
utilities,
amfi_detect,
network_handler,
kdk_handler
)
from data import (
model_array,
os_data,
sip_data,
sys_patch_dict,
smbios_data,
cpu_data
)
class DetectRootPatch:
"""
Library for querying root volume patches applicable for booted system
"""
def __init__(self, model: str, global_constants: constants.Constants):
self.model: str = model
self.constants: constants.Constants = global_constants
self.computer = self.constants.computer
# GPU Patch Detection
@@ -62,19 +83,24 @@ 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):
if gpu.class_code and gpu.class_code != 0xFFFFFFFF:
print(f"- Found GPU ({i}): {utilities.friendly_hex(gpu.vendor_id)}:{utilities.friendly_hex(gpu.device_id)}")
logging.info(f"- Found GPU ({i}): {utilities.friendly_hex(gpu.vendor_id)}:{utilities.friendly_hex(gpu.device_id)}")
if gpu.arch in [device_probe.NVIDIA.Archs.Tesla] and self.constants.force_nv_web is False:
if self.constants.detected_os > non_metal_os:
self.nvidia_tesla = True
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:
@@ -94,6 +120,8 @@ class detect_root_patch:
self.supports_metal = True
if self.constants.detected_os >= os_data.os_data.ventura:
self.amfi_must_disable = True
if (self.constants.detected_os == os_data.os_data.ventura and self.constants.detected_os_minor >= 4) or self.constants.detected_os > os_data.os_data.ventura:
self.amfi_shim_bins = True
elif gpu.arch in [
device_probe.NVIDIA.Archs.Fermi,
device_probe.NVIDIA.Archs.Kepler,
@@ -164,7 +192,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:
@@ -172,18 +200,22 @@ 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:
self.ivy_gpu = True
if self.constants.detected_os >= os_data.os_data.ventura:
self.amfi_must_disable = True
if (self.constants.detected_os == os_data.os_data.ventura and self.constants.detected_os_minor >= 4) or self.constants.detected_os > os_data.os_data.ventura:
self.amfi_shim_bins = True
self.supports_metal = True
elif gpu.arch == device_probe.Intel.Archs.Haswell:
if self.constants.detected_os > os_data.os_data.monterey:
self.haswell_gpu = True
self.amfi_must_disable = True
if (self.constants.detected_os == os_data.os_data.ventura and self.constants.detected_os_minor >= 4) or self.constants.detected_os > os_data.os_data.ventura:
self.amfi_shim_bins = True
self.supports_metal = True
elif gpu.arch == device_probe.Intel.Archs.Broadwell:
if self.constants.detected_os > os_data.os_data.monterey:
@@ -219,17 +251,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
@@ -271,7 +307,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:
@@ -280,25 +320,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:
@@ -308,8 +368,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:
@@ -320,8 +389,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:
@@ -332,15 +410,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):
if sys_patch_helpers.sys_patch_helpers(self.constants).determine_kdk_present() is None:
return False
return True
def check_sip(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):
"""
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
@@ -366,7 +466,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
@@ -400,10 +508,19 @@ class detect_root_patch:
return False
def detect_patch_set(self):
self.has_network = utilities.verify_network_connection()
if self.check_uhci_ohci() is True:
# 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:
self.legacy_uhci_ohci = True
self.requires_root_kc = True
@@ -436,12 +553,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,
@@ -468,10 +585,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(),
f"Validation: SIP is enabled (Required: {self.check_sip()[2]} or higher)": self.sip_enabled,
"Validation: Unpatching Possible": self._verify_unpatch_allowed(),
f"Validation: SIP is enabled (Required: {self._check_sip()[2]} or higher)": self.sip_enabled,
f"Validation: Currently Booted SIP: ({hex(py_sip_xnu.SipXnu().get_sip_status().value)})": self.sip_enabled,
"Validation: SecureBootModel is enabled": self.sbm_enabled,
f"Validation: {'AMFI' if self.constants.host_is_hackintosh is True or self.get_amfi_level_needed() > 2 else 'Library Validation'} is enabled": self.amfi_enabled if self.amfi_must_disable is True else False,
f"Validation: {'AMFI' if self.constants.host_is_hackintosh is True or self._get_amfi_level_needed() > 2 else 'Library Validation'} is enabled": self.amfi_enabled if self.amfi_must_disable is True else False,
"Validation: FileVault is enabled": self.fv_enabled,
"Validation: System is dosdude1 patched": self.dosdude_patched,
"Validation: WhateverGreen.kext missing": self.missing_whatever_green if self.nvidia_web is True else False,
@@ -483,77 +601,100 @@ class detect_root_patch:
return self.root_patch_dict
def get_amfi_level_needed(self):
if self.amfi_must_disable is True:
if self.constants.detected_os > os_data.os_data.catalina:
if self.constants.detected_os >= os_data.os_data.ventura:
if self.amfi_shim_bins is True:
# Currently we require AMFI outright disabled
# in Ventura to work with shim'd binaries
return 3
return 1
return 0
def verify_patch_allowed(self, print_errors=False):
sip_dict = self.check_sip()
def _get_amfi_level_needed(self):
"""
Query the AMFI level needed for the patcher to work
Returns:
int: AMFI level needed
"""
if self.amfi_must_disable is False:
return amfi_detect.AmfiConfigDetectLevel.NO_CHECK
if self.constants.detected_os < os_data.os_data.big_sur:
return amfi_detect.AmfiConfigDetectLevel.NO_CHECK
if self.constants.detected_os >= os_data.os_data.ventura:
if self.amfi_shim_bins is True:
# Currently we require AMFI outright disabled
# in Ventura to work with shim'd binaries
return amfi_detect.AmfiConfigDetectLevel.ALLOW_ALL
return amfi_detect.AmfiConfigDetectLevel.LIBRARY_VALIDATION
def verify_patch_allowed(self, print_errors: bool = False):
"""
Validate that the patcher can be run
Parameters:
print_errors (bool): Print errors to console
Returns:
bool: True if patching is allowed, False otherwise
"""
sip_dict = self._check_sip()
sip = sip_dict[0]
sip_value = sip_dict[1]
self.sip_enabled, self.sbm_enabled, self.fv_enabled, self.dosdude_patched = utilities.patching_status(sip, self.constants.detected_os)
self.amfi_enabled = not amfi_detect.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()
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:
print("\nCannot patch! Please disable System Integrity Protection (SIP).")
print("Disable SIP in Patcher Settings and Rebuild OpenCore\n")
print("Ensure the following bits are set for csr-active-config:")
print("\n".join(sip))
print(sip_value)
logging.info("\nCannot patch! Please disable System Integrity Protection (SIP).")
logging.info("Disable SIP in Patcher Settings and Rebuild OpenCore\n")
logging.info("Ensure the following bits are set for csr-active-config:")
logging.info("\n".join(sip))
logging.info(sip_value)
if self.sbm_enabled is True:
print("\nCannot patch! Please disable Apple Secure Boot.")
print("Disable SecureBootModel in Patcher Settings and Rebuild OpenCore")
print("For Hackintoshes, set SecureBootModel to Disabled")
logging.info("\nCannot patch! Please disable Apple Secure Boot.")
logging.info("Disable SecureBootModel in Patcher Settings and Rebuild OpenCore")
logging.info("For Hackintoshes, set SecureBootModel to Disabled")
if self.fv_enabled is True:
print("\nCannot patch! Please disable FileVault.")
print("For OCLP Macs, please rebuild your config with 0.2.5 or newer")
print("For others, Go to System Preferences -> Security and disable FileVault")
logging.info("\nCannot patch! Please disable FileVault.")
logging.info("For OCLP Macs, please rebuild your config with 0.2.5 or newer")
logging.info("For others, Go to System Preferences -> Security and disable FileVault")
if self.amfi_enabled is True and self.amfi_must_disable is True:
print("\nCannot patch! Please disable AMFI.")
print("For Hackintoshes, please add amfi_get_out_of_my_way=1 to boot-args")
logging.info("\nCannot patch! Please disable AMFI.")
logging.info("For Hackintoshes, please add amfi_get_out_of_my_way=1 to boot-args")
if self.dosdude_patched is True:
print("\nCannot patch! Detected machine has already been patched by another patcher")
print("Please ensure your install is either clean or patched with OpenCore Legacy Patcher")
logging.info("\nCannot patch! Detected machine has already been patched by another patcher")
logging.info("Please ensure your install is either clean or patched with OpenCore Legacy Patcher")
if self.nvidia_web is True:
if self.missing_nv_web_opengl is True:
print("\nCannot patch! Force OpenGL property missing")
print("Please ensure ngfxgl=1 is set in boot-args")
logging.info("\nCannot patch! Force OpenGL property missing")
logging.info("Please ensure ngfxgl=1 is set in boot-args")
if self.missing_nv_compat is True:
print("\nCannot patch! Force Nvidia compatibility property missing")
print("Please ensure ngfxcompat=1 is set in boot-args")
logging.info("\nCannot patch! Force Nvidia compatibility property missing")
logging.info("Please ensure ngfxcompat=1 is set in boot-args")
if self.missing_nv_web_nvram is True:
print("\nCannot patch! nvda_drv(_vrl) variable missing")
print("Please ensure nvda_drv_vrl=1 is set in boot-args")
logging.info("\nCannot patch! nvda_drv(_vrl) variable missing")
logging.info("Please ensure nvda_drv_vrl=1 is set in boot-args")
if self.missing_whatever_green is True:
print("\nCannot patch! WhateverGreen.kext missing")
print("Please ensure WhateverGreen.kext is installed")
logging.info("\nCannot patch! WhateverGreen.kext missing")
logging.info("Please ensure WhateverGreen.kext is installed")
if (not self.has_network) if (self.requires_root_kc and self.missing_kdk and self.constants.detected_os >= os_data.os_data.ventura.value) else False:
print("\nCannot patch! Network Connection Required")
print("Please ensure you have an active internet connection")
logging.info("\nCannot patch! Network Connection Required")
logging.info("Please ensure you have an active internet connection")
if any(
[
@@ -574,47 +715,87 @@ class detect_root_patch:
]
):
return False
else:
return True
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 = {}
return True
def _verify_unpatch_allowed(self):
"""
Validate that the unpatcher can be run
Preconditions:
Must be called after verify_patch_allowed()
Returns:
bool: True if unpatching is allowed, False otherwise
"""
return not self.sip_enabled
def generate_patchset(self, hardware_details: 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).patchset_dict
required_patches: dict = {}
utilities.cls()
print("- The following patches will be applied:")
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({"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"]})
# Patchset breaks Display Profiles, don't install if primary GPU is AMD
if self.constants.computer.real_model not in ["Macmini5,2", "iMac12,1", "iMac12,2"]:
required_patches.update({"Non-Metal ColorSync Workaround": all_hardware_patchset["Graphics"]["Non-Metal ColorSync Workaround"]})
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({"Metal 3802 Common Extended": all_hardware_patchset["Graphics"]["Metal 3802 Common Extended"]})
required_patches.update({"Catalina GVA": all_hardware_patchset["Graphics"]["Catalina GVA"]})
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
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({"Metal 3802 Common Extended": all_hardware_patchset["Graphics"]["Metal 3802 Common Extended"]})
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"]})
@@ -622,9 +803,10 @@ 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"]})
required_patches.update({"Metal 3802 Common Extended": all_hardware_patchset["Graphics"]["Metal 3802 Common Extended"]})
required_patches.update({"Catalina GVA": all_hardware_patchset["Graphics"]["Catalina GVA"]})
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
required_patches.update({"Big Sur OpenCL": all_hardware_patchset["Graphics"]["Big Sur OpenCL"]})
@@ -636,11 +818,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"]})
@@ -651,16 +835,18 @@ 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"]})
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
if hardware_details["Graphics: AMD Legacy GCN"] is True:
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"]})
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"]})
@@ -668,20 +854,28 @@ 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"]})
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"]})
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"]})
@@ -699,8 +893,8 @@ class detect_root_patch:
del(required_patches[patch_name])
else:
if required_patches[patch_name]["Display Name"]:
print(f" - {required_patches[patch_name]['Display Name']}")
logging.info(f" - {required_patches[patch_name]['Display Name']}")
else:
print(" - No patch sets found for booted model")
logging.info(" - No patch sets found for booted model")
return required_patches

View File

@@ -1,32 +0,0 @@
# Download PatcherSupportPkg for usage with Root Patching
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
from resources import utilities
from pathlib import Path
import shutil
class grab_patcher_support_pkg:
def __init__(self, constants):
self.constants = constants
def generate_pkg_link(self):
link = f"{self.constants.url_patcher_support_pkg}{self.constants.patcher_support_pkg_version}/Universal-Binaries.zip"
return link
def download_files(self):
link = self.generate_pkg_link()
if Path(self.constants.payload_local_binaries_root_path).exists():
print("- Removing old Root Patcher Payload folder")
# Delete folder
shutil.rmtree(self.constants.payload_local_binaries_root_path)
download_result = None
if Path(self.constants.payload_local_binaries_root_path_zip).exists():
print(f"- Found local Universal-Binaries.zip, skipping download")
download_result = True
else:
print(f"- No local version found, downloading...")
download_result = utilities.download_file(link, self.constants.payload_local_binaries_root_path_zip)
return download_result, link

View File

@@ -1,57 +1,86 @@
# Additional support functions for sys_patch.py
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
# Copyright (C) 2020-2023, Dhinak G, Mykola Grymalyuk
import subprocess
import tempfile
from data import os_data
from resources import generate_smbios, utilities
from pathlib import Path
from datetime import datetime
import plistlib
import os
import logging
import subprocess
from pathlib import Path
from datetime import datetime
from resources import constants, bplist
class sys_patch_helpers:
def __init__(self, constants):
self.constants = constants
from data import os_data
from resources import bplist, constants, generate_smbios, utilities
def snb_board_id_patch(self, source_files_path):
# AppleIntelSNBGraphicsFB hard codes the supported Board IDs for Sandy Bridge iGPUs
# Because of this, the kext errors out on unsupported systems
# This function simply patches in a supported Board ID, using 'determine_best_board_id_for_sandy()'
# to supplement the ideal Board ID
class SysPatchHelpers:
"""
Library of helper functions for sys_patch.py and related libraries
"""
def __init__(self, global_constants: constants.Constants):
self.constants: constants.Constants = global_constants
def snb_board_id_patch(self, source_files_path: str):
"""
Patch AppleIntelSNBGraphicsFB.kext to support unsupported Board IDs
AppleIntelSNBGraphicsFB hard codes the supported Board IDs for Sandy Bridge iGPUs
Because of this, the kext errors out on unsupported systems
This function simply patches in a supported Board ID, using 'determine_best_board_id_for_sandy()'
to supplement the ideal Board ID
Parameters:
source_files_path (str): Path to the source files
"""
source_files_path = str(source_files_path)
if self.constants.computer.reported_board_id not in self.constants.sandy_board_id_stock:
print(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)
print(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):
print(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:
print(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,94 +96,36 @@ 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 install_kdk(self):
if not self.constants.kdk_download_path.exists():
return
print(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:
print("- Failed to install KDK:")
print(result.stdout.decode('utf-8'))
if result.stderr:
print(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)
print("- 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)
print(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
# 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
print("- Disabling WindowServer Caching")
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"])
# Disable writing to WindowServer folder
@@ -165,38 +136,49 @@ 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
print("- Parsing Notification Centre Widgets")
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()
if not file_path.exists():
print(" - Defaults file not found, skipping")
logging.info(" - Defaults file not found, skipping")
return
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
print(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)
@@ -204,21 +186,28 @@ 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
print("- Installing Kernel Collection syncing utility")
logging.info("- Installing Kernel Collection syncing utility")
result = utilities.elevated([self.constants.rsrrepair_userspace_path, "--install"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0:
print(f" - Failed to install RSRRepair: {result.stdout.decode()}")
logging.info(f" - Failed to install RSRRepair: {result.stdout.decode()}")

View File

@@ -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)

View File

@@ -3,31 +3,33 @@
# Call check_binary_updates() to determine if any updates are available
# Returns dict with Link and Version of the latest binary update if available
import requests
import logging
from resources import network_handler, constants
REPO_LATEST_RELEASE_URL: str = "https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest"
class check_binary_updates:
def __init__(self, constants):
self.constants = constants
self.binary_version = self.constants.patcher_version
self.binary_version_array = self.binary_version.split(".")
self.binary_version_array = [int(x) for x in self.binary_version_array]
self.binary_url = "https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest"
class CheckBinaryUpdates:
def __init__(self, global_constants: constants.Constants) -> None:
self.constants: constants.Constants = global_constants
self.available_binaries = {}
self.binary_version = self.constants.patcher_version
self.binary_version_array = [int(x) for x in self.binary_version.split(".")]
def verify_network_connection(self, url):
try:
response = requests.head(url, timeout=5)
if response:
return True
except (requests.exceptions.Timeout,
requests.exceptions.TooManyRedirects,
requests.exceptions.ConnectionError,
requests.exceptions.HTTPError):
return False
return False
def check_if_build_newer(self, remote_version=None, local_version=None):
def _check_if_build_newer(self, remote_version: list = None, local_version: list = None) -> bool:
"""
Check if the remote version is newer than the local version
Parameters:
remote_version (list): Remote version to compare against
local_version (list): Local version to compare against
Returns:
bool: True if remote version is newer, False if not
"""
if remote_version is None:
remote_version = self.remote_version_array
if local_version is None:
@@ -47,13 +49,32 @@ class check_binary_updates:
return False
def determine_local_build_type(self):
def _determine_local_build_type(self) -> str:
"""
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) -> 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:
@@ -61,45 +82,43 @@ class check_binary_updates:
else:
return "Unknown"
def check_binary_updates(self):
# print("- Checking for updates...")
if self.verify_network_connection(self.binary_url):
# print("- Network connection functional")
response = requests.get(self.binary_url)
data_set = response.json()
# print("- Retrieved latest version data")
self.remote_version = data_set["tag_name"]
# print(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:
# print("- Remote version is newer")
for asset in data_set["assets"]:
print(f"- Found asset: {asset['name']}")
if self.determine_remote_type(asset["name"]) == self.determine_local_build_type():
# print(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:
# print("- No matching binaries available")
return None
# else:
# print("- Failed to connect to GitHub API")
def check_binary_updates(self) -> dict:
"""
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

View File

@@ -1,6 +1,5 @@
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
# Copyright (C) 2020-2023, Dhinak G, Mykola Grymalyuk
import hashlib
import math
import os
import plistlib
@@ -9,18 +8,15 @@ from pathlib import Path
import os
import binascii
import argparse
import time
import atexit
import requests
import shutil
import urllib.parse
import py_sip_xnu
import logging
from resources import constants, ioreg
from data import sip_data, os_data
SESSION = requests.Session()
def hexswap(input_hex: str):
hex_pairs = [input_hex[i : i + 2] for i in range(0, len(input_hex), 2)]
@@ -39,8 +35,8 @@ def string_to_hex(input_string):
def process_status(process_result):
if process_result.returncode != 0:
print(f"Process failed with exit code {process_result.returncode}")
print(f"Please report the issue on the Discord server")
logging.info(f"Process failed with exit code {process_result.returncode}")
logging.info(f"Please report the issue on the Discord server")
raise Exception(f"Process result: \n{process_result.stdout.decode()}")
@@ -55,11 +51,11 @@ def human_fmt(num):
def header(lines):
lines = [i for i in lines if i is not None]
total_length = len(max(lines, key=len)) + 4
print("#" * (total_length))
logging.info("#" * (total_length))
for line in lines:
left_side = math.floor(((total_length - 2 - len(line.strip())) / 2))
print("#" + " " * left_side + line.strip() + " " * (total_length - len("#" + " " * left_side + line.strip()) - 1) + "#")
print("#" * total_length)
logging.info("#" + " " * left_side + line.strip() + " " * (total_length - len("#" + " " * left_side + line.strip()) - 1) + "#")
logging.info("#" * total_length)
RECOVERY_STATUS = None
@@ -124,7 +120,7 @@ sleep_process = None
def disable_sleep_while_running():
global sleep_process
print("- Disabling Idle Sleep")
logging.info("- Disabling Idle Sleep")
if sleep_process is None:
# If sleep_process is active, we'll just keep it running
sleep_process = subprocess.Popen(["caffeinate", "-d", "-i", "-s"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -134,7 +130,7 @@ def disable_sleep_while_running():
def enable_sleep_after_running():
global sleep_process
if sleep_process:
print("- Re-enabling Idle Sleep")
logging.info("- Re-enabling Idle Sleep")
sleep_process.kill()
sleep_process = None
@@ -283,7 +279,7 @@ def cls():
if not check_recovery():
os.system("cls" if os.name == "nt" else "clear")
else:
print("\u001Bc")
logging.info("\u001Bc")
def check_command_line_tools():
# Determine whether Command Line Tools exist
@@ -360,124 +356,6 @@ def get_firmware_vendor(*, decode: bool = False):
value = value.strip("\0")
return value
def verify_network_connection(url=None):
if url is None:
url = "https://www.google.com"
try:
response = SESSION.head(url, timeout=5, allow_redirects=True)
return True
except (requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.ConnectionError, requests.exceptions.HTTPError):
return False
def download_file(link, location, is_gui=None, verify_checksum=False):
if verify_network_connection(link):
disable_sleep_while_running()
base_name = Path(link).name
if Path(location).exists():
Path(location).unlink()
head_response = SESSION.head(link, allow_redirects=True)
try:
# Handle cases where Content-Length has garbage or is missing
total_file_size = int(head_response.headers['Content-Length'])
except KeyError:
total_file_size = 0
if total_file_size > 1024:
file_size_rounded = round(total_file_size / 1024 / 1024, 2)
file_size_string = f" of {file_size_rounded}MB"
# Check if we have enough space
if total_file_size > get_free_space():
print(f"Not enough space to download {base_name} ({file_size_rounded}MB)")
return False
else:
file_size_string = ""
response = SESSION.get(link, stream=True)
# SU Catalog's link is quite long, strip to make it bearable
if "sucatalog.gz" in base_name:
base_name = "sucatalog.gz"
header = f"# Downloading: {base_name} #"
box_length = len(header)
box_string = "#" * box_length
dl = 0
total_downloaded_string = ""
global clear
with location.open("wb") as file:
count = 0
start = time.perf_counter()
for chunk in response.iter_content(1024 * 1024 * 4):
dl += len(chunk)
file.write(chunk)
count += len(chunk)
if is_gui is None:
if clear:
cls()
print(box_string)
print(header)
print(box_string)
print("")
if total_file_size > 1024:
total_downloaded_string = f" ({round(float(dl / total_file_size * 100), 2)}%)"
print(f"{round(count / 1024 / 1024, 2)}MB Downloaded{file_size_string}{total_downloaded_string}\nAverage Download Speed: {round(dl//(time.perf_counter() - start) / 100000 / 8, 2)} MB/s")
if verify_checksum is True:
# Verify checksum
# Note that this can be quite taxing on slower Macs
checksum = hashlib.sha256()
with location.open("rb") as file:
chunk = file.read(1024 * 1024 * 16)
while chunk:
checksum.update(chunk)
chunk = file.read(1024 * 1024 * 16)
enable_sleep_after_running()
return checksum
enable_sleep_after_running()
return True
else:
cls()
header = "# Could not establish Network Connection with provided link! #"
box_length = len(header)
box_string = "#" * box_length
print(box_string)
print(header)
print(box_string)
if constants.Constants().url_patcher_support_pkg in link:
# If we're downloading PatcherSupportPkg, present offline build
print("\nPlease grab the offline variant of OpenCore Legacy Patcher from Github:")
print(f"https://github.com/dortania/OpenCore-Legacy-Patcher/releases/download/{constants.Constants().patcher_version}/OpenCore-Patcher-TUI-Offline.app.zip")
else:
print(link)
return None
def download_apple_developer_portal(link, location, is_gui=None, verify_checksum=False):
TOKEN_URL_BASE = "https://developerservices2.apple.com/services/download?path="
remote_path = urllib.parse.urlparse(link).path
token_url = urllib.parse.urlunparse(urllib.parse.urlparse(TOKEN_URL_BASE)._replace(query=urllib.parse.urlencode({"path": remote_path})))
try:
response = SESSION.get(token_url, timeout=5)
except (requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.ConnectionError):
print(" - Could not contact Apple download servers")
return None
try:
response.raise_for_status()
except requests.exceptions.HTTPError:
if response.status_code == 400 and "The path specified is invalid" in response.text:
print(" - File does not exist on Apple download servers")
else:
print(" - Could not request download authorization from Apple download servers")
return None
return download_file(link, location, is_gui, verify_checksum)
def dump_constants(constants):
with open(os.path.join(os.path.expanduser('~'), 'Desktop', 'internal_data.txt'), 'w') as f:
f.write(str(vars(constants)))
@@ -544,9 +422,19 @@ def find_disk_off_uuid(uuid):
return None
def get_free_space(disk=None):
"""
Get free space on disk in bytes
Parameters:
disk (str): Path to mounted disk (or folder on disk)
Returns:
int: Free space in bytes
"""
if disk is None:
disk = "/"
total, used, free = shutil.disk_usage("/")
total, used, free = shutil.disk_usage(disk)
return free
def grab_mount_point_from_disk(disk):
@@ -562,16 +450,6 @@ def monitor_disk_output(disk):
output = output[-2]
return output
def validate_link(link):
# Check if link is 404
try:
response = SESSION.head(link, timeout=5, allow_redirects=True)
if response.status_code == 404:
return False
else:
return True
except (requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.ConnectionError, requests.exceptions.HTTPError):
return False
def block_os_updaters():
# Disables any processes that would be likely to mess with
@@ -591,7 +469,7 @@ def block_os_updaters():
for bad_process in bad_processes:
if bad_process in current_process:
if pid != "":
print(f"- Killing Process: {pid} - {current_process.split('/')[-1]}")
logging.info(f"- Killing Process: {pid} - {current_process.split('/')[-1]}")
subprocess.run(["kill", "-9", pid])
break

View File

@@ -1,75 +1,110 @@
import logging
import subprocess
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
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
def validate(settings):
# Runs through ocvalidate to check for errors
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,
]
class PatcherValidation:
"""
Validation class for the patcher
valid_dumps_native = [
example_data.iMac.iMac201_Stock,
example_data.MacBookPro.MacBookPro141_SSD_Upgrade,
]
Primarily for Continuous Integration
"""
settings.validate = True
def __init__(self, global_constants: constants.Constants) -> None:
self.constants: constants.Constants = global_constants
self.constants.validate = True
self.valid_dumps = [
example_data.MacBookPro.MacBookPro92_Stock,
example_data.MacBookPro.MacBookPro111_Stock,
example_data.MacBookPro.MacBookPro133_Stock,
example_data.Macmini.Macmini52_Stock,
example_data.Macmini.Macmini61_Stock,
example_data.Macmini.Macmini71_Stock,
example_data.iMac.iMac81_Stock,
example_data.iMac.iMac112_Stock,
example_data.iMac.iMac122_Upgraded,
example_data.iMac.iMac122_Upgraded_Nvidia,
example_data.iMac.iMac151_Stock,
example_data.MacPro.MacPro31_Stock,
example_data.MacPro.MacPro31_Upgrade,
example_data.MacPro.MacPro31_Modern_AMD,
example_data.MacPro.MacPro31_Modern_Kepler,
example_data.MacPro.MacPro41_Upgrade,
example_data.MacPro.MacPro41_Modern_AMD,
example_data.MacPro.MacPro41_51__Flashed_Modern_AMD,
example_data.MacPro.MacPro41_51_Flashed_NVIDIA_WEB_DRIVERS,
]
self.valid_dumps_native = [
example_data.iMac.iMac201_Stock,
example_data.MacBookPro.MacBookPro141_SSD_Upgrade,
]
self._validate_configs()
self._validate_sys_patch()
def _build_prebuilt(self) -> None:
"""
Generate a build for each predefined model
Then validate against ocvalidate
"""
def build_prebuilt():
for model in model_array.SupportedSMBIOS:
print(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)
logging.info(f"Validating predefined model: {model}")
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:
print("Error on build!")
print(result.stdout.decode())
logging.info("Error on build!")
logging.info(result.stdout.decode())
raise Exception(f"Validation failed for predefined model: {model}")
else:
print(f"Validation succeeded for predefined model: {model}")
logging.info(f"Validation succeeded for predefined model: {model}")
def build_dumps():
for model in valid_dumps:
settings.computer = model
settings.custom_model = ""
print(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) -> None:
"""
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:
print("Error on build!")
print(result.stdout.decode())
raise Exception(f"Validation failed for predefined model: {settings.computer.real_model}")
logging.info("Error on build!")
logging.info(result.stdout.decode())
raise Exception(f"Validation failed for predefined model: {self.constants.computer.real_model}")
else:
print(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: int, minor_kernel: int) -> None:
"""
Validate that all files in the patchset are present in the payload
Parameters:
major_kernel (int): Major kernel version
minor_kernel (int): Minor kernel version
"""
patchset = sys_patch_dict.SystemPatchDictionary(major_kernel, minor_kernel, self.constants.legacy_accel_support).patchset_dict
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"]}')
@@ -80,58 +115,83 @@ def validate(settings):
if install_type in patchset[patch_subject][patch_core]:
for install_directory in patchset[patch_subject][patch_core][install_type]:
for install_file in patchset[patch_subject][patch_core][install_type][install_directory]:
source_file = str(settings.payload_local_binaries_root_path) + "/" + patchset[patch_subject][patch_core][install_type][install_directory][install_file] + install_directory + "/" + install_file
source_file = str(self.constants.payload_local_binaries_root_path) + "/" + patchset[patch_subject][patch_core][install_type][install_directory][install_file] + install_directory + "/" + install_file
if not Path(source_file).exists():
print(f"File not found: {source_file}")
logging.info(f"File not found: {source_file}")
raise Exception(f"Failed to find {source_file}")
print(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):
logging.info(f"- Validating against Darwin {major_kernel}.{minor_kernel}")
if not sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist", None):
raise Exception("Failed to generate patchset plist")
# Remove the plist file after validation
Path(settings.payload_path / f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist").unlink()
Path(self.constants.payload_path / f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist").unlink()
def validate_sys_patch():
if Path(settings.payload_local_binaries_root_path_zip).exists():
print("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)
def _validate_sys_patch(self) -> None:
"""
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(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)
print("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._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.SysPatchHelpers(self.constants).snb_board_id_patch(self.constants.payload_local_binaries_root_path)
# Clean up
subprocess.run(
[
"rm", "-rf", self.constants.payload_local_binaries_root_path
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
else:
print("- Skipping Root Patch File integrity validation")
logging.info("- Skipping Root Patch File integrity validation")
def validate_configs():
def _validate_configs(self) -> None:
"""
Validates build modules
"""
# First run is with default settings
build_prebuilt()
build_dumps()
self._build_prebuilt()
self._build_dumps()
# Second run, flip all settings
settings.verbose_debug = True
settings.opencore_debug = True
settings.opencore_build = "DEBUG"
settings.kext_debug = True
settings.kext_variant = "DEBUG"
settings.kext_debug = True
settings.showpicker = False
settings.sip_status = False
settings.secure_status = True
settings.firewire_boot = True
settings.nvme_boot = True
settings.enable_wake_on_wlan = True
settings.disable_tb = True
settings.force_surplus = True
settings.software_demux = True
settings.serial_settings = "Minimal"
build_prebuilt()
build_dumps()
self.constants.verbose_debug = True
self.constants.opencore_debug = True
self.constants.opencore_build = "DEBUG"
self.constants.kext_debug = True
self.constants.kext_variant = "DEBUG"
self.constants.kext_debug = True
self.constants.showpicker = False
self.constants.sip_status = False
self.constants.secure_status = True
self.constants.firewire_boot = True
self.constants.nvme_boot = True
self.constants.enable_wake_on_wlan = True
self.constants.disable_tb = True
self.constants.force_surplus = True
self.constants.software_demux = True
self.constants.serial_settings = "Minimal"
self._build_prebuilt()
self._build_dumps()
validate_configs()
validate_sys_patch()
subprocess.run(["rm", "-rf", self.constants.build_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)