Compare commits

..

255 Commits
0.6.5 ... 0.6.7

Author SHA1 Message Date
Mykola Grymalyuk
cada66a6b5 Add safe guards for logging 2023-06-02 12:11:32 -06:00
Mykola Grymalyuk
1ef98e0a4b Avoid logging wxPython bugs 2023-06-02 09:46:28 -06:00
Mykola Grymalyuk
4b3f7b837e Merge branch 'main' of https://github.com/dortania/OpenCore-Legacy-Patcher 2023-06-02 08:38:27 -06:00
Mykola Grymalyuk
17443b4fbf logging: Add milliseconds to log file name 2023-06-02 08:38:17 -06:00
Dhinak G
557f0a1bd9 Merge pull request #1074 from TechEnthusiasm/OCLP-debug-image
Oclp debug image
2023-06-02 10:15:16 -04:00
TechEnthusiasm
77bb25b3a5 Update DEBUG.md 2023-06-02 14:56:11 +01:00
TechEnthusiasm
6a48038696 Add files via upload 2023-06-02 09:49:55 -04:00
Mykola Grymalyuk
e6251da97a Add safe guards for macOS 14 2023-06-01 18:39:22 -06:00
Dhinak G
7239730c44 sys_patch_auto.py: Skip malformed kext plists
Anything with `GPUCompanionBundles` should have a proper plist
2023-05-31 11:49:38 -04:00
Mykola Grymalyuk
980e102675 GUI: Add pulse for unknown total size 2023-05-30 19:20:46 -06:00
Mykola Grymalyuk
dd66bca395 Merge branch 'main' of https://github.com/dortania/OpenCore-Legacy-Patcher 2023-05-30 15:43:02 -06:00
Mykola Grymalyuk
831495923a Support crash logging in CLI code 2023-05-30 15:42:17 -06:00
Mykola Grymalyuk
c9882f84bc Merge pull request #1073 from Jazzzny/blurbeta-workaround
Quality of Life - Add workaround for MenuBar2 crashing after patching
2023-05-30 13:47:01 -07:00
Mykola Grymalyuk
4110f7a553 Merge branch 'main' into blurbeta-workaround 2023-05-30 13:46:54 -07:00
Jazzzny
e99de2360a Update 2023-05-30 16:42:59 -04:00
Jazzzny
41ce771c33 Update CHANGELOG 2023-05-30 16:42:22 -04:00
Jazzzny
c0f951411a Change to global .plist file 2023-05-30 16:32:41 -04:00
Mykola Grymalyuk
47be068aaf analytics_handler.py: Adjust commit info sent 2023-05-30 14:23:34 -06:00
Mykola Grymalyuk
49f6a62926 Add “Reveal Log File” button to menubar 2023-05-30 14:18:07 -06:00
Mykola Grymalyuk
1cf0e3d363 Merge pull request #1070 from dortania/logging-rework
Unify logging format
2023-05-30 12:28:16 -07:00
Mykola Grymalyuk
739c488698 GUI: Disable Pulse() work-around on PSP 1.1.2 and newer 2023-05-30 12:22:06 -06:00
Mykola Grymalyuk
a6de37adf0 Merge branch 'main' into logging-rework 2023-05-30 10:56:09 -07:00
Mykola Grymalyuk
7bb00fd695 Sync PatcherSupportPkg 2023-05-30 11:55:54 -06:00
Mykola Grymalyuk
26fdb2ca2c GUI: Force Pulse() usage on all 2023-05-29 14:46:53 -06:00
Mykola Grymalyuk
af77e7979e Merge branch 'main' into logging-rework 2023-05-29 12:42:05 -07:00
Mykola Grymalyuk
3c5f5f84d2 Sync PatcherSupportPkg 2023-05-29 13:41:11 -06:00
Mykola Grymalyuk
6ab297423e Revert to old Centre logic 2023-05-29 12:46:28 -06:00
Mykola Grymalyuk
2435ea747c Test custom centreing again 2023-05-29 11:14:37 -06:00
Mykola Grymalyuk
d0eac893fe GUI: Move delay to sys_patch_start.py 2023-05-29 10:11:22 -06:00
Mykola Grymalyuk
c62fefb575 GUI: Add error handling to int conversion 2023-05-29 10:10:15 -06:00
Mykola Grymalyuk
87234740bd logging_handler.py: Don’t auto dismiss 2023-05-29 10:02:54 -06:00
Mykola Grymalyuk
47a1c6a2e5 logging_handler.py: Add additional logging info 2023-05-29 09:57:03 -06:00
Mykola Grymalyuk
cee8d8e768 GUI: Fix delay logic 2023-05-29 09:55:11 -06:00
Mykola Grymalyuk
e133beee36 gui_sys_patch_start.py: Exit on KDK failure
Nothing to recover from, best to close the app
2023-05-29 09:54:49 -06:00
Mykola Grymalyuk
0388eaa6f2 GUI: Strip Centre() work-around 2023-05-29 09:28:25 -06:00
Mykola Grymalyuk
984ba65d41 GUI: Add delay handler 2023-05-29 09:12:04 -06:00
Mykola Grymalyuk
a6879ede75 logging_handler.py: Add debug info for crashes 2023-05-28 14:37:34 -06:00
Mykola Grymalyuk
bfcabb3a4a logging_handler.py: Add support for sending crash reports 2023-05-28 13:13:49 -06:00
Jazzzny
efdb278691 oopsies 2023-05-28 14:48:10 -04:00
Jazzzny
dd1b60c8be Use full pathing 2023-05-28 14:44:00 -04:00
Mykola Grymalyuk
80e836e3bb gui_support.py: Implement manual window centreing 2023-05-28 10:57:05 -06:00
Mykola Grymalyuk
597a4a6e14 GUI: Log window sizing 2023-05-28 09:52:45 -06:00
Jazzzny
ca85d0d4ca Add workaround for MenuBar2 crashing 2023-05-28 11:42:52 -04:00
Mykola Grymalyuk
abea113272 GUI: Move Centre() to after init 2023-05-28 09:15:33 -06:00
Mykola Grymalyuk
48cf833cb8 GUI: Strip sleep test 2023-05-27 20:03:02 -06:00
Mykola Grymalyuk
ea3ff5d3b6 GUI: Add progress bar for patch fetching 2023-05-27 20:01:12 -06:00
Mykola Grymalyuk
01d666e2c3 GUI: Add handling for lack of parent 2023-05-27 13:49:15 -06:00
Mykola Grymalyuk
d8904bb3c0 GUI: Add handling for missing parent 2023-05-27 13:36:08 -06:00
Mykola Grymalyuk
d725798c60 GUI SysPatch: Seperate Display and Patch frames 2023-05-27 13:24:41 -06:00
Mykola Grymalyuk
bad9dbaecc Add exception handler test 2023-05-27 12:44:10 -06:00
Mykola Grymalyuk
79f6754c4f Sync changelog 2023-05-27 12:08:42 -06:00
Mykola Grymalyuk
12a2d5ade1 logging_handler.py: Don’t display user’s username 2023-05-27 12:05:22 -06:00
Mykola Grymalyuk
051196993f Merge branch 'main' into logging-rework 2023-05-27 10:53:52 -07:00
Mykola Grymalyuk
969cc65ae5 integrity_verification.py: Add extra error handling for missing/botched files 2023-05-27 11:52:02 -06:00
Mykola Grymalyuk
4a7b6437a0 logging_handler.py: Switch to per-launch log file 2023-05-27 11:50:52 -06:00
Mykola Grymalyuk
a2cdc0339a misc.py: Strip media argument 2023-05-27 11:23:35 -06:00
Mykola Grymalyuk
7d0bbf62bf main.py: Fix payload routing 2023-05-27 10:58:16 -06:00
Mykola Grymalyuk
c8b6eec14f gui_install_oc.py: Fix missing spacer 2023-05-26 21:45:46 -06:00
Mykola Grymalyuk
0427c9ef44 Rename LICENSE.md to LICENSE.txt 2023-05-25 17:06:42 -06:00
Mykola Grymalyuk
663bec68bf Merge pull request #1071 from dortania/license
Use BSD4 License
2023-05-25 16:04:43 -07:00
Mykola Grymalyuk
cf28e8d2b5 Strip extra 2023-05-25 16:38:06 -06:00
Mykola Grymalyuk
b1d0d733d9 Use BSD4 License 2023-05-25 16:13:41 -06:00
Mykola Grymalyuk
4f0b605786 logging_handler.py: Reveal log on crash 2023-05-25 11:25:13 -06:00
Mykola Grymalyuk
c031917a12 Merge branch 'main' into logging-rework 2023-05-24 12:52:58 -07:00
Mykola Grymalyuk
d8a79cf67e Reformat logging system 2023-05-24 12:24:09 -06:00
Mykola Grymalyuk
18b6ce8684 Merge pull request #1069 from ParaDoX1994/main
Fix iMac16,2 model
2023-05-23 18:20:37 -07:00
neon ball
d875cdf6a5 Fix iMac16,2 model 2023-05-24 04:12:52 +03:00
Mykola Grymalyuk
80ea0cd217 main.py: setpgrp() to prevent suddent termination 2023-05-22 16:10:58 -06:00
Mykola Grymalyuk
77da01dfb4 Increment build 2023-05-22 16:09:06 -06:00
Mykola Grymalyuk
a5319bf432 Merge branch 'main' of https://github.com/dortania/OpenCore-Legacy-Patcher 2023-05-22 14:10:25 -06:00
Mykola Grymalyuk
3c5d93f79e GUI: Hide self before prompting 2023-05-22 14:10:17 -06:00
Dhinak G
2ca2bc451a Docs: Update SOURCE.md 2023-05-22 16:06:17 -04:00
Mykola Grymalyuk
ab9208da58 GUI Update: Add alternative pulse for non-Metal 2023-05-22 13:46:15 -06:00
Mykola Grymalyuk
3132b0dcb7 Merge pull request #1066 from ParaDoX1994/new-gui-docs
GUI documentation refresh for 0.6.6
2023-05-22 12:15:10 -07:00
Mykola Grymalyuk
b22c28e07b Merge pull request #1068 from Jazzzny/among-us
Enhancement - Restore Photo Booth App on Non-Metal Macs running macOS Monterey and newer
2023-05-22 12:13:18 -07:00
Mykola Grymalyuk
aa81b0ba49 Merge branch 'main' into among-us 2023-05-22 12:10:14 -07:00
Mykola Grymalyuk
ba5dd16201 Sync PatcherSupportPkg 2023-05-22 13:09:40 -06:00
Mykola Grymalyuk
5f30adab73 logging_handler.py: Add CLI check 2023-05-22 12:38:39 -06:00
Mykola Grymalyuk
4c9c7965b6 GUI: Add conditional to Graphics override 2023-05-22 12:31:33 -06:00
Mykola Grymalyuk
1e86681d3c Validation: Support root patch validation on GA 2023-05-22 12:23:07 -06:00
Mykola Grymalyuk
79cf5cb86f GUI: Add Cursor checkbox 2023-05-22 12:22:24 -06:00
Jazzzny
e6a3536e31 Update CHANGELOG 2023-05-22 14:05:01 -04:00
Jazzzny
af733dc08c Merge branch 'dortania:main' into among-us 2023-05-22 13:48:26 -04:00
Jazzzny
1d564ed653 Push 2023-05-22 13:40:45 -04:00
Mykola Grymalyuk
f20c9e3a09 GUI: Add “Beta Menu Bar” configuration 2023-05-22 11:29:22 -06:00
Mykola Grymalyuk
422eee04b7 logging_handler: Resolve exception logging 2023-05-22 10:58:49 -06:00
Mykola Grymalyuk
1f23ceef7f sys_patch_helpers.py: Publish signed binaries 2023-05-22 09:44:25 -06:00
Mykola Grymalyuk
5c402e1820 GUI: Fix formatting on patch errors 2023-05-21 19:58:00 -06:00
Mykola Grymalyuk
a92cbe94a1 sys_patch.py: Add error handling for non-str entries 2023-05-21 19:52:50 -06:00
educovas
89ef9fb904 Sync PatcherSupportPkg 2023-05-21 22:14:59 -03:00
Mykola Grymalyuk
bfe8776a1a GUI: Add comment on stripping 2023-05-21 18:42:26 -06:00
Mykola Grymalyuk
52e31d0b1d GUI: Add additional handling 2023-05-21 15:12:30 -06:00
Mykola Grymalyuk
5a7308fa2a GUI: Don’t super on relaunch 2023-05-21 14:44:37 -06:00
Mykola Grymalyuk
b0617887dd GUI: Check before closing 2023-05-21 13:34:59 -06:00
Mykola Grymalyuk
5b4464a1f2 GUI: Fix argument overloading 2023-05-21 13:13:41 -06:00
Mykola Grymalyuk
d4f004c558 GUI: Recommend Monterey for USB1.1 units 2023-05-21 09:57:56 -06:00
Mykola Grymalyuk
df5e7525c2 GUI: Better match format 2023-05-20 16:43:29 -06:00
Mykola Grymalyuk
b56ec7c679 GUI: Allow users to view repo on updates 2023-05-20 13:50:37 -06:00
Mykola Grymalyuk
cd9ce32c04 GUI: Don’t prompt to root patch if no new patches 2023-05-20 13:18:11 -06:00
Mykola Grymalyuk
a65ceaa376 GUI: Add menubar item 2023-05-20 13:03:06 -06:00
Mykola Grymalyuk
2788dfc78e Merge pull request #1067 from dortania/gui-main-demo
Implement new main menu UI
2023-05-20 11:28:56 -07:00
Mykola Grymalyuk
888387dfa2 GUI: Adjust relaunch size 2023-05-20 12:27:14 -06:00
Mykola Grymalyuk
9c41fff3d4 GUI: Add error handling when parent is not provided 2023-05-20 12:19:19 -06:00
Mykola Grymalyuk
3acc4dc9d9 GUI: Set Choice size and avoid ComboBox usage 2023-05-20 10:33:06 -06:00
neon ball
99f6d8f060 Add new main menu image 2023-05-20 19:16:41 +03:00
neon ball
efdcbd24c1 Merge branch 'dortania:main' into new-gui-docs 2023-05-20 19:16:24 +03:00
Mykola Grymalyuk
fa322d73c5 Merge remote-tracking branch 'origin/main' into gui-main-demo 2023-05-20 09:55:19 -06:00
Mykola Grymalyuk
cb6f294d35 GUI: Adjust ComboBox width 2023-05-20 09:52:46 -06:00
Mykola Grymalyuk
4ac46d4b54 GUI: Change settings gear placement 2023-05-20 09:47:37 -06:00
Mykola Grymalyuk
48dd47aed3 PKG: Fix alias creation 2023-05-19 22:37:16 -06:00
Mykola Grymalyuk
58b3748034 GUI: Test new main menu 2023-05-19 22:06:14 -06:00
Mykola Grymalyuk
fefd651157 Sync CHANGELOG 2023-05-19 16:30:42 -06:00
Mykola Grymalyuk
2b1d598bf3 GUI: Fix ComboBox not updating variable 2023-05-19 16:23:48 -06:00
neon ball
a863e55a01 Delete MacPro3,1 Ethernet note
Fixed in 0.6.6
2023-05-19 23:49:25 +03:00
neon ball
b297692b77 Merge branch 'dortania:main' into new-gui-docs 2023-05-19 23:47:42 +03:00
neon ball
a17b43525a Jazzny's change
Refer to https://discord.com/channels/417165963327176704/835350746971111484/1109208345849634906
2023-05-19 22:59:57 +03:00
Mykola Grymalyuk
fd9cd85254 install.py: Fix function declaration 2023-05-19 11:18:43 -06:00
Mykola Grymalyuk
aa196b6c0d Merge pull request #1064 from dortania/gui-refactor
GUI Refactor: Implement modularized wxPython system
2023-05-18 20:48:07 -07:00
Mykola Grymalyuk
cb38ee9477 GUI: Add model check 2023-05-18 21:43:58 -06:00
Mykola Grymalyuk
6faff5d52a GUI: Misc adjustments 2023-05-18 20:48:49 -06:00
Dhinak G
cabcdcd381 Fix app name 2023-05-18 20:51:51 -04:00
Mykola Grymalyuk
e36e9b35e9 GUI: Set about and quit items 2023-05-18 18:36:39 -06:00
neon ball
566df783c8 Fix wording 2023-05-19 03:15:25 +03:00
neon ball
edbd96a7e9 3rd time's the charm 2023-05-19 03:00:52 +03:00
neon ball
72f7d20ce5 Fix link again 2023-05-19 03:00:21 +03:00
neon ball
39147e95be Fix link 2023-05-19 02:58:32 +03:00
neon ball
7d1d3dd9f6 Include note about TeraScale 1 Ventura fix 2023-05-19 02:58:04 +03:00
neon ball
6146aa48c6 Fix linking 2023-05-19 02:18:59 +03:00
neon ball
266261484f Remove mention of verbose
It is disabled by default, hence redundant
2023-05-19 02:18:25 +03:00
neon ball
231e545ca4 Change wording 2023-05-19 02:15:57 +03:00
neon ball
f18184bded Documentation image refresh for 0.6.6 2023-05-19 02:13:40 +03:00
neon ball
7171de0679 Docs update for 0.6.6 2023-05-19 02:12:37 +03:00
Mykola Grymalyuk
d360a8ee8b GUI: Unify Centre() calls 2023-05-18 16:09:26 -06:00
Mykola Grymalyuk
5505737b37 gui_sys_patch.py: Disable return during patch 2023-05-18 16:06:48 -06:00
Mykola Grymalyuk
11bc64f8e6 GUI: Unify Yield() calls 2023-05-18 16:06:33 -06:00
Mykola Grymalyuk
300726ea96 sys_patch.py: Remove input() calls 2023-05-18 15:47:34 -06:00
Mykola Grymalyuk
186669b9cd install.py: Clean functions 2023-05-18 15:44:55 -06:00
Mykola Grymalyuk
b4afa8bc28 Merge branch 'main' into gui-refactor 2023-05-18 14:41:48 -07:00
Mykola Grymalyuk
04441590f0 CI: Add support for validation on non-dortania repos 2023-05-18 14:58:56 -06:00
Mykola Grymalyuk
f5d9ecfc25 Merge pull request #1065 from Jazzzny/betterthanzoe-keyboardfix
Enhancement - Implement injector kext to fix Function Keys on legacy units
2023-05-18 13:56:16 -07:00
Jazzzny
f6f4131b53 Fix issue 2023-05-18 16:55:47 -04:00
Mykola Grymalyuk
47458daae2 Settings: Default to binary source 2023-05-18 14:18:14 -06:00
Mykola Grymalyuk
0507654d09 install.py: Fix regression 2023-05-18 13:52:45 -06:00
Mykola Grymalyuk
0ed1cbde2f Merge branch 'main' into gui-refactor 2023-05-18 12:11:02 -07:00
Mykola Grymalyuk
c80c46288c Sync Lilu 2023-05-18 13:00:42 -06:00
Mykola Grymalyuk
0cd1509a38 GUI: Adjust hyperlink spacing 2023-05-18 11:15:41 -06:00
Mykola Grymalyuk
06b8b08d4b GUI: Simplify cleaning 2023-05-18 10:34:46 -06:00
Mykola Grymalyuk
6fc895a45b GUI: Add proper break on cleaning 2023-05-18 10:31:37 -06:00
Mykola Grymalyuk
98bbddc03d Settings: Add additional safe guards 2023-05-18 10:28:18 -06:00
Mykola Grymalyuk
46d2a86e2f GUI Settings: Fix formatting on pre-Big Sur UI 2023-05-18 09:44:46 -06:00
Mykola Grymalyuk
5a6407ab14 GUI Patch: Use anchors for patch labels 2023-05-18 09:43:18 -06:00
Jazzzny
8b38939759 Relocate 2023-05-18 10:05:24 -04:00
Jazzzny
4b6587dc3b Cosmetic fix 2023-05-18 10:04:21 -04:00
Jazzzny
8adbc3b5d2 REvert 2023-05-18 09:53:46 -04:00
Jazzzny
62e565b8c0 Revert 2023-05-18 09:53:30 -04:00
Jazzzny
749a0be86e update CHANGELOG 2023-05-18 09:42:00 -04:00
Jazzzny
a1de379c5f Include MacBookPro4,1 with keyboard injector 2023-05-18 09:41:39 -04:00
Jazzzny
bf00c013c8 Update docs, changelog, and a shameless plug 2023-05-18 09:32:59 -04:00
Jazzzny
8c848f9317 Resstore Fn keys on MacBook5,2 and 4,1 2023-05-18 09:29:09 -04:00
Mykola Grymalyuk
fa711c18cc GUI: Revert helper usage 2023-05-17 19:34:52 -06:00
Mykola Grymalyuk
2d5ea95ee0 Revert previous commit 2023-05-17 19:30:23 -06:00
Mykola Grymalyuk
0cc8b7b705 utilities.py: Use geteuid() for elevated 2023-05-17 19:26:42 -06:00
Mykola Grymalyuk
fecd71ef46 GUI: Reset update stage on main menu 2023-05-17 16:07:14 -06:00
Mykola Grymalyuk
451262d50b GUI: Avoid building if host is unsupported 2023-05-17 15:11:01 -06:00
Mykola Grymalyuk
88f895b61e Fix main thread crash from wx invocation in thread 2023-05-17 14:35:07 -06:00
Mykola Grymalyuk
28f10824fd GUI: Adjust AppleScript prompt 2023-05-17 13:54:12 -06:00
Mykola Grymalyuk
2f6666edfb main.py: Fix conversion 2023-05-17 13:32:43 -06:00
Mykola Grymalyuk
831fbc3319 GUI: Convert more osascript calls 2023-05-17 13:12:35 -06:00
Mykola Grymalyuk
7417fc4180 Swtich to py-applescript library 2023-05-17 12:45:24 -06:00
Mykola Grymalyuk
96fcba8391 Utlize LSUIElement for Helper binary 2023-05-17 12:36:27 -06:00
Mykola Grymalyuk
d576752744 Merge branch 'main' into gui-refactor 2023-05-17 10:07:06 -07:00
Mykola Grymalyuk
034a30a283 Sync PatcherSupportPkg 2023-05-17 11:03:15 -06:00
educovas
adcacc478d Syn changelog 2023-05-17 12:36:26 -03:00
Mykola Grymalyuk
e853be2e84 Merge branch 'main' into gui-refactor 2023-05-16 14:01:35 -07:00
Mykola Grymalyuk
c414c9cde7 firmware.py: Resolve low power mode on MacPro6,1 2023-05-16 14:34:20 -06:00
Mykola Grymalyuk
87f9f39179 GUI Update: Break on error 2023-05-16 12:35:47 -06:00
Mykola Grymalyuk
0a18db3142 GUI Flash: Use CallAfter() for byte info 2023-05-16 12:29:38 -06:00
Mykola Grymalyuk
63ba8d4a16 GUI: Default to no for formatting 2023-05-16 12:24:14 -06:00
Mykola Grymalyuk
017d59f57d gui_support: Resolve gauge sizing error 2023-05-16 12:23:52 -06:00
Mykola Grymalyuk
0f55b074af GUI: Add branch selection 2023-05-16 11:35:54 -06:00
Mykola Grymalyuk
652e8659e4 GUI: Use settings format panel 2023-05-16 10:58:32 -06:00
Mykola Grymalyuk
f7d5b9345f GUI: Remove extra loop
Should only need to unzip twice, if more than there’s a serious problem
2023-05-16 10:30:53 -06:00
Mykola Grymalyuk
54480c3776 GUI: Use range for extraction loop 2023-05-16 10:29:26 -06:00
Mykola Grymalyuk
36c39a7e71 Merge branch 'main' into gui-refactor 2023-05-16 09:14:55 -07:00
Mykola Grymalyuk
a837a27dc0 Sync OpenCorePkg 2023-05-16 08:42:14 -06:00
Mykola Grymalyuk
8dcec0dfa8 validation.py: Clean up after root patch validation 2023-05-16 08:32:41 -06:00
Mykola Grymalyuk
85b90b13e4 Sync kexts 2023-05-16 08:28:23 -06:00
Mykola Grymalyuk
79c173aaff Set icon for Helper 2023-05-15 15:05:55 -06:00
Mykola Grymalyuk
f69d393acd GUI: Centre on updates 2023-05-15 11:33:52 -06:00
Mykola Grymalyuk
bebb71b1de GUI: Remove old libraries 2023-05-15 11:11:41 -06:00
Mykola Grymalyuk
2f12236ac8 GUI: Implement update flow 2023-05-15 11:10:12 -06:00
Mykola Grymalyuk
a7bfef5ed7 GUI: Sync changelog 2023-05-14 23:48:02 -06:00
Mykola Grymalyuk
4ad65f00b5 GUI: Remove unused import 2023-05-14 23:47:50 -06:00
Mykola Grymalyuk
9993436029 GUI: Fix launch errors when run from update directory 2023-05-14 23:06:18 -06:00
Mykola Grymalyuk
1f156170cf GUI: Publish custom serials if already in-use 2023-05-14 22:44:17 -06:00
Mykola Grymalyuk
81247d8cf4 GUI: Rename OCLP-Helper
Supports displaying icon as well as more familiar naming on prompt
2023-05-14 20:55:49 -06:00
Mykola Grymalyuk
5929e81337 GUI: Implement update.plist creation on update 2023-05-14 20:46:52 -06:00
Mykola Grymalyuk
d8267838ae GUI: Merge non-Metal and root patch menu 2023-05-14 10:20:55 -06:00
Mykola Grymalyuk
8f450b525b GUI: Reset version 2023-05-13 22:17:27 -06:00
Mykola Grymalyuk
681441df7e GUI: Add host check to native models 2023-05-13 21:57:57 -06:00
Mykola Grymalyuk
e02c26e703 GUI: Report unsupported installers 2023-05-13 21:51:41 -06:00
Mykola Grymalyuk
1b9e45b5f9 GUI: Adjust spacing 2023-05-13 19:38:25 -06:00
Mykola Grymalyuk
6a0090987c GUI: Fix path checking 2023-05-13 19:00:11 -06:00
Mykola Grymalyuk
02d3f6cc64 Constants: Set app version for demo 2023-05-13 18:20:22 -06:00
Mykola Grymalyuk
2e964ba9c2 GUI: Add app update checks 2023-05-13 18:19:57 -06:00
Mykola Grymalyuk
a2c0994bde GUI: Publish title for ComboBox entries 2023-05-13 13:05:52 -06:00
Mykola Grymalyuk
d0e257a364 GUI: Fix mis-selection 2023-05-13 12:43:09 -06:00
Mykola Grymalyuk
8c5165a06c GUI: Add MXM model and FU configuration 2023-05-13 12:31:41 -06:00
Mykola Grymalyuk
422283a3ac GUI: Add extra padding for pre-Big Sur UI 2023-05-13 09:52:28 -06:00
Mykola Grymalyuk
24225a3748 GUI: Adjust window height 2023-05-13 09:20:39 -06:00
Mykola Grymalyuk
0b59384130 GUI: Fix index 2023-05-13 00:44:23 -06:00
Mykola Grymalyuk
d04822b770 GUI: Add non-Metal settings 2023-05-13 00:31:20 -06:00
Mykola Grymalyuk
84648c67cf GUI: Set default to no 2023-05-13 00:06:33 -06:00
Mykola Grymalyuk
42112fcca9 GUI: Add serial spoofing 2023-05-13 00:02:01 -06:00
Mykola Grymalyuk
2bbb7153e7 GUI: Add stats 2023-05-12 23:08:09 -06:00
Mykola Grymalyuk
dc0c75964d GUI: Add SIP configuration 2023-05-12 22:01:42 -06:00
Mykola Grymalyuk
c7c6569c4b GUI: Add basic settings 2023-05-12 13:45:46 -06:00
Mykola Grymalyuk
28d3e981c5 GUI: Implement Pulse() work-around for non-Metal
ASB I beg of you, please fix this. This hack is so painful…
2023-05-12 09:44:44 -06:00
Mykola Grymalyuk
3bd9d85ae2 GUI: Fix model selection 2023-05-09 12:56:07 -06:00
Mykola Grymalyuk
b43c66c0bb GUI: Add basic settings 2023-05-09 12:39:33 -06:00
Mykola Grymalyuk
2a002d8e82 GUI: Fix sys_patch spacing 2023-05-08 17:52:08 -06:00
Mykola Grymalyuk
324d7362c7 GUI: Fix spacing 2023-05-08 16:58:12 -06:00
Mykola Grymalyuk
3dc4b60af1 GUI: Add default selections 2023-05-08 15:51:47 -06:00
Mykola Grymalyuk
33a885a309 GUI: Add download stopping 2023-05-08 15:16:32 -06:00
Mykola Grymalyuk
4f1cb8abcc GUI: Add installer flashing 2023-05-08 15:16:10 -06:00
Mykola Grymalyuk
21e7a75cc9 GUI: Add additional error handling 2023-05-07 19:02:00 -06:00
Mykola Grymalyuk
3ef6e4a853 GUI: Implement download GUI class
Unifies all download UIs
2023-05-07 17:41:46 -06:00
Mykola Grymalyuk
bd70c4a24a GUI: Add error handling for object deletion 2023-05-07 13:29:13 -06:00
Mykola Grymalyuk
aaf7519e94 GUI: Remove unused RedirectText class 2023-05-07 12:50:37 -06:00
Mykola Grymalyuk
28cdc3f61b GUI: Add help menu 2023-05-07 12:44:30 -06:00
Mykola Grymalyuk
4068bc1661 GUI: Wait for payloads.dmg mount 2023-05-07 12:23:57 -06:00
Mykola Grymalyuk
1204daa330 GUI: Add root patching support 2023-05-07 12:05:58 -06:00
Mykola Grymalyuk
f8b2b5a759 Implement callback 2023-05-06 21:10:53 -06:00
Mykola Grymalyuk
f3e2dfc4de Push experimental build 2023-05-06 19:49:29 -06:00
Mykola Grymalyuk
a6e0c142ca kdk_handler.py: Fix matching 2023-05-04 17:38:24 -06:00
Mykola Grymalyuk
d9e9fea2f7 Sync changelog 2023-05-04 16:16:26 -06:00
Mykola Grymalyuk
978a16f397 Merge pull request #1063 from dortania/dmg-rework
Implement DMG-based PatcherSupportPkg system
2023-05-04 15:15:38 -07:00
Mykola Grymalyuk
d21b984918 Implement DMG-based PatcherSupportPkg 2023-05-04 15:02:41 -06:00
Mykola Grymalyuk
c308bcb993 payloads: USe variable name for mounting 2023-05-04 12:43:06 -06:00
Mykola Grymalyuk
15103007a5 CI: Comment out cert 2023-05-04 11:51:10 -06:00
Mykola Grymalyuk
a932b5a483 CI: Enclose paramter 2023-05-04 11:49:09 -06:00
Mykola Grymalyuk
9b96514b91 CI: Fix ID detection 2023-05-04 11:45:19 -06:00
Mykola Grymalyuk
a33142e29e CI: Use v2 for cert import 2023-05-04 11:19:12 -06:00
Mykola Grymalyuk
ef9df5265b Build: Rename payloads.dmg volume
Avoids confusion when users see “payloads” mounted in disk utility with no extra info
2023-05-04 11:18:39 -06:00
Mykola Grymalyuk
bc8a2727c3 CI: Fix syntax 2023-05-04 10:42:18 -06:00
Mykola Grymalyuk
17a1b823fc Merge branch 'main' of https://github.com/dortania/OpenCore-Legacy-Patcher 2023-05-04 10:39:48 -06:00
Mykola Grymalyuk
993f0c22d5 CI: Implement cert check
Allows for easier swapping between self-hosted and Github-provided runners
2023-05-04 10:39:39 -06:00
Mykola Grymalyuk
bd42aad340 Merge pull request #1059 from Jazzzny/colorsync-downgrade-toggle
Enhancement - Add a ColorSync downgrade toggle
2023-05-04 08:41:56 -07:00
Mykola Grymalyuk
fcd3afe29d Merge branch 'main' into colorsync-downgrade-toggle 2023-05-04 08:05:06 -07:00
Mykola Grymalyuk
ed62fe91a2 Increment build 2023-05-04 08:47:03 -06:00
Jazzzny
aae6cc705f Fix, thanks Slav 2023-04-28 18:17:23 -04:00
Jazzzny
eeb2e6cb1a Add changelog 2023-04-20 20:01:05 -04:00
Jazzzny
6ac18b251c Clean up 2023-04-20 19:58:20 -04:00
Jazzzny
11a9ab7b96 Add support for disabling ColorSync patch 2023-04-20 19:58:20 -04:00
Jazzzny
b87737f55e Create modified build without ColorSync downgrade 2023-04-20 19:58:20 -04:00
102 changed files with 5906 additions and 4728 deletions

View File

@@ -30,9 +30,9 @@ jobs:
- name: Build Binary - name: Build Binary
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 }}" --key "${{ env.ANALYTICS_KEY }}" --site "${{ env.ANALYTICS_SITE }}" 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 }}" --key "${{ env.ANALYTICS_KEY }}" --site "${{ env.ANALYTICS_SITE }}"
# Uncomment when using Github Runners or first run on self-hosted
# - name: Import Certificate # - name: Import Certificate
# uses: apple-actions/import-codesign-certs@v1 # if: (!security find-certificate -c "${{ env.MAC_CODESIGN_IDENTITY }}")
# uses: apple-actions/import-codesign-certs@v2
# with: # with:
# p12-file-base64: ${{ secrets.MAC_CODESIGN_CERT }} # p12-file-base64: ${{ secrets.MAC_CODESIGN_CERT }}
# p12-password: ${{ secrets.MAC_NOTARIZATION_PASSWORD }} # p12-password: ${{ secrets.MAC_NOTARIZATION_PASSWORD }}

23
.github/workflows/validate-external.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
name: CI - Validation (External)
on:
push:
workflow_dispatch:
release:
types: [published]
jobs:
build:
name: Validate
runs-on: macos-latest
if: github.repository_owner != 'dortania'
steps:
- uses: actions/checkout@v3
- name: Install Python
uses: actions/setup-python@v2
with:
python-version: '3.10'
- name: Install Dependencies
run: python3 -m pip install -r requirements.txt
- name: Validate
run: python3 OpenCore-Patcher-GUI.command --validate

View File

@@ -11,10 +11,6 @@ jobs:
name: Validate name: Validate
runs-on: x86_64_monterey runs-on: x86_64_monterey
if: github.repository_owner == 'dortania' 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: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Validate - name: Validate

4
.gitignore vendored
View File

@@ -33,3 +33,7 @@ __pycache__/
/payloads/OpenCore-Legacy-Patcher-*.plist /payloads/OpenCore-Legacy-Patcher-*.plist
/payloads/KDK.dmg /payloads/KDK.dmg
*.log *.log
/Universal-Binaries.dmg
/payloads/KDKInfo.plist
/payloads/update.sh
/payloads/OpenCore-Patcher.app

View File

@@ -30,7 +30,7 @@ class CreateBinary:
def __init__(self): def __init__(self):
start = time.time() start = time.time()
print("- Starting build script") print("Starting build script")
self.args = self._parse_arguments() self.args = self._parse_arguments()
@@ -39,7 +39,7 @@ class CreateBinary:
self._preflight_processes() self._preflight_processes()
self._build_binary() self._build_binary()
self._postflight_processes() self._postflight_processes()
print(f"- Build script completed in {str(round(time.time() - start, 2))} seconds") print(f"Build script completed in {str(round(time.time() - start, 2))} seconds")
def _set_cwd(self): def _set_cwd(self):
@@ -48,7 +48,7 @@ class CreateBinary:
""" """
os.chdir(Path(__file__).resolve().parent) os.chdir(Path(__file__).resolve().parent)
print(f"- Current Working Directory: \n\t{os.getcwd()}") print(f"Current Working Directory:\n- {os.getcwd()}")
def _parse_arguments(self): def _parse_arguments(self):
@@ -99,7 +99,7 @@ class CreateBinary:
Start preflight processes Start preflight processes
""" """
print("- Starting preflight processes") print("Starting preflight processes")
self._setup_pathing() self._setup_pathing()
self._delete_extra_binaries() self._delete_extra_binaries()
self._download_resources() self._download_resources()
@@ -111,7 +111,7 @@ class CreateBinary:
Start postflight processes Start postflight processes
""" """
print("- Starting postflight processes") print("Starting postflight processes")
self._patch_load_command() self._patch_load_command()
self._add_commit_data() self._add_commit_data()
self._post_flight_cleanup() self._post_flight_cleanup()
@@ -124,19 +124,19 @@ class CreateBinary:
""" """
if Path(f"./dist/OpenCore-Patcher.app").exists(): if Path(f"./dist/OpenCore-Patcher.app").exists():
print("- Found OpenCore-Patcher.app, removing...") print("Found OpenCore-Patcher.app, removing...")
rm_output = subprocess.run( rm_output = subprocess.run(
["rm", "-rf", "./dist/OpenCore-Patcher.app"], ["rm", "-rf", "./dist/OpenCore-Patcher.app"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE stdout=subprocess.PIPE, stderr=subprocess.PIPE
) )
if rm_output.returncode != 0: if rm_output.returncode != 0:
print("- Remove failed") print("Remove failed")
print(rm_output.stderr.decode('utf-8')) print(rm_output.stderr.decode('utf-8'))
raise Exception("Remove failed") raise Exception("Remove failed")
self._embed_key() self._embed_key()
print("- Building GUI binary...") print("Building GUI binary...")
build_args = [self.pyinstaller_path, "./OpenCore-Patcher-GUI.spec", "--noconfirm"] build_args = [self.pyinstaller_path, "./OpenCore-Patcher-GUI.spec", "--noconfirm"]
build_result = subprocess.run(build_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) build_result = subprocess.run(build_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -144,10 +144,18 @@ class CreateBinary:
self._strip_key() self._strip_key()
if build_result.returncode != 0: if build_result.returncode != 0:
print("- Build failed") print("Build failed")
print(build_result.stderr.decode('utf-8')) print(build_result.stderr.decode('utf-8'))
raise Exception("Build failed") raise Exception("Build failed")
# Next embed support icns into ./Resources
print("Embedding icns...")
for file in Path("payloads/Icon/AppIcons").glob("*.icns"):
subprocess.run(
["cp", str(file), "./dist/OpenCore-Patcher.app/Contents/Resources/"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
@@ -157,15 +165,15 @@ class CreateBinary:
""" """
if not self.args.key: if not self.args.key:
print("- No developer key provided, skipping...") print("No developer key provided, skipping...")
return return
if not self.args.site: if not self.args.site:
print("- No site provided, skipping...") print("No site provided, skipping...")
return return
print("- Embedding developer key...") print("Embedding developer key...")
if not Path("./resources/analytics_handler.py").exists(): if not Path("./resources/analytics_handler.py").exists():
print("- analytics_handler.py not found") print("analytics_handler.py not found")
return return
lines = [] lines = []
@@ -188,15 +196,15 @@ class CreateBinary:
""" """
if not self.args.key: if not self.args.key:
print("- No developer key provided, skipping...") print("No developer key provided, skipping...")
return return
if not self.args.site: if not self.args.site:
print("- No site provided, skipping...") print("No site provided, skipping...")
return return
print("- Stripping developer key...") print("Stripping developer key...")
if not Path("./resources/analytics_handler.py").exists(): if not Path("./resources/analytics_handler.py").exists():
print("- analytics_handler.py not found") print("analytics_handler.py not found")
return return
lines = [] lines = []
@@ -236,11 +244,10 @@ class CreateBinary:
"launcher.sh", "launcher.sh",
"OC-Patcher-TUI.icns", "OC-Patcher-TUI.icns",
"OC-Patcher.icns", "OC-Patcher.icns",
"Universal-Binaries.zip",
] ]
print("- Deleting extra binaries...") print("Deleting extra binaries...")
for file in Path("payloads").glob(pattern="*"): for file in Path("payloads").glob(pattern="*"):
if file.is_dir(): if file.is_dir():
if file.name in whitelist_folders: if file.name in whitelist_folders:
@@ -261,20 +268,20 @@ class CreateBinary:
patcher_support_pkg_version = constants.Constants().patcher_support_pkg_version patcher_support_pkg_version = constants.Constants().patcher_support_pkg_version
required_resources = [ required_resources = [
"Universal-Binaries.zip" "Universal-Binaries.dmg"
] ]
print("- Downloading required resources...") print("Downloading required resources...")
for resource in required_resources: for resource in required_resources:
if Path(f"./payloads/{resource}").exists(): if Path(f"./{resource}").exists():
if self.args.reset_binaries: if self.args.reset_binaries:
print(f"- Removing old {resource}") print(f"- Removing old {resource}")
rm_output = subprocess.run( rm_output = subprocess.run(
["rm", "-rf", f"./payloads/{resource}"], ["rm", "-rf", f"./{resource}"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE stdout=subprocess.PIPE, stderr=subprocess.PIPE
) )
if rm_output.returncode != 0: if rm_output.returncode != 0:
print("- Remove failed") print("Remove failed")
print(rm_output.stderr.decode('utf-8')) print(rm_output.stderr.decode('utf-8'))
raise Exception("Remove failed") raise Exception("Remove failed")
else: else:
@@ -298,13 +305,6 @@ class CreateBinary:
print(f"- {resource} not found") print(f"- {resource} not found")
raise Exception(f"{resource} not found") raise Exception(f"{resource} not found")
print(" - Moving into payloads")
mv_output = subprocess.run(["mv", resource, "./payloads/"], 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 _generate_payloads_dmg(self): def _generate_payloads_dmg(self):
""" """
@@ -324,16 +324,16 @@ class CreateBinary:
stdout=subprocess.PIPE, stderr=subprocess.PIPE stdout=subprocess.PIPE, stderr=subprocess.PIPE
) )
if rm_output.returncode != 0: if rm_output.returncode != 0:
print("- Remove failed") print("Remove failed")
print(rm_output.stderr.decode('utf-8')) print(rm_output.stderr.decode('utf-8'))
raise Exception("Remove failed") raise Exception("Remove failed")
print("- Generating DMG...") print("- Generating DMG...")
dmg_output = subprocess.run([ dmg_output = subprocess.run([
'hdiutil', 'create', './payloads.dmg', 'hdiutil', 'create', './payloads.dmg',
'-megabytes', '32000', '-megabytes', '32000', # Overlays can only be as large as the disk image allows
'-format', 'UDZO', '-ov', '-format', 'UDZO', '-ov',
'-volname', 'payloads', '-volname', 'OpenCore Patcher Resources (Base)',
'-fs', 'HFS+', '-fs', 'HFS+',
'-srcfolder', './payloads', '-srcfolder', './payloads',
'-passphrase', 'password', '-encryption' '-passphrase', 'password', '-encryption'

View File

@@ -1,5 +1,101 @@
# OpenCore Legacy Patcher changelog # OpenCore Legacy Patcher changelog
## 0.6.7
- Resolve partition buttons overlapping in Install OpenCore UI
- ex. "EFI" and additional FAT32 partitions on a single drive
- Re-enable mediaanalysisd on Ventura
- Allows for Live Text support on systems with3802 GPUs
- ie. Intel Ivy Bridge and Haswell, Nvidia Kepler
- Previously disabled due to high instability in Photos with Face Scanning, now resolved
- Work-around crashing after patching with MenuBar2 implementation enabled
- Setting must be re-enabled after patching
- Update non-Metal Binaries:
- Resolve window placement defaulting past top of screen for some apps
- ex. OpenCore-Patcher.app during root patching
- Resolve indeterminate progress bars not rendering with wxWidgets in Monterey and later
- ex. OpenCore-Patcher.app
- UI changes:
- Add "Show Log File" button to menubar
- Avoid listing unsupported installer to download by default
- ex. macOS 14 InstallAssistant.pkg
- Resolve crash when fetching remote macOS installers offline
- Avoid displaying root patches on unsupported macOS versions
- ex. macOS 14
- Backend changes:
- Call `setpgrp()` to prevent app from being killed if parent process is killed (ie. LaunchAgents)
- Rework logging handler:
- Implement formatted logging
- Allowing easier debugging
- Implement per-version, per-run file logging
- ex. OpenCore-Patcher (0.6.7) (2021-12-31-12-34-56-666903).log
- Keep only 10 latest log files
- Reveal log file in Finder on main thread crash
- Avoid writing username to log file
- Resolve SharedSupport.dmg pathing error during macOS Installer Verification
- Applicable to systems with 2 (or more) USB Installers with the same name plugged in
- Resolve payloads path being mis-routed during CLI calls
- Add UI when fetching root patches for host
- Remove progress bar work-around for non-Metal in Monterey and later
- Requires host to have been patched with PatcherSupportPkg 1.1.2 or newer
- Increment Binaries:
- PatcherSupportPkg 1.1.2 - release
## 0.6.6
- Implement option to disable ColorSync downgrade on HD 3000 Macs
- Allows for Display Profiles support on some units
- Note: black box rendering issues will likely appear
- Thanks [@jazzzny](https://github.com/Jazzzny)
- Rename payloads.dmg volume name to "OpenCore Patcher Resources (Base)"
- Allows for better identification when mounted (ex. Disk Utility while app is running)
- Implement DMG-based PatcherSupportPkg system
- Reduces both app size and root patching time
- Resolve incorrect remote KDK matching for macOS betas
- ex. Beta 4 KDK being recommended for Beta 3 install
- Resolve low power mode on MacPro6,1
- Credit to CaseyJ's [PCI Bus Enumeration Patch](https://github.com/AMD-OSX/AMD_Vanilla/pull/196)
- Resolve PCI eject menu appearing on unsupported hardware
- Resolve kernel panic on wake for AMD TeraScale 1 and Nvidia Tesla 8000 series GPUs
- Resolve loss of Ethernet after wake on MacPro3,1 in Ventura
- Resolve graphics corruption on wake for TeraScale 1
- Patch currently limited to Ventura and newer
- Restore Function Keys on MacBook5,2 and MacBook4,1
- Implementation by [@jazzzny](https://github.com/Jazzzny)
- Update non-Metal Binaries:
- Resolves cryptexd and sshd crashes
- Resolves screen recording regression
- Resolves Photo Booth on macOS Monterey and later
- May require tccplus for permissions
- Resolve Application alias not being created with AutoPatcher
- Backend changes:
- Rename OCLP-Helper to OpenCore-Patcher
- Allows for better identification when displaying prompts
- Reimplement wxPython GUI into modularized system:
- Allows for easier maintenance and future expansion
- Changes include:
- Reworked settings UI
- Unified download UI with time remaining
- Implement in-app update system
- Guides users to update OpenCore and Root Patches once update's installed
- Expand app update checks to include nightly users
- ex. 0.6.6 nightly -> 0.6.6 release
- Implement macOS installer verification after flashing
- Implement proper UI call backs on long processes
- ex. Root patching
- Implement default selections for disks and installers
- Set about and quit items
- Utilize `py-applescript` for authorization prompts
- Avoids displaying prompts with `osascript` in the title
- Due to limitations, only used for installer creation and OpenCore installation
- Resolve exception handler not logging to file
- Display raised exceptions from main thread to users
- Increment Binaries:
- PatcherSupportPkg 1.1.0 - release
- OpenCorePkg 0.9.2 - release
- Lilu 1.6.6 - rolling (d8f3782)
- RestrictEvents 1.1.1 - release
- FeatureUnlock 1.1.4 - release
- BlueToolFixup 2.6.6 - release
## 0.6.5 ## 0.6.5
- Update 3802 Patchset Binaries: - Update 3802 Patchset Binaries:
- Resolves additional 3rd party app crashes on Metal with macOS 13.3+ - Resolves additional 3rd party app crashes on Metal with macOS 13.3+

14
LICENSE.txt Normal file
View File

@@ -0,0 +1,14 @@
Copyright (c) 2020-2023, Dhinak G
Copyright (c) 2020-2023, Mykola Grymalyuk
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must display the following acknowledgement:
This product includes software developed by the organization.
4. Neither the name of the copyright holder nor the names the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -9,7 +9,7 @@ block_cipher = None
a = Analysis(['OpenCore-Patcher-GUI.command'], a = Analysis(['OpenCore-Patcher-GUI.command'],
pathex=[], pathex=[],
binaries=[], binaries=[],
datas=[('payloads.dmg', '.')], datas=[('payloads.dmg', '.'), ('Universal-Binaries.dmg', '.')],
hiddenimports=[], hiddenimports=[],
hookspath=[], hookspath=[],
hooksconfig={}, hooksconfig={},
@@ -49,6 +49,7 @@ app = BUNDLE(coll,
icon="payloads/OC-Patcher.icns", icon="payloads/OC-Patcher.icns",
bundle_identifier="com.dortania.opencore-legacy-patcher", bundle_identifier="com.dortania.opencore-legacy-patcher",
info_plist={ info_plist={
"CFBundleName": "OpenCore Legacy Patcher",
"CFBundleShortVersionString": constants.Constants().patcher_version, "CFBundleShortVersionString": constants.Constants().patcher_version,
"NSHumanReadableCopyright": constants.Constants().copyright_date, "NSHumanReadableCopyright": constants.Constants().copyright_date,
"LSMinimumSystemVersion": "10.10.0", "LSMinimumSystemVersion": "10.10.0",

View File

@@ -91,6 +91,8 @@ To run the project from source, see here: [Build and run from source](./SOURCE.m
* Aid with Nvidia Web Driver research and development * Aid with Nvidia Web Driver research and development
* [joevt](https://github.com/joevt) * [joevt](https://github.com/joevt)
* [FixPCIeLinkrate](https://github.com/joevt/joevtApps) * [FixPCIeLinkrate](https://github.com/joevt/joevtApps)
* [Jazzzny](https://github.com/Jazzzny)
* Research and various contributions to the project
* Amazing users who've graciously donate hardware: * Amazing users who've graciously donate hardware:
* [JohnD](https://forums.macrumors.com/members/johnd.53633/) - 2013 Mac Pro * [JohnD](https://forums.macrumors.com/members/johnd.53633/) - 2013 Mac Pro
* [SpiGAndromeda](https://github.com/SpiGAndromeda) - AMD Vega 64 * [SpiGAndromeda](https://github.com/SpiGAndromeda) - AMD Vega 64

View File

@@ -2,13 +2,13 @@
OpenCore Legacy Patcher at its core is a Python-based GUI/CLI-based application. In turn, to run the project from source, you simply need to invoke the OpenCore-Patcher-GUI.command file via Python. OpenCore Legacy Patcher at its core is a Python-based GUI/CLI-based application. In turn, 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: For developers wishing to validate mainline changes, you may use this link: [GUI (Graphical Based App)](https://nightly.link/dortania/OpenCore-Legacy-Patcher/workflows/build-app-wxpython/main/OpenCore-Patcher.app%20%28GUI%29.zip)
* [GUI (Graphical Based App)](https://nightly.link/dortania/OpenCore-Legacy-Patcher/workflows/build-app-wxpython/main/OpenCore-Patcher.app%20%28GUI%29.zip) * **Warning**: Nightly builds (untagged builds built from the latest commit) are actively developed OpenCore Legacy Patcher builds. These builds have not been tested, are not guaranteed to work, and are not guaranteed to be safe. Do not use nightlies without a good reason to do so, and do not use them on your main machine. Additionally, these binaries should not be used without first consulting the [CHANGELOG](./CHANGELOG.md).
**Warning**: These binaries should not be used without first consulting the [CHANGELOG](./CHANGELOG.md). Do not distribute these links in forums, please link to this document instead. **Do not share _any_ links to these binaries** in forums; please link to **this document only**.
* Additionally, do not reupload these binaries or download binaries from other sites. Using binaries from untrusted sources is a security issue, as they may have been tampered with.
* Users running new builds of the project without understanding what has changed are at a higher risk of bricking their installation as they do not read any warnings provided in the CHANGELOG. We wish to minimize these situations as much as possible. * Users running new builds of the project without understanding what has changed and the implications of installing software under active development are at a higher risk of bricking their installation as they do not read any warnings provided in the CHANGELOG. We wish to minimize these situations as much as possible.
## Getting Started ## Getting Started
@@ -57,10 +57,6 @@ Pass `-h` or `--help` for more information on supported CLI arguments.
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 Python 3 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 Python 3 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 best compatibility, generate binaries on macOS Mojave. These binaries will be compatible with macOS 10.9 to macOS 12.
* Currently our build system is a [Macmini8,1 provided by MacStadium](https://www.macstadium.com/opensource) running macOS Mojave (10.14.6).
```sh ```sh
# Install PyInstaller # Install PyInstaller
pip3 install pyinstaller pip3 install pyinstaller

View File

@@ -103,6 +103,9 @@ class SystemPatchDictionary():
"GPUSupport.framework": "10.14.3", "GPUSupport.framework": "10.14.3",
"SkyLight.framework": f"10.14.6-{self.os_major}", "SkyLight.framework": f"10.14.6-{self.os_major}",
}, },
"/System/Applications": {
**({ "Photo Booth.app": "11.7.6"} if self.os_major >= os_data.os_data.monterey else {}),
},
}, },
"Remove": { "Remove": {
"/System/Library/Extensions": [ "/System/Library/Extensions": [
@@ -144,6 +147,8 @@ class SystemPatchDictionary():
**({"defaults write /Library/Preferences/.GlobalPreferences.plist ShowDate -int 1": True } if self.os_float >= self.macOS_12_4 else {}), **({"defaults write /Library/Preferences/.GlobalPreferences.plist ShowDate -int 1": True } if self.os_float >= self.macOS_12_4 else {}),
"defaults write /Library/Preferences/.GlobalPreferences.plist InternalDebugUseGPUProcessForCanvasRenderingEnabled -bool false": True, "defaults write /Library/Preferences/.GlobalPreferences.plist InternalDebugUseGPUProcessForCanvasRenderingEnabled -bool false": True,
"defaults write /Library/Preferences/.GlobalPreferences.plist WebKitExperimentalUseGPUProcessForCanvasRenderingEnabled -bool false": True, "defaults write /Library/Preferences/.GlobalPreferences.plist WebKitExperimentalUseGPUProcessForCanvasRenderingEnabled -bool false": True,
# MenuBar2 breaks macOS if enabled before patching
"defaults write /Library/Preferences/.GlobalPreferences.plist Amy.MenuBar2Beta -bool false": True,
}, },
}, },
"Non-Metal IOAccelerator Common": { "Non-Metal IOAccelerator Common": {
@@ -699,7 +704,7 @@ class SystemPatchDictionary():
"AMD3800Controller.kext": "10.13.6", "AMD3800Controller.kext": "10.13.6",
"AMD4600Controller.kext": "10.13.6", "AMD4600Controller.kext": "10.13.6",
"AMD4800Controller.kext": "10.13.6", "AMD4800Controller.kext": "10.13.6",
"ATIRadeonX2000.kext": "10.13.6", "ATIRadeonX2000.kext": "10.13.6" if self.os_major < os_data.os_data.ventura else "10.13.6 TS1",
"ATIRadeonX2000GA.plugin": "10.13.6", "ATIRadeonX2000GA.plugin": "10.13.6",
"ATIRadeonX2000GLDriver.bundle": "10.13.6", "ATIRadeonX2000GLDriver.bundle": "10.13.6",
"ATIRadeonX2000VADriver.bundle": "10.13.6", "ATIRadeonX2000VADriver.bundle": "10.13.6",

View File

@@ -7,7 +7,7 @@
* [Keyboard Backlight broken](#keyboard-backlight-broken) * [Keyboard Backlight broken](#keyboard-backlight-broken)
* [Photos and Maps Apps Heavily Distorted](#photos-and-maps-apps-heavily-distorted) * [Photos and Maps Apps Heavily Distorted](#photos-and-maps-apps-heavily-distorted)
* [Cannot press "Done" when editing a Sidebar Widget](#cannot-press-done-when-editing-a-sidebar-widget) * [Cannot press "Done" when editing a Sidebar Widget](#cannot-press-done-when-editing-a-sidebar-widget)
* [Wake from sleep heavily distorted on AMD/ATI in macOS 11.3 and newer](#wake-from-sleep-heavily-distorted-on-amd-ati-in-macos-11-3-and-newer) * [Wake from sleep heavily distorted on AMD/ATI from macOS 11.3 to Monterey](#wake-from-sleep-heavily-distorted-on-amd-ati-from-macos-11-3-to-monterey)
* [Unable to switch GPUs on 2011 15" and 17" MacBook Pros](#unable-to-switch-gpus-on-2011-15-and-17-macbook-pros) * [Unable to switch GPUs on 2011 15" and 17" MacBook Pros](#unable-to-switch-gpus-on-2011-15-and-17-macbook-pros)
* [Erratic Colours on ATI TeraScale 2 GPUs (HD5000/HD6000)](#erratic-colours-on-ati-terascale-2-gpus-hd5000-hd6000) * [Erratic Colours on ATI TeraScale 2 GPUs (HD5000/HD6000)](#erratic-colours-on-ati-terascale-2-gpus-hd5000-hd6000)
* [Unable to allow Safari Extensions](#unable-to-allow-Safari-Extensions) * [Unable to allow Safari Extensions](#unable-to-allow-Safari-Extensions)
@@ -104,9 +104,11 @@ Due to the Metal Backend, the enhanced color output of these apps seems to heavi
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 ## Wake from sleep heavily distorted on AMD/ATI from macOS 11.3 to Monterey
Unfortunately, this is a very well known issue that the community is investigating. A currently known solution is to downgrade to macOS 11.2.3 or older until a proper fix can be found. Additionally, logging out and logging back in can resolve the issue without requiring a reboot. **Fixed for macOS Ventura starting from 0.6.6. Big Sur and Monterey will continue to exhibit the issue.**
For older versions, only known solution is to downgrade to macOS 11.2.3 or older. Additionally, logging out and logging back in can resolve the issue without requiring a reboot.
* Note, this issue should be exclusive to TeraScale 1 GPUs (ie. HD2000-4000). TeraScale 2 GPUs should not exhibit this issue. * Note, this issue should be exclusive to TeraScale 1 GPUs (ie. HD2000-4000). TeraScale 2 GPUs should not exhibit this issue.

View File

@@ -10,6 +10,8 @@ The easiest way to debug yourself is via Patcher Settings. Here there are many d
* "Enable OpenCore DEBUG" * "Enable OpenCore DEBUG"
* "Enable Kext DEBUG" * "Enable Kext DEBUG"
![](../images/ocdebugimage.png)
When you've enabled these 3 options, rebuild OpenCore and install to your drive. This will provide much greater debug information as well as write logs to the EFI Partition. When you've enabled these 3 options, rebuild OpenCore and install to your drive. This will provide much greater debug information as well as write logs to the EFI Partition.
## Obtaining OpenCore logs from disk ## Obtaining OpenCore logs from disk

View File

@@ -20,4 +20,4 @@ This patcher is made of multiple external applications from different people and
* [VMM Patch Set](https://github.com/dortania/OpenCore-Legacy-Patcher/blob/4a8f61a01da72b38a4b2250386cc4b497a31a839/payloads/Config/config.plist#L1222-L1281) - parrotgeek1 * [VMM Patch Set](https://github.com/dortania/OpenCore-Legacy-Patcher/blob/4a8f61a01da72b38a4b2250386cc4b497a31a839/payloads/Config/config.plist#L1222-L1281) - parrotgeek1
* Apple Binaries - Apple Inc. * Apple Binaries - Apple Inc.
Remaining files within OpenCore Legacy Patcher are copyrighted 2020-2022 Mykola Grymalyuk & Dhinak G. For integration into other projects, please request written permission. Remaining files within OpenCore Legacy Patcher are distributed under BSD-4-Clause license.

View File

@@ -298,7 +298,8 @@ Below is an explanation of what Kexts OpenCore Legacy Patcher will inject into m
* Reason: Prevents AVXFSCompressionTypeZlib crash on pre AVX1.0 systems in 12.4+ * Reason: Prevents AVXFSCompressionTypeZlib crash on pre AVX1.0 systems in 12.4+
* SimpleMSR * SimpleMSR
* Reason: Disables BD PROCHOT to prevent firmware throttling on Nehalem+ MacBooks * Reason: Disables BD PROCHOT to prevent firmware throttling on Nehalem+ MacBooks
* LegacyKeyboardInjector
* Reason: Fixes function keys on MacBook4,1/MacBook5,2
::: :::

View File

@@ -1,7 +1,7 @@
# Post-Installation # Post-Installation
* [Booting without USB drive](#booting-without-usb-drive) * [Booting without USB drive](#booting-without-usb-drive)
* [Booting seamlessly without Verbose or OpenCore Picker](#booting-seamlessly-without-verbose-or-opencore-picker) * [Booting seamlessly without Boot Picker](#booting-seamlessly-without-boot-picker)
* [Applying Post Install Volume Patches](#applying-post-install-volume-patches) * [Applying Post Install Volume Patches](#applying-post-install-volume-patches)
## Booting without USB drive ## Booting without USB drive
@@ -16,17 +16,13 @@ Once you've installed macOS through OpenCore, you can boot up and go through the
And voila! No more USB drive required. And voila! No more USB drive required.
## Booting seamlessly without Verbose or OpenCore Picker ## Booting seamlessly without Boot Picker
To do this, run the OpenCore Patcher and head to Patcher Settings: To do this, run the OpenCore Patcher and head to Patcher Settings, then uncheck "Show OpenCore Bootpicker" on the Build tab:
![](../images/OCLP-GUI-Settings-ShowPicker.png) ![](../images/OCLP-GUI-Settings-ShowPicker.png)
Here you can change different patcher settings, however the main interest is: Once you've toggled it 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.
* 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.
## Enabling SIP ## Enabling SIP
@@ -34,6 +30,8 @@ For many users, SIP will be lowered by default on build. For Intel HD 4000 users
Note: Machines running macOS Ventura or systems with non-Metal GPUs cannot enable SIP outright, due to having a patched root volume. Enabling it will brick the installation. Note: Machines running macOS Ventura or systems with non-Metal GPUs cannot enable SIP outright, due to having a patched root volume. Enabling it will brick the installation.
Going forward with 0.6.6, SIP settings can be accessed from the Security tab shown in the images.
| SIP Enabled | SIP Lowered (Root Patching) | SIP Disabled | | SIP Enabled | SIP Lowered (Root Patching) | SIP Disabled |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| ![](../images/OCLP-GUI-Settings-SIP-Enabled.png) | ![](../images/OCLP-GUI-Settings-SIP-Root-Patch.png) | ![](../images/OCLP-GUI-Settings-SIP-Disabled.png) | | ![](../images/OCLP-GUI-Settings-SIP-Enabled.png) | ![](../images/OCLP-GUI-Settings-SIP-Root-Patch.png) | ![](../images/OCLP-GUI-Settings-SIP-Disabled.png) |
@@ -48,22 +46,16 @@ If you're unsure whether you should enable SIP, leave it as-is. Systems where yo
Post Install Volume Patches, sometimes also called root patches, are patches that have to be installed to disk for some older Macs to gain back functionality. Post Install Volume Patches, sometimes also called root patches, are patches that have to be installed to disk for some older Macs to gain back functionality.
OCLP v0.4.4 and higher include an autopatcher, which will automatically root patch your system but **only if the USB install media was created within OCLP.** OCLP will automatically root patch your system during a first time install **if the USB install media was created within OCLP.** Users will also be prompted to install these patches after macOS updates or whenever patches are not detected on the system. We recommend rebuilding OpenCore with the latest version of OCLP to take advantage of these new features.
Users with OCLP v0.4.4 or higher will also be prompted to install these patches after macOS updates or whenever patches are not detected on the system. We recommend rebuilding OpenCore with the latest version of OCLP to take advantage of these new features. Users can also see whether applicable patches have been installed, date and version the system was root patched with in the Post-Install Menu.
| Automatic install prompt | Status |
In OCLP v0.4.5 a new indicator was added to help users to see if, when and on what version the system was root patched. Note that the "Available patches" section above this does not track the status and will always show the patches that are available, whether they're installed or not.
| Automatic install prompt in 0.4.4+ | Last patched status in 0.4.5+ |
| :--- | :--- | | :--- | :--- |
| ![](../images/OCLP-GUI-root-patch-update.png) | ![](../images/OCLP-GUI-Root-Patch-Status.png) | | ![](../images/OCLP-GUI-root-patch-update.png) | ![](../images/OCLP-GUI-Root-Patch-Status.png) |
### Running Post Install patches manually ### Running Post Install patches manually
If you're using OCLP v0.4.3 or earlier, or need to run the patcher manually, you can do so with the app. There is no harm in trying to run the Patcher, as without compatible hardware, nothing will be done. You can see below on whether your hardware needs root volume patching or not. If you're using OCLP v0.4.3 or earlier, or need to run the patcher manually, you can do so with the app. There is no harm in trying to run the Patcher, as without compatible hardware, nothing will be done. You can see below on whether your hardware needs root volume patching or not.

View File

@@ -209,7 +209,7 @@ Ventura has dropped more models which includes all of the blacklisted Macs in qu
Firstly run the GUI version of OpenCore Legacy Patcher. Firstly run the GUI version of OpenCore Legacy Patcher.
Then go to **Settings**, go to **SMBIOS Settings**, set SMBIOS Spoof Level to **Moderate**. Set SMBIOS Spoof Model **one listed next to your native model in the table for spoofed models below.** Then go to **Settings** and **SMBIOS** tab, set SMBIOS Spoof Level to **Moderate**. Set SMBIOS Spoof Model **one listed next to your native model in the table for spoofed models below.**
Notice that "Allow native models" and "Allow Native Spoofs" **are NOT** enabled unlike on Monterey, this is on purpose. They are no longer relevant on Ventura and enabling them will cause boot issues. Notice that "Allow native models" and "Allow Native Spoofs" **are NOT** enabled unlike on Monterey, this is on purpose. They are no longer relevant on Ventura and enabling them will cause boot issues.
@@ -239,14 +239,16 @@ Spoofing to any model with native Ventura support should work, but these are the
::: details macOS Monterey ::: details macOS Monterey
Firstly, run the GUI version of OpenCore Legacy Patcher. Secondly, go to **Settings** and tick **Allow native models**. Firstly, run the GUI version of OpenCore Legacy Patcher. Secondly, go to **Settings** then the **App** tab and tick **Allow native models**.
Then, go to **SMBIOS Settings**, tick **Allow Native Spoofs**, set SMBIOS Spoof Level to **Moderate**. Set SMBIOS Spoof Model to **one listed next to your native model in the table for spoofed models below.** [](../images/OCLP-App-Allow-Native-Models.png)
Then, go to **SMBIOS** tab, tick **Allow spoofing native Macs**, set SMBIOS Spoof Level to **Moderate**. Set SMBIOS Spoof Model to **one listed next to your native model in the table for spoofed models below.**
| Main Settings view | SMBIOS settings | | Main Settings view | SMBIOS settings |
| :--- | :--- | | :--- | :--- |
| ![](../images/OCLP-allow-native-models.png) | ![](../images/OCLP-smbios-settings.png) | | ![](../images/OCLP-SMBIOS-Allow-Native-Spoof.png) | ![](../images/OCLP-smbios-settings.png) |
::: details Table for spoofed models (click to expand) ::: details Table for spoofed models (click to expand)

View File

@@ -7,7 +7,7 @@ With the release of OpenCore Legacy Patcher v0.5.0 and newer, early support for
Ventura's release dropped a large amount of Intel hardware, thus requiring the usage of OpenCore Legacy Patcher on the following models (in addition to previously removed models): Ventura's release dropped a large amount of Intel hardware, thus requiring the usage of OpenCore Legacy Patcher on the following models (in addition to previously removed models):
* iMac16,1 (21.5-inch, Late 2015) * iMac16,1 (21.5-inch, Late 2015)
* iMac16,2 (21.5-inch 4K, Late 2015) * iMac16,2 (21.5-inch and 21.5-inch 4K, Late 2015)
* iMac17,1 (27-inch 5K, Late 2015) * iMac17,1 (27-inch 5K, Late 2015)
* MacBook9,1 (12-inch, Early 2016) * MacBook9,1 (12-inch, Early 2016)
* MacBookAir7,1 (11-inch, Early 2015) * MacBookAir7,1 (11-inch, Early 2015)
@@ -78,11 +78,6 @@ With OpenCore Legacy Patcher v0.6.0, basic support has been implemented via Root
::: :::
### Ethernet issue with Early 2008 Mac Pro
MacPro3,1 suffers from the 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 and newer) ::: details Legacy Wireless Support (Resolved in v0.6.0 and newer)

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 450 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 506 KiB

After

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 KiB

After

Width:  |  Height:  |  Size: 530 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 499 KiB

After

Width:  |  Height:  |  Size: 533 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 530 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 451 KiB

BIN
images/ocdebugimage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 KiB

After

Width:  |  Height:  |  Size: 445 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 446 KiB

View File

@@ -1383,6 +1383,24 @@
<key>PlistPath</key> <key>PlistPath</key>
<string>Contents/Info.plist</string> <string>Contents/Info.plist</string>
</dict> </dict>
<dict>
<key>Arch</key>
<string>x86_64</string>
<key>BundlePath</key>
<string>LegacyKeyboardInjector.kext</string>
<key>Comment</key>
<string>Legacy Keyboard support for macOS 11+</string>
<key>Enabled</key>
<false/>
<key>ExecutablePath</key>
<string></string>
<key>MaxKernel</key>
<string></string>
<key>MinKernel</key>
<string>17.0.0</string>
<key>PlistPath</key>
<string>Contents/Info.plist</string>
</dict>
<dict> <dict>
<key>Arch</key> <key>Arch</key>
<string>x86_64</string> <string>x86_64</string>
@@ -1943,6 +1961,36 @@
<key>Skip</key> <key>Skip</key>
<integer>0</integer> <integer>0</integer>
</dict> </dict>
<dict>
<key>Arch</key>
<string>x86_64</string>
<key>Base</key>
<string>__ZN17IOPCIConfigurator18IOPCIIsHotplugPortEP16IOPCIConfigEntry</string>
<key>Comment</key>
<string>CaseySJ - Fix PCI bus enumeration</string>
<key>Count</key>
<integer>1</integer>
<key>Enabled</key>
<false/>
<key>Find</key>
<data>hNt1S0GLVzg=</data>
<key>Identifier</key>
<string>com.apple.iokit.IOPCIFamily</string>
<key>Limit</key>
<integer>0</integer>
<key>Mask</key>
<data></data>
<key>MaxKernel</key>
<string></string>
<key>MinKernel</key>
<string>22.0.0</string>
<key>Replace</key>
<data>hNvrS0GLVzg=</data>
<key>ReplaceMask</key>
<data></data>
<key>Skip</key>
<integer>0</integer>
</dict>
</array> </array>
<key>Quirks</key> <key>Quirks</key>
<dict> <dict>
@@ -1960,6 +2008,8 @@
<false/> <false/>
<key>DisableIoMapper</key> <key>DisableIoMapper</key>
<false/> <false/>
<key>DisableIoMapperMapping</key>
<false/>
<key>DisableLinkeditJettison</key> <key>DisableLinkeditJettison</key>
<false/> <false/>
<key>DisableRtcChecksum</key> <key>DisableRtcChecksum</key>
@@ -2617,6 +2667,8 @@
<string>Disabled</string> <string>Disabled</string>
<key>IgnoreTextInGraphics</key> <key>IgnoreTextInGraphics</key>
<false/> <false/>
<key>InitialMode</key>
<string>Auto</string>
<key>ProvideConsoleGop</key> <key>ProvideConsoleGop</key>
<true/> <true/>
<key>ReconnectGraphicsOnConnect</key> <key>ReconnectGraphicsOnConnect</key>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,4 +1,11 @@
#!/bin/sh #!/bin/sh
# Create alias for OpenCore-Patcher.app
if [ ! -d "/Applications/OpenCore-Patcher.app" ]; then
ln -s "/Library/Application Support/Dortania/OpenCore-Patcher.app" "/Applications/OpenCore-Patcher.app"
fi
# Start root patching
app_path="/Library/Application Support/Dortania/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher" app_path="/Library/Application Support/Dortania/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher"
args="--patch_sys_vol" args="--patch_sys_vol"
"$app_path" "$args" &> "/Users/Shared/.OCLP-AutoPatcher-Log-$(date +"%Y_%m_%d_%I_%M_%p").txt" "$app_path" "$args" &> "/Users/Shared/.OCLP-AutoPatcher-Log-$(date +"%Y_%m_%d_%I_%M_%p").txt"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -152,6 +152,26 @@ class GenerateKexts:
self._get_latest_release(kext_folder, kext_name) self._get_latest_release(kext_folder, kext_name)
def _is_build_nightly(self, kext: str, version: str) -> bool:
# Load CHANGELOG.md
changelog_path = Path(f"../../CHANGELOG.md").absolute()
with open(changelog_path, "r") as changelog_file:
changelog = changelog_file.read()
# Check if kext is in changelog
if kext not in changelog:
return False
# Check if kext is 'rolling' or 'nightly'
for line in changelog.split("\n"):
if kext in line and version in line:
if ("rolling" in line or "nightly" in line):
return True
break
return False
def _get_latest_release(self, kext_folder, kext_name, override_kext_zip_name=None): def _get_latest_release(self, kext_folder, kext_name, override_kext_zip_name=None):
# Get latest release from GitHub API # Get latest release from GitHub API
repo_url = KEXT_DICTIONARY[kext_folder][kext_name]["Repository"].replace("https://github.com", "https://api.github.com/repos") repo_url = KEXT_DICTIONARY[kext_folder][kext_name]["Repository"].replace("https://github.com", "https://api.github.com/repos")
@@ -178,6 +198,11 @@ class GenerateKexts:
if packaging.version.parse(remote_version) <= packaging.version.parse(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}") print(f" {kext_name} {variant} is up to date: v{local_version}")
if remote_version == local_version:
if self._is_build_nightly(kext_name, local_version) is False:
continue
print(f" {kext_name} {variant} is a nightly build, updating...")
else:
continue continue
for asset in latest_release["assets"]: for asset in latest_release["assets"]:
@@ -186,9 +211,9 @@ class GenerateKexts:
print(f" Downloading {kext_name} {variant}: v{remote_version}...") 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" 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.replace(f'v{remote_version}', f'v{local_version}')}").exists():
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}')}"]) subprocess.run(["rm", "-rf", f"./{kext_folder}/{zip_name.replace(f'v{remote_version}', f'v{local_version}')}"])
self._download_file(asset["browser_download_url"], f"./{kext_folder}/{zip_name}", f"{kext_name}.kext")
self._update_constants_file(KEXT_DICTIONARY[kext_folder][kext_name]["Constants Variable"], local_version, remote_version) self._update_constants_file(KEXT_DICTIONARY[kext_folder][kext_name]["Constants Variable"], local_version, remote_version)
if override_kext_zip_name: if override_kext_zip_name:

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>OpenCore-Patcher</string>
<key>CFBundleExecutable</key>
<string>OpenCore-Patcher</string>
<key>CFBundleIconFile</key>
<string>OC-Patcher.icns</string>
<key>CFBundleIdentifier</key>
<string>com.dortania.opencore-legacy-patcher-helper</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>OpenCore-Patcher</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>LSMinimumSystemVersion</key>
<string>10.10.0</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2020-2023 Dortania</string>
<key>LSUIElement</key>
<true/>
</dict>
</plist>

View File

@@ -4,3 +4,4 @@ wxpython
pyinstaller pyinstaller
packaging packaging
py_sip_xnu py_sip_xnu
py-applescript

View File

@@ -9,8 +9,9 @@ from resources import network_handler, constants, global_settings
DATE_FORMAT: str = "%Y-%m-%d %H-%M-%S" DATE_FORMAT: str = "%Y-%m-%d %H-%M-%S"
ANALYTICS_SERVER: str = "" ANALYTICS_SERVER: str = ""
SITE_KEY: str = "" SITE_KEY: str = ""
CRASH_URL: str = ANALYTICS_SERVER + "/crash"
VALID_ENTRIES: dict = { VALID_ANALYTICS_ENTRIES: dict = {
'KEY': str, # Prevent abuse (embedded at compile time) 'KEY': str, # Prevent abuse (embedded at compile time)
'UNIQUE_IDENTITY': str, # Host's UUID as SHA1 hash 'UNIQUE_IDENTITY': str, # Host's UUID as SHA1 hash
'APPLICATION_NAME': str, # ex. OpenCore Legacy Patcher 'APPLICATION_NAME': str, # ex. OpenCore Legacy Patcher
@@ -23,17 +24,63 @@ VALID_ENTRIES: dict = {
'TIMESTAMP': datetime.datetime, # ex. 2021-09-01-12-00-00 'TIMESTAMP': datetime.datetime, # ex. 2021-09-01-12-00-00
} }
VALID_CRASH_ENTRIES: dict = {
'KEY': str, # Prevent abuse (embedded at compile time)
'APPLICATION_VERSION': str, # ex. 0.2.0
'APPLICATION_COMMIT': str, # ex. 0.2.0 or {commit hash if not a release}
'OS_VERSION': str, # ex. 10.15.7
'MODEL': str, # ex. MacBookPro11,5
'TIMESTAMP': datetime.datetime, # ex. 2021-09-01-12-00-00
'CRASH_LOG': str, # ex. "This is a crash log"
}
class Analytics: class Analytics:
def __init__(self, global_constants: constants.Constants) -> None: def __init__(self, global_constants: constants.Constants) -> None:
self.constants: constants.Constants = global_constants self.constants: constants.Constants = global_constants
self.unique_identity = str(self.constants.computer.uuid_sha1)
self.application = str("OpenCore Legacy Patcher")
self.version = str(self.constants.patcher_version)
self.os = str(self.constants.detected_os_version)
self.model = str(self.constants.computer.real_model)
self.date = str(datetime.datetime.now().strftime(DATE_FORMAT))
def send_analytics(self) -> None:
if global_settings.GlobalEnviromentSettings().read_property("DisableCrashAndAnalyticsReporting") is True: if global_settings.GlobalEnviromentSettings().read_property("DisableCrashAndAnalyticsReporting") is True:
return return
self._generate_base_data() self._generate_base_data()
self._post_data() self._post_analytics_data()
def send_crash_report(self, log_file: Path) -> None:
if ANALYTICS_SERVER == "":
return
if SITE_KEY == "":
return
if global_settings.GlobalEnviromentSettings().read_property("DisableCrashAndAnalyticsReporting") is True:
return
if not log_file.exists():
return
if self.constants.commit_info[0].startswith("refs/tags"):
# Avoid being overloaded with crash reports
return
commit_info = self.constants.commit_info[0].split("/")[-1] + "_" + self.constants.commit_info[1].split("T")[0] + "_" + self.constants.commit_info[2].split("/")[-1]
crash_data= {
"KEY": SITE_KEY,
"APPLICATION_VERSION": self.version,
"APPLICATION_COMMIT": commit_info,
"OS_VERSION": self.os,
"MODEL": self.model,
"TIMESTAMP": self.date,
"CRASH_LOG": log_file.read_text()
}
network_handler.NetworkUtilities().post(CRASH_URL, json = crash_data)
def _get_country(self) -> str: def _get_country(self) -> str:
@@ -54,12 +101,6 @@ class Analytics:
def _generate_base_data(self) -> None: def _generate_base_data(self) -> None:
self.unique_identity = str(self.constants.computer.uuid_sha1)
self.application = str("OpenCore Legacy Patcher")
self.version = str(self.constants.patcher_version)
self.os = str( self.constants.detected_os_version)
self.model = str(self.constants.computer.real_model)
self.gpus = [] self.gpus = []
self.firmware = str(self.constants.computer.firmware_vendor) self.firmware = str(self.constants.computer.firmware_vendor)
@@ -78,14 +119,14 @@ class Analytics:
'GPUS': self.gpus, 'GPUS': self.gpus,
'FIRMWARE': self.firmware, 'FIRMWARE': self.firmware,
'LOCATION': self.location, 'LOCATION': self.location,
'TIMESTAMP': str(datetime.datetime.now().strftime(DATE_FORMAT)), 'TIMESTAMP': self.date,
} }
# convert to JSON: # convert to JSON:
self.data = json.dumps(self.data) self.data = json.dumps(self.data)
def _post_data(self) -> None: def _post_analytics_data(self) -> None:
# Post data to analytics server # Post data to analytics server
if ANALYTICS_SERVER == "": if ANALYTICS_SERVER == "":
return return

View File

@@ -50,7 +50,7 @@ class arguments:
""" """
Enter validation mode Enter validation mode
""" """
logging.info("Set Validation Mode")
validation.PatcherValidation(self.constants) validation.PatcherValidation(self.constants)
@@ -59,9 +59,9 @@ class arguments:
Start root volume patching Start root volume patching
""" """
logging.info("- Set System Volume patching") logging.info("Set System Volume patching")
if "Library/InstallerSandboxes/" in str(self.constants.payload_path): if "Library/InstallerSandboxes/" in str(self.constants.payload_path):
logging.info("- Running from Installer Sandbox") logging.info("- Running from Installer Sandbox, blocking OS updaters")
thread = threading.Thread(target=sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, None).start_patch) thread = threading.Thread(target=sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, None).start_patch)
thread.start() thread.start()
while thread.is_alive(): while thread.is_alive():
@@ -75,7 +75,7 @@ class arguments:
""" """
Start root volume unpatching Start root volume unpatching
""" """
logging.info("- Set System 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() sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, None).start_unpatch()
@@ -84,7 +84,7 @@ class arguments:
Start root volume auto patching Start root volume auto patching
""" """
logging.info("- Set Auto patching") logging.info("Set Auto patching")
sys_patch_auto.AutomaticSysPatch(self.constants).start_auto_patch() sys_patch_auto.AutomaticSysPatch(self.constants).start_auto_patch()
@@ -92,6 +92,7 @@ class arguments:
""" """
Start config building process Start config building process
""" """
logging.info("Set OpenCore Build")
if self.args.model: if self.args.model:
if self.args.model: if self.args.model:

View File

@@ -113,7 +113,7 @@ class BuildOpenCore:
self.config["#Revision"]["Hardware-Probe"] = pickle.dumps(computer_copy) self.config["#Revision"]["Hardware-Probe"] = pickle.dumps(computer_copy)
else: else:
self.config["#Revision"]["Build-Type"] = "OpenCore Built for External Machine" self.config["#Revision"]["Build-Type"] = "OpenCore Built for External Machine"
self.config["#Revision"]["OpenCore-Version"] = f"{self.constants.opencore_version} - {self.constants.opencore_build} - {self.constants.opencore_commit}" self.config["#Revision"]["OpenCore-Version"] = f"{self.constants.opencore_version} - {self.constants.opencore_build}"
self.config["#Revision"]["Original-Model"] = self.model self.config["#Revision"]["Original-Model"] = self.model
self.config["NVRAM"]["Add"]["4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102"]["OCLP-Version"] = f"{self.constants.patcher_version}" self.config["NVRAM"]["Add"]["4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102"]["OCLP-Version"] = f"{self.constants.patcher_version}"
self.config["NVRAM"]["Add"]["4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102"]["OCLP-Model"] = self.model self.config["NVRAM"]["Add"]["4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102"]["OCLP-Model"] = self.model
@@ -153,5 +153,3 @@ class BuildOpenCore:
logging.info(f"Your OpenCore EFI for {self.model} has been built at:") logging.info(f"Your OpenCore EFI for {self.model} has been built at:")
logging.info(f" {self.constants.opencore_release_folder}") logging.info(f" {self.constants.opencore_release_folder}")
logging.info("") logging.info("")
if self.constants.gui_mode is False:
input("Press [Enter] to continue\n")

View File

@@ -73,7 +73,7 @@ class BuildFirmware:
support.BuildSupport(self.model, self.constants, self.config).enable_kext("AppleIntelCPUPowerManagement.kext", self.constants.aicpupm_version, self.constants.aicpupm_path) support.BuildSupport(self.model, self.constants, self.config).enable_kext("AppleIntelCPUPowerManagement.kext", self.constants.aicpupm_version, self.constants.aicpupm_path)
support.BuildSupport(self.model, self.constants, self.config).enable_kext("AppleIntelCPUPowerManagementClient.kext", self.constants.aicpupm_version, self.constants.aicpupm_client_path) support.BuildSupport(self.model, self.constants, self.config).enable_kext("AppleIntelCPUPowerManagementClient.kext", self.constants.aicpupm_version, self.constants.aicpupm_client_path)
if smbios_data.smbios_dictionary[self.model]["CPU Generation"] <= cpu_data.cpu_data.sandy_bridge.value or self.constants.disable_xcpm is True: if smbios_data.smbios_dictionary[self.model]["CPU Generation"] <= cpu_data.cpu_data.sandy_bridge.value or self.constants.disable_fw_throttle is True:
# With macOS 12.3 Beta 1, Apple dropped the 'plugin-type' check within X86PlatformPlugin # With macOS 12.3 Beta 1, Apple dropped the 'plugin-type' check within X86PlatformPlugin
# Because of this, X86PP will match onto the CPU instead of ACPI_SMC_PlatformPlugin # Because of this, X86PP will match onto the CPU instead of ACPI_SMC_PlatformPlugin
# This causes power management to break on pre-Ivy Bridge CPUs as they don't have correct # This causes power management to break on pre-Ivy Bridge CPUs as they don't have correct
@@ -81,11 +81,11 @@ class BuildFirmware:
# This patch will simply increase ASPP's 'IOProbeScore' to outmatch X86PP # This patch will simply increase ASPP's 'IOProbeScore' to outmatch X86PP
logging.info("- Overriding ACPI SMC matching") logging.info("- Overriding ACPI SMC matching")
support.BuildSupport(self.model, self.constants, self.config).enable_kext("ASPP-Override.kext", self.constants.aspp_override_version, self.constants.aspp_override_path) support.BuildSupport(self.model, self.constants, self.config).enable_kext("ASPP-Override.kext", self.constants.aspp_override_version, self.constants.aspp_override_path)
if self.constants.disable_xcpm is True: if self.constants.disable_fw_throttle is True:
# Only inject on older OSes if user requests # Only inject on older OSes if user requests
support.BuildSupport(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Add"], "BundlePath", "ASPP-Override.kext")["MinKernel"] = "" support.BuildSupport(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Add"], "BundlePath", "ASPP-Override.kext")["MinKernel"] = ""
if self.constants.disable_msr_power_ctl is True and smbios_data.smbios_dictionary[self.model]["CPU Generation"] >= cpu_data.cpu_data.nehalem.value: if self.constants.disable_fw_throttle is True and smbios_data.smbios_dictionary[self.model]["CPU Generation"] >= cpu_data.cpu_data.nehalem.value:
logging.info("- Disabling Firmware Throttling") logging.info("- Disabling Firmware Throttling")
# Nehalem and newer systems force firmware throttling via MSR_POWER_CTL # Nehalem and newer systems force firmware throttling via MSR_POWER_CTL
support.BuildSupport(self.model, self.constants, self.config).enable_kext("SimpleMSR.kext", self.constants.simplemsr_version, self.constants.simplemsr_path) support.BuildSupport(self.model, self.constants, self.config).enable_kext("SimpleMSR.kext", self.constants.simplemsr_version, self.constants.simplemsr_path)
@@ -220,17 +220,28 @@ class BuildFirmware:
self._dual_dp_handling() self._dual_dp_handling()
# Force VMM as a temporary solution to getting the MacPro6,1 booting in Ventura # Patches IOPCIConfigurator.cpp's IOPCIIsHotplugPort to skip configRead16/32 calls
# With macOS Ventura, Apple removed AppleIntelCPUPowerManagement.kext and assumed XCPM support across all Macs # Credit to CaseySJ for original discovery:
# This change resulted in broken OS booting as the machine had no power management support # - Patch: https://github.com/AMD-OSX/AMD_Vanilla/pull/196
# Currently the AICPUPM fix is not fully functional, thus forcing VMM is a temporary solution # - Source: https://github.com/apple-oss-distributions/IOPCIFamily/blob/main/IOPCIConfigurator.cpp#L968-L1022
# Waiting for XNU source to be released to fix this properly #
# Ref: https://forums.macrumors.com/threads/opencore-on-the-mac-pro.2207814/ # Currently all pre-Sandy Bridge Macs lacking an iGPU benefit from this patch as well as MacPro6,1
if self.model in ["MacPro6,1", "iMac7,1", "iMac8,1", "MacBookPro4,1"] or self.constants.set_vmm_cpuid is True: # Otherwise some graphics hardware will fail to wake, macOS will misreport hardware as ExpressCard-based,
# prevents MacPro6,1 from both booting unaccelerated and breaks low power states.
if (
self.model in ["MacPro6,1", "MacBookPro4,1"] or
(
smbios_data.smbios_dictionary[self.model]["CPU Generation"] < cpu_data.cpu_data.sandy_bridge.value and \
not self.model.startswith("MacBook")
)
):
logging.info("- Adding PCI Bus Enumeration Patch")
support.BuildSupport(self.model, self.constants, self.config).get_item_by_kv(self.config["Kernel"]["Patch"], "Comment", "CaseySJ - Fix PCI bus enumeration")["Enabled"] = True
if self.constants.set_vmm_cpuid is True:
logging.info("- Enabling VMM patch") logging.info("- Enabling VMM patch")
self.config["Kernel"]["Emulate"]["Cpuid1Data"] = binascii.unhexlify("00000000000000000000008000000000") self.config["Kernel"]["Emulate"]["Cpuid1Data"] = binascii.unhexlify("00000000000000000000008000000000")
self.config["Kernel"]["Emulate"]["Cpuid1Mask"] = binascii.unhexlify("00000000000000000000008000000000") self.config["Kernel"]["Emulate"]["Cpuid1Mask"] = binascii.unhexlify("00000000000000000000008000000000")
self.config["Kernel"]["Emulate"]["MinKernel"] = "22.0.0"
if ( if (
self.model.startswith("MacBook") self.model.startswith("MacBook")

View File

@@ -259,7 +259,7 @@ class BuildGraphicsAudio:
"CAIL,CAIL_DisableUVDPowerGating": 1, "CAIL,CAIL_DisableUVDPowerGating": 1,
"CAIL,CAIL_DisableVCEPowerGating": 1, "CAIL,CAIL_DisableVCEPowerGating": 1,
}) })
if self.constants.imac_model == "Legacy GCN": if self.constants.imac_model == "GCN":
logging.info("- Adding Legacy GCN Power Gate Patches") logging.info("- Adding Legacy GCN Power Gate Patches")
self.config["DeviceProperties"]["Add"][backlight_path].update({ self.config["DeviceProperties"]["Add"][backlight_path].update({
"CAIL,CAIL_DisableDrmdmaPowerGating": 1, "CAIL,CAIL_DisableDrmdmaPowerGating": 1,
@@ -274,7 +274,7 @@ class BuildGraphicsAudio:
"CAIL,CAIL_DisableUVDPowerGating": 1, "CAIL,CAIL_DisableUVDPowerGating": 1,
"CAIL,CAIL_DisableVCEPowerGating": 1, "CAIL,CAIL_DisableVCEPowerGating": 1,
}) })
elif self.constants.imac_model == "AMD Lexa": elif self.constants.imac_model == "Lexa":
logging.info("- Adding Lexa Spoofing Patches") logging.info("- Adding Lexa Spoofing Patches")
self.config["DeviceProperties"]["Add"][backlight_path].update({ self.config["DeviceProperties"]["Add"][backlight_path].update({
"model": "AMD Radeon Pro WX 3200", "model": "AMD Radeon Pro WX 3200",
@@ -285,7 +285,7 @@ class BuildGraphicsAudio:
"model": "AMD Radeon Pro WX 3200", "model": "AMD Radeon Pro WX 3200",
"device-id": binascii.unhexlify("FF67"), "device-id": binascii.unhexlify("FF67"),
}) })
elif self.constants.imac_model == "AMD Navi": elif self.constants.imac_model == "Navi":
logging.info("- Adding Navi Spoofing Patches") logging.info("- Adding Navi Spoofing Patches")
navi_backlight_path = backlight_path+"/Pci(0x0,0x0)/Pci(0x0,0x0)" navi_backlight_path = backlight_path+"/Pci(0x0,0x0)/Pci(0x0,0x0)"
self.config["DeviceProperties"]["Add"][navi_backlight_path] = { self.config["DeviceProperties"]["Add"][navi_backlight_path] = {

View File

@@ -106,23 +106,6 @@ class BuildMiscellaneous:
logging.info("- Disabling memory error reporting") logging.info("- Disabling memory error reporting")
re_block_args.append("pcie") re_block_args.append("pcie")
# Resolve mediaanalysisd crashing on 3802 GPUs
gpu_dict = [] if self.constants.custom_model else self.constants.computer.gpus
if gpu_dict == []:
gpu_dict = smbios_data.smbios_dictionary[self.model]["Stock GPUs"] if self.model in smbios_data.smbios_dictionary else []
for gpu in gpu_dict:
if not self.constants.custom_model:
gpu = gpu.arch
if gpu in [
device_probe.Intel.Archs.Ivy_Bridge,
device_probe.Intel.Archs.Haswell,
device_probe.NVIDIA.Archs.Kepler,
]:
logging.info("- Disabling mediaanalysisd")
re_block_args.append("media")
break
return re_block_args return re_block_args
@@ -192,19 +175,20 @@ class BuildMiscellaneous:
Trackpad Handler Trackpad Handler
""" """
# Pre-Force Touch trackpad support for macOS Ventura # Pre-Force Touch trackpad & keyboard support for macOS Ventura
if smbios_data.smbios_dictionary[self.model]["CPU Generation"] < cpu_data.cpu_data.skylake.value: if smbios_data.smbios_dictionary[self.model]["CPU Generation"] < cpu_data.cpu_data.skylake.value:
if self.model.startswith("MacBook"): if self.model.startswith("MacBook"):
# These units got force touch early, so ignore them # These units got force touch & the new keyboard mapping early, so ignore them
if self.model not in ["MacBookPro11,4", "MacBookPro11,5", "MacBookPro12,1", "MacBook8,1"]: if self.model not in ["MacBookPro11,4", "MacBookPro11,5", "MacBookPro12,1", "MacBook8,1"]:
support.BuildSupport(self.model, self.constants, self.config).enable_kext("AppleUSBTopCase.kext", self.constants.topcase_version, self.constants.top_case_path) support.BuildSupport(self.model, self.constants, self.config).enable_kext("AppleUSBTopCase.kext", self.constants.topcase_version, self.constants.top_case_path)
support.BuildSupport(self.model, self.constants, self.config).get_kext_by_bundle_path("AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCButtons.kext")["Enabled"] = True support.BuildSupport(self.model, self.constants, self.config).get_kext_by_bundle_path("AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCButtons.kext")["Enabled"] = True
support.BuildSupport(self.model, self.constants, self.config).get_kext_by_bundle_path("AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext")["Enabled"] = True support.BuildSupport(self.model, self.constants, self.config).get_kext_by_bundle_path("AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext")["Enabled"] = True
support.BuildSupport(self.model, self.constants, self.config).get_kext_by_bundle_path("AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyEventDriver.kext")["Enabled"] = True support.BuildSupport(self.model, self.constants, self.config).get_kext_by_bundle_path("AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyEventDriver.kext")["Enabled"] = True
support.BuildSupport(self.model, self.constants, self.config).enable_kext("AppleUSBMultitouch.kext", self.constants.multitouch_version, self.constants.multitouch_path) support.BuildSupport(self.model, self.constants, self.config).enable_kext("AppleUSBMultitouch.kext", self.constants.multitouch_version, self.constants.multitouch_path)
# Legacy Trackpad support # Legacy Trackpad & Keyboard support
if self.model in ["MacBook4,1", "MacBook5,2"]: if self.model in ["MacBook4,1", "MacBook5,2"]:
support.BuildSupport(self.model, self.constants, self.config).enable_kext("AppleUSBTrackpad.kext", self.constants.apple_trackpad, self.constants.apple_trackpad_path) support.BuildSupport(self.model, self.constants, self.config).enable_kext("AppleUSBTrackpad.kext", self.constants.apple_trackpad, self.constants.apple_trackpad_path)
support.BuildSupport(self.model, self.constants, self.config).enable_kext("LegacyKeyboardInjector.kext", self.constants.legacy_keyboard, self.constants.legacy_keyboard_path) # Inject legacy personalities into AppleUSBTCKeyboard and AppleUSBTCKeyEventDriver
def _thunderbolt_handling(self) -> None: def _thunderbolt_handling(self) -> None:

View File

@@ -12,9 +12,10 @@ from data import os_data
class Constants: class Constants:
def __init__(self) -> None: def __init__(self) -> None:
# Patcher Versioning # Patcher Versioning
self.patcher_version: str = "0.6.5" # OpenCore-Legacy-Patcher self.patcher_version: str = "0.6.7" # OpenCore-Legacy-Patcher
self.patcher_support_pkg_version: str = "0.9.7" # PatcherSupportPkg self.patcher_support_pkg_version: str = "1.1.2" # PatcherSupportPkg
self.copyright_date: str = "Copyright © 2020-2023 Dortania" self.copyright_date: str = "Copyright © 2020-2023 Dortania"
self.patcher_name: str = "OpenCore Legacy Patcher"
# URLs # URLs
self.url_patcher_support_pkg: str = "https://github.com/dortania/PatcherSupportPkg/releases/download/" self.url_patcher_support_pkg: str = "https://github.com/dortania/PatcherSupportPkg/releases/download/"
@@ -26,13 +27,12 @@ class Constants:
# OpenCore Versioning # OpenCore Versioning
# https://github.com/acidanthera/OpenCorePkg # https://github.com/acidanthera/OpenCorePkg
self.opencore_commit: str = "41b8aca - 04-03-2023" self.opencore_version: str = "0.9.2"
self.opencore_version: str = "0.9.1"
# Kext Versioning # Kext Versioning
## Acidanthera ## Acidanthera
## https://github.com/acidanthera ## https://github.com/acidanthera
self.lilu_version: str = "1.6.4" # Lilu self.lilu_version: str = "1.6.6" # Lilu
self.whatevergreen_version: str = "1.6.4" # WhateverGreen self.whatevergreen_version: str = "1.6.4" # WhateverGreen
self.whatevergreen_navi_version: str = "1.6.4-Navi" # WhateverGreen (Navi Patch) self.whatevergreen_navi_version: str = "1.6.4-Navi" # WhateverGreen (Navi Patch)
self.airportbcrmfixup_version: str = "2.1.7" # AirPortBrcmFixup self.airportbcrmfixup_version: str = "2.1.7" # AirPortBrcmFixup
@@ -42,7 +42,7 @@ class Constants:
self.featureunlock_version: str = "1.1.4" # FeatureUnlock self.featureunlock_version: str = "1.1.4" # FeatureUnlock
self.debugenhancer_version: str = "1.0.7" # DebugEnhancer self.debugenhancer_version: str = "1.0.7" # DebugEnhancer
self.cpufriend_version: str = "1.2.6" # CPUFriend self.cpufriend_version: str = "1.2.6" # CPUFriend
self.bluetool_version: str = "2.6.5" # BlueToolFixup (BrcmPatchRAM) self.bluetool_version: str = "2.6.6" # BlueToolFixup (BrcmPatchRAM)
self.cslvfixup_version: str = "2.6.1" # CSLVFixup self.cslvfixup_version: str = "2.6.1" # CSLVFixup
self.autopkg_version: str = "1.0.2" # AutoPkgInstaller self.autopkg_version: str = "1.0.2" # AutoPkgInstaller
self.cryptexfixup_version: str = "1.0.1" # CryptexFixup self.cryptexfixup_version: str = "1.0.1" # CryptexFixup
@@ -105,15 +105,19 @@ class Constants:
## https://github.com/flagersgit/KDKlessWorkaround ## https://github.com/flagersgit/KDKlessWorkaround
self.kdkless_version: str = "1.0.0" self.kdkless_version: str = "1.0.0"
## Jazzzny
self.legacy_keyboard: str = "1.0.0" # LegacyKeyboardInjector - Jazzzny
# Get resource path # Get resource path
self.current_path: Path = Path(__file__).parent.parent.resolve() self.current_path: Path = Path(__file__).parent.parent.resolve()
self.original_path: Path = Path(__file__).parent.parent.resolve()
self.payload_path: Path = self.current_path / Path("payloads") self.payload_path: Path = self.current_path / Path("payloads")
# Patcher Settings # Patcher Settings
## Internal settings ## Internal settings
self.allow_oc_everywhere: bool = False # Set whether Patcher can be run on unsupported Macs 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.gui_mode: bool = False # Determine whether running in a GUI or TUI
self.cli_mode: bool = False # Determine if running in CLI mode self.cli_mode: bool = True # Determine if running in CLI mode
self.validate: bool = False # Enable validation testing for CI self.validate: bool = False # Enable validation testing for CI
self.recovery_status: bool = False # Detect if booted into RecoveryOS self.recovery_status: bool = False # Detect if booted into RecoveryOS
self.ignore_updates: bool = False # Ignore OCLP updates self.ignore_updates: bool = False # Ignore OCLP updates
@@ -129,6 +133,8 @@ class Constants:
self.launcher_script: str = None # Determine launch file path (None if 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.booted_oc_disk: str = None # Determine current disk OCLP booted from
self.unpack_thread = None # Determine if unpack thread finished (threading.Thread) self.unpack_thread = None # Determine if unpack thread finished (threading.Thread)
self.update_stage: int = 0 # Determine update stage (see gui_support.py)
self.log_filepath: Path = None # Path to log file
self.commit_info: tuple = (None, None, None) # Commit info (Branch, Commit Date, Commit URL) self.commit_info: tuple = (None, None, None) # Commit info (Branch, Commit Date, Commit URL)
@@ -203,13 +209,13 @@ class Constants:
self.dGPU_switch: bool = False # Set Display GPU Switching for Windows self.dGPU_switch: bool = False # Set Display GPU Switching for Windows
self.force_surplus: bool = False # Force SurPlus patch in newer OSes self.force_surplus: bool = False # Force SurPlus patch in newer OSes
self.force_latest_psp: bool = False # Force latest PatcherSupportPkg self.force_latest_psp: bool = False # Force latest PatcherSupportPkg
self.disable_msr_power_ctl: bool = False # Disable MSR Power Control (missing battery throttling) self.disable_fw_throttle: bool = False # Disable MSR Power Control and XCPM
self.software_demux: bool = False # Enable Software Demux patch set self.software_demux: bool = False # Enable Software Demux patch set
self.force_vmm: bool = False # Force VMM patch self.force_vmm: bool = False # Force VMM patch
self.disable_connectdrivers: bool = False # Disable ConnectDrivers (hibernation) self.disable_connectdrivers: bool = False # Disable ConnectDrivers (hibernation)
self.set_content_caching: bool = False # Set Content Caching 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_vmm_cpuid: bool = False # Set VMM bit inside CPUID
self.disable_cat_colorsync: bool = False # Disable the ColorSync patch to regain Display Profiles
self.set_alc_usage: bool = True # Set AppleALC usage self.set_alc_usage: bool = True # Set AppleALC usage
self.allow_3rd_party_drives: bool = True # Allow ThridPartyDrives quirk self.allow_3rd_party_drives: bool = True # Allow ThridPartyDrives quirk
self.allow_nvme_fixing: bool = True # Allow NVMe Kernel Space Patches self.allow_nvme_fixing: bool = True # Allow NVMe Kernel Space Patches
@@ -224,6 +230,17 @@ class Constants:
] ]
# Payload Location # Payload Location
# Support Disk Images
@property
def payload_path_dmg(self):
return self.original_path / Path("payloads.dmg")
@property
def payload_local_binaries_root_path_dmg(self):
return self.original_path / Path("Universal-Binaries.dmg")
# OpenCore # OpenCore
@property @property
def opencore_zip_source(self): def opencore_zip_source(self):
@@ -485,6 +502,10 @@ class Constants:
def apple_isight_path(self): def apple_isight_path(self):
return self.payload_kexts_path / Path(f"Misc/LegacyUSBVideoSupport-v{self.apple_isight_version}.zip") return self.payload_kexts_path / Path(f"Misc/LegacyUSBVideoSupport-v{self.apple_isight_version}.zip")
@property
def legacy_keyboard_path(self):
return self.payload_kexts_path / Path(f"Misc/LegacyKeyboardInjector-v{self.legacy_keyboard}.zip")
@property @property
def apple_raid_path(self): def apple_raid_path(self):
return self.payload_kexts_path / Path(f"Misc/AppleRAIDCard-v{self.apple_raid_version}.zip") return self.payload_kexts_path / Path(f"Misc/AppleRAIDCard-v{self.apple_raid_version}.zip")
@@ -605,7 +626,7 @@ class Constants:
@property @property
def oclp_helper_path(self): def oclp_helper_path(self):
return self.payload_path / Path("Tools/OCLP-Helper") return self.payload_path / Path("Tools/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher")
@property @property
def rsrrepair_userspace_path(self): def rsrrepair_userspace_path(self):
@@ -649,14 +670,16 @@ class Constants:
def payload_local_binaries_root_path(self): def payload_local_binaries_root_path(self):
return self.payload_path / Path("Universal-Binaries") return self.payload_path / Path("Universal-Binaries")
@property
def payload_local_binaries_root_path_zip(self):
return self.payload_path / Path("Universal-Binaries.zip")
@property @property
def kdk_download_path(self): def kdk_download_path(self):
return self.payload_path / Path("KDK.dmg") return self.payload_path / Path("KDK.dmg")
@property
def icns_resource_path(self):
if self.launcher_script:
return self.payload_path / Path("Icon/AppIcons")
return Path(self.launcher_binary).parent.parent / Path("Resources")
sbm_values = [ sbm_values = [
"j137ap", # iMacPro1,1 "j137ap", # iMacPro1,1

View File

@@ -71,6 +71,14 @@ class GenerateDefaults:
global_settings.GlobalEnviromentSettings().write_property("MacBookPro_TeraScale_2_Accel", False) global_settings.GlobalEnviromentSettings().write_property("MacBookPro_TeraScale_2_Accel", False)
self.constants.allow_ts2_accel = False self.constants.allow_ts2_accel = False
if self.model in ["MacBookAir4,1","MacBookAir4,2","MacBookPro8,1","MacBookPro8,2","MacBookPro8,3","Macmini5,1"]:
colorsync_status = global_settings.GlobalEnviromentSettings().read_property("Disable_ColorSync_Downgrade")
if colorsync_status is True:
self.constants.disable_cat_colorsync = True
else:
global_settings.GlobalEnviromentSettings().write_property("Disable_ColorSync_Downgrade", False)
self.constants.disable_cat_colorsync = False
if self.model in smbios_data.smbios_dictionary: if self.model in smbios_data.smbios_dictionary:
if smbios_data.smbios_dictionary[self.model]["CPU Generation"] >= cpu_data.cpu_data.skylake.value: if smbios_data.smbios_dictionary[self.model]["CPU Generation"] >= cpu_data.cpu_data.skylake.value:
# On 2016-2017 MacBook Pros, 15" devices used a stock Samsung SSD with IONVMeController # On 2016-2017 MacBook Pros, 15" devices used a stock Samsung SSD with IONVMeController

View File

@@ -48,7 +48,7 @@ class GlobalEnviromentSettings:
try: try:
plistlib.dump(plist, Path(self.global_settings_plist).open("wb")) plistlib.dump(plist, Path(self.global_settings_plist).open("wb"))
except PermissionError: except PermissionError:
logging.info("- Failed to write to global settings file") logging.info("Failed to write to global settings file")
def _generate_settings_file(self) -> None: def _generate_settings_file(self) -> None:
@@ -57,7 +57,7 @@ class GlobalEnviromentSettings:
try: try:
plistlib.dump({"Developed by Dortania": True,}, Path(self.global_settings_plist).open("wb")) plistlib.dump({"Developed by Dortania": True,}, Path(self.global_settings_plist).open("wb"))
except PermissionError: except PermissionError:
logging.info("- Permission error: Unable to write to global settings file") logging.info("Permission error: Unable to write to global settings file")
def _convert_defaults_to_global_settings(self) -> None: def _convert_defaults_to_global_settings(self) -> None:
@@ -76,14 +76,15 @@ class GlobalEnviromentSettings:
try: try:
plistlib.dump(global_settings_plist, Path(self.global_settings_plist).open("wb")) plistlib.dump(global_settings_plist, Path(self.global_settings_plist).open("wb"))
except PermissionError: except PermissionError:
logging.info("- Permission error: Unable to write to global settings file") logging.info("Permission error: Unable to write to global settings file")
return return
# delete defaults plist # delete defaults plist
try: try:
Path(defaults_path).unlink() Path(defaults_path).unlink()
except PermissionError: except Exception as e:
logging.info("- Permission error: Unable to delete defaults plist") logging.error("Error: Unable to delete defaults plist")
logging.error(e)
def _fix_file_permission(self) -> None: def _fix_file_permission(self) -> None:
@@ -100,6 +101,6 @@ class GlobalEnviromentSettings:
# Set file permission to allow any user to write to log file # Set file permission to allow any user to write to log file
result = subprocess.run(["chmod", "777", self.global_settings_plist], capture_output=True) result = subprocess.run(["chmod", "777", self.global_settings_plist], capture_output=True)
if result.returncode != 0: if result.returncode != 0:
logging.warning("- Failed to fix settings file permissions:") logging.warning("Failed to fix settings file permissions:")
if result.stderr: if result.stderr:
logging.warning(result.stderr.decode("utf-8")) logging.warning(result.stderr.decode("utf-8"))

View File

@@ -1,104 +0,0 @@
import wx
import webbrowser
from resources import constants
from data import os_data
class gui_help_menu:
def __init__(self, versions, frame, frame_modal):
self.constants: constants.Constants = versions
self.frame = frame
self.frame_modal = frame_modal
# Define Window Size
self.WINDOW_WIDTH_MAIN = 300
def reset_frame_modal(self):
if not self.frame_modal:
self.frame_modal = wx.Dialog(self.frame)
else:
self.frame_modal.DestroyChildren()
self.frame_modal.Close()
if self.constants.detected_os >= os_data.os_data.big_sur:
self.frame_modal.ShowWithoutActivating()
def help_menu(self, event=None):
# Define Menu
# Header: Get help with OpenCore Legacy Patcher
# Subheader: Following resources are available:
# Button: Official Guide
# Button: Official Discord Server
self.reset_frame_modal()
self.frame_modal.SetSize((self.WINDOW_WIDTH_MAIN, -1))
# Header
self.header = wx.StaticText(self.frame_modal, label="Patcher Resources", pos=(10,10))
self.header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
self.header.Centre(wx.HORIZONTAL)
# Subheader
self.subheader = wx.StaticText(self.frame_modal, label="Following resources are available:")
self.subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
self.subheader.SetPosition(
wx.Point(
self.header.GetPosition().x,
self.header.GetPosition().y + self.header.GetSize().height + 5
)
)
self.subheader.Centre(wx.HORIZONTAL)
# Official Guide
self.guide = wx.Button(self.frame_modal, label="Official Guide", size=(200,30))
self.guide.SetPosition(
wx.Point(
self.subheader.GetPosition().x,
self.subheader.GetPosition().y + self.subheader.GetSize().height + 5
)
)
self.guide.Bind(wx.EVT_BUTTON, lambda event: webbrowser.open(self.constants.guide_link))
self.guide.Centre(wx.HORIZONTAL)
# Official Discord Server
self.discord = wx.Button(self.frame_modal, label="Official Discord Server", size=(200,30))
self.discord.SetPosition(
wx.Point(
self.guide.GetPosition().x,
self.guide.GetPosition().y + self.guide.GetSize().height
)
)
self.discord.Bind(wx.EVT_BUTTON, lambda event: webbrowser.open(self.constants.discord_link))
self.discord.Centre(wx.HORIZONTAL)
# Overclock Button
self.overclock = wx.Button(self.frame_modal, label="Official Support Phone", size=(200,30))
self.overclock.SetPosition(
wx.Point(
self.discord.GetPosition().x,
self.discord.GetPosition().y + self.discord.GetSize().height
)
)
self.overclock.Bind(wx.EVT_BUTTON, lambda event: webbrowser.open("https://www.youtube.com/watch?v=dQw4w9WgXcQ"))
self.overclock.Centre(wx.HORIZONTAL)
self.return_to_main = wx.Button(self.frame_modal, label="Return to Main Menu", size=(150,30))
self.return_to_main.SetPosition(
wx.Point(
self.overclock.GetPosition().x,
self.overclock.GetPosition().y + self.overclock.GetSize().height + 5
)
)
self.return_to_main.Bind(wx.EVT_BUTTON, lambda event: self.frame_modal.Close())
self.return_to_main.Centre(wx.HORIZONTAL)
# Set Window Size to below Copyright Label
self.frame_modal.SetSize(
(
-1,
self.return_to_main.GetPosition().y + self.return_to_main.GetSize().height + 40
)
)
self.frame_modal.ShowWindowModal()

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +0,0 @@
import wx
import time
class RedirectText(object):
def __init__(self,aWxTextCtrl, sleep):
self.out=aWxTextCtrl
self.sleep = sleep
def write(self,string):
self.out.WriteText(string)
wx.GetApp().Yield()
if self.sleep:
time.sleep(0.01)
def fileno(self):
return 1
def flush(self):
pass
class RedirectLabel(object):
def __init__(self,aWxTextCtrl):
self.out=aWxTextCtrl
def write(self,string):
if "MB/s" in string:
self.out.SetLabel(string)
self.out.Centre(wx.HORIZONTAL)
wx.GetApp().Yield()
time.sleep(0.01)
def fileno(self):
return 1
def flush(self):
pass
class RedirectLabelAll(object):
def __init__(self,aWxTextCtrl):
self.out=aWxTextCtrl
def write(self,string):
self.out.SetLabel(string)
self.out.Centre(wx.HORIZONTAL)
wx.GetApp().Yield()
time.sleep(0.01)

View File

@@ -2,15 +2,17 @@
# Usage solely for TUI # Usage solely for TUI
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk # Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
import logging
import plistlib import plistlib
import subprocess import subprocess
import shutil import applescript
import os
import logging
from pathlib import Path from pathlib import Path
from resources import utilities, constants from resources import utilities, constants
from data import os_data from data import os_data
class tui_disk_installation: class tui_disk_installation:
def __init__(self, versions): def __init__(self, versions):
self.constants: constants.Constants = versions self.constants: constants.Constants = versions
@@ -75,49 +77,37 @@ class tui_disk_installation:
return supported_partitions return supported_partitions
def install_opencore(self, full_disk_identifier): def _determine_sd_card(self, media_name: str):
def determine_sd_card(media_name):
# Array filled with common SD Card names # Array filled with common SD Card names
# Note most USB-based SD Card readers generally report as "Storage Device" # Note most USB-based SD Card readers generally report as "Storage Device"
# Thus no reliable way to detect further without parsing IOService output (kUSBProductString) # Thus no reliable way to detect further without parsing IOService output (kUSBProductString)
if ( if any(x in media_name for x in ("SD Card", "SD/MMC", "SDXC Reader", "SD Reader", "Card Reader")):
"SD Card" in media_name or \
"SD/MMC" in media_name or \
"SDXC Reader" in media_name or \
"SD Reader" in media_name or \
"Card Reader" in media_name
):
return True return True
return False return False
def install_opencore(self, full_disk_identifier: str):
# TODO: Apple Script fails in Yosemite(?) and older # TODO: Apple Script fails in Yosemite(?) and older
args = [ logging.info(f"Mounting partition: {full_disk_identifier}")
"osascript",
"-e",
f'''do shell script "diskutil mount {full_disk_identifier}"'''
' with prompt "OpenCore Legacy Patcher needs administrator privileges to mount your EFI."'
" with administrator privileges"
" without altering line endings",
]
if self.constants.detected_os >= os_data.os_data.el_capitan and not self.constants.recovery_status: if self.constants.detected_os >= os_data.os_data.el_capitan and not self.constants.recovery_status:
result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) try:
else: applescript.AppleScript(f'''do shell script "diskutil mount {full_disk_identifier}" with prompt "OpenCore Legacy Patcher needs administrator privileges to mount this volume." with administrator privileges without altering line endings''').run()
result = subprocess.run(f"diskutil mount {full_disk_identifier}".split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE) except applescript.ScriptError as e:
if "User canceled" in str(e):
if result.returncode != 0: logging.info("Mount cancelled by user")
if "execution error" in result.stderr.decode() and result.stderr.decode().strip()[-5:-1] == "-128":
# cancelled prompt
return return
else: logging.info(f"An error occurred: {e}")
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": if utilities.check_boot_mode() == "safe_boot":
logging.info("\nSafe Mode detected. FAT32 is unsupported by macOS in this mode.") logging.info("\nSafe Mode detected. FAT32 is unsupported by macOS in this mode.")
logging.info("Please disable Safe Mode and try again.") logging.info("Please disable Safe Mode and try again.")
return return
else:
result = subprocess.run(f"diskutil mount {full_disk_identifier}".split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
logging.info("Mount failed")
logging.info(result.stderr.decode())
return
partition_info = plistlib.loads(subprocess.run(f"diskutil info -plist {full_disk_identifier}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) 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"] parent_disk = partition_info["ParentWholeDisk"]
drive_host_info = plistlib.loads(subprocess.run(f"diskutil info -plist {parent_disk}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) drive_host_info = plistlib.loads(subprocess.run(f"diskutil info -plist {parent_disk}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
@@ -128,71 +118,57 @@ class tui_disk_installation:
ssd_type = False ssd_type = False
mount_path = Path(partition_info["MountPoint"]) mount_path = Path(partition_info["MountPoint"])
disk_type = partition_info["BusProtocol"] disk_type = partition_info["BusProtocol"]
utilities.cls()
utilities.header(["Copying OpenCore"])
if mount_path.exists(): if not mount_path.exists():
if (mount_path / Path("EFI/Microsoft")).exists() and self.constants.gui_mode is False:
logging.info("- Found Windows Boot Loader")
logging.info("\nWould you like to continue installing OpenCore?")
logging.info("Installing OpenCore onto this drive may make Windows unbootable until OpenCore")
logging.info("is removed from the partition")
logging.info("We highly recommend users partition 200MB off their drive with Disk Utility")
logging.info(" Name:\t\t OPENCORE")
logging.info(" Format:\t\t FAT32")
logging.info(" Size:\t\t 200MB")
choice = input("\nWould you like to still install OpenCore to this drive?(y/n): ")
if not choice in ["y", "Y", "Yes", "yes"]:
subprocess.run(["diskutil", "umount", mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode()
return False
if (mount_path / Path("EFI/OC")).exists():
logging.info("- Removing preexisting EFI/OC folder")
shutil.rmtree(mount_path / Path("EFI/OC"), onerror=rmtree_handler)
if (mount_path / Path("System")).exists():
logging.info("- Removing preexisting System folder")
shutil.rmtree(mount_path / Path("System"), onerror=rmtree_handler)
if (mount_path / Path("boot.efi")).exists():
logging.info("- Removing preexisting boot.efi")
os.remove(mount_path / Path("boot.efi"))
logging.info("- Copying OpenCore onto EFI partition")
shutil.copytree(self.constants.opencore_release_folder / Path("EFI/OC"), mount_path / Path("EFI/OC"))
shutil.copytree(self.constants.opencore_release_folder / Path("System"), mount_path / Path("System"))
if Path(self.constants.opencore_release_folder / Path("boot.efi")).exists():
shutil.copy(self.constants.opencore_release_folder / Path("boot.efi"), mount_path / Path("boot.efi"))
if self.constants.boot_efi is True:
logging.info("- Converting Bootstrap to BOOTx64.efi")
if (mount_path / Path("EFI/BOOT")).exists():
shutil.rmtree(mount_path / Path("EFI/BOOT"), onerror=rmtree_handler)
Path(mount_path / Path("EFI/BOOT")).mkdir()
shutil.move(mount_path / Path("System/Library/CoreServices/boot.efi"), mount_path / Path("EFI/BOOT/BOOTx64.efi"))
shutil.rmtree(mount_path / Path("System"), onerror=rmtree_handler)
if determine_sd_card(sd_type) is True:
logging.info("- Adding SD Card icon")
shutil.copy(self.constants.icon_path_sd, mount_path)
elif ssd_type is True:
logging.info("- Adding SSD icon")
shutil.copy(self.constants.icon_path_ssd, mount_path)
elif disk_type == "USB":
logging.info("- Adding External USB Drive icon")
shutil.copy(self.constants.icon_path_external, mount_path)
else:
logging.info("- Adding Internal Drive icon")
shutil.copy(self.constants.icon_path_internal, mount_path)
logging.info("- Cleaning install location")
if not self.constants.recovery_status:
logging.info("- Unmounting EFI partition")
subprocess.run(["diskutil", "umount", mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode()
logging.info("- OpenCore transfer complete")
if self.constants.gui_mode is False:
logging.info("\nPress [Enter] to continue.\n")
input()
else:
logging.info("EFI failed to mount!") logging.info("EFI failed to mount!")
return False return False
return True
def rmtree_handler(func, path, exc_info): if (mount_path / Path("EFI/OC")).exists():
if exc_info[0] == FileNotFoundError: logging.info("Removing preexisting EFI/OC folder")
return subprocess.run(["rm", "-rf", mount_path / Path("EFI/OC")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
raise # pylint: disable=misplaced-bare-raise
if (mount_path / Path("System")).exists():
logging.info("Removing preexisting System folder")
subprocess.run(["rm", "-rf", mount_path / Path("System")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if (mount_path / Path("boot.efi")).exists():
logging.info("Removing preexisting boot.efi")
subprocess.run(["rm", mount_path / Path("boot.efi")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
logging.info("Copying OpenCore onto EFI partition")
subprocess.run(["mkdir", "-p", mount_path / Path("EFI")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run(["cp", "-r", self.constants.opencore_release_folder / Path("EFI/OC"), mount_path / Path("EFI/OC")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run(["cp", "-r", self.constants.opencore_release_folder / Path("System"), mount_path / Path("System")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if Path(self.constants.opencore_release_folder / Path("boot.efi")).exists():
subprocess.run(["cp", self.constants.opencore_release_folder / Path("boot.efi"), mount_path / Path("boot.efi")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if self.constants.boot_efi is True:
logging.info("Converting Bootstrap to BOOTx64.efi")
if (mount_path / Path("EFI/BOOT")).exists():
subprocess.run(["rm", "-rf", mount_path / Path("EFI/BOOT")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Path(mount_path / Path("EFI/BOOT")).mkdir()
subprocess.run(["mv", mount_path / Path("System/Library/CoreServices/boot.efi"), mount_path / Path("EFI/BOOT/BOOTx64.efi")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run(["rm", "-rf", mount_path / Path("System")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if self._determine_sd_card(sd_type) is True:
logging.info("Adding SD Card icon")
subprocess.run(["cp", self.constants.icon_path_sd, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
elif ssd_type is True:
logging.info("Adding SSD icon")
subprocess.run(["cp", self.constants.icon_path_ssd, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
elif disk_type == "USB":
logging.info("Adding External USB Drive icon")
subprocess.run(["cp", self.constants.icon_path_external, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
else:
logging.info("Adding Internal Drive icon")
subprocess.run(["cp", self.constants.icon_path_internal, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
logging.info("Cleaning install location")
if not self.constants.recovery_status:
logging.info("Unmounting EFI partition")
subprocess.run(["diskutil", "umount", mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode()
logging.info("OpenCore transfer complete")
return True

View File

@@ -100,6 +100,18 @@ class ChunklistVerification:
self.status = ChunklistStatus.FAILURE self.status = ChunklistStatus.FAILURE
return return
if not Path(self.file_path).exists():
self.error_msg = f"File {self.file_path} does not exist"
self.status = ChunklistStatus.FAILURE
logging.info(self.error_msg)
return
if not Path(self.file_path).is_file():
self.error_msg = f"File {self.file_path} is not a file"
self.status = ChunklistStatus.FAILURE
logging.info(self.error_msg)
return
with self.file_path.open("rb") as f: with self.file_path.open("rb") as f:
for chunk in self.chunks: for chunk in self.chunks:
self.current_chunk += 1 self.current_chunk += 1

View File

@@ -94,7 +94,7 @@ class KernelDebugKitObject:
global KDK_ASSET_LIST global KDK_ASSET_LIST
logging.info("- Pulling KDK list from KdkSupportPkg API") logging.info("Pulling KDK list from KdkSupportPkg API")
if KDK_ASSET_LIST: if KDK_ASSET_LIST:
return KDK_ASSET_LIST return KDK_ASSET_LIST
@@ -107,11 +107,11 @@ class KernelDebugKitObject:
timeout=5 timeout=5
) )
except (requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.ConnectionError): except (requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.ConnectionError):
logging.info("- Could not contact KDK API") logging.info("Could not contact KDK API")
return None return None
if results.status_code != 200: if results.status_code != 200:
logging.info("- Could not fetch KDK list") logging.info("Could not fetch KDK list")
return None return None
KDK_ASSET_LIST = 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)
@@ -138,12 +138,12 @@ class KernelDebugKitObject:
if os_data.os_conversion.os_to_kernel(str(parsed_version.major)) < os_data.os_data.ventura: if os_data.os_conversion.os_to_kernel(str(parsed_version.major)) < os_data.os_data.ventura:
self.error_msg = "KDKs are not required for macOS Monterey or older" self.error_msg = "KDKs are not required for macOS Monterey or older"
logging.warning(f"- {self.error_msg}") logging.warning(f"{self.error_msg}")
return return
self.kdk_installed_path = self._local_kdk_installed() self.kdk_installed_path = self._local_kdk_installed()
if self.kdk_installed_path: if self.kdk_installed_path:
logging.info(f"- KDK already installed ({Path(self.kdk_installed_path).name}), skipping") logging.info(f"KDK already installed ({Path(self.kdk_installed_path).name}), skipping")
self.kdk_already_installed = True self.kdk_already_installed = True
self.success = True self.success = True
return return
@@ -151,44 +151,56 @@ class KernelDebugKitObject:
remote_kdk_version = self._get_remote_kdks() remote_kdk_version = self._get_remote_kdks()
if remote_kdk_version is None: if remote_kdk_version is None:
logging.warning("- Failed to fetch KDK list, falling back to local KDK matching") logging.warning("Failed to fetch KDK list, falling back to local KDK matching")
# First check if a KDK matching the current macOS version is installed # First check if a KDK matching the current macOS version is installed
# ex. 13.0.1 vs 13.0 # ex. 13.0.1 vs 13.0
loose_version = f"{parsed_version.major}.{parsed_version.minor}" loose_version = f"{parsed_version.major}.{parsed_version.minor}"
logging.info(f"- Checking for KDKs loosely matching {loose_version}") logging.info(f"Checking for KDKs loosely matching {loose_version}")
self.kdk_installed_path = self._local_kdk_installed(match=loose_version, check_version=True) self.kdk_installed_path = self._local_kdk_installed(match=loose_version, check_version=True)
if self.kdk_installed_path: if self.kdk_installed_path:
logging.info(f"- Found matching KDK: {Path(self.kdk_installed_path).name}") logging.info(f"Found matching KDK: {Path(self.kdk_installed_path).name}")
self.kdk_already_installed = True self.kdk_already_installed = True
self.success = True self.success = True
return return
older_version = f"{parsed_version.major}.{parsed_version.minor - 1 if parsed_version.minor > 0 else 0}" 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}") logging.info(f"Checking for KDKs matching {older_version}")
self.kdk_installed_path = self._local_kdk_installed(match=older_version, check_version=True) self.kdk_installed_path = self._local_kdk_installed(match=older_version, check_version=True)
if self.kdk_installed_path: if self.kdk_installed_path:
logging.info(f"- Found matching KDK: {Path(self.kdk_installed_path).name}") logging.info(f"Found matching KDK: {Path(self.kdk_installed_path).name}")
self.kdk_already_installed = True self.kdk_already_installed = True
self.success = True self.success = True
return return
logging.warning(f"- Couldn't find KDK matching {host_version} or {older_version}, please install one manually") logging.warning(f"Couldn't find KDK matching {host_version} or {older_version}, please install one manually")
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." 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."
return return
# First check exact match
for kdk in remote_kdk_version: for kdk in remote_kdk_version:
kdk_version = cast(packaging.version.Version, packaging.version.parse(kdk["version"])) if (kdk["build"] != host_build):
if (kdk["build"] == host_build): continue
self.kdk_url = kdk["url"] self.kdk_url = kdk["url"]
self.kdk_url_build = kdk["build"] self.kdk_url_build = kdk["build"]
self.kdk_url_version = kdk["version"] self.kdk_url_version = kdk["version"]
self.kdk_url_expected_size = kdk["fileSize"] self.kdk_url_expected_size = kdk["fileSize"]
self.kdk_url_is_exactly_match = True self.kdk_url_is_exactly_match = True
break 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)):
# If no exact match, check for closest match
if self.kdk_url == "":
for kdk in remote_kdk_version:
kdk_version = cast(packaging.version.Version, packaging.version.parse(kdk["version"]))
if kdk_version > parsed_version:
continue
if kdk_version.major != parsed_version.major:
continue
if kdk_version.minor not in range(parsed_version.minor - 1, parsed_version.minor + 1):
continue
# The KDK list is already sorted by version then date, so the first match is the closest # 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 = kdk["url"]
self.kdk_closest_match_url_build = kdk["build"] self.kdk_closest_match_url_build = kdk["build"]
@@ -199,29 +211,29 @@ class KernelDebugKitObject:
if self.kdk_url == "": if self.kdk_url == "":
if self.kdk_closest_match_url == "": if self.kdk_closest_match_url == "":
logging.warning(f"- No KDKs found for {host_build} ({host_version})") logging.warning(f"No KDKs found for {host_build} ({host_version})")
self.error_msg = f"No KDKs found for {host_build} ({host_version})" self.error_msg = f"No KDKs found for {host_build} ({host_version})"
return return
logging.info(f"- No direct match found for {host_build}, falling back to closest match") 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})") logging.info(f"Closest Match: {self.kdk_closest_match_url_build} ({self.kdk_closest_match_url_version})")
self.kdk_url = self.kdk_closest_match_url self.kdk_url = self.kdk_closest_match_url
self.kdk_url_build = self.kdk_closest_match_url_build self.kdk_url_build = self.kdk_closest_match_url_build
self.kdk_url_version = self.kdk_closest_match_url_version self.kdk_url_version = self.kdk_closest_match_url_version
self.kdk_url_expected_size = self.kdk_closest_match_url_expected_size self.kdk_url_expected_size = self.kdk_closest_match_url_expected_size
else: else:
logging.info(f"- Direct match found for {host_build} ({host_version})") logging.info(f"Direct match found for {host_build} ({host_version})")
# Check if this KDK is already installed # Check if this KDK is already installed
self.kdk_installed_path = self._local_kdk_installed(match=self.kdk_url_build) self.kdk_installed_path = self._local_kdk_installed(match=self.kdk_url_build)
if self.kdk_installed_path: if self.kdk_installed_path:
logging.info(f"- KDK already installed ({Path(self.kdk_installed_path).name}), skipping") logging.info(f"KDK already installed ({Path(self.kdk_installed_path).name}), skipping")
self.kdk_already_installed = True self.kdk_already_installed = True
self.success = True self.success = True
return return
logging.info("- Following KDK is recommended:") logging.info("Following KDK is recommended:")
logging.info(f"- KDK Build: {self.kdk_url_build}") logging.info(f"- KDK Build: {self.kdk_url_build}")
logging.info(f"- KDK Version: {self.kdk_url_version}") logging.info(f"- KDK Version: {self.kdk_url_version}")
logging.info(f"- KDK URL: {self.kdk_url}") logging.info(f"- KDK URL: {self.kdk_url}")
@@ -244,7 +256,7 @@ class KernelDebugKitObject:
self.error_msg = "" self.error_msg = ""
if self.kdk_already_installed: if self.kdk_already_installed:
logging.info("- No download required, KDK already installed") logging.info("No download required, KDK already installed")
self.success = True self.success = True
return None return None
@@ -253,7 +265,7 @@ class KernelDebugKitObject:
logging.error(self.error_msg) logging.error(self.error_msg)
return None return None
logging.info(f"- Returning DownloadObject for KDK: {Path(self.kdk_url).name}") logging.info(f"Returning DownloadObject for KDK: {Path(self.kdk_url).name}")
self.success = True self.success = True
kdk_download_path = self.constants.kdk_download_path if override_path == "" else Path(override_path) kdk_download_path = self.constants.kdk_download_path if override_path == "" else Path(override_path)
@@ -282,7 +294,7 @@ class KernelDebugKitObject:
plist_path.touch() plist_path.touch()
plistlib.dump(kdk_dict, plist_path.open("wb"), sort_keys=False) plistlib.dump(kdk_dict, plist_path.open("wb"), sort_keys=False)
except Exception as e: except Exception as e:
logging.error(f"- Failed to generate KDK Info.plist: {e}") logging.error(f"Failed to generate KDK Info.plist: {e}")
def _local_kdk_valid(self, kdk_path: Path) -> bool: def _local_kdk_valid(self, kdk_path: Path) -> bool:
@@ -302,14 +314,14 @@ class KernelDebugKitObject:
""" """
if not Path(f"{kdk_path}/System/Library/CoreServices/SystemVersion.plist").exists(): 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") logging.info(f"Corrupted KDK found ({kdk_path.name}), removing due to missing SystemVersion.plist")
self._remove_kdk(kdk_path) self._remove_kdk(kdk_path)
return False return False
# Get build from KDK # Get build from KDK
kdk_plist_data = plistlib.load(Path(f"{kdk_path}/System/Library/CoreServices/SystemVersion.plist").open("rb")) kdk_plist_data = plistlib.load(Path(f"{kdk_path}/System/Library/CoreServices/SystemVersion.plist").open("rb"))
if "ProductBuildVersion" not in kdk_plist_data: if "ProductBuildVersion" not in kdk_plist_data:
logging.info(f"- Corrupted KDK found ({kdk_path.name}), removing due to missing ProductBuildVersion") logging.info(f"Corrupted KDK found ({kdk_path.name}), removing due to missing ProductBuildVersion")
self._remove_kdk(kdk_path) self._remove_kdk(kdk_path)
return False return False
@@ -319,7 +331,7 @@ class KernelDebugKitObject:
result = subprocess.run(["pkgutil", "--files", f"com.apple.pkg.KDK.{kdk_build}"], capture_output=True) result = subprocess.run(["pkgutil", "--files", f"com.apple.pkg.KDK.{kdk_build}"], capture_output=True)
if result.returncode != 0: if result.returncode != 0:
# If pkg receipt is missing, we'll fallback to legacy validation # 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") logging.info(f"pkg receipt missing for {kdk_path.name}, falling back to legacy validation")
return self._local_kdk_valid_legacy(kdk_path) return self._local_kdk_valid_legacy(kdk_path)
# Go through each line of the pkg receipt and ensure it exists # Go through each line of the pkg receipt and ensure it exists
@@ -327,7 +339,7 @@ class KernelDebugKitObject:
if not line.startswith("System/Library/Extensions"): if not line.startswith("System/Library/Extensions"):
continue continue
if not Path(f"{kdk_path}/{line}").exists(): if not Path(f"{kdk_path}/{line}").exists():
logging.info(f"- Corrupted KDK found ({kdk_path.name}), removing due to missing file: {line}") logging.info(f"Corrupted KDK found ({kdk_path.name}), removing due to missing file: {line}")
self._remove_kdk(kdk_path) self._remove_kdk(kdk_path)
return False return False
@@ -356,7 +368,7 @@ class KernelDebugKitObject:
for kext in KEXT_CATALOG: for kext in KEXT_CATALOG:
if not Path(f"{kdk_path}/System/Library/Extensions/{kext}").exists(): 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}") logging.info(f"Corrupted KDK found, removing due to missing: {kdk_path}/System/Library/Extensions/{kext}")
self._remove_kdk(kdk_path) self._remove_kdk(kdk_path)
return False return False
@@ -415,15 +427,15 @@ class KernelDebugKitObject:
if not kdk_pkg.name.endswith(f"{match}.pkg"): if not kdk_pkg.name.endswith(f"{match}.pkg"):
continue continue
logging.info(f"- Found KDK backup: {kdk_pkg.name}") logging.info(f"Found KDK backup: {kdk_pkg.name}")
if self.passive is False: if self.passive is False:
logging.info("- Attempting KDK restoration") logging.info("Attempting KDK restoration")
if KernelDebugKitUtilities().install_kdk_pkg(kdk_pkg): if KernelDebugKitUtilities().install_kdk_pkg(kdk_pkg):
logging.info("- Successfully restored KDK") logging.info("Successfully restored KDK")
return self._local_kdk_installed(match=match, check_version=check_version) return self._local_kdk_installed(match=match, check_version=check_version)
else: else:
# When in passive mode, we're just checking if a KDK could be restored # When in passive mode, we're just checking if a KDK could be restored
logging.info("- KDK restoration skipped, running in passive mode") logging.info("KDK restoration skipped, running in passive mode")
return kdk_pkg return kdk_pkg
return None return None
@@ -441,22 +453,22 @@ class KernelDebugKitObject:
return return
if os.getuid() != 0: if os.getuid() != 0:
logging.warning("- Cannot remove KDK, not running as root") logging.warning("Cannot remove KDK, not running as root")
return return
if not Path(kdk_path).exists(): if not Path(kdk_path).exists():
logging.warning(f"- KDK does not exist: {kdk_path}") logging.warning(f"KDK does not exist: {kdk_path}")
return return
rm_args = ["rm", "-rf" if Path(kdk_path).is_dir() else "-f", kdk_path] 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) result = utilities.elevated(rm_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0: if result.returncode != 0:
logging.warning(f"- Failed to remove KDK: {kdk_path}") logging.warning(f"Failed to remove KDK: {kdk_path}")
logging.warning(f"- {result.stdout.decode('utf-8')}") logging.warning(f"{result.stdout.decode('utf-8')}")
return return
logging.info(f"- Successfully removed KDK: {kdk_path}") logging.info(f"Successfully removed KDK: {kdk_path}")
def _remove_unused_kdks(self, exclude_builds: list = None) -> None: def _remove_unused_kdks(self, exclude_builds: list = None) -> None:
@@ -483,7 +495,7 @@ class KernelDebugKitObject:
if not Path(KDK_INSTALL_PATH).exists(): if not Path(KDK_INSTALL_PATH).exists():
return return
logging.info("- Cleaning unused KDKs") logging.info("Cleaning unused KDKs")
for kdk_folder in Path(KDK_INSTALL_PATH).iterdir(): for kdk_folder in Path(KDK_INSTALL_PATH).iterdir():
if kdk_folder.name.endswith(".kdk") or kdk_folder.name.endswith(".pkg"): if kdk_folder.name.endswith(".kdk") or kdk_folder.name.endswith(".pkg"):
should_remove = True should_remove = True
@@ -520,16 +532,18 @@ class KernelDebugKitObject:
# TODO: should we use the checksum from the API? # 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) result = subprocess.run(["hdiutil", "verify", self.constants.kdk_download_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0: if result.returncode != 0:
logging.info("- Error: Kernel Debug Kit checksum verification failed!") logging.info("Error: Kernel Debug Kit checksum verification failed!")
logging.info(f"- Output: {result.stderr.decode('utf-8')}") 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)" 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}") logging.info(f"{msg}")
self.error_msg = msg self.error_msg = msg
return False
self._remove_unused_kdks() self._remove_unused_kdks()
self.success = True self.success = True
logging.info("Kernel Debug Kit checksum verified")
return True
class KernelDebugKitUtilities: class KernelDebugKitUtilities:
@@ -554,17 +568,17 @@ class KernelDebugKitUtilities:
""" """
if os.getuid() != 0: if os.getuid() != 0:
logging.warning("- Cannot install KDK, not running as root") logging.warning("Cannot install KDK, not running as root")
return False return False
logging.info(f"- Installing KDK package: {kdk_path.name}") logging.info(f"Installing KDK package: {kdk_path.name}")
logging.info(f"- This may take a while...") logging.info(f"- This may take a while...")
# TODO: Check whether enough disk space is available # TODO: Check whether enough disk space is available
result = utilities.elevated(["installer", "-pkg", kdk_path, "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) result = utilities.elevated(["installer", "-pkg", kdk_path, "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0: if result.returncode != 0:
logging.info("- Failed to install KDK:") logging.info("Failed to install KDK:")
logging.info(result.stdout.decode('utf-8')) logging.info(result.stdout.decode('utf-8'))
if result.stderr: if result.stderr:
logging.info(result.stderr.decode('utf-8')) logging.info(result.stderr.decode('utf-8'))
@@ -585,21 +599,21 @@ class KernelDebugKitUtilities:
""" """
if os.getuid() != 0: if os.getuid() != 0:
logging.warning("- Cannot install KDK, not running as root") logging.warning("Cannot install KDK, not running as root")
return False return False
logging.info(f"- Extracting downloaded KDK disk image") logging.info(f"Extracting downloaded KDK disk image")
with tempfile.TemporaryDirectory() as mount_point: with tempfile.TemporaryDirectory() as mount_point:
result = subprocess.run(["hdiutil", "attach", kdk_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) result = subprocess.run(["hdiutil", "attach", kdk_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0: if result.returncode != 0:
logging.info("- Failed to mount KDK:") logging.info("Failed to mount KDK:")
logging.info(result.stdout.decode('utf-8')) logging.info(result.stdout.decode('utf-8'))
return False return False
kdk_pkg_path = Path(f"{mount_point}/KernelDebugKit.pkg") kdk_pkg_path = Path(f"{mount_point}/KernelDebugKit.pkg")
if not kdk_pkg_path.exists(): if not kdk_pkg_path.exists():
logging.warning("- Failed to find KDK package in DMG, likely corrupted!!!") logging.warning("Failed to find KDK package in DMG, likely corrupted!!!")
self._unmount_disk_image(mount_point) self._unmount_disk_image(mount_point)
return False return False
@@ -610,7 +624,7 @@ class KernelDebugKitUtilities:
self._create_backup(kdk_pkg_path, Path(f"{kdk_path.parent}/{KDK_INFO_PLIST}")) self._create_backup(kdk_pkg_path, Path(f"{kdk_path.parent}/{KDK_INFO_PLIST}"))
self._unmount_disk_image(mount_point) self._unmount_disk_image(mount_point)
logging.info("- Successfully installed KDK") logging.info("Successfully installed KDK")
return True return True
def _unmount_disk_image(self, mount_point) -> None: def _unmount_disk_image(self, mount_point) -> None:
@@ -633,31 +647,31 @@ class KernelDebugKitUtilities:
""" """
if not kdk_path.exists(): if not kdk_path.exists():
logging.warning("- KDK does not exist, cannot create backup") logging.warning("KDK does not exist, cannot create backup")
return return
if not kdk_info_plist.exists(): if not kdk_info_plist.exists():
logging.warning("- KDK Info.plist does not exist, cannot create backup") logging.warning("KDK Info.plist does not exist, cannot create backup")
return return
kdk_info_dict = plistlib.load(kdk_info_plist.open("rb")) kdk_info_dict = plistlib.load(kdk_info_plist.open("rb"))
if 'version' not in kdk_info_dict or 'build' not in kdk_info_dict: if 'version' not in kdk_info_dict or 'build' not in kdk_info_dict:
logging.warning("- Malformed KDK Info.plist provided, cannot create backup") logging.warning("Malformed KDK Info.plist provided, cannot create backup")
return return
if os.getuid() != 0: if os.getuid() != 0:
logging.warning("- Cannot create KDK backup, not running as root") logging.warning("Cannot create KDK backup, not running as root")
return return
kdk_dst_name = f"KDK_{kdk_info_dict['version']}_{kdk_info_dict['build']}.pkg" 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}") kdk_dst_path = Path(f"{KDK_INSTALL_PATH}/{kdk_dst_name}")
logging.info(f"- Creating backup: {kdk_dst_name}") logging.info(f"Creating backup: {kdk_dst_name}")
if kdk_dst_path.exists(): if kdk_dst_path.exists():
logging.info("- Backup already exists, skipping") logging.info("Backup already exists, skipping")
return return
result = utilities.elevated(["cp", "-R", kdk_path, kdk_dst_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) result = utilities.elevated(["cp", "-R", kdk_path, kdk_dst_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0: if result.returncode != 0:
logging.info("- Failed to create KDK backup:") logging.info("Failed to create KDK backup:")
logging.info(result.stdout.decode('utf-8')) logging.info(result.stdout.decode('utf-8'))

View File

@@ -1,9 +1,16 @@
import logging
import sys
import threading
import os import os
import sys
import pprint
import logging
import threading
import traceback
import subprocess import subprocess
import applescript
from pathlib import Path from pathlib import Path
from datetime import datetime
from resources import constants, analytics_handler, global_settings
class InitializeLoggingSupport: class InitializeLoggingSupport:
@@ -26,25 +33,26 @@ class InitializeLoggingSupport:
""" """
def __init__(self) -> None: def __init__(self, global_constants: constants.Constants) -> None:
self.log_filename: str = "OpenCore-Patcher.log" self.constants: constants.Constants = global_constants
log_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S-%f")
self.log_filename: str = f"OpenCore-Patcher_{self.constants.patcher_version}_{log_time}.log"
self.log_filepath: Path = None self.log_filepath: Path = None
self.original_excepthook: sys = sys.excepthook self.original_excepthook: sys = sys.excepthook
self.original_thread_excepthook: threading = threading.excepthook self.original_thread_excepthook: threading = threading.excepthook
self.max_file_size: int = 1024 * 1024 * 10 # 10 MB self.max_file_size: int = 1024 * 1024 # 1 MB
self.file_size_redline: int = 1024 * 1024 * 9 # 9 MB, when to start cleaning log file self.file_size_redline: int = 1024 * 1024 - 1024 * 100 # 900 KB, when to start cleaning log file
self._initialize_logging_path() self._initialize_logging_path()
self._clean_log_file()
self._attempt_initialize_logging_configuration() self._attempt_initialize_logging_configuration()
self._start_logging()
self._implement_custom_traceback_handler() self._implement_custom_traceback_handler()
self._fix_file_permission() self._fix_file_permission()
self._clean_prior_version_logs()
def __del__(self) -> None:
self._restore_original_excepthook()
def _initialize_logging_path(self) -> None: def _initialize_logging_path(self) -> None:
@@ -52,39 +60,57 @@ class InitializeLoggingSupport:
Initialize logging framework storage path Initialize logging framework storage path
""" """
self.log_filepath = Path(f"~/Library/Logs/{self.log_filename}").expanduser() base_path = Path("~/Library/Logs").expanduser()
if not base_path.exists():
if not self.log_filepath.parent.exists():
# Likely in an installer environment, store in /Users/Shared # Likely in an installer environment, store in /Users/Shared
self.log_filepath = Path("/Users/Shared") / self.log_filename base_path = Path("/Users/Shared")
else:
print("- Initializing logging framework...") # create Dortania folder if it doesn't exist
print(f" - Log file: {self.log_filepath}") base_path = base_path / "Dortania"
if not base_path.exists():
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: try:
if backup_log_filepath.exists(): base_path.mkdir()
backup_log_filepath.unlink()
# Rename current log file to backup log file
self.log_filepath.rename(backup_log_filepath)
except Exception as e: except Exception as e:
print(f"- Failed to clean log file: {e}") logging.error(f"Failed to create Dortania folder: {e}")
base_path = Path("/Users/Shared")
self.log_filepath = Path(f"{base_path}/{self.log_filename}").expanduser()
self.constants.log_filepath = self.log_filepath
def _clean_prior_version_logs(self) -> None:
"""
Clean logs from old Patcher versions
Keep 10 latest logs
"""
paths = [
self.log_filepath.parent, # ~/Library/Logs/Dortania
self.log_filepath.parent.parent, # ~/Library/Logs (old location)
]
logs = []
for path in paths:
for file in path.glob("OpenCore-Patcher*"):
if not file.is_file():
continue
if not file.name.endswith(".log"):
continue
if file.name == self.log_filename:
continue
logs.append(file)
logs.sort(key=lambda x: x.stat().st_mtime, reverse=True)
for log in logs[9:]:
try:
log.unlink()
except Exception as e:
logging.error(f"Failed to delete log file: {e}")
def _fix_file_permission(self) -> None: def _fix_file_permission(self) -> None:
@@ -98,11 +124,21 @@ class InitializeLoggingSupport:
if os.geteuid() != 0: if os.geteuid() != 0:
return return
result = subprocess.run(["chmod", "777", self.log_filepath], capture_output=True) paths = [
self.log_filepath, # ~/Library/Logs/Dortania/OpenCore-Patcher_{version}_{date}.log
self.log_filepath.parent, # ~/Library/Logs/Dortania
]
for path in paths:
result = subprocess.run(["chmod", "777", path], capture_output=True)
if result.returncode != 0: if result.returncode != 0:
print(f"- Failed to fix log file permissions") logging.error(f"Failed to fix log file permissions")
if result.stdout:
logging.error("STDOUT:")
logging.error(result.stdout.decode("utf-8"))
if result.stderr: if result.stderr:
print(result.stderr.decode("utf-8")) logging.error("STDERR:")
logging.error(result.stderr.decode("utf-8"))
def _initialize_logging_configuration(self, log_to_file: bool = True) -> None: def _initialize_logging_configuration(self, log_to_file: bool = True) -> None:
@@ -119,7 +155,7 @@ class InitializeLoggingSupport:
logging.basicConfig( logging.basicConfig(
level=logging.NOTSET, level=logging.NOTSET,
format="%(asctime)s - %(filename)s (%(lineno)d): %(message)s", format="[%(asctime)s] [%(filename)-32s] [%(lineno)-4d]: %(message)s",
handlers=[ handlers=[
logging.StreamHandler(stream = sys.stdout), logging.StreamHandler(stream = sys.stdout),
logging.FileHandler(self.log_filepath) if log_to_file is True else logging.NullHandler() logging.FileHandler(self.log_filepath) if log_to_file is True else logging.NullHandler()
@@ -140,11 +176,32 @@ class InitializeLoggingSupport:
try: try:
self._initialize_logging_configuration() self._initialize_logging_configuration()
except Exception as e: except Exception as e:
print(f"- Failed to initialize logging framework: {e}") print(f"Failed to initialize logging framework: {e}")
print("- Retrying without logging to file...") print("Retrying without logging to file...")
self._initialize_logging_configuration(log_to_file=False) self._initialize_logging_configuration(log_to_file=False)
def _start_logging(self):
"""
Start logging, used as easily identifiable start point in logs
"""
str_msg = f"# OpenCore Legacy Patcher ({self.constants.patcher_version}) #"
str_len = len(str_msg)
logging.info('#' * str_len)
logging.info(str_msg)
logging.info('#' * str_len)
logging.info("Log file set:")
# Display relative path to avoid disclosing user's username
try:
path = self.log_filepath.relative_to(Path.home())
logging.info(f"~/{path}")
except ValueError:
logging.info(self.log_filepath)
def _implement_custom_traceback_handler(self) -> None: def _implement_custom_traceback_handler(self) -> None:
""" """
Reroute traceback to logging module Reroute traceback to logging module
@@ -155,6 +212,49 @@ class InitializeLoggingSupport:
Reroute traceback in main thread to logging module Reroute traceback in main thread to logging module
""" """
logging.error("Uncaught exception in main thread", exc_info=(type, value, tb)) logging.error("Uncaught exception in main thread", exc_info=(type, value, tb))
self._display_debug_properties()
if "wx/" in "".join(traceback.format_exception(type, value, tb)):
# Likely a GUI error, don't display error dialog
return
if self.constants.cli_mode is True:
threading.Thread(target=analytics_handler.Analytics(self.constants).send_crash_report, args=(self.log_filepath,)).start()
return
error_msg = f"OpenCore Legacy Patcher encountered the following internal error:\n\n"
error_msg += f"{type.__name__}: {value}"
if tb:
error_msg += f"\n\n{traceback.extract_tb(tb)[-1]}"
cant_log: bool = global_settings.GlobalEnviromentSettings().read_property("DisableCrashAndAnalyticsReporting")
if not isinstance(cant_log, bool):
cant_log = False
if self.constants.commit_info[0].startswith("refs/tags"):
cant_log = True
if cant_log is True:
error_msg += "\n\nReveal log file?"
else:
error_msg += "\n\nSend crash report to Dortania?"
# Ask user if they want to send crash report
try:
result = applescript.AppleScript(f'display dialog "{error_msg}" with title "OpenCore Legacy Patcher ({self.constants.patcher_version})" buttons {{"Yes", "No"}} default button "Yes" with icon caution').run()
except Exception as e:
logging.error(f"Failed to display crash report dialog: {e}")
return
if result[applescript.AEType(b'bhit')] != "Yes":
return
if cant_log is True:
subprocess.run(["open", "--reveal", self.log_filepath])
return
threading.Thread(target=analytics_handler.Analytics(self.constants).send_crash_report, args=(self.log_filepath,)).start()
def custom_thread_excepthook(args) -> None: def custom_thread_excepthook(args) -> None:
""" """
@@ -173,3 +273,23 @@ class InitializeLoggingSupport:
sys.excepthook = self.original_excepthook sys.excepthook = self.original_excepthook
threading.excepthook = self.original_thread_excepthook threading.excepthook = self.original_thread_excepthook
def _display_debug_properties(self) -> None:
"""
Display debug properties, primarily after main thread crash
"""
logging.info("Host Properties:")
logging.info(f" XNU Version: {self.constants.detected_os}.{self.constants.detected_os_minor}")
logging.info(f" XNU Build: {self.constants.detected_os_build}")
logging.info(f" macOS Version: {self.constants.detected_os_version}")
logging.info("Debug Properties:")
logging.info(f" Effective User ID: {os.geteuid()}")
logging.info(f" Effective Group ID: {os.getegid()}")
logging.info(f" Real User ID: {os.getuid()}")
logging.info(f" Real Group ID: {os.getgid()}")
logging.info(" Arguments passed to Patcher:")
for arg in sys.argv:
logging.info(f" {arg}")
logging.info(f"Host Properties:\n{pprint.pformat(self.constants.computer.__dict__, indent=4)}")

View File

@@ -6,6 +6,7 @@ import subprocess
import tempfile import tempfile
import enum import enum
import logging import logging
import applescript
from data import os_data from data import os_data
from resources import network_handler, utilities from resources import network_handler, utilities
@@ -52,23 +53,20 @@ class InstallerCreation():
bool: True if successful, False otherwise bool: True if successful, False otherwise
""" """
logging.info("- Extracting macOS installer from InstallAssistant.pkg\n This may take some time") logging.info("Extracting macOS installer from InstallAssistant.pkg")
args = [ try:
"osascript", applescript.AppleScript(
"-e",
f'''do shell script "installer -pkg {Path(download_path)}/InstallAssistant.pkg -target /"''' f'''do shell script "installer -pkg {Path(download_path)}/InstallAssistant.pkg -target /"'''
' with prompt "OpenCore Legacy Patcher needs administrator privileges to add InstallAssistant."' ' with prompt "OpenCore Legacy Patcher needs administrator privileges to extract the installer."'
" with administrator privileges" " with administrator privileges"
" without altering line endings", " without altering line endings",
] ).run()
except Exception as e:
result = subprocess.run(args,stdout=subprocess.PIPE, stderr=subprocess.PIPE) logging.info("Failed to install InstallAssistant")
if result.returncode != 0: logging.info(f" Error Code: {e}")
logging.info("- Failed to install InstallAssistant")
logging.info(f" Error Code: {result.returncode}")
return False return False
logging.info("- InstallAssistant installed") logging.info("InstallAssistant installed")
return True return True
@@ -203,7 +201,6 @@ fi
if not any(all_disks[disk]['removable'] is False for partition in all_disks[disk]): if not any(all_disks[disk]['removable'] is False for partition in all_disks[disk]):
continue continue
logging.info(f"disk {disk}: {all_disks[disk]['name']} ({utilities.human_fmt(all_disks[disk]['size'])})")
list_disks.update({ list_disks.update({
disk: { disk: {
"identifier": all_disks[disk]["identifier"], "identifier": all_disks[disk]["identifier"],
@@ -400,6 +397,7 @@ class RemoteInstallerCatalog:
"integrity": integrity, "integrity": integrity,
"Source": "Apple Inc.", "Source": "Apple Inc.",
"Variant": catalog_url, "Variant": catalog_url,
"OS": os_data.os_conversion.os_to_kernel(version)
} }
}) })
@@ -481,7 +479,6 @@ class RemoteInstallerCatalog:
os_builds.append(newest_apps[ia]["Build"]) os_builds.append(newest_apps[ia]["Build"])
# Final passthrough
# Remove Betas if there's a non-beta version available # Remove Betas if there's a non-beta version available
for ia in list(newest_apps): for ia in list(newest_apps):
if newest_apps[ia]["Variant"] in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]: if newest_apps[ia]["Variant"] in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]:
@@ -490,6 +487,11 @@ class RemoteInstallerCatalog:
newest_apps.pop(ia) newest_apps.pop(ia)
break break
# Remove unsupported versions (namely 14)
for ia in list(newest_apps):
if newest_apps[ia]["Version"].split(".")[0] not in supported_versions:
newest_apps.pop(ia)
return newest_apps return newest_apps
@@ -585,6 +587,7 @@ class LocalInstallerCatalog:
"Build": app_sdk, "Build": app_sdk,
"Path": application, "Path": application,
"Minimum Host OS": min_required, "Minimum Host OS": min_required,
"OS": kernel
} }
}) })

View File

@@ -1,12 +1,13 @@
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk # Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
import os
import sys import sys
import time import time
import logging import logging
import threading import threading
from pathlib import Path from pathlib import Path
from resources.gui import gui_main from resources.wx_gui import gui_entry
from resources import ( from resources import (
constants, constants,
utilities, utilities,
@@ -27,18 +28,14 @@ class OpenCoreLegacyPatcher:
""" """
def __init__(self) -> None: def __init__(self) -> None:
logging_handler.InitializeLoggingSupport()
self.constants: constants.Constants = constants.Constants() self.constants: constants.Constants = constants.Constants()
self.constants.wxpython_variant: bool = True logging_handler.InitializeLoggingSupport(self.constants)
logging.info(f"- Loading OpenCore Legacy Patcher v{self.constants.patcher_version}...")
self._generate_base_data() self._generate_base_data()
if utilities.check_cli_args() is None: if utilities.check_cli_args() is None:
gui_main.wx_python_gui(self.constants).main_menu(None) gui_entry.EntryPoint(self.constants).start()
def _generate_base_data(self) -> None: def _generate_base_data(self) -> None:
@@ -46,6 +43,11 @@ class OpenCoreLegacyPatcher:
Generate base data required for the patcher to run Generate base data required for the patcher to run
""" """
self.constants.wxpython_variant: bool = True
# Ensure we live after parent process dies (ie. LaunchAgent)
os.setpgrp()
# Generate OS data # Generate OS data
os_data = os_probe.OSProbe() os_data = os_probe.OSProbe()
self.constants.detected_os = os_data.detect_kernel_major() self.constants.detected_os = os_data.detect_kernel_major()
@@ -90,22 +92,18 @@ class OpenCoreLegacyPatcher:
# Generate defaults # Generate defaults
defaults.GenerateDefaults(self.computer.real_model, True, self.constants) defaults.GenerateDefaults(self.computer.real_model, True, self.constants)
threading.Thread(target=analytics_handler.Analytics, args=(self.constants,)).start() threading.Thread(target=analytics_handler.Analytics(self.constants).send_analytics).start()
if utilities.check_cli_args() is None: 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") self.constants.cli_mode = False
return return
logging.info("- Detected arguments, switching to CLI mode") logging.info("Detected arguments, switching to CLI mode")
self.constants.gui_mode = True # Assumes no user interaction is required self.constants.gui_mode = True # Assumes no user interaction is required
ignore_args = ["--auto_patch", "--gui_patch", "--gui_unpatch"] ignore_args = ["--auto_patch", "--gui_patch", "--gui_unpatch", "--update_installed"]
if not any(x in sys.argv for x in ignore_args): if not any(x in sys.argv for x in ignore_args):
self.constants.current_path = Path.cwd() 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) ignore_args = ignore_args.pop(0)
if not any(x in sys.argv for x in ignore_args): if not any(x in sys.argv for x in ignore_args):

View File

@@ -9,6 +9,7 @@ import threading
import logging import logging
import enum import enum
import hashlib import hashlib
import atexit
from pathlib import Path from pathlib import Path
from resources import utilities from resources import utilities
@@ -203,10 +204,10 @@ class DownloadObject:
""" """
self.status = DownloadStatus.DOWNLOADING self.status = DownloadStatus.DOWNLOADING
logging.info(f"- Starting download: {self.filename}") logging.info(f"Starting download: {self.filename}")
if spawn_thread: if spawn_thread:
if self.active_thread: if self.active_thread:
logging.error("- Download already in progress") logging.error("Download already in progress")
return return
self.should_checksum = verify_checksum self.should_checksum = verify_checksum
self.active_thread = threading.Thread(target=self._download, args=(display_progress,)) self.active_thread = threading.Thread(target=self._download, args=(display_progress,))
@@ -266,8 +267,8 @@ class DownloadObject:
else: else:
raise Exception("Content-Length missing from headers") raise Exception("Content-Length missing from headers")
except Exception as e: except Exception as e:
logging.error(f"- Error determining file size {self.url}: {str(e)}") logging.error(f"Error determining file size {self.url}: {str(e)}")
logging.error("- Assuming file size is 0") logging.error("Assuming file size is 0")
self.total_file_size = 0.0 self.total_file_size = 0.0
@@ -294,17 +295,17 @@ class DownloadObject:
try: try:
if Path(path).exists(): if Path(path).exists():
logging.info(f"- Deleting existing file: {path}") logging.info(f"Deleting existing file: {path}")
Path(path).unlink() Path(path).unlink()
return True return True
if not Path(path).parent.exists(): if not Path(path).parent.exists():
logging.info(f"- Creating directory: {Path(path).parent}") logging.info(f"Creating directory: {Path(path).parent}")
Path(path).parent.mkdir(parents=True, exist_ok=True) Path(path).parent.mkdir(parents=True, exist_ok=True)
available_space = utilities.get_free_space(Path(path).parent) available_space = utilities.get_free_space(Path(path).parent)
if self.total_file_size > available_space: 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)}" 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) logging.error(msg)
raise Exception(msg) raise Exception(msg)
@@ -312,9 +313,10 @@ class DownloadObject:
self.error = True self.error = True
self.error_msg = str(e) self.error_msg = str(e)
self.status = DownloadStatus.ERROR self.status = DownloadStatus.ERROR
logging.error(f"- Error preparing working directory {path}: {self.error_msg}") logging.error(f"Error preparing working directory {path}: {self.error_msg}")
return False return False
logging.info(f"- Directory ready: {path}")
return True return True
@@ -340,6 +342,7 @@ class DownloadObject:
response = NetworkUtilities().get(self.url, stream=True, timeout=10) response = NetworkUtilities().get(self.url, stream=True, timeout=10)
with open(self.filepath, 'wb') as file: with open(self.filepath, 'wb') as file:
atexit.register(self.stop)
for i, chunk in enumerate(response.iter_content(1024 * 1024 * 4)): for i, chunk in enumerate(response.iter_content(1024 * 1024 * 4)):
if self.should_stop: if self.should_stop:
raise Exception("Download stopped") raise Exception("Download stopped")
@@ -351,12 +354,12 @@ class DownloadObject:
if display_progress and i % 100: if display_progress and i % 100:
# Don't use logging here, as we'll be spamming the log file # Don't use logging here, as we'll be spamming the log file
if self.total_file_size == 0.0: if self.total_file_size == 0.0:
print(f"- Downloaded {utilities.human_fmt(self.downloaded_file_size)} of {self.filename}") print(f"Downloaded {utilities.human_fmt(self.downloaded_file_size)} of {self.filename}")
else: 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)") 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 self.download_complete = True
logging.info(f"- Download complete: {self.filename}") logging.info(f"Download complete: {self.filename}")
logging.info("- Stats:") logging.info("Stats:")
logging.info(f"- Downloaded size: {utilities.human_fmt(self.downloaded_file_size)}") 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"- 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"- Speed: {utilities.human_fmt(self.downloaded_file_size / (time.time() - self.start_time))}/s")
@@ -365,7 +368,7 @@ class DownloadObject:
self.error = True self.error = True
self.error_msg = str(e) self.error_msg = str(e)
self.status = DownloadStatus.ERROR self.status = DownloadStatus.ERROR
logging.error(f"- Error downloading {self.url}: {self.error_msg}") logging.error(f"Error downloading {self.url}: {self.error_msg}")
self.status = DownloadStatus.COMPLETE self.status = DownloadStatus.COMPLETE
utilities.enable_sleep_after_running() utilities.enable_sleep_after_running()
@@ -380,7 +383,6 @@ class DownloadObject:
""" """
if self.total_file_size == 0.0: if self.total_file_size == 0.0:
logging.error("- File size is 0, cannot calculate percent")
return -1 return -1
return self.downloaded_file_size / self.total_file_size * 100 return self.downloaded_file_size / self.total_file_size * 100
@@ -405,9 +407,11 @@ class DownloadObject:
""" """
if self.total_file_size == 0.0: if self.total_file_size == 0.0:
logging.error("- File size is 0, cannot calculate time remaining")
return -1 return -1
return (self.total_file_size - self.downloaded_file_size) / self.get_speed() speed = self.get_speed()
if speed <= 0:
return -1
return (self.total_file_size - self.downloaded_file_size) / speed
def get_file_size(self) -> float: def get_file_size(self) -> float:

View File

@@ -28,15 +28,15 @@ class RoutePayloadDiskImage:
""" """
if self.constants.wxpython_variant is True and not self.constants.launcher_script: if self.constants.wxpython_variant is True and not self.constants.launcher_script:
logging.info("- 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() self.temp_dir = tempfile.TemporaryDirectory()
logging.info(f"- New payloads location: {self.temp_dir.name}") logging.info(f"New payloads location: {self.temp_dir.name}")
logging.info("- Creating payloads directory") logging.info("Creating payloads directory")
Path(self.temp_dir.name / Path("payloads")).mkdir(parents=True, exist_ok=True) 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( output = subprocess.run(
[ [
"hdiutil", "attach", "-noverify", f"{self.constants.payload_path}.dmg", "hdiutil", "attach", "-noverify", f"{self.constants.payload_path_dmg}",
"-mountpoint", Path(self.temp_dir.name / Path("payloads")), "-mountpoint", Path(self.temp_dir.name / Path("payloads")),
"-nobrowse", "-nobrowse",
"-shadow", Path(self.temp_dir.name / Path("payloads_overlay")), "-shadow", Path(self.temp_dir.name / Path("payloads_overlay")),
@@ -45,17 +45,17 @@ class RoutePayloadDiskImage:
stdout=subprocess.PIPE, stderr=subprocess.STDOUT stdout=subprocess.PIPE, stderr=subprocess.STDOUT
) )
if output.returncode == 0: if output.returncode == 0:
logging.info("- Mounted payloads.dmg") logging.info("Mounted payloads.dmg")
self.constants.current_path = Path(self.temp_dir.name) self.constants.current_path = Path(self.temp_dir.name)
self.constants.payload_path = Path(self.temp_dir.name) / Path("payloads") 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: else:
logging.info("- Failed to mount payloads.dmg") logging.info("Failed to mount payloads.dmg")
logging.info(f"Output: {output.stdout.decode()}") logging.info(f"Output: {output.stdout.decode()}")
logging.info(f"Return Code: {output.returncode}") logging.info(f"Return Code: {output.returncode}")
def _unmount_active_dmgs(self, unmount_all_active=True) -> None: def _unmount_active_dmgs(self, unmount_all_active: bool = True) -> None:
""" """
Unmounts disk images associated with OCLP Unmounts disk images associated with OCLP
@@ -70,19 +70,21 @@ class RoutePayloadDiskImage:
dmg_info = subprocess.run(["hdiutil", "info", "-plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) dmg_info = subprocess.run(["hdiutil", "info", "-plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
dmg_info = plistlib.loads(dmg_info.stdout) dmg_info = plistlib.loads(dmg_info.stdout)
for variant in ["Universal-Binaries.dmg", "payloads.dmg"]:
for image in dmg_info["images"]: for image in dmg_info["images"]:
if image["image-path"].endswith("payloads.dmg"): if image["image-path"].endswith(variant):
if unmount_all_active is False: if unmount_all_active is False:
# Check that only our personal payloads.dmg is unmounted # Check that only our personal payloads.dmg is unmounted
if "shadow-path" in image: if "shadow-path" in image:
if self.temp_dir.name in image["shadow-path"]: if self.temp_dir.name in image["shadow-path"]:
logging.info("- Unmounting personal payloads.dmg") logging.info(f"Unmounting personal {variant}")
subprocess.run( subprocess.run(
["hdiutil", "detach", image["system-entities"][0]["dev-entry"], "-force"], ["hdiutil", "detach", image["system-entities"][0]["dev-entry"], "-force"],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT stdout=subprocess.PIPE, stderr=subprocess.STDOUT
) )
else: else:
logging.info(f"- Unmounting payloads.dmg at: {image['system-entities'][0]['dev-entry']}") logging.info(f"Unmounting {variant} at: {image['system-entities'][0]['dev-entry']}")
subprocess.run( subprocess.run(
["hdiutil", "detach", image["system-entities"][0]["dev-entry"], "-force"], ["hdiutil", "detach", image["system-entities"][0]["dev-entry"], "-force"],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT stdout=subprocess.PIPE, stderr=subprocess.STDOUT

View File

@@ -67,13 +67,6 @@ class PatchSysVolume:
self.skip_root_kmutil_requirement = self.hardware_details["Settings: Supports Auxiliary Cache"] self.skip_root_kmutil_requirement = self.hardware_details["Settings: Supports Auxiliary Cache"]
def __del__(self) -> None:
"""
Ensures that each time we're patching, we're using a clean PatcherSupportPkg folder
"""
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: Path = None, custom_data_mount_path: Path = None) -> None: def _init_pathing(self, custom_root_mount_path: Path = None, custom_data_mount_path: Path = None) -> None:
""" """
@@ -287,8 +280,6 @@ class PatchSysVolume:
if self.needs_kmutil_exemptions is True: if self.needs_kmutil_exemptions is True:
logging.info("Note: Apple will require you to open System Preferences -> Security to allow the new kernel extensions to be loaded") 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 self.constants.root_patcher_succeeded = True
if self.constants.gui_mode is False:
input("\nPress [ENTER] to continue")
def _rebuild_kernel_collection(self) -> bool: def _rebuild_kernel_collection(self) -> bool:
@@ -372,8 +363,6 @@ class PatchSysVolume:
logging.info(result.stdout.decode()) logging.info(result.stdout.decode())
logging.info("") logging.info("")
logging.info("\nPlease reboot the machine to avoid potential issues rerunning the patcher") 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 return False
if self.skip_root_kmutil_requirement is True: if self.skip_root_kmutil_requirement is True:
@@ -385,8 +374,6 @@ class PatchSysVolume:
logging.info(result.stdout.decode()) logging.info(result.stdout.decode())
logging.info("") logging.info("")
logging.info("\nPlease reboot the machine to avoid potential issues rerunning the patcher") 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 return False
for file in ["KextPolicy", "KextPolicy-shm", "KextPolicy-wal"]: for file in ["KextPolicy", "KextPolicy-shm", "KextPolicy-wal"]:
@@ -505,6 +492,8 @@ class PatchSysVolume:
if Path(oclp_path).exists(): if Path(oclp_path).exists():
oclp_plist_data = plistlib.load(Path(oclp_path).open("rb")) oclp_plist_data = plistlib.load(Path(oclp_path).open("rb"))
for key in oclp_plist_data: for key in oclp_plist_data:
if isinstance(oclp_plist_data[key], (bool, int)):
continue
if "Install" not in oclp_plist_data[key]: if "Install" not in oclp_plist_data[key]:
continue continue
for location in oclp_plist_data[key]["Install"]: for location in oclp_plist_data[key]["Install"]:
@@ -854,10 +843,27 @@ class PatchSysVolume:
logging.info("- Local PatcherSupportPkg resources available, continuing...") logging.info("- Local PatcherSupportPkg resources available, continuing...")
return True return True
if Path(self.constants.payload_local_binaries_root_path_zip).exists(): if Path(self.constants.payload_local_binaries_root_path_dmg).exists():
logging.info("- Local PatcherSupportPkg resources available, unzipping...") logging.info("- Local PatcherSupportPkg resources available, mounting...")
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)) output = subprocess.run(
[
"hdiutil", "attach", "-noverify", f"{self.constants.payload_local_binaries_root_path_dmg}",
"-mountpoint", Path(self.constants.payload_path / Path("Universal-Binaries")),
"-nobrowse",
"-shadow", Path(self.constants.payload_path / Path("Universal-Binaries_overlay")),
"-passphrase", "password"
],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
if output.returncode != 0:
logging.info("- Failed to mount Universal-Binaries.dmg")
logging.info(f"Output: {output.stdout.decode()}")
logging.info(f"Return Code: {output.returncode}")
return False
logging.info("- Mounted Universal-Binaries.dmg")
return True return True
logging.info("- PatcherSupportPkg resources missing, Patcher likely corrupted!!!") logging.info("- PatcherSupportPkg resources missing, Patcher likely corrupted!!!")
@@ -875,33 +881,18 @@ class PatchSysVolume:
self.patch_set_dictionary = sys_patch_generate.GenerateRootPatchSets(self.computer.real_model, self.constants, self.hardware_details).patchset self.patch_set_dictionary = sys_patch_generate.GenerateRootPatchSets(self.computer.real_model, self.constants, self.hardware_details).patchset
if self.patch_set_dictionary == {}: if self.patch_set_dictionary == {}:
change_menu = None
logging.info("- No Root Patches required for your machine!") logging.info("- No Root Patches required for your machine!")
if self.constants.gui_mode is False: return
input("\nPress [ENTER] to return to the main menu: ")
elif self.constants.gui_mode is False:
change_menu = input("Would you like to continue with Root Volume Patching?(y/n): ")
else:
change_menu = "y"
logging.info("- Continuing root patching")
if change_menu in ["y", "Y"]:
logging.info("- Verifying whether Root Patching possible") 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: 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") logging.info("- Patcher is capable of patching")
if self._check_files(): if self._check_files():
if self._mount_root_vol() is True: if self._mount_root_vol() is True:
self._patch_root_vol() self._patch_root_vol()
if self.constants.gui_mode is False:
input("\nPress [ENTER] to return to the main menu")
else: else:
logging.info("- 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:
logging.info("- Returning to main menu")
def start_unpatch(self) -> None: def start_unpatch(self) -> None:
""" """
@@ -912,11 +903,5 @@ class PatchSysVolume:
if sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).verify_patch_allowed(print_errors=True) is True: if sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).verify_patch_allowed(print_errors=True) is True:
if self._mount_root_vol() is True: if self._mount_root_vol() is True:
self._unpatch_root_vol() self._unpatch_root_vol()
if self.constants.gui_mode is False:
input("\nPress [ENTER] to return to the main menu")
else: else:
logging.info("- 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")

View File

@@ -1,14 +1,17 @@
# Copyright (C) 2022, Mykola Grymalyuk # Copyright (C) 2022, Mykola Grymalyuk
import wx
import logging
import plistlib import plistlib
import subprocess import subprocess
import webbrowser import webbrowser
import logging
from pathlib import Path from pathlib import Path
from resources import utilities, updates, global_settings, network_handler, constants from resources import utilities, updates, global_settings, network_handler, constants
from resources.sys_patch import sys_patch_detect from resources.sys_patch import sys_patch_detect
from resources.gui import gui_main from resources.wx_gui import gui_entry
class AutomaticSysPatch: class AutomaticSysPatch:
@@ -42,6 +45,28 @@ class AutomaticSysPatch:
logging.info("- 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 return
dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates()
if dict:
for key in dict:
version = dict[key]["Version"]
logging.info(f"- Found new version: {version}")
app = wx.App()
frame = wx.Frame(None, -1, "OpenCore Legacy Patcher")
dialog = wx.MessageDialog(
parent=frame,
message=f"Current Version: {self.constants.patcher_version}{' (Nightly)' if not self.constants.commit_info[0].startswith('refs/tags') else ''}\nNew version: {version}\nWould you like to update?",
caption="Update Available for OpenCore Legacy Patcher!",
style=wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION
)
dialog.SetYesNoCancelLabels("Download and install", "View on Github", "Ignore")
response = dialog.ShowModal()
if response == wx.ID_YES:
gui_entry.EntryPoint(self.constants).start(entry=gui_entry.SupportedEntryPoints.UPDATE_APP)
elif response == wx.ID_NO:
webbrowser.open(dict[key]["Github Link"])
return
if utilities.check_seal() is True: if utilities.check_seal() is True:
logging.info("- Detected Snapshot seal intact, detecting patches") logging.info("- Detected Snapshot seal intact, detecting patches")
patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set()
@@ -58,9 +83,8 @@ class AutomaticSysPatch:
for patch in patches: for patch in patches:
if patches[patch] is True and not patch.startswith("Settings") and not patch.startswith("Validation"): if patches[patch] is True and not patch.startswith("Settings") and not patch.startswith("Validation"):
patch_string += f"- {patch}\n" patch_string += f"- {patch}\n"
# Check for updates
dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates() logging.info("- No new binaries found on Github, proceeding with patching")
if not dict:
logging.info("- No new binaries found on Github, proceeding with patching") logging.info("- No new binaries found on Github, proceeding with patching")
if self.constants.launcher_script is None: if self.constants.launcher_script is None:
args_string = f"'{self.constants.launcher_binary}' --gui_patch" args_string = f"'{self.constants.launcher_binary}' --gui_patch"
@@ -97,30 +121,6 @@ class AutomaticSysPatch:
stderr=subprocess.STDOUT stderr=subprocess.STDOUT
) )
return return
else:
for key in dict:
version = dict[key]["Version"]
github_link = dict[key]["Github Link"]
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
# we never want to run the root patcher if there are updates available
args = [
"osascript",
"-e",
f"""display dialog "OpenCore Legacy Patcher has detected you're running without Root Patches, and would like to install them.\n\nHowever we've detected a new version of OCLP on Github. Would you like to view this?\n\nCurrent Version: {self.constants.patcher_version}\nLatest Version: {version}\n\nNote: After downloading the latest OCLP version, open the app and run the 'Post Install Root Patcher' from the main menu." """
f'with icon POSIX file "{self.constants.app_icon_path}"',
]
output = subprocess.run(
args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
if output.returncode == 0:
webbrowser.open(github_link)
return
else: else:
logging.info("- No patches detected") logging.info("- No patches detected")
else: else:
@@ -170,7 +170,7 @@ class AutomaticSysPatch:
if output.returncode == 0: if output.returncode == 0:
logging.info("- Launching GUI's Build/Install menu") logging.info("- Launching GUI's Build/Install menu")
self.constants.start_build_install = True self.constants.start_build_install = True
gui_main.wx_python_gui(self.constants).main_menu(None) gui_entry.EntryPoint(self.constants).start(entry=gui_entry.SupportedEntryPoints.BUILD_OC)
return False return False
@@ -245,7 +245,7 @@ class AutomaticSysPatch:
if output.returncode == 0: if output.returncode == 0:
logging.info("- Launching GUI's Build/Install menu") logging.info("- Launching GUI's Build/Install menu")
self.constants.start_build_install = True self.constants.start_build_install = True
gui_main.wx_python_gui(self.constants).main_menu(None) gui_entry.EntryPoint(self.constants).start(entry=gui_entry.SupportedEntryPoints.BUILD_OC)
except KeyError: except KeyError:
logging.info("- Unable to determine if boot disk is removable, skipping prompt") logging.info("- Unable to determine if boot disk is removable, skipping prompt")
@@ -350,7 +350,11 @@ class AutomaticSysPatch:
for kext in Path("/Library/Extensions").glob("*.kext"): for kext in Path("/Library/Extensions").glob("*.kext"):
if not Path(f"{kext}/Contents/Info.plist").exists(): if not Path(f"{kext}/Contents/Info.plist").exists():
continue continue
try:
kext_plist = plistlib.load(open(f"{kext}/Contents/Info.plist", "rb")) kext_plist = plistlib.load(open(f"{kext}/Contents/Info.plist", "rb"))
except Exception as e:
logging.info(f" - Failed to load plist for {kext.name}: {e}")
continue
if "GPUCompanionBundles" not in kext_plist: if "GPUCompanionBundles" not in kext_plist:
continue continue
logging.info(f" - Found kext with GPUCompanionBundles: {kext.name}") logging.info(f" - Found kext with GPUCompanionBundles: {kext.name}")

View File

@@ -92,7 +92,7 @@ class DetectRootPatch:
non_metal_os = os_data.os_data.catalina non_metal_os = os_data.os_data.catalina
for i, gpu in enumerate(gpus): for i, gpu in enumerate(gpus):
if gpu.class_code and gpu.class_code != 0xFFFFFFFF: if gpu.class_code and gpu.class_code != 0xFFFFFFFF:
logging.info(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 gpu.arch in [device_probe.NVIDIA.Archs.Tesla] and self.constants.force_nv_web is False:
if self.constants.detected_os > non_metal_os: if self.constants.detected_os > non_metal_os:
self.nvidia_tesla = True self.nvidia_tesla = True
@@ -579,6 +579,7 @@ class DetectRootPatch:
"Settings: Kernel Debug Kit missing": self.missing_kdk if self.constants.detected_os >= os_data.os_data.ventura.value else False, "Settings: Kernel Debug Kit missing": self.missing_kdk if self.constants.detected_os >= os_data.os_data.ventura.value else False,
"Validation: Patching Possible": self.verify_patch_allowed(), "Validation: Patching Possible": self.verify_patch_allowed(),
"Validation: Unpatching Possible": self._verify_unpatch_allowed(), "Validation: Unpatching Possible": self._verify_unpatch_allowed(),
f"Validation: Unsupported Host OS": True if self.constants.detected_os > os_data.os_data.ventura and not Path("~/.dortania_developer").expanduser().exists() else False,
f"Validation: SIP is enabled (Required: {self._check_sip()[2]} or higher)": self.sip_enabled, 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, f"Validation: Currently Booted SIP: ({hex(py_sip_xnu.SipXnu().get_sip_status().value)})": self.sip_enabled,
"Validation: SecureBootModel is enabled": self.sbm_enabled, "Validation: SecureBootModel is enabled": self.sbm_enabled,

View File

@@ -40,7 +40,7 @@ class GenerateRootPatchSets:
utilities.cls() utilities.cls()
logging.info("- The following patches will be applied:") logging.info("The following patches will be applied:")
if self.hardware_details["Graphics: Intel Ironlake"] is True: if self.hardware_details["Graphics: Intel Ironlake"] is True:
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]}) required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
@@ -52,8 +52,8 @@ class GenerateRootPatchSets:
required_patches.update({"High Sierra GVA": all_hardware_patchset["Graphics"]["High Sierra GVA"]}) 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({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]})
required_patches.update({"Intel Sandy Bridge": all_hardware_patchset["Graphics"]["Intel Sandy Bridge"]}) 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 # Patchset breaks Display Profiles, don't install if primary GPU is AMD. Give users option to disable patch in settings to restore Display Profiles
if self.constants.computer.real_model not in ["Macmini5,2", "iMac12,1", "iMac12,2"]: if self.constants.computer.real_model not in ["Macmini5,2", "iMac12,1", "iMac12,2"] or self.constants.disable_cat_colorsync is False:
required_patches.update({"Non-Metal ColorSync Workaround": all_hardware_patchset["Graphics"]["Non-Metal ColorSync Workaround"]}) required_patches.update({"Non-Metal ColorSync Workaround": all_hardware_patchset["Graphics"]["Non-Metal ColorSync Workaround"]})
if self.hardware_details["Graphics: Intel Ivy Bridge"] is True: if self.hardware_details["Graphics: Intel Ivy Bridge"] is True:

View File

@@ -42,10 +42,10 @@ class SysPatchHelpers:
if self.constants.computer.reported_board_id in self.constants.sandy_board_id_stock: if self.constants.computer.reported_board_id in self.constants.sandy_board_id_stock:
return return
logging.info(f"- Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching") logging.info(f"Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching")
board_to_patch = generate_smbios.determine_best_board_id_for_sandy(self.constants.computer.reported_board_id, self.constants.computer.gpus) 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}") 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()) 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()) reported_board_hex = bytes.fromhex(self.constants.computer.reported_board_id.encode('utf-8').hex())
@@ -54,12 +54,12 @@ class SysPatchHelpers:
# Pad the reported Board ID with zeros to match the length of the board to patch # 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)) 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): 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}") 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!!!") 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" path = source_files_path + "/10.13.6/System/Library/Extensions/AppleIntelSNBGraphicsFB.kext/Contents/MacOS/AppleIntelSNBGraphicsFB"
if not Path(path).exists(): if not Path(path).exists():
logging.info(f"- Error: Could not find {path}") logging.info(f"Error: Could not find {path}")
raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!") raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!")
with open(path, 'rb') as f: with open(path, 'rb') as f:
@@ -97,6 +97,7 @@ class SysPatchHelpers:
"Commit URL": f"{self.constants.commit_info[2]}", "Commit URL": f"{self.constants.commit_info[2]}",
"Kernel Debug Kit Used": f"{kdk_string}", "Kernel Debug Kit Used": f"{kdk_string}",
"OS Version": f"{self.constants.detected_os}.{self.constants.detected_os_minor} ({self.constants.detected_os_build})", "OS Version": f"{self.constants.detected_os}.{self.constants.detected_os_minor} ({self.constants.detected_os_build})",
"Custom Signature": bool(Path(self.constants.payload_local_binaries_root_path / ".signed").exists()),
} }
data.update(patchset) data.update(patchset)
@@ -127,7 +128,7 @@ class SysPatchHelpers:
if self.constants.detected_os < os_data.os_data.ventura: if self.constants.detected_os < os_data.os_data.ventura:
return return
logging.info("- Disabling WindowServer Caching") logging.info("Disabling WindowServer Caching")
# Invoke via 'bash -c' to resolve pathing # Invoke via 'bash -c' to resolve pathing
utilities.elevated(["bash", "-c", "rm -rf /private/var/folders/*/*/*/WindowServer/com.apple.WindowServer"]) utilities.elevated(["bash", "-c", "rm -rf /private/var/folders/*/*/*/WindowServer/com.apple.WindowServer"])
# Disable writing to WindowServer folder # Disable writing to WindowServer folder
@@ -149,7 +150,7 @@ class SysPatchHelpers:
if self.constants.detected_os < os_data.os_data.ventura: if self.constants.detected_os < os_data.os_data.ventura:
return return
logging.info("- 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 = "~/Library/Containers/com.apple.notificationcenterui/Data/Library/Preferences/com.apple.notificationcenterui.plist"
file_path = Path(file_path).expanduser() file_path = Path(file_path).expanduser()
@@ -209,7 +210,7 @@ class SysPatchHelpers:
if self.constants.detected_os < os_data.os_data.big_sur: if self.constants.detected_os < os_data.os_data.big_sur:
return return
logging.info("- 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) result = utilities.elevated([self.constants.rsrrepair_userspace_path, "--install"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0: if result.returncode != 0:
logging.info(f"- Failed to install RSRRepair: {result.stdout.decode()}") logging.info(f"- Failed to install RSRRepair: {result.stdout.decode()}")
@@ -261,7 +262,7 @@ class SysPatchHelpers:
if not file.name.startswith("31001."): if not file.name.startswith("31001."):
continue continue
logging.info(f"- Merging GPUCompiler.framework libraries to match binary") logging.info(f"Merging GPUCompiler.framework libraries to match binary")
src_dir = f"{LIBRARY_DIR}/{file.name}" src_dir = f"{LIBRARY_DIR}/{file.name}"
if not Path(f"{DEST_DIR}/lib").exists(): if not Path(f"{DEST_DIR}/lib").exists():

View File

@@ -2,7 +2,6 @@
# Check whether new updates are available for OpenCore Legacy Patcher binary # Check whether new updates are available for OpenCore Legacy Patcher binary
# Call check_binary_updates() to determine if any updates are available # Call check_binary_updates() to determine if any updates are available
# Returns dict with Link and Version of the latest binary update if available # Returns dict with Link and Version of the latest binary update if available
import requests
import logging import logging
from resources import network_handler, constants from resources import network_handler, constants
@@ -35,6 +34,12 @@ class CheckBinaryUpdates:
if local_version is None: if local_version is None:
local_version = self.binary_version_array local_version = self.binary_version_array
if local_version == remote_version:
if not self.constants.commit_info[0].startswith("refs/tags"):
# Check for nightly builds
return True
# Pad version numbers to match length (ie. 0.1.0 vs 0.1.0.1) # Pad version numbers to match length (ie. 0.1.0 vs 0.1.0.1)
while len(remote_version) > len(local_version): while len(remote_version) > len(local_version):
local_version.append(0) local_version.append(0)
@@ -99,6 +104,9 @@ class CheckBinaryUpdates:
response = network_handler.NetworkUtilities().get(REPO_LATEST_RELEASE_URL) response = network_handler.NetworkUtilities().get(REPO_LATEST_RELEASE_URL)
data_set = response.json() data_set = response.json()
if "tag_name" not in data_set:
return None
self.remote_version = data_set["tag_name"] self.remote_version = data_set["tag_name"]
self.remote_version_array = self.remote_version.split(".") self.remote_version_array = self.remote_version.split(".")
@@ -108,7 +116,7 @@ class CheckBinaryUpdates:
return None return None
for asset in data_set["assets"]: for asset in data_set["assets"]:
logging.info(f"- Found asset: {asset['name']}") logging.info(f"Found asset: {asset['name']}")
if self._determine_remote_type(asset["name"]) == self._determine_local_build_type(): if self._determine_remote_type(asset["name"]) == self._determine_local_build_type():
available_binaries.update({ available_binaries.update({
asset['name']: { asset['name']: {

View File

@@ -48,6 +48,44 @@ def human_fmt(num):
return "%.1f %s" % (num, "EB") return "%.1f %s" % (num, "EB")
def seconds_to_readable_time(seconds) -> str:
"""
Convert seconds to a readable time format
Parameters:
seconds (int | float | str): Seconds to convert
Returns:
str: Readable time format
"""
seconds = int(seconds)
time = ""
if seconds == 0:
return "Almost done"
if seconds < 0:
return "Indeterminate"
years, seconds = divmod(seconds, 31536000)
days, seconds = divmod(seconds, 86400)
hours, seconds = divmod(seconds, 3600)
minutes, seconds = divmod(seconds, 60)
if years > 0:
return "Over a year"
if days > 0:
if days > 31:
return "Over a month"
time += f"{days}d "
if hours > 0:
time += f"{hours}h "
if minutes > 0:
time += f"{minutes}m "
if seconds > 0:
time += f"{seconds}s"
return time
def header(lines): def header(lines):
lines = [i for i in lines if i is not None] lines = [i for i in lines if i is not None]
total_length = len(max(lines, key=len)) + 4 total_length = len(max(lines, key=len)) + 4
@@ -120,7 +158,7 @@ sleep_process = None
def disable_sleep_while_running(): def disable_sleep_while_running():
global sleep_process global sleep_process
logging.info("- Disabling Idle Sleep") logging.info("Disabling Idle Sleep")
if sleep_process is None: if sleep_process is None:
# If sleep_process is active, we'll just keep it running # 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) sleep_process = subprocess.Popen(["caffeinate", "-d", "-i", "-s"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -130,7 +168,7 @@ def disable_sleep_while_running():
def enable_sleep_after_running(): def enable_sleep_after_running():
global sleep_process global sleep_process
if sleep_process: if sleep_process:
logging.info("- Re-enabling Idle Sleep") logging.info("Re-enabling Idle Sleep")
sleep_process.kill() sleep_process.kill()
sleep_process = None sleep_process = None
@@ -356,9 +394,6 @@ def get_firmware_vendor(*, decode: bool = False):
value = value.strip("\0") value = value.strip("\0")
return value return value
def dump_constants(constants):
with open(os.path.join(os.path.expanduser('~'), 'Desktop', 'internal_data.txt'), 'w') as f:
f.write(str(vars(constants)))
def find_apfs_physical_volume(device): def find_apfs_physical_volume(device):
# ex: disk3s1s1 # ex: disk3s1s1
@@ -478,7 +513,7 @@ def block_os_updaters():
for bad_process in bad_processes: for bad_process in bad_processes:
if bad_process in current_process: if bad_process in current_process:
if pid != "": if pid != "":
logging.info(f"- Killing Process: {pid} - {current_process.split('/')[-1]}") logging.info(f"Killing Process: {pid} - {current_process.split('/')[-1]}")
subprocess.run(["kill", "-9", pid]) subprocess.run(["kill", "-9", pid])
break break
@@ -534,6 +569,7 @@ def check_cli_args():
parser.add_argument("--gui_patch", help="Starts GUI in Root Patcher", action="store_true", required=False) parser.add_argument("--gui_patch", help="Starts GUI in Root Patcher", action="store_true", required=False)
parser.add_argument("--gui_unpatch", help="Starts GUI in Root Unpatcher", action="store_true", required=False) parser.add_argument("--gui_unpatch", help="Starts GUI in Root Unpatcher", action="store_true", required=False)
parser.add_argument("--auto_patch", help="Check if patches are needed and prompt user", action="store_true", required=False) parser.add_argument("--auto_patch", help="Check if patches are needed and prompt user", action="store_true", required=False)
parser.add_argument("--update_installed", help="Prompt user to finish updating via GUI", action="store_true", required=False)
args = parser.parse_args() args = parser.parse_args()
if not (args.build or args.patch_sys_vol or args.unpatch_sys_vol or args.validate or args.auto_patch): if not (args.build or args.patch_sys_vol or args.unpatch_sys_vol or args.validate or args.auto_patch):

View File

@@ -4,7 +4,7 @@ from pathlib import Path
from resources.sys_patch import sys_patch_helpers from resources.sys_patch import sys_patch_helpers
from resources.build import build from resources.build import build
from resources import constants from resources import constants, network_handler
from data import example_data, model_array, sys_patch_dict, os_data from data import example_data, model_array, sys_patch_dict, os_data
@@ -120,7 +120,7 @@ class PatcherValidation:
logging.info(f"File not found: {source_file}") logging.info(f"File not found: {source_file}")
raise Exception(f"Failed to find {source_file}") raise Exception(f"Failed to find {source_file}")
logging.info(f"- Validating against Darwin {major_kernel}.{minor_kernel}") 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): 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") raise Exception("Failed to generate patchset plist")
@@ -133,18 +133,35 @@ class PatcherValidation:
Validates sys_patch modules Validates sys_patch modules
""" """
if Path(self.constants.payload_local_binaries_root_path_zip).exists(): if not Path(self.constants.payload_local_binaries_root_path_dmg).exists():
dl_obj = network_handler.DownloadObject(f"https://github.com/dortania/PatcherSupportPkg/releases/download/{self.constants.patcher_support_pkg_version}/Universal-Binaries.dmg", self.constants.payload_local_binaries_root_path_dmg)
dl_obj.download(spawn_thread=False)
if dl_obj.download_complete is False:
logging.info("Failed to download Universal-Binaries.dmg")
raise Exception("Failed to download Universal-Binaries.dmg")
logging.info("Validating Root Patch File integrity") logging.info("Validating Root Patch File integrity")
if not Path(self.constants.payload_local_binaries_root_path).exists(): output = subprocess.run(
subprocess.run(
[ [
"ditto", "-V", "-x", "-k", "--sequesterRsrc", "--rsrc", "hdiutil", "attach", "-noverify", f"{self.constants.payload_local_binaries_root_path_dmg}",
self.constants.payload_local_binaries_root_path_zip, "-mountpoint", Path(self.constants.payload_path / Path("Universal-Binaries")),
self.constants.payload_path "-nobrowse",
"-shadow", Path(self.constants.payload_path / Path("Universal-Binaries_overlay")),
"-passphrase", "password"
], ],
stdout=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
stderr=subprocess.STDOUT
) )
if output.returncode != 0:
logging.info("Failed to mount Universal-Binaries.dmg")
logging.info(f"Output: {output.stdout.decode()}")
logging.info(f"Return Code: {output.returncode}")
raise Exception("Failed to mount Universal-Binaries.dmg")
logging.info("Mounted Universal-Binaries.dmg")
for supported_os in [os_data.os_data.big_sur, os_data.os_data.monterey, os_data.os_data.ventura]: 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): for i in range(0, 10):
self._validate_root_patch_files(supported_os, i) self._validate_root_patch_files(supported_os, i)
@@ -152,16 +169,28 @@ class PatcherValidation:
self.constants.computer.reported_board_id = "Mac-7BA5B2DFE22DDD8C" 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) sys_patch_helpers.SysPatchHelpers(self.constants).snb_board_id_patch(self.constants.payload_local_binaries_root_path)
# Clean up # unmount the dmg
output = subprocess.run(
[
"hdiutil", "detach", Path(self.constants.payload_path / Path("Universal-Binaries")),
"-force"
],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
if output.returncode != 0:
logging.info("Failed to unmount Universal-Binaries.dmg")
logging.info(f"Output: {output.stdout.decode()}")
logging.info(f"Return Code: {output.returncode}")
raise Exception("Failed to unmount Universal-Binaries.dmg")
subprocess.run( subprocess.run(
[ [
"rm", "-rf", self.constants.payload_local_binaries_root_path "rm", "-f", Path(self.constants.payload_path / Path("Universal-Binaries_overlay"))
], ],
stdout=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
stderr=subprocess.STDOUT
) )
else:
logging.info("- Skipping Root Patch File integrity validation")
def _validate_configs(self) -> None: def _validate_configs(self) -> None:

View File

@@ -0,0 +1,67 @@
# About frame, just to sat
import wx
import wx.adv
import logging
from resources.wx_gui import gui_support
from resources import constants
class AboutFrame(wx.Frame):
def __init__(self, global_constants: constants.Constants) -> None:
if wx.FindWindowByName("About"):
return
logging.info("Generating About frame")
super(AboutFrame, self).__init__(None, title="About", size=(350, 350), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
self.constants: constants.Constants = global_constants
self.Centre()
self.hyperlink_colour = (25, 179, 231)
self._generate_elements(self)
self.Show()
def _generate_elements(self, frame: wx.Frame) -> None:
# Set title
title = wx.StaticText(frame, label="OpenCore Legacy Patcher", pos=(-1, 5))
title.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title.Centre(wx.HORIZONTAL)
# Set version
version = wx.StaticText(frame, label=f"Version: {self.constants.patcher_version}", pos=(-1, title.GetPosition()[1] + title.GetSize()[1] + 5))
version.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
version.Centre(wx.HORIZONTAL)
# Description
description = [
"Written by a small group of Mac hobbyists who just",
"want to keep old machines out of the landfill!",
]
spacer = 5
for line in description:
desc = wx.StaticText(frame, label=line, pos=(-1, version.GetPosition()[1] + version.GetSize()[1] + 5 + spacer))
desc.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
desc.Centre(wx.HORIZONTAL)
spacer += 20
# Set icon
icon_mac = "/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/com.apple.macbook-unibody-plastic.icns"
icon_mac = wx.StaticBitmap(frame, bitmap=wx.Bitmap(icon_mac, wx.BITMAP_TYPE_ICON), pos=(5, desc.GetPosition()[1] - 15))
icon_mac.SetSize((160, 160))
icon_mac.Centre(wx.HORIZONTAL)
icon_path = str(self.constants.app_icon_path)
icon = wx.StaticBitmap(frame, bitmap=wx.Bitmap(icon_path, wx.BITMAP_TYPE_ICON), pos=(5, desc.GetPosition()[1] + desc.GetSize()[1] + 17))
icon.SetSize((64, 64))
icon.Centre(wx.HORIZONTAL)
# Set frame size
frame.SetSize((-1, icon.GetPosition()[1] + icon.GetSize()[1] + 60))

View File

@@ -0,0 +1,160 @@
# Generate UI for Building OpenCore
import wx
import logging
import threading
import traceback
from resources import constants
from resources.build import build
from resources.wx_gui import (
gui_main_menu,
gui_install_oc,
gui_support
)
class BuildFrame(wx.Frame):
"""
Create a frame for building OpenCore
Uses a Modal Dialog for smoother transition from other frames
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None) -> None:
logging.info("Initializing Build Frame")
super(BuildFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
gui_support.GenerateMenubar(self, global_constants).generate()
self.install_button: wx.Button = None
self.text_box: wx.TextCtrl = None
self.frame_modal: wx.Dialog = None
self.constants: constants.Constants = global_constants
self.title: str = title
self.stock_output = logging.getLogger().handlers[0].stream
self.frame_modal = wx.Dialog(self, title=title, size=(400, 200))
self._generate_elements(self.frame_modal)
if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE:
self.constants.update_stage = gui_support.AutoUpdateStages.BUILDING
self.Centre()
self.frame_modal.ShowWindowModal()
self._invoke_build()
def _generate_elements(self, frame: wx.Frame = None) -> None:
"""
Generate UI elements for build frame
Format:
- Title label: Build and Install OpenCore
- Text: Model: {Build or Host Model}
- Button: Install OpenCore
- Read-only text box: {empty}
- Button: Return to Main Menu
"""
frame = self if not frame else frame
title_label = wx.StaticText(frame, label="Build and Install OpenCore", pos=(-1,5))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
model_label = wx.StaticText(frame, label=f"Model: {self.constants.custom_model or self.constants.computer.real_model}", pos=(-1,30))
model_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
model_label.Centre(wx.HORIZONTAL)
# Button: Install OpenCore
install_button = wx.Button(frame, label="🔩 Install OpenCore", pos=(-1, model_label.GetPosition()[1] + model_label.GetSize()[1]), size=(150, 30))
install_button.Bind(wx.EVT_BUTTON, self.on_install)
install_button.Centre(wx.HORIZONTAL)
install_button.Disable()
self.install_button = install_button
# Read-only text box: {empty}
text_box = wx.TextCtrl(frame, value="", pos=(-1, install_button.GetPosition()[1] + install_button.GetSize()[1] + 10), size=(400, 350), style=wx.TE_READONLY | wx.TE_MULTILINE | wx.TE_RICH2)
text_box.Centre(wx.HORIZONTAL)
self.text_box = text_box
# Button: Return to Main Menu
return_button = wx.Button(frame, label="Return to Main Menu", pos=(-1, text_box.GetPosition()[1] + text_box.GetSize()[1] + 5), size=(200, 30))
return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu)
return_button.Centre(wx.HORIZONTAL)
return_button.Disable()
self.return_button = return_button
# Adjust window size to fit all elements
frame.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40))
def _invoke_build(self) -> None:
"""
Invokes build function and waits for it to finish
"""
while gui_support.PayloadMount(self.constants, self).is_unpack_finished() is False:
wx.Yield()
thread = threading.Thread(target=self._build)
thread.start()
while thread.is_alive():
wx.Yield()
self.return_button.Enable()
dialog = wx.MessageDialog(
parent=self,
message=f"Would you like to install OpenCore now?",
caption="Finished building your OpenCore configuration!",
style=wx.YES_NO | wx.ICON_QUESTION
)
dialog.SetYesNoLabels("Install to disk", "View build log")
self.on_install() if dialog.ShowModal() == wx.ID_YES else self.install_button.Enable()
def _build(self) -> None:
"""
Calls build function and redirects stdout to the text box
"""
logger = logging.getLogger()
logger.addHandler(gui_support.ThreadHandler(self.text_box))
try:
build.BuildOpenCore(self.constants.custom_model or self.constants.computer.real_model, self.constants)
except:
logging.error("An internal error occurred while building:\n")
logging.error(traceback.format_exc())
logger.removeHandler(logger.handlers[2])
def on_return_to_main_menu(self, event: wx.Event = None) -> None:
"""
Return to main menu
"""
self.frame_modal.Hide()
main_menu_frame = gui_main_menu.MainFrame(
None,
title=self.title,
global_constants=self.constants,
screen_location=self.GetScreenPosition()
)
main_menu_frame.Show()
self.frame_modal.Destroy()
self.Destroy()
def on_install(self, event: wx.Event = None) -> None:
"""
Launch install frame
"""
self.frame_modal.Destroy()
self.Destroy()
install_oc_frame = gui_install_oc.InstallOCFrame(
None,
title=self.title,
global_constants=self.constants,
screen_location=self.GetScreenPosition()
)
install_oc_frame.Show()

View File

@@ -0,0 +1,104 @@
# Generate UI for downloading files
import wx
import logging
from resources import (
constants,
network_handler,
utilities
)
class DownloadFrame(wx.Frame):
"""
Update provided frame with download stats
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, download_obj: network_handler.DownloadObject, item_name: str) -> None:
logging.info("Initializing Download Frame")
self.constants: constants.Constants = global_constants
self.title: str = title
self.parent: wx.Frame = parent
self.download_obj: network_handler.DownloadObject = download_obj
self.item_name: str = item_name
self.user_cancelled: bool = False
self.frame_modal = wx.Dialog(parent, title=title, size=(400, 200))
self._generate_elements(self.frame_modal)
def _generate_elements(self, frame: wx.Dialog = None) -> None:
"""
Generate elements for download frame
"""
frame = self if not frame else frame
title_label = wx.StaticText(frame, label=f"Downloading: {self.item_name}", pos=(-1,5))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
label_amount = wx.StaticText(frame, label="0.00 B downloaded of 0.00B (0.00%)", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5))
label_amount.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
label_amount.Centre(wx.HORIZONTAL)
label_speed = wx.StaticText(frame, label="Average download speed: Unknown", pos=(-1, label_amount.GetPosition()[1] + label_amount.GetSize()[1] + 5))
label_speed.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
label_speed.Centre(wx.HORIZONTAL)
label_est_time = wx.StaticText(frame, label="Estimated time remaining: Unknown", pos=(-1, label_speed.GetPosition()[1] + label_speed.GetSize()[1] + 5))
label_est_time.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
label_est_time.Centre(wx.HORIZONTAL)
progress_bar = wx.Gauge(frame, range=100, pos=(-1, label_est_time.GetPosition()[1] + label_est_time.GetSize()[1] + 5), size=(300, 20))
progress_bar.Centre(wx.HORIZONTAL)
return_button = wx.Button(frame, label="Return", pos=(-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 5))
return_button.Bind(wx.EVT_BUTTON, lambda event: self.terminate_download())
return_button.Centre(wx.HORIZONTAL)
# Set size of frame
frame.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40))
frame.ShowWindowModal()
self.download_obj.download()
while self.download_obj.is_active():
percentage: int = self.download_obj.get_percent()
if percentage == -1:
amount_str = f"{utilities.human_fmt(self.download_obj.downloaded_file_size)} downloaded"
progress_bar.Pulse()
else:
amount_str = f"{utilities.human_fmt(self.download_obj.downloaded_file_size)} downloaded of {utilities.human_fmt(self.download_obj.total_file_size)} ({percentage:.2f}%)"
progress_bar.SetValue(int(percentage))
label_amount.SetLabel(amount_str)
label_amount.Centre(wx.HORIZONTAL)
label_speed.SetLabel(
f"Average download speed: {utilities.human_fmt(self.download_obj.get_speed())}/s"
)
label_est_time.SetLabel(
f"Estimated time remaining: {utilities.seconds_to_readable_time(self.download_obj.get_time_remaining())}"
)
wx.Yield()
if self.download_obj.download_complete is False and self.user_cancelled is False:
wx.MessageBox(f"Download failed: \n{self.download_obj.error_msg}", "Error", wx.OK | wx.ICON_ERROR)
frame.Destroy()
def terminate_download(self) -> None:
"""
Terminate download
"""
if wx.MessageBox("Are you sure you want to cancel the download?", "Cancel Download", wx.YES_NO | wx.ICON_QUESTION | wx.NO_DEFAULT) == wx.YES:
logging.info("User cancelled download")
self.user_cancelled = True
self.download_obj.stop()

View File

@@ -0,0 +1,92 @@
# Entry point for the wxPython GUI
import wx
import sys
import atexit
import logging
from resources import constants
from resources.wx_gui import (
gui_main_menu,
gui_build,
gui_install_oc,
gui_sys_patch_start,
gui_update,
)
from resources.sys_patch import sys_patch_detect
class SupportedEntryPoints:
"""
Enum for supported entry points
"""
MAIN_MENU = gui_main_menu.MainFrame
BUILD_OC = gui_build.BuildFrame
INSTALL_OC = gui_install_oc.InstallOCFrame
SYS_PATCH = gui_sys_patch_start.SysPatchStartFrame
UPDATE_APP = gui_update.UpdateFrame
class EntryPoint:
def __init__(self, global_constants: constants.Constants) -> None:
self.app: wx.App = None
self.main_menu_frame: gui_main_menu.MainFrame = None
self.constants: constants.Constants = global_constants
self.constants.gui_mode = True
def _generate_base_data(self) -> None:
self.app = wx.App()
self.app.SetAppName(self.constants.patcher_name)
def start(self, entry: SupportedEntryPoints = gui_main_menu.MainFrame) -> None:
"""
Launches entry point for the wxPython GUI
"""
self._generate_base_data()
if "--gui_patch" in sys.argv or "--gui_unpatch" in sys.argv:
entry = gui_sys_patch_start.SysPatchStartFrame
patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set()
logging.info(f"Entry point set: {entry.__name__}")
# Normally set by main.py, but transitions from CLI mode may not have this set
self.constants.gui_mode = True
self.frame: wx.Frame = entry(
None,
title=f"{self.constants.patcher_name} ({self.constants.patcher_version})",
global_constants=self.constants,
screen_location=None,
**({"patches": patches} if "--gui_patch" in sys.argv or "--gui_unpatch" in sys.argv else {})
)
atexit.register(self.OnCloseFrame)
if "--gui_patch" in sys.argv:
self.frame.start_root_patching()
elif "--gui_unpatch" in sys.argv:
self.frame.revert_root_patching()
self.app.MainLoop()
def OnCloseFrame(self, event: wx.Event = None) -> None:
"""
Closes the wxPython GUI
"""
if not self.frame:
return
logging.info("Cleaning up wxPython GUI")
self.frame.SetTransparent(0)
wx.Yield()
self.frame.DestroyChildren()
self.frame.Destroy()
self.app.ExitMainLoop()

View File

@@ -0,0 +1,67 @@
# Generate UI for help menu
import wx
import logging
import webbrowser
from resources import constants
class HelpFrame(wx.Frame):
"""
Append to main menu through a modal dialog
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None) -> None:
logging.info("Initializing Help Frame")
self.dialog = wx.Dialog(parent, title=title, size=(300, 200))
self.constants: constants.Constants = global_constants
self.title: str = title
self._generate_elements(self.dialog)
self.dialog.ShowWindowModal()
def _generate_elements(self, frame: wx.Frame = None) -> None:
"""
Format:
- Title: Patcher Resources
- Text: Following resources are available:
- Button: Official Guide
- Button: Community Discord Server
- Button: Official Phone Support
- Button: Return to Main Menu
"""
frame = self if not frame else frame
title_label = wx.StaticText(frame, label="Patcher Resources", pos=(-1,5))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
text_label = wx.StaticText(frame, label="Following resources are available:", pos=(-1,30))
text_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
text_label.Centre(wx.HORIZONTAL)
buttons = {
"Official Guide": self.constants.guide_link,
"Official Phone Support": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"Community Discord Server": self.constants.discord_link,
}
for button in buttons:
help_button = wx.Button(frame, label=button, pos=(-1, text_label.GetPosition()[1] + text_label.GetSize()[1] + (list(buttons.keys()).index(button) * 30)), size=(200, 30))
help_button.Bind(wx.EVT_BUTTON, lambda event, temp=buttons[button]: webbrowser.open(temp))
help_button.Centre(wx.HORIZONTAL)
# Button: Return to Main Menu
return_button = wx.Button(frame, label="Return to Main Menu", pos=(-1, help_button.GetPosition()[1] + help_button.GetSize()[1]), size=(150, 30))
return_button.Bind(wx.EVT_BUTTON, lambda event: frame.Close())
return_button.Centre(wx.HORIZONTAL)
# Set size of frame
frame.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40))

View File

@@ -0,0 +1,355 @@
import wx
import threading
import logging
import traceback
from resources.wx_gui import gui_main_menu, gui_support, gui_sys_patch_display
from resources import constants, install
from data import os_data
class InstallOCFrame(wx.Frame):
"""
Create a frame for installing OpenCore to disk
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None):
logging.info("Initializing Install OpenCore Frame")
super(InstallOCFrame, self).__init__(parent, title=title, size=(300, 120), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
gui_support.GenerateMenubar(self, global_constants).generate()
self.constants: constants.Constants = global_constants
self.title: str = title
self.result: bool = False
self.available_disks: dict = None
self.stock_output = logging.getLogger().handlers[0].stream
self.progress_bar_animation: gui_support.GaugePulseCallback = None
self.hyperlink_colour = (25, 179, 231)
self._generate_elements()
if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE:
self.constants.update_stage = gui_support.AutoUpdateStages.INSTALLING
self.Centre()
self.Show()
self._display_disks()
def _generate_elements(self) -> None:
"""
Display indeterminate progress bar while collecting disk information
Format:
- Title label: Install OpenCore
- Text: Fetching information on local disks...
- Progress bar: {indeterminate}
"""
# Title label: Install OpenCore
title_label = wx.StaticText(self, label="Install OpenCore", pos=(-1,5))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# Text: Parsing local disks...
text_label = wx.StaticText(self, label="Fetching information on local disks...", pos=(-1,30))
text_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
text_label.Centre(wx.HORIZONTAL)
self.text_label = text_label
# Progress bar: {indeterminate}
progress_bar = wx.Gauge(self, range=100, pos=(-1, text_label.GetPosition()[1] + text_label.GetSize()[1]), size=(150, 30), style=wx.GA_HORIZONTAL | wx.GA_SMOOTH)
progress_bar.Centre(wx.HORIZONTAL)
progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar)
progress_bar_animation.start_pulse()
self.progress_bar_animation = progress_bar_animation
self.progress_bar = progress_bar
def _fetch_disks(self) -> None:
"""
Fetch information on local disks
"""
self.available_disks = install.tui_disk_installation(self.constants).list_disks()
# Need to clean up output on pre-Sierra
# Disk images are mixed in with regular disks (ex. payloads.dmg)
ignore = ["disk image", "read-only", "virtual"]
for disk in self.available_disks.copy():
if any(string in self.available_disks[disk]['name'].lower() for string in ignore):
del self.available_disks[disk]
def _display_disks(self) -> None:
"""
Display disk selection dialog
"""
thread = threading.Thread(target=self._fetch_disks)
thread.start()
while thread.is_alive():
wx.Yield()
continue
self.progress_bar_animation.stop_pulse()
self.progress_bar.Hide()
# Create wxDialog for disk selection
dialog = wx.Dialog(self, title=self.title, size=(380, -1))
# Title label: Install OpenCore
title_label = wx.StaticText(dialog, label="Install OpenCore", pos=(-1,5))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# Text: select disk to install OpenCore onto
text_label = wx.StaticText(dialog, label="Select disk to install OpenCore onto:", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5))
text_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
text_label.Centre(wx.HORIZONTAL)
# Add note: "Missing disks? Ensure they're FAT32 or formatted as GUID/GPT"
gpt_note = wx.StaticText(dialog, label="Missing disks? Ensure they're FAT32 or formatted as GUID/GPT", pos=(-1, text_label.GetPosition()[1] + text_label.GetSize()[1] + 5))
gpt_note.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
gpt_note.Centre(wx.HORIZONTAL)
# Add buttons for each disk
if self.available_disks:
# Only show booted disk if building for host
disk_root = self.constants.booted_oc_disk if self.constants.custom_model is None else None
if disk_root:
# disk6s1 -> disk6
disk_root = self.constants.booted_oc_disk.strip("disk")
disk_root = "disk" + disk_root.split("s")[0]
logging.info(f"Checking if booted disk is present: {disk_root}")
# Add buttons for each disk
items = len(self.available_disks)
longest_label = max((len(self.available_disks[disk]['disk']) + len(self.available_disks[disk]['name']) + len(str(self.available_disks[disk]['size']))) for disk in self.available_disks)
longest_label = longest_label * 9
spacer = 0
logging.info("Available disks:")
for disk in self.available_disks:
# Create a button for each disk
logging.info(f"- {self.available_disks[disk]['disk']} - {self.available_disks[disk]['name']} - {self.available_disks[disk]['size']}")
disk_button = wx.Button(dialog, label=f"{self.available_disks[disk]['disk']} - {self.available_disks[disk]['name']} - {self.available_disks[disk]['size']}", size=(longest_label ,30), pos=(-1, gpt_note.GetPosition()[1] + gpt_note.GetSize()[1] + 5 + spacer))
disk_button.Centre(wx.HORIZONTAL)
disk_button.Bind(wx.EVT_BUTTON, lambda event, disk=disk: self._display_volumes(disk, self.available_disks))
if disk_root == self.available_disks[disk]['disk'] or items == 1:
disk_button.SetDefault()
spacer += 25
if disk_root:
# Add note: "Note: Blue represent the disk OpenCore is currently booted from"
disk_label = wx.StaticText(dialog, label="Note: Blue represent the disk OpenCore is currently booted from", pos=(-1, disk_button.GetPosition()[1] + disk_button.GetSize()[1] + 5))
disk_label.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
disk_label.Centre(wx.HORIZONTAL)
else:
disk_label = wx.StaticText(dialog, label="", pos=(-1, disk_button.GetPosition()[1] + 15))
disk_label.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
else:
# Text: Failed to find any applicable disks
disk_label = wx.StaticText(dialog, label="Failed to find any applicable disks", pos=(-1, gpt_note.GetPosition()[1] + gpt_note.GetSize()[1] + 5))
disk_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
disk_label.Centre(wx.HORIZONTAL)
# Add button: Search for disks again
search_button = wx.Button(dialog, label="Search for disks again", size=(160,30), pos=(-1, disk_label.GetPosition()[1] + disk_label.GetSize()[1] + 5))
search_button.Centre(wx.HORIZONTAL)
search_button.Bind(wx.EVT_BUTTON, self.on_reload_frame)
# Add button: Return to main menu
return_button = wx.Button(dialog, label="Return to main menu", size=(160,30), pos=(-1, search_button.GetPosition()[1] + 20))
return_button.Centre(wx.HORIZONTAL)
return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu)
# Set size
dialog.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40))
dialog.ShowWindowModal()
self.dialog = dialog
def _display_volumes(self, disk: str, dataset: dict) -> None:
"""
List volumes on disk
"""
self.dialog.Close()
# Create dialog
dialog = wx.Dialog(
self,
title=f"Volumes on {disk}",
style=wx.CAPTION | wx.CLOSE_BOX,
size=(300, 300)
)
# Add text: "Volumes on {disk}"
text_label = wx.StaticText(dialog, label=f"Volumes on {disk}", pos=(-1, 10))
text_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
text_label.Centre(wx.HORIZONTAL)
partitions = install.tui_disk_installation(self.constants).list_partitions(disk, dataset)
items = len(partitions)
longest_label = max((len(partitions[partition]['partition']) + len(partitions[partition]['name']) + len(str(partitions[partition]['size']))) for partition in partitions)
longest_label = longest_label * 10
spacer = 0
logging.info(f"Available partitions for {disk}:")
for partition in partitions:
logging.info(f"- {partitions[partition]['partition']} - {partitions[partition]['name']} - {partitions[partition]['size']}")
disk_button = wx.Button(dialog, label=f"{partitions[partition]['partition']} - {partitions[partition]['name']} - {partitions[partition]['size']}", size=(longest_label,30), pos=(-1, text_label.GetPosition()[1] + text_label.GetSize()[1] + 5 + spacer))
disk_button.Centre(wx.HORIZONTAL)
disk_button.Bind(wx.EVT_BUTTON, lambda event, partition=partition: self._install_oc_process(partition))
if items == 1 or self.constants.booted_oc_disk == partitions[partition]['partition']:
disk_button.SetDefault()
spacer += 25
# Add button: Return to main menu
return_button = wx.Button(dialog, label="Return to main menu", size=(150,30), pos=(-1, disk_button.GetPosition()[1] + disk_button.GetSize()[1]))
return_button.Centre(wx.HORIZONTAL)
return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu)
# Set size
dialog.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40))
# Show dialog
dialog.ShowWindowModal()
self.dialog = dialog
def _install_oc_process(self, partition: dict) -> None:
"""
Install OpenCore to disk
"""
self.dialog.Close()
# Create dialog
dialog = wx.Dialog(
self,
title=f"Installing OpenCore to {partition}",
style=wx.CAPTION | wx.CLOSE_BOX,
size=(370, 200)
)
# Add text: "Installing OpenCore to {partition}"
text_label = wx.StaticText(dialog, label=f"Installing OpenCore to {partition}", pos=(-1, 10))
text_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
text_label.Centre(wx.HORIZONTAL)
# Read-only text box: {empty}
text_box = wx.TextCtrl(dialog, value="", pos=(-1, text_label.GetPosition()[1] + text_label.GetSize()[1] + 10), size=(370, 200), style=wx.TE_READONLY | wx.TE_MULTILINE | wx.TE_RICH2)
text_box.Centre(wx.HORIZONTAL)
self.text_box = text_box
# Add button: Return to main menu
return_button = wx.Button(dialog, label="Return to main menu", size=(200,30), pos=(-1, text_box.GetPosition()[1] + text_box.GetSize()[1] + 10))
return_button.Centre(wx.HORIZONTAL)
return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu)
return_button.Disable()
# Set size
dialog.SetSize((370, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40))
# Show dialog
dialog.ShowWindowModal()
self.dialog = dialog
# Install OpenCore
self._invoke_install_oc(partition)
return_button.Enable()
def _invoke_install_oc(self, partition: dict) -> None:
"""
Invoke OpenCore installation
"""
thread = threading.Thread(target=self._install_oc, args=(partition,))
thread.start()
while thread.is_alive():
wx.Yield()
if self.result is True:
if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE and self.constants.detected_os >= os_data.os_data.big_sur:
self.constants.update_stage = gui_support.AutoUpdateStages.ROOT_PATCHING
popup_message = wx.MessageDialog(
self,
f"OpenCore has finished installing to disk.\n\nWould you like to update your root patches next?", "Success",
wx.YES_NO | wx.YES_DEFAULT
)
popup_message.ShowModal()
if popup_message.GetReturnCode() == wx.ID_YES:
self.Hide()
gui_sys_patch_display.SysPatchDisplayFrame(
parent=None,
title=self.title,
global_constants=self.constants,
screen_location=self.GetPosition()
)
self.Destroy()
return
elif not self.constants.custom_model:
gui_support.RestartHost(self).restart(message="OpenCore has finished installing to disk.\n\nYou will need to reboot and hold the Option key and select OpenCore/Boot EFI's option.\n\nWould you like to reboot?")
else:
popup_message = wx.MessageDialog(
self,
f"OpenCore has finished installing to disk.\n\nYou can eject the drive, insert it into the {self.constants.custom_model}, reboot, hold the Option key and select OpenCore/Boot EFI's option.", "Success",
wx.OK
)
popup_message.ShowModal()
else:
if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE:
self.constants.update_stage = gui_support.AutoUpdateStages.FINISHED
def _install_oc(self, partition: dict) -> None:
"""
Install OpenCore to disk
"""
logging.info(f"Installing OpenCore to {partition}")
logger = logging.getLogger()
logger.addHandler(gui_support.ThreadHandler(self.text_box))
try:
self.result = install.tui_disk_installation(self.constants).install_opencore(partition)
except:
logging.error("An internal error occurred while installing:\n")
logging.error(traceback.format_exc())
logger.removeHandler(logger.handlers[2])
def on_reload_frame(self, event: wx.Event = None) -> None:
"""
Reload frame
"""
self.Destroy()
frame = InstallOCFrame(
None,
title=self.title,
global_constants=self.constants,
screen_location=self.GetScreenPosition()
)
frame.Show()
def on_return_to_main_menu(self, event: wx.Event = None) -> None:
"""
Return to main menu
"""
main_menu_frame = gui_main_menu.MainFrame(
None,
title=self.title,
global_constants=self.constants,
screen_location=self.GetScreenPosition()
)
main_menu_frame.Show()
self.Destroy()

View File

@@ -0,0 +1,396 @@
import wx
import logging
import threading
import webbrowser
from pathlib import Path
from resources.wx_gui import (
gui_main_menu,
gui_support,
gui_download,
gui_macos_installer_flash
)
from resources import (
constants,
macos_installer_handler,
utilities,
network_handler,
integrity_verification
)
from data import os_data, smbios_data, cpu_data
class macOSInstallerDownloadFrame(wx.Frame):
"""
Create a frame for downloading and creating macOS installers
Uses a Modal Dialog for smoother transition from other frames
Note: Flashing installers is passed to gui_macos_installer_flash.py
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None):
logging.info("Initializing macOS Installer Download Frame")
self.constants: constants.Constants = global_constants
self.title: str = title
self.parent: wx.Frame = parent
self.available_installers = None
self.available_installers_latest = None
self.catalog_seed: macos_installer_handler.SeedType = macos_installer_handler.SeedType.DeveloperSeed
self.frame_modal = wx.Dialog(parent, title=title, size=(330, 200))
self._generate_elements(self.frame_modal)
self.frame_modal.ShowWindowModal()
def _generate_elements(self, frame: wx.Frame = None) -> None:
"""
Format:
- Title: Create macOS Installer
- Button: Download macOS Installer
- Button: Use existing macOS Installer
- Button: Return to Main Menu
"""
frame = self if not frame else frame
title_label = wx.StaticText(frame, label="Create macOS Installer", pos=(-1,5))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# Button: Download macOS Installer
download_button = wx.Button(frame, label="Download macOS Installer", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5), size=(200, 30))
download_button.Bind(wx.EVT_BUTTON, self.on_download)
download_button.Centre(wx.HORIZONTAL)
# Button: Use existing macOS Installer
existing_button = wx.Button(frame, label="Use existing macOS Installer", pos=(-1, download_button.GetPosition()[1] + download_button.GetSize()[1] - 5), size=(200, 30))
existing_button.Bind(wx.EVT_BUTTON, self.on_existing)
existing_button.Centre(wx.HORIZONTAL)
# Button: Return to Main Menu
return_button = wx.Button(frame, label="Return to Main Menu", pos=(-1, existing_button.GetPosition()[1] + existing_button.GetSize()[1] + 5), size=(150, 30))
return_button.Bind(wx.EVT_BUTTON, self.on_return)
return_button.Centre(wx.HORIZONTAL)
# Set size of frame
frame.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40))
def _generate_catalog_frame(self) -> None:
"""
Generate frame to display available installers
"""
super(macOSInstallerDownloadFrame, self).__init__(None, title=self.title, size=(300, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
gui_support.GenerateMenubar(self, self.constants).generate()
self.Centre()
# Title: Pulling installer catalog
title_label = wx.StaticText(self, label="Pulling installer catalog", pos=(-1,5))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# Progress bar
progress_bar = wx.Gauge(self, range=100, pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5), size=(250, 30))
progress_bar.Centre(wx.HORIZONTAL)
progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar)
progress_bar_animation.start_pulse()
# Set size of frame
self.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40))
self.Show()
# Grab installer catalog
def _fetch_installers():
logging.info(f"Fetching installer catalog: {macos_installer_handler.SeedType(self.catalog_seed).name}")
remote_obj = macos_installer_handler.RemoteInstallerCatalog(seed_override=self.catalog_seed)
self.available_installers = remote_obj.available_apps
self.available_installers_latest = remote_obj.available_apps_latest
thread = threading.Thread(target=_fetch_installers)
thread.start()
while thread.is_alive():
wx.Yield()
progress_bar_animation.stop_pulse()
progress_bar.Hide()
self._display_available_installers()
def _display_available_installers(self, event: wx.Event = None, show_full: bool = False) -> None:
"""
Display available installers in frame
"""
self.frame_modal.Destroy()
dialog = wx.Dialog(self, title="Select macOS Installer", size=(300, 200))
# Title: Select macOS Installer
title_label = wx.StaticText(dialog, label="Select macOS Installer", pos=(-1,5))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# Subtitle: Installers currently available from Apple:
subtitle_label = wx.StaticText(dialog, label="Installers currently available from Apple:", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5))
subtitle_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
subtitle_label.Centre(wx.HORIZONTAL)
# List of installers
installers = self.available_installers_latest if show_full is False else self.available_installers
if installers:
spacer = 0
logging.info(f"Available installers on SUCatalog ({'All entries' if show_full else 'Latest only'}):")
for app in installers:
logging.info(f"- macOS {installers[app]['Version']} ({installers[app]['Build']}):\n - Size: {utilities.human_fmt(installers[app]['Size'])}\n - Source: {installers[app]['Source']}\n - Variant: {installers[app]['Variant']}\n - Link: {installers[app]['Link']}\n")
extra = " Beta" if installers[app]['Variant'] in ["DeveloperSeed" , "PublicSeed"] else ""
installer_button = wx.Button(dialog, label=f"macOS {installers[app]['Version']}{extra} ({installers[app]['Build']} - {utilities.human_fmt(installers[app]['Size'])})", pos=(-1, subtitle_label.GetPosition()[1] + subtitle_label.GetSize()[1] + 5 + spacer), size=(270, 30))
installer_button.Bind(wx.EVT_BUTTON, lambda event, temp=app: self.on_download_installer(installers[temp]))
installer_button.Centre(wx.HORIZONTAL)
spacer += 25
# Since installers are sorted by version, set the latest installer as the default button
# Note that on full display, the last installer is generally a beta
if show_full is False and app == list(installers.keys())[-1]:
installer_button.SetDefault()
else:
logging.error("No installers found on SUCatalog")
installer_button = wx.StaticText(dialog, label="Failed to fetch catalog from Apple", pos=(-1, subtitle_label.GetPosition()[1] + subtitle_label.GetSize()[1] + 5))
installer_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
installer_button.Centre(wx.HORIZONTAL)
# Show all available installers
show_all_button = wx.Button(dialog, label="Show all available installers" if show_full is False else "Show only latest installers", pos=(-1, installer_button.GetPosition()[1] + installer_button.GetSize()[1]), size=(200, 30))
show_all_button.Bind(wx.EVT_BUTTON, lambda event: self._display_available_installers(event, not show_full))
show_all_button.Centre(wx.HORIZONTAL)
# Return to Main Menu
return_button = wx.Button(dialog, label="Return to Main Menu", pos=(-1, show_all_button.GetPosition()[1] + show_all_button.GetSize()[1] - 7), size=(150, 30))
return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu)
return_button.Centre(wx.HORIZONTAL)
# Set size of frame
dialog.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40))
dialog.ShowWindowModal()
self.frame_modal = dialog
def on_download_installer(self, app: dict) -> None:
"""
Download macOS installer
"""
logging.info(f"Selected macOS {app['Version']} ({app['Build']})")
# Notify user whether their model is compatible with the selected installer
problems = []
model = self.constants.custom_model or self.constants.computer.real_model
if model in smbios_data.smbios_dictionary:
if app["OS"] >= os_data.os_data.ventura:
if smbios_data.smbios_dictionary[model]["CPU Generation"] <= cpu_data.cpu_data.penryn or model in ["MacPro4,1", "MacPro5,1", "Xserve3,1"]:
if model.startswith("MacBook"):
problems.append("Lack of internal Keyboard/Trackpad in macOS installer.")
else:
problems.append("Lack of internal Keyboard/Mouse in macOS installer.")
if problems:
logging.warning(f"Potential issues with {model} and {app['Version']} ({app['Build']}): {problems}")
problems = "\n".join(problems)
dlg = wx.MessageDialog(self.frame_modal, f"Your model ({model}) may not be fully supported by this installer. You may encounter the following issues:\n\n{problems}\n\nFor more information, see associated page. Otherwise, we recommend using macOS Monterey", "Potential Issues", wx.YES_NO | wx.CANCEL | wx.ICON_WARNING)
dlg.SetYesNoCancelLabels("View Github Issue", "Download Anyways", "Cancel")
result = dlg.ShowModal()
if result == wx.ID_CANCEL:
return
elif result == wx.ID_YES:
webbrowser.open("https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1021")
return
host_space = utilities.get_free_space()
needed_space = app['Size'] * 2
if host_space < needed_space:
logging.error(f"Insufficient space to download and extract: {utilities.human_fmt(host_space)} available vs {utilities.human_fmt(needed_space)} required")
dlg = wx.MessageDialog(self.frame_modal, f"You do not have enough free space to download and extract this installer. Please free up some space and try again\n\n{utilities.human_fmt(host_space)} available vs {utilities.human_fmt(needed_space)} required", "Insufficient Space", wx.OK | wx.ICON_WARNING)
dlg.ShowModal()
return
self.frame_modal.Close()
download_obj = network_handler.DownloadObject(app['Link'], self.constants.payload_path / "InstallAssistant.pkg")
gui_download.DownloadFrame(
self,
title=self.title,
global_constants=self.constants,
download_obj=download_obj,
item_name=f"macOS {app['Version']} ({app['Build']})",
)
if download_obj.download_complete is False:
self.on_return_to_main_menu()
return
self._validate_installer(app['integrity'])
def _validate_installer(self, chunklist_link: str) -> None:
"""
Validate macOS installer
"""
self.SetSize((300, 200))
for child in self.GetChildren():
child.Destroy()
# Title: Validating macOS Installer
title_label = wx.StaticText(self, label="Validating macOS Installer", pos=(-1,5))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# Label: Validating chunk 0 of 0
chunk_label = wx.StaticText(self, label="Validating chunk 0 of 0", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5))
chunk_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
chunk_label.Centre(wx.HORIZONTAL)
# Progress bar
progress_bar = wx.Gauge(self, range=100, pos=(-1, chunk_label.GetPosition()[1] + chunk_label.GetSize()[1] + 5), size=(270, 30))
progress_bar.Centre(wx.HORIZONTAL)
# Set size of frame
self.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40))
self.Show()
chunklist_stream = network_handler.NetworkUtilities().get(chunklist_link).content
if chunklist_stream:
logging.info("Validating macOS installer")
utilities.disable_sleep_while_running()
chunk_obj = integrity_verification.ChunklistVerification(self.constants.payload_path / Path("InstallAssistant.pkg"), chunklist_stream)
if chunk_obj.chunks:
progress_bar.SetValue(chunk_obj.current_chunk)
progress_bar.SetRange(chunk_obj.total_chunks)
wx.App.Get().Yield()
chunk_obj.validate()
while chunk_obj.status == integrity_verification.ChunklistStatus.IN_PROGRESS:
progress_bar.SetValue(chunk_obj.current_chunk)
chunk_label.SetLabel(f"Validating chunk {chunk_obj.current_chunk} of {chunk_obj.total_chunks}")
chunk_label.Centre(wx.HORIZONTAL)
wx.App.Get().Yield()
if chunk_obj.status == integrity_verification.ChunklistStatus.FAILURE:
logging.error(f"Chunklist validation failed: Hash mismatch on {chunk_obj.current_chunk}")
wx.MessageBox(f"Chunklist validation failed: Hash mismatch on {chunk_obj.current_chunk}\n\nThis generally happens when downloading on unstable connections such as WiFi or cellular.\n\nPlease try redownloading again on a stable connection (ie. Ethernet)", "Corrupted Installer!", wx.OK | wx.ICON_ERROR)
self.on_return_to_main_menu()
return
logging.info("macOS installer validated")
# Extract installer
title_label.SetLabel("Extracting macOS Installer")
title_label.Centre(wx.HORIZONTAL)
chunk_label.SetLabel("May take a few minutes...")
chunk_label.Centre(wx.HORIZONTAL)
progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar)
progress_bar_animation.start_pulse()
# Start thread to extract installer
self.result = False
def extract_installer():
self.result = macos_installer_handler.InstallerCreation().install_macOS_installer(self.constants.payload_path)
thread = threading.Thread(target=extract_installer)
thread.start()
# Show frame
self.Show()
# Wait for thread to finish
while thread.is_alive():
wx.Yield()
progress_bar_animation.stop_pulse()
progress_bar.Hide()
chunk_label.SetLabel("Successfully extracted macOS installer" if self.result is True else "Failed to extract macOS installer")
chunk_label.Centre(wx.HORIZONTAL)
# Create macOS Installer button
create_installer_button = wx.Button(self, label="Create macOS Installer", pos=(-1, progress_bar.GetPosition()[1]), size=(170, 30))
create_installer_button.Bind(wx.EVT_BUTTON, self.on_existing)
create_installer_button.Centre(wx.HORIZONTAL)
if self.result is False:
create_installer_button.Disable()
# Return to main menu button
return_button = wx.Button(self, label="Return to Main Menu", pos=(-1, create_installer_button.GetPosition()[1] + create_installer_button.GetSize()[1]), size=(150, 30))
return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu)
return_button.Centre(wx.HORIZONTAL)
# Set size of frame
self.SetSize((-1, return_button.GetPosition()[1] + return_button.GetSize()[1] + 40))
# Show frame
self.Show()
if self.result is False:
wx.MessageBox("An error occurred while extracting the macOS installer. Could be due to a corrupted installer", "Error", wx.OK | wx.ICON_ERROR)
return
user_input = wx.MessageBox("Finished extracting the installer, would you like to continue and create a macOS installer?", "Create macOS Installer?", wx.YES_NO | wx.ICON_QUESTION)
if user_input == wx.YES:
self.on_existing()
def on_download(self, event: wx.Event) -> None:
"""
Display available macOS versions to download
"""
self.frame_modal.Close()
self.parent.Hide()
self._generate_catalog_frame()
self.parent.Close()
def on_existing(self, event: wx.Event = None) -> None:
"""
Display local macOS installers
"""
frames = [self, self.frame_modal, self.parent]
for frame in frames:
if frame:
frame.Close()
gui_macos_installer_flash.macOSInstallerFlashFrame(
None,
title=self.title,
global_constants=self.constants,
**({"screen_location": self.GetScreenPosition()} if self else {})
)
for frame in frames:
if frame:
frame.Destroy()
def on_return(self, event: wx.Event) -> None:
"""
Return to main menu (dismiss frame)
"""
self.frame_modal.Close()
def on_return_to_main_menu(self, event: wx.Event = None) -> None:
"""
Return to main menu
"""
if self.frame_modal:
self.frame_modal.Hide()
main_menu_frame = gui_main_menu.MainFrame(
None,
title=self.title,
global_constants=self.constants,
screen_location=self.GetScreenPosition()
)
main_menu_frame.Show()
if self.frame_modal:
self.frame_modal.Destroy()
self.Destroy()

View File

@@ -0,0 +1,587 @@
import wx
import time
import logging
import plistlib
import tempfile
import threading
import subprocess
from pathlib import Path
from resources.wx_gui import gui_main_menu, gui_build, gui_support
from resources import (
constants,
macos_installer_handler,
utilities,
network_handler,
kdk_handler,
)
from data import os_data
class macOSInstallerFlashFrame(wx.Frame):
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None):
logging.info("Initializing macOS Installer Flash Frame")
super(macOSInstallerFlashFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
gui_support.GenerateMenubar(self, global_constants).generate()
self.constants: constants.Constants = global_constants
self.title: str = title
self.available_installers_local: dict = {}
self.available_disks: dict = {}
self.prepare_result: bool = False
self.progress_bar_animation: gui_support.GaugePulseCallback = None
self.frame_modal: wx.Dialog = None
self._generate_elements()
self.Centre()
self.Show()
self._populate_installers()
def _generate_elements(self) -> None:
"""
Fetches local macOS Installers for users to select from
"""
# Title: Fetching local macOS Installers
title_label = wx.StaticText(self, label="Fetching local macOS Installers", pos=(-1,1))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# Progress bar
progress_bar = wx.Gauge(self, range=100, pos=(-1, 30), size=(200, 30))
progress_bar.Centre(wx.HORIZONTAL)
progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar)
progress_bar_animation.start_pulse()
self.progress_bar_animation = progress_bar_animation
# Set size of frame
self.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40))
def _populate_installers(self) -> None:
# Grab installer catalog
def fetch_installers():
self.available_installers_local = macos_installer_handler.LocalInstallerCatalog().available_apps
thread = threading.Thread(target=fetch_installers)
thread.start()
while thread.is_alive():
wx.Yield()
frame_modal = wx.Dialog(self, title=self.title, size=(350, 200))
# Title: Select macOS Installer
title_label = wx.StaticText(frame_modal, label="Select local macOS Installer", pos=(-1,5))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# List of installers
if self.available_installers_local:
logging.info("Installer(s) found:")
spacer = 10
entries = len(self.available_installers_local)
for app in self.available_installers_local:
logging.info(f"- {self.available_installers_local[app]['Short Name']}: {self.available_installers_local[app]['Version']} ({self.available_installers_local[app]['Build']})")
app_str = f"{self.available_installers_local[app]['Short Name']}"
unsupported: bool = self.available_installers_local[app]['Minimum Host OS'] > self.constants.detected_os
if unsupported:
min_str = os_data.os_conversion.convert_kernel_to_marketing_name(self.available_installers_local[app]['Minimum Host OS'])
app_str += f" (Requires {min_str})"
else:
app_str += f": {self.available_installers_local[app]['Version']} ({self.available_installers_local[app]['Build']})"
installer_button = wx.Button(frame_modal, label=app_str, pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + spacer), size=(300, 30))
installer_button.Bind(wx.EVT_BUTTON, lambda event, temp=app: self.on_select(self.available_installers_local[temp]))
installer_button.Centre(wx.HORIZONTAL)
spacer += 25
if unsupported:
installer_button.Disable()
elif entries == 1:
installer_button.SetDefault()
else:
installer_button = wx.StaticText(frame_modal, label="No installers found in '/Applications'", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5))
installer_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
installer_button.Centre(wx.HORIZONTAL)
# Button: Return to Main Menu
cancel_button = wx.Button(frame_modal, label="Return to Main Menu", pos=(-1, installer_button.GetPosition()[1] + installer_button.GetSize()[1]), size=(150, 30))
cancel_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu)
cancel_button.Centre(wx.HORIZONTAL)
# Set size of frame
frame_modal.SetSize((-1, cancel_button.GetPosition()[1] + cancel_button.GetSize()[1] + 40))
self.progress_bar_animation.stop_pulse()
frame_modal.ShowWindowModal()
self.frame_modal = frame_modal
def on_select(self, installer: dict) -> None:
logging.info(f"Selected installer: {installer['Short Name']} ({installer['Version']} ({installer['Build']}))")
self.frame_modal.Destroy()
for child in self.GetChildren():
child.Destroy()
# Fetching information on local disks
title_label = wx.StaticText(self, label="Fetching information on local disks", pos=(-1,1))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# Progress bar
progress_bar = wx.Gauge(self, range=100, pos=(-1, 30), size=(200, 30))
progress_bar.Centre(wx.HORIZONTAL)
progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar)
progress_bar_animation.start_pulse()
# Set size of frame
self.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40))
# Fetch local disks
def _fetch_disks():
self.available_disks = macos_installer_handler.InstallerCreation().list_disk_to_format()
# Need to clean up output on pre-Sierra
# Disk images are mixed in with regular disks (ex. payloads.dmg)
ignore = ["disk image", "read-only", "virtual"]
for disk in self.available_disks.copy():
if any(string in self.available_disks[disk]['name'].lower() for string in ignore):
del self.available_disks[disk]
thread = threading.Thread(target=_fetch_disks)
thread.start()
while thread.is_alive():
wx.Yield()
self.frame_modal = wx.Dialog(self, title=self.title, size=(350, 200))
# Title: Select local disk
title_label = wx.StaticText(self.frame_modal, label="Select local disk", pos=(-1,5))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# Label: Selected USB will be erased, please backup any data
warning_label = wx.StaticText(self.frame_modal, label="Selected USB will be erased, please backup any data", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5))
warning_label.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
warning_label.Centre(wx.HORIZONTAL)
# List of disks
if self.available_disks:
spacer = 5
entries = len(self.available_disks)
logging.info("Available disks:")
for disk in self.available_disks:
logging.info(f" - {disk}: {self.available_disks[disk]['name']} - {utilities.human_fmt(self.available_disks[disk]['size'])}")
disk_button = wx.Button(self.frame_modal, label=f"{disk}: {self.available_disks[disk]['name']} - {utilities.human_fmt(self.available_disks[disk]['size'])}", pos=(-1, warning_label.GetPosition()[1] + warning_label.GetSize()[1] + spacer), size=(300, 30))
disk_button.Bind(wx.EVT_BUTTON, lambda event, temp=disk: self.on_select_disk(self.available_disks[temp], installer))
disk_button.Centre(wx.HORIZONTAL)
if entries == 1:
disk_button.SetDefault()
spacer += 25
else:
disk_button = wx.StaticText(self.frame_modal, label="No disks found", pos=(-1, warning_label.GetPosition()[1] + warning_label.GetSize()[1] + 5))
disk_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
disk_button.Centre(wx.HORIZONTAL)
# Search for disks again
search_button = wx.Button(self.frame_modal, label="Search for disks again", pos=(-1, disk_button.GetPosition()[1] + disk_button.GetSize()[1]), size=(160, 30))
search_button.Bind(wx.EVT_BUTTON, lambda event, temp=installer: self.on_select(temp))
search_button.Centre(wx.HORIZONTAL)
# Button: Return to Main Menu
cancel_button = wx.Button(self.frame_modal, label="Return to Main Menu", pos=(-1, search_button.GetPosition()[1] + search_button.GetSize()[1] - 10), size=(160, 30))
cancel_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu)
cancel_button.Centre(wx.HORIZONTAL)
# Set size of frame
self.frame_modal.SetSize((-1, cancel_button.GetPosition()[1] + cancel_button.GetSize()[1] + 40))
progress_bar_animation.stop_pulse()
self.frame_modal.ShowWindowModal()
def on_select_disk(self, disk: dict, installer: dict) -> None:
answer = wx.MessageBox(f"Are you sure you want to erase '{disk['name']}'?\nAll data will be lost, this cannot be undone.", "Confirmation", wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
if answer != wx.YES:
return
logging.info(f"Selected disk: {disk['name']}")
self.frame_modal.Destroy()
for child in self.GetChildren():
child.Destroy()
self.SetSize((450, -1))
# Title: Creating Installer: {installer_name}
title_label = wx.StaticText(self, label=f"Creating Installer: {installer['Short Name']}", pos=(-1,1))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# Label: Creating macOS installers can take 30min+ on slower USB drives.
warning_label = wx.StaticText(self, label="Creating macOS installers can take 30min+ on slower USB drives.", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 5))
warning_label.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
warning_label.Centre(wx.HORIZONTAL)
# Label: We will notify you when the installer is ready.
warning_label = wx.StaticText(self, label="We will notify you when the installer is ready.", pos=(-1, warning_label.GetPosition()[1] + warning_label.GetSize()[1] + 5))
warning_label.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
warning_label.Centre(wx.HORIZONTAL)
# Label: Bytes Written: 0 MB
bytes_written_label = wx.StaticText(self, label="Bytes Written: 0.00 MB", pos=(-1, warning_label.GetPosition()[1] + warning_label.GetSize()[1] + 5))
bytes_written_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
bytes_written_label.Centre(wx.HORIZONTAL)
# Progress bar
progress_bar = wx.Gauge(self, range=100, pos=(-1, bytes_written_label.GetPosition()[1] + bytes_written_label.GetSize()[1] + 5), size=(300, 30))
progress_bar.Centre(wx.HORIZONTAL)
progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar)
progress_bar_animation.start_pulse()
# Set size of frame
self.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40))
self.Show()
# Prepare resources
if self._prepare_resources(installer['Path'], disk['identifier']) is False:
logging.error("Failed to prepare resources, cannot continue.")
wx.MessageBox("Failed to prepare resources, cannot continue.", "Error", wx.OK | wx.ICON_ERROR)
self.on_return_to_main_menu()
return
# Base Size
estimated_size = 16000
# AutoPkg (700MB~)
estimated_size += 700 if installer['OS'] >= os_data.os_data.big_sur else 0
# KDK (700MB~, and overhead for copying to installer)
estimated_size += 700 * 2 if installer['OS'] >= os_data.os_data.ventura else 0
progress_bar_animation.stop_pulse()
progress_bar.SetRange(estimated_size)
# /dev/diskX -> diskX
root_disk = disk['identifier'][5:]
initial_bytes_written = float(utilities.monitor_disk_output(root_disk))
self.result = False
def _flash():
logging.info(f"Flashing {installer['Path']} to {root_disk}")
self.result = self._flash_installer(root_disk)
thread = threading.Thread(target=_flash)
thread.start()
# Wait for installer to be created
while thread.is_alive():
try:
total_bytes_written = float(utilities.monitor_disk_output(root_disk))
except:
total_bytes_written = initial_bytes_written
bytes_written = total_bytes_written - initial_bytes_written
wx.CallAfter(bytes_written_label.SetLabel, f"Bytes Written: {bytes_written:.2f} MB")
try:
bytes_written = int(bytes_written)
except:
bytes_written = 0
wx.CallAfter(progress_bar.SetValue, bytes_written)
wx.Yield()
if self.result is False:
logging.error("Failed to flash installer, cannot continue.")
self.on_return_to_main_menu()
return
# Next verify the installer
progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar)
progress_bar_animation.start_pulse()
bytes_written_label.SetLabel("Validating Installer Integrity...")
error_message = self._validate_installer_pkg(disk['identifier'])
progress_bar_animation.stop_pulse()
if error_message != "":
progress_bar.SetValue(0)
wx.MessageBox(f"Failed to validate installer, cannot continue.\n This can generally happen due to a faulty USB drive, as flashing is an intensive process that can trigger hardware faults not normally seen. \n\n{error_message}", "Corrupted Installer!", wx.OK | wx.ICON_ERROR)
self.on_return_to_main_menu()
return
progress_bar.SetValue(estimated_size)
if gui_support.CheckProperties(self.constants).host_can_build() is False:
wx.MessageBox("Installer created successfully! If you want to install OpenCore to this USB, you will need to change the Target Model in settings", "Successfully created the macOS installer!", wx.OK | wx.ICON_INFORMATION)
self.on_return_to_main_menu()
return
answer = wx.MessageBox("Installer created successfully, would you like to continue and Install OpenCore to this disk?", "Successfully created the macOS installer!", wx.YES_NO | wx.ICON_QUESTION)
if answer != wx.YES:
self.on_return_to_main_menu()
return
# Install OpenCore
self.Hide()
gui_build.BuildFrame(
parent=None,
title=self.title,
global_constants=self.constants,
screen_location=self.GetPosition()
)
self.Destroy()
def _prepare_resources(self, installer_path: str, disk: str) -> None:
def prepare_script(self, installer_path: str, disk: str, constants: constants.Constants):
self.prepare_result = macos_installer_handler.InstallerCreation().generate_installer_creation_script(constants.payload_path, installer_path, disk)
thread = threading.Thread(target=prepare_script, args=(self, installer_path, disk, self.constants))
thread.start()
while thread.is_alive():
wx.Yield()
return self.prepare_result
def _flash_installer(self, disk) -> bool:
utilities.disable_sleep_while_running()
logging.info("Creating macOS installer")
thread = threading.Thread(target=self._auto_package_handler)
thread.start()
# print contents of installer.sh
with open(self.constants.installer_sh_path, "r") as f:
logging.info(f"installer.sh contents:\n{f.read()}")
args = [self.constants.oclp_helper_path, "/bin/sh", self.constants.installer_sh_path]
result = subprocess.run(args, capture_output=True, text=True)
output = result.stdout
error = result.stderr if result.stderr else ""
if "Install media now available at" not in output:
logging.info("Failed to create macOS installer")
popup = wx.MessageDialog(self, f"Failed to create macOS installer\n\nOutput: {output}\n\nError: {error}", "Error", wx.OK | wx.ICON_ERROR)
popup.ShowModal()
return False
logging.info("Successfully created macOS installer")
while thread.is_alive():
# wait for download_thread to finish
# though highly unlikely this thread is still alive (flashing an Installer will take a while)
time.sleep(0.1)
logging.info("Installing Root Patcher to drive")
self._install_installer_pkg(disk)
utilities.enable_sleep_after_running()
return True
def _auto_package_handler(self):
"""
Function's main goal is to grab the correct AutoPkg-Assets.pkg and unzip it
Note the following:
- When running a release build, pull from Github's release page with the same versioning
- When running from source/unable to find on Github, use the nightly.link variant
- If nightly also fails, fall back to the manually uploaded variant
"""
link = self.constants.installer_pkg_url
if network_handler.NetworkUtilities(link).validate_link() is False:
logging.info("Stock Install.pkg is missing on Github, falling back to Nightly")
link = self.constants.installer_pkg_url_nightly
if link.endswith(".zip"):
path = self.constants.installer_pkg_zip_path
else:
path = self.constants.installer_pkg_path
autopkg_download = network_handler.DownloadObject(link, path)
autopkg_download.download(spawn_thread=False)
if autopkg_download.download_complete is False:
logging.warning("Failed to download Install.pkg")
logging.warning(autopkg_download.error_msg)
return
# Download thread will re-enable Idle Sleep after downloading
utilities.disable_sleep_while_running()
if not str(path).endswith(".zip"):
return
if Path(self.constants.installer_pkg_path).exists():
subprocess.run(["rm", self.constants.installer_pkg_path])
subprocess.run(["ditto", "-V", "-x", "-k", "--sequesterRsrc", "--rsrc", self.constants.installer_pkg_zip_path, self.constants.payload_path])
def _install_installer_pkg(self, disk):
disk = disk + "s2" # ESP sits at 1, and we know macOS will have created the main partition at 2
if not Path(self.constants.installer_pkg_path).exists():
return
path = utilities.grab_mount_point_from_disk(disk)
if not Path(path + "/System/Library/CoreServices/SystemVersion.plist").exists():
return
os_version = plistlib.load(Path(path + "/System/Library/CoreServices/SystemVersion.plist").open("rb"))
kernel_version = os_data.os_conversion.os_to_kernel(os_version["ProductVersion"])
if int(kernel_version) < os_data.os_data.big_sur:
logging.info("Installer unsupported, requires Big Sur or newer")
return
subprocess.run(["mkdir", "-p", f"{path}/Library/Packages/"])
subprocess.run(["cp", "-r", self.constants.installer_pkg_path, f"{path}/Library/Packages/"])
self._kdk_chainload(os_version["ProductBuildVersion"], os_version["ProductVersion"], Path(path + "/Library/Packages/"))
def _kdk_chainload(self, build: str, version: str, download_dir: str):
"""
Download the correct KDK to be chainloaded in the macOS installer
Parameters
build (str): The build number of the macOS installer (e.g. 20A5343j)
version (str): The version of the macOS installer (e.g. 11.0.1)
"""
kdk_dmg_path = Path(download_dir) / "KDK.dmg"
kdk_pkg_path = Path(download_dir) / "KDK.pkg"
if kdk_dmg_path.exists():
kdk_dmg_path.unlink()
if kdk_pkg_path.exists():
kdk_pkg_path.unlink()
logging.info("Initiating KDK download")
logging.info(f"- Build: {build}")
logging.info(f"- Version: {version}")
logging.info(f"- Working Directory: {download_dir}")
kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, build, version, ignore_installed=True)
if kdk_obj.success is False:
logging.info("Failed to retrieve KDK")
logging.info(kdk_obj.error_msg)
return
kdk_download_obj = kdk_obj.retrieve_download(override_path=kdk_dmg_path)
if kdk_download_obj is None:
logging.info("Failed to retrieve KDK")
logging.info(kdk_obj.error_msg)
# Check remaining disk space before downloading
space = utilities.get_free_space(download_dir)
if space < (kdk_obj.kdk_url_expected_size * 2):
logging.info("Not enough disk space to download and install KDK")
logging.info(f"Attempting to download locally first")
if space < kdk_obj.kdk_url_expected_size:
logging.info("Not enough disk space to install KDK, skipping")
return
# Ideally we'd download the KDK onto the disk to display progress in the UI
# However we'll just download to our temp directory and move it to the target disk
kdk_dmg_path = self.constants.kdk_download_path
kdk_download_obj.download(spawn_thread=False)
if kdk_download_obj.download_complete is False:
logging.info("Failed to download KDK")
logging.info(kdk_download_obj.error_msg)
return
if not kdk_dmg_path.exists():
logging.info(f"KDK missing: {kdk_dmg_path}")
return
# Now that we have a KDK, extract it to get the pkg
with tempfile.TemporaryDirectory() as mount_point:
logging.info("Mounting KDK")
result = subprocess.run(["hdiutil", "attach", kdk_dmg_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0:
logging.info("Failed to mount KDK")
logging.info(result.stdout.decode("utf-8"))
return
logging.info("Copying KDK")
subprocess.run(["cp", "-r", f"{mount_point}/KernelDebugKit.pkg", kdk_pkg_path])
logging.info("Unmounting KDK")
result = subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0:
logging.info("Failed to unmount KDK")
logging.info(result.stdout.decode("utf-8"))
return
logging.info("Removing KDK Disk Image")
kdk_dmg_path.unlink()
def _validate_installer_pkg(self, disk: str) -> bool:
logging.info("Validating installer pkg")
error_message = ""
def _integrity_check():
nonlocal error_message
for folder in Path(utilities.grab_mount_point_from_disk(disk + "s2")).glob("*.app"):
if folder.is_dir():
dmg_path = folder / "Contents" / "SharedSupport" / "SharedSupport.dmg"
break
if not Path(dmg_path).exists():
logging.error(f"Failed to find {dmg_path}")
error_message = f"Failed to find {dmg_path}"
return error_message
result = subprocess.run(["hdiutil", "verify", dmg_path],stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
if result.stdout:
logging.error(result.stdout.decode("utf-8"))
error_message = "STDOUT: " + result.stdout.decode("utf-8")
if result.stderr:
logging.error(result.stderr.decode("utf-8"))
error_message += "\n\nSTDERR: " + result.stderr.decode("utf-8")
thread = threading.Thread(target=_integrity_check)
thread.start()
while thread.is_alive():
wx.Yield()
if error_message == "":
logging.info("Installer pkg validated")
return error_message
return error_message
def on_return_to_main_menu(self, event: wx.Event = None):
if self.frame_modal:
self.frame_modal.Hide()
if self:
if isinstance(self, wx.Frame):
self.Hide()
main_menu_frame = gui_main_menu.MainFrame(
None,
title=self.title,
global_constants=self.constants,
screen_location=self.GetScreenPosition()
)
main_menu_frame.Show()
if self.frame_modal:
self.frame_modal.Destroy()
if self:
if isinstance(self, wx.Frame):
self.Destroy()

View File

@@ -0,0 +1,324 @@
# Generate GUI for main menu
import wx
import sys
import logging
import threading
import webbrowser
from resources.wx_gui import (
gui_build,
gui_macos_installer_download,
gui_support,
gui_help,
gui_settings,
gui_sys_patch_start,
gui_sys_patch_display,
gui_update,
)
from resources import (
constants,
global_settings,
updates
)
from data import os_data
class MainFrame(wx.Frame):
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None):
logging.info("Initializing Main Menu Frame")
super(MainFrame, self).__init__(parent, title=title, size=(600, 400), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
gui_support.GenerateMenubar(self, global_constants).generate()
self.constants: constants.Constants = global_constants
self.title: str = title
self.model_label: wx.StaticText = None
self.build_button: wx.Button = None
self.constants.update_stage = gui_support.AutoUpdateStages.INACTIVE
self._generate_elements()
self.Centre()
self.Show()
self._preflight_checks()
def _generate_elements(self) -> None:
"""
Generate UI elements for the main menu
Format:
- Title label: OpenCore Legacy Patcher v{X.Y.Z}
- Text: Model: {Build or Host Model}
- Buttons:
- Build and Install OpenCore
- Post-Install Root Patch
- Create macOS Installer
- Settings
- Help
- Text: Copyright
"""
# Title label: OpenCore Legacy Patcher v{X.Y.Z}
title_label = wx.StaticText(self, label=f"OpenCore Legacy Patcher v{self.constants.patcher_version}", pos=(-1,10))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# Text: Model: {Build or Host Model}
model_label = wx.StaticText(self, label=f"Model: {self.constants.custom_model or self.constants.computer.real_model}", pos=(-1, title_label.GetPosition()[1] + 25
))
model_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
model_label.Centre(wx.HORIZONTAL)
self.model_label = model_label
# Buttons:
menu_buttons = {
"Build and Install OpenCore": {
"function": self.on_build_and_install,
"description": [
"Prepares provided drive to be able",
"to boot unsupported OSes.",
"Use on installers or internal drives."
],
"icon": str(self.constants.icns_resource_path / "OC-Build.icns"),
},
"Create macOS Installer": {
"function": self.on_create_macos_installer,
"description": [
"Download and flash a macOS",
"Installer for your system.",
],
"icon": str(self.constants.icns_resource_path / "OC-Installer.icns"),
},
"⚙️ Settings": {
"function": self.on_settings,
"description": [
],
},
"Post-Install Root Patch": {
"function": self.on_post_install_root_patch,
"description": [
"Installs hardware drivers and",
"patches for your system after",
"installing a new version of macOS.",
],
"icon": str(self.constants.icns_resource_path / "OC-Patch.icns"),
},
"Support": {
"function": self.on_help,
"description": [
"Resources for OpenCore Legacy",
"Patcher.",
],
"icon": str(self.constants.icns_resource_path / "OC-Support.icns"),
},
}
button_x = 30
button_y = model_label.GetPosition()[1] + 30
rollover = len(menu_buttons) / 2
if rollover % 1 != 0:
rollover = int(rollover) + 1
index = 0
max_height = 0
for button_name, button_function in menu_buttons.items():
# place icon
if "icon" in button_function:
icon = wx.StaticBitmap(self, bitmap=wx.Bitmap(button_function["icon"], wx.BITMAP_TYPE_ICON), pos=(button_x - 10, button_y), size=(64, 64))
if button_name == "Post-Install Root Patch":
icon.SetPosition((-1, button_y + 7))
if button_name == "Create macOS Installer":
icon.SetPosition((button_x - 5, button_y + 3))
if button_name == "Support":
# icon_mac.SetSize((80, 80))
icon.SetPosition((button_x - 7, button_y + 3))
if button_name == "Build and Install OpenCore":
icon.SetSize((70, 70))
if button_name == "⚙️ Settings":
button_y += 5
button = wx.Button(self, label=button_name, pos=(button_x + 70, button_y), size=(180, 30))
button.Bind(wx.EVT_BUTTON, lambda event, function=button_function["function"]: function(event))
button_y += 30
# # Text: Description
description_label = wx.StaticText(self, label='\n'.join(button_function["description"]), pos=(button_x + 75, button.GetPosition()[1] + button.GetSize()[1] + 3))
description_label.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
# button_y += 15
for i, line in enumerate(button_function["description"]):
if line == "":
continue
if i == 0:
button_y += 11
else:
button_y += 13
button_y += 25
if button_name == "Build and Install OpenCore":
self.build_button = button
if gui_support.CheckProperties(self.constants).host_can_build() is False:
button.Disable()
elif button_name == "Post-Install Root Patch":
if self.constants.detected_os < os_data.os_data.big_sur:
button.Disable()
elif button_name == "⚙️ Settings":
button.SetSize((100, -1))
button.Centre(wx.HORIZONTAL)
description_label.Centre(wx.HORIZONTAL)
index += 1
if index == rollover:
max_height = button_y
button_x = 320
button_y = model_label.GetPosition()[1] + 30
# Text: Copyright
copy_label = wx.StaticText(self, label=self.constants.copyright_date, pos=(-1, max_height - 15))
copy_label.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
copy_label.Centre(wx.HORIZONTAL)
# Set window size
self.SetSize((-1, copy_label.GetPosition()[1] + 50))
def _preflight_checks(self):
if (
self.constants.computer.build_model != None and
self.constants.computer.build_model != self.constants.computer.real_model and
self.constants.host_is_hackintosh is False
):
# Notify user they're booting an unsupported configuration
pop_up = wx.MessageDialog(
self,
f"We found you are currently booting OpenCore built for a different unit: {self.constants.computer.build_model}\n\nWe builds configs to match individual units and cannot be mixed or reused with different Macs.\n\nPlease Build and Install a new OpenCore config, and reboot your Mac.",
"Unsupported Configuration Detected!",
style=wx.OK | wx.ICON_EXCLAMATION
)
pop_up.ShowModal()
self.on_build_and_install()
return
if "--update_installed" in sys.argv and self.constants.has_checked_updates is False and gui_support.CheckProperties(self.constants).host_can_build():
# Notify user that the update has been installed
self.constants.has_checked_updates = True
pop_up = wx.MessageDialog(
self,
f"OpenCore Legacy Patcher has been updated to the latest version: {self.constants.patcher_version}\n\nWould you like to update OpenCore and your root volume patches?",
"Update successful!",
style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_INFORMATION
)
pop_up.ShowModal()
if pop_up.GetReturnCode() != wx.ID_YES:
print("Skipping OpenCore and root volume patch update...")
return
print("Updating OpenCore and root volume patches...")
self.constants.update_stage = gui_support.AutoUpdateStages.CHECKING
self.Hide()
pos = self.GetPosition()
gui_build.BuildFrame(
parent=None,
title=self.title,
global_constants=self.constants,
screen_location=pos
)
self.Close()
threading.Thread(target=self._check_for_updates).start()
def _check_for_updates(self):
if self.constants.has_checked_updates is True:
return
ignore_updates = global_settings.GlobalEnviromentSettings().read_property("IgnoreAppUpdates")
if ignore_updates is True:
self.constants.ignore_updates = True
return
self.constants.ignore_updates = False
self.constants.has_checked_updates = True
dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates()
if not dict:
return
for entry in dict:
version = dict[entry]["Version"]
logging.info(f"New version: {version}")
dialog = wx.MessageDialog(
parent=self,
message=f"Current Version: {self.constants.patcher_version}{' (Nightly)' if not self.constants.commit_info[0].startswith('refs/tags') else ''}\nNew version: {version}\nWould you like to update?",
caption="Update Available for OpenCore Legacy Patcher!",
style=wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION
)
dialog.SetYesNoCancelLabels("Download and install", "Ignore", "View on Github")
response = dialog.ShowModal()
if response == wx.ID_YES:
wx.CallAfter(self.on_update, dict[entry]["Link"], version)
elif response == wx.ID_CANCEL:
webbrowser.open(dict[entry]["Github Link"])
def on_build_and_install(self, event: wx.Event = None):
self.Hide()
gui_build.BuildFrame(
parent=None,
title=self.title,
global_constants=self.constants,
screen_location=self.GetPosition()
)
self.Destroy()
def on_post_install_root_patch(self, event: wx.Event = None):
gui_sys_patch_display.SysPatchDisplayFrame(
parent=self,
title=self.title,
global_constants=self.constants,
screen_location=self.GetPosition()
)
def on_create_macos_installer(self, event: wx.Event = None):
gui_macos_installer_download.macOSInstallerDownloadFrame(
parent=self,
title=self.title,
global_constants=self.constants,
screen_location=self.GetPosition()
)
def on_settings(self, event: wx.Event = None):
gui_settings.SettingsFrame(
parent=self,
title=self.title,
global_constants=self.constants,
screen_location=self.GetPosition()
)
def on_help(self, event: wx.Event = None):
gui_help.HelpFrame(
parent=self,
title=self.title,
global_constants=self.constants,
screen_location=self.GetPosition()
)
def on_update(self, oclp_url: str, oclp_version: str):
gui_update.UpdateFrame(
parent=self,
title=self.title,
global_constants=self.constants,
screen_location=self.GetPosition(),
url=oclp_url,
version_label=oclp_version
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,334 @@
import wx
import os
import sys
import time
import logging
import plistlib
import threading
import subprocess
import applescript
import packaging.version
from pathlib import Path
from resources.wx_gui import gui_about
from resources import constants
from data import model_array, os_data, smbios_data
class AutoUpdateStages:
INACTIVE = 0
CHECKING = 1
BUILDING = 2
INSTALLING = 3
ROOT_PATCHING = 4
FINISHED = 5
class GenerateMenubar:
def __init__(self, frame: wx.Frame, global_constants: constants.Constants) -> None:
self.frame: wx.Frame = frame
self.constants: constants.Constants = global_constants
def generate(self) -> wx.MenuBar:
menubar = wx.MenuBar()
fileMenu = wx.Menu()
aboutItem = fileMenu.Append(wx.ID_ABOUT, "&About OpenCore Legacy Patcher")
fileMenu.AppendSeparator()
relaunchItem = fileMenu.Append(wx.ID_ANY, "&Relaunch as Root")
fileMenu.AppendSeparator()
revealLogItem = fileMenu.Append(wx.ID_ANY, "&Reveal Log File")
menubar.Append(fileMenu, "&File")
self.frame.SetMenuBar(menubar)
self.frame.Bind(wx.EVT_MENU, lambda event: gui_about.AboutFrame(self.constants), aboutItem)
self.frame.Bind(wx.EVT_MENU, lambda event: RelaunchApplicationAsRoot(self.frame, self.constants).relaunch(None), relaunchItem)
self.frame.Bind(wx.EVT_MENU, lambda event: subprocess.run(["open", "-R", self.constants.log_filepath]), revealLogItem)
if os.geteuid() == 0:
relaunchItem.Enable(False)
class GaugePulseCallback:
"""
Uses an alternative Pulse() method for wx.Gauge() on macOS Monterey+ with non-Metal GPUs
Dirty hack, however better to display some form of animation than none at all
Note: This work-around is no longer needed on hosts using PatcherSupportPkg 1.1.2 or newer
"""
def __init__(self, global_constants: constants.Constants, gauge: wx.Gauge) -> None:
self.gauge: wx.Gauge = gauge
self.pulse_thread: threading.Thread = None
self.pulse_thread_active: bool = False
self.gauge_value: int = 0
self.pulse_forward: bool = True
self.max_value: int = gauge.GetRange()
self.non_metal_alternative: bool = CheckProperties(global_constants).host_is_non_metal()
if self.non_metal_alternative is True:
if CheckProperties(global_constants).host_psp_version() >= packaging.version.Version("1.1.2"):
self.non_metal_alternative = False
def start_pulse(self) -> None:
if self.non_metal_alternative is False:
self.gauge.Pulse()
return
self.pulse_thread_active = True
self.pulse_thread = threading.Thread(target=self._pulse)
self.pulse_thread.start()
def stop_pulse(self) -> None:
if self.non_metal_alternative is False:
return
self.pulse_thread_active = False
self.pulse_thread.join()
def _pulse(self) -> None:
while self.pulse_thread_active:
if self.gauge_value == 0:
self.pulse_forward = True
elif self.gauge_value == self.max_value:
self.pulse_forward = False
if self.pulse_forward:
self.gauge_value += 1
else:
self.gauge_value -= 1
wx.CallAfter(self.gauge.SetValue, self.gauge_value)
time.sleep(0.005)
class CheckProperties:
def __init__(self, global_constants: constants.Constants) -> None:
self.constants: constants.Constants = global_constants
def host_can_build(self):
"""
Check if host supports building OpenCore configs
"""
if self.constants.custom_model:
return True
if self.constants.host_is_hackintosh is True:
return False
if self.constants.allow_oc_everywhere is True:
return True
if self.constants.computer.real_model in model_array.SupportedSMBIOS:
return True
return False
def host_is_non_metal(self, general_check: bool = False):
"""
Check if host is non-metal
Primarily for wx.Gauge().Pulse() workaround (where animation doesn't work on Monterey+)
"""
if self.constants.detected_os < os_data.os_data.monterey and general_check is False:
return False
if self.constants.detected_os < os_data.os_data.big_sur and general_check is True:
return False
if not Path("/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLightOld.dylib").exists():
# SkyLight stubs are only used on non-Metal
return False
return True
def host_has_cpu_gen(self, gen: int) -> bool:
"""
Check if host has a CPU generation equal to or greater than the specified generation
"""
model = self.constants.custom_model if self.constants.custom_model else self.constants.computer.real_model
if model in smbios_data.smbios_dictionary:
if smbios_data.smbios_dictionary[model]["CPU Generation"] >= gen:
return True
return False
def host_psp_version(self) -> packaging.version.Version:
"""
Grab PatcherSupportPkg version from OpenCore-Legacy-Patcher.plist
"""
oclp_plist_path = "/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist"
if not Path(oclp_plist_path).exists():
return packaging.version.Version("0.0.0")
oclp_plist = plistlib.load(open(oclp_plist_path, "rb"))
if "PatcherSupportPkg" not in oclp_plist:
return packaging.version.Version("0.0.0")
if oclp_plist["PatcherSupportPkg"].startswith("v"):
oclp_plist["PatcherSupportPkg"] = oclp_plist["PatcherSupportPkg"][1:]
return packaging.version.parse(oclp_plist["PatcherSupportPkg"])
class PayloadMount:
def __init__(self, global_constants: constants.Constants, frame: wx.Frame) -> None:
self.constants: constants.Constants = global_constants
self.frame: wx.Frame = frame
def is_unpack_finished(self):
if self.constants.unpack_thread.is_alive():
return False
if Path(self.constants.payload_kexts_path).exists():
return True
# Raise error to end program
popup = wx.MessageDialog(
self.frame,
f"During unpacking of our internal files, we seemed to have encountered an error.\n\nIf you keep seeing this error, please try rebooting and redownloading the application.",
"Internal Error occurred!",
style=wx.OK | wx.ICON_EXCLAMATION
)
popup.ShowModal()
self.frame.Freeze()
sys.exit(1)
class ThreadHandler(logging.Handler):
"""
Reroutes logging output to a wx.TextCtrl using UI callbacks
"""
def __init__(self, text_box: wx.TextCtrl):
logging.Handler.__init__(self)
self.text_box = text_box
def emit(self, record: logging.LogRecord):
wx.CallAfter(self.text_box.AppendText, self.format(record) + '\n')
class RestartHost:
"""
Restarts the host machine
"""
def __init__(self, frame: wx.Frame) -> None:
self.frame: wx.Frame = frame
def restart(self, event: wx.Event = None, message: str = ""):
self.popup = wx.MessageDialog(
self.frame,
message,
"Reboot to apply?",
wx.YES_NO | wx.YES_DEFAULT | wx.ICON_INFORMATION
)
self.popup.SetYesNoLabels("Reboot", "Ignore")
answer = self.popup.ShowModal()
if answer == wx.ID_YES:
# Reboots with Count Down prompt (user can still dismiss if needed)
self.frame.Hide()
wx.Yield()
try:
applescript.AppleScript('tell app "loginwindow" to «event aevtrrst»').run()
except applescript.ScriptError as e:
logging.error(f"Error while trying to reboot: {e}")
sys.exit(0)
class RelaunchApplicationAsRoot:
"""
Relaunches the application as root
"""
def __init__(self, frame: wx.Frame, global_constants: constants.Constants) -> None:
self.constants = global_constants
self.frame: wx.Frame = frame
def relaunch(self, event: wx.Event):
self.dialog = wx.MessageDialog(
self.frame,
"OpenCore Legacy Patcher needs to relaunch as admin to continue. You will be prompted to enter your password.",
"Relaunch as root?",
wx.YES_NO | wx.ICON_QUESTION
)
# Show Dialog Box
if self.dialog.ShowModal() != wx.ID_YES:
logging.info("User cancelled relaunch")
return
timer: int = 5
program_arguments: str = ""
if event:
if event.GetEventObject() != wx.Menu:
try:
if event.GetEventObject().GetLabel() in ["Start Root Patching", "Reinstall Root Patches"]:
program_arguments = " --gui_patch"
elif event.GetEventObject().GetLabel() == "Revert Root Patches":
program_arguments = " --gui_unpatch"
except TypeError:
pass
if self.constants.launcher_script is None:
program_arguments = f"'{self.constants.launcher_binary}'{program_arguments}"
else:
program_arguments = f"{self.constants.launcher_binary} {self.constants.launcher_script}{program_arguments}"
# Relaunch as root
args = [
"osascript",
"-e",
f'''do shell script "{program_arguments}"'''
' with prompt "OpenCore Legacy Patcher needs administrator privileges to relaunch as admin."'
" with administrator privileges"
" without altering line endings",
]
self.frame.DestroyChildren()
self.frame.SetSize(300, 300)
self.frame.Centre()
# Header
header = wx.StaticText(self.frame, label="Relaunching as root", pos=(-1, 5))
header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
header.Centre(wx.HORIZONTAL)
# Add count down label
countdown_label = wx.StaticText(self.frame, label=f"Closing old process in {timer} seconds", pos=(0, header.GetPosition().y + header.GetSize().height + 3))
countdown_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
countdown_label.Centre(wx.HORIZONTAL)
# Set size of frame
self.frame.SetSize((-1, countdown_label.GetPosition().y + countdown_label.GetSize().height + 40))
wx.Yield()
logging.info(f"Relaunching as root with command: {program_arguments}")
subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
wx.Yield()
countdown_label.SetLabel(f"Closing old process in {timer} seconds")
time.sleep(1)
timer -= 1
if timer == 0:
break
sys.exit(0)

View File

@@ -0,0 +1,338 @@
import wx
import os
import logging
import plistlib
import threading
from pathlib import Path
from resources import (
constants,
)
from resources.sys_patch import (
sys_patch_detect
)
from resources.wx_gui import (
gui_main_menu,
gui_support,
gui_sys_patch_start,
)
class SysPatchDisplayFrame(wx.Frame):
"""
Create a modal frame for displaying root patches
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None):
logging.info("Initializing Root Patch Display Frame")
if parent:
self.frame = parent
else:
super().__init__(parent, title=title, size=(360, 200), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX)
self.frame = self
self.frame.Centre()
self.title = title
self.constants: constants.Constants = global_constants
self.frame_modal: wx.Dialog = None
self.return_button: wx.Button = None
self.available_patches: bool = False
self.init_with_parent = True if parent else False
self.frame_modal = wx.Dialog(self.frame, title=title, size=(360, 200))
self._generate_elements_display_patches(self.frame_modal)
if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE:
if self.available_patches is False:
gui_support.RestartHost(self.frame).restart(message="No root patch updates needed!\n\nWould you like to reboot to apply the new OpenCore build?")
def _generate_elements_display_patches(self, frame: wx.Frame = None) -> None:
"""
Generate UI elements for root patching frame
Format:
- Title label: Post-Install Menu
- Label: Available patches:
- Labels: {patch name}
- Button: Start Root Patching
- Button: Revert Root Patches
- Button: Return to Main Menu
"""
frame = self if not frame else frame
title_label = wx.StaticText(frame, label="Post-Install Menu", pos=(-1, 10))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# Label: Fetching patches...
available_label = wx.StaticText(frame, label="Fetching patches for host", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 10))
available_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
available_label.Centre(wx.HORIZONTAL)
# Progress bar
progress_bar = wx.Gauge(frame, range=100, pos=(-1, available_label.GetPosition()[1] + available_label.GetSize()[1] + 10), size=(250, 20))
progress_bar.Centre(wx.HORIZONTAL)
progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar)
progress_bar_animation.start_pulse()
# Set window height
frame.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 40))
# Labels: {patch name}
patches: dict = {}
def _fetch_patches(self) -> None:
nonlocal patches
patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set()
thread = threading.Thread(target=_fetch_patches, args=(self,))
thread.start()
frame.ShowWindowModal()
while thread.is_alive():
wx.Yield()
frame.Close()
progress_bar.Hide()
progress_bar_animation.stop_pulse()
available_label.SetLabel("Available patches for your system:")
available_label.Centre(wx.HORIZONTAL)
can_unpatch: bool = patches["Validation: Unpatching Possible"]
if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches):
logging.info("No applicable patches available")
patches = []
# Check if OCLP has already applied the same patches
no_new_patches = not self._check_if_new_patches_needed(patches) if patches else False
if not patches:
# Prompt user with no patches found
patch_label = wx.StaticText(frame, label="No patches required", pos=(-1, available_label.GetPosition()[1] + 20))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
patch_label.Centre(wx.HORIZONTAL)
else:
# Add Label for each patch
i = 0
if no_new_patches is True:
patch_label = wx.StaticText(frame, label="All applicable patches already installed", pos=(-1, available_label.GetPosition()[1] + 20))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
patch_label.Centre(wx.HORIZONTAL)
i = i + 20
else:
longest_patch = ""
for patch in patches:
if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True):
if len(patch) > len(longest_patch):
longest_patch = patch
anchor = wx.StaticText(frame, label=longest_patch, pos=(-1, available_label.GetPosition()[1] + 20))
anchor.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
anchor.Centre(wx.HORIZONTAL)
anchor.Hide()
logging.info("Available patches:")
for patch in patches:
if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True):
i = i + 20
logging.info(f"- {patch}")
patch_label = wx.StaticText(frame, label=f"- {patch}", pos=(anchor.GetPosition()[0], available_label.GetPosition()[1] + i))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
if i == 20:
patch_label.SetLabel(patch_label.GetLabel().replace("-", ""))
patch_label.Centre(wx.HORIZONTAL)
if patches["Validation: Patching Possible"] is False:
# Cannot patch due to the following reasons:
patch_label = wx.StaticText(frame, label="Cannot patch due to the following reasons:", pos=(-1, patch_label.GetPosition()[1] + 25))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
patch_label.Centre(wx.HORIZONTAL)
longest_patch = ""
for patch in patches:
if not patch.startswith("Validation"):
continue
if patches[patch] is False:
continue
if patch == "Validation: Unpatching Possible":
continue
if len(patch) > len(longest_patch):
longest_patch = patch
anchor = wx.StaticText(frame, label=longest_patch.split('Validation: ')[1], pos=(-1, patch_label.GetPosition()[1] + 20))
anchor.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
anchor.Centre(wx.HORIZONTAL)
anchor.Hide()
i = 0
for patch in patches:
if not patch.startswith("Validation"):
continue
if patches[patch] is False:
continue
if patch == "Validation: Unpatching Possible":
continue
patch_label = wx.StaticText(frame, label=f"- {patch.split('Validation: ')[1]}", pos=(anchor.GetPosition()[0], anchor.GetPosition()[1] + i))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
i = i + 20
if i == 20:
patch_label.SetLabel(patch_label.GetLabel().replace("-", ""))
patch_label.Centre(wx.HORIZONTAL)
else:
if self.constants.computer.oclp_sys_version and self.constants.computer.oclp_sys_date:
date = self.constants.computer.oclp_sys_date.split(" @")
date = date[0] if len(date) == 2 else ""
patch_text = f"{self.constants.computer.oclp_sys_version}, {date}"
patch_label = wx.StaticText(frame, label="Root Volume last patched:", pos=(-1, patch_label.GetPosition().y + 25))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
patch_label.Centre(wx.HORIZONTAL)
patch_label = wx.StaticText(frame, label=patch_text, pos=(available_label.GetPosition().x - 10, patch_label.GetPosition().y + 20))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
patch_label.Centre(wx.HORIZONTAL)
# Button: Start Root Patching
start_button = wx.Button(frame, label="Start Root Patching", pos=(10, patch_label.GetPosition().y + 25), size=(170, 30))
start_button.Bind(wx.EVT_BUTTON, lambda event: self.on_start_root_patching(patches))
start_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
start_button.Centre(wx.HORIZONTAL)
# Button: Revert Root Patches
revert_button = wx.Button(frame, label="Revert Root Patches", pos=(10, start_button.GetPosition().y + start_button.GetSize().height - 5), size=(170, 30))
revert_button.Bind(wx.EVT_BUTTON, lambda event: self.on_revert_root_patching(patches))
revert_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
revert_button.Centre(wx.HORIZONTAL)
# Button: Return to Main Menu
return_button = wx.Button(frame, label="Return to Main Menu", pos=(10, revert_button.GetPosition().y + revert_button.GetSize().height), size=(150, 30))
return_button.Bind(wx.EVT_BUTTON, self.on_return_dismiss if self.init_with_parent else self.on_return_to_main_menu)
return_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
return_button.Centre(wx.HORIZONTAL)
self.return_button = return_button
# Disable buttons if unsupported
if not patches:
start_button.Disable()
else:
self.available_patches = True
if patches["Validation: Patching Possible"] is False:
start_button.Disable()
elif no_new_patches is False:
start_button.SetDefault()
else:
self.available_patches = False
if can_unpatch is False:
revert_button.Disable()
# Relaunch as root if not root
if os.geteuid() != 0:
start_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch)
revert_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch)
# Set frame size
frame.SetSize((-1, return_button.GetPosition().y + return_button.GetSize().height + 15))
frame.ShowWindowModal()
def on_start_root_patching(self, patches: dict):
frame = gui_sys_patch_start.SysPatchStartFrame(
parent=None,
title=self.title,
global_constants=self.constants,
patches=patches,
)
frame.start_root_patching()
self.on_return_dismiss() if self.init_with_parent else self.on_return_to_main_menu()
def on_revert_root_patching(self, patches: dict):
frame = gui_sys_patch_start.SysPatchStartFrame(
parent=None,
title=self.title,
global_constants=self.constants,
patches=patches,
)
frame.revert_root_patching()
self.on_return_dismiss() if self.init_with_parent else self.on_return_to_main_menu()
def on_return_to_main_menu(self, event: wx.Event = None):
# Get frame from event
frame_modal: wx.Dialog = event.GetEventObject().GetParent()
frame: wx.Frame = frame_modal.Parent
frame_modal.Hide()
frame.Hide()
main_menu_frame = gui_main_menu.MainFrame(
None,
title=self.title,
global_constants=self.constants,
)
main_menu_frame.Show()
frame.Destroy()
def on_return_dismiss(self, event: wx.Event = None):
self.frame_modal.Hide()
self.frame_modal.Destroy()
def _check_if_new_patches_needed(self, patches: dict) -> bool:
"""
Checks if any new patches are needed for the user to install
Newer users will assume the root patch menu will present missing patches.
Thus we'll need to see if the exact same OCLP build was used already
"""
logging.info("Checking if new patches are needed")
if self.constants.commit_info[0] in ["Running from source", "Built from source"]:
return True
if self.constants.computer.oclp_sys_url != self.constants.commit_info[2]:
# If commits are different, assume patches are as well
return True
oclp_plist = "/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist"
if not Path(oclp_plist).exists():
# If it doesn't exist, no patches were ever installed
# ie. all patches applicable
return True
oclp_plist_data = plistlib.load(open(oclp_plist, "rb"))
for patch in patches:
if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True):
# Patches should share the same name as the plist key
# See sys_patch_dict.py for more info
patch_installed = False
for key in oclp_plist_data:
if isinstance(oclp_plist_data[key], (bool, int)):
continue
if "Display Name" not in oclp_plist_data[key]:
continue
if oclp_plist_data[key]["Display Name"] == patch:
patch_installed = True
break
if patch_installed is False:
logging.info(f"- Patch {patch} not installed")
return True
logging.info("No new patches detected for system")
return False

View File

@@ -0,0 +1,378 @@
import wx
import sys
import time
import logging
import plistlib
import traceback
import threading
import subprocess
from pathlib import Path
from resources import (
constants,
kdk_handler,
global_settings,
)
from resources.sys_patch import (
sys_patch,
sys_patch_detect
)
from resources.wx_gui import (
gui_main_menu,
gui_support,
gui_download,
)
from data import os_data
class SysPatchStartFrame(wx.Frame):
"""
Create a frame for root patching
Uses a Modal Dialog for smoother transition from other frames
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None, patches: dict = {}):
logging.info("Initializing Root Patching Frame")
self.title = title
self.constants: constants.Constants = global_constants
self.frame_modal: wx.Dialog = None
self.return_button: wx.Button = None
self.available_patches: bool = False
self.patches: dict = patches
super(SysPatchStartFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
gui_support.GenerateMenubar(self, self.constants).generate()
self.Centre()
if self.patches == {}:
self.patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set()
def _kdk_download(self, frame: wx.Frame = None) -> bool:
frame = self if not frame else frame
logging.info("KDK missing, generating KDK download frame")
header = wx.StaticText(frame, label="Downloading Kernel Debug Kit", pos=(-1,5))
header.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
header.Centre(wx.HORIZONTAL)
subheader = wx.StaticText(frame, label="Fetching KDK database...", pos=(-1, header.GetPosition()[1] + header.GetSize()[1] + 5))
subheader.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
subheader.Centre(wx.HORIZONTAL)
progress_bar = wx.Gauge(frame, range=100, pos=(-1, subheader.GetPosition()[1] + subheader.GetSize()[1] + 5), size=(250, 20))
progress_bar.Centre(wx.HORIZONTAL)
progress_bar_animation = gui_support.GaugePulseCallback(self.constants, progress_bar)
progress_bar_animation.start_pulse()
# Set size of frame
frame.SetSize((-1, progress_bar.GetPosition()[1] + progress_bar.GetSize()[1] + 35))
frame.Show()
# Generate KDK object
self.kdk_obj: kdk_handler.KernelDebugKitObject = None
def _kdk_thread_spawn():
self.kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version)
kdk_thread = threading.Thread(target=_kdk_thread_spawn)
kdk_thread.start()
while kdk_thread.is_alive():
wx.Yield()
if self.kdk_obj.success is False:
progress_bar_animation.stop_pulse()
progress_bar.SetValue(0)
wx.MessageBox(f"KDK download failed: {self.kdk_obj.error_msg}", "Error", wx.OK | wx.ICON_ERROR)
return False
kdk_download_obj = self.kdk_obj.retrieve_download()
if not kdk_download_obj:
# KDK is already downloaded
return True
gui_download.DownloadFrame(
self,
title=self.title,
global_constants=self.constants,
download_obj=kdk_download_obj,
item_name=f"KDK Build {self.kdk_obj.kdk_url_build}"
)
if kdk_download_obj.download_complete is False:
return False
logging.info("KDK download complete, validating with hdiutil")
header.SetLabel(f"Validating KDK: {self.kdk_obj.kdk_url_build}")
header.Centre(wx.HORIZONTAL)
subheader.SetLabel("Checking if checksum is valid...")
subheader.Centre(wx.HORIZONTAL)
wx.Yield()
progress_bar_animation.stop_pulse()
if self.kdk_obj.validate_kdk_checksum() is False:
progress_bar.SetValue(0)
logging.error("KDK checksum validation failed")
logging.error(self.kdk_obj.error_msg)
msg = wx.MessageDialog(frame, f"KDK checksum validation failed: {self.kdk_obj.error_msg}", "Error", wx.OK | wx.ICON_ERROR)
msg.ShowModal()
return False
progress_bar.SetValue(100)
logging.info("KDK download complete")
for child in frame.GetChildren():
child.Destroy()
return True
def _generate_modal(self, patches: dict = {}, variant: str = "Root Patching"):
"""
Create UI for root patching/unpatching
"""
supported_variants = ["Root Patching", "Revert Root Patches"]
if variant not in supported_variants:
logging.error(f"Unsupported variant: {variant}")
return
self.frame_modal.Close() if self.frame_modal else None
dialog = wx.Dialog(self, title=self.title, size=(400, 200))
# Title
title = wx.StaticText(dialog, label=variant, pos=(-1, 10))
title.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title.Centre(wx.HORIZONTAL)
if variant == "Root Patching":
# Label
label = wx.StaticText(dialog, label="Root Patching will patch the following:", pos=(-1, title.GetPosition()[1] + 30))
label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
label.Centre(wx.HORIZONTAL)
# Get longest patch label, then create anchor for patch labels
longest_patch = ""
for patch in patches:
if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True):
if len(patch) > len(longest_patch):
longest_patch = patch
anchor = wx.StaticText(dialog, label=longest_patch, pos=(label.GetPosition()[0], label.GetPosition()[1] + 20))
anchor.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
anchor.Centre(wx.HORIZONTAL)
anchor.Hide()
# Labels
i = 0
logging.info("Available patches:")
for patch in patches:
if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True):
logging.info(f"- {patch}")
patch_label = wx.StaticText(dialog, label=f"- {patch}", pos=(anchor.GetPosition()[0], label.GetPosition()[1] + 20 + i))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
i = i + 20
if i == 20:
patch_label.SetLabel(patch_label.GetLabel().replace("-", ""))
patch_label.Centre(wx.HORIZONTAL)
elif i == 0:
patch_label = wx.StaticText(dialog, label="No patches to apply", pos=(label.GetPosition()[0], label.GetPosition()[1] + 20))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
patch_label.Centre(wx.HORIZONTAL)
else:
patch_label = wx.StaticText(dialog, label="Reverting to last sealed snapshot", pos=(-1, title.GetPosition()[1] + 30))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
patch_label.Centre(wx.HORIZONTAL)
# Text box
text_box = wx.TextCtrl(dialog, pos=(10, patch_label.GetPosition()[1] + 30), size=(400, 400), style=wx.TE_READONLY | wx.TE_MULTILINE | wx.TE_RICH2)
text_box.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
text_box.Centre(wx.HORIZONTAL)
self.text_box = text_box
# Button: Return to Main Menu
return_button = wx.Button(dialog, label="Return to Main Menu", pos=(10, text_box.GetPosition()[1] + text_box.GetSize()[1] + 5), size=(150, 30))
return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu)
return_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
return_button.Centre(wx.HORIZONTAL)
self.return_button = return_button
# Set frame size
dialog.SetSize((-1, return_button.GetPosition().y + return_button.GetSize().height + 33))
self.frame_modal = dialog
dialog.ShowWindowModal()
def start_root_patching(self):
logging.info("Starting root patching")
while gui_support.PayloadMount(self.constants, self).is_unpack_finished() is False:
wx.Yield()
if self.patches["Settings: Kernel Debug Kit missing"] is True:
if self._kdk_download(self) is False:
sys.exit(1)
self._generate_modal(self.patches, "Root Patching")
self.return_button.Disable()
thread = threading.Thread(target=self._start_root_patching, args=(self.patches,))
thread.start()
while thread.is_alive():
wx.Yield()
self._post_patch()
self.return_button.Enable()
def _start_root_patching(self, patches: dict):
logger = logging.getLogger()
logger.addHandler(gui_support.ThreadHandler(self.text_box))
try:
sys_patch.PatchSysVolume(self.constants.computer.real_model, self.constants, patches).start_patch()
except:
logging.error("An internal error occurred while running the Root Patcher:\n")
logging.error(traceback.format_exc())
logger.removeHandler(logger.handlers[2])
def revert_root_patching(self):
logging.info("Reverting root patches")
self._generate_modal(self.patches, "Revert Root Patches")
self.return_button.Disable()
thread = threading.Thread(target=self._revert_root_patching, args=(self.patches,))
thread.start()
while thread.is_alive():
wx.Yield()
self._post_patch()
self.return_button.Enable()
def _revert_root_patching(self, patches: dict):
logger = logging.getLogger()
logger.addHandler(gui_support.ThreadHandler(self.text_box))
try:
sys_patch.PatchSysVolume(self.constants.computer.real_model, self.constants, patches).start_unpatch()
except:
logging.error("An internal error occurred while running the Root Patcher:\n")
logging.error(traceback.format_exc())
logger.removeHandler(logger.handlers[2])
def on_return_to_main_menu(self, event: wx.Event = None):
# Get frame from event
frame_modal: wx.Dialog = event.GetEventObject().GetParent()
frame: wx.Frame = frame_modal.Parent
frame_modal.Hide()
frame.Hide()
main_menu_frame = gui_main_menu.MainFrame(
None,
title=self.title,
global_constants=self.constants,
)
main_menu_frame.Show()
frame.Destroy()
def on_return_dismiss(self, event: wx.Event = None):
self.frame_modal.Hide()
self.frame_modal.Destroy()
def _post_patch(self):
if self.constants.root_patcher_succeeded is False:
return
if self.constants.needs_to_open_preferences is False:
gui_support.RestartHost(self.frame_modal).restart(message="Root Patcher finished successfully!\n\nWould you like to reboot now?")
return
if self.constants.detected_os >= os_data.os_data.ventura:
gui_support.RestartHost(self.frame_modal).restart(message="Root Patcher finished successfully!\nIf you were prompted to open System Settings to authorize new kexts, this can be ignored. Your system is ready once restarted.\n\nWould you like to reboot now?")
return
# Create dialog box to open System Preferences -> Security and Privacy
self.popup = wx.MessageDialog(
self.frame_modal,
"We just finished installing the patches to your Root Volume!\n\nHowever, Apple requires users to manually approve the kernel extensions installed before they can be used next reboot.\n\nWould you like to open System Preferences?",
"Open System Preferences?",
wx.YES_NO | wx.ICON_INFORMATION
)
self.popup.SetYesNoLabels("Open System Preferences", "Ignore")
answer = self.popup.ShowModal()
if answer == wx.ID_YES:
output =subprocess.run(
[
"osascript", "-e",
'tell app "System Preferences" to activate',
"-e", 'tell app "System Preferences" to reveal anchor "General" of pane id "com.apple.preference.security"',
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
if output.returncode != 0:
# Some form of fallback if unaccelerated state errors out
subprocess.run(["open", "-a", "System Preferences"])
time.sleep(5)
sys.exit(0)
def _check_if_new_patches_needed(self, patches: dict) -> bool:
"""
Checks if any new patches are needed for the user to install
Newer users will assume the root patch menu will present missing patches.
Thus we'll need to see if the exact same OCLP build was used already
"""
logging.info("Checking if new patches are needed")
if self.constants.commit_info[0] in ["Running from source", "Built from source"]:
return True
if self.constants.computer.oclp_sys_url != self.constants.commit_info[2]:
# If commits are different, assume patches are as well
return True
oclp_plist = "/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist"
if not Path(oclp_plist).exists():
# If it doesn't exist, no patches were ever installed
# ie. all patches applicable
return True
oclp_plist_data = plistlib.load(open(oclp_plist, "rb"))
for patch in patches:
if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True):
# Patches should share the same name as the plist key
# See sys_patch_dict.py for more info
patch_installed = False
for key in oclp_plist_data:
if isinstance(oclp_plist_data[key], (bool, int)):
continue
if "Display Name" not in oclp_plist_data[key]:
continue
if oclp_plist_data[key]["Display Name"] == patch:
patch_installed = True
break
if patch_installed is False:
logging.info(f"- Patch {patch} not installed")
return True
logging.info("No new patches detected for system")
return False

Some files were not shown because too many files have changed in this diff Show More