diff --git a/.github/workflows/build-app-offline.yml b/.github/workflows/build-app-offline.yml index da45755ea..c34fe5af1 100644 --- a/.github/workflows/build-app-offline.yml +++ b/.github/workflows/build-app-offline.yml @@ -38,3 +38,5 @@ jobs: file: OpenCore-Patcher-TUI-Offline.app.zip tag: ${{ github.ref }} file_glob: true + - name: Validate OpenCore + run: ./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher --validate diff --git a/.github/workflows/build-app-wxpython-offline.yml b/.github/workflows/build-app-wxpython-offline.yml index ef6a67576..e51bed106 100644 --- a/.github/workflows/build-app-wxpython-offline.yml +++ b/.github/workflows/build-app-wxpython-offline.yml @@ -22,13 +22,19 @@ jobs: - run: 'codesign -s "Developer ID Application: Mykola Grymalyuk (S74BDJXQMD)" -v --force --deep --timestamp --entitlements ./payloads/entitlements.plist -o runtime "dist/OpenCore-Patcher.app"' - run: cd dist; zip -r ../OpenCore-Patcher-wxPython.app.zip OpenCore-Patcher.app - run: ./../sign-wxpython.sh + - run: packagesbuild ./payloads/InstallPackage/OCLP-Install-Setup.pkgproj - run: mv ./OpenCore-Patcher-wxPython.app.zip ./OpenCore-Patcher-GUI-Offline.app.zip - name: Upload App to Artifacts uses: actions/upload-artifact@v2 with: name: OpenCore-Patcher.app (GUI Offline) path: OpenCore-Patcher-GUI-Offline.app.zip - - name: Upload to Release + - name: Upload Package to Artifacts + uses: actions/upload-artifact@v2 + with: + name: OCLP-Install.pkg + path: ./dist/OCLP-Install.pkg + - name: Upload Binary to Release if: github.event_name == 'release' uses: svenstaro/upload-release-action@e74ff71f7d8a4c4745b560a485cc5fdb9b5b999d with: @@ -36,5 +42,11 @@ jobs: file: OpenCore-Patcher-GUI-Offline.app.zip tag: ${{ github.ref }} file_glob: true - - name: Validate OpenCore - run: ./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher --validate \ No newline at end of file + - name: Upload Package to Release + if: github.event_name == 'release' + uses: svenstaro/upload-release-action@e74ff71f7d8a4c4745b560a485cc5fdb9b5b999d + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: ./dist/OCLP-Install.pkg + tag: ${{ github.ref }} + file_glob: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index a3eb7db80..5f5c657a8 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ __pycache__/ /payloads/Installer.sh /payloads/Info.plist /payloads/seed.plist +/payloads/OCLP-Install.pkg +/payloads/OCLP-Install.pkg.zip diff --git a/gui/gui_main.py b/gui/gui_main.py index 8fb25c3aa..9ab5e3227 100644 --- a/gui/gui_main.py +++ b/gui/gui_main.py @@ -11,6 +11,7 @@ import os import wx.adv from wx.lib.agw import hyperlink import threading +from pathlib import Path from resources import constants, defaults, build, install, installer, sys_patch_download, utilities, sys_patch_detect, sys_patch, run, generate_smbios, updates from data import model_array, os_data, smbios_data, sip_data @@ -23,6 +24,7 @@ class wx_python_gui: self.constants.gui_mode = True self.walkthrough_mode = False self.finished_auto_patch = False + self.target_disk = "" # Backup stdout for usage with wxPython self.stock_stdout = sys.stdout @@ -1418,15 +1420,18 @@ class wx_python_gui: print("- Starting creation script as admin") wx.GetApp().Yield() time.sleep(1) - thread = threading.Thread(target=self.start_script) - thread.start() disk = disk[5:] + self.target_disk = disk + install_thread = threading.Thread(target=self.start_script) + install_thread.start() + self.download_thread = threading.Thread(target=self.download_and_unzip_pkg) + self.download_thread.start() default_output = float(utilities.monitor_disk_output(disk)) while True: time.sleep(0.1) output = float(utilities.monitor_disk_output(disk)) bytes_written = output - default_output - if thread.is_alive(): + if install_thread.is_alive(): self.progress_bar.SetValue(bytes_written) self.progress_label.SetLabel(f"Bytes Written: {round(bytes_written, 2)}MB") self.progress_label.Centre(wx.HORIZONTAL) @@ -1446,6 +1451,12 @@ class wx_python_gui: output, error, returncode = run.Run()._stream_output(comm=args) if "Install media now available at" in output: print("- Sucessfully created macOS installer") + while self.download_thread.is_alive(): + # wait for download_thread to finish + # though highly unlikely this thread is still alive (flashing an Installer will take a while) + time.sleep(0.1) + print("- Installing Root Patcher to drive") + self.install_installer_pkg(self.target_disk) popup_message = wx.MessageDialog(self.frame, "Sucessfully created a macOS installer!\nYou can now install OpenCore onto this drive", "Success", wx.OK) popup_message.ShowModal() else: @@ -1453,6 +1464,33 @@ class wx_python_gui: popup = wx.MessageDialog(self.frame, f"Failed to create macOS installer\n\nOutput: {output}\n\nError: {error}", "Error", wx.OK | wx.ICON_ERROR) popup.ShowModal() + + def download_and_unzip_pkg(self): + # Function's main goal is to grab the correct OCLP-Install.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 not utilities.validate_link(link): + print("- Stock Install.pkg is missing on Github, falling back to Nightly") + link = self.constants.installer_pkg_url_nightly + if not utilities.validate_link(link): + print("- Nightly Install.pkg is missing on Github, exiting") + return + + if utilities.download_file(link, self.constants.installer_pkg_zip_path): + 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 Path(self.constants.installer_pkg_path).exists(): + path = utilities.grab_mount_point_from_disk(disk) + subprocess.run(["mkdir", "-p", f"{path}/Library/Packages/"]) + subprocess.run(["cp", "-r", self.constants.installer_pkg_path, f"{path}/Library/Packages/"]) + def settings_menu(self, event=None): # Define Menu # - Header: Settings diff --git a/payloads/Config/config.plist b/payloads/Config/config.plist index 92979055a..53b4b8709 100644 --- a/payloads/Config/config.plist +++ b/payloads/Config/config.plist @@ -1203,6 +1203,24 @@ PlistPath Contents/Info.plist + + Arch + x86_64 + BundlePath + AutoPkgInstaller.kext + Comment + Chainload OpenCore-Patcher installation + Enabled + + ExecutablePath + Contents/MacOS/AutoPkgInstaller + MaxKernel + + MinKernel + 20.0.0 + PlistPath + Contents/Info.plist + Block diff --git a/payloads/InstallPackage/OCLP-Install-Setup.pkgproj b/payloads/InstallPackage/OCLP-Install-Setup.pkgproj new file mode 100644 index 000000000..c07c92f83 --- /dev/null +++ b/payloads/InstallPackage/OCLP-Install-Setup.pkgproj @@ -0,0 +1,874 @@ + + + + + PACKAGES + + + MUST-CLOSE-APPLICATION-ITEMS + + MUST-CLOSE-APPLICATIONS + + PACKAGE_FILES + + DEFAULT_INSTALL_LOCATION + / + HIERARCHY + + CHILDREN + + + CHILDREN + + GID + 80 + PATH + Applications + PATH_TYPE + 0 + PERMISSIONS + 509 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + + CHILDREN + + + BUNDLE_CAN_DOWNGRADE + + BUNDLE_POSTINSTALL_PATH + + PATH_TYPE + 0 + + BUNDLE_PREINSTALL_PATH + + PATH_TYPE + 0 + + CHILDREN + + GID + 80 + PATH + ../../dist/OpenCore-Patcher.app + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + + GID + 80 + PATH + Dortania + PATH_TYPE + 2 + PERMISSIONS + 509 + TYPE + 2 + UID + 0 + + + GID + 80 + PATH + Application Support + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Automator + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Documentation + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Extensions + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Filesystems + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Frameworks + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Input Methods + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Internet Plug-Ins + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Keyboard Layouts + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + ../com.dortania.opencore-legacy-patcher.auto-patch.plist + PATH_TYPE + 1 + PERMISSIONS + 420 + TYPE + 3 + UID + 0 + + + GID + 0 + PATH + LaunchAgents + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + LaunchDaemons + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PreferencePanes + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Preferences + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 80 + PATH + Printers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + PrivilegedHelperTools + PATH_TYPE + 0 + PERMISSIONS + 1005 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickLook + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + QuickTime + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Screen Savers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Scripts + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Services + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + Widgets + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + Library + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + Shared + PATH_TYPE + 0 + PERMISSIONS + 1023 + TYPE + 1 + UID + 0 + + + GID + 80 + PATH + Users + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + / + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + PAYLOAD_TYPE + 0 + PRESERVE_EXTENDED_ATTRIBUTES + + SHOW_INVISIBLE + + SPLIT_FORKS + + TREAT_MISSING_FILES_AS_WARNING + + VERSION + 5 + + PACKAGE_SCRIPTS + + POSTINSTALL_PATH + + PATH + postinstall.sh + PATH_TYPE + 1 + + PREINSTALL_PATH + + PATH_TYPE + 0 + + RESOURCES + + + PACKAGE_SETTINGS + + AUTHENTICATION + 1 + CONCLUSION_ACTION + 0 + FOLLOW_SYMBOLIC_LINKS + + IDENTIFIER + com.mygreatcompany.pkg.OCLP-Install + LOCATION + 0 + NAME + OCLP-Install + OVERWRITE_PERMISSIONS + + PAYLOAD_SIZE + -1 + REFERENCE_PATH + + RELOCATABLE + + USE_HFS+_COMPRESSION + + VERSION + 1.0 + + TYPE + 0 + UUID + 4312D78E-7981-41F2-A0E9-5C7E11AC61C5 + + + PROJECT + + PROJECT_COMMENTS + + NOTES + + + + PROJECT_PRESENTATION + + BACKGROUND + + APPAREANCES + + DARK_AQUA + + LIGHT_AQUA + + + SHARED_SETTINGS_FOR_ALL_APPAREANCES + + + INSTALLATION_STEPS + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewIntroductionController + INSTALLER_PLUGIN + Introduction + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewReadMeController + INSTALLER_PLUGIN + ReadMe + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewLicenseController + INSTALLER_PLUGIN + License + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewDestinationSelectController + INSTALLER_PLUGIN + TargetSelect + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewInstallationTypeController + INSTALLER_PLUGIN + PackageSelection + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewInstallationController + INSTALLER_PLUGIN + Install + LIST_TITLE_KEY + InstallerSectionTitle + + + ICPRESENTATION_CHAPTER_VIEW_CONTROLLER_CLASS + ICPresentationViewSummaryController + INSTALLER_PLUGIN + Summary + LIST_TITLE_KEY + InstallerSectionTitle + + + INTRODUCTION + + LOCALIZATIONS + + + LICENSE + + LOCALIZATIONS + + MODE + 0 + + README + + LOCALIZATIONS + + + TITLE + + LOCALIZATIONS + + + LANGUAGE + English + VALUE + OCLP-Install + + + + + PROJECT_REQUIREMENTS + + LIST + + RESOURCES + + ROOT_VOLUME_ONLY + + + PROJECT_SETTINGS + + BUILD_FORMAT + 0 + BUILD_PATH + + PATH + ../../dist + PATH_TYPE + 1 + + EXCLUDED_FILES + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + .DS_Store + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove .DS_Store files + PROXY_TOOLTIP + Remove ".DS_Store" files created by the Finder. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + .pbdevelopment + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove .pbdevelopment files + PROXY_TOOLTIP + Remove ".pbdevelopment" files created by ProjectBuilder or Xcode. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + CVS + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .cvsignore + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + .cvspass + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + .svn + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .git + TYPE + 1 + + + REGULAR_EXPRESSION + + STRING + .gitignore + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Remove SCM metadata + PROXY_TOOLTIP + Remove helper files and folders used by the CVS, SVN or Git Source Code Management systems. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + classes.nib + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + designable.db + TYPE + 0 + + + REGULAR_EXPRESSION + + STRING + info.nib + TYPE + 0 + + + PROTECTED + + PROXY_NAME + Optimize nib files + PROXY_TOOLTIP + Remove "classes.nib", "info.nib" and "designable.nib" files within .nib bundles. + STATE + + + + PATTERNS_ARRAY + + + REGULAR_EXPRESSION + + STRING + Resources Disabled + TYPE + 1 + + + PROTECTED + + PROXY_NAME + Remove Resources Disabled folders + PROXY_TOOLTIP + Remove "Resources Disabled" folders. + STATE + + + + SEPARATOR + + + + NAME + OCLP-Install + PAYLOAD_ONLY + + TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING + + + + TYPE + 0 + VERSION + 2 + + diff --git a/payloads/InstallPackage/postinstall.sh b/payloads/InstallPackage/postinstall.sh new file mode 100755 index 000000000..f530f6e79 --- /dev/null +++ b/payloads/InstallPackage/postinstall.sh @@ -0,0 +1,6 @@ +#!/bin/sh +app_path="/Library/Application Support/Dortania/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher" +args="--patch_sys_vol" +"$app_path" "$args" &> "/Users/Shared/.OCLP-AutoPatcher-Log-$(date +"%Y_%m_%d_%I_%M_%p").txt" +log show --last boot > "/Users/Shared/.OCLP-System-Log-$(date +"%Y_%m_%d_%I_%M_%p").txt" +reboot \ No newline at end of file diff --git a/payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.0-DEBUG.zip b/payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.0-DEBUG.zip new file mode 100644 index 000000000..ff0cd0f17 Binary files /dev/null and b/payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.0-DEBUG.zip differ diff --git a/payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.0-RELEASE.zip b/payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.0-RELEASE.zip new file mode 100644 index 000000000..23cbded43 Binary files /dev/null and b/payloads/Kexts/Acidanthera/AutoPkgInstaller-v1.0.0-RELEASE.zip differ diff --git a/payloads/com.dortania.opencore-legacy-patcher.auto-patch.plist b/payloads/com.dortania.opencore-legacy-patcher.auto-patch.plist new file mode 100644 index 000000000..eb3d52631 --- /dev/null +++ b/payloads/com.dortania.opencore-legacy-patcher.auto-patch.plist @@ -0,0 +1,15 @@ + + + + + Label + com.dortania.opencore-legacy-patcher.auto-patch + ProgramArguments + + /Library/Application Support/Dortania/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher + --auto_patch + + RunAtLoad + + + diff --git a/resources/arguments.py b/resources/arguments.py index 649e025d9..ad46b79a6 100644 --- a/resources/arguments.py +++ b/resources/arguments.py @@ -1,6 +1,8 @@ import sys -from resources import defaults, build, utilities, validation, sys_patch +from resources import defaults, build, utilities, validation, sys_patch, sys_patch_auto from data import model_array +import threading +import time # Generic building args class arguments: @@ -101,7 +103,19 @@ If you plan to create the USB for another machine, please select the "Change Mod print("- Set Mojave/Catalina root patch configuration") settings.moj_cat_accel = True print("- Set System Volume patching") - sys_patch.PatchSysVolume(settings.custom_model or settings.computer.real_model, settings, None).start_patch() + + if "Library/InstallerSandboxes/" in str(settings.payload_path): + print("- Running from Installer Sandbox") + thread = threading.Thread(target=sys_patch.PatchSysVolume(settings.custom_model or settings.computer.real_model, settings, None).start_patch) + thread.start() + while thread.is_alive(): + utilities.block_os_updaters() + time.sleep(1) + else: + sys_patch.PatchSysVolume(settings.custom_model or settings.computer.real_model, settings, None).start_patch() elif self.args.unpatch_sys_vol: print("- Set System Volume unpatching") sys_patch.PatchSysVolume(settings.custom_model or settings.computer.real_model, settings, None).start_unpatch() + elif self.args.auto_patch: + print("- Set Auto patching") + sys_patch_auto.AutomaticSysPatch.start_auto_patch(settings) \ No newline at end of file diff --git a/resources/build.py b/resources/build.py index aae25da9a..adf5b293a 100644 --- a/resources/build.py +++ b/resources/build.py @@ -861,20 +861,20 @@ class BuildOpenCore: print("- Setting Vault configuration") self.config["Misc"]["Security"]["Vault"] = "Secure" self.get_efi_binary_by_path("OpenShell.efi", "Misc", "Tools")["Enabled"] = False - if self.constants.custom_sip_value: - print(f"- Setting SIP value to: {self.constants.custom_sip_value}") - self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["csr-active-config"] = utilities.string_to_hex(self.constants.custom_sip_value.lstrip("0x")) - # Work-around 12.3 bug where Electron apps no longer launch with SIP lowered - # Unknown whether this is intended behavior or not, revisit with 12.4 - print("- Adding ipc_control_port_options=0 to boot-args") - self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " ipc_control_port_options=0" - elif self.constants.sip_status is False: - print("- Set SIP to allow Root Volume patching") - self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["csr-active-config"] = binascii.unhexlify("02080000") + if self.constants.sip_status is False or self.constants.custom_sip_value: # Work-around 12.3 bug where Electron apps no longer launch with SIP lowered # Unknown whether this is intended behavior or not, revisit with 12.4 print("- Adding ipc_control_port_options=0 to boot-args") self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " ipc_control_port_options=0" + # Adds AutoPkgInstaller for Automatic OpenCore-Patcher installation + self.enable_kext("AutoPkgInstaller.kext", self.constants.autopkg_version, self.constants.autopkg_path) + if self.constants.custom_sip_value: + print(f"- Setting SIP value to: {self.constants.custom_sip_value}") + self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["csr-active-config"] = utilities.string_to_hex(self.constants.custom_sip_value.lstrip("0x")) + elif self.constants.sip_status is False: + print("- Set SIP to allow Root Volume patching") + self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["csr-active-config"] = binascii.unhexlify("02080000") + # if self.constants.amfi_status is False: # print("- Disabling AMFI") # self.config["NVRAM"]["Add"]["7C436110-AB2A-4BBB-A880-FE41995C9F82"]["boot-args"] += " amfi_get_out_of_my_way=1" diff --git a/resources/constants.py b/resources/constants.py index e7ebfc5ec..46dfb4bd4 100644 --- a/resources/constants.py +++ b/resources/constants.py @@ -23,6 +23,8 @@ class Constants: self.repo_link = "https://github.com/dortania/OpenCore-Legacy-Patcher" self.repo_link_latest = f"{self.repo_link}/releases/tag/{self.patcher_version}" self.copyright_date = "Copyright © 2020-2022 Dortania" + self.installer_pkg_url = f"{self.repo_link_latest}/OCLP-Install.pkg.zip" + self.installer_pkg_url_nightly = "http://nightly.link/dortania/OpenCore-Legacy-Patcher/workflows/build-app-wxpython-offline/main/OCLP-Install.pkg.zip" # OpenCore Versioning # https://github.com/acidanthera/OpenCorePkg @@ -44,6 +46,7 @@ class Constants: self.cpufriend_version = "1.2.5" # CPUFriend self.bluetool_version = "2.6.1" # BlueToolFixup (BrcmPatchRAM) self.cslvfixup_version = "2.6.1" # CSLVFixup + self.autopkg_version = "1.0.0" # AutoPkgInstaller ## Apple ## https://www.apple.com @@ -211,6 +214,11 @@ class Constants: @property def payload_mnt1_path(self): return self.payload_path / Path("mnt1") + + # Launch Agent + @property + def auto_patch_launch_agent_path(self): + return self.payload_path / Path("com.dortania.opencore-legacy-patcher.auto-patch.plist") # ACPI @property @@ -378,6 +386,10 @@ class Constants: @property def cslvfixup_path(self): return self.payload_kexts_path / Path(f"Acidanthera/CSLVFixup-v{self.cslvfixup_version}.zip") + + @property + def autopkg_path(self): + return self.payload_kexts_path / Path(f"Acidanthera/AutoPkgInstaller-v{self.autopkg_version}-{self.kext_variant}.zip") @property def innie_path(self): @@ -550,6 +562,14 @@ class Constants: def gui_path(self): return self.payload_path / Path("Icon/Resources.zip") + @property + def installer_pkg_path(self): + return self.payload_path / Path("OCLP-Install.pkg") + + @property + def installer_pkg_zip_path(self): + return self.payload_path / Path("OCLP-Install.pkg.zip") + # Apple Payloads Paths @property diff --git a/resources/sys_patch.py b/resources/sys_patch.py index ecc09e82e..5eb1afe02 100644 --- a/resources/sys_patch.py +++ b/resources/sys_patch.py @@ -10,7 +10,6 @@ import shutil import subprocess import zipfile from pathlib import Path -import sys from resources import constants, utilities, generate_smbios, sys_patch_download, sys_patch_detect from data import sip_data, sys_patch_data, os_data @@ -242,12 +241,16 @@ class PatchSysVolume: else: result = utilities.elevated(["kextcache", "-i", f"{self.mount_location}/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - # kextcache always returns 0, even if it fails - # Check the output for 'KernelCache ID' to see if the cache was successfully rebuilt + # kextcache notes: + # - kextcache always returns 0, even if it fails + # - Check the output for 'KernelCache ID' to see if the cache was successfully rebuilt + # kmutil notes: + # - will return 71 on failure to build KCs + # - will return -10 if the volume is missing (ie. unmounted by another process) if result.returncode != 0 or (self.constants.detected_os < os_data.os_data.catalina and "KernelCache ID" not in result.stdout.decode()): self.success_status = False print("- Unable to build new kernel cache") - print("\nReason for Patch Failure:") + print(f"\nReason for Patch Failure({result.returncode}):") print(result.stdout.decode()) print("") print("\nPlease reboot the machine to avoid potential issues rerunning the patcher") @@ -306,6 +309,58 @@ class PatchSysVolume: utilities.process_status(utilities.elevated(["chmod", "-Rf", "755", f"{self.mount_extensions}/{add_current_kext}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) utilities.process_status(utilities.elevated(["chown", "-Rf", "root:wheel", f"{self.mount_extensions}/{add_current_kext}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + def install_auto_patcher_launch_agent(self): + # Installs the following: + # - OpenCore-Patcher.app in /Library/Application Support/Dortania/ + # - com.dortania.opencore-legacy-patcher.auto-patch.plist in /Library/LaunchAgents/ + if self.constants.launcher_script is None: + # Verify our binary isn't located in '/Library/Application Support/Dortania/' + # As we'd simply be duplicating ourselves + if not self.constants.launcher_binary.startswith("/Library/Application Support/Dortania/"): + print("- Installing Auto Patcher Launch Agent") + + if not Path("Library/Application Support/Dortania").exists(): + print("- Creating /Library/Application Support/Dortania/") + utilities.process_status(utilities.elevated(["mkdir", "-p", "/Library/Application Support/Dortania"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + + print("- Copying OpenCore Patcher to /Library/Application Support/Dortania/") + if Path("/Library/Application Support/Dortania/OpenCore-Patcher.app").exists(): + print("- Deleting existing OpenCore-Patcher") + utilities.process_status(utilities.elevated(["rm", "-R", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + + # Strip everything after OpenCore-Patcher.app + path = str(self.constants.launcher_binary).split("/Contents/MacOS/OpenCore-Patcher")[0] + print(f"- Copying {path} to /Library/Application Support/Dortania/") + utilities.process_status(utilities.elevated(["cp", "-R", path, "/Library/Application Support/Dortania/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + + if not Path("/Library/Application Support/Dortania/OpenCore-Patcher.app").exists(): + # Sometimes the binary the user launches maye have a suffix (ie. OpenCore-Patcher 3.app) + # We'll want to rename it to OpenCore-Patcher.app + path = path.split("/")[-1] + print(f"- Renaming {path} to OpenCore-Patcher.app") + utilities.process_status(utilities.elevated(["mv", f"/Library/Application Support/Dortania/{path}", "/Library/Application Support/Dortania/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + + # Copy over our launch agent + print("- Copying auto-patch.plist Launch Agent to /Library/LaunchAgents/") + if Path("/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist").exists(): + print("- Deleting existing auto-patch.plist") + utilities.process_status(utilities.elevated(["rm", "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + utilities.process_status(utilities.elevated(["cp", self.constants.auto_patch_launch_agent_path, "/Library/LaunchAgents/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + + # Set the permissions on the com.dortania.opencore-legacy-patcher.auto-patch.plist + print("- Setting permissions on auto-patch.plist") + utilities.process_status(utilities.elevated(["chmod", "644", "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + utilities.process_status(utilities.elevated(["chown", "root:wheel", "/Library/LaunchAgents/com.dortania.opencore-legacy-patcher.auto-patch.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + + # Making app alias + # Simply an easy way for users to notice the app + # If there's already an alias or exiting app, skip + if not Path("/Applications/OpenCore-Patcher.app").exists(): + print("- Making app alias") + utilities.process_status(utilities.elevated(["ln", "-s", "/Library/Application Support/Dortania/OpenCore-Patcher.app", "/Applications/OpenCore-Patcher.app"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)) + else: + print("- Skipping Auto Patcher Launch Agent, not supported when running from source") + def clean_skylight_plugins(self): if (Path(self.mount_application_support) / Path("SkyLightPlugins/")).exists(): print("- Found SkylightPlugins folder, removing") @@ -649,6 +704,9 @@ class PatchSysVolume: print("- Installing Legacy Mux Brightness support") self.add_legacy_mux_patch() + if self.constants.wxpython_variant is True and self.constants.detected_os >= os_data.os_data.big_sur: + self.install_auto_patcher_launch_agent() + if self.validate is False: self.rebuild_snapshot() @@ -667,7 +725,7 @@ class PatchSysVolume: return output def download_files(self): - if self.constants.gui_mode is False: + if self.constants.gui_mode is False or "Library/InstallerSandboxes/" in str(self.constants.payload_path): download_result, os_ver, link = sys_patch_download.grab_patcher_support_pkg(self.constants).download_files() else: download_result = True diff --git a/resources/sys_patch_auto.py b/resources/sys_patch_auto.py new file mode 100644 index 000000000..ceb68cae7 --- /dev/null +++ b/resources/sys_patch_auto.py @@ -0,0 +1,99 @@ +# Auto Patching's main purpose is to try and tell the user they're missing root patches +# New users may not realize OS updates remove our patches, so we try and run when nessasary +# Conditions for running: +# - Verify running GUI (TUI users can write their own scripts) +# - Verify the Snapshot Seal is in tact (if not, assume user is running patches) +# - Verify this model needs patching (if not, assume user upgraded hardware and OCLP was not removed) +# - Verify there are no updates for OCLP (ensure we have the latest patch sets) +# If all these tests pass, start Root Patcher +# Copyright (C) 2022, Mykola Grymalyuk + +import subprocess +import webbrowser +from resources import sys_patch_detect, utilities, sys_patch_detect, updates + +class AutomaticSysPatch: + def start_auto_patch(settings): + print("- Starting Automatic Patching") + if settings.wxpython_variant is True: + if utilities.check_seal() is True: + print("- Detected Snapshot seal in tact, detecting patches") + patches = sys_patch_detect.detect_root_patch(settings.computer.real_model, settings).detect_patch_set() + if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches): + patches = [] + if patches: + print("- Detected applicable patches, determining whether possible to patch") + if patches["Validation: Patching Possible"] is True: + print("- Determined patching is possible, checking for OCLP updates") + patch_string = "" + for patch in patches: + if patches[patch] is True and not patch.startswith("Settings") and not patch.startswith("Validation"): + patch_string += f"- {patch}\n" + # Check for updates + dict = updates.check_binary_updates(settings).check_binary_updates() + if not dict: + print("- No new binaries found on Github, proceeding with patching") + if settings.launcher_script is None: + args_string = f"'{settings.launcher_binary}' --gui_patch" + else: + args_string = f"{settings.launcher_binary} {settings.launcher_script} --gui_patch" + + warning_str = "" + if utilities.verify_network_connection("https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest") is False: + warning_str = f"""\n\nWARNING: We're unable to verify whether there are any new releases of OpenCore Legacy Patcher on Github. Be aware that you may be using an outdated version for this OS. If you're unsure, verify on Github that OpenCore Legacy Patcher {settings.patcher_version} is the latest official release""" + + args = [ + "osascript", + "-e", + f"""display dialog "OpenCore Legacy Patcher has detected you're running without Root Patches, and would like to install them.\n\nmacOS wipes all root patches during OS installs and updates, so they need to be reinstalled.\n\nFollowing Patches have been detected for your system: \n{patch_string}\nWould you like to apply these patches?{warning_str}" """ + f'with icon POSIX file "{settings.app_icon_path}"', + ] + output = subprocess.run( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) + if output.returncode == 0: + args = [ + "osascript", + "-e", + f'''do shell script "{args_string}"''' + f' with prompt "OpenCore Legacy Patcher would like to patch your root volume"' + " with administrator privileges" + " without altering line endings" + ] + subprocess.run( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) + else: + for entry in dict: + version = dict[entry]["Version"] + github_link = dict[entry]["Github Link"] + print(f"- Found new version: {version}") + + # launch oascript 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: {settings.patcher_version}\nRemote Version: {version}" """ + f'with icon POSIX file "{settings.app_icon_path}"', + ] + output = subprocess.run( + args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) + if output.returncode == 0: + webbrowser.open(github_link) + else: + print("- Cannot run patching") + else: + print("- No patches detected") + else: + print("- Detected Snapshot seal not in tact, skipping") + else: + print("- Auto Patch option is not supported on TUI, please use GUI") \ No newline at end of file diff --git a/resources/utilities.py b/resources/utilities.py index 81a88fa4f..70740c85e 100644 --- a/resources/utilities.py +++ b/resources/utilities.py @@ -11,7 +11,7 @@ import os import binascii import argparse from ctypes import CDLL, c_uint, byref -import sys, time +import time try: import requests @@ -397,6 +397,10 @@ def download_file(link, location, is_gui=None, verify_checksum=False): print(link) return None +def grab_mount_point_from_disk(disk): + data = plistlib.loads(subprocess.run(f"diskutil info -plist {disk}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode()) + return data["MountPoint"] + def monitor_disk_output(disk): # Returns MB written on drive output = subprocess.check_output(["iostat", "-Id", disk]) @@ -406,6 +410,39 @@ def monitor_disk_output(disk): output = output[-2] return output +def validate_link(link): + # Check if link is 404 + try: + response = requests.head(link, timeout=5) + if response.status_code == 404: + return False + else: + return True + except (requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.ConnectionError, requests.exceptions.HTTPError): + return False + +def block_os_updaters(): + # Disables any processes that would be likely to mess with + # the root volume while we're working with it. + bad_processes = [ + "softwareupdate", + "SoftwareUpdate", + "Software Update", + "MobileSoftwareUpdate", + ] + output = subprocess.check_output(["ps", "-ax"]) + lines = output.splitlines() + for line in lines: + entry = line.split() + pid = entry[0].decode() + current_process = entry[3].decode() + for bad_process in bad_processes: + if bad_process in current_process: + if pid != "": + print(f"- Killing Process: {pid} - {current_process.split('/')[-1]}") + subprocess.run(["kill", "-9", pid]) + break + def check_boot_mode(): # Check whether we're in Safe Mode or not sys_plist = plistlib.loads(subprocess.run(["system_profiler", "SPSoftwareDataType"], stdout=subprocess.PIPE).stdout) @@ -455,8 +492,10 @@ def check_cli_args(): # GUI args parser.add_argument("--gui_patch", help="Starts GUI in Root Patcher", action="store_true", required=False) parser.add_argument("--gui_unpatch", help="Starts GUI in Root Unpatcher", action="store_true", required=False) + parser.add_argument("--auto_patch", help="Check if patches are needed and prompt user", action="store_true", required=False) + args = parser.parse_args() - if not (args.build or args.patch_sys_vol or args.unpatch_sys_vol or args.validate): + if not (args.build or args.patch_sys_vol or args.unpatch_sys_vol or args.validate or args.auto_patch): return None else: return args