mirror of
https://github.com/dortania/OpenCore-Legacy-Patcher.git
synced 2026-06-21 06:30:52 +10:00
Merge branch 'main' into pullrequests/Ausdauersportler/main
This commit is contained in:
@@ -95,7 +95,6 @@ body:
|
|||||||
description: What variant of our software are you running?
|
description: What variant of our software are you running?
|
||||||
options:
|
options:
|
||||||
- GUI (Graphical User Interface)
|
- GUI (Graphical User Interface)
|
||||||
- TUI (Text User Interface)
|
|
||||||
- CLI (Command Line Interface)
|
- CLI (Command Line Interface)
|
||||||
- Other/Non-Applicable
|
- Other/Non-Applicable
|
||||||
validations:
|
validations:
|
||||||
|
|||||||
@@ -10,16 +10,21 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
name: Build wxPython
|
name: Build wxPython
|
||||||
runs-on: x86_64_mojave
|
runs-on: x86_64_mojave
|
||||||
|
if: github.repository_owner == 'dortania'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
branch: ${{ github.ref }}
|
branch: ${{ github.ref }}
|
||||||
commiturl: ${{ github.event.head_commit.url }}${{ github.event.release.html_url }}
|
commiturl: ${{ github.event.head_commit.url }}${{ github.event.release.html_url }}
|
||||||
commitdate: ${{ github.event.head_commit.timestamp }}${{ github.event.release.published_at }}
|
commitdate: ${{ github.event.head_commit.timestamp }}${{ github.event.release.published_at }}
|
||||||
|
MAC_NOTARIZATION_USERNAME: ${{ secrets.MAC_NOTARIZATION_USERNAME }}
|
||||||
|
MAC_NOTARIZATION_PASSWORD: ${{ secrets.MAC_NOTARIZATION_PASSWORD }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- run: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 Build-Binary.command --reset_binaries --branch "${{ env.branch }}" --commit "${{ env.commiturl }}" --commit_date "${{ env.commitdate }}"
|
- run: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 Build-Binary.command --reset_binaries --branch "${{ env.branch }}" --commit "${{ env.commiturl }}" --commit_date "${{ env.commitdate }}"
|
||||||
- run: 'codesign -s "Developer ID Application: Mykola Grymalyuk (S74BDJXQMD)" -v --force --deep --timestamp --entitlements ./payloads/entitlements.plist -o runtime "dist/OpenCore-Patcher.app"'
|
- run: 'codesign -s "Developer ID Application: Mykola Grymalyuk (S74BDJXQMD)" -v --force --deep --timestamp --entitlements ./payloads/entitlements.plist -o runtime "dist/OpenCore-Patcher.app"'
|
||||||
- run: cd dist; ditto -c -k --sequesterRsrc --keepParent OpenCore-Patcher.app ../OpenCore-Patcher-wxPython.app.zip
|
- run: cd dist; ditto -c -k --sequesterRsrc --keepParent OpenCore-Patcher.app ../OpenCore-Patcher-wxPython.app.zip
|
||||||
- run: ./../sign-wxpython.sh
|
- run: xcrun altool --notarize-app --primary-bundle-id "com.dortania.opencore-legacy-patcher" --username "${{ env.MAC_NOTARIZATION_USERNAME }}" --password "${{ env.MAC_NOTARIZATION_PASSWORD }}" --file OpenCore-Patcher-wxPython.app.zip
|
||||||
- run: packagesbuild ./payloads/InstallPackage/AutoPkg-Assets-Setup.pkgproj
|
- run: packagesbuild ./payloads/InstallPackage/AutoPkg-Assets-Setup.pkgproj
|
||||||
- run: mv ./OpenCore-Patcher-wxPython.app.zip ./OpenCore-Patcher-GUI.app.zip
|
- run: mv ./OpenCore-Patcher-wxPython.app.zip ./OpenCore-Patcher-GUI.app.zip
|
||||||
- name: Upload App to Artifacts
|
- name: Upload App to Artifacts
|
||||||
@@ -27,11 +32,13 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: OpenCore-Patcher.app (GUI)
|
name: OpenCore-Patcher.app (GUI)
|
||||||
path: OpenCore-Patcher-GUI.app.zip
|
path: OpenCore-Patcher-GUI.app.zip
|
||||||
|
|
||||||
- name: Upload Package to Artifacts
|
- name: Upload Package to Artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: AutoPkg-Assets.pkg
|
name: AutoPkg-Assets.pkg
|
||||||
path: ./dist/AutoPkg-Assets.pkg
|
path: ./dist/AutoPkg-Assets.pkg
|
||||||
|
|
||||||
- name: Upload Binary to Release
|
- name: Upload Binary to Release
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
uses: svenstaro/upload-release-action@e74ff71f7d8a4c4745b560a485cc5fdb9b5b999d
|
uses: svenstaro/upload-release-action@e74ff71f7d8a4c4745b560a485cc5fdb9b5b999d
|
||||||
@@ -40,6 +47,7 @@ jobs:
|
|||||||
file: OpenCore-Patcher-GUI.app.zip
|
file: OpenCore-Patcher-GUI.app.zip
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
file_glob: true
|
file_glob: true
|
||||||
|
|
||||||
- name: Upload Package to Release
|
- name: Upload Package to Release
|
||||||
if: github.event_name == 'release'
|
if: github.event_name == 'release'
|
||||||
uses: svenstaro/upload-release-action@e74ff71f7d8a4c4745b560a485cc5fdb9b5b999d
|
uses: svenstaro/upload-release-action@e74ff71f7d8a4c4745b560a485cc5fdb9b5b999d
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
name: CI - Build TUI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
workflow_dispatch:
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
name: Build TUI
|
|
||||||
runs-on: x86_64_mojave
|
|
||||||
env:
|
|
||||||
branch: ${{ github.ref }}
|
|
||||||
commiturl: ${{ github.event.head_commit.url }}${{ github.event.release.html_url }}
|
|
||||||
commitdate: ${{ github.event.head_commit.timestamp }}${{ github.event.release.published_at }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- run: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 Build-Binary.command --build_tui --reset_binaries --branch "${{ env.branch }}" --commit "${{ env.commiturl }}" --commit_date "${{ env.commitdate }}"
|
|
||||||
- run: 'codesign -s "Developer ID Application: Mykola Grymalyuk (S74BDJXQMD)" -v --force --deep --timestamp --entitlements ./payloads/entitlements.plist -o runtime "dist/OpenCore-Patcher.app"'
|
|
||||||
- run: cd dist; zip -r ../OpenCore-Patcher-TUI.app.zip OpenCore-Patcher.app
|
|
||||||
- run: ./../sign-tui.sh
|
|
||||||
- name: Upload App to Artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: OpenCore-Patcher-TUI.app
|
|
||||||
path: OpenCore-Patcher-TUI.app.zip
|
|
||||||
|
|
||||||
- name: Validate OpenCore
|
|
||||||
run: ./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher --validate
|
|
||||||
@@ -9,6 +9,7 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
name: Build Site and Deploy
|
name: Build Site and Deploy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: github.repository_owner == 'dortania'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
name: CI - Validation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
workflow_dispatch:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Validate
|
||||||
|
runs-on: x86_64_mojave
|
||||||
|
if: github.repository_owner == 'dortania'
|
||||||
|
env:
|
||||||
|
branch: ${{ github.ref }}
|
||||||
|
commiturl: ${{ github.event.head_commit.url }}${{ github.event.release.html_url }}
|
||||||
|
commitdate: ${{ github.event.head_commit.timestamp }}${{ github.event.release.published_at }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- run: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 OpenCore-Patcher-GUI.command --validate
|
||||||
+166
-98
@@ -1,14 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# This script's main purpose is to handle the following:
|
# Generate stand alone application for OpenCore-Patcher
|
||||||
# - Download PatcherSupportPkg resources
|
# Copyright (C) 2022-2023 - Mykola Grymalyuk
|
||||||
# - Convert payloads directory into DMG (GUI only)
|
|
||||||
# - Build Binary via Pyinstaller
|
|
||||||
# - Add Launcher.sh (TUI only)
|
|
||||||
# - Patch 'LC_VERSION_MIN_MACOSX' to OS X 10.10
|
|
||||||
# - Add commit data to Info.plist
|
|
||||||
|
|
||||||
# Copyright (C) 2022 - Mykola Grymalyuk
|
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import time
|
import time
|
||||||
@@ -21,26 +14,49 @@ import sys
|
|||||||
|
|
||||||
from resources import constants
|
from resources import constants
|
||||||
|
|
||||||
class create_binary:
|
|
||||||
|
class CreateBinary:
|
||||||
|
"""
|
||||||
|
Library for creating OpenCore-Patcher application
|
||||||
|
|
||||||
|
This script's main purpose is to handle the following:
|
||||||
|
- Download external dependancies (ex. PatcherSupportPkg)
|
||||||
|
- Convert payloads directory into DMG
|
||||||
|
- Build Binary via Pyinstaller
|
||||||
|
- Patch 'LC_VERSION_MIN_MACOSX' to OS X 10.10
|
||||||
|
- Add commit data to Info.plist
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
start = time.time()
|
start = time.time()
|
||||||
print("- Starting build script")
|
print("- Starting build script")
|
||||||
self.set_cwd()
|
|
||||||
self.args = self.parse_arguments()
|
|
||||||
|
|
||||||
self.preflight_processes()
|
self.args = self._parse_arguments()
|
||||||
self.build_binary()
|
|
||||||
self.postflight_processes()
|
self._set_cwd()
|
||||||
|
|
||||||
|
self._preflight_processes()
|
||||||
|
self._build_binary()
|
||||||
|
self._postflight_processes()
|
||||||
print(f"- Build script completed in {str(round(time.time() - start, 2))} seconds")
|
print(f"- Build script completed in {str(round(time.time() - start, 2))} seconds")
|
||||||
|
|
||||||
def set_cwd(self):
|
|
||||||
|
def _set_cwd(self):
|
||||||
|
"""
|
||||||
|
Initialize current working directory to parent of this script
|
||||||
|
"""
|
||||||
|
|
||||||
os.chdir(Path(__file__).resolve().parent)
|
os.chdir(Path(__file__).resolve().parent)
|
||||||
print(f"- Current Working Directory: \n\t{os.getcwd()}")
|
print(f"- Current Working Directory: \n\t{os.getcwd()}")
|
||||||
|
|
||||||
def parse_arguments(self):
|
|
||||||
|
def _parse_arguments(self):
|
||||||
|
"""
|
||||||
|
Parse arguments passed to script
|
||||||
|
"""
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Builds OpenCore-Patcher binary')
|
parser = argparse.ArgumentParser(description='Builds OpenCore-Patcher binary')
|
||||||
parser.add_argument('--build_tui', action='store_true', help='Builds TUI binary, if omitted GUI binary is built')
|
|
||||||
parser.add_argument('--branch', type=str, help='Git branch name')
|
parser.add_argument('--branch', type=str, help='Git branch name')
|
||||||
parser.add_argument('--commit', type=str, help='Git commit URL')
|
parser.add_argument('--commit', type=str, help='Git commit URL')
|
||||||
parser.add_argument('--commit_date', type=str, help='Git commit date')
|
parser.add_argument('--commit_date', type=str, help='Git commit date')
|
||||||
@@ -48,7 +64,12 @@ class create_binary:
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def setup_pathing(self):
|
|
||||||
|
def _setup_pathing(self):
|
||||||
|
"""
|
||||||
|
Initialize pathing for pyinstaller
|
||||||
|
"""
|
||||||
|
|
||||||
python_path = sys.executable
|
python_path = sys.executable
|
||||||
python_binary = python_path.split("/")[-1]
|
python_binary = python_path.split("/")[-1]
|
||||||
python_bin_dir = python_path.strip(python_binary)
|
python_bin_dir = python_path.strip(python_binary)
|
||||||
@@ -70,25 +91,36 @@ class create_binary:
|
|||||||
|
|
||||||
self.pyinstaller_path = pyinstaller_path
|
self.pyinstaller_path = pyinstaller_path
|
||||||
|
|
||||||
def preflight_processes(self):
|
|
||||||
|
def _preflight_processes(self):
|
||||||
|
"""
|
||||||
|
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()
|
||||||
if not self.args.build_tui:
|
self._generate_payloads_dmg()
|
||||||
# payloads.dmg is only needed for GUI builds
|
|
||||||
self.generate_payloads_dmg()
|
|
||||||
|
def _postflight_processes(self):
|
||||||
|
"""
|
||||||
|
Start postflight processes
|
||||||
|
"""
|
||||||
|
|
||||||
def postflight_processes(self):
|
|
||||||
print("- Starting postflight processes")
|
print("- Starting postflight processes")
|
||||||
if self.args.build_tui:
|
self._patch_load_command()
|
||||||
self.move_launcher()
|
self._add_commit_data()
|
||||||
self.patch_load_command()
|
self._post_flight_cleanup()
|
||||||
self.add_commit_data()
|
self._mini_validate()
|
||||||
self.post_flight_cleanup()
|
|
||||||
self.mini_validate()
|
|
||||||
|
def _build_binary(self):
|
||||||
|
"""
|
||||||
|
Build binary via pyinstaller
|
||||||
|
"""
|
||||||
|
|
||||||
def build_binary(self):
|
|
||||||
if Path(f"./dist/OpenCore-Patcher.app").exists():
|
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(
|
||||||
@@ -101,12 +133,8 @@ class create_binary:
|
|||||||
raise Exception("Remove failed")
|
raise Exception("Remove failed")
|
||||||
|
|
||||||
|
|
||||||
if self.args.build_tui:
|
print("- Building GUI binary...")
|
||||||
print("- Building TUI binary...")
|
build_args = [self.pyinstaller_path, "./OpenCore-Patcher-GUI.spec", "--noconfirm"]
|
||||||
build_args = [self.pyinstaller_path, "./OpenCore-Patcher.spec", "--noconfirm"]
|
|
||||||
else:
|
|
||||||
print("- Building GUI binary...")
|
|
||||||
build_args = [self.pyinstaller_path, "./OpenCore-Patcher-GUI.spec", "--noconfirm"]
|
|
||||||
|
|
||||||
build_result = subprocess.run(build_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
build_result = subprocess.run(build_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
if build_result.returncode != 0:
|
if build_result.returncode != 0:
|
||||||
@@ -114,27 +142,52 @@ class create_binary:
|
|||||||
print(build_result.stderr.decode('utf-8'))
|
print(build_result.stderr.decode('utf-8'))
|
||||||
raise Exception("Build failed")
|
raise Exception("Build failed")
|
||||||
|
|
||||||
def delete_extra_binaries(self):
|
|
||||||
delete_files = [
|
def _delete_extra_binaries(self):
|
||||||
"AutoPkg-Assets.pkg",
|
"""
|
||||||
"AutoPkg-Assets.pkg.zip",
|
Delete extra binaries from payloads directory
|
||||||
"InstallAssistant.pkg",
|
"""
|
||||||
"InstallAssistant.pkg.integrityDataV1",
|
|
||||||
"KDK.dmg",
|
whitelist_folders = [
|
||||||
|
"ACPI",
|
||||||
|
"Config",
|
||||||
|
"Drivers",
|
||||||
|
"Icon",
|
||||||
|
"InstallPackage",
|
||||||
|
"Kexts",
|
||||||
|
"OpenCore",
|
||||||
|
"Tools",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
whitelist_files = [
|
||||||
|
"com.dortania.opencore-legacy-patcher.auto-patch.plist",
|
||||||
|
"entitlements.plist",
|
||||||
|
"launcher.sh",
|
||||||
|
"OC-Patcher-TUI.icns",
|
||||||
|
"OC-Patcher.icns",
|
||||||
|
"Universal-Binaries.zip",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
print("- Deleting extra binaries...")
|
print("- Deleting extra binaries...")
|
||||||
for file in Path("payloads").glob(pattern="*"):
|
for file in Path("payloads").glob(pattern="*"):
|
||||||
if file.name in delete_files or file.name.startswith("OpenCore-Legacy-Patcher"):
|
if file.is_dir():
|
||||||
|
if file.name in whitelist_folders:
|
||||||
|
continue
|
||||||
print(f" - Deleting {file.name}")
|
print(f" - Deleting {file.name}")
|
||||||
file.unlink()
|
|
||||||
elif (Path(file) / Path("Contents/Resources/createinstallmedia")).exists():
|
|
||||||
print(f" - Deleting {file}")
|
|
||||||
subprocess.run(["rm", "-rf", file])
|
|
||||||
elif Path(file).is_dir() and file.name == "Universal-Binaries":
|
|
||||||
print(f" - Deleting {file}")
|
|
||||||
subprocess.run(["rm", "-rf", file])
|
subprocess.run(["rm", "-rf", file])
|
||||||
|
else:
|
||||||
|
if file.name in whitelist_files:
|
||||||
|
continue
|
||||||
|
print(f" - Deleting {file.name}")
|
||||||
|
subprocess.run(["rm", "-f", file])
|
||||||
|
|
||||||
|
|
||||||
|
def _download_resources(self):
|
||||||
|
"""
|
||||||
|
Download required dependencies
|
||||||
|
"""
|
||||||
|
|
||||||
def download_resources(self):
|
|
||||||
patcher_support_pkg_version = constants.Constants().patcher_support_pkg_version
|
patcher_support_pkg_version = constants.Constants().patcher_support_pkg_version
|
||||||
required_resources = [
|
required_resources = [
|
||||||
"Universal-Binaries.zip"
|
"Universal-Binaries.zip"
|
||||||
@@ -181,21 +234,29 @@ class create_binary:
|
|||||||
print(mv_output.stderr.decode('utf-8'))
|
print(mv_output.stderr.decode('utf-8'))
|
||||||
raise Exception("Move failed")
|
raise Exception("Move failed")
|
||||||
|
|
||||||
def generate_payloads_dmg(self):
|
|
||||||
|
def _generate_payloads_dmg(self):
|
||||||
|
"""
|
||||||
|
Generate disk image containing all payloads
|
||||||
|
Disk image will be password protected due to issues with
|
||||||
|
Apple's notarization system and inclusion of kernel extensions
|
||||||
|
"""
|
||||||
|
|
||||||
if Path("./payloads.dmg").exists():
|
if Path("./payloads.dmg").exists():
|
||||||
if self.args.reset_binaries:
|
if not self.args.reset_binaries:
|
||||||
print(" - Removing old payloads.dmg")
|
|
||||||
rm_output = subprocess.run(
|
|
||||||
["rm", "-rf", "./payloads.dmg"],
|
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
||||||
)
|
|
||||||
if rm_output.returncode != 0:
|
|
||||||
print("- Remove failed")
|
|
||||||
print(rm_output.stderr.decode('utf-8'))
|
|
||||||
raise Exception("Remove failed")
|
|
||||||
else:
|
|
||||||
print(" - payloads.dmg already exists, skipping creation")
|
print(" - payloads.dmg already exists, skipping creation")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
print(" - Removing old payloads.dmg")
|
||||||
|
rm_output = subprocess.run(
|
||||||
|
["rm", "-rf", "./payloads.dmg"],
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
if rm_output.returncode != 0:
|
||||||
|
print("- Remove failed")
|
||||||
|
print(rm_output.stderr.decode('utf-8'))
|
||||||
|
raise Exception("Remove failed")
|
||||||
|
|
||||||
print(" - Generating DMG...")
|
print(" - Generating DMG...")
|
||||||
dmg_output = subprocess.run([
|
dmg_output = subprocess.run([
|
||||||
'hdiutil', 'create', './payloads.dmg',
|
'hdiutil', 'create', './payloads.dmg',
|
||||||
@@ -213,7 +274,12 @@ class create_binary:
|
|||||||
|
|
||||||
print(" - DMG generation complete")
|
print(" - DMG generation complete")
|
||||||
|
|
||||||
def add_commit_data(self):
|
|
||||||
|
def _add_commit_data(self):
|
||||||
|
"""
|
||||||
|
Add commit data to Info.plist
|
||||||
|
"""
|
||||||
|
|
||||||
if not self.args.branch and not self.args.commit and not self.args.commit_date:
|
if not self.args.branch and not self.args.commit and not self.args.commit_date:
|
||||||
print(" - No commit data provided, adding source info")
|
print(" - No commit data provided, adding source info")
|
||||||
branch = "Built from source"
|
branch = "Built from source"
|
||||||
@@ -233,20 +299,25 @@ class create_binary:
|
|||||||
}
|
}
|
||||||
plistlib.dump(plist, Path(plist_path).open("wb"), sort_keys=True)
|
plistlib.dump(plist, Path(plist_path).open("wb"), sort_keys=True)
|
||||||
|
|
||||||
def patch_load_command(self):
|
|
||||||
# Patches LC_VERSION_MIN_MACOSX in Load Command to report 10.10
|
def _patch_load_command(self):
|
||||||
#
|
"""
|
||||||
# By default Pyinstaller will create binaries supporting 10.13+
|
Patch LC_VERSION_MIN_MACOSX in Load Command to report 10.10
|
||||||
# However this limitation is entirely arbitrary for our libraries
|
|
||||||
# and instead we're able to support 10.10 without issues.
|
By default Pyinstaller will create binaries supporting 10.13+
|
||||||
#
|
However this limitation is entirely arbitrary for our libraries
|
||||||
# To verify set version:
|
and instead we're able to support 10.10 without issues.
|
||||||
# otool -l ./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher
|
|
||||||
#
|
To verify set version:
|
||||||
# cmd LC_VERSION_MIN_MACOSX
|
otool -l ./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher
|
||||||
# cmdsize 16
|
|
||||||
# version 10.13
|
cmd LC_VERSION_MIN_MACOSX
|
||||||
# sdk 10.9
|
cmdsize 16
|
||||||
|
version 10.13
|
||||||
|
sdk 10.9
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
print(" - Patching LC_VERSION_MIN_MACOSX")
|
print(" - Patching LC_VERSION_MIN_MACOSX")
|
||||||
path = './dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher'
|
path = './dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher'
|
||||||
find = b'\x00\x0D\x0A\x00' # 10.13 (0xA0D)
|
find = b'\x00\x0D\x0A\x00' # 10.13 (0xA0D)
|
||||||
@@ -257,19 +328,12 @@ class create_binary:
|
|||||||
with open(path, 'wb') as f:
|
with open(path, 'wb') as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
|
|
||||||
def move_launcher(self):
|
|
||||||
print(" - Adding TUI launcher")
|
|
||||||
mv_output = subprocess.run(
|
|
||||||
["cp", "./payloads/launcher.sh", "./dist/OpenCore-Patcher.app/Contents/MacOS/Launcher"],
|
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
|
||||||
)
|
|
||||||
if mv_output.returncode != 0:
|
|
||||||
print(" - Move failed")
|
|
||||||
print(mv_output.stderr.decode('utf-8'))
|
|
||||||
raise Exception("Move failed")
|
|
||||||
|
|
||||||
def post_flight_cleanup(self):
|
def _post_flight_cleanup(self):
|
||||||
# Remove ./dist/OpenCore-Patcher
|
"""
|
||||||
|
Post flight cleanup
|
||||||
|
"""
|
||||||
|
|
||||||
path = "./dist/OpenCore-Patcher"
|
path = "./dist/OpenCore-Patcher"
|
||||||
print(f" - Removing {path}")
|
print(f" - Removing {path}")
|
||||||
rm_output = subprocess.run(
|
rm_output = subprocess.run(
|
||||||
@@ -281,9 +345,12 @@ class create_binary:
|
|||||||
print(rm_output.stderr.decode('utf-8'))
|
print(rm_output.stderr.decode('utf-8'))
|
||||||
raise Exception(f"Remove failed: {path}")
|
raise Exception(f"Remove failed: {path}")
|
||||||
|
|
||||||
def mini_validate(self):
|
|
||||||
# Ensure binary can start
|
def _mini_validate(self):
|
||||||
# Only build a single config, TUI CI will do in-depth validation
|
"""
|
||||||
|
Validate generated binary
|
||||||
|
"""
|
||||||
|
|
||||||
print(" - Validating binary")
|
print(" - Validating binary")
|
||||||
validate_output = subprocess.run(
|
validate_output = subprocess.run(
|
||||||
["./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher", "--build", "--model", "MacPro3,1"],
|
["./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher", "--build", "--model", "MacPro3,1"],
|
||||||
@@ -294,5 +361,6 @@ class create_binary:
|
|||||||
print(validate_output.stderr.decode('utf-8'))
|
print(validate_output.stderr.decode('utf-8'))
|
||||||
raise Exception("Validation failed")
|
raise Exception("Validation failed")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
create_binary()
|
CreateBinary()
|
||||||
+23
-2
@@ -7,11 +7,27 @@
|
|||||||
- Ensure `Moraea_BlurBeta` is set on non-Metal systems
|
- Ensure `Moraea_BlurBeta` is set on non-Metal systems
|
||||||
- Implement proper Root Unpatching verification in GUI
|
- Implement proper Root Unpatching verification in GUI
|
||||||
- Removes arbitrary patch requirements used against unpatching (ex. network connection)
|
- Removes arbitrary patch requirements used against unpatching (ex. network connection)
|
||||||
|
- Implement Kernel Debug Kit installation during OS installs
|
||||||
|
- Avoids network requirement for first time installs
|
||||||
|
- Paired along side AutoPkgInstaller
|
||||||
|
- Implement Kernel Debug Kit backup system
|
||||||
|
- Allows for easy restoration of KDKs if OS updates corrupted installed KDKs
|
||||||
|
- Update Wireless binaries
|
||||||
|
- Fixed WiFi preferences crash with legacy wifi patches
|
||||||
|
- Update non-Metal Binaries
|
||||||
|
- Improved menubar blur saturation
|
||||||
|
- Fixed System Settings hover effects, including Bluetooth connect button
|
||||||
|
- Add Books hacks (reimplement cover image generation, disable broken page curl animation)
|
||||||
|
- Fixed unresponsive buttons
|
||||||
|
- Implement Hardware Encoding support for AMD Polaris and Vega GPUs
|
||||||
|
- Applicable for pre-Haswell Macs on macOS Ventura
|
||||||
|
- Resolves DRM playback issues on Netflix, Disney+, etc.
|
||||||
- Backend Changes:
|
- Backend Changes:
|
||||||
- Refactored kdk_handler.py
|
- Refactored kdk_handler.py
|
||||||
- Prioritizes KdkSupportPkg repository for downloads
|
- Prioritizes KdkSupportPkg repository for downloads
|
||||||
- Skips calls to Apple's now defunct Developer Portal API
|
- Skips calls to Apple's now defunct Developer Portal API
|
||||||
- Support local loose matching when no network connection is available
|
- Support local loose matching when no network connection is available
|
||||||
|
- Implement pkg receipt verification to validate integrity of KDKs
|
||||||
- Implemented logging framework usage for more reliable logging
|
- Implemented logging framework usage for more reliable logging
|
||||||
- Logs are stored under `~/OpenCore-Patcher.log`
|
- Logs are stored under `~/OpenCore-Patcher.log`
|
||||||
- Subsequent runs are appended to the log, allowing for easy debugging
|
- Subsequent runs are appended to the log, allowing for easy debugging
|
||||||
@@ -19,7 +35,11 @@
|
|||||||
- Allows for more reliable network calls and downloads
|
- Allows for more reliable network calls and downloads
|
||||||
- Better supports network timeouts and disconnects
|
- Better supports network timeouts and disconnects
|
||||||
- Dramatically less noise in console during downloads
|
- Dramatically less noise in console during downloads
|
||||||
- Removed unused sys_patch_downloader.py module
|
- Implemented new macOS Installer handler
|
||||||
|
- Removed unused modules:
|
||||||
|
- sys_patch_downloader.py
|
||||||
|
- run.py
|
||||||
|
- TUI modules
|
||||||
- Build Server Changes:
|
- Build Server Changes:
|
||||||
- Upgrade Python backend to 3.10.9
|
- Upgrade Python backend to 3.10.9
|
||||||
- Upgrade Python modules:
|
- Upgrade Python modules:
|
||||||
@@ -29,7 +49,8 @@
|
|||||||
- pyinstaller - 5.7.0
|
- pyinstaller - 5.7.0
|
||||||
- packaging - 23.0
|
- packaging - 23.0
|
||||||
- Increment Binaries:
|
- Increment Binaries:
|
||||||
- PatcherSupportPkg 0.8.3 - release
|
- PatcherSupportPkg 0.8.4 - release
|
||||||
|
- AutoPkgInstaller 1.0.2 - release
|
||||||
|
|
||||||
## 0.6.1
|
## 0.6.1
|
||||||
- Avoid usage of KDKlessWorkaround on hardware not requiring it
|
- Avoid usage of KDKlessWorkaround on hardware not requiring it
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
from resources import main
|
from resources import main
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main.OpenCoreLegacyPatcher(True)
|
main.OpenCoreLegacyPatcher()
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
|
|
||||||
from resources import main
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main.OpenCoreLegacyPatcher()
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# -*- mode: python ; coding: utf-8 -*-
|
|
||||||
import sys, os
|
|
||||||
sys.path.append(os.path.abspath(os.getcwd()))
|
|
||||||
from resources import constants
|
|
||||||
block_cipher = None
|
|
||||||
|
|
||||||
|
|
||||||
a = Analysis(['OpenCore-Patcher.command'],
|
|
||||||
pathex=['resources', 'data'],
|
|
||||||
binaries=[],
|
|
||||||
datas=[('payloads', 'payloads')],
|
|
||||||
hiddenimports=[],
|
|
||||||
hookspath=[],
|
|
||||||
runtime_hooks=[],
|
|
||||||
excludes=['wxPython', 'wxpython'],
|
|
||||||
win_no_prefer_redirects=False,
|
|
||||||
win_private_assemblies=False,
|
|
||||||
cipher=block_cipher,
|
|
||||||
noarchive=False)
|
|
||||||
pyz = PYZ(a.pure, a.zipped_data,
|
|
||||||
cipher=block_cipher)
|
|
||||||
exe = EXE(pyz,
|
|
||||||
a.scripts,
|
|
||||||
a.binaries,
|
|
||||||
a.zipfiles,
|
|
||||||
a.datas,
|
|
||||||
[],
|
|
||||||
name='OpenCore-Patcher',
|
|
||||||
debug=False,
|
|
||||||
bootloader_ignore_signals=False,
|
|
||||||
strip=False,
|
|
||||||
upx=True,
|
|
||||||
upx_exclude=[],
|
|
||||||
runtime_tmpdir=None,
|
|
||||||
console=True )
|
|
||||||
app = BUNDLE(exe,
|
|
||||||
name='OpenCore-Patcher.app',
|
|
||||||
icon="payloads/OC-Patcher-TUI.icns",
|
|
||||||
bundle_identifier="com.dortania.opencore-legacy-patcher-tui",
|
|
||||||
info_plist={
|
|
||||||
"CFBundleShortVersionString": constants.Constants().patcher_version,
|
|
||||||
"CFBundleExecutable": "MacOS/Launcher",
|
|
||||||
"NSHumanReadableCopyright": constants.Constants().copyright_date,
|
|
||||||
})
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
# Build and run from source
|
# Build and run from source
|
||||||
|
|
||||||
OpenCore Legacy Patcher at its core is a python-based TUI/CLI based application. This means that to run the project from source, you simply need to invoke the OpenCore-Patcher.command file via Python.
|
OpenCore Legacy Patcher at its core is a python-based GUI/CLI based application. This means that to run the project from source, you simply need to invoke the OpenCore-Patcher-GUI.command file via Python.
|
||||||
|
|
||||||
For developers wishing to validate mainline changes, you may use these nightly links:
|
For developers wishing to validate mainline changes, you may use these nightly links:
|
||||||
|
|
||||||
* [GUI (Graphical Based App)](https://nightly.link/dortania/OpenCore-Legacy-Patcher/workflows/build-app-wxpython/main/OpenCore-Patcher.app%20%28GUI%29.zip)
|
* [GUI (Graphical Based App)](https://nightly.link/dortania/OpenCore-Legacy-Patcher/workflows/build-app-wxpython/main/OpenCore-Patcher.app%20%28GUI%29.zip)
|
||||||
* [TUI (Text Based App)](https://nightly.link/dortania/OpenCore-Legacy-Patcher/workflows/build-app/main/OpenCore-Patcher-TUI.app.zip)
|
|
||||||
|
|
||||||
**Warning**: These binaries should not be used without first consulting the [CHANGELOG](./CHANGELOG.md). Do not distribute these links in forums, instead direct to this file.
|
**Warning**: These binaries should not be used without first consulting the [CHANGELOG](./CHANGELOG.md). Do not distribute these links in forums, instead direct to this file.
|
||||||
|
|
||||||
@@ -32,41 +31,31 @@ pip3 install -r requirements.txt
|
|||||||
|
|
||||||
If you have installation error, see following troubleshooting options:
|
If you have installation error, see following troubleshooting options:
|
||||||
|
|
||||||
* Use Python 3.9
|
* Use Python 3.10
|
||||||
* Currently our build server uses py3.9 for generating binaries used in releases
|
* Currently our build server uses py3.10 for generating binaries used in releases
|
||||||
* Use .whl snapshots for installing additional dependencies
|
* Use .whl snapshots for installing additional dependencies
|
||||||
* [wxPython 4.1.1 wheel for py3.9](https://files.pythonhosted.org/packages/2c/a8/7027e8ca3ba20dc2ed2acd556e31941cb44097ab87d6f81d646a79de4eab/wxPython-4.1.1-cp39-cp39-macosx_10_10_x86_64.whl)
|
|
||||||
* [PyObjc 8.5 wheel for py3](https://files.pythonhosted.org/packages/69/3d/786f379dd669a078cf0c4a686e242c9b643071c23367bfbd3d9a7eb589ec/pyobjc-8.5-py3-none-any.whl)
|
|
||||||
* [Requests 2.27.1 for py2/py3](https://files.pythonhosted.org/packages/2d/61/08076519c80041bc0ffa1a8af0cbd3bf3e2b62af10435d269a9d0f40564d/requests-2.27.1-py2.py3-none-any.whl)
|
|
||||||
* [pyinstaller 5.3 for py3](https://files.pythonhosted.org/packages/65/70/625e86e5a45cb975a9c32a10a721394d10771275c69207308b80bc6a758e/pyinstaller-5.3-py3-none-macosx_10_13_universal2.whl)
|
|
||||||
|
|
||||||
|
|
||||||
## Running OpenCore Legacy Patcher
|
## Running OpenCore Legacy Patcher
|
||||||
|
|
||||||
To run the project from source, simply invoke via python3:
|
To run the project from source, simply invoke via python3:
|
||||||
|
|
||||||
```sh
|
|
||||||
# Launch TUI
|
|
||||||
python3 OpenCore-Patcher.command
|
|
||||||
```
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Launch GUI
|
# Launch GUI
|
||||||
python3 OpenCore-Patcher-GUI.command
|
python3 OpenCore-Patcher-GUI.command
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the OpenCore-Patcher.command file can be run as both a TUI and a CLI utility for other programs to call. If no core arguments are passed, the TUI is initialized. Otherwise the CLI will start:
|
Note that the OpenCore-Patcher-GUI.command file can be run as both a GUI and a CLI utility for other programs to call. If no core arguments are passed, the GUI is initialized. Otherwise the CLI will start:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Launch CLI
|
# Launch CLI
|
||||||
python3 OpenCore-Patcher.command --build --model iMac12,2 --verbose
|
python3 OpenCore-Patcher-GUI.command --build --model iMac12,2 --verbose
|
||||||
```
|
```
|
||||||
|
|
||||||
See `-h`/`--help` for more information on supported CLI arguments.
|
See `-h`/`--help` for more information on supported CLI arguments.
|
||||||
|
|
||||||
## Generating prebuilt binaries
|
## Generating prebuilt binaries
|
||||||
|
|
||||||
The main goal of generating prebuilt binaries is to strip the requirement of a local python installation for users. For developers, there's very little benefit besides enabling dark mode support in the GUI. For development, simply use the OpenCore-Patcher.command file with a python3 installation.
|
The main goal of generating prebuilt binaries is to strip the requirement of a local python installation for users. For developers, there's very little benefit besides enabling dark mode support in the GUI. For development, simply use the OpenCore-Patcher-GUI.command file with a python3 installation.
|
||||||
|
|
||||||
* Note that due to PyInstaller's linking mechanism, binaries generated on Catalina and newer are not compatible with High Sierra and older
|
* Note that due to PyInstaller's linking mechanism, binaries generated on Catalina and newer are not compatible with High Sierra and older
|
||||||
* To ensure the largest compatibility, generate binaries on macOS Mojave. These binaries will be compatible with macOS 10.9 to macOS 12.
|
* To ensure the largest compatibility, generate binaries on macOS Mojave. These binaries will be compatible with macOS 10.9 to macOS 12.
|
||||||
@@ -79,7 +68,6 @@ pip3 install pyinstaller
|
|||||||
cd ~/Developer/OpenCore-Legacy-Patcher/
|
cd ~/Developer/OpenCore-Legacy-Patcher/
|
||||||
# Create the pyinstaller based Application
|
# Create the pyinstaller based Application
|
||||||
# Optional Arguments
|
# Optional Arguments
|
||||||
# '--build_tui': Create TUI vairant
|
|
||||||
# '--reset_binaries': Redownload and generate support files
|
# '--reset_binaries': Redownload and generate support files
|
||||||
python3 Build-Binary.command
|
python3 Build-Binary.command
|
||||||
# Open build folder
|
# Open build folder
|
||||||
|
|||||||
+12
-11
@@ -6,19 +6,20 @@
|
|||||||
# - 0x3 used in 11.0.1 dyld source:
|
# - 0x3 used in 11.0.1 dyld source:
|
||||||
# - https://github.com/apple-oss-distributions/dyld/blob/5c9192436bb195e7a8fe61f22a229ee3d30d8222/testing/test-cases/kernel-hello-world.dtest/main.c#L2
|
# - https://github.com/apple-oss-distributions/dyld/blob/5c9192436bb195e7a8fe61f22a229ee3d30d8222/testing/test-cases/kernel-hello-world.dtest/main.c#L2
|
||||||
|
|
||||||
class apple_mobile_file_integrity:
|
import enum
|
||||||
|
|
||||||
|
class AppleMobileFileIntegrity(enum.IntEnum):
|
||||||
# Names set are solely for readability
|
# Names set are solely for readability
|
||||||
# Internal names are unknown
|
# Internal names are unknown
|
||||||
amfi_values = {
|
AMFI_ALLOW_TASK_FOR_PID: int = 0x1 # Allow Task for PID (alt. amfi_unrestrict_task_for_pid=0x1)
|
||||||
"AMFI_ALLOW_TASK_FOR_PID": False, # 0x1 - Allow Task for PID (alt. amfi_unrestrict_task_for_pid=0x1)
|
AMFI_ALLOW_INVALID_SIGNATURE: int = 0x2 # Reduce sig enforcement (alt. amfi_allow_any_signature=0x1)
|
||||||
"AMFI_ALLOW_INVALID_SIGNATURE": False, # 0x2 - Reduce sig enforcement (alt. amfi_allow_any_signature=0x1)
|
AMFI_LV_ENFORCE_THIRD_PARTY: int = 0x4 # Don't mark external binaries as platform binaries
|
||||||
"AMFI_LV_ENFORCE_THIRD_PARTY": False, # 0x4 - Don't mark external binaries as platform binaries
|
AMFI_UNKNOWN_1: int = 0x8
|
||||||
"AMFI_UNKNOWN_1": False, # 0x8
|
AMFI_UNKNOWN_2: int = 0x10
|
||||||
"AMFI_UNKNOWN_2": False, # 0x10
|
AMFI_UNKNOWN_3: int = 0x20
|
||||||
"AMFI_UNKNOWN_3": False, # 0x20
|
AMFI_UNKNOWN_4: int = 0x40
|
||||||
"AMFI_UNKNOWN_4": False, # 0x40
|
AMFI_ALLOW_EVERYTHING: int = 0x80 # Disable sig enforcement and Library Validation (alt. amfi_get_out_of_my_way=0x1)
|
||||||
"AMFI_ALLOW_EVERYTHING": False, # 0x80 - Disable sig enforcement and Library Validation (alt. amfi_get_out_of_my_way=0x1)
|
|
||||||
},
|
|
||||||
|
|
||||||
# Internally within AMFI.kext, Apple references 0x2 and 0x80 as both 'Disable signature enforcement'
|
# Internally within AMFI.kext, Apple references 0x2 and 0x80 as both 'Disable signature enforcement'
|
||||||
# However 0x80 is a higher privilege than 0x2, and breaks TCC support in OS (ex. Camera, Microphone, etc prompts)
|
# However 0x80 is a higher privilege than 0x2, and breaks TCC support in OS (ex. Camera, Microphone, etc prompts)
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
# Data for SkyLightShim Plugin systems
|
|
||||||
class shim_list:
|
|
||||||
shim_pathing = {
|
|
||||||
"CoreWLAN.dylib": "/System/Library/CoreServices/WiFiAgent.app/Contents/MacOS/WiFiAgent",
|
|
||||||
"BacklightFixup.dylib": "/System/Library/CoreServices/loginwindow.app/Contents/MacOS/loginwindow",
|
|
||||||
}
|
|
||||||
|
|
||||||
shim_legacy_accel = [
|
|
||||||
"CoreWLAN.dylib",
|
|
||||||
]
|
|
||||||
|
|
||||||
shim_legacy_accel_keyboard = [
|
|
||||||
"BacklightFixup.dylib",
|
|
||||||
]
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# Mirrors of Apple's InstallAssistant.pkg
|
|
||||||
# Currently only listing important Installers no longer on Apple's servers
|
|
||||||
|
|
||||||
Install_macOS_Big_Sur_11_2_3 = {
|
|
||||||
"Version": "11.2.3",
|
|
||||||
"Build": "20D91",
|
|
||||||
"Link": "https://archive.org/download/install-assistant-20D91/InstallAssistant.pkg",
|
|
||||||
"Size": 12211077798,
|
|
||||||
"Source": "Archive.org",
|
|
||||||
"integrity": None,
|
|
||||||
}
|
|
||||||
@@ -296,6 +296,55 @@ def SystemPatchDictionary(os_major, os_minor, non_metal_os_support):
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
# Support for 3802 GPUs were broken with 13.3+
|
||||||
|
# Downgrades 31001 stack to 13.2.1, however nukes AMFI support
|
||||||
|
# Extremely fugly, only for reference purposes at this time
|
||||||
|
"Metal 3802 Common Extended": {
|
||||||
|
"Display Name": "",
|
||||||
|
"OS Support": {
|
||||||
|
"Minimum OS Support": {
|
||||||
|
"OS Major": os_data.os_data.ventura,
|
||||||
|
"OS Minor": 4 # 13.3
|
||||||
|
},
|
||||||
|
"Maximum OS Support": {
|
||||||
|
"OS Major": os_data.os_data.max_os,
|
||||||
|
"OS Minor": 99
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Install Reference": {
|
||||||
|
"/System/Library/Frameworks": {
|
||||||
|
"Metal.framework": "13.2.1",
|
||||||
|
},
|
||||||
|
"/System/Library/PrivateFrameworks": {
|
||||||
|
"MTLCompiler.framework": "13.2.1",
|
||||||
|
"GPUCompiler.framework": "13.2.1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
# Primarily for AMD GCN GPUs
|
||||||
|
"Revert GVA Downgrade": {
|
||||||
|
"Display Name": "",
|
||||||
|
"OS Support": {
|
||||||
|
"Minimum OS Support": {
|
||||||
|
"OS Major": os_data.os_data.ventura,
|
||||||
|
"OS Minor": 0
|
||||||
|
},
|
||||||
|
"Maximum OS Support": {
|
||||||
|
"OS Major": os_data.os_data.max_os,
|
||||||
|
"OS Minor": 99
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Remove": {
|
||||||
|
"/System/Library/PrivateFrameworks/AppleGVA.framework/Versions/A/": [
|
||||||
|
"AppleGVA",
|
||||||
|
],
|
||||||
|
"/System/Library/PrivateFrameworks/AppleGVACore.framework/Versions/A/": [
|
||||||
|
"AppleGVACore",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
# For GPUs last natively supported in Catalina/Big Sur
|
# For GPUs last natively supported in Catalina/Big Sur
|
||||||
# Restores DRM support
|
# Restores DRM support
|
||||||
"Catalina GVA": {
|
"Catalina GVA": {
|
||||||
@@ -657,6 +706,8 @@ def SystemPatchDictionary(os_major, os_minor, non_metal_os_support):
|
|||||||
"AMDFramebuffer.kext": "12.5",
|
"AMDFramebuffer.kext": "12.5",
|
||||||
"AMDSupport.kext": "12.5",
|
"AMDSupport.kext": "12.5",
|
||||||
|
|
||||||
|
"AMDRadeonVADriver.bundle": "12.5",
|
||||||
|
"AMDRadeonVADriver2.bundle": "12.5",
|
||||||
"AMDRadeonX4000GLDriver.bundle": "12.5",
|
"AMDRadeonX4000GLDriver.bundle": "12.5",
|
||||||
"AMDMTLBronzeDriver.bundle": "12.5",
|
"AMDMTLBronzeDriver.bundle": "12.5",
|
||||||
"AMDShared.bundle": "12.5",
|
"AMDShared.bundle": "12.5",
|
||||||
@@ -680,7 +731,9 @@ def SystemPatchDictionary(os_major, os_minor, non_metal_os_support):
|
|||||||
"Install": {
|
"Install": {
|
||||||
"/System/Library/Extensions": {
|
"/System/Library/Extensions": {
|
||||||
"AMDRadeonX4000.kext": "12.5",
|
"AMDRadeonX4000.kext": "12.5",
|
||||||
|
"AMDRadeonX4000HWServices.kext": "12.5",
|
||||||
|
|
||||||
|
"AMDRadeonVADriver2.bundle": "12.5",
|
||||||
"AMDRadeonX4000GLDriver.bundle": "12.5",
|
"AMDRadeonX4000GLDriver.bundle": "12.5",
|
||||||
"AMDMTLBronzeDriver.bundle": "12.5",
|
"AMDMTLBronzeDriver.bundle": "12.5",
|
||||||
"AMDShared.bundle": "12.5",
|
"AMDShared.bundle": "12.5",
|
||||||
@@ -702,7 +755,9 @@ def SystemPatchDictionary(os_major, os_minor, non_metal_os_support):
|
|||||||
"Install": {
|
"Install": {
|
||||||
"/System/Library/Extensions": {
|
"/System/Library/Extensions": {
|
||||||
"AMDRadeonX5000.kext": "12.5",
|
"AMDRadeonX5000.kext": "12.5",
|
||||||
|
"AMDRadeonX5000HWServices.kext": "12.5",
|
||||||
|
|
||||||
|
"AMDRadeonVADriver2.bundle": "12.5",
|
||||||
"AMDRadeonX5000GLDriver.bundle": "12.5",
|
"AMDRadeonX5000GLDriver.bundle": "12.5",
|
||||||
"AMDRadeonX5000MTLDriver.bundle": "12.5",
|
"AMDRadeonX5000MTLDriver.bundle": "12.5",
|
||||||
"AMDRadeonX5000Shared.bundle": "12.5",
|
"AMDRadeonX5000Shared.bundle": "12.5",
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ Regarding OS support, see below:
|
|||||||
|
|
||||||
| Support Entry | Supported OSes | Description | Comment |
|
| Support Entry | Supported OSes | Description | Comment |
|
||||||
| :--- | :--- | :--- | :--- |
|
| :--- | :--- | :--- | :--- |
|
||||||
| HostOS | macOS 10.9 - macOS 13 | Refers to OSes where running OpenCore-Patcher.app are supported | Supports 10.7+ if [Python 3.9 or higher](https://www.python.org/downloads/) is manually installed, simply run the `OpenCore-Patcher.command` located in the repo |
|
| HostOS | macOS 10.9 - macOS 13 | Refers to OSes where running OpenCore-Patcher.app are supported | Supports 10.7+ if [Python 3.9 or higher](https://www.python.org/downloads/) is manually installed, simply run the `OpenCore-Patcher-GUI.command` located in the repo |
|
||||||
| TargetOS | macOS 11 - macOS 13 | Refers to OSes that can be patched to run with OpenCore | May support 10.4 and newer (in a potentially broken state). No support provided. |
|
| TargetOS | macOS 11 - macOS 13 | Refers to OSes that can be patched to run with OpenCore | May support 10.4 and newer (in a potentially broken state). No support provided. |
|
||||||
|
|
||||||
### MacBook
|
### MacBook
|
||||||
|
|||||||
@@ -20,14 +20,11 @@ And voila! No more USB drive required.
|
|||||||
|
|
||||||
To do this, run the OpenCore Patcher and head to Patcher Settings:
|
To do this, run the OpenCore Patcher and head to Patcher Settings:
|
||||||
|
|
||||||
| GUI Settings | TUI Settings
|

|
||||||
| :--- | :--- |
|
|
||||||
| |  |
|
|
||||||
|
|
||||||
Here you can change different patcher settings, however the main interest is:
|
Here you can change different patcher settings, however the main interest is:
|
||||||
|
|
||||||
* Show Boot Picker (GUI)
|
* Show Boot Picker
|
||||||
* Set ShowPicker Mode (TUI)
|
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+92
-49
@@ -1,27 +1,55 @@
|
|||||||
# Determine AppleMobileFileIntegrity's OS configuration
|
# Determine AppleMobileFileIntegrity's OS configuration
|
||||||
|
# Copyright (C) 2022-2023, Mykola Grymalyuk
|
||||||
|
|
||||||
|
import enum
|
||||||
from resources import utilities
|
from resources import utilities
|
||||||
|
from data import amfi_data
|
||||||
|
|
||||||
class amfi_configuration_detection:
|
|
||||||
|
class AmfiConfigDetectLevel(enum.IntEnum):
|
||||||
|
"""
|
||||||
|
Configuration levels used by AmfiConfigurationDetection
|
||||||
|
"""
|
||||||
|
|
||||||
|
NO_CHECK: int = 0
|
||||||
|
LIBRARY_VALIDATION: int = 1 # For Ventura, use LIBRARY_VALIDATION_AND_SIG
|
||||||
|
LIBRARY_VALIDATION_AND_SIG: int = 2
|
||||||
|
ALLOW_ALL: int = 3
|
||||||
|
|
||||||
|
|
||||||
|
class AmfiConfigurationDetection:
|
||||||
|
"""
|
||||||
|
Detect AppleMobileFileIntegrity's OS configuration
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
>>> import amfi_detect
|
||||||
|
>>> can_patch = amfi_detect.AmfiConfigurationDetection().check_config(amfi_detect.AmfiConfigDetectLevel.ALLOW_ALL)
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.AMFI_ALLOW_TASK_FOR_PID = False
|
self.AMFI_ALLOW_TASK_FOR_PID: bool = False
|
||||||
self.AMFI_ALLOW_INVALID_SIGNATURE = False
|
self.AMFI_ALLOW_INVALID_SIGNATURE: bool = False
|
||||||
self.AMFI_LV_ENFORCE_THIRD_PARTY = False
|
self.AMFI_LV_ENFORCE_THIRD_PARTY: bool = False
|
||||||
self.AMFI_ALLOW_EVERYTHING = False
|
self.AMFI_ALLOW_EVERYTHING: bool = False
|
||||||
self.SKIP_LIBRARY_VALIDATION = False
|
self.SKIP_LIBRARY_VALIDATION: bool = False
|
||||||
|
|
||||||
self.boot_args = []
|
self.boot_args: list = []
|
||||||
self.oclp_args = []
|
self.oclp_args: list = []
|
||||||
|
|
||||||
self.init_nvram_dicts()
|
self._init_nvram_dicts()
|
||||||
|
|
||||||
self.parse_amfi_bitmask()
|
self._parse_amfi_bitmask()
|
||||||
self.parse_amfi_boot_args()
|
self._parse_amfi_boot_args()
|
||||||
self.parse_oclp_configuration()
|
self._parse_oclp_configuration()
|
||||||
|
|
||||||
|
|
||||||
def init_nvram_dicts(self):
|
def _init_nvram_dicts(self):
|
||||||
|
"""
|
||||||
|
Initialize the boot-args and OCLP-Settings NVRAM dictionaries
|
||||||
|
"""
|
||||||
|
|
||||||
boot_args = utilities.get_nvram("boot-args", decode=True)
|
boot_args = utilities.get_nvram("boot-args", decode=True)
|
||||||
oclp_args = utilities.get_nvram("OCLP-Settings", "4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102", decode=True)
|
oclp_args = utilities.get_nvram("OCLP-Settings", "4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102", decode=True)
|
||||||
|
|
||||||
@@ -32,40 +60,47 @@ class amfi_configuration_detection:
|
|||||||
self.oclp_args = oclp_args.split(" ")
|
self.oclp_args = oclp_args.split(" ")
|
||||||
|
|
||||||
|
|
||||||
def parse_amfi_bitmask(self):
|
def _parse_amfi_bitmask(self):
|
||||||
# See data/amfi_data.py for more information
|
"""
|
||||||
|
Parse the AMFI bitmask from boot-args
|
||||||
|
See data/amfi_data.py for more information
|
||||||
|
"""
|
||||||
|
|
||||||
amfi_value = 0
|
amfi_value = 0
|
||||||
for arg in self.boot_args:
|
for arg in self.boot_args:
|
||||||
if arg.startswith("amfi="):
|
if not arg.startswith("amfi="):
|
||||||
try:
|
continue
|
||||||
amfi_value = arg.split("=")
|
try:
|
||||||
if len(amfi_value) != 2:
|
amfi_value = arg.split("=")
|
||||||
return
|
if len(amfi_value) != 2:
|
||||||
amfi_value = amfi_value[1]
|
|
||||||
if amfi_value.startswith("0x"):
|
|
||||||
amfi_value = int(amfi_value, 16)
|
|
||||||
else:
|
|
||||||
amfi_value = int(amfi_value)
|
|
||||||
except:
|
|
||||||
return
|
return
|
||||||
break
|
amfi_value = amfi_value[1]
|
||||||
|
if amfi_value.startswith("0x"):
|
||||||
|
amfi_value = int(amfi_value, 16)
|
||||||
|
else:
|
||||||
|
amfi_value = int(amfi_value)
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
break
|
||||||
|
|
||||||
if amfi_value == 0:
|
if amfi_value == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
if amfi_value & 0x1:
|
self.AMFI_ALLOW_TASK_FOR_PID: bool = amfi_value & amfi_data.AppleMobileFileIntegrity.AMFI_ALLOW_TASK_FOR_PID
|
||||||
self.AMFI_ALLOW_TASK_FOR_PID = True
|
self.AMFI_ALLOW_INVALID_SIGNATURE: bool = amfi_value & amfi_data.AppleMobileFileIntegrity.AMFI_ALLOW_INVALID_SIGNATURE
|
||||||
if amfi_value & 0x2:
|
self.AMFI_LV_ENFORCE_THIRD_PARTY: bool = amfi_value & amfi_data.AppleMobileFileIntegrity.AMFI_LV_ENFORCE_THIRD_PARTY
|
||||||
self.AMFI_ALLOW_INVALID_SIGNATURE = True
|
|
||||||
if amfi_value & 0x4:
|
if amfi_value & amfi_data.AppleMobileFileIntegrity.AMFI_ALLOW_EVERYTHING:
|
||||||
self.AMFI_LV_ENFORCE_THIRD_PARTY = True
|
self.AMFI_ALLOW_EVERYTHING = True
|
||||||
if amfi_value & 0x80:
|
self.SKIP_LIBRARY_VALIDATION = True
|
||||||
self.AMFI_ALLOW_EVERYTHING = True
|
|
||||||
self.SKIP_LIBRARY_VALIDATION = True
|
|
||||||
self.AMFI_ALLOW_INVALID_SIGNATURE = True
|
self.AMFI_ALLOW_INVALID_SIGNATURE = True
|
||||||
|
|
||||||
|
|
||||||
def parse_amfi_boot_args(self):
|
def _parse_amfi_boot_args(self):
|
||||||
|
"""
|
||||||
|
Parse the AMFI boot-args
|
||||||
|
"""
|
||||||
|
|
||||||
for arg in self.boot_args:
|
for arg in self.boot_args:
|
||||||
if arg.startswith("amfi_unrestrict_task_for_pid"):
|
if arg.startswith("amfi_unrestrict_task_for_pid"):
|
||||||
value = arg.split("=")
|
value = arg.split("=")
|
||||||
@@ -86,26 +121,34 @@ class amfi_configuration_detection:
|
|||||||
self.AMFI_ALLOW_INVALID_SIGNATURE = True
|
self.AMFI_ALLOW_INVALID_SIGNATURE = True
|
||||||
|
|
||||||
|
|
||||||
def parse_oclp_configuration(self):
|
def _parse_oclp_configuration(self):
|
||||||
|
"""
|
||||||
|
Parse the OCLP configuration
|
||||||
|
"""
|
||||||
|
|
||||||
if "-allow_amfi" in self.oclp_args:
|
if "-allow_amfi" in self.oclp_args:
|
||||||
self.SKIP_LIBRARY_VALIDATION = True
|
self.SKIP_LIBRARY_VALIDATION = True
|
||||||
|
|
||||||
|
|
||||||
def check_config(self, level):
|
def check_config(self, level: int):
|
||||||
# Levels:
|
"""
|
||||||
# - 0: No checks
|
Check the AMFI configuration based on provided AMFI level
|
||||||
# - 1. Library Validation (Monterey and Older)
|
See AmfiConfigLevel enum for valid levels
|
||||||
# - 2. Library Validation and Signature Checks (Ventura and Newer)
|
|
||||||
# - 3. Disable all AMFI checks
|
|
||||||
|
|
||||||
if level == 0:
|
Parameters:
|
||||||
|
level (int): The level of AMFI checks to check for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the AMFI configuration matches the level, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
|
if level == AmfiConfigDetectLevel.NO_CHECK:
|
||||||
return True
|
return True
|
||||||
|
if level == AmfiConfigDetectLevel.LIBRARY_VALIDATION:
|
||||||
if level == 1:
|
|
||||||
return self.SKIP_LIBRARY_VALIDATION
|
return self.SKIP_LIBRARY_VALIDATION
|
||||||
if level == 2:
|
if level == AmfiConfigDetectLevel.LIBRARY_VALIDATION_AND_SIG:
|
||||||
return bool(self.SKIP_LIBRARY_VALIDATION and self.AMFI_ALLOW_INVALID_SIGNATURE)
|
return bool(self.SKIP_LIBRARY_VALIDATION and self.AMFI_ALLOW_INVALID_SIGNATURE)
|
||||||
if level == 3:
|
if level == AmfiConfigDetectLevel.ALLOW_ALL:
|
||||||
return self.AMFI_ALLOW_EVERYTHING
|
return self.AMFI_ALLOW_EVERYTHING
|
||||||
|
|
||||||
return False
|
return False
|
||||||
+175
-101
@@ -1,116 +1,190 @@
|
|||||||
import sys
|
|
||||||
from resources import defaults, utilities, validation
|
|
||||||
from resources.sys_patch import sys_patch, sys_patch_auto
|
|
||||||
from resources.build import build
|
|
||||||
from data import model_array
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from resources import defaults, utilities, validation, constants
|
||||||
|
from resources.sys_patch import sys_patch, sys_patch_auto
|
||||||
|
from resources.build import build
|
||||||
|
from data import model_array
|
||||||
|
|
||||||
|
|
||||||
# Generic building args
|
# Generic building args
|
||||||
class arguments:
|
class arguments:
|
||||||
def __init__(self):
|
|
||||||
|
def __init__(self, global_constants: constants.Constants):
|
||||||
|
self.constants: constants.Constants = global_constants
|
||||||
|
|
||||||
self.args = utilities.check_cli_args()
|
self.args = utilities.check_cli_args()
|
||||||
|
|
||||||
def parse_arguments(self, settings):
|
self._parse_arguments()
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_arguments(self):
|
||||||
|
"""
|
||||||
|
Parses arguments passed to the patcher
|
||||||
|
"""
|
||||||
|
|
||||||
if self.args.validate:
|
if self.args.validate:
|
||||||
validation.validate(settings)
|
self._validation_handler()
|
||||||
elif self.args.build:
|
return
|
||||||
|
|
||||||
|
if self.args.build:
|
||||||
|
self._build_handler()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.args.patch_sys_vol:
|
||||||
|
self._sys_patch_handler()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.args.unpatch_sys_vol:
|
||||||
|
self._sys_unpatch_handler()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.args.auto_patch:
|
||||||
|
self._sys_patch_auto_handler()
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def _validation_handler(self):
|
||||||
|
"""
|
||||||
|
Enter validation mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
validation.PatcherValidation(self.constants)
|
||||||
|
|
||||||
|
|
||||||
|
def _sys_patch_handler(self):
|
||||||
|
"""
|
||||||
|
Start root volume patching
|
||||||
|
"""
|
||||||
|
|
||||||
|
logging.info("- Set System Volume patching")
|
||||||
|
if "Library/InstallerSandboxes/" in str(self.constants.payload_path):
|
||||||
|
logging.info("- Running from Installer Sandbox")
|
||||||
|
thread = threading.Thread(target=sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, None).start_patch)
|
||||||
|
thread.start()
|
||||||
|
while thread.is_alive():
|
||||||
|
utilities.block_os_updaters()
|
||||||
|
time.sleep(1)
|
||||||
|
else:
|
||||||
|
sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, None).start_patch()
|
||||||
|
|
||||||
|
|
||||||
|
def _sys_unpatch_handler(self):
|
||||||
|
"""
|
||||||
|
Start root volume unpatching
|
||||||
|
"""
|
||||||
|
logging.info("- Set System Volume unpatching")
|
||||||
|
sys_patch.PatchSysVolume(self.constants.custom_model or self.constants.computer.real_model, self.constants, None).start_unpatch()
|
||||||
|
|
||||||
|
|
||||||
|
def _sys_patch_auto_handler(self):
|
||||||
|
"""
|
||||||
|
Start root volume auto patching
|
||||||
|
"""
|
||||||
|
|
||||||
|
logging.info("- Set Auto patching")
|
||||||
|
sys_patch_auto.AutomaticSysPatch(self.constants).start_auto_patch()
|
||||||
|
|
||||||
|
|
||||||
|
def _build_handler(self):
|
||||||
|
"""
|
||||||
|
Start config building process
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.args.model:
|
||||||
if self.args.model:
|
if self.args.model:
|
||||||
if self.args.model:
|
logging.info(f"- Using custom model: {self.args.model}")
|
||||||
logging.info(f"- Using custom model: {self.args.model}")
|
self.constants.custom_model = self.args.model
|
||||||
settings.custom_model = self.args.model
|
defaults.GenerateDefaults(self.constants.custom_model, False, self.constants)
|
||||||
defaults.generate_defaults(settings.custom_model, False, settings)
|
elif self.constants.computer.real_model not in model_array.SupportedSMBIOS and self.constants.allow_oc_everywhere is False:
|
||||||
elif settings.computer.real_model not in model_array.SupportedSMBIOS and settings.allow_oc_everywhere is False:
|
logging.info(
|
||||||
logging.info(
|
"""Your model is not supported by this patcher for running unsupported OSes!"
|
||||||
"""Your model is not supported by this patcher for running unsupported OSes!"
|
|
||||||
|
|
||||||
If you plan to create the USB for another machine, please select the "Change Model" option in the menu."""
|
If you plan to create the USB for another machine, please select the "Change Model" option in the menu."""
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
logging.info(f"- Using detected model: {settings.computer.real_model}")
|
logging.info(f"- Using detected model: {self.constants.computer.real_model}")
|
||||||
defaults.generate_defaults(settings.custom_model, True, settings)
|
defaults.GenerateDefaults(self.constants.custom_model, True, self.constants)
|
||||||
|
|
||||||
if self.args.disk:
|
if self.args.disk:
|
||||||
logging.info(f"- Install Disk set: {self.args.disk}")
|
logging.info(f"- Install Disk set: {self.args.disk}")
|
||||||
settings.disk = self.args.disk
|
self.constants.disk = self.args.disk
|
||||||
if self.args.verbose:
|
|
||||||
logging.info("- Set verbose configuration")
|
|
||||||
settings.verbose_debug = True
|
|
||||||
else:
|
|
||||||
settings.verbose_debug = False # Override Defaults detected
|
|
||||||
if self.args.debug_oc:
|
|
||||||
logging.info("- Set OpenCore DEBUG configuration")
|
|
||||||
settings.opencore_debug = True
|
|
||||||
settings.opencore_build = "DEBUG"
|
|
||||||
if self.args.debug_kext:
|
|
||||||
logging.info("- Set kext DEBUG configuration")
|
|
||||||
settings.kext_debug = True
|
|
||||||
if self.args.hide_picker:
|
|
||||||
logging.info("- Set HidePicker configuration")
|
|
||||||
settings.showpicker = False
|
|
||||||
if self.args.disable_sip:
|
|
||||||
logging.info("- Set Disable SIP configuration")
|
|
||||||
settings.sip_status = False
|
|
||||||
else:
|
|
||||||
settings.sip_status = True # Override Defaults detected
|
|
||||||
if self.args.disable_smb:
|
|
||||||
logging.info("- Set Disable SecureBootModel configuration")
|
|
||||||
settings.secure_status = False
|
|
||||||
else:
|
|
||||||
settings.secure_status = True # Override Defaults detected
|
|
||||||
if self.args.vault:
|
|
||||||
logging.info("- Set Vault configuration")
|
|
||||||
settings.vault = True
|
|
||||||
if self.args.firewire:
|
|
||||||
logging.info("- Set FireWire Boot configuration")
|
|
||||||
settings.firewire_boot = True
|
|
||||||
if self.args.nvme:
|
|
||||||
logging.info("- Set NVMe Boot configuration")
|
|
||||||
settings.nvme_boot = True
|
|
||||||
if self.args.wlan:
|
|
||||||
logging.info("- Set Wake on WLAN configuration")
|
|
||||||
settings.enable_wake_on_wlan = True
|
|
||||||
if self.args.disable_tb:
|
|
||||||
logging.info("- Set Disable Thunderbolt configuration")
|
|
||||||
settings.disable_tb = True
|
|
||||||
if self.args.force_surplus:
|
|
||||||
logging.info("- Forcing SurPlus override configuration")
|
|
||||||
settings.force_surplus = True
|
|
||||||
if self.args.moderate_smbios:
|
|
||||||
logging.info("- Set Moderate SMBIOS Patching configuration")
|
|
||||||
settings.serial_settings = "Moderate"
|
|
||||||
if self.args.smbios_spoof:
|
|
||||||
if self.args.smbios_spoof == "Minimal":
|
|
||||||
settings.serial_settings = "Minimal"
|
|
||||||
elif self.args.smbios_spoof == "Moderate":
|
|
||||||
settings.serial_settings = "Moderate"
|
|
||||||
elif self.args.smbios_spoof == "Advanced":
|
|
||||||
settings.serial_settings = "Advanced"
|
|
||||||
else:
|
|
||||||
logging.info(f"- Unknown SMBIOS arg passed: {self.args.smbios_spoof}")
|
|
||||||
|
|
||||||
if self.args.support_all:
|
if self.args.verbose:
|
||||||
logging.info("- Building for natively supported model")
|
logging.info("- Set verbose configuration")
|
||||||
settings.allow_oc_everywhere = True
|
self.constants.verbose_debug = True
|
||||||
settings.serial_settings = "None"
|
else:
|
||||||
build.build_opencore(settings.custom_model or settings.computer.real_model, settings).build_opencore()
|
self.constants.verbose_debug = False # Override Defaults detected
|
||||||
elif self.args.patch_sys_vol:
|
|
||||||
logging.info("- Set System Volume patching")
|
|
||||||
|
|
||||||
if "Library/InstallerSandboxes/" in str(settings.payload_path):
|
if self.args.debug_oc:
|
||||||
logging.info("- Running from Installer Sandbox")
|
logging.info("- Set OpenCore DEBUG configuration")
|
||||||
thread = threading.Thread(target=sys_patch.PatchSysVolume(settings.custom_model or settings.computer.real_model, settings, None).start_patch)
|
self.constants.opencore_debug = True
|
||||||
thread.start()
|
self.constants.opencore_build = "DEBUG"
|
||||||
while thread.is_alive():
|
|
||||||
utilities.block_os_updaters()
|
if self.args.debug_kext:
|
||||||
time.sleep(1)
|
logging.info("- Set kext DEBUG configuration")
|
||||||
|
self.constants.kext_debug = True
|
||||||
|
|
||||||
|
if self.args.hide_picker:
|
||||||
|
logging.info("- Set HidePicker configuration")
|
||||||
|
self.constants.showpicker = False
|
||||||
|
|
||||||
|
if self.args.disable_sip:
|
||||||
|
logging.info("- Set Disable SIP configuration")
|
||||||
|
self.constants.sip_status = False
|
||||||
|
else:
|
||||||
|
self.constants.sip_status = True # Override Defaults detected
|
||||||
|
|
||||||
|
if self.args.disable_smb:
|
||||||
|
logging.info("- Set Disable SecureBootModel configuration")
|
||||||
|
self.constants.secure_status = False
|
||||||
|
else:
|
||||||
|
self.constants.secure_status = True # Override Defaults detected
|
||||||
|
|
||||||
|
if self.args.vault:
|
||||||
|
logging.info("- Set Vault configuration")
|
||||||
|
self.constants.vault = True
|
||||||
|
|
||||||
|
if self.args.firewire:
|
||||||
|
logging.info("- Set FireWire Boot configuration")
|
||||||
|
self.constants.firewire_boot = True
|
||||||
|
|
||||||
|
if self.args.nvme:
|
||||||
|
logging.info("- Set NVMe Boot configuration")
|
||||||
|
self.constants.nvme_boot = True
|
||||||
|
|
||||||
|
if self.args.wlan:
|
||||||
|
logging.info("- Set Wake on WLAN configuration")
|
||||||
|
self.constants.enable_wake_on_wlan = True
|
||||||
|
|
||||||
|
if self.args.disable_tb:
|
||||||
|
logging.info("- Set Disable Thunderbolt configuration")
|
||||||
|
self.constants.disable_tb = True
|
||||||
|
|
||||||
|
if self.args.force_surplus:
|
||||||
|
logging.info("- Forcing SurPlus override configuration")
|
||||||
|
self.constants.force_surplus = True
|
||||||
|
|
||||||
|
if self.args.moderate_smbios:
|
||||||
|
logging.info("- Set Moderate SMBIOS Patching configuration")
|
||||||
|
self.constants.serial_settings = "Moderate"
|
||||||
|
|
||||||
|
if self.args.smbios_spoof:
|
||||||
|
if self.args.smbios_spoof == "Minimal":
|
||||||
|
self.constants.serial_settings = "Minimal"
|
||||||
|
elif self.args.smbios_spoof == "Moderate":
|
||||||
|
self.constants.serial_settings = "Moderate"
|
||||||
|
elif self.args.smbios_spoof == "Advanced":
|
||||||
|
self.constants.serial_settings = "Advanced"
|
||||||
else:
|
else:
|
||||||
sys_patch.PatchSysVolume(settings.custom_model or settings.computer.real_model, settings, None).start_patch()
|
logging.info(f"- Unknown SMBIOS arg passed: {self.args.smbios_spoof}")
|
||||||
elif self.args.unpatch_sys_vol:
|
|
||||||
logging.info("- Set System Volume unpatching")
|
if self.args.support_all:
|
||||||
sys_patch.PatchSysVolume(settings.custom_model or settings.computer.real_model, settings, None).start_unpatch()
|
logging.info("- Building for natively supported model")
|
||||||
elif self.args.auto_patch:
|
self.constants.allow_oc_everywhere = True
|
||||||
logging.info("- Set Auto patching")
|
self.constants.serial_settings = "None"
|
||||||
sys_patch_auto.AutomaticSysPatch(settings).start_auto_patch()
|
|
||||||
|
build.build_opencore(self.constants.custom_model or self.constants.computer.real_model, self.constants).build_opencore()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+21
-10
@@ -1,29 +1,40 @@
|
|||||||
# Parse Commit Info from binary's info.plist
|
# Parse Commit Info from binary's info.plist
|
||||||
# App Structure:
|
|
||||||
# OpenCore-Patcher.app:
|
|
||||||
# Contents:
|
|
||||||
# MacOS:
|
|
||||||
# OpenCore-Patcher
|
|
||||||
# Info.plist
|
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import plistlib
|
import plistlib
|
||||||
|
|
||||||
class commit_info:
|
class ParseCommitInfo:
|
||||||
|
|
||||||
|
def __init__(self, binary_path: str):
|
||||||
|
"""
|
||||||
|
Parameters:
|
||||||
|
binary_path (str): Path to binary
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, binary_path):
|
|
||||||
self.binary_path = str(binary_path)
|
self.binary_path = str(binary_path)
|
||||||
self.plist_path = self.convert_binary_path_to_plist_path()
|
self.plist_path = self._convert_binary_path_to_plist_path()
|
||||||
|
|
||||||
|
|
||||||
def convert_binary_path_to_plist_path(self):
|
def _convert_binary_path_to_plist_path(self):
|
||||||
|
"""
|
||||||
|
Resolve Info.plist path from binary path
|
||||||
|
"""
|
||||||
|
|
||||||
if Path(self.binary_path).exists():
|
if Path(self.binary_path).exists():
|
||||||
plist_path = self.binary_path.replace("MacOS/OpenCore-Patcher", "Info.plist")
|
plist_path = self.binary_path.replace("MacOS/OpenCore-Patcher", "Info.plist")
|
||||||
if Path(plist_path).exists() and plist_path.endswith(".plist"):
|
if Path(plist_path).exists() and plist_path.endswith(".plist"):
|
||||||
return plist_path
|
return plist_path
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def generate_commit_info(self):
|
def generate_commit_info(self):
|
||||||
|
"""
|
||||||
|
Generate commit info from Info.plist
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (Branch, Commit Date, Commit URL)
|
||||||
|
"""
|
||||||
|
|
||||||
if self.plist_path:
|
if self.plist_path:
|
||||||
plist_info = plistlib.load(Path(self.plist_path).open("rb"))
|
plist_info = plistlib.load(Path(self.plist_path).open("rb"))
|
||||||
if "Github" in plist_info:
|
if "Github" in plist_info:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class Constants:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
# Patcher Versioning
|
# Patcher Versioning
|
||||||
self.patcher_version = "0.6.2" # OpenCore-Legacy-Patcher
|
self.patcher_version = "0.6.2" # OpenCore-Legacy-Patcher
|
||||||
self.patcher_support_pkg_version = "0.8.3" # PatcherSupportPkg
|
self.patcher_support_pkg_version = "0.8.5" # PatcherSupportPkg
|
||||||
self.url_patcher_support_pkg = "https://github.com/dortania/PatcherSupportPkg/releases/download/"
|
self.url_patcher_support_pkg = "https://github.com/dortania/PatcherSupportPkg/releases/download/"
|
||||||
self.nightly_url_patcher_support_pkg = "https://nightly.link/dortania/PatcherSupportPkg/workflows/build/master/"
|
self.nightly_url_patcher_support_pkg = "https://nightly.link/dortania/PatcherSupportPkg/workflows/build/master/"
|
||||||
self.discord_link = "https://discord.gg/rqdPgH8xSN"
|
self.discord_link = "https://discord.gg/rqdPgH8xSN"
|
||||||
@@ -44,7 +44,7 @@ class Constants:
|
|||||||
self.cpufriend_version = "1.2.6" # CPUFriend
|
self.cpufriend_version = "1.2.6" # CPUFriend
|
||||||
self.bluetool_version = "2.6.4" # BlueToolFixup (BrcmPatchRAM)
|
self.bluetool_version = "2.6.4" # BlueToolFixup (BrcmPatchRAM)
|
||||||
self.cslvfixup_version = "2.6.1" # CSLVFixup
|
self.cslvfixup_version = "2.6.1" # CSLVFixup
|
||||||
self.autopkg_version = "1.0.1" # AutoPkgInstaller
|
self.autopkg_version = "1.0.2" # AutoPkgInstaller
|
||||||
self.cryptexfixup_version = "1.0.1" # CryptexFixup
|
self.cryptexfixup_version = "1.0.1" # CryptexFixup
|
||||||
|
|
||||||
## Apple
|
## Apple
|
||||||
|
|||||||
+76
-37
@@ -1,42 +1,60 @@
|
|||||||
# Generate Default Data
|
# Generate Default Data
|
||||||
from resources import utilities, device_probe, generate_smbios, global_settings
|
|
||||||
from data import smbios_data, cpu_data, os_data
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
from resources import (
|
||||||
|
utilities,
|
||||||
|
device_probe,
|
||||||
|
generate_smbios,
|
||||||
|
global_settings,
|
||||||
|
constants
|
||||||
|
)
|
||||||
|
from data import (
|
||||||
|
smbios_data,
|
||||||
|
cpu_data,
|
||||||
|
os_data
|
||||||
|
)
|
||||||
|
|
||||||
class generate_defaults:
|
|
||||||
|
|
||||||
def __init__(self, model, host_is_target, settings):
|
class GenerateDefaults:
|
||||||
self.model = model
|
|
||||||
self.constants = settings
|
def __init__(self, model: str, host_is_target: bool, global_constants: constants.Constants):
|
||||||
self.host_is_target = host_is_target
|
self.constants: constants.Constants = global_constants
|
||||||
|
|
||||||
|
self.model: str = model
|
||||||
|
|
||||||
|
self.host_is_target: bool = host_is_target
|
||||||
|
|
||||||
# Reset Variables
|
# Reset Variables
|
||||||
self.constants.sip_status = True
|
self.constants.sip_status: bool = True
|
||||||
self.constants.secure_status = False
|
self.constants.secure_status: bool = False
|
||||||
self.constants.disable_cs_lv = False
|
self.constants.disable_cs_lv: bool = False
|
||||||
self.constants.disable_amfi = False
|
self.constants.disable_amfi: bool = False
|
||||||
self.constants.fu_status = True
|
self.constants.fu_status: bool = True
|
||||||
self.constants.fu_arguments = None
|
|
||||||
|
|
||||||
self.constants.custom_serial_number = ""
|
self.constants.fu_arguments: str = None
|
||||||
self.constants.custom_board_serial_number = ""
|
|
||||||
|
self.constants.custom_serial_number: str = ""
|
||||||
|
self.constants.custom_board_serial_number: str = ""
|
||||||
|
|
||||||
|
if self.host_is_target is True:
|
||||||
|
for gpu in self.constants.computer.gpus:
|
||||||
|
if gpu.device_id_unspoofed == -1:
|
||||||
|
gpu.device_id_unspoofed = gpu.device_id
|
||||||
|
if gpu.vendor_id_unspoofed == -1:
|
||||||
|
gpu.vendor_id_unspoofed = gpu.vendor_id
|
||||||
|
|
||||||
for gpu in self.constants.computer.gpus:
|
self._general_probe()
|
||||||
if gpu.device_id_unspoofed == -1:
|
self._nvram_probe()
|
||||||
gpu.device_id_unspoofed = gpu.device_id
|
self._gpu_probe()
|
||||||
if gpu.vendor_id_unspoofed == -1:
|
self._networking_probe()
|
||||||
gpu.vendor_id_unspoofed = gpu.vendor_id
|
self._misc_hardwares_probe()
|
||||||
|
self._smbios_probe()
|
||||||
self.general_probe()
|
|
||||||
self.nvram_probe()
|
|
||||||
self.gpu_probe()
|
|
||||||
self.networking_probe()
|
|
||||||
self.misc_hardwares_probe()
|
|
||||||
self.smbios_probe()
|
|
||||||
|
|
||||||
|
|
||||||
def general_probe(self):
|
def _general_probe(self):
|
||||||
|
"""
|
||||||
|
General probe for data
|
||||||
|
"""
|
||||||
|
|
||||||
if "Book" in self.model:
|
if "Book" in self.model:
|
||||||
self.constants.set_content_caching = False
|
self.constants.set_content_caching = False
|
||||||
@@ -46,11 +64,11 @@ class generate_defaults:
|
|||||||
if self.model in ["MacBookPro8,2", "MacBookPro8,3"]:
|
if self.model in ["MacBookPro8,2", "MacBookPro8,3"]:
|
||||||
# Users disabling TS2 most likely have a faulty dGPU
|
# Users disabling TS2 most likely have a faulty dGPU
|
||||||
# users can override this in settings
|
# users can override this in settings
|
||||||
ts2_status = global_settings.global_settings().read_property("MacBookPro_TeraScale_2_Accel")
|
ts2_status = global_settings.GlobalEnviromentSettings().read_property("MacBookPro_TeraScale_2_Accel")
|
||||||
if ts2_status is True:
|
if ts2_status is True:
|
||||||
self.constants.allow_ts2_accel = True
|
self.constants.allow_ts2_accel = True
|
||||||
else:
|
else:
|
||||||
global_settings.global_settings().write_property("MacBookPro_TeraScale_2_Accel", False)
|
global_settings.GlobalEnviromentSettings().write_property("MacBookPro_TeraScale_2_Accel", False)
|
||||||
self.constants.allow_ts2_accel = False
|
self.constants.allow_ts2_accel = False
|
||||||
|
|
||||||
if self.model in smbios_data.smbios_dictionary:
|
if self.model in smbios_data.smbios_dictionary:
|
||||||
@@ -67,14 +85,19 @@ class generate_defaults:
|
|||||||
# Check if running in RecoveryOS
|
# Check if running in RecoveryOS
|
||||||
self.constants.recovery_status = utilities.check_recovery()
|
self.constants.recovery_status = utilities.check_recovery()
|
||||||
|
|
||||||
if global_settings.global_settings().read_property("Force_Web_Drivers") is True:
|
if global_settings.GlobalEnviromentSettings().read_property("Force_Web_Drivers") is True:
|
||||||
self.constants.force_nv_web = True
|
self.constants.force_nv_web = True
|
||||||
|
|
||||||
result = global_settings.global_settings().read_property("ShouldNukeKDKs")
|
result = global_settings.GlobalEnviromentSettings().read_property("ShouldNukeKDKs")
|
||||||
if result is False:
|
if result is False:
|
||||||
self.constants.should_nuke_kdks = False
|
self.constants.should_nuke_kdks = False
|
||||||
|
|
||||||
def smbios_probe(self):
|
|
||||||
|
def _smbios_probe(self):
|
||||||
|
"""
|
||||||
|
SMBIOS specific probe
|
||||||
|
"""
|
||||||
|
|
||||||
if not self.host_is_target:
|
if not self.host_is_target:
|
||||||
if self.model in ["MacPro4,1", "MacPro5,1"]:
|
if self.model in ["MacPro4,1", "MacPro5,1"]:
|
||||||
# Allow H.265 on AMD
|
# Allow H.265 on AMD
|
||||||
@@ -105,7 +128,11 @@ class generate_defaults:
|
|||||||
self.constants.force_vmm = False
|
self.constants.force_vmm = False
|
||||||
|
|
||||||
|
|
||||||
def nvram_probe(self):
|
def _nvram_probe(self):
|
||||||
|
"""
|
||||||
|
NVRAM specific probe
|
||||||
|
"""
|
||||||
|
|
||||||
if not self.host_is_target:
|
if not self.host_is_target:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -126,7 +153,11 @@ class generate_defaults:
|
|||||||
self.constants.custom_cpu_model_value = custom_cpu_model_value.split("%00")[0]
|
self.constants.custom_cpu_model_value = custom_cpu_model_value.split("%00")[0]
|
||||||
|
|
||||||
|
|
||||||
def networking_probe(self):
|
def _networking_probe(self):
|
||||||
|
"""
|
||||||
|
Networking specific probe
|
||||||
|
"""
|
||||||
|
|
||||||
if self.host_is_target:
|
if self.host_is_target:
|
||||||
if not (
|
if not (
|
||||||
(
|
(
|
||||||
@@ -163,7 +194,11 @@ class generate_defaults:
|
|||||||
self.constants.fu_status = True
|
self.constants.fu_status = True
|
||||||
self.constants.fu_arguments = " -disable_sidecar_mac"
|
self.constants.fu_arguments = " -disable_sidecar_mac"
|
||||||
|
|
||||||
def misc_hardwares_probe(self):
|
|
||||||
|
def _misc_hardwares_probe(self):
|
||||||
|
"""
|
||||||
|
Misc probe
|
||||||
|
"""
|
||||||
if self.host_is_target:
|
if self.host_is_target:
|
||||||
if self.constants.computer.usb_controllers:
|
if self.constants.computer.usb_controllers:
|
||||||
if self.model in smbios_data.smbios_dictionary:
|
if self.model in smbios_data.smbios_dictionary:
|
||||||
@@ -176,7 +211,11 @@ class generate_defaults:
|
|||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def gpu_probe(self):
|
def _gpu_probe(self):
|
||||||
|
"""
|
||||||
|
Graphics specific probe
|
||||||
|
"""
|
||||||
|
|
||||||
gpu_dict = []
|
gpu_dict = []
|
||||||
if self.host_is_target:
|
if self.host_is_target:
|
||||||
gpu_dict = self.constants.computer.gpus
|
gpu_dict = self.constants.computer.gpus
|
||||||
|
|||||||
@@ -6,32 +6,42 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import plistlib
|
import plistlib
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
class global_settings:
|
|
||||||
|
class GlobalEnviromentSettings:
|
||||||
|
"""
|
||||||
|
Library for querying and writing global enviroment settings
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.file_name = ".com.dortania.opencore-legacy-patcher.plist"
|
self.file_name: str = ".com.dortania.opencore-legacy-patcher.plist"
|
||||||
self.global_settings_folder = "/Users/Shared"
|
self.global_settings_folder: str = "/Users/Shared"
|
||||||
self.global_settings_plist = f"{self.global_settings_folder}/{self.file_name}"
|
self.global_settings_plist: str = f"{self.global_settings_folder}/{self.file_name}"
|
||||||
self.generate_settings_file()
|
|
||||||
self.convert_defaults_to_global_settings()
|
|
||||||
|
|
||||||
def generate_settings_file(self):
|
self._generate_settings_file()
|
||||||
if Path(self.global_settings_plist).exists():
|
self._convert_defaults_to_global_settings()
|
||||||
return
|
self._fix_file_permission()
|
||||||
try:
|
|
||||||
plistlib.dump({"Developed by Dortania": True,}, Path(self.global_settings_plist).open("wb"))
|
|
||||||
except PermissionError:
|
def read_property(self, property_name: str):
|
||||||
logging.info("- Permission error: Unable to write to global settings file")
|
"""
|
||||||
|
Reads a property from the global settings file
|
||||||
|
"""
|
||||||
|
|
||||||
def read_property(self, property_name):
|
|
||||||
if Path(self.global_settings_plist).exists():
|
if Path(self.global_settings_plist).exists():
|
||||||
plist = plistlib.load(Path(self.global_settings_plist).open("rb"))
|
plist = plistlib.load(Path(self.global_settings_plist).open("rb"))
|
||||||
if property_name in plist:
|
if property_name in plist:
|
||||||
return plist[property_name]
|
return plist[property_name]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def write_property(self, property_name, property_value):
|
|
||||||
|
def write_property(self, property_name: str, property_value):
|
||||||
|
"""
|
||||||
|
Writes a property to the global settings file
|
||||||
|
"""
|
||||||
|
|
||||||
if Path(self.global_settings_plist).exists():
|
if Path(self.global_settings_plist).exists():
|
||||||
plist = plistlib.load(Path(self.global_settings_plist).open("rb"))
|
plist = plistlib.load(Path(self.global_settings_plist).open("rb"))
|
||||||
plist[property_name] = property_value
|
plist[property_name] = property_value
|
||||||
@@ -41,7 +51,20 @@ class global_settings:
|
|||||||
logging.info("- Failed to write to global settings file")
|
logging.info("- Failed to write to global settings file")
|
||||||
|
|
||||||
|
|
||||||
def convert_defaults_to_global_settings(self):
|
def _generate_settings_file(self):
|
||||||
|
if Path(self.global_settings_plist).exists():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
plistlib.dump({"Developed by Dortania": True,}, Path(self.global_settings_plist).open("wb"))
|
||||||
|
except PermissionError:
|
||||||
|
logging.info("- Permission error: Unable to write to global settings file")
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_defaults_to_global_settings(self):
|
||||||
|
"""
|
||||||
|
Converts legacy defaults to global settings
|
||||||
|
"""
|
||||||
|
|
||||||
defaults_path = "~/Library/Preferences/com.dortania.opencore-legacy-patcher.plist"
|
defaults_path = "~/Library/Preferences/com.dortania.opencore-legacy-patcher.plist"
|
||||||
defaults_path = Path(defaults_path).expanduser()
|
defaults_path = Path(defaults_path).expanduser()
|
||||||
|
|
||||||
@@ -60,4 +83,23 @@ class global_settings:
|
|||||||
try:
|
try:
|
||||||
Path(defaults_path).unlink()
|
Path(defaults_path).unlink()
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
logging.info("- Permission error: Unable to delete defaults plist")
|
logging.info("- Permission error: Unable to delete defaults plist")
|
||||||
|
|
||||||
|
|
||||||
|
def _fix_file_permission(self):
|
||||||
|
"""
|
||||||
|
Fixes file permission for log file
|
||||||
|
|
||||||
|
If OCLP was invoked as root, file permission will only allow root to write to settings file
|
||||||
|
This in turn breaks normal OCLP execution to write to settings file
|
||||||
|
"""
|
||||||
|
|
||||||
|
if os.geteuid() != 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Set file permission to allow any user to write to log file
|
||||||
|
result = subprocess.run(["chmod", "777", self.global_settings_plist], capture_output=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
logging.warning("- Failed to fix settings file permissions:")
|
||||||
|
if result.stderr:
|
||||||
|
logging.warning(result.stderr.decode("utf-8"))
|
||||||
+191
-65
@@ -3,28 +3,51 @@
|
|||||||
# Currently Work in Progress
|
# Currently Work in Progress
|
||||||
|
|
||||||
import plistlib
|
import plistlib
|
||||||
import wx
|
|
||||||
import sys
|
|
||||||
import webbrowser
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
import os
|
|
||||||
import wx.adv
|
|
||||||
from wx.lib.agw import hyperlink
|
|
||||||
import threading
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
import binascii
|
import binascii
|
||||||
import hashlib
|
import hashlib
|
||||||
from datetime import datetime
|
|
||||||
import py_sip_xnu
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from resources import constants, defaults, install, installer, utilities, run, generate_smbios, updates, integrity_verification, global_settings, kdk_handler, network_handler
|
import logging
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import wx
|
||||||
|
import wx.adv
|
||||||
|
from wx.lib.agw import hyperlink
|
||||||
|
|
||||||
|
import py_sip_xnu
|
||||||
|
|
||||||
|
from resources import (
|
||||||
|
constants,
|
||||||
|
defaults,
|
||||||
|
install,
|
||||||
|
utilities,
|
||||||
|
generate_smbios,
|
||||||
|
updates,
|
||||||
|
integrity_verification,
|
||||||
|
global_settings,
|
||||||
|
kdk_handler,
|
||||||
|
network_handler,
|
||||||
|
macos_installer_handler
|
||||||
|
)
|
||||||
|
|
||||||
from resources.sys_patch import sys_patch_detect, sys_patch
|
from resources.sys_patch import sys_patch_detect, sys_patch
|
||||||
from resources.build import build
|
from resources.build import build
|
||||||
from data import model_array, os_data, smbios_data, sip_data, cpu_data
|
|
||||||
from resources.gui import menu_redirect, gui_help
|
from resources.gui import menu_redirect, gui_help
|
||||||
|
|
||||||
|
from data import model_array, os_data, smbios_data, sip_data, cpu_data
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class wx_python_gui:
|
class wx_python_gui:
|
||||||
def __init__(self, versions, frame=None, frame_modal=None):
|
def __init__(self, versions, frame=None, frame_modal=None):
|
||||||
@@ -232,7 +255,7 @@ class wx_python_gui:
|
|||||||
if local_build_date <= installed_build_date:
|
if local_build_date <= installed_build_date:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif updates.check_binary_updates(self.constants).check_if_build_newer(local_version, application_version) is False:
|
elif updates.CheckBinaryUpdates(self.constants)._check_if_build_newer(local_version, application_version) is False:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Ask user if they want to move the application to the Applications folder
|
# Ask user if they want to move the application to the Applications folder
|
||||||
@@ -283,11 +306,11 @@ class wx_python_gui:
|
|||||||
return
|
return
|
||||||
|
|
||||||
did_find_update = False
|
did_find_update = False
|
||||||
ignore_updates = global_settings.global_settings().read_property("IgnoreAppUpdates")
|
ignore_updates = global_settings.GlobalEnviromentSettings().read_property("IgnoreAppUpdates")
|
||||||
if ignore_updates is not True:
|
if ignore_updates is not True:
|
||||||
self.constants.ignore_updates = False
|
self.constants.ignore_updates = False
|
||||||
self.constants.has_checked_updates = True
|
self.constants.has_checked_updates = True
|
||||||
dict = updates.check_binary_updates(self.constants).check_binary_updates()
|
dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates()
|
||||||
if dict:
|
if dict:
|
||||||
for entry in dict:
|
for entry in dict:
|
||||||
version = dict[entry]["Version"]
|
version = dict[entry]["Version"]
|
||||||
@@ -307,7 +330,7 @@ class wx_python_gui:
|
|||||||
elif response == wx.ID_NO:
|
elif response == wx.ID_NO:
|
||||||
logging.info("- Setting IgnoreAppUpdates to True")
|
logging.info("- Setting IgnoreAppUpdates to True")
|
||||||
self.constants.ignore_updates = True
|
self.constants.ignore_updates = True
|
||||||
global_settings.global_settings().write_property("IgnoreAppUpdates", True)
|
global_settings.GlobalEnviromentSettings().write_property("IgnoreAppUpdates", True)
|
||||||
else:
|
else:
|
||||||
self.constants.ignore_updates = True
|
self.constants.ignore_updates = True
|
||||||
logging.info("- Ignoring App Updates due to defaults")
|
logging.info("- Ignoring App Updates due to defaults")
|
||||||
@@ -565,10 +588,10 @@ class wx_python_gui:
|
|||||||
if self.constants.start_build_install is True:
|
if self.constants.start_build_install is True:
|
||||||
self.build_install_menu()
|
self.build_install_menu()
|
||||||
elif "--gui_patch" in sys.argv:
|
elif "--gui_patch" in sys.argv:
|
||||||
self.patches = sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).detect_patch_set()
|
self.patches = sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).detect_patch_set()
|
||||||
self.root_patch_start()
|
self.root_patch_start()
|
||||||
elif "--gui_unpatch" in sys.argv:
|
elif "--gui_unpatch" in sys.argv:
|
||||||
self.patches = sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).detect_patch_set()
|
self.patches = sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).detect_patch_set()
|
||||||
self.root_patch_revert()
|
self.root_patch_revert()
|
||||||
self.finished_auto_patch = True
|
self.finished_auto_patch = True
|
||||||
self.constants.start_build_install = False
|
self.constants.start_build_install = False
|
||||||
@@ -1055,7 +1078,7 @@ class wx_python_gui:
|
|||||||
)
|
)
|
||||||
self.subheader.Centre(wx.HORIZONTAL)
|
self.subheader.Centre(wx.HORIZONTAL)
|
||||||
|
|
||||||
patches = sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).detect_patch_set()
|
patches = sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).detect_patch_set()
|
||||||
self.patches = patches
|
self.patches = patches
|
||||||
can_unpatch = patches["Validation: Unpatching Possible"]
|
can_unpatch = patches["Validation: Unpatching Possible"]
|
||||||
if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches):
|
if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches):
|
||||||
@@ -1263,24 +1286,36 @@ class wx_python_gui:
|
|||||||
self.pulse_alternative(self.progress_bar)
|
self.pulse_alternative(self.progress_bar)
|
||||||
wx.GetApp().Yield()
|
wx.GetApp().Yield()
|
||||||
|
|
||||||
self.progress_bar.Hide()
|
|
||||||
|
|
||||||
if self.patches["Settings: Kernel Debug Kit missing"] is True:
|
if self.patches["Settings: Kernel Debug Kit missing"] is True:
|
||||||
# Download KDK (if needed)
|
# Download KDK (if needed)
|
||||||
self.subheader.SetLabel("Downloading Kernel Debug Kit")
|
self.subheader.SetLabel("Downloading Kernel Debug Kit")
|
||||||
self.subheader.Centre(wx.HORIZONTAL)
|
self.subheader.Centre(wx.HORIZONTAL)
|
||||||
self.developer_note.SetLabel("Starting shortly")
|
self.developer_note.SetLabel("Starting shortly")
|
||||||
|
|
||||||
|
wx.GetApp().Yield()
|
||||||
|
|
||||||
kdk_result = False
|
kdk_result = False
|
||||||
kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version)
|
self.kdk_obj = None
|
||||||
if kdk_obj.success is True:
|
def kdk_thread_spawn():
|
||||||
kdk_download_obj = kdk_obj.retrieve_download()
|
self.kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version)
|
||||||
|
|
||||||
|
kdk_thread = threading.Thread(target=kdk_thread_spawn)
|
||||||
|
kdk_thread.start()
|
||||||
|
|
||||||
|
while kdk_thread.is_alive():
|
||||||
|
self.pulse_alternative(self.progress_bar)
|
||||||
|
wx.GetApp().Yield()
|
||||||
|
|
||||||
|
self.progress_bar.Hide()
|
||||||
|
|
||||||
|
if self.kdk_obj.success is True:
|
||||||
|
kdk_download_obj = self.kdk_obj.retrieve_download()
|
||||||
if not kdk_download_obj:
|
if not kdk_download_obj:
|
||||||
kdk_result = True
|
kdk_result = True
|
||||||
else:
|
else:
|
||||||
kdk_download_obj.download()
|
kdk_download_obj.download()
|
||||||
|
|
||||||
self.header.SetLabel(f"Downloading KDK Build: {kdk_obj.kdk_url_build}")
|
self.header.SetLabel(f"Downloading KDK Build: {self.kdk_obj.kdk_url_build}")
|
||||||
self.header.Centre(wx.HORIZONTAL)
|
self.header.Centre(wx.HORIZONTAL)
|
||||||
|
|
||||||
self.progress_bar.SetValue(0)
|
self.progress_bar.SetValue(0)
|
||||||
@@ -1304,7 +1339,7 @@ class wx_python_gui:
|
|||||||
)
|
)
|
||||||
self.developer_note.Centre(wx.HORIZONTAL)
|
self.developer_note.Centre(wx.HORIZONTAL)
|
||||||
|
|
||||||
self.progress_bar.SetValue(kdk_download_obj.get_percent())
|
self.progress_bar.SetValue(int(kdk_download_obj.get_percent()))
|
||||||
|
|
||||||
wx.GetApp().Yield()
|
wx.GetApp().Yield()
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
@@ -1314,12 +1349,12 @@ class wx_python_gui:
|
|||||||
logging.error(kdk_download_obj.error_msg)
|
logging.error(kdk_download_obj.error_msg)
|
||||||
error_msg = kdk_download_obj.error_msg
|
error_msg = kdk_download_obj.error_msg
|
||||||
else:
|
else:
|
||||||
kdk_result = kdk_obj.validate_kdk_checksum()
|
kdk_result = self.kdk_obj.validate_kdk_checksum()
|
||||||
error_msg = kdk_obj.error_msg
|
error_msg = self.kdk_obj.error_msg
|
||||||
else:
|
else:
|
||||||
logging.error("Failed to download KDK")
|
logging.error("Failed to download KDK")
|
||||||
logging.error(kdk_obj.error_msg)
|
logging.error(self.kdk_obj.error_msg)
|
||||||
error_msg = kdk_obj.error_msg
|
error_msg = self.kdk_obj.error_msg
|
||||||
|
|
||||||
if kdk_result is False:
|
if kdk_result is False:
|
||||||
# Create popup window to inform user of error
|
# Create popup window to inform user of error
|
||||||
@@ -1617,7 +1652,9 @@ class wx_python_gui:
|
|||||||
# Download installer catalog
|
# Download installer catalog
|
||||||
if ias is None:
|
if ias is None:
|
||||||
def ia():
|
def ia():
|
||||||
self.available_installers = installer.list_downloadable_macOS_installers(self.constants.payload_path, "DeveloperSeed")
|
remote_obj = macos_installer_handler.RemoteInstallerCatalog(seed_override=macos_installer_handler.SeedType.DeveloperSeed)
|
||||||
|
self.available_installers = remote_obj.available_apps
|
||||||
|
self.available_installers_latest = remote_obj.available_apps_latest
|
||||||
|
|
||||||
logging.info("- Downloading installer catalog...")
|
logging.info("- Downloading installer catalog...")
|
||||||
thread_ia = threading.Thread(target=ia)
|
thread_ia = threading.Thread(target=ia)
|
||||||
@@ -1655,7 +1692,7 @@ class wx_python_gui:
|
|||||||
i = -20
|
i = -20
|
||||||
if available_installers:
|
if available_installers:
|
||||||
if ias is None:
|
if ias is None:
|
||||||
available_installers = installer.only_list_newest_installers(available_installers)
|
available_installers = self.available_installers_latest
|
||||||
for app in available_installers:
|
for app in available_installers:
|
||||||
logging.info(f"macOS {available_installers[app]['Version']} ({available_installers[app]['Build']}):\n - Size: {utilities.human_fmt(available_installers[app]['Size'])}\n - Source: {available_installers[app]['Source']}\n - Variant: {available_installers[app]['Variant']}\n - Link: {available_installers[app]['Link']}\n")
|
logging.info(f"macOS {available_installers[app]['Version']} ({available_installers[app]['Build']}):\n - Size: {utilities.human_fmt(available_installers[app]['Size'])}\n - Source: {available_installers[app]['Source']}\n - Variant: {available_installers[app]['Variant']}\n - Link: {available_installers[app]['Link']}\n")
|
||||||
if available_installers[app]['Variant'] in ["DeveloperSeed" , "PublicSeed"]:
|
if available_installers[app]['Variant'] in ["DeveloperSeed" , "PublicSeed"]:
|
||||||
@@ -1823,7 +1860,7 @@ class wx_python_gui:
|
|||||||
)
|
)
|
||||||
self.download_label_2.Centre(wx.HORIZONTAL)
|
self.download_label_2.Centre(wx.HORIZONTAL)
|
||||||
|
|
||||||
self.download_progress.SetValue(ia_download.get_percent())
|
self.download_progress.SetValue(int(ia_download.get_percent()))
|
||||||
|
|
||||||
wx.GetApp().Yield()
|
wx.GetApp().Yield()
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
@@ -1929,7 +1966,7 @@ class wx_python_gui:
|
|||||||
self.header.Centre(wx.HORIZONTAL)
|
self.header.Centre(wx.HORIZONTAL)
|
||||||
self.verifying_chunk_label.SetLabel("Installing into Applications folder")
|
self.verifying_chunk_label.SetLabel("Installing into Applications folder")
|
||||||
self.verifying_chunk_label.Centre(wx.HORIZONTAL)
|
self.verifying_chunk_label.Centre(wx.HORIZONTAL)
|
||||||
thread_install = threading.Thread(target=installer.install_macOS_installer, args=(self.constants.payload_path,))
|
thread_install = threading.Thread(target=macos_installer_handler.InstallerCreation().install_macOS_installer, args=(self.constants.payload_path,))
|
||||||
thread_install.start()
|
thread_install.start()
|
||||||
self.progress_bar.Pulse()
|
self.progress_bar.Pulse()
|
||||||
while thread_install.is_alive():
|
while thread_install.is_alive():
|
||||||
@@ -1966,7 +2003,7 @@ class wx_python_gui:
|
|||||||
|
|
||||||
# Spawn thread to get list of installers
|
# Spawn thread to get list of installers
|
||||||
def get_installers():
|
def get_installers():
|
||||||
self.available_installers = installer.list_local_macOS_installers()
|
self.available_installers = macos_installer_handler.LocalInstallerCatalog().available_apps
|
||||||
|
|
||||||
thread_get_installers = threading.Thread(target=get_installers)
|
thread_get_installers = threading.Thread(target=get_installers)
|
||||||
thread_get_installers.start()
|
thread_get_installers.start()
|
||||||
@@ -2070,7 +2107,7 @@ class wx_python_gui:
|
|||||||
self.usb_selection_label.Centre(wx.HORIZONTAL)
|
self.usb_selection_label.Centre(wx.HORIZONTAL)
|
||||||
|
|
||||||
i = -15
|
i = -15
|
||||||
available_disks = installer.list_disk_to_format()
|
available_disks = macos_installer_handler.InstallerCreation().list_disk_to_format()
|
||||||
if available_disks:
|
if available_disks:
|
||||||
logging.info("Disks found")
|
logging.info("Disks found")
|
||||||
for disk in available_disks:
|
for disk in available_disks:
|
||||||
@@ -2153,7 +2190,7 @@ class wx_python_gui:
|
|||||||
self.developer_note_label_2.Centre(wx.HORIZONTAL)
|
self.developer_note_label_2.Centre(wx.HORIZONTAL)
|
||||||
|
|
||||||
# Progress Bar
|
# Progress Bar
|
||||||
max_file_size = 1024 * 1024 * 1024 * 18 # 18GB, best guess for installer + chainloaded packages
|
max_file_size = 19000 # Best guess for installer + chainloaded packages
|
||||||
self.progress_bar = wx.Gauge(self.frame, range=max_file_size, size=(-1, 20))
|
self.progress_bar = wx.Gauge(self.frame, range=max_file_size, size=(-1, 20))
|
||||||
self.progress_bar.SetPosition(
|
self.progress_bar.SetPosition(
|
||||||
wx.Point(
|
wx.Point(
|
||||||
@@ -2225,7 +2262,7 @@ class wx_python_gui:
|
|||||||
output = float(utilities.monitor_disk_output(disk))
|
output = float(utilities.monitor_disk_output(disk))
|
||||||
bytes_written = output - default_output
|
bytes_written = output - default_output
|
||||||
if install_thread.is_alive():
|
if install_thread.is_alive():
|
||||||
self.progress_bar.SetValue(bytes_written)
|
self.progress_bar.SetValue(int(bytes_written))
|
||||||
self.progress_label.SetLabel(f"Bytes Written: {round(bytes_written, 2)}MB")
|
self.progress_label.SetLabel(f"Bytes Written: {round(bytes_written, 2)}MB")
|
||||||
wx.GetApp().Yield()
|
wx.GetApp().Yield()
|
||||||
else:
|
else:
|
||||||
@@ -2270,12 +2307,15 @@ class wx_python_gui:
|
|||||||
self.return_to_main_menu.Enable()
|
self.return_to_main_menu.Enable()
|
||||||
|
|
||||||
def prepare_script(self, installer_path, disk):
|
def prepare_script(self, installer_path, disk):
|
||||||
self.prepare_result = installer.generate_installer_creation_script(self.constants.payload_path, installer_path, disk)
|
self.prepare_result = macos_installer_handler.InstallerCreation().generate_installer_creation_script(self.constants.payload_path, installer_path, disk)
|
||||||
|
|
||||||
def start_script(self):
|
def start_script(self):
|
||||||
utilities.disable_sleep_while_running()
|
utilities.disable_sleep_while_running()
|
||||||
args = [self.constants.oclp_helper_path, "/bin/sh", self.constants.installer_sh_path]
|
args = [self.constants.oclp_helper_path, "/bin/sh", self.constants.installer_sh_path]
|
||||||
output, error, returncode = run.Run()._stream_output(comm=args)
|
result = subprocess.run(args, capture_output=True, text=True)
|
||||||
|
output = result.stdout
|
||||||
|
error = result.stderr if result.stderr else ""
|
||||||
|
|
||||||
if "Install media now available at" in output:
|
if "Install media now available at" in output:
|
||||||
logging.info("- Successfully created macOS installer")
|
logging.info("- Successfully created macOS installer")
|
||||||
while self.download_thread.is_alive():
|
while self.download_thread.is_alive():
|
||||||
@@ -2325,18 +2365,104 @@ class wx_python_gui:
|
|||||||
subprocess.run(["ditto", "-V", "-x", "-k", "--sequesterRsrc", "--rsrc", self.constants.installer_pkg_zip_path, self.constants.payload_path])
|
subprocess.run(["ditto", "-V", "-x", "-k", "--sequesterRsrc", "--rsrc", self.constants.installer_pkg_zip_path, self.constants.payload_path])
|
||||||
|
|
||||||
|
|
||||||
|
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 install_installer_pkg(self, disk):
|
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
|
disk = disk + "s2" # ESP sits at 1, and we know macOS will have created the main partition at 2
|
||||||
if Path(self.constants.installer_pkg_path).exists():
|
|
||||||
path = utilities.grab_mount_point_from_disk(disk)
|
if not Path(self.constants.installer_pkg_path).exists():
|
||||||
if 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"])
|
path = utilities.grab_mount_point_from_disk(disk)
|
||||||
if int(kernel_version) >= os_data.os_data.big_sur:
|
if not Path(path + "/System/Library/CoreServices/SystemVersion.plist").exists():
|
||||||
subprocess.run(["mkdir", "-p", f"{path}/Library/Packages/"])
|
return
|
||||||
subprocess.run(["cp", "-r", self.constants.installer_pkg_path, f"{path}/Library/Packages/"])
|
|
||||||
else:
|
os_version = plistlib.load(Path(path + "/System/Library/CoreServices/SystemVersion.plist").open("rb"))
|
||||||
logging.info("- Installer unsupported, requires Big Sur or newer")
|
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 settings_menu(self, event=None):
|
def settings_menu(self, event=None):
|
||||||
@@ -2523,11 +2649,11 @@ class wx_python_gui:
|
|||||||
if user_choice == self.computer.real_model:
|
if user_choice == self.computer.real_model:
|
||||||
logging.info(f"Using Real Model: {user_choice}")
|
logging.info(f"Using Real Model: {user_choice}")
|
||||||
self.constants.custom_model = None
|
self.constants.custom_model = None
|
||||||
defaults.generate_defaults(self.computer.real_model, True, self.constants)
|
defaults.GenerateDefaults(self.computer.real_model, True, self.constants)
|
||||||
else:
|
else:
|
||||||
logging.info(f"Using Custom Model: {user_choice}")
|
logging.info(f"Using Custom Model: {user_choice}")
|
||||||
self.constants.custom_model = user_choice
|
self.constants.custom_model = user_choice
|
||||||
defaults.generate_defaults(self.constants.custom_model, False, self.constants)
|
defaults.GenerateDefaults(self.constants.custom_model, False, self.constants)
|
||||||
# Reload Settings
|
# Reload Settings
|
||||||
self.settings_menu(None)
|
self.settings_menu(None)
|
||||||
|
|
||||||
@@ -2864,7 +2990,7 @@ class wx_python_gui:
|
|||||||
else:
|
else:
|
||||||
logging.info("Nuke KDKs disabled")
|
logging.info("Nuke KDKs disabled")
|
||||||
self.constants.should_nuke_kdks = False
|
self.constants.should_nuke_kdks = False
|
||||||
global_settings.global_settings().write_property("ShouldNukeKDKs", self.constants.should_nuke_kdks)
|
global_settings.GlobalEnviromentSettings().write_property("ShouldNukeKDKs", self.constants.should_nuke_kdks)
|
||||||
|
|
||||||
def disable_library_validation_click(self, event):
|
def disable_library_validation_click(self, event):
|
||||||
if self.disable_library_validation_checkbox.GetValue():
|
if self.disable_library_validation_checkbox.GetValue():
|
||||||
@@ -2887,9 +3013,9 @@ class wx_python_gui:
|
|||||||
def set_ignore_app_updates_click(self, event):
|
def set_ignore_app_updates_click(self, event):
|
||||||
self.constants.ignore_updates = self.set_ignore_app_updates_checkbox.GetValue()
|
self.constants.ignore_updates = self.set_ignore_app_updates_checkbox.GetValue()
|
||||||
if self.constants.ignore_updates is True:
|
if self.constants.ignore_updates is True:
|
||||||
global_settings.global_settings().write_property("IgnoreAppUpdates", True)
|
global_settings.GlobalEnviromentSettings().write_property("IgnoreAppUpdates", True)
|
||||||
else:
|
else:
|
||||||
global_settings.global_settings().write_property("IgnoreAppUpdates", False)
|
global_settings.GlobalEnviromentSettings().write_property("IgnoreAppUpdates", False)
|
||||||
|
|
||||||
def firewire_click(self, event=None):
|
def firewire_click(self, event=None):
|
||||||
if self.firewire_boot_checkbox.GetValue():
|
if self.firewire_boot_checkbox.GetValue():
|
||||||
@@ -2974,21 +3100,21 @@ class wx_python_gui:
|
|||||||
def ts2_accel_click(self, event=None):
|
def ts2_accel_click(self, event=None):
|
||||||
if self.set_terascale_accel_checkbox.GetValue():
|
if self.set_terascale_accel_checkbox.GetValue():
|
||||||
logging.info("TS2 Acceleration Enabled")
|
logging.info("TS2 Acceleration Enabled")
|
||||||
global_settings.global_settings().write_property("MacBookPro_TeraScale_2_Accel", True)
|
global_settings.GlobalEnviromentSettings().write_property("MacBookPro_TeraScale_2_Accel", True)
|
||||||
self.constants.allow_ts2_accel = True
|
self.constants.allow_ts2_accel = True
|
||||||
else:
|
else:
|
||||||
logging.info("TS2 Acceleration Disabled")
|
logging.info("TS2 Acceleration Disabled")
|
||||||
global_settings.global_settings().write_property("MacBookPro_TeraScale_2_Accel", False)
|
global_settings.GlobalEnviromentSettings().write_property("MacBookPro_TeraScale_2_Accel", False)
|
||||||
self.constants.allow_ts2_accel = False
|
self.constants.allow_ts2_accel = False
|
||||||
|
|
||||||
def force_web_drivers_click(self, event=None):
|
def force_web_drivers_click(self, event=None):
|
||||||
if self.force_web_drivers_checkbox.GetValue():
|
if self.force_web_drivers_checkbox.GetValue():
|
||||||
logging.info("Force Web Drivers Enabled")
|
logging.info("Force Web Drivers Enabled")
|
||||||
global_settings.global_settings().write_property("Force_Web_Drivers", True)
|
global_settings.GlobalEnviromentSettings().write_property("Force_Web_Drivers", True)
|
||||||
self.constants.force_nv_web = True
|
self.constants.force_nv_web = True
|
||||||
else:
|
else:
|
||||||
logging.info("Force Web Drivers Disabled")
|
logging.info("Force Web Drivers Disabled")
|
||||||
global_settings.global_settings().write_property("Force_Web_Drivers", False)
|
global_settings.GlobalEnviromentSettings().write_property("Force_Web_Drivers", False)
|
||||||
self.constants.force_nv_web = False
|
self.constants.force_nv_web = False
|
||||||
|
|
||||||
def windows_gmux_click(self, event=None):
|
def windows_gmux_click(self, event=None):
|
||||||
@@ -3698,19 +3824,19 @@ OpenCore Legacy Patcher by default knows the most ideal
|
|||||||
self.subheader_4.SetPosition(wx.Point(0, self.subheader2_2.GetPosition().y + self.subheader2_2.GetSize().height+ 5))
|
self.subheader_4.SetPosition(wx.Point(0, self.subheader2_2.GetPosition().y + self.subheader2_2.GetSize().height+ 5))
|
||||||
self.subheader_4.Centre(wx.HORIZONTAL)
|
self.subheader_4.Centre(wx.HORIZONTAL)
|
||||||
|
|
||||||
is_dark_menu_bar = subprocess.run(["defaults", "read", "-g", "Moraea_DarkMenuBar"], stdout=subprocess.PIPE).stdout.decode("utf-8").strip()
|
is_dark_menu_bar = subprocess.run(["defaults", "read", "-g", "Moraea_DarkMenuBar"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode("utf-8").strip()
|
||||||
if is_dark_menu_bar in ["1", "true"]:
|
if is_dark_menu_bar in ["1", "true"]:
|
||||||
is_dark_menu_bar = True
|
is_dark_menu_bar = True
|
||||||
else:
|
else:
|
||||||
is_dark_menu_bar = False
|
is_dark_menu_bar = False
|
||||||
|
|
||||||
is_blur_enabled = subprocess.run(["defaults", "read", "-g", "Moraea_BlurBeta"], stdout=subprocess.PIPE).stdout.decode("utf-8").strip()
|
is_blur_enabled = subprocess.run(["defaults", "read", "-g", "Moraea_BlurBeta"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode("utf-8").strip()
|
||||||
if is_blur_enabled in ["1", "true"]:
|
if is_blur_enabled in ["1", "true"]:
|
||||||
is_blur_enabled = True
|
is_blur_enabled = True
|
||||||
else:
|
else:
|
||||||
is_blur_enabled = False
|
is_blur_enabled = False
|
||||||
|
|
||||||
is_rim_disabled = subprocess.run(["defaults", "read", "-g", "Moraea_RimBetaDisabled"], stdout=subprocess.PIPE).stdout.decode("utf-8").strip()
|
is_rim_disabled = subprocess.run(["defaults", "read", "-g", "Moraea_RimBetaDisabled"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode("utf-8").strip()
|
||||||
if is_rim_disabled in ["1", "true"]:
|
if is_rim_disabled in ["1", "true"]:
|
||||||
is_rim_disabled = True
|
is_rim_disabled = True
|
||||||
else:
|
else:
|
||||||
|
|||||||
+8
-75
@@ -8,7 +8,7 @@ import shutil
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from resources import utilities, constants, tui_helpers
|
from resources import utilities, constants
|
||||||
from data import os_data
|
from data import os_data
|
||||||
|
|
||||||
class tui_disk_installation:
|
class tui_disk_installation:
|
||||||
@@ -75,65 +75,6 @@ class tui_disk_installation:
|
|||||||
return supported_partitions
|
return supported_partitions
|
||||||
|
|
||||||
|
|
||||||
def copy_efi(self):
|
|
||||||
utilities.cls()
|
|
||||||
utilities.header(["Installing OpenCore to Drive"])
|
|
||||||
|
|
||||||
if not self.constants.opencore_release_folder.exists():
|
|
||||||
tui_helpers.TUIOnlyLogging.info(
|
|
||||||
["Installing OpenCore to Drive"],
|
|
||||||
"Press [Enter] to go back.\n",
|
|
||||||
[
|
|
||||||
"""OpenCore folder missing!
|
|
||||||
Please build OpenCore first!"""
|
|
||||||
],
|
|
||||||
).start()
|
|
||||||
return
|
|
||||||
|
|
||||||
logging.info("\nDisk picker is loading...")
|
|
||||||
|
|
||||||
all_disks = self.list_disks()
|
|
||||||
menu = tui_helpers.TUIMenu(
|
|
||||||
["Select Disk"],
|
|
||||||
"Please select the disk you would like to install OpenCore to: ",
|
|
||||||
in_between=["Missing disks? Ensure they have an EFI or FAT32 partition."],
|
|
||||||
return_number_instead_of_direct_call=True,
|
|
||||||
loop=True,
|
|
||||||
)
|
|
||||||
for disk in all_disks:
|
|
||||||
menu.add_menu_option(f"{disk}: {all_disks[disk]['name']} ({all_disks[disk]['size']})", key=disk[4:])
|
|
||||||
|
|
||||||
response = menu.start()
|
|
||||||
|
|
||||||
if response == -1:
|
|
||||||
return
|
|
||||||
|
|
||||||
disk_identifier = "disk" + response
|
|
||||||
selected_disk = all_disks[disk_identifier]
|
|
||||||
|
|
||||||
menu = tui_helpers.TUIMenu(
|
|
||||||
["Select Partition"],
|
|
||||||
"Please select the partition you would like to install OpenCore to: ",
|
|
||||||
return_number_instead_of_direct_call=True,
|
|
||||||
loop=True,
|
|
||||||
in_between=["Missing partitions? Ensure they are formatted as an EFI or FAT32.", "", "* denotes likely candidate."],
|
|
||||||
)
|
|
||||||
for partition in selected_disk["partitions"]:
|
|
||||||
if selected_disk["partitions"][partition]["fs"] not in ("msdos", "EFI"):
|
|
||||||
continue
|
|
||||||
text = f"{partition}: {selected_disk['partitions'][partition]['name']} ({utilities.human_fmt(selected_disk['partitions'][partition]['size'])})"
|
|
||||||
if selected_disk["partitions"][partition]["type"] == "EFI" or (
|
|
||||||
selected_disk["partitions"][partition]["type"] == "Microsoft Basic Data" and selected_disk["partitions"][partition]["size"] < 1024 * 1024 * 512
|
|
||||||
): # 512 megabytes:
|
|
||||||
text += " *"
|
|
||||||
menu.add_menu_option(text, key=partition[len(disk_identifier) + 1 :])
|
|
||||||
|
|
||||||
response = menu.start()
|
|
||||||
|
|
||||||
if response == -1:
|
|
||||||
return
|
|
||||||
self.install_opencore(f"{disk_identifier}s{response}")
|
|
||||||
|
|
||||||
def install_opencore(self, full_disk_identifier):
|
def install_opencore(self, full_disk_identifier):
|
||||||
def determine_sd_card(media_name):
|
def determine_sd_card(media_name):
|
||||||
# Array filled with common SD Card names
|
# Array filled with common SD Card names
|
||||||
@@ -169,18 +110,13 @@ Please build OpenCore first!"""
|
|||||||
# cancelled prompt
|
# cancelled prompt
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
if self.constants.gui_mode is False:
|
logging.info("An error occurred!")
|
||||||
tui_helpers.TUIOnlyLogging.info(
|
logging.info(result.stderr.decode())
|
||||||
["Copying OpenCore"], "Press [Enter] to go back.\n", ["An error occurred!"] + result.stderr.decode().split("\n") + [""]
|
|
||||||
).start()
|
|
||||||
else:
|
|
||||||
logging.info("An error occurred!")
|
|
||||||
logging.info(result.stderr.decode())
|
|
||||||
|
|
||||||
# Check if we're in Safe Mode, and if so, tell user FAT32 is unsupported
|
# 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
|
||||||
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"]
|
||||||
@@ -252,10 +188,7 @@ Please build OpenCore first!"""
|
|||||||
logging.info("\nPress [Enter] to continue.\n")
|
logging.info("\nPress [Enter] to continue.\n")
|
||||||
input()
|
input()
|
||||||
else:
|
else:
|
||||||
if self.constants.gui_mode is False:
|
logging.info("EFI failed to mount!")
|
||||||
tui_helpers.TUIOnlyLogging.info(["Copying OpenCore"], "Press [Enter] to go back.\n", ["EFI failed to mount!"]).start()
|
|
||||||
else:
|
|
||||||
logging.info("EFI failed to mount!")
|
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -1,486 +0,0 @@
|
|||||||
# Creates a macOS Installer
|
|
||||||
from pathlib import Path
|
|
||||||
import plistlib
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
import logging
|
|
||||||
from resources import utilities, tui_helpers, network_handler
|
|
||||||
|
|
||||||
def list_local_macOS_installers():
|
|
||||||
# Finds all applicable macOS installers
|
|
||||||
# within a user's /Applications folder
|
|
||||||
# Returns a list of installers
|
|
||||||
application_list = {}
|
|
||||||
|
|
||||||
for application in Path("/Applications").iterdir():
|
|
||||||
# Verify whether application has createinstallmedia
|
|
||||||
try:
|
|
||||||
if (Path("/Applications") / Path(application) / Path("Contents/Resources/createinstallmedia")).exists():
|
|
||||||
plist = plistlib.load((Path("/Applications") / Path(application) / Path("Contents/Info.plist")).open("rb"))
|
|
||||||
try:
|
|
||||||
# Doesn't reflect true OS build, but best to report SDK in the event multiple installers are found with same version
|
|
||||||
app_version = plist["DTPlatformVersion"]
|
|
||||||
clean_name = plist["CFBundleDisplayName"]
|
|
||||||
try:
|
|
||||||
app_sdk = plist["DTSDKBuild"]
|
|
||||||
except KeyError:
|
|
||||||
app_sdk = "Unknown"
|
|
||||||
|
|
||||||
# app_version can sometimes report GM instead of the actual version
|
|
||||||
# This is a workaround to get the actual version
|
|
||||||
if app_version.startswith("GM"):
|
|
||||||
try:
|
|
||||||
app_version = int(app_sdk[:2])
|
|
||||||
if app_version < 20:
|
|
||||||
app_version = f"10.{app_version - 4}"
|
|
||||||
else:
|
|
||||||
app_version = f"{app_version - 9}.0"
|
|
||||||
except ValueError:
|
|
||||||
app_version = "Unknown"
|
|
||||||
# Check if App Version is High Sierra or newer
|
|
||||||
can_add = False
|
|
||||||
if app_version.startswith("10."):
|
|
||||||
app_sub_version = app_version.split(".")[1]
|
|
||||||
if int(app_sub_version) >= 13:
|
|
||||||
can_add = True
|
|
||||||
else:
|
|
||||||
can_add = False
|
|
||||||
else:
|
|
||||||
can_add = True
|
|
||||||
|
|
||||||
# Check SharedSupport.dmg's data
|
|
||||||
results = parse_sharedsupport_version(Path("/Applications") / Path(application)/ Path("Contents/SharedSupport/SharedSupport.dmg"))
|
|
||||||
if results[0] is not None:
|
|
||||||
app_sdk = results[0]
|
|
||||||
if results[1] is not None:
|
|
||||||
app_version = results[1]
|
|
||||||
|
|
||||||
if can_add is True:
|
|
||||||
application_list.update({
|
|
||||||
application: {
|
|
||||||
"Short Name": clean_name,
|
|
||||||
"Version": app_version,
|
|
||||||
"Build": app_sdk,
|
|
||||||
"Path": application,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
except PermissionError:
|
|
||||||
pass
|
|
||||||
# Sort Applications by version
|
|
||||||
application_list = {k: v for k, v in sorted(application_list.items(), key=lambda item: item[1]["Version"])}
|
|
||||||
return application_list
|
|
||||||
|
|
||||||
def parse_sharedsupport_version(sharedsupport_path):
|
|
||||||
detected_build = None
|
|
||||||
detected_os = None
|
|
||||||
sharedsupport_path = Path(sharedsupport_path)
|
|
||||||
|
|
||||||
if not sharedsupport_path.exists():
|
|
||||||
return (detected_build, detected_os)
|
|
||||||
|
|
||||||
if not sharedsupport_path.name.endswith(".dmg"):
|
|
||||||
return (detected_build, detected_os)
|
|
||||||
|
|
||||||
|
|
||||||
# Create temporary directory to extract SharedSupport.dmg to
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
|
||||||
output = subprocess.run(
|
|
||||||
[
|
|
||||||
"hdiutil", "attach", "-noverify", sharedsupport_path,
|
|
||||||
"-mountpoint", tmpdir,
|
|
||||||
"-nobrowse",
|
|
||||||
],
|
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
|
|
||||||
)
|
|
||||||
if output.returncode != 0:
|
|
||||||
return (detected_build, detected_os)
|
|
||||||
|
|
||||||
ss_info = Path("SFR/com_apple_MobileAsset_SFRSoftwareUpdate/com_apple_MobileAsset_SFRSoftwareUpdate.xml")
|
|
||||||
|
|
||||||
if Path(tmpdir / ss_info).exists():
|
|
||||||
plist = plistlib.load((tmpdir / ss_info).open("rb"))
|
|
||||||
if "Build" in plist["Assets"][0]:
|
|
||||||
detected_build = plist["Assets"][0]["Build"]
|
|
||||||
if "OSVersion" in plist["Assets"][0]:
|
|
||||||
detected_os = plist["Assets"][0]["OSVersion"]
|
|
||||||
|
|
||||||
# Unmount SharedSupport.dmg
|
|
||||||
output = subprocess.run(["hdiutil", "detach", tmpdir], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
||||||
|
|
||||||
return (detected_build, detected_os)
|
|
||||||
|
|
||||||
|
|
||||||
def create_installer(installer_path, volume_name):
|
|
||||||
# Creates a macOS installer
|
|
||||||
# Takes a path to the installer and the Volume
|
|
||||||
# Returns boolean on success status
|
|
||||||
|
|
||||||
createinstallmedia_path = Path("/Applications") / Path(installer_path) / Path("Contents/Resources/createinstallmedia")
|
|
||||||
|
|
||||||
# Sanity check in the event the user somehow deleted it between the time we found it and now
|
|
||||||
if (createinstallmedia_path).exists():
|
|
||||||
utilities.cls()
|
|
||||||
utilities.header(["Starting createinstallmedia"])
|
|
||||||
logging.info("This will take some time, recommend making some coffee while you wait\n")
|
|
||||||
utilities.elevated([createinstallmedia_path, "--volume", f"/Volumes/{volume_name}", "--nointeraction"])
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logging.info("- Failed to find createinstallmedia")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def download_install_assistant(download_path, ia_link):
|
|
||||||
# Downloads InstallAssistant.pkg
|
|
||||||
ia_download = network_handler.DownloadObject(ia_link, (Path(download_path) / Path("InstallAssistant.pkg")))
|
|
||||||
ia_download.download(display_progress=True, spawn_thread=False)
|
|
||||||
|
|
||||||
if ia_download.download_complete is True:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def install_macOS_installer(download_path):
|
|
||||||
logging.info("- Extracting macOS installer from InstallAssistant.pkg\n This may take some time")
|
|
||||||
args = [
|
|
||||||
"osascript",
|
|
||||||
"-e",
|
|
||||||
f'''do shell script "installer -pkg {Path(download_path)}/InstallAssistant.pkg -target /"'''
|
|
||||||
' with prompt "OpenCore Legacy Patcher needs administrator privileges to add InstallAssistant."'
|
|
||||||
" with administrator privileges"
|
|
||||||
" without altering line endings",
|
|
||||||
]
|
|
||||||
|
|
||||||
result = subprocess.run(args,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
if result.returncode == 0:
|
|
||||||
logging.info("- InstallAssistant installed")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logging.info("- Failed to install InstallAssistant")
|
|
||||||
logging.info(f" Error Code: {result.returncode}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def list_downloadable_macOS_installers(download_path, catalog):
|
|
||||||
available_apps = {}
|
|
||||||
if catalog == "DeveloperSeed":
|
|
||||||
link = "https://swscan.apple.com/content/catalogs/others/index-13seed-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog"
|
|
||||||
elif catalog == "PublicSeed":
|
|
||||||
link = "https://swscan.apple.com/content/catalogs/others/index-13beta-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog"
|
|
||||||
else:
|
|
||||||
link = "https://swscan.apple.com/content/catalogs/others/index-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog"
|
|
||||||
|
|
||||||
if network_handler.NetworkUtilities(link).verify_network_connection() is True:
|
|
||||||
try:
|
|
||||||
catalog_plist = plistlib.loads(network_handler.SESSION.get(link).content)
|
|
||||||
except plistlib.InvalidFileException:
|
|
||||||
return available_apps
|
|
||||||
|
|
||||||
for item in catalog_plist["Products"]:
|
|
||||||
try:
|
|
||||||
# Check if entry has SharedSupport and BuildManifest
|
|
||||||
# Ensures only Big Sur and newer Installers are listed
|
|
||||||
catalog_plist["Products"][item]["ExtendedMetaInfo"]["InstallAssistantPackageIdentifiers"]["SharedSupport"]
|
|
||||||
catalog_plist["Products"][item]["ExtendedMetaInfo"]["InstallAssistantPackageIdentifiers"]["BuildManifest"]
|
|
||||||
|
|
||||||
for bm_package in catalog_plist["Products"][item]["Packages"]:
|
|
||||||
if "Info.plist" in bm_package["URL"] and "InstallInfo.plist" not in bm_package["URL"]:
|
|
||||||
try:
|
|
||||||
build_plist = plistlib.loads(network_handler.SESSION.get(bm_package["URL"]).content)
|
|
||||||
except plistlib.InvalidFileException:
|
|
||||||
continue
|
|
||||||
# Ensure Apple Silicon specific Installers are not listed
|
|
||||||
if "VMM-x86_64" not in build_plist["MobileAssetProperties"]["SupportedDeviceModels"]:
|
|
||||||
continue
|
|
||||||
version = build_plist["MobileAssetProperties"]["OSVersion"]
|
|
||||||
build = build_plist["MobileAssetProperties"]["Build"]
|
|
||||||
try:
|
|
||||||
catalog_url = build_plist["MobileAssetProperties"]["BridgeVersionInfo"]["CatalogURL"]
|
|
||||||
if "beta" in catalog_url:
|
|
||||||
catalog_url = "PublicSeed"
|
|
||||||
elif "customerseed" in catalog_url:
|
|
||||||
catalog_url = "CustomerSeed"
|
|
||||||
elif "seed" in catalog_url:
|
|
||||||
catalog_url = "DeveloperSeed"
|
|
||||||
else:
|
|
||||||
catalog_url = "Public"
|
|
||||||
except KeyError:
|
|
||||||
# Assume Public if no catalog URL is found
|
|
||||||
catalog_url = "Public"
|
|
||||||
for ia_package in catalog_plist["Products"][item]["Packages"]:
|
|
||||||
if "InstallAssistant.pkg" in ia_package["URL"]:
|
|
||||||
download_link = ia_package["URL"]
|
|
||||||
size = ia_package["Size"]
|
|
||||||
integrity = ia_package["IntegrityDataURL"]
|
|
||||||
|
|
||||||
available_apps.update({
|
|
||||||
item: {
|
|
||||||
"Version": version,
|
|
||||||
"Build": build,
|
|
||||||
"Link": download_link,
|
|
||||||
"Size": size,
|
|
||||||
"integrity": integrity,
|
|
||||||
"Source": "Apple Inc.",
|
|
||||||
"Variant": catalog_url,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
available_apps = {k: v for k, v in sorted(available_apps.items(), key=lambda x: x[1]['Version'])}
|
|
||||||
return available_apps
|
|
||||||
|
|
||||||
def only_list_newest_installers(available_apps):
|
|
||||||
# Takes a dictionary of available installers
|
|
||||||
# Returns a dictionary of only the newest installers
|
|
||||||
# This is used to avoid overwhelming the user with installer options
|
|
||||||
|
|
||||||
# Only strip OSes that we know are supported
|
|
||||||
supported_versions = ["10.13", "10.14", "10.15", "11", "12", "13"]
|
|
||||||
|
|
||||||
for version in supported_versions:
|
|
||||||
remote_version_minor = 0
|
|
||||||
remote_version_security = 0
|
|
||||||
os_builds = []
|
|
||||||
|
|
||||||
# First determine the largest version
|
|
||||||
for ia in available_apps:
|
|
||||||
if available_apps[ia]["Version"].startswith(version):
|
|
||||||
if available_apps[ia]["Variant"] not in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]:
|
|
||||||
remote_version = available_apps[ia]["Version"].split(".")
|
|
||||||
if remote_version[0] == "10":
|
|
||||||
remote_version.pop(0)
|
|
||||||
remote_version.pop(0)
|
|
||||||
else:
|
|
||||||
remote_version.pop(0)
|
|
||||||
if int(remote_version[0]) > remote_version_minor:
|
|
||||||
remote_version_minor = int(remote_version[0])
|
|
||||||
remote_version_security = 0 # Reset as new minor version found
|
|
||||||
if len(remote_version) > 1:
|
|
||||||
if int(remote_version[1]) > remote_version_security:
|
|
||||||
remote_version_security = int(remote_version[1])
|
|
||||||
|
|
||||||
# Now remove all versions that are not the largest
|
|
||||||
for ia in list(available_apps):
|
|
||||||
# Don't use Beta builds to determine latest version
|
|
||||||
if available_apps[ia]["Variant"] in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if available_apps[ia]["Version"].startswith(version):
|
|
||||||
remote_version = available_apps[ia]["Version"].split(".")
|
|
||||||
if remote_version[0] == "10":
|
|
||||||
remote_version.pop(0)
|
|
||||||
remote_version.pop(0)
|
|
||||||
else:
|
|
||||||
remote_version.pop(0)
|
|
||||||
if int(remote_version[0]) < remote_version_minor:
|
|
||||||
available_apps.pop(ia)
|
|
||||||
continue
|
|
||||||
if int(remote_version[0]) == remote_version_minor:
|
|
||||||
if len(remote_version) > 1:
|
|
||||||
if int(remote_version[1]) < remote_version_security:
|
|
||||||
available_apps.pop(ia)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
if remote_version_security > 0:
|
|
||||||
available_apps.pop(ia)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Remove duplicate builds
|
|
||||||
# ex. macOS 12.5.1 has 2 builds in the Software Update Catalog
|
|
||||||
# ref: https://twitter.com/classicii_mrmac/status/1560357471654379522
|
|
||||||
if available_apps[ia]["Build"] in os_builds:
|
|
||||||
available_apps.pop(ia)
|
|
||||||
continue
|
|
||||||
|
|
||||||
os_builds.append(available_apps[ia]["Build"])
|
|
||||||
|
|
||||||
# Final passthrough
|
|
||||||
# Remove Betas if there's a non-beta version available
|
|
||||||
for ia in list(available_apps):
|
|
||||||
if available_apps[ia]["Variant"] in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]:
|
|
||||||
for ia2 in available_apps:
|
|
||||||
if available_apps[ia2]["Version"].split(".")[0] == available_apps[ia]["Version"].split(".")[0] and available_apps[ia2]["Variant"] not in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]:
|
|
||||||
available_apps.pop(ia)
|
|
||||||
break
|
|
||||||
|
|
||||||
return available_apps
|
|
||||||
|
|
||||||
def format_drive(disk_id):
|
|
||||||
# Formats a disk for macOS install
|
|
||||||
# Takes a disk ID
|
|
||||||
# Returns boolean on success status
|
|
||||||
header = f"# Formatting disk{disk_id} for macOS installer #"
|
|
||||||
box_length = len(header)
|
|
||||||
utilities.cls()
|
|
||||||
logging.info("#" * box_length)
|
|
||||||
logging.info(header)
|
|
||||||
logging.info("#" * box_length)
|
|
||||||
logging.info("")
|
|
||||||
#logging.info(f"- Formatting disk{disk_id} for macOS installer")
|
|
||||||
format_process = utilities.elevated(["diskutil", "eraseDisk", "HFS+", "OCLP-Installer", f"disk{disk_id}"])
|
|
||||||
if format_process.returncode == 0:
|
|
||||||
logging.info("- Disk formatted")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logging.info("- Failed to format disk")
|
|
||||||
logging.info(f" Error Code: {format_process.returncode}")
|
|
||||||
input("\nPress Enter to exit")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def select_disk_to_format():
|
|
||||||
utilities.cls()
|
|
||||||
utilities.header(["Installing OpenCore to Drive"])
|
|
||||||
|
|
||||||
logging.info("\nDisk picker is loading...")
|
|
||||||
|
|
||||||
all_disks = {}
|
|
||||||
# TODO: AllDisksAndPartitions is not supported in Snow Leopard and older
|
|
||||||
try:
|
|
||||||
# High Sierra and newer
|
|
||||||
disks = plistlib.loads(subprocess.run("diskutil list -plist physical".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
|
|
||||||
except ValueError:
|
|
||||||
# Sierra and older
|
|
||||||
disks = plistlib.loads(subprocess.run("diskutil list -plist".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
|
|
||||||
for disk in disks["AllDisksAndPartitions"]:
|
|
||||||
disk_info = plistlib.loads(subprocess.run(f"diskutil info -plist {disk['DeviceIdentifier']}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
|
|
||||||
try:
|
|
||||||
all_disks[disk["DeviceIdentifier"]] = {"identifier": disk_info["DeviceNode"], "name": disk_info["MediaName"], "size": disk_info["TotalSize"], "removable": disk_info["Internal"], "partitions": {}}
|
|
||||||
except KeyError:
|
|
||||||
# Avoid crashing with CDs installed
|
|
||||||
continue
|
|
||||||
menu = tui_helpers.TUIMenu(
|
|
||||||
["Select Disk to write the macOS Installer onto"],
|
|
||||||
"Please select the disk you would like to install OpenCore to: ",
|
|
||||||
in_between=["Missing drives? Verify they are 14GB+ and external (ie. USB)", "", "Ensure all data is backed up on selected drive, entire drive will be erased!"],
|
|
||||||
return_number_instead_of_direct_call=True,
|
|
||||||
loop=True,
|
|
||||||
)
|
|
||||||
for disk in all_disks:
|
|
||||||
# Strip disks that are under 14GB (15,032,385,536 bytes)
|
|
||||||
# createinstallmedia isn't great at detecting if a disk has enough space
|
|
||||||
if not any(all_disks[disk]['size'] > 15032385536 for partition in all_disks[disk]):
|
|
||||||
continue
|
|
||||||
# Strip internal disks as well (avoid user formatting their SSD/HDD)
|
|
||||||
# Ensure user doesn't format their boot drive
|
|
||||||
if not any(all_disks[disk]['removable'] is False for partition in all_disks[disk]):
|
|
||||||
continue
|
|
||||||
menu.add_menu_option(f"{disk}: {all_disks[disk]['name']} ({utilities.human_fmt(all_disks[disk]['size'])})", key=disk[4:])
|
|
||||||
|
|
||||||
response = menu.start()
|
|
||||||
|
|
||||||
if response == -1:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def list_disk_to_format():
|
|
||||||
all_disks = {}
|
|
||||||
list_disks = {}
|
|
||||||
# TODO: AllDisksAndPartitions is not supported in Snow Leopard and older
|
|
||||||
try:
|
|
||||||
# High Sierra and newer
|
|
||||||
disks = plistlib.loads(subprocess.run("diskutil list -plist physical".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
|
|
||||||
except ValueError:
|
|
||||||
# Sierra and older
|
|
||||||
disks = plistlib.loads(subprocess.run("diskutil list -plist".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
|
|
||||||
for disk in disks["AllDisksAndPartitions"]:
|
|
||||||
disk_info = plistlib.loads(subprocess.run(f"diskutil info -plist {disk['DeviceIdentifier']}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
|
|
||||||
try:
|
|
||||||
all_disks[disk["DeviceIdentifier"]] = {"identifier": disk_info["DeviceNode"], "name": disk_info["MediaName"], "size": disk_info["TotalSize"], "removable": disk_info["Internal"], "partitions": {}}
|
|
||||||
except KeyError:
|
|
||||||
# Avoid crashing with CDs installed
|
|
||||||
continue
|
|
||||||
for disk in all_disks:
|
|
||||||
# Strip disks that are under 14GB (15,032,385,536 bytes)
|
|
||||||
# createinstallmedia isn't great at detecting if a disk has enough space
|
|
||||||
if not any(all_disks[disk]['size'] > 15032385536 for partition in all_disks[disk]):
|
|
||||||
continue
|
|
||||||
# Strip internal disks as well (avoid user formatting their SSD/HDD)
|
|
||||||
# Ensure user doesn't format their boot drive
|
|
||||||
if not any(all_disks[disk]['removable'] is False for partition in all_disks[disk]):
|
|
||||||
continue
|
|
||||||
logging.info(f"disk {disk}: {all_disks[disk]['name']} ({utilities.human_fmt(all_disks[disk]['size'])})")
|
|
||||||
list_disks.update({
|
|
||||||
disk: {
|
|
||||||
"identifier": all_disks[disk]["identifier"],
|
|
||||||
"name": all_disks[disk]["name"],
|
|
||||||
"size": all_disks[disk]["size"],
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return list_disks
|
|
||||||
|
|
||||||
# Create global tmp directory
|
|
||||||
tmp_dir = tempfile.TemporaryDirectory()
|
|
||||||
|
|
||||||
def generate_installer_creation_script(tmp_location, installer_path, disk):
|
|
||||||
# Creates installer.sh to be piped to OCLP-Helper and run as admin
|
|
||||||
# Goals:
|
|
||||||
# - Format provided disk as HFS+ GPT
|
|
||||||
# - Run createinstallmedia on provided disk
|
|
||||||
# Implementing this into a single installer.sh script allows us to only call
|
|
||||||
# OCLP-Helper once to avoid nagging the user about permissions
|
|
||||||
|
|
||||||
additional_args = ""
|
|
||||||
script_location = Path(tmp_location) / Path("Installer.sh")
|
|
||||||
|
|
||||||
# Due to a bug in createinstallmedia, running from '/Applications' may sometimes error:
|
|
||||||
# 'Failed to extract AssetData/boot/Firmware/Manifests/InstallerBoot/*'
|
|
||||||
# This affects native Macs as well even when manually invoking createinstallmedia
|
|
||||||
|
|
||||||
# To resolve, we'll copy into our temp directory and run from there
|
|
||||||
|
|
||||||
# Create a new tmp directory
|
|
||||||
# Our current one is a disk image, thus CoW will not work
|
|
||||||
global tmp_dir
|
|
||||||
ia_tmp = tmp_dir.name
|
|
||||||
|
|
||||||
logging.info(f"Creating temporary directory at {ia_tmp}")
|
|
||||||
# Delete all files in tmp_dir
|
|
||||||
for file in Path(ia_tmp).glob("*"):
|
|
||||||
subprocess.run(["rm", "-rf", str(file)])
|
|
||||||
|
|
||||||
# Copy installer to tmp (use CoW to avoid extra disk writes)
|
|
||||||
args = ["cp", "-cR", installer_path, ia_tmp]
|
|
||||||
if utilities.check_filesystem_type() != "apfs":
|
|
||||||
# HFS+ disks do not support CoW
|
|
||||||
args[1] = "-R"
|
|
||||||
# Ensure we have enough space for the duplication
|
|
||||||
space_available = utilities.get_free_space()
|
|
||||||
space_needed = Path(ia_tmp).stat().st_size
|
|
||||||
if space_available < space_needed:
|
|
||||||
logging.info("Not enough free space to create installer.sh")
|
|
||||||
logging.info(f"{utilities.human_fmt(space_available)} available, {utilities.human_fmt(space_needed)} required")
|
|
||||||
return False
|
|
||||||
subprocess.run(args)
|
|
||||||
|
|
||||||
# Adjust installer_path to point to the copied installer
|
|
||||||
installer_path = Path(ia_tmp) / Path(Path(installer_path).name)
|
|
||||||
if not Path(installer_path).exists():
|
|
||||||
logging.info(f"Failed to copy installer to {ia_tmp}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
createinstallmedia_path = str(Path(installer_path) / Path("Contents/Resources/createinstallmedia"))
|
|
||||||
plist_path = str(Path(installer_path) / Path("Contents/Info.plist"))
|
|
||||||
if Path(plist_path).exists():
|
|
||||||
plist = plistlib.load(Path(plist_path).open("rb"))
|
|
||||||
if "DTPlatformVersion" in plist:
|
|
||||||
platform_version = plist["DTPlatformVersion"]
|
|
||||||
platform_version = platform_version.split(".")[0]
|
|
||||||
if platform_version[0] == "10":
|
|
||||||
if int(platform_version[1]) < 13:
|
|
||||||
additional_args = f" --applicationpath '{installer_path}'"
|
|
||||||
|
|
||||||
if script_location.exists():
|
|
||||||
script_location.unlink()
|
|
||||||
script_location.touch()
|
|
||||||
|
|
||||||
with script_location.open("w") as script:
|
|
||||||
script.write(f'''#!/bin/bash
|
|
||||||
erase_disk='diskutil eraseDisk HFS+ OCLP-Installer {disk}'
|
|
||||||
if $erase_disk; then
|
|
||||||
"{createinstallmedia_path}" --volume /Volumes/OCLP-Installer --nointeraction{additional_args}
|
|
||||||
fi
|
|
||||||
''')
|
|
||||||
if Path(script_location).exists():
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
+308
-63
@@ -4,6 +4,8 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import cast
|
from typing import cast
|
||||||
|
import tempfile
|
||||||
|
import plistlib
|
||||||
|
|
||||||
import packaging.version
|
import packaging.version
|
||||||
import requests
|
import requests
|
||||||
@@ -13,10 +15,14 @@ import os
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from resources import utilities, network_handler
|
from resources import utilities, network_handler, constants
|
||||||
from resources.constants import Constants
|
from data import os_data
|
||||||
|
|
||||||
KDK_INSTALL_PATH = "/Library/Developer/KDKs"
|
KDK_INSTALL_PATH: str = "/Library/Developer/KDKs"
|
||||||
|
KDK_INFO_PLIST: str = "KDKInfo.plist"
|
||||||
|
KDK_API_LINK: str = "https://raw.githubusercontent.com/dortania/KdkSupportPkg/gh-pages/manifest.json"
|
||||||
|
|
||||||
|
KDK_ASSET_LIST: list = None
|
||||||
|
|
||||||
|
|
||||||
class KernelDebugKitObject:
|
class KernelDebugKitObject:
|
||||||
@@ -43,12 +49,14 @@ class KernelDebugKitObject:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, constants: Constants, host_build: str, host_version: str, ignore_installed: bool = False):
|
def __init__(self, global_constants: constants.Constants, host_build: str, host_version: str, ignore_installed: bool = False, passive: bool = False):
|
||||||
self.constants: Constants = constants
|
self.constants: constants.Constants = global_constants
|
||||||
|
|
||||||
self.host_build: str = host_build # ex. 20A5384c
|
self.host_build: str = host_build # ex. 20A5384c
|
||||||
self.host_version: str = host_version # ex. 11.0.1
|
self.host_version: str = host_version # ex. 11.0.1
|
||||||
|
|
||||||
|
self.passive: bool = passive # Don't perform actions requiring elevated privileges
|
||||||
|
|
||||||
self.ignore_installed: bool = ignore_installed # If True, will ignore any installed KDKs and download the latest
|
self.ignore_installed: bool = ignore_installed # If True, will ignore any installed KDKs and download the latest
|
||||||
self.kdk_already_installed: bool = False
|
self.kdk_already_installed: bool = False
|
||||||
|
|
||||||
@@ -75,17 +83,20 @@ class KernelDebugKitObject:
|
|||||||
self._get_latest_kdk()
|
self._get_latest_kdk()
|
||||||
|
|
||||||
|
|
||||||
def _get_available_kdks(self):
|
def _get_remote_kdks(self):
|
||||||
"""
|
"""
|
||||||
Fetches a list of available KDKs from the KdkSupportPkg API
|
Fetches a list of available KDKs from the KdkSupportPkg API
|
||||||
|
Additionally caches the list for future use, avoiding extra API calls
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list: A list of KDKs, sorted by version and date if available. Returns None if the API is unreachable
|
list: A list of KDKs, sorted by version and date if available. Returns None if the API is unreachable
|
||||||
"""
|
"""
|
||||||
|
|
||||||
KDK_API_LINK = "https://raw.githubusercontent.com/dortania/KdkSupportPkg/gh-pages/manifest.json"
|
global KDK_ASSET_LIST
|
||||||
|
|
||||||
logging.info("- Pulling KDK list from KdkSupportPkg API")
|
logging.info("- Pulling KDK list from KdkSupportPkg API")
|
||||||
|
if KDK_ASSET_LIST:
|
||||||
|
return KDK_ASSET_LIST
|
||||||
|
|
||||||
try:
|
try:
|
||||||
results = network_handler.SESSION.get(
|
results = network_handler.SESSION.get(
|
||||||
@@ -93,7 +104,7 @@ class KernelDebugKitObject:
|
|||||||
headers={
|
headers={
|
||||||
"User-Agent": f"OCLP/{self.constants.patcher_version}"
|
"User-Agent": f"OCLP/{self.constants.patcher_version}"
|
||||||
},
|
},
|
||||||
timeout=10
|
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")
|
||||||
@@ -103,14 +114,16 @@ class KernelDebugKitObject:
|
|||||||
logging.info("- Could not fetch KDK list")
|
logging.info("- Could not fetch KDK list")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return sorted(results.json(), key=lambda x: (packaging.version.parse(x["version"]), datetime.datetime.fromisoformat(x["date"])), reverse=True)
|
KDK_ASSET_LIST = sorted(results.json(), key=lambda x: (packaging.version.parse(x["version"]), datetime.datetime.fromisoformat(x["date"])), reverse=True)
|
||||||
|
|
||||||
|
return KDK_ASSET_LIST
|
||||||
|
|
||||||
|
|
||||||
def _get_latest_kdk(self, host_build: str = None, host_version: str = None):
|
def _get_latest_kdk(self, host_build: str = None, host_version: str = None):
|
||||||
"""
|
"""
|
||||||
Fetches the latest KDK for the current macOS version
|
Fetches the latest KDK for the current macOS version
|
||||||
|
|
||||||
Args:
|
Parameters:
|
||||||
host_build (str, optional): The build version of the current macOS version.
|
host_build (str, optional): The build version of the current macOS version.
|
||||||
If empty, will use the host_build from the class. Defaults to None.
|
If empty, will use the host_build from the class. Defaults to None.
|
||||||
host_version (str, optional): The version of the current macOS version.
|
host_version (str, optional): The version of the current macOS version.
|
||||||
@@ -121,17 +134,21 @@ class KernelDebugKitObject:
|
|||||||
host_build = self.host_build
|
host_build = self.host_build
|
||||||
host_version = self.host_version
|
host_version = self.host_version
|
||||||
|
|
||||||
logging.info(f"- Fetching latest KDK for {host_build} ({host_version})")
|
parsed_version = cast(packaging.version.Version, packaging.version.parse(host_version))
|
||||||
self.kdk_installed_path = self._local_kdk_installed_build()
|
|
||||||
|
if os_data.os_conversion.os_to_kernel(str(parsed_version.major)) < os_data.os_data.ventura:
|
||||||
|
self.error_msg = "KDKs are not required for macOS Monterey or older"
|
||||||
|
logging.warning(f"- {self.error_msg}")
|
||||||
|
return
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
remote_kdk_version = self._get_available_kdks()
|
remote_kdk_version = self._get_remote_kdks()
|
||||||
|
|
||||||
parsed_version = cast(packaging.version.Version, packaging.version.parse(host_version))
|
|
||||||
|
|
||||||
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")
|
||||||
@@ -140,17 +157,19 @@ class KernelDebugKitObject:
|
|||||||
# 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_version(loose_version)
|
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.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_version(older_version)
|
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.success = True
|
self.success = True
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -195,7 +214,7 @@ class KernelDebugKitObject:
|
|||||||
|
|
||||||
|
|
||||||
# Check if this KDK is already installed
|
# Check if this KDK is already installed
|
||||||
self.kdk_installed_path = self._local_kdk_installed_build(self.kdk_url_build)
|
self.kdk_installed_path = self._local_kdk_installed(match=self.kdk_url_build)
|
||||||
if self.kdk_installed_path:
|
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
|
||||||
@@ -236,18 +255,93 @@ class KernelDebugKitObject:
|
|||||||
|
|
||||||
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
|
||||||
return network_handler.DownloadObject(self.kdk_url, self.constants.kdk_download_path if override_path == "" else Path(override_path))
|
|
||||||
|
kdk_download_path = self.constants.kdk_download_path if override_path == "" else Path(override_path)
|
||||||
|
kdk_plist_path = Path(f"{kdk_download_path.parent}/{KDK_INFO_PLIST}") if override_path == "" else Path(f"{Path(override_path).parent}/{KDK_INFO_PLIST}")
|
||||||
|
|
||||||
|
self._generate_kdk_info_plist(kdk_plist_path)
|
||||||
|
return network_handler.DownloadObject(self.kdk_url, kdk_download_path)
|
||||||
|
|
||||||
|
|
||||||
def _local_kdk_valid(self, kdk_path: str):
|
def _generate_kdk_info_plist(self, plist_path: str):
|
||||||
|
"""
|
||||||
|
Generates a KDK Info.plist
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
plist_path = Path(plist_path)
|
||||||
|
if plist_path.exists():
|
||||||
|
plist_path.unlink()
|
||||||
|
|
||||||
|
kdk_dict = {
|
||||||
|
"build": self.kdk_url_build,
|
||||||
|
"version": self.kdk_url_version,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
plist_path.touch()
|
||||||
|
plistlib.dump(kdk_dict, plist_path.open("wb"), sort_keys=False)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"- Failed to generate KDK Info.plist: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def _local_kdk_valid(self, kdk_path: Path):
|
||||||
"""
|
"""
|
||||||
Validates provided KDK, ensure no corruption
|
Validates provided KDK, ensure no corruption
|
||||||
|
|
||||||
The reason for this is due to macOS deleting files from the KDK during OS updates,
|
The reason for this is due to macOS deleting files from the KDK during OS updates,
|
||||||
similar to how Install macOS.app is deleted during OS updates
|
similar to how Install macOS.app is deleted during OS updates
|
||||||
|
|
||||||
Args:
|
Uses Apple's pkg receipt system to verify the original contents of the KDK
|
||||||
kdk_path (str): Path to KDK
|
|
||||||
|
Parameters:
|
||||||
|
kdk_path (Path): Path to KDK
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if valid, False if invalid
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not Path(f"{kdk_path}/System/Library/CoreServices/SystemVersion.plist").exists():
|
||||||
|
logging.info(f"- Corrupted KDK found ({kdk_path.name}), removing due to missing SystemVersion.plist")
|
||||||
|
self._remove_kdk(kdk_path)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Get build from KDK
|
||||||
|
kdk_plist_data = plistlib.load(Path(f"{kdk_path}/System/Library/CoreServices/SystemVersion.plist").open("rb"))
|
||||||
|
if "ProductBuildVersion" not in kdk_plist_data:
|
||||||
|
logging.info(f"- Corrupted KDK found ({kdk_path.name}), removing due to missing ProductBuildVersion")
|
||||||
|
self._remove_kdk(kdk_path)
|
||||||
|
return False
|
||||||
|
|
||||||
|
kdk_build = kdk_plist_data["ProductBuildVersion"]
|
||||||
|
|
||||||
|
# Check pkg receipts for this build, will give a canonical list if all files that should be present
|
||||||
|
result = subprocess.run(["pkgutil", "--files", f"com.apple.pkg.KDK.{kdk_build}"], capture_output=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
# If pkg receipt is missing, we'll fallback to legacy validation
|
||||||
|
logging.info(f"- pkg receipt missing for {kdk_path.name}, falling back to legacy validation")
|
||||||
|
return self._local_kdk_valid_legacy(kdk_path)
|
||||||
|
|
||||||
|
# Go through each line of the pkg receipt and ensure it exists
|
||||||
|
for line in result.stdout.decode("utf-8").splitlines():
|
||||||
|
if not line.startswith("System/Library/Extensions"):
|
||||||
|
continue
|
||||||
|
if not Path(f"{kdk_path}/{line}").exists():
|
||||||
|
logging.info(f"- Corrupted KDK found ({kdk_path.name}), removing due to missing file: {line}")
|
||||||
|
self._remove_kdk(kdk_path)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _local_kdk_valid_legacy(self, kdk_path: Path):
|
||||||
|
"""
|
||||||
|
Legacy variant of validating provided KDK
|
||||||
|
Uses best guess of files that should be present
|
||||||
|
This should ideally never be invoked, but used as a fallback
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
kdk_path (Path): Path to KDK
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if valid, False if invalid
|
bool: True if valid, False if invalid
|
||||||
@@ -260,8 +354,6 @@ class KernelDebugKitObject:
|
|||||||
"AMDRadeonX6000.kext/Contents/MacOS/AMDRadeonX6000",
|
"AMDRadeonX6000.kext/Contents/MacOS/AMDRadeonX6000",
|
||||||
]
|
]
|
||||||
|
|
||||||
kdk_path = Path(kdk_path)
|
|
||||||
|
|
||||||
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}")
|
||||||
@@ -271,39 +363,14 @@ class KernelDebugKitObject:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _local_kdk_installed_build(self, build: str = None):
|
def _local_kdk_installed(self, match: str = None, check_version: bool = False):
|
||||||
"""
|
"""
|
||||||
Checks if KDK matching build is installed
|
Checks if KDK matching build is installed
|
||||||
If so, validates it has not been corrupted
|
If so, validates it has not been corrupted
|
||||||
|
|
||||||
Returns:
|
Parameters:
|
||||||
str: Path to KDK if valid, None if not
|
match (str): string to match against (ex. build or version)
|
||||||
"""
|
check_version (bool): If True, match against version, otherwise match against build
|
||||||
|
|
||||||
if self.ignore_installed is True:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if build is None:
|
|
||||||
build = self.host_build
|
|
||||||
|
|
||||||
if not Path(KDK_INSTALL_PATH).exists():
|
|
||||||
return None
|
|
||||||
|
|
||||||
for kdk_folder in Path(KDK_INSTALL_PATH).iterdir():
|
|
||||||
if not kdk_folder.is_dir():
|
|
||||||
continue
|
|
||||||
if not kdk_folder.name.endswith(f"{build}.kdk"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if self._local_kdk_valid(kdk_folder):
|
|
||||||
return kdk_folder
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _local_kdk_installed_version(self, version: str = None):
|
|
||||||
"""
|
|
||||||
Checks if KDK matching version is installed
|
|
||||||
If so, validates it has not been corrupted
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Path to KDK if valid, None if not
|
str: Path to KDK if valid, None if not
|
||||||
@@ -312,8 +379,11 @@ class KernelDebugKitObject:
|
|||||||
if self.ignore_installed is True:
|
if self.ignore_installed is True:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if version is None:
|
if match is None:
|
||||||
version = self.host_version
|
if check_version:
|
||||||
|
match = self.host_version
|
||||||
|
else:
|
||||||
|
match = self.host_build
|
||||||
|
|
||||||
if not Path(KDK_INSTALL_PATH).exists():
|
if not Path(KDK_INSTALL_PATH).exists():
|
||||||
return None
|
return None
|
||||||
@@ -321,12 +391,41 @@ class KernelDebugKitObject:
|
|||||||
for kdk_folder in Path(KDK_INSTALL_PATH).iterdir():
|
for kdk_folder in Path(KDK_INSTALL_PATH).iterdir():
|
||||||
if not kdk_folder.is_dir():
|
if not kdk_folder.is_dir():
|
||||||
continue
|
continue
|
||||||
if version not in kdk_folder.name:
|
if check_version:
|
||||||
continue
|
if match not in kdk_folder.name:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if not kdk_folder.name.endswith(f"{match}.kdk"):
|
||||||
|
continue
|
||||||
|
|
||||||
if self._local_kdk_valid(kdk_folder):
|
if self._local_kdk_valid(kdk_folder):
|
||||||
return kdk_folder
|
return kdk_folder
|
||||||
|
|
||||||
|
# If we can't find a KDK, next check if there's a backup present
|
||||||
|
# Check for KDK packages in the same directory as the KDK
|
||||||
|
for kdk_pkg in Path(KDK_INSTALL_PATH).iterdir():
|
||||||
|
if kdk_pkg.is_dir():
|
||||||
|
continue
|
||||||
|
if not kdk_pkg.name.endswith(".pkg"):
|
||||||
|
continue
|
||||||
|
if check_version:
|
||||||
|
if match not in kdk_pkg.name:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if not kdk_pkg.name.endswith(f"{match}.pkg"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
logging.info(f"- Found KDK backup: {kdk_pkg.name}")
|
||||||
|
if self.passive is False:
|
||||||
|
logging.info("- Attempting KDK restoration")
|
||||||
|
if KernelDebugKitUtilities().install_kdk_pkg(kdk_pkg):
|
||||||
|
logging.info("- Successfully restored KDK")
|
||||||
|
return self._local_kdk_installed(match=match, check_version=check_version)
|
||||||
|
else:
|
||||||
|
# When in passive mode, we're just checking if a KDK could be restored
|
||||||
|
logging.info("- KDK restoration skipped, running in passive mode")
|
||||||
|
return kdk_pkg
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -334,15 +433,26 @@ class KernelDebugKitObject:
|
|||||||
"""
|
"""
|
||||||
Removes provided KDK
|
Removes provided KDK
|
||||||
|
|
||||||
Args:
|
Parameters:
|
||||||
kdk_path (str): Path to KDK
|
kdk_path (str): Path to KDK
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if self.passive is True:
|
||||||
|
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
|
||||||
|
|
||||||
result = utilities.elevated(["rm", "-rf", kdk_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
if not Path(kdk_path).exists():
|
||||||
|
logging.warning(f"- KDK does not exist: {kdk_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
rm_args = ["rm", "-f", kdk_path]
|
||||||
|
if Path(kdk_path).is_dir():
|
||||||
|
rm_args = ["rm", "-rf", kdk_path]
|
||||||
|
|
||||||
|
result = utilities.elevated(rm_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
if result.returncode != 0:
|
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')}")
|
||||||
@@ -354,11 +464,13 @@ class KernelDebugKitObject:
|
|||||||
"""
|
"""
|
||||||
Removes KDKs that are not in use
|
Removes KDKs that are not in use
|
||||||
|
|
||||||
Args:
|
Parameters:
|
||||||
exclude_builds (list, optional): Builds to exclude from removal.
|
exclude_builds (list, optional): Builds to exclude from removal.
|
||||||
If None, defaults to host and closest match builds.
|
If None, defaults to host and closest match builds.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if self.passive is True:
|
||||||
|
return
|
||||||
|
|
||||||
if exclude_builds is None:
|
if exclude_builds is None:
|
||||||
exclude_builds = [
|
exclude_builds = [
|
||||||
@@ -375,10 +487,12 @@ class KernelDebugKitObject:
|
|||||||
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.is_dir():
|
if kdk_folder.is_dir():
|
||||||
if kdk_folder.name.endswith(".kdk"):
|
if kdk_folder.name.endswith(".kdk") or kdk_folder.name.endswith(".pkg"):
|
||||||
should_remove = True
|
should_remove = True
|
||||||
for build in exclude_builds:
|
for build in exclude_builds:
|
||||||
if build != "" and kdk_folder.name.endswith(f"{build}.kdk"):
|
if build != "":
|
||||||
|
continue
|
||||||
|
if kdk_folder.name.endswith(f"{build}.kdk") or kdk_folder.name.endswith(f"{build}.pkg"):
|
||||||
should_remove = False
|
should_remove = False
|
||||||
break
|
break
|
||||||
if should_remove is False:
|
if should_remove is False:
|
||||||
@@ -390,7 +504,7 @@ class KernelDebugKitObject:
|
|||||||
"""
|
"""
|
||||||
Validates KDK DMG checksum
|
Validates KDK DMG checksum
|
||||||
|
|
||||||
Args:
|
Parameters:
|
||||||
kdk_dmg_path (str, optional): Path to KDK DMG. Defaults to None.
|
kdk_dmg_path (str, optional): Path to KDK DMG. Defaults to None.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -420,3 +534,134 @@ class KernelDebugKitObject:
|
|||||||
self._remove_unused_kdks()
|
self._remove_unused_kdks()
|
||||||
|
|
||||||
self.success = True
|
self.success = True
|
||||||
|
|
||||||
|
|
||||||
|
class KernelDebugKitUtilities:
|
||||||
|
"""
|
||||||
|
Utilities for KDK handling
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def install_kdk_pkg(self, kdk_path: Path):
|
||||||
|
"""
|
||||||
|
Installs provided KDK packages
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
kdk_path (Path): Path to KDK package
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False if not
|
||||||
|
"""
|
||||||
|
|
||||||
|
if os.getuid() != 0:
|
||||||
|
logging.warning("- Cannot install KDK, not running as root")
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.info(f"- Installing KDK package: {kdk_path.name}")
|
||||||
|
logging.info(f" - This may take a while...")
|
||||||
|
|
||||||
|
# TODO: Check whether enough disk space is available
|
||||||
|
|
||||||
|
result = utilities.elevated(["installer", "-pkg", kdk_path, "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
if result.returncode != 0:
|
||||||
|
logging.info("- Failed to install KDK:")
|
||||||
|
logging.info(result.stdout.decode('utf-8'))
|
||||||
|
if result.stderr:
|
||||||
|
logging.info(result.stderr.decode('utf-8'))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def install_kdk_dmg(self, kdk_path: Path):
|
||||||
|
"""
|
||||||
|
Installs provided KDK disk image
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
kdk_path (Path): Path to KDK disk image
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False if not
|
||||||
|
"""
|
||||||
|
|
||||||
|
if os.getuid() != 0:
|
||||||
|
logging.warning("- Cannot install KDK, not running as root")
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.info(f"- Extracting downloaded KDK disk image")
|
||||||
|
with tempfile.TemporaryDirectory() as mount_point:
|
||||||
|
result = subprocess.run(["hdiutil", "attach", kdk_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
if result.returncode != 0:
|
||||||
|
logging.info("- Failed to mount KDK:")
|
||||||
|
logging.info(result.stdout.decode('utf-8'))
|
||||||
|
return False
|
||||||
|
|
||||||
|
kdk_pkg_path = Path(f"{mount_point}/KernelDebugKit.pkg")
|
||||||
|
|
||||||
|
if not kdk_pkg_path.exists():
|
||||||
|
logging.warning("- Failed to find KDK package in DMG, likely corrupted!!!")
|
||||||
|
self._unmount_disk_image(mount_point)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.install_kdk_pkg(kdk_pkg_path) is False:
|
||||||
|
self._unmount_disk_image(mount_point)
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._create_backup(kdk_pkg_path, Path(f"{kdk_path.parent}/{KDK_INFO_PLIST}"))
|
||||||
|
self._unmount_disk_image(mount_point)
|
||||||
|
|
||||||
|
logging.info("- Successfully installed KDK")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _unmount_disk_image(self, mount_point):
|
||||||
|
"""
|
||||||
|
Unmounts provided disk image silently
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
mount_point (Path): Path to mount point
|
||||||
|
"""
|
||||||
|
subprocess.run(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
|
||||||
|
|
||||||
|
def _create_backup(self, kdk_path: Path, kdk_info_plist: Path):
|
||||||
|
"""
|
||||||
|
Creates a backup of the KDK
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
kdk_path (Path): Path to KDK
|
||||||
|
kdk_info_plist (Path): Path to KDK Info.plist
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not kdk_path.exists():
|
||||||
|
logging.warning("- KDK does not exist, cannot create backup")
|
||||||
|
return
|
||||||
|
if not kdk_info_plist.exists():
|
||||||
|
logging.warning("- KDK Info.plist does not exist, cannot create backup")
|
||||||
|
return
|
||||||
|
|
||||||
|
kdk_info_dict = plistlib.load(kdk_info_plist.open("rb"))
|
||||||
|
|
||||||
|
if 'version' not in kdk_info_dict or 'build' not in kdk_info_dict:
|
||||||
|
logging.warning("- Malformed KDK Info.plist provided, cannot create backup")
|
||||||
|
return
|
||||||
|
|
||||||
|
if os.getuid() != 0:
|
||||||
|
logging.warning("- Cannot create KDK backup, not running as root")
|
||||||
|
return
|
||||||
|
|
||||||
|
kdk_dst_name = f"KDK_{kdk_info_dict['version']}_{kdk_info_dict['build']}.pkg"
|
||||||
|
kdk_dst_path = Path(f"{KDK_INSTALL_PATH}/{kdk_dst_name}")
|
||||||
|
|
||||||
|
logging.info(f"- Creating backup: {kdk_dst_name}")
|
||||||
|
if kdk_dst_path.exists():
|
||||||
|
logging.info("- Backup already exists, skipping")
|
||||||
|
return
|
||||||
|
|
||||||
|
result = utilities.elevated(["cp", "-R", kdk_path, kdk_dst_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
if result.returncode != 0:
|
||||||
|
logging.info("- Failed to create KDK backup:")
|
||||||
|
logging.info(result.stdout.decode('utf-8'))
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
@@ -19,9 +21,12 @@ class InitializeLoggingSupport:
|
|||||||
>>> from resources.logging_handler import InitializeLoggingSupport
|
>>> from resources.logging_handler import InitializeLoggingSupport
|
||||||
>>> InitializeLoggingSupport()
|
>>> InitializeLoggingSupport()
|
||||||
|
|
||||||
|
FOR DEVELOPERS:
|
||||||
|
- Do not invoke logging until after '_attempt_initialize_logging_configuration()' has been invoked
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self):
|
||||||
self.log_filename: str = "OpenCore-Patcher.log"
|
self.log_filename: str = "OpenCore-Patcher.log"
|
||||||
self.log_filepath: Path = None
|
self.log_filepath: Path = None
|
||||||
|
|
||||||
@@ -35,6 +40,7 @@ class InitializeLoggingSupport:
|
|||||||
self._clean_log_file()
|
self._clean_log_file()
|
||||||
self._attempt_initialize_logging_configuration()
|
self._attempt_initialize_logging_configuration()
|
||||||
self._implement_custom_traceback_handler()
|
self._implement_custom_traceback_handler()
|
||||||
|
self._fix_file_permission()
|
||||||
|
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
@@ -81,12 +87,34 @@ class InitializeLoggingSupport:
|
|||||||
print(f"- Failed to clean log file: {e}")
|
print(f"- Failed to clean log file: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def _fix_file_permission(self):
|
||||||
|
"""
|
||||||
|
Fixes file permission for log file
|
||||||
|
|
||||||
|
If OCLP was invoked as root, file permission will only allow root to write to log file
|
||||||
|
This in turn breaks normal OCLP execution to write to log file
|
||||||
|
"""
|
||||||
|
|
||||||
|
if os.geteuid() != 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
result = subprocess.run(["chmod", "777", self.log_filepath], capture_output=True)
|
||||||
|
if result.returncode != 0:
|
||||||
|
print(f"- Failed to fix log file permissions")
|
||||||
|
if result.stderr:
|
||||||
|
print(result.stderr.decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
def _initialize_logging_configuration(self, log_to_file: bool = True):
|
def _initialize_logging_configuration(self, log_to_file: bool = True):
|
||||||
"""
|
"""
|
||||||
Initialize logging framework configuration
|
Initialize logging framework configuration
|
||||||
|
|
||||||
StreamHandler's format is used to mimic the default behavior of print()
|
StreamHandler's format is used to mimic the default behavior of print()
|
||||||
While FileHandler's format is for more in-depth logging
|
While FileHandler's format is for more in-depth logging
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
log_to_file (bool): Whether to log to file or not
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
@@ -101,6 +129,7 @@ class InitializeLoggingSupport:
|
|||||||
logging.getLogger().handlers[0].setFormatter(logging.Formatter("%(message)s"))
|
logging.getLogger().handlers[0].setFormatter(logging.Formatter("%(message)s"))
|
||||||
logging.getLogger().handlers[1].maxBytes = self.max_file_size
|
logging.getLogger().handlers[1].maxBytes = self.max_file_size
|
||||||
|
|
||||||
|
|
||||||
def _attempt_initialize_logging_configuration(self):
|
def _attempt_initialize_logging_configuration(self):
|
||||||
"""
|
"""
|
||||||
Attempt to initialize logging framework configuration
|
Attempt to initialize logging framework configuration
|
||||||
@@ -136,6 +165,7 @@ class InitializeLoggingSupport:
|
|||||||
sys.excepthook = custom_excepthook
|
sys.excepthook = custom_excepthook
|
||||||
threading.excepthook = custom_thread_excepthook
|
threading.excepthook = custom_thread_excepthook
|
||||||
|
|
||||||
|
|
||||||
def _restore_original_excepthook(self):
|
def _restore_original_excepthook(self):
|
||||||
"""
|
"""
|
||||||
Restore original traceback handlers
|
Restore original traceback handlers
|
||||||
|
|||||||
@@ -0,0 +1,590 @@
|
|||||||
|
# Handler for macOS installers, both local and remote
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import plistlib
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import enum
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from data import os_data
|
||||||
|
from resources import network_handler, utilities
|
||||||
|
|
||||||
|
|
||||||
|
APPLICATION_SEARCH_PATH: str = "/Applications"
|
||||||
|
SFR_SOFTWARE_UPDATE_PATH: str = "SFR/com_apple_MobileAsset_SFRSoftwareUpdate/com_apple_MobileAsset_SFRSoftwareUpdate.xml"
|
||||||
|
|
||||||
|
CATALOG_URL_BASE: str = "https://swscan.apple.com/content/catalogs/others/index"
|
||||||
|
CATALOG_URL_EXTENSION: str = "13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog"
|
||||||
|
CATALOG_URL_VERSION: str = "13"
|
||||||
|
|
||||||
|
tmp_dir = tempfile.TemporaryDirectory()
|
||||||
|
|
||||||
|
|
||||||
|
class InstallerCreation():
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def install_macOS_installer(self, download_path: str):
|
||||||
|
"""
|
||||||
|
Installs InstallAssistant.pkg
|
||||||
|
"""
|
||||||
|
|
||||||
|
logging.info("- Extracting macOS installer from InstallAssistant.pkg\n This may take some time")
|
||||||
|
args = [
|
||||||
|
"osascript",
|
||||||
|
"-e",
|
||||||
|
f'''do shell script "installer -pkg {Path(download_path)}/InstallAssistant.pkg -target /"'''
|
||||||
|
' with prompt "OpenCore Legacy Patcher needs administrator privileges to add InstallAssistant."'
|
||||||
|
" with administrator privileges"
|
||||||
|
" without altering line endings",
|
||||||
|
]
|
||||||
|
|
||||||
|
result = subprocess.run(args,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
if result.returncode != 0:
|
||||||
|
logging.info("- Failed to install InstallAssistant")
|
||||||
|
logging.info(f" Error Code: {result.returncode}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.info("- InstallAssistant installed")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def generate_installer_creation_script(self, tmp_location, installer_path, disk):
|
||||||
|
"""
|
||||||
|
Creates installer.sh to be piped to OCLP-Helper and run as admin
|
||||||
|
|
||||||
|
Script includes:
|
||||||
|
- Format provided disk as HFS+ GPT
|
||||||
|
- Run createinstallmedia on provided disk
|
||||||
|
|
||||||
|
Implementing this into a single installer.sh script allows us to only call
|
||||||
|
OCLP-Helper once to avoid nagging the user about permissions
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
tmp_location (str): Path to temporary directory
|
||||||
|
installer_path (str): Path to InstallAssistant.pkg
|
||||||
|
disk (str): Disk to install to
|
||||||
|
"""
|
||||||
|
|
||||||
|
additional_args = ""
|
||||||
|
script_location = Path(tmp_location) / Path("Installer.sh")
|
||||||
|
|
||||||
|
# Due to a bug in createinstallmedia, running from '/Applications' may sometimes error:
|
||||||
|
# 'Failed to extract AssetData/boot/Firmware/Manifests/InstallerBoot/*'
|
||||||
|
# This affects native Macs as well even when manually invoking createinstallmedia
|
||||||
|
|
||||||
|
# To resolve, we'll copy into our temp directory and run from there
|
||||||
|
|
||||||
|
# Create a new tmp directory
|
||||||
|
# Our current one is a disk image, thus CoW will not work
|
||||||
|
global tmp_dir
|
||||||
|
ia_tmp = tmp_dir.name
|
||||||
|
|
||||||
|
logging.info(f"Creating temporary directory at {ia_tmp}")
|
||||||
|
# Delete all files in tmp_dir
|
||||||
|
for file in Path(ia_tmp).glob("*"):
|
||||||
|
subprocess.run(["rm", "-rf", str(file)])
|
||||||
|
|
||||||
|
# Copy installer to tmp (use CoW to avoid extra disk writes)
|
||||||
|
args = ["cp", "-cR", installer_path, ia_tmp]
|
||||||
|
if utilities.check_filesystem_type() != "apfs":
|
||||||
|
# HFS+ disks do not support CoW
|
||||||
|
args[1] = "-R"
|
||||||
|
|
||||||
|
# Ensure we have enough space for the duplication
|
||||||
|
space_available = utilities.get_free_space()
|
||||||
|
space_needed = Path(ia_tmp).stat().st_size
|
||||||
|
if space_available < space_needed:
|
||||||
|
logging.info("Not enough free space to create installer.sh")
|
||||||
|
logging.info(f"{utilities.human_fmt(space_available)} available, {utilities.human_fmt(space_needed)} required")
|
||||||
|
return False
|
||||||
|
|
||||||
|
subprocess.run(args)
|
||||||
|
|
||||||
|
# Adjust installer_path to point to the copied installer
|
||||||
|
installer_path = Path(ia_tmp) / Path(Path(installer_path).name)
|
||||||
|
if not Path(installer_path).exists():
|
||||||
|
logging.info(f"Failed to copy installer to {ia_tmp}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
createinstallmedia_path = str(Path(installer_path) / Path("Contents/Resources/createinstallmedia"))
|
||||||
|
plist_path = str(Path(installer_path) / Path("Contents/Info.plist"))
|
||||||
|
if Path(plist_path).exists():
|
||||||
|
plist = plistlib.load(Path(plist_path).open("rb"))
|
||||||
|
if "DTPlatformVersion" in plist:
|
||||||
|
platform_version = plist["DTPlatformVersion"]
|
||||||
|
platform_version = platform_version.split(".")[0]
|
||||||
|
if platform_version[0] == "10":
|
||||||
|
if int(platform_version[1]) < 13:
|
||||||
|
additional_args = f" --applicationpath '{installer_path}'"
|
||||||
|
|
||||||
|
if script_location.exists():
|
||||||
|
script_location.unlink()
|
||||||
|
script_location.touch()
|
||||||
|
|
||||||
|
with script_location.open("w") as script:
|
||||||
|
script.write(f'''#!/bin/bash
|
||||||
|
erase_disk='diskutil eraseDisk HFS+ OCLP-Installer {disk}'
|
||||||
|
if $erase_disk; then
|
||||||
|
"{createinstallmedia_path}" --volume /Volumes/OCLP-Installer --nointeraction{additional_args}
|
||||||
|
fi
|
||||||
|
''')
|
||||||
|
if Path(script_location).exists():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def list_disk_to_format(self):
|
||||||
|
"""
|
||||||
|
List applicable disks for macOS installer creation
|
||||||
|
Only lists disks that are:
|
||||||
|
- 14GB or larger
|
||||||
|
- External
|
||||||
|
|
||||||
|
Current limitations:
|
||||||
|
- Does not support PCIe based SD cards readers
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Dictionary of disks
|
||||||
|
"""
|
||||||
|
|
||||||
|
all_disks: dict = {}
|
||||||
|
list_disks: dict = {}
|
||||||
|
|
||||||
|
# TODO: AllDisksAndPartitions is not supported in Snow Leopard and older
|
||||||
|
try:
|
||||||
|
# High Sierra and newer
|
||||||
|
disks = plistlib.loads(subprocess.run("diskutil list -plist physical".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
|
||||||
|
except ValueError:
|
||||||
|
# Sierra and older
|
||||||
|
disks = plistlib.loads(subprocess.run("diskutil list -plist".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
|
||||||
|
|
||||||
|
for disk in disks["AllDisksAndPartitions"]:
|
||||||
|
disk_info = plistlib.loads(subprocess.run(f"diskutil info -plist {disk['DeviceIdentifier']}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
|
||||||
|
try:
|
||||||
|
all_disks[disk["DeviceIdentifier"]] = {"identifier": disk_info["DeviceNode"], "name": disk_info["MediaName"], "size": disk_info["TotalSize"], "removable": disk_info["Internal"], "partitions": {}}
|
||||||
|
except KeyError:
|
||||||
|
# Avoid crashing with CDs installed
|
||||||
|
continue
|
||||||
|
|
||||||
|
for disk in all_disks:
|
||||||
|
# Strip disks that are under 14GB (15,032,385,536 bytes)
|
||||||
|
# createinstallmedia isn't great at detecting if a disk has enough space
|
||||||
|
if not any(all_disks[disk]['size'] > 15032385536 for partition in all_disks[disk]):
|
||||||
|
continue
|
||||||
|
# Strip internal disks as well (avoid user formatting their SSD/HDD)
|
||||||
|
# Ensure user doesn't format their boot drive
|
||||||
|
if not any(all_disks[disk]['removable'] is False for partition in all_disks[disk]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
logging.info(f"disk {disk}: {all_disks[disk]['name']} ({utilities.human_fmt(all_disks[disk]['size'])})")
|
||||||
|
list_disks.update({
|
||||||
|
disk: {
|
||||||
|
"identifier": all_disks[disk]["identifier"],
|
||||||
|
"name": all_disks[disk]["name"],
|
||||||
|
"size": all_disks[disk]["size"],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return list_disks
|
||||||
|
|
||||||
|
|
||||||
|
class SeedType(enum.Enum):
|
||||||
|
"""
|
||||||
|
Enum for catalog types
|
||||||
|
"""
|
||||||
|
DeveloperSeed = 0
|
||||||
|
PublicSeed = 1
|
||||||
|
CustomerSeed = 2
|
||||||
|
PublicRelease = 3
|
||||||
|
|
||||||
|
|
||||||
|
class RemoteInstallerCatalog:
|
||||||
|
"""
|
||||||
|
Parses Apple's Software Update catalog and finds all macOS installers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, seed_override: SeedType = SeedType.PublicRelease):
|
||||||
|
|
||||||
|
self.catalog_url: str = self._construct_catalog_url(seed_override)
|
||||||
|
|
||||||
|
self.available_apps: dict = self._parse_catalog()
|
||||||
|
self.available_apps_latest: dict = self._list_newest_installers_only()
|
||||||
|
|
||||||
|
|
||||||
|
def _construct_catalog_url(self, seed_type: SeedType):
|
||||||
|
"""
|
||||||
|
Constructs the catalog URL based on the seed type
|
||||||
|
|
||||||
|
Args:
|
||||||
|
seed_type (SeedType): The seed type to use
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
url: str = ""
|
||||||
|
|
||||||
|
if seed_type == SeedType.DeveloperSeed:
|
||||||
|
url = f"{CATALOG_URL_BASE}-{CATALOG_URL_VERSION}seed-{CATALOG_URL_EXTENSION}"
|
||||||
|
elif seed_type == SeedType.PublicSeed:
|
||||||
|
url = f"{CATALOG_URL_BASE}-{CATALOG_URL_VERSION}beta-{CATALOG_URL_EXTENSION}"
|
||||||
|
elif seed_type == SeedType.CustomerSeed:
|
||||||
|
url = f"{CATALOG_URL_BASE}-{CATALOG_URL_VERSION}customerseed-{CATALOG_URL_EXTENSION}"
|
||||||
|
else:
|
||||||
|
url = f"{CATALOG_URL_BASE}-{CATALOG_URL_EXTENSION}"
|
||||||
|
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def _fetch_catalog(self):
|
||||||
|
"""
|
||||||
|
Fetches the catalog from Apple's servers
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The catalog as a dictionary
|
||||||
|
"""
|
||||||
|
|
||||||
|
catalog: dict = {}
|
||||||
|
|
||||||
|
if network_handler.NetworkUtilities(self.catalog_url).verify_network_connection() is False:
|
||||||
|
return catalog
|
||||||
|
|
||||||
|
try:
|
||||||
|
catalog = plistlib.loads(network_handler.SESSION.get(self.catalog_url).content)
|
||||||
|
except plistlib.InvalidFileException:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return catalog
|
||||||
|
|
||||||
|
def _parse_catalog(self):
|
||||||
|
available_apps: dict = {}
|
||||||
|
|
||||||
|
catalog: dict = self._fetch_catalog()
|
||||||
|
if not catalog:
|
||||||
|
return available_apps
|
||||||
|
|
||||||
|
if "Products" not in catalog:
|
||||||
|
return available_apps
|
||||||
|
|
||||||
|
for product in catalog["Products"]:
|
||||||
|
if "ExtendedMetaInfo" not in catalog["Products"][product]:
|
||||||
|
continue
|
||||||
|
if "Packages" not in catalog["Products"][product]:
|
||||||
|
continue
|
||||||
|
if "InstallAssistantPackageIdentifiers" not in catalog["Products"][product]["ExtendedMetaInfo"]:
|
||||||
|
continue
|
||||||
|
if "SharedSupport" not in catalog["Products"][product]["ExtendedMetaInfo"]["InstallAssistantPackageIdentifiers"]:
|
||||||
|
continue
|
||||||
|
if "BuildManifest" not in catalog["Products"][product]["ExtendedMetaInfo"]["InstallAssistantPackageIdentifiers"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for bm_package in catalog["Products"][product]["Packages"]:
|
||||||
|
if "Info.plist" not in bm_package["URL"]:
|
||||||
|
continue
|
||||||
|
if "InstallInfo.plist" in bm_package["URL"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
build_plist = plistlib.loads(network_handler.SESSION.get(bm_package["URL"]).content)
|
||||||
|
except plistlib.InvalidFileException:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "MobileAssetProperties" not in build_plist:
|
||||||
|
continue
|
||||||
|
if "SupportedDeviceModels" not in build_plist["MobileAssetProperties"]:
|
||||||
|
continue
|
||||||
|
if "OSVersion" not in build_plist["MobileAssetProperties"]:
|
||||||
|
continue
|
||||||
|
if "Build" not in build_plist["MobileAssetProperties"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Ensure Apple Silicon specific Installers are not listed
|
||||||
|
if "VMM-x86_64" not in build_plist["MobileAssetProperties"]["SupportedDeviceModels"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
version = build_plist["MobileAssetProperties"]["OSVersion"]
|
||||||
|
build = build_plist["MobileAssetProperties"]["Build"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
catalog_url = build_plist["MobileAssetProperties"]["BridgeVersionInfo"]["CatalogURL"]
|
||||||
|
if "beta" in catalog_url:
|
||||||
|
catalog_url = "PublicSeed"
|
||||||
|
elif "customerseed" in catalog_url:
|
||||||
|
catalog_url = "CustomerSeed"
|
||||||
|
elif "seed" in catalog_url:
|
||||||
|
catalog_url = "DeveloperSeed"
|
||||||
|
else:
|
||||||
|
catalog_url = "Public"
|
||||||
|
except KeyError:
|
||||||
|
# Assume Public if no catalog URL is found
|
||||||
|
catalog_url = "Public"
|
||||||
|
|
||||||
|
download_link = None
|
||||||
|
integrity = None
|
||||||
|
size = None
|
||||||
|
|
||||||
|
for ia_package in catalog["Products"][product]["Packages"]:
|
||||||
|
if "InstallAssistant.pkg" not in ia_package["URL"]:
|
||||||
|
continue
|
||||||
|
if "URL" not in ia_package:
|
||||||
|
continue
|
||||||
|
if "IntegrityDataURL" not in ia_package:
|
||||||
|
continue
|
||||||
|
if "Size" not in ia_package:
|
||||||
|
size = 0
|
||||||
|
|
||||||
|
download_link = ia_package["URL"]
|
||||||
|
integrity = ia_package["IntegrityDataURL"]
|
||||||
|
size = ia_package["Size"]
|
||||||
|
|
||||||
|
|
||||||
|
if any([version, build, download_link, size, integrity]) is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
available_apps.update({
|
||||||
|
product: {
|
||||||
|
"Version": version,
|
||||||
|
"Build": build,
|
||||||
|
"Link": download_link,
|
||||||
|
"Size": size,
|
||||||
|
"integrity": integrity,
|
||||||
|
"Source": "Apple Inc.",
|
||||||
|
"Variant": catalog_url,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
available_apps = {k: v for k, v in sorted(available_apps.items(), key=lambda x: x[1]['Version'])}
|
||||||
|
return available_apps
|
||||||
|
|
||||||
|
|
||||||
|
def _list_newest_installers_only(self):
|
||||||
|
"""
|
||||||
|
Returns a dictionary of the newest macOS installers only.
|
||||||
|
Primarily used to avoid overwhelming the user with a list of
|
||||||
|
installers that are not the newest version.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary of the newest macOS installers only.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.available_apps is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
newest_apps: dict = self.available_apps.copy()
|
||||||
|
supported_versions = ["10.13", "10.14", "10.15", "11", "12", "13"]
|
||||||
|
|
||||||
|
|
||||||
|
for version in supported_versions:
|
||||||
|
remote_version_minor = 0
|
||||||
|
remote_version_security = 0
|
||||||
|
os_builds = []
|
||||||
|
|
||||||
|
# First determine the largest version
|
||||||
|
for ia in newest_apps:
|
||||||
|
if newest_apps[ia]["Version"].startswith(version):
|
||||||
|
if newest_apps[ia]["Variant"] not in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]:
|
||||||
|
remote_version = newest_apps[ia]["Version"].split(".")
|
||||||
|
if remote_version[0] == "10":
|
||||||
|
remote_version.pop(0)
|
||||||
|
remote_version.pop(0)
|
||||||
|
else:
|
||||||
|
remote_version.pop(0)
|
||||||
|
if int(remote_version[0]) > remote_version_minor:
|
||||||
|
remote_version_minor = int(remote_version[0])
|
||||||
|
remote_version_security = 0 # Reset as new minor version found
|
||||||
|
if len(remote_version) > 1:
|
||||||
|
if int(remote_version[1]) > remote_version_security:
|
||||||
|
remote_version_security = int(remote_version[1])
|
||||||
|
|
||||||
|
# Now remove all versions that are not the largest
|
||||||
|
for ia in list(newest_apps):
|
||||||
|
# Don't use Beta builds to determine latest version
|
||||||
|
if newest_apps[ia]["Variant"] in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if newest_apps[ia]["Version"].startswith(version):
|
||||||
|
remote_version = newest_apps[ia]["Version"].split(".")
|
||||||
|
if remote_version[0] == "10":
|
||||||
|
remote_version.pop(0)
|
||||||
|
remote_version.pop(0)
|
||||||
|
else:
|
||||||
|
remote_version.pop(0)
|
||||||
|
if int(remote_version[0]) < remote_version_minor:
|
||||||
|
newest_apps.pop(ia)
|
||||||
|
continue
|
||||||
|
if int(remote_version[0]) == remote_version_minor:
|
||||||
|
if len(remote_version) > 1:
|
||||||
|
if int(remote_version[1]) < remote_version_security:
|
||||||
|
newest_apps.pop(ia)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if remote_version_security > 0:
|
||||||
|
newest_apps.pop(ia)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Remove duplicate builds
|
||||||
|
# ex. macOS 12.5.1 has 2 builds in the Software Update Catalog
|
||||||
|
# ref: https://twitter.com/classicii_mrmac/status/1560357471654379522
|
||||||
|
if newest_apps[ia]["Build"] in os_builds:
|
||||||
|
newest_apps.pop(ia)
|
||||||
|
continue
|
||||||
|
|
||||||
|
os_builds.append(newest_apps[ia]["Build"])
|
||||||
|
|
||||||
|
# Final passthrough
|
||||||
|
# Remove Betas if there's a non-beta version available
|
||||||
|
for ia in list(newest_apps):
|
||||||
|
if newest_apps[ia]["Variant"] in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]:
|
||||||
|
for ia2 in newest_apps:
|
||||||
|
if newest_apps[ia2]["Version"].split(".")[0] == newest_apps[ia]["Version"].split(".")[0] and newest_apps[ia2]["Variant"] not in ["CustomerSeed", "DeveloperSeed", "PublicSeed"]:
|
||||||
|
newest_apps.pop(ia)
|
||||||
|
break
|
||||||
|
|
||||||
|
return newest_apps
|
||||||
|
|
||||||
|
|
||||||
|
class LocalInstallerCatalog:
|
||||||
|
"""
|
||||||
|
Finds all macOS installers on the local machine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.available_apps: dict = self._list_local_macOS_installers()
|
||||||
|
|
||||||
|
|
||||||
|
def _list_local_macOS_installers(self):
|
||||||
|
"""
|
||||||
|
Searches for macOS installers in /Applications
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: A dictionary of macOS installers found on the local machine.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
"Install macOS Big Sur Beta.app": {
|
||||||
|
"Short Name": "Big Sur Beta",
|
||||||
|
"Version": "11.0",
|
||||||
|
"Build": "20A5343i",
|
||||||
|
"Path": "/Applications/Install macOS Big Sur Beta.app",
|
||||||
|
},
|
||||||
|
etc...
|
||||||
|
"""
|
||||||
|
|
||||||
|
application_list: dict = {}
|
||||||
|
|
||||||
|
for application in Path(APPLICATION_SEARCH_PATH).iterdir():
|
||||||
|
# Certain Microsoft Applications have strange permissions disabling us from reading them
|
||||||
|
try:
|
||||||
|
if not (Path(APPLICATION_SEARCH_PATH) / Path(application) / Path("Contents/Resources/createinstallmedia")).exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not (Path(APPLICATION_SEARCH_PATH) / Path(application) / Path("Contents/Info.plist")).exists():
|
||||||
|
continue
|
||||||
|
except PermissionError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
application_info_plist = plistlib.load((Path(APPLICATION_SEARCH_PATH) / Path(application) / Path("Contents/Info.plist")).open("rb"))
|
||||||
|
except (PermissionError, TypeError, plistlib.InvalidFileException):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if "DTPlatformVersion" not in application_info_plist:
|
||||||
|
continue
|
||||||
|
if "CFBundleDisplayName" not in application_info_plist:
|
||||||
|
continue
|
||||||
|
|
||||||
|
app_version = application_info_plist["DTPlatformVersion"]
|
||||||
|
clean_name = application_info_plist["CFBundleDisplayName"]
|
||||||
|
|
||||||
|
if "DTSDKBuild" in application_info_plist:
|
||||||
|
app_sdk = application_info_plist["DTSDKBuild"]
|
||||||
|
else:
|
||||||
|
app_sdk = "Unknown"
|
||||||
|
|
||||||
|
# app_version can sometimes report GM instead of the actual version
|
||||||
|
# This is a workaround to get the actual version
|
||||||
|
if app_version.startswith("GM"):
|
||||||
|
try:
|
||||||
|
app_version = int(app_sdk[:2])
|
||||||
|
if app_version < 20:
|
||||||
|
app_version = f"10.{app_version - 4}"
|
||||||
|
else:
|
||||||
|
app_version = f"{app_version - 9}.0"
|
||||||
|
except ValueError:
|
||||||
|
app_version = "Unknown"
|
||||||
|
|
||||||
|
# Check if App Version is High Sierra or newer
|
||||||
|
if os_data.os_conversion.os_to_kernel(app_version) < os_data.os_data.high_sierra:
|
||||||
|
continue
|
||||||
|
|
||||||
|
results = self._parse_sharedsupport_version(Path(APPLICATION_SEARCH_PATH) / Path(application)/ Path("Contents/SharedSupport/SharedSupport.dmg"))
|
||||||
|
if results[0] is not None:
|
||||||
|
app_sdk = results[0]
|
||||||
|
if results[1] is not None:
|
||||||
|
app_version = results[1]
|
||||||
|
|
||||||
|
application_list.update({
|
||||||
|
application: {
|
||||||
|
"Short Name": clean_name,
|
||||||
|
"Version": app_version,
|
||||||
|
"Build": app_sdk,
|
||||||
|
"Path": application,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# Sort Applications by version
|
||||||
|
application_list = {k: v for k, v in sorted(application_list.items(), key=lambda item: item[1]["Version"])}
|
||||||
|
return application_list
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_sharedsupport_version(self, sharedsupport_path: Path):
|
||||||
|
"""
|
||||||
|
Determine true version of macOS installer by parsing SharedSupport.dmg
|
||||||
|
This is required due to Info.plist reporting the application version, not the OS version
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
sharedsupport_path (Path): Path to SharedSupport.dmg
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: Tuple containing the build and OS version
|
||||||
|
"""
|
||||||
|
|
||||||
|
detected_build: str = None
|
||||||
|
detected_os: str = None
|
||||||
|
|
||||||
|
if not sharedsupport_path.exists():
|
||||||
|
return (detected_build, detected_os)
|
||||||
|
|
||||||
|
if not sharedsupport_path.name.endswith(".dmg"):
|
||||||
|
return (detected_build, detected_os)
|
||||||
|
|
||||||
|
|
||||||
|
# Create temporary directory to extract SharedSupport.dmg to
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
|
||||||
|
output = subprocess.run(
|
||||||
|
[
|
||||||
|
"hdiutil", "attach", "-noverify", sharedsupport_path,
|
||||||
|
"-mountpoint", tmpdir,
|
||||||
|
"-nobrowse",
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
|
||||||
|
)
|
||||||
|
|
||||||
|
if output.returncode != 0:
|
||||||
|
return (detected_build, detected_os)
|
||||||
|
|
||||||
|
ss_info = Path(SFR_SOFTWARE_UPDATE_PATH)
|
||||||
|
|
||||||
|
if Path(tmpdir / ss_info).exists():
|
||||||
|
plist = plistlib.load((tmpdir / ss_info).open("rb"))
|
||||||
|
if "Assets" in plist:
|
||||||
|
if "Build" in plist["Assets"][0]:
|
||||||
|
detected_build = plist["Assets"][0]["Build"]
|
||||||
|
if "OSVersion" in plist["Assets"][0]:
|
||||||
|
detected_os = plist["Assets"][0]["OSVersion"]
|
||||||
|
|
||||||
|
# Unmount SharedSupport.dmg
|
||||||
|
subprocess.run(["hdiutil", "detach", tmpdir], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
|
||||||
|
return (detected_build, detected_os)
|
||||||
+57
-90
@@ -4,21 +4,16 @@ import sys
|
|||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from data import model_array
|
from resources.gui import gui_main
|
||||||
from resources.build import build
|
|
||||||
from resources import (
|
from resources import (
|
||||||
cli_menu,
|
|
||||||
constants,
|
constants,
|
||||||
utilities,
|
utilities,
|
||||||
device_probe,
|
device_probe,
|
||||||
os_probe,
|
os_probe,
|
||||||
defaults,
|
defaults,
|
||||||
arguments,
|
arguments,
|
||||||
install,
|
|
||||||
tui_helpers,
|
|
||||||
reroute_payloads,
|
reroute_payloads,
|
||||||
commit_info,
|
commit_info,
|
||||||
logging_handler
|
logging_handler
|
||||||
@@ -26,35 +21,50 @@ from resources import (
|
|||||||
|
|
||||||
|
|
||||||
class OpenCoreLegacyPatcher:
|
class OpenCoreLegacyPatcher:
|
||||||
def __init__(self, launch_gui=False):
|
"""
|
||||||
self.constants = constants.Constants()
|
Initial entry point for starting OpenCore Legacy Patcher
|
||||||
self.constants.wxpython_variant = launch_gui
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
logging_handler.InitializeLoggingSupport()
|
logging_handler.InitializeLoggingSupport()
|
||||||
|
|
||||||
|
self.constants: constants.Constants = constants.Constants()
|
||||||
|
|
||||||
|
self.constants.wxpython_variant: bool = True
|
||||||
|
|
||||||
logging.info(f"- Loading OpenCore Legacy Patcher v{self.constants.patcher_version}...")
|
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:
|
||||||
if launch_gui is True:
|
gui_main.wx_python_gui(self.constants).main_menu(None)
|
||||||
utilities.disable_cls()
|
|
||||||
from resources.gui import gui_main
|
|
||||||
gui_main.wx_python_gui(self.constants).main_menu(None)
|
|
||||||
else:
|
|
||||||
self.main_menu()
|
|
||||||
|
|
||||||
|
|
||||||
def generate_base_data(self):
|
def _generate_base_data(self):
|
||||||
self.constants.detected_os = os_probe.detect_kernel_major()
|
"""
|
||||||
self.constants.detected_os_minor = os_probe.detect_kernel_minor()
|
Generate base data required for the patcher to run
|
||||||
self.constants.detected_os_build = os_probe.detect_os_build()
|
"""
|
||||||
self.constants.detected_os_version = os_probe.detect_os_version()
|
|
||||||
|
# Generate OS data
|
||||||
|
os_data = os_probe.OSProbe()
|
||||||
|
self.constants.detected_os = os_data.detect_kernel_major()
|
||||||
|
self.constants.detected_os_minor = os_data.detect_kernel_minor()
|
||||||
|
self.constants.detected_os_build = os_data.detect_os_build()
|
||||||
|
self.constants.detected_os_version = os_data.detect_os_version()
|
||||||
|
|
||||||
|
# Generate computer data
|
||||||
self.constants.computer = device_probe.Computer.probe()
|
self.constants.computer = device_probe.Computer.probe()
|
||||||
self.constants.recovery_status = utilities.check_recovery()
|
|
||||||
self.computer = self.constants.computer
|
self.computer = self.constants.computer
|
||||||
self.constants.booted_oc_disk = utilities.find_disk_off_uuid(utilities.clean_device_path(self.computer.opencore_path))
|
self.constants.booted_oc_disk = utilities.find_disk_off_uuid(utilities.clean_device_path(self.computer.opencore_path))
|
||||||
if self.constants.computer.firmware_vendor:
|
if self.constants.computer.firmware_vendor:
|
||||||
if self.constants.computer.firmware_vendor != "Apple":
|
if self.constants.computer.firmware_vendor != "Apple":
|
||||||
self.constants.host_is_hackintosh = True
|
self.constants.host_is_hackintosh = True
|
||||||
|
|
||||||
|
# Generate environment data
|
||||||
|
self.constants.recovery_status = utilities.check_recovery()
|
||||||
|
utilities.disable_cls()
|
||||||
|
|
||||||
|
# Generate binary data
|
||||||
launcher_script = None
|
launcher_script = None
|
||||||
launcher_binary = sys.executable
|
launcher_binary = sys.executable
|
||||||
if "python" in launcher_binary:
|
if "python" in launcher_binary:
|
||||||
@@ -64,83 +74,40 @@ class OpenCoreLegacyPatcher:
|
|||||||
launcher_script = launcher_script.replace("/resources/main.py", "/OpenCore-Patcher-GUI.command")
|
launcher_script = launcher_script.replace("/resources/main.py", "/OpenCore-Patcher-GUI.command")
|
||||||
self.constants.launcher_binary = launcher_binary
|
self.constants.launcher_binary = launcher_binary
|
||||||
self.constants.launcher_script = launcher_script
|
self.constants.launcher_script = launcher_script
|
||||||
self.constants.unpack_thread = threading.Thread(target=reroute_payloads.reroute_payloads(self.constants).setup_tmp_disk_image)
|
|
||||||
self.constants.unpack_thread.start()
|
|
||||||
self.constants.commit_info = commit_info.commit_info(self.constants.launcher_binary).generate_commit_info()
|
|
||||||
|
|
||||||
# Now that we have commit info, update nightly link
|
# Initialize working directory
|
||||||
|
self.constants.unpack_thread = threading.Thread(target=reroute_payloads.RoutePayloadDiskImage, args=(self.constants,))
|
||||||
|
self.constants.unpack_thread.start()
|
||||||
|
|
||||||
|
# Generate commit info
|
||||||
|
self.constants.commit_info = commit_info.ParseCommitInfo(self.constants.launcher_binary).generate_commit_info()
|
||||||
if self.constants.commit_info[0] not in ["Running from source", "Built from source"]:
|
if self.constants.commit_info[0] not in ["Running from source", "Built from source"]:
|
||||||
|
# Now that we have commit info, update nightly link
|
||||||
branch = self.constants.commit_info[0]
|
branch = self.constants.commit_info[0]
|
||||||
branch = branch.replace("refs/heads/", "")
|
branch = branch.replace("refs/heads/", "")
|
||||||
self.constants.installer_pkg_url_nightly = self.constants.installer_pkg_url_nightly.replace("main", branch)
|
self.constants.installer_pkg_url_nightly = self.constants.installer_pkg_url_nightly.replace("main", branch)
|
||||||
|
|
||||||
defaults.generate_defaults(self.computer.real_model, True, self.constants)
|
# Generate defaults
|
||||||
|
defaults.GenerateDefaults(self.computer.real_model, True, self.constants)
|
||||||
|
|
||||||
if utilities.check_cli_args() is not None:
|
if utilities.check_cli_args() is None:
|
||||||
logging.info("- Detected arguments, switching to CLI mode")
|
|
||||||
self.constants.gui_mode = True # Assumes no user interaction is required
|
|
||||||
ignore_args = ["--auto_patch", "--gui_patch", "--gui_unpatch"]
|
|
||||||
if not any(x in sys.argv for x in ignore_args):
|
|
||||||
self.constants.current_path = Path.cwd()
|
|
||||||
self.constants.cli_mode = True
|
|
||||||
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
|
|
||||||
logging.info("- Rerouting payloads location")
|
|
||||||
self.constants.payload_path = sys._MEIPASS / Path("payloads")
|
|
||||||
ignore_args = ignore_args.pop(0)
|
|
||||||
if not any(x in sys.argv for x in ignore_args):
|
|
||||||
while self.constants.unpack_thread.is_alive():
|
|
||||||
time.sleep(0.1)
|
|
||||||
arguments.arguments().parse_arguments(self.constants)
|
|
||||||
else:
|
|
||||||
logging.info(f"- No arguments present, loading {'GUI' if self.constants.wxpython_variant is True else 'TUI'} mode")
|
logging.info(f"- No arguments present, loading {'GUI' if self.constants.wxpython_variant is True else 'TUI'} mode")
|
||||||
|
return
|
||||||
|
|
||||||
|
logging.info("- Detected arguments, switching to CLI mode")
|
||||||
|
self.constants.gui_mode = True # Assumes no user interaction is required
|
||||||
|
|
||||||
def main_menu(self):
|
ignore_args = ["--auto_patch", "--gui_patch", "--gui_unpatch"]
|
||||||
response = None
|
if not any(x in sys.argv for x in ignore_args):
|
||||||
while not (response and response == -1):
|
self.constants.current_path = Path.cwd()
|
||||||
title = [
|
self.constants.cli_mode = True
|
||||||
f"OpenCore Legacy Patcher v{self.constants.patcher_version}",
|
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
|
||||||
f"Selected Model: {self.constants.custom_model or self.computer.real_model}",
|
logging.info("- Rerouting payloads location")
|
||||||
]
|
self.constants.payload_path = sys._MEIPASS / Path("payloads")
|
||||||
|
ignore_args = ignore_args.pop(0)
|
||||||
|
|
||||||
if (self.constants.custom_model or self.computer.real_model) not in model_array.SupportedSMBIOS and self.constants.allow_oc_everywhere is False:
|
if not any(x in sys.argv for x in ignore_args):
|
||||||
in_between = [
|
while self.constants.unpack_thread.is_alive():
|
||||||
"Your model is not supported by this patcher for running unsupported OSes!",
|
time.sleep(0.1)
|
||||||
"",
|
|
||||||
'If you plan to create the USB for another machine, please select the \n"Change Model" option in the menu.',
|
|
||||||
"",
|
|
||||||
'If you want to run OCLP on a native Mac, please toggle \n"Allow OpenCore on native Models" in settings',
|
|
||||||
]
|
|
||||||
elif not self.constants.custom_model and self.computer.real_model == "iMac7,1" and "SSE4.1" not in self.computer.cpu.flags:
|
|
||||||
in_between = [
|
|
||||||
"Your model requires a CPU upgrade to a CPU supporting SSE4.1+ to be supported by this patcher!",
|
|
||||||
"",
|
|
||||||
f'If you plan to create the USB for another {self.computer.real_model} with SSE4.1+, please select the "Change Model" option in the menu.',
|
|
||||||
]
|
|
||||||
elif self.constants.custom_model == "iMac7,1":
|
|
||||||
in_between = ["This model is supported", "However please ensure the CPU has been upgraded to support SSE4.1+"]
|
|
||||||
else:
|
|
||||||
in_between = ["This model is supported"]
|
|
||||||
|
|
||||||
menu = tui_helpers.TUIMenu(title, "Please select an option: ", in_between=in_between, auto_number=True, top_level=True)
|
arguments.arguments(self.constants)
|
||||||
|
|
||||||
options = (
|
|
||||||
[["Build OpenCore", build.build_opencore(self.constants.custom_model or self.constants.computer.real_model, self.constants).build_opencore]]
|
|
||||||
if ((self.constants.custom_model or self.computer.real_model) in model_array.SupportedSMBIOS) or self.constants.allow_oc_everywhere is True
|
|
||||||
else []
|
|
||||||
) + [
|
|
||||||
["Install OpenCore to USB/internal drive", install.tui_disk_installation(self.constants).copy_efi],
|
|
||||||
["Post-Install Volume Patch", cli_menu.MenuOptions(self.constants.custom_model or self.computer.real_model, self.constants).PatchVolume],
|
|
||||||
["Change Model", cli_menu.MenuOptions(self.constants.custom_model or self.computer.real_model, self.constants).change_model],
|
|
||||||
["Patcher Settings", cli_menu.MenuOptions(self.constants.custom_model or self.computer.real_model, self.constants).patcher_settings],
|
|
||||||
["Installer Creation", cli_menu.MenuOptions(self.constants.custom_model or self.computer.real_model, self.constants).download_macOS],
|
|
||||||
["Credits", cli_menu.MenuOptions(self.constants.custom_model or self.computer.real_model, self.constants).credits],
|
|
||||||
]
|
|
||||||
|
|
||||||
for option in options:
|
|
||||||
menu.add_menu_option(option[0], function=option[1])
|
|
||||||
|
|
||||||
response = menu.start()
|
|
||||||
|
|
||||||
if getattr(sys, "frozen", False) and self.constants.recovery_status is False:
|
|
||||||
subprocess.run("""osascript -e 'tell application "Terminal" to close first window' & exit""", shell=True)
|
|
||||||
|
|||||||
+67
-21
@@ -5,31 +5,77 @@ import subprocess
|
|||||||
import plistlib
|
import plistlib
|
||||||
|
|
||||||
|
|
||||||
def detect_kernel_major():
|
class OSProbe:
|
||||||
# Return Major Kernel Version
|
"""
|
||||||
# Example Output: 21 (integer)
|
Library for querying OS information specific to macOS
|
||||||
return int(platform.uname().release.partition(".")[0])
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.uname_data = platform.uname()
|
||||||
|
|
||||||
|
|
||||||
def detect_kernel_minor():
|
def detect_kernel_major(self):
|
||||||
# Return Minor Kernel Version
|
"""
|
||||||
# Example Output: 1 (integer)
|
Detect the booted major kernel version
|
||||||
return int(platform.uname().release.partition(".")[2].partition(".")[0])
|
|
||||||
|
Returns:
|
||||||
|
int: Major kernel version (ex. 21, from 21.1.0)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return int(self.uname_data.release.partition(".")[0])
|
||||||
|
|
||||||
|
|
||||||
def detect_os_version():
|
def detect_kernel_minor(self):
|
||||||
# Return OS version
|
"""
|
||||||
# Example Output: 12.0 (string)
|
Detect the booted minor kernel version
|
||||||
return subprocess.run("sw_vers -productVersion".split(), stdout=subprocess.PIPE).stdout.decode().strip()
|
|
||||||
|
Returns:
|
||||||
|
int: Minor kernel version (ex. 1, from 21.1.0)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return int(self.uname_data.release.partition(".")[2].partition(".")[0])
|
||||||
|
|
||||||
|
|
||||||
def detect_os_build():
|
def detect_os_version(self):
|
||||||
# Return OS build
|
"""
|
||||||
# Example Output: 21A5522h (string)
|
Detect the booted OS version
|
||||||
|
|
||||||
# With macOS 13.2, Apple implemented the Rapid Security Response system which
|
Returns:
|
||||||
# will change the reported build to the RSR version and not the original host
|
str: OS version (ex. 12.0)
|
||||||
# To get the proper versions:
|
"""
|
||||||
# - Host: /System/Library/CoreServices/SystemVersion.plist
|
|
||||||
# - RSR: /System/Volumes/Preboot/Cryptexes/OS/System/Library/CoreServices/SystemVersion.plist
|
result = subprocess.run(["sw_vers", "-productVersion"], stdout=subprocess.PIPE)
|
||||||
return plistlib.load(open("/System/Library/CoreServices/SystemVersion.plist", "rb"))["ProductBuildVersion"]
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError("Failed to detect OS version")
|
||||||
|
|
||||||
|
return result.stdout.decode().strip()
|
||||||
|
|
||||||
|
|
||||||
|
def detect_os_build(self, rsr: bool = False):
|
||||||
|
"""
|
||||||
|
Detect the booted OS build
|
||||||
|
|
||||||
|
Implementation note:
|
||||||
|
With macOS 13.2, Apple implemented the Rapid Security Response system which
|
||||||
|
will change the reported build to the RSR version and not the original host
|
||||||
|
|
||||||
|
To get the proper versions:
|
||||||
|
- Host: /System/Library/CoreServices/SystemVersion.plist
|
||||||
|
- RSR: /System/Volumes/Preboot/Cryptexes/OS/System/Library/CoreServices/SystemVersion.plist
|
||||||
|
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
rsr (bool): Whether to use the RSR version of the build
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: OS build (ex. 21A5522h)
|
||||||
|
"""
|
||||||
|
|
||||||
|
file_path = "/System/Library/CoreServices/SystemVersion.plist"
|
||||||
|
if rsr is True:
|
||||||
|
file_path = f"/System/Volumes/Preboot/Cryptexes/OS{file_path}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
return plistlib.load(open(file_path, "rb"))["ProductBuildVersion"]
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Failed to detect OS build: {e}")
|
||||||
|
|||||||
@@ -9,21 +9,31 @@ import tempfile
|
|||||||
import atexit
|
import atexit
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
class reroute_payloads:
|
from resources import constants
|
||||||
def __init__(self, constants):
|
|
||||||
self.constants = constants
|
class RoutePayloadDiskImage:
|
||||||
|
|
||||||
|
def __init__(self, global_constants: constants.Constants):
|
||||||
|
self.constants: constants.Constants = global_constants
|
||||||
|
|
||||||
|
self._setup_tmp_disk_image()
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_tmp_disk_image(self):
|
||||||
|
"""
|
||||||
|
Initialize temp directory and mount payloads.dmg
|
||||||
|
Create overlay for patcher to write to
|
||||||
|
|
||||||
|
Currently only applicable for GUI variant and not running from source
|
||||||
|
"""
|
||||||
|
|
||||||
def setup_tmp_disk_image(self):
|
|
||||||
# Create a temp directory to mount the payloads.dmg
|
|
||||||
# Then reroute r/w to this new temp directory
|
|
||||||
# Currently only applicable for GUI variant
|
|
||||||
if self.constants.wxpython_variant is True and not self.constants.launcher_script:
|
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",
|
||||||
@@ -38,16 +48,25 @@ class reroute_payloads:
|
|||||||
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):
|
|
||||||
# Find all DMGs that are mounted, and forcefully unmount them
|
def _unmount_active_dmgs(self, unmount_all_active=True):
|
||||||
# If our disk image was previously mounted, we need to unmount it to use again
|
"""
|
||||||
# This can happen if we crash during a previous secession, however 'atexit' class should hopefully avoid this
|
Unmounts disk images associated with OCLP
|
||||||
|
|
||||||
|
Finds all DMGs that are mounted, and forcefully unmount them
|
||||||
|
If our disk image was previously mounted, we need to unmount it to use again
|
||||||
|
This can happen if we crash during a previous secession, however 'atexit' class should hopefully avoid this
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
unmount_all_active (bool): If True, unmount all active DMGs, otherwise only unmount our own DMG
|
||||||
|
"""
|
||||||
|
|
||||||
dmg_info = subprocess.run(["hdiutil", "info", "-plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
dmg_info = 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)
|
||||||
|
|
||||||
|
|||||||
@@ -1,152 +0,0 @@
|
|||||||
# Module for running processes with real time output
|
|
||||||
# Written by CorpNewt
|
|
||||||
# Source: https://github.com/corpnewt/pymodules/blob/884c3de15b6a2570afde52fe8a14a3e946ffb18a/run.py
|
|
||||||
|
|
||||||
import sys, subprocess, time, threading, shlex, logging
|
|
||||||
from queue import Queue, Empty
|
|
||||||
|
|
||||||
ON_POSIX = 'posix' in sys.builtin_module_names
|
|
||||||
|
|
||||||
class Run:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
def _read_output(self, pipe, q):
|
|
||||||
try:
|
|
||||||
for line in iter(lambda: pipe.read(1), b''):
|
|
||||||
q.put(line)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
pipe.close()
|
|
||||||
|
|
||||||
def _create_thread(self, output):
|
|
||||||
# Creates a new queue and thread object to watch based on the output pipe sent
|
|
||||||
q = Queue()
|
|
||||||
t = threading.Thread(target=self._read_output, args=(output, q))
|
|
||||||
t.daemon = True
|
|
||||||
return (q,t)
|
|
||||||
|
|
||||||
def _stream_output(self, comm, shell = False):
|
|
||||||
output = error = ""
|
|
||||||
p = None
|
|
||||||
try:
|
|
||||||
if shell and type(comm) is list:
|
|
||||||
comm = " ".join(shlex.quote(x) for x in comm)
|
|
||||||
if not shell and type(comm) is str:
|
|
||||||
comm = shlex.split(comm)
|
|
||||||
p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0, universal_newlines=True, close_fds=ON_POSIX)
|
|
||||||
# Setup the stdout thread/queue
|
|
||||||
q,t = self._create_thread(p.stdout)
|
|
||||||
qe,te = self._create_thread(p.stderr)
|
|
||||||
# Start both threads
|
|
||||||
t.start()
|
|
||||||
te.start()
|
|
||||||
|
|
||||||
while True:
|
|
||||||
c = z = ""
|
|
||||||
try: c = q.get_nowait()
|
|
||||||
except Empty: pass
|
|
||||||
else:
|
|
||||||
sys.stdout.write(c)
|
|
||||||
output += c
|
|
||||||
sys.stdout.flush()
|
|
||||||
try: z = qe.get_nowait()
|
|
||||||
except Empty: pass
|
|
||||||
else:
|
|
||||||
sys.stderr.write(z)
|
|
||||||
error += z
|
|
||||||
sys.stderr.flush()
|
|
||||||
if not c==z=="": continue # Keep going until empty
|
|
||||||
# No output - see if still running
|
|
||||||
p.poll()
|
|
||||||
if p.returncode != None:
|
|
||||||
# Subprocess ended
|
|
||||||
break
|
|
||||||
# No output, but subprocess still running - stall for 20ms
|
|
||||||
time.sleep(0.02)
|
|
||||||
|
|
||||||
o, e = p.communicate()
|
|
||||||
return (output+o, error+e, p.returncode)
|
|
||||||
except:
|
|
||||||
if p:
|
|
||||||
try: o, e = p.communicate()
|
|
||||||
except: o = e = ""
|
|
||||||
return (output+o, error+e, p.returncode)
|
|
||||||
return ("", "Command not found!", 1)
|
|
||||||
|
|
||||||
def _decode(self, value, encoding="utf-8", errors="ignore"):
|
|
||||||
# Helper method to only decode if bytes type
|
|
||||||
if sys.version_info >= (3,0) and isinstance(value, bytes):
|
|
||||||
return value.decode(encoding,errors)
|
|
||||||
return value
|
|
||||||
|
|
||||||
def _run_command(self, comm, shell = False):
|
|
||||||
c = None
|
|
||||||
try:
|
|
||||||
if shell and type(comm) is list:
|
|
||||||
comm = " ".join(shlex.quote(x) for x in comm)
|
|
||||||
if not shell and type(comm) is str:
|
|
||||||
comm = shlex.split(comm)
|
|
||||||
p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
c = p.communicate()
|
|
||||||
except:
|
|
||||||
if c == None:
|
|
||||||
return ("", "Command not found!", 1)
|
|
||||||
return (self._decode(c[0]), self._decode(c[1]), p.returncode)
|
|
||||||
|
|
||||||
def run(self, command_list, leave_on_fail = False):
|
|
||||||
# Command list should be an array of dicts
|
|
||||||
if type(command_list) is dict:
|
|
||||||
# We only have one command
|
|
||||||
command_list = [command_list]
|
|
||||||
output_list = []
|
|
||||||
for comm in command_list:
|
|
||||||
args = comm.get("args", [])
|
|
||||||
shell = comm.get("shell", False)
|
|
||||||
stream = comm.get("stream", False)
|
|
||||||
sudo = comm.get("sudo", False)
|
|
||||||
stdout = comm.get("stdout", False)
|
|
||||||
stderr = comm.get("stderr", False)
|
|
||||||
mess = comm.get("message", None)
|
|
||||||
show = comm.get("show", False)
|
|
||||||
|
|
||||||
if not mess == None:
|
|
||||||
logging.info(mess)
|
|
||||||
|
|
||||||
if not len(args):
|
|
||||||
# nothing to process
|
|
||||||
continue
|
|
||||||
if sudo:
|
|
||||||
# Check if we have sudo
|
|
||||||
out = self._run_command(["which", "sudo"])
|
|
||||||
if "sudo" in out[0]:
|
|
||||||
# Can sudo
|
|
||||||
if type(args) is list:
|
|
||||||
args.insert(0, out[0].replace("\n", "")) # add to start of list
|
|
||||||
elif type(args) is str:
|
|
||||||
args = out[0].replace("\n", "") + " " + args # add to start of string
|
|
||||||
|
|
||||||
if show:
|
|
||||||
logging.info(" ".join(args))
|
|
||||||
|
|
||||||
if stream:
|
|
||||||
# Stream it!
|
|
||||||
out = self._stream_output(args, shell)
|
|
||||||
else:
|
|
||||||
# Just run and gather output
|
|
||||||
out = self._run_command(args, shell)
|
|
||||||
if stdout and len(out[0]):
|
|
||||||
logging.info(out[0])
|
|
||||||
if stderr and len(out[1]):
|
|
||||||
logging.info(out[1])
|
|
||||||
# Append output
|
|
||||||
output_list.append(out)
|
|
||||||
# Check for errors
|
|
||||||
if leave_on_fail and out[2] != 0:
|
|
||||||
# Got an error - leave
|
|
||||||
break
|
|
||||||
if len(output_list) == 1:
|
|
||||||
# We only ran one command - just return that output
|
|
||||||
return output_list[0]
|
|
||||||
return output_list
|
|
||||||
+121
-114
@@ -46,9 +46,9 @@ from data import os_data
|
|||||||
|
|
||||||
|
|
||||||
class PatchSysVolume:
|
class PatchSysVolume:
|
||||||
def __init__(self, model, versions, hardware_details=None):
|
def __init__(self, model: str, global_constants: constants.Constants, hardware_details: list = None):
|
||||||
self.model = model
|
self.model = model
|
||||||
self.constants: constants.Constants() = versions
|
self.constants: constants.Constants = global_constants
|
||||||
self.computer = self.constants.computer
|
self.computer = self.constants.computer
|
||||||
self.root_mount_path = None
|
self.root_mount_path = None
|
||||||
self.root_supports_snapshot = utilities.check_if_root_is_apfs_snapshot()
|
self.root_supports_snapshot = utilities.check_if_root_is_apfs_snapshot()
|
||||||
@@ -61,9 +61,9 @@ class PatchSysVolume:
|
|||||||
# GUI will detect hardware patches before starting PatchSysVolume()
|
# GUI will detect hardware patches before starting PatchSysVolume()
|
||||||
# However the TUI will not, so allow for data to be passed in manually avoiding multiple calls
|
# However the TUI will not, so allow for data to be passed in manually avoiding multiple calls
|
||||||
if hardware_details is None:
|
if hardware_details is None:
|
||||||
hardware_details = sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).detect_patch_set()
|
hardware_details = sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).detect_patch_set()
|
||||||
self.hardware_details = hardware_details
|
self.hardware_details = hardware_details
|
||||||
self.init_pathing(custom_root_mount_path=None, custom_data_mount_path=None)
|
self._init_pathing(custom_root_mount_path=None, custom_data_mount_path=None)
|
||||||
|
|
||||||
self.skip_root_kmutil_requirement = self.hardware_details["Settings: Supports Auxiliary Cache"]
|
self.skip_root_kmutil_requirement = self.hardware_details["Settings: Supports Auxiliary Cache"]
|
||||||
|
|
||||||
@@ -72,7 +72,15 @@ class PatchSysVolume:
|
|||||||
if Path(self.constants.payload_local_binaries_root_path).exists():
|
if Path(self.constants.payload_local_binaries_root_path).exists():
|
||||||
shutil.rmtree(self.constants.payload_local_binaries_root_path)
|
shutil.rmtree(self.constants.payload_local_binaries_root_path)
|
||||||
|
|
||||||
def init_pathing(self, custom_root_mount_path=None, custom_data_mount_path=None):
|
def _init_pathing(self, custom_root_mount_path: Path = None, custom_data_mount_path: Path = None):
|
||||||
|
"""
|
||||||
|
Initializes the pathing for root volume patching
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
custom_root_mount_path (Path): Custom path to mount the root volume
|
||||||
|
custom_data_mount_path (Path): Custom path to mount the data volume
|
||||||
|
|
||||||
|
"""
|
||||||
if custom_root_mount_path and custom_data_mount_path:
|
if custom_root_mount_path and custom_data_mount_path:
|
||||||
self.mount_location = custom_root_mount_path
|
self.mount_location = custom_root_mount_path
|
||||||
self.data_mount_location = custom_data_mount_path
|
self.data_mount_location = custom_data_mount_path
|
||||||
@@ -83,11 +91,12 @@ class PatchSysVolume:
|
|||||||
else:
|
else:
|
||||||
self.mount_location = ""
|
self.mount_location = ""
|
||||||
self.mount_location_data = ""
|
self.mount_location_data = ""
|
||||||
|
|
||||||
self.mount_extensions = f"{self.mount_location}/System/Library/Extensions"
|
self.mount_extensions = f"{self.mount_location}/System/Library/Extensions"
|
||||||
self.mount_application_support = f"{self.mount_location_data}/Library/Application Support"
|
self.mount_application_support = f"{self.mount_location_data}/Library/Application Support"
|
||||||
|
|
||||||
|
|
||||||
def mount_root_vol(self):
|
def _mount_root_vol(self):
|
||||||
# Returns boolean if Root Volume is available
|
# Returns boolean if Root Volume is available
|
||||||
self.root_mount_path = utilities.get_disk_path()
|
self.root_mount_path = utilities.get_disk_path()
|
||||||
if self.root_mount_path.startswith("disk"):
|
if self.root_mount_path.startswith("disk"):
|
||||||
@@ -113,56 +122,53 @@ class PatchSysVolume:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def invoke_kdk_handler(self):
|
def _merge_kdk_with_root(self, save_hid_cs=False):
|
||||||
# If we're invoked, there is no KDK installed (or something went wrong)
|
|
||||||
kdk_result = False
|
|
||||||
error_msg = ""
|
|
||||||
|
|
||||||
kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version)
|
|
||||||
|
|
||||||
if kdk_obj.success is False:
|
|
||||||
error_msg = kdk_obj.error_msg
|
|
||||||
return kdk_result, error_msg, None
|
|
||||||
|
|
||||||
kdk_download_obj = kdk_obj.retrieve_download()
|
|
||||||
|
|
||||||
# We didn't get a download object, something's wrong
|
|
||||||
if not kdk_download_obj:
|
|
||||||
if kdk_obj.kdk_already_installed is True:
|
|
||||||
error_msg = "KDK already installed, function should not have been invoked"
|
|
||||||
return kdk_result, error_msg, None
|
|
||||||
else:
|
|
||||||
error_msg = "Could not retrieve KDK"
|
|
||||||
return kdk_result, error_msg, None
|
|
||||||
|
|
||||||
# Hold thread until download is complete
|
|
||||||
kdk_download_obj.download(spawn_thread=False)
|
|
||||||
|
|
||||||
if kdk_download_obj.download_complete is False:
|
|
||||||
error_msg = kdk_download_obj.error_msg
|
|
||||||
return kdk_result, error_msg, None
|
|
||||||
|
|
||||||
kdk_result = kdk_obj.validate_kdk_checksum()
|
|
||||||
downloaded_kdk = self.constants.kdk_download_path
|
|
||||||
|
|
||||||
return kdk_result, error_msg, downloaded_kdk
|
|
||||||
|
|
||||||
|
|
||||||
def merge_kdk_with_root(self, save_hid_cs=False):
|
|
||||||
if self.skip_root_kmutil_requirement is True:
|
if self.skip_root_kmutil_requirement is True:
|
||||||
return
|
return
|
||||||
if self.constants.detected_os < os_data.os_data.ventura:
|
if self.constants.detected_os < os_data.os_data.ventura:
|
||||||
return
|
return
|
||||||
|
|
||||||
downloaded_kdk = None
|
if self.constants.kdk_download_path.exists():
|
||||||
kdk_path = sys_patch_helpers.sys_patch_helpers(self.constants).determine_kdk_present(match_closest=False)
|
if kdk_handler.KernelDebugKitUtilities().install_kdk_dmg(self.constants.kdk_download_path) is False:
|
||||||
if kdk_path is None:
|
logging.info("Failed to install KDK")
|
||||||
if not self.constants.kdk_download_path.exists():
|
raise Exception("Failed to install KDK")
|
||||||
kdk_result, error_msg, downloaded_kdk = self.invoke_kdk_handler()
|
|
||||||
if kdk_result is False:
|
kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version)
|
||||||
raise Exception(f"Unable to download KDK: {error_msg}")
|
if kdk_obj.success is False:
|
||||||
sys_patch_helpers.sys_patch_helpers(self.constants).install_kdk()
|
logging.info(f"Unable to get KDK info: {kdk_obj.error_msg}")
|
||||||
kdk_path = sys_patch_helpers.sys_patch_helpers(self.constants).determine_kdk_present(match_closest=True, override_build=downloaded_kdk)
|
raise Exception(f"Unable to get KDK info: {kdk_obj.error_msg}")
|
||||||
|
|
||||||
|
if kdk_obj.kdk_already_installed is False:
|
||||||
|
|
||||||
|
kdk_download_obj = kdk_obj.retrieve_download()
|
||||||
|
if not kdk_download_obj:
|
||||||
|
logging.info(f"Could not retrieve KDK: {kdk_obj.error_msg}")
|
||||||
|
|
||||||
|
# Hold thread until download is complete
|
||||||
|
kdk_download_obj.download(spawn_thread=False)
|
||||||
|
|
||||||
|
if kdk_download_obj.download_complete is False:
|
||||||
|
error_msg = kdk_download_obj.error_msg
|
||||||
|
logging.info(f"Could not download KDK: {error_msg}")
|
||||||
|
raise Exception(f"Could not download KDK: {error_msg}")
|
||||||
|
|
||||||
|
if kdk_obj.validate_kdk_checksum() is False:
|
||||||
|
logging.info(f"KDK checksum validation failed: {kdk_obj.error_msg}")
|
||||||
|
raise Exception(f"KDK checksum validation failed: {kdk_obj.error_msg}")
|
||||||
|
|
||||||
|
kdk_handler.KernelDebugKitUtilities().install_kdk_dmg(self.constants.kdk_download_path)
|
||||||
|
# re-init kdk_obj to get the new kdk_installed_path
|
||||||
|
kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version)
|
||||||
|
if kdk_obj.success is False:
|
||||||
|
logging.info(f"Unable to get KDK info: {kdk_obj.error_msg}")
|
||||||
|
raise Exception(f"Unable to get KDK info: {kdk_obj.error_msg}")
|
||||||
|
|
||||||
|
if kdk_obj.kdk_already_installed is False:
|
||||||
|
# We shouldn't get here, but just in case
|
||||||
|
logging.warning(f"KDK was not installed, but should have been: {kdk_obj.error_msg}")
|
||||||
|
raise Exception("KDK was not installed, but should have been: {kdk_obj.error_msg}")
|
||||||
|
|
||||||
|
kdk_path = Path(kdk_obj.kdk_installed_path) if kdk_obj.kdk_installed_path != "" else None
|
||||||
|
|
||||||
oclp_plist = Path("/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist")
|
oclp_plist = Path("/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist")
|
||||||
if (Path(self.mount_location) / Path("System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern")).exists() and oclp_plist.exists():
|
if (Path(self.mount_location) / Path("System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern")).exists() and oclp_plist.exists():
|
||||||
@@ -178,7 +184,7 @@ class PatchSysVolume:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if kdk_path is None:
|
if kdk_path is None:
|
||||||
logging.info(f"- Unable to find Kernel Debug Kit: {downloaded_kdk}")
|
logging.info(f"- Unable to find Kernel Debug Kit")
|
||||||
raise Exception("Unable to find Kernel Debug Kit")
|
raise Exception("Unable to find Kernel Debug Kit")
|
||||||
self.kdk_path = kdk_path
|
self.kdk_path = kdk_path
|
||||||
logging.info(f"- Found KDK at: {kdk_path}")
|
logging.info(f"- Found KDK at: {kdk_path}")
|
||||||
@@ -190,7 +196,7 @@ class PatchSysVolume:
|
|||||||
# Note it's a folder, not a file
|
# Note it's a folder, not a file
|
||||||
utilities.elevated(["cp", "-r", cs_path, f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
utilities.elevated(["cp", "-r", cs_path, f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
|
||||||
logging.info("- Merging KDK with Root Volume")
|
logging.info(f"- Merging KDK with Root Volume: {kdk_path.name}")
|
||||||
utilities.elevated(
|
utilities.elevated(
|
||||||
# Only merge '/System/Library/Extensions'
|
# Only merge '/System/Library/Extensions'
|
||||||
# 'Kernels' and 'KernelSupport' is wasted space for root patching (we don't care above dev kernels)
|
# 'Kernels' and 'KernelSupport' is wasted space for root patching (we don't care above dev kernels)
|
||||||
@@ -214,7 +220,7 @@ class PatchSysVolume:
|
|||||||
utilities.elevated(["rm", "-rf", f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
utilities.elevated(["rm", "-rf", f"{self.constants.payload_path}/IOHIDEventDriver_CodeSignature.bak"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
|
||||||
|
|
||||||
def unpatch_root_vol(self):
|
def _unpatch_root_vol(self):
|
||||||
if self.constants.detected_os > os_data.os_data.catalina and self.root_supports_snapshot is True:
|
if self.constants.detected_os > os_data.os_data.catalina and self.root_supports_snapshot is True:
|
||||||
logging.info("- Reverting to last signed APFS snapshot")
|
logging.info("- Reverting to last signed APFS snapshot")
|
||||||
result = utilities.elevated(["bless", "--mount", self.mount_location, "--bootefi", "--last-sealed-snapshot"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
result = utilities.elevated(["bless", "--mount", self.mount_location, "--bootefi", "--last-sealed-snapshot"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
@@ -224,18 +230,18 @@ class PatchSysVolume:
|
|||||||
logging.info(result.stdout.decode())
|
logging.info(result.stdout.decode())
|
||||||
logging.info("- Failed to revert snapshot via Apple's 'bless' command")
|
logging.info("- Failed to revert snapshot via Apple's 'bless' command")
|
||||||
else:
|
else:
|
||||||
self.clean_skylight_plugins()
|
self._clean_skylight_plugins()
|
||||||
self.delete_nonmetal_enforcement()
|
self._delete_nonmetal_enforcement()
|
||||||
self.clean_auxiliary_kc()
|
self._clean_auxiliary_kc()
|
||||||
self.constants.root_patcher_succeeded = True
|
self.constants.root_patcher_succeeded = True
|
||||||
logging.info("- Unpatching complete")
|
logging.info("- Unpatching complete")
|
||||||
logging.info("\nPlease reboot the machine for patches to take effect")
|
logging.info("\nPlease reboot the machine for patches to take effect")
|
||||||
|
|
||||||
def rebuild_snapshot(self):
|
def _rebuild_snapshot(self):
|
||||||
if self.rebuild_kernel_collection() is True:
|
if self._rebuild_kernel_collection() is True:
|
||||||
self.update_preboot_kernel_cache()
|
self.update_preboot_kernel_cache()
|
||||||
self.rebuild_dyld_shared_cache()
|
self._rebuild_dyld_shared_cache()
|
||||||
if self.create_new_apfs_snapshot() is True:
|
if self._create_new_apfs_snapshot() is True:
|
||||||
logging.info("- Patching complete")
|
logging.info("- Patching complete")
|
||||||
logging.info("\nPlease reboot the machine for patches to take effect")
|
logging.info("\nPlease reboot the machine for patches to take effect")
|
||||||
if self.needs_kmutil_exemptions is True:
|
if self.needs_kmutil_exemptions is True:
|
||||||
@@ -244,7 +250,7 @@ class PatchSysVolume:
|
|||||||
if self.constants.gui_mode is False:
|
if self.constants.gui_mode is False:
|
||||||
input("\nPress [ENTER] to continue")
|
input("\nPress [ENTER] to continue")
|
||||||
|
|
||||||
def rebuild_kernel_collection(self):
|
def _rebuild_kernel_collection(self):
|
||||||
logging.info("- Rebuilding Kernel Cache (This may take some time)")
|
logging.info("- Rebuilding Kernel Cache (This may take some time)")
|
||||||
if self.constants.detected_os > os_data.os_data.catalina:
|
if self.constants.detected_os > os_data.os_data.catalina:
|
||||||
# Base Arguments
|
# Base Arguments
|
||||||
@@ -331,15 +337,15 @@ class PatchSysVolume:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
for file in ["KextPolicy", "KextPolicy-shm", "KextPolicy-wal"]:
|
for file in ["KextPolicy", "KextPolicy-shm", "KextPolicy-wal"]:
|
||||||
self.remove_file("/private/var/db/SystemPolicyConfiguration/", file)
|
self._remove_file("/private/var/db/SystemPolicyConfiguration/", file)
|
||||||
else:
|
else:
|
||||||
# Install RSRHelper utility to handle desynced KCs
|
# Install RSRHelper utility to handle desynced KCs
|
||||||
sys_patch_helpers.sys_patch_helpers(self.constants).install_rsr_repair_binary()
|
sys_patch_helpers.SysPatchHelpers(self.constants).install_rsr_repair_binary()
|
||||||
|
|
||||||
logging.info("- Successfully built new kernel cache")
|
logging.info("- Successfully built new kernel cache")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def create_new_apfs_snapshot(self):
|
def _create_new_apfs_snapshot(self):
|
||||||
if self.root_supports_snapshot is True:
|
if self.root_supports_snapshot is True:
|
||||||
logging.info("- Creating new APFS snapshot")
|
logging.info("- Creating new APFS snapshot")
|
||||||
bless = utilities.elevated(
|
bless = utilities.elevated(
|
||||||
@@ -356,24 +362,25 @@ class PatchSysVolume:
|
|||||||
if "Can't use last-sealed-snapshot or create-snapshot on non system volume" in bless.stdout.decode():
|
if "Can't use last-sealed-snapshot or create-snapshot on non system volume" in bless.stdout.decode():
|
||||||
logging.info("- This is an APFS bug with Monterey and newer! Perform a clean installation to ensure your APFS volume is built correctly")
|
logging.info("- This is an APFS bug with Monterey and newer! Perform a clean installation to ensure your APFS volume is built correctly")
|
||||||
return False
|
return False
|
||||||
self.unmount_drive()
|
self._unmount_drive()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def unmount_drive(self):
|
def _unmount_drive(self):
|
||||||
logging.info("- Unmounting Root Volume (Don't worry if this fails)")
|
logging.info("- Unmounting Root Volume (Don't worry if this fails)")
|
||||||
utilities.elevated(["diskutil", "unmount", self.root_mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode()
|
utilities.elevated(["diskutil", "unmount", self.root_mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode()
|
||||||
|
|
||||||
def rebuild_dyld_shared_cache(self):
|
def _rebuild_dyld_shared_cache(self):
|
||||||
if self.constants.detected_os <= os_data.os_data.catalina:
|
if self.constants.detected_os > os_data.os_data.catalina:
|
||||||
logging.info("- Rebuilding dyld shared cache")
|
return
|
||||||
utilities.process_status(utilities.elevated(["update_dyld_shared_cache", "-root", f"{self.mount_location}/"]))
|
logging.info("- Rebuilding dyld shared cache")
|
||||||
|
utilities.process_status(utilities.elevated(["update_dyld_shared_cache", "-root", f"{self.mount_location}/"]))
|
||||||
|
|
||||||
def update_preboot_kernel_cache(self):
|
def update_preboot_kernel_cache(self):
|
||||||
if self.constants.detected_os == os_data.os_data.catalina:
|
if self.constants.detected_os == os_data.os_data.catalina:
|
||||||
logging.info("- Rebuilding preboot kernel cache")
|
logging.info("- Rebuilding preboot kernel cache")
|
||||||
utilities.process_status(utilities.elevated(["kcditto"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
utilities.process_status(utilities.elevated(["kcditto"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
||||||
|
|
||||||
def clean_skylight_plugins(self):
|
def _clean_skylight_plugins(self):
|
||||||
if (Path(self.mount_application_support) / Path("SkyLightPlugins/")).exists():
|
if (Path(self.mount_application_support) / Path("SkyLightPlugins/")).exists():
|
||||||
logging.info("- Found SkylightPlugins folder, removing old plugins")
|
logging.info("- Found SkylightPlugins folder, removing old plugins")
|
||||||
utilities.process_status(utilities.elevated(["rm", "-Rf", f"{self.mount_application_support}/SkyLightPlugins"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
utilities.process_status(utilities.elevated(["rm", "-Rf", f"{self.mount_application_support}/SkyLightPlugins"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
||||||
@@ -382,14 +389,14 @@ class PatchSysVolume:
|
|||||||
logging.info("- Creating SkylightPlugins folder")
|
logging.info("- Creating SkylightPlugins folder")
|
||||||
utilities.process_status(utilities.elevated(["mkdir", "-p", f"{self.mount_application_support}/SkyLightPlugins/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
utilities.process_status(utilities.elevated(["mkdir", "-p", f"{self.mount_application_support}/SkyLightPlugins/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
||||||
|
|
||||||
def delete_nonmetal_enforcement(self):
|
def _delete_nonmetal_enforcement(self):
|
||||||
for arg in ["useMetal", "useIOP"]:
|
for arg in ["useMetal", "useIOP"]:
|
||||||
result = subprocess.run(["defaults", "read", "/Library/Preferences/com.apple.CoreDisplay", arg], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode("utf-8").strip()
|
result = subprocess.run(["defaults", "read", "/Library/Preferences/com.apple.CoreDisplay", arg], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout.decode("utf-8").strip()
|
||||||
if result in ["0", "false", "1", "true"]:
|
if result in ["0", "false", "1", "true"]:
|
||||||
logging.info(f"- Removing non-Metal Enforcement Preference: {arg}")
|
logging.info(f"- Removing non-Metal Enforcement Preference: {arg}")
|
||||||
utilities.elevated(["defaults", "delete", "/Library/Preferences/com.apple.CoreDisplay", arg])
|
utilities.elevated(["defaults", "delete", "/Library/Preferences/com.apple.CoreDisplay", arg])
|
||||||
|
|
||||||
def clean_auxiliary_kc(self):
|
def _clean_auxiliary_kc(self):
|
||||||
# When reverting root volume patches, the AuxKC will still retain the UUID
|
# When reverting root volume patches, the AuxKC will still retain the UUID
|
||||||
# it was built against. Thus when Boot/SysKC are reverted, Aux will break
|
# it was built against. Thus when Boot/SysKC are reverted, Aux will break
|
||||||
# To resolve this, delete all installed kexts in /L*/E* and rebuild the AuxKC
|
# To resolve this, delete all installed kexts in /L*/E* and rebuild the AuxKC
|
||||||
@@ -410,7 +417,7 @@ class PatchSysVolume:
|
|||||||
for file in oclp_plist_data[key]["Install"][location]:
|
for file in oclp_plist_data[key]["Install"][location]:
|
||||||
if not file.endswith(".kext"):
|
if not file.endswith(".kext"):
|
||||||
continue
|
continue
|
||||||
self.remove_file("/Library/Extensions", file)
|
self._remove_file("/Library/Extensions", file)
|
||||||
|
|
||||||
# Handle situations where users migrated from older OSes with a lot of garbage in /L*/E*
|
# Handle situations where users migrated from older OSes with a lot of garbage in /L*/E*
|
||||||
# ex. Nvidia Web Drivers, NetUSB, dosdude1's patches, etc.
|
# ex. Nvidia Web Drivers, NetUSB, dosdude1's patches, etc.
|
||||||
@@ -434,17 +441,17 @@ class PatchSysVolume:
|
|||||||
# ex. Symlinks pointing to symlinks pointing to dead files
|
# ex. Symlinks pointing to symlinks pointing to dead files
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def write_patchset(self, patchset):
|
def _write_patchset(self, patchset):
|
||||||
destination_path = f"{self.mount_location}/System/Library/CoreServices"
|
destination_path = f"{self.mount_location}/System/Library/CoreServices"
|
||||||
file_name = "OpenCore-Legacy-Patcher.plist"
|
file_name = "OpenCore-Legacy-Patcher.plist"
|
||||||
destination_path_file = f"{destination_path}/{file_name}"
|
destination_path_file = f"{destination_path}/{file_name}"
|
||||||
if sys_patch_helpers.sys_patch_helpers(self.constants).generate_patchset_plist(patchset, file_name, self.kdk_path):
|
if sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, file_name, self.kdk_path):
|
||||||
logging.info("- Writing patchset information to Root Volume")
|
logging.info("- Writing patchset information to Root Volume")
|
||||||
if Path(destination_path_file).exists():
|
if Path(destination_path_file).exists():
|
||||||
utilities.process_status(utilities.elevated(["rm", destination_path_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
utilities.process_status(utilities.elevated(["rm", destination_path_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
||||||
utilities.process_status(utilities.elevated(["cp", f"{self.constants.payload_path}/{file_name}", destination_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
utilities.process_status(utilities.elevated(["cp", f"{self.constants.payload_path}/{file_name}", destination_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
||||||
|
|
||||||
def add_auxkc_support(self, install_file, source_folder_path, install_patch_directory, destination_folder_path):
|
def _add_auxkc_support(self, install_file, source_folder_path, install_patch_directory, destination_folder_path):
|
||||||
# In macOS Ventura, KDKs are required to build new Boot and System KCs
|
# In macOS Ventura, KDKs are required to build new Boot and System KCs
|
||||||
# However for some patch sets, we're able to use the Auxiliary KCs with '/Library/Extensions'
|
# However for some patch sets, we're able to use the Auxiliary KCs with '/Library/Extensions'
|
||||||
|
|
||||||
@@ -480,11 +487,11 @@ class PatchSysVolume:
|
|||||||
plist_data["OSBundleRequired"] = "Auxiliary"
|
plist_data["OSBundleRequired"] = "Auxiliary"
|
||||||
plistlib.dump(plist_data, plist_path.open("wb"))
|
plistlib.dump(plist_data, plist_path.open("wb"))
|
||||||
|
|
||||||
self.check_kexts_needs_authentication(install_file)
|
self._check_kexts_needs_authentication(install_file)
|
||||||
|
|
||||||
return updated_install_location
|
return updated_install_location
|
||||||
|
|
||||||
def check_kexts_needs_authentication(self, kext_name):
|
def _check_kexts_needs_authentication(self, kext_name):
|
||||||
# Verify whether the user needs to authenticate in System Preferences
|
# Verify whether the user needs to authenticate in System Preferences
|
||||||
# Specifically under 'private/var/db/KernelManagement/AuxKC/CurrentAuxKC/com.apple.kcgen.instructions.plist'
|
# Specifically under 'private/var/db/KernelManagement/AuxKC/CurrentAuxKC/com.apple.kcgen.instructions.plist'
|
||||||
# ["kextsToBuild"][i]:
|
# ["kextsToBuild"][i]:
|
||||||
@@ -506,21 +513,21 @@ class PatchSysVolume:
|
|||||||
logging.info(f" - {kext_name} requires authentication in System Preferences")
|
logging.info(f" - {kext_name} requires authentication in System Preferences")
|
||||||
self.constants.needs_to_open_preferences = True # Notify in GUI to open System Preferences
|
self.constants.needs_to_open_preferences = True # Notify in GUI to open System Preferences
|
||||||
|
|
||||||
def patch_root_vol(self):
|
def _patch_root_vol(self):
|
||||||
logging.info(f"- Running patches for {self.model}")
|
logging.info(f"- Running patches for {self.model}")
|
||||||
if self.patch_set_dictionary != {}:
|
if self.patch_set_dictionary != {}:
|
||||||
self.execute_patchset(self.patch_set_dictionary)
|
self._execute_patchset(self.patch_set_dictionary)
|
||||||
else:
|
else:
|
||||||
self.execute_patchset(sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).generate_patchset(self.hardware_details))
|
self._execute_patchset(sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).generate_patchset(self.hardware_details))
|
||||||
|
|
||||||
if self.constants.wxpython_variant is True and self.constants.detected_os >= os_data.os_data.big_sur:
|
if self.constants.wxpython_variant is True and self.constants.detected_os >= os_data.os_data.big_sur:
|
||||||
sys_patch_auto.AutomaticSysPatch(self.constants).install_auto_patcher_launch_agent()
|
sys_patch_auto.AutomaticSysPatch(self.constants).install_auto_patcher_launch_agent()
|
||||||
|
|
||||||
self.rebuild_snapshot()
|
self._rebuild_snapshot()
|
||||||
|
|
||||||
def execute_patchset(self, required_patches):
|
def _execute_patchset(self, required_patches):
|
||||||
source_files_path = str(self.constants.payload_local_binaries_root_path)
|
source_files_path = str(self.constants.payload_local_binaries_root_path)
|
||||||
self.preflight_checks(required_patches, source_files_path)
|
self._preflight_checks(required_patches, source_files_path)
|
||||||
for patch in required_patches:
|
for patch in required_patches:
|
||||||
logging.info("- Installing Patchset: " + patch)
|
logging.info("- Installing Patchset: " + patch)
|
||||||
if "Remove" in required_patches[patch]:
|
if "Remove" in required_patches[patch]:
|
||||||
@@ -528,7 +535,7 @@ class PatchSysVolume:
|
|||||||
logging.info("- Remove Files at: " + remove_patch_directory)
|
logging.info("- Remove Files at: " + remove_patch_directory)
|
||||||
for remove_patch_file in required_patches[patch]["Remove"][remove_patch_directory]:
|
for remove_patch_file in required_patches[patch]["Remove"][remove_patch_directory]:
|
||||||
destination_folder_path = str(self.mount_location) + remove_patch_directory
|
destination_folder_path = str(self.mount_location) + remove_patch_directory
|
||||||
self.remove_file(destination_folder_path, remove_patch_file)
|
self._remove_file(destination_folder_path, remove_patch_file)
|
||||||
|
|
||||||
|
|
||||||
for method_install in ["Install", "Install Non-Root"]:
|
for method_install in ["Install", "Install Non-Root"]:
|
||||||
@@ -542,10 +549,10 @@ class PatchSysVolume:
|
|||||||
else:
|
else:
|
||||||
if install_patch_directory == "/Library/Extensions":
|
if install_patch_directory == "/Library/Extensions":
|
||||||
self.needs_kmutil_exemptions = True
|
self.needs_kmutil_exemptions = True
|
||||||
self.check_kexts_needs_authentication(install_file)
|
self._check_kexts_needs_authentication(install_file)
|
||||||
destination_folder_path = str(self.mount_location_data) + install_patch_directory
|
destination_folder_path = str(self.mount_location_data) + install_patch_directory
|
||||||
|
|
||||||
updated_destination_folder_path = self.add_auxkc_support(install_file, source_folder_path, install_patch_directory, destination_folder_path)
|
updated_destination_folder_path = self._add_auxkc_support(install_file, source_folder_path, install_patch_directory, destination_folder_path)
|
||||||
|
|
||||||
if destination_folder_path != updated_destination_folder_path:
|
if destination_folder_path != updated_destination_folder_path:
|
||||||
# Update required_patches to reflect the new destination folder path
|
# Update required_patches to reflect the new destination folder path
|
||||||
@@ -556,7 +563,7 @@ class PatchSysVolume:
|
|||||||
|
|
||||||
destination_folder_path = updated_destination_folder_path
|
destination_folder_path = updated_destination_folder_path
|
||||||
|
|
||||||
self.install_new_file(source_folder_path, destination_folder_path, install_file)
|
self._install_new_file(source_folder_path, destination_folder_path, install_file)
|
||||||
|
|
||||||
if "Processes" in required_patches[patch]:
|
if "Processes" in required_patches[patch]:
|
||||||
for process in required_patches[patch]["Processes"]:
|
for process in required_patches[patch]["Processes"]:
|
||||||
@@ -569,24 +576,24 @@ class PatchSysVolume:
|
|||||||
logging.info(f"- Running Process:\n{process}")
|
logging.info(f"- Running Process:\n{process}")
|
||||||
utilities.process_status(subprocess.run(process, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True))
|
utilities.process_status(subprocess.run(process, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True))
|
||||||
if any(x in required_patches for x in ["AMD Legacy GCN", "AMD Legacy Polaris", "AMD Legacy Vega"]):
|
if any(x in required_patches for x in ["AMD Legacy GCN", "AMD Legacy Polaris", "AMD Legacy Vega"]):
|
||||||
sys_patch_helpers.sys_patch_helpers(self.constants).disable_window_server_caching()
|
sys_patch_helpers.SysPatchHelpers(self.constants).disable_window_server_caching()
|
||||||
if any(x in required_patches for x in ["Intel Ivy Bridge", "Intel Haswell"]):
|
if any(x in required_patches for x in ["Intel Ivy Bridge", "Intel Haswell"]):
|
||||||
sys_patch_helpers.sys_patch_helpers(self.constants).remove_news_widgets()
|
sys_patch_helpers.SysPatchHelpers(self.constants).remove_news_widgets()
|
||||||
self.write_patchset(required_patches)
|
self._write_patchset(required_patches)
|
||||||
|
|
||||||
def preflight_checks(self, required_patches, source_files_path):
|
def _preflight_checks(self, required_patches, source_files_path):
|
||||||
logging.info("- Running Preflight Checks before patching")
|
logging.info("- Running Preflight Checks before patching")
|
||||||
|
|
||||||
# Make sure old SkyLight plugins aren't being used
|
# Make sure old SkyLight plugins aren't being used
|
||||||
self.clean_skylight_plugins()
|
self._clean_skylight_plugins()
|
||||||
# Make sure non-Metal Enforcement preferences are not present
|
# Make sure non-Metal Enforcement preferences are not present
|
||||||
self.delete_nonmetal_enforcement()
|
self._delete_nonmetal_enforcement()
|
||||||
# Make sure we clean old kexts in /L*/E* that are not in the patchset
|
# Make sure we clean old kexts in /L*/E* that are not in the patchset
|
||||||
self.clean_auxiliary_kc()
|
self._clean_auxiliary_kc()
|
||||||
|
|
||||||
# Make sure SNB kexts are compatible with the host
|
# Make sure SNB kexts are compatible with the host
|
||||||
if "Intel Sandy Bridge" in required_patches:
|
if "Intel Sandy Bridge" in required_patches:
|
||||||
sys_patch_helpers.sys_patch_helpers(self.constants).snb_board_id_patch(source_files_path)
|
sys_patch_helpers.SysPatchHelpers(self.constants).snb_board_id_patch(source_files_path)
|
||||||
|
|
||||||
for patch in required_patches:
|
for patch in required_patches:
|
||||||
# Check if all files are present
|
# Check if all files are present
|
||||||
@@ -602,11 +609,11 @@ class PatchSysVolume:
|
|||||||
should_save_cs = False
|
should_save_cs = False
|
||||||
if "Legacy USB 1.1" in required_patches:
|
if "Legacy USB 1.1" in required_patches:
|
||||||
should_save_cs = True
|
should_save_cs = True
|
||||||
self.merge_kdk_with_root(save_hid_cs=should_save_cs)
|
self._merge_kdk_with_root(save_hid_cs=should_save_cs)
|
||||||
|
|
||||||
logging.info("- Finished Preflight, starting patching")
|
logging.info("- Finished Preflight, starting patching")
|
||||||
|
|
||||||
def install_new_file(self, source_folder, destination_folder, file_name):
|
def _install_new_file(self, source_folder, destination_folder, file_name):
|
||||||
# .frameworks are merged
|
# .frameworks are merged
|
||||||
# .kexts and .apps are deleted and replaced
|
# .kexts and .apps are deleted and replaced
|
||||||
file_name_str = str(file_name)
|
file_name_str = str(file_name)
|
||||||
@@ -619,7 +626,7 @@ class PatchSysVolume:
|
|||||||
# merge with rsync
|
# merge with rsync
|
||||||
logging.info(f" - Installing: {file_name}")
|
logging.info(f" - Installing: {file_name}")
|
||||||
utilities.elevated(["rsync", "-r", "-i", "-a", f"{source_folder}/{file_name}", f"{destination_folder}/"], stdout=subprocess.PIPE)
|
utilities.elevated(["rsync", "-r", "-i", "-a", f"{source_folder}/{file_name}", f"{destination_folder}/"], stdout=subprocess.PIPE)
|
||||||
self.fix_permissions(destination_folder + "/" + file_name)
|
self._fix_permissions(destination_folder + "/" + file_name)
|
||||||
elif Path(source_folder + "/" + file_name_str).is_dir():
|
elif Path(source_folder + "/" + file_name_str).is_dir():
|
||||||
# Applicable for .kext, .app, .plugin, .bundle, all of which are directories
|
# Applicable for .kext, .app, .plugin, .bundle, all of which are directories
|
||||||
if Path(destination_folder + "/" + file_name).exists():
|
if Path(destination_folder + "/" + file_name).exists():
|
||||||
@@ -628,7 +635,7 @@ class PatchSysVolume:
|
|||||||
else:
|
else:
|
||||||
logging.info(f" - Installing: {file_name}")
|
logging.info(f" - Installing: {file_name}")
|
||||||
utilities.process_status(utilities.elevated(["cp", "-R", f"{source_folder}/{file_name}", destination_folder], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
utilities.process_status(utilities.elevated(["cp", "-R", f"{source_folder}/{file_name}", destination_folder], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
||||||
self.fix_permissions(destination_folder + "/" + file_name)
|
self._fix_permissions(destination_folder + "/" + file_name)
|
||||||
else:
|
else:
|
||||||
# Assume it's an individual file, replace as normal
|
# Assume it's an individual file, replace as normal
|
||||||
if Path(destination_folder + "/" + file_name).exists():
|
if Path(destination_folder + "/" + file_name).exists():
|
||||||
@@ -637,9 +644,9 @@ class PatchSysVolume:
|
|||||||
else:
|
else:
|
||||||
logging.info(f" - Installing: {file_name}")
|
logging.info(f" - Installing: {file_name}")
|
||||||
utilities.process_status(utilities.elevated(["cp", f"{source_folder}/{file_name}", destination_folder], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
utilities.process_status(utilities.elevated(["cp", f"{source_folder}/{file_name}", destination_folder], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
||||||
self.fix_permissions(destination_folder + "/" + file_name)
|
self._fix_permissions(destination_folder + "/" + file_name)
|
||||||
|
|
||||||
def remove_file(self, destination_folder, file_name):
|
def _remove_file(self, destination_folder, file_name):
|
||||||
if Path(destination_folder + "/" + file_name).exists():
|
if Path(destination_folder + "/" + file_name).exists():
|
||||||
logging.info(f" - Removing: {file_name}")
|
logging.info(f" - Removing: {file_name}")
|
||||||
if Path(destination_folder + "/" + file_name).is_dir():
|
if Path(destination_folder + "/" + file_name).is_dir():
|
||||||
@@ -648,7 +655,7 @@ class PatchSysVolume:
|
|||||||
utilities.process_status(utilities.elevated(["rm", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
utilities.process_status(utilities.elevated(["rm", f"{destination_folder}/{file_name}"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
||||||
|
|
||||||
|
|
||||||
def fix_permissions(self, destination_file):
|
def _fix_permissions(self, destination_file):
|
||||||
chmod_args = ["chmod", "-Rf", "755", destination_file]
|
chmod_args = ["chmod", "-Rf", "755", destination_file]
|
||||||
chown_args = ["chown", "-Rf", "root:wheel", destination_file]
|
chown_args = ["chown", "-Rf", "root:wheel", destination_file]
|
||||||
if not Path(destination_file).is_dir():
|
if not Path(destination_file).is_dir():
|
||||||
@@ -659,7 +666,7 @@ class PatchSysVolume:
|
|||||||
utilities.process_status(utilities.elevated(chown_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
utilities.process_status(utilities.elevated(chown_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
||||||
|
|
||||||
|
|
||||||
def check_files(self):
|
def _check_files(self):
|
||||||
if Path(self.constants.payload_local_binaries_root_path).exists():
|
if Path(self.constants.payload_local_binaries_root_path).exists():
|
||||||
logging.info("- Local PatcherSupportPkg resources available, continuing...")
|
logging.info("- Local PatcherSupportPkg resources available, continuing...")
|
||||||
return True
|
return True
|
||||||
@@ -678,7 +685,7 @@ class PatchSysVolume:
|
|||||||
def start_patch(self):
|
def start_patch(self):
|
||||||
logging.info("- Starting Patch Process")
|
logging.info("- Starting Patch Process")
|
||||||
logging.info(f"- Determining Required Patch set for Darwin {self.constants.detected_os}")
|
logging.info(f"- Determining Required Patch set for Darwin {self.constants.detected_os}")
|
||||||
self.patch_set_dictionary = sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).generate_patchset(self.hardware_details)
|
self.patch_set_dictionary = sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).generate_patchset(self.hardware_details)
|
||||||
|
|
||||||
if self.patch_set_dictionary == {}:
|
if self.patch_set_dictionary == {}:
|
||||||
change_menu = None
|
change_menu = None
|
||||||
@@ -692,11 +699,11 @@ class PatchSysVolume:
|
|||||||
logging.info("- Continuing root patching")
|
logging.info("- Continuing root patching")
|
||||||
if change_menu in ["y", "Y"]:
|
if change_menu in ["y", "Y"]:
|
||||||
logging.info("- Verifying whether Root Patching possible")
|
logging.info("- Verifying whether Root Patching possible")
|
||||||
if sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).verify_patch_allowed(print_errors=not self.constants.wxpython_variant) is True:
|
if sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).verify_patch_allowed(print_errors=not self.constants.wxpython_variant) is True:
|
||||||
logging.info("- Patcher is capable of patching")
|
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:
|
if self.constants.gui_mode is False:
|
||||||
input("\nPress [ENTER] to return to the main menu")
|
input("\nPress [ENTER] to return to the main menu")
|
||||||
else:
|
else:
|
||||||
@@ -711,9 +718,9 @@ class PatchSysVolume:
|
|||||||
|
|
||||||
def start_unpatch(self):
|
def start_unpatch(self):
|
||||||
logging.info("- Starting Unpatch Process")
|
logging.info("- Starting Unpatch Process")
|
||||||
if sys_patch_detect.detect_root_patch(self.computer.real_model, self.constants).verify_patch_allowed(print_errors=True) is True:
|
if sys_patch_detect.DetectRootPatch(self.computer.real_model, self.constants).verify_patch_allowed(print_errors=True) is True:
|
||||||
if self.mount_root_vol() is True:
|
if self._mount_root_vol() is True:
|
||||||
self.unpatch_root_vol()
|
self._unpatch_root_vol()
|
||||||
if self.constants.gui_mode is False:
|
if self.constants.gui_mode is False:
|
||||||
input("\nPress [ENTER] to return to the main menu")
|
input("\nPress [ENTER] to return to the main menu")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,29 +1,42 @@
|
|||||||
# Auto Patching's main purpose is to try and tell the user they're missing root patches
|
|
||||||
# New users may not realize OS updates remove our patches, so we try and run when nessasary
|
|
||||||
# Conditions for running:
|
|
||||||
# - Verify running GUI (TUI users can write their own scripts)
|
|
||||||
# - Verify the Snapshot Seal is intact (if not, assume user is running patches)
|
|
||||||
# - Verify this model needs patching (if not, assume user upgraded hardware and OCLP was not removed)
|
|
||||||
# - Verify there are no updates for OCLP (ensure we have the latest patch sets)
|
|
||||||
# If all these tests pass, start Root Patcher
|
|
||||||
# Copyright (C) 2022, Mykola Grymalyuk
|
# Copyright (C) 2022, Mykola Grymalyuk
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
import plistlib
|
import plistlib
|
||||||
import subprocess
|
import subprocess
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import logging
|
import logging
|
||||||
from resources import utilities, updates, global_settings, network_handler
|
from pathlib import Path
|
||||||
|
|
||||||
|
from resources import utilities, updates, global_settings, network_handler, constants
|
||||||
from resources.sys_patch import sys_patch_detect
|
from resources.sys_patch import sys_patch_detect
|
||||||
from resources.gui import gui_main
|
from resources.gui import gui_main
|
||||||
|
|
||||||
class AutomaticSysPatch:
|
|
||||||
|
|
||||||
def __init__(self, constants):
|
class AutomaticSysPatch:
|
||||||
self.constants = constants
|
"""
|
||||||
|
Library of functions for launch agent, including automatic patching
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, global_constants: constants.Constants):
|
||||||
|
self.constants: constants.Constants = global_constants
|
||||||
|
|
||||||
|
|
||||||
def start_auto_patch(self):
|
def start_auto_patch(self):
|
||||||
|
"""
|
||||||
|
Initiates automatic patching
|
||||||
|
|
||||||
|
Auto Patching's main purpose is to try and tell the user they're missing root patches
|
||||||
|
New users may not realize OS updates remove our patches, so we try and run when nessasary
|
||||||
|
|
||||||
|
Conditions for running:
|
||||||
|
- Verify running GUI (TUI users can write their own scripts)
|
||||||
|
- Verify the Snapshot Seal is intact (if not, assume user is running patches)
|
||||||
|
- Verify this model needs patching (if not, assume user upgraded hardware and OCLP was not removed)
|
||||||
|
- Verify there are no updates for OCLP (ensure we have the latest patch sets)
|
||||||
|
|
||||||
|
If all these tests pass, start Root Patcher
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
logging.info("- Starting Automatic Patching")
|
logging.info("- Starting Automatic Patching")
|
||||||
if self.constants.wxpython_variant is False:
|
if self.constants.wxpython_variant is False:
|
||||||
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")
|
||||||
@@ -31,7 +44,7 @@ class AutomaticSysPatch:
|
|||||||
|
|
||||||
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.detect_root_patch(self.constants.computer.real_model, self.constants).detect_patch_set()
|
patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set()
|
||||||
if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches):
|
if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches):
|
||||||
patches = []
|
patches = []
|
||||||
if patches:
|
if patches:
|
||||||
@@ -46,7 +59,7 @@ class AutomaticSysPatch:
|
|||||||
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
|
# Check for updates
|
||||||
dict = updates.check_binary_updates(self.constants).check_binary_updates()
|
dict = updates.CheckBinaryUpdates(self.constants).check_binary_updates()
|
||||||
if not dict:
|
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:
|
||||||
@@ -113,26 +126,35 @@ class AutomaticSysPatch:
|
|||||||
else:
|
else:
|
||||||
logging.info("- Detected Snapshot seal not intact, skipping")
|
logging.info("- Detected Snapshot seal not intact, skipping")
|
||||||
|
|
||||||
if self.determine_if_versions_match() is False:
|
if self._determine_if_versions_match():
|
||||||
self.determine_if_boot_matches()
|
self._determine_if_boot_matches()
|
||||||
|
|
||||||
|
|
||||||
def determine_if_versions_match(self):
|
def _determine_if_versions_match(self):
|
||||||
|
"""
|
||||||
|
Determine if the booted version of OCLP matches the installed version
|
||||||
|
|
||||||
|
ie. Installed app is 0.2.0, but EFI version is 0.1.0
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if versions match, False if not
|
||||||
|
"""
|
||||||
|
|
||||||
logging.info("- Checking booted vs installed OCLP Build")
|
logging.info("- Checking booted vs installed OCLP Build")
|
||||||
if self.constants.computer.oclp_version is None:
|
if self.constants.computer.oclp_version is None:
|
||||||
logging.info("- Booted version not found")
|
logging.info("- Booted version not found")
|
||||||
return False
|
return True
|
||||||
|
|
||||||
if self.constants.computer.oclp_version == self.constants.patcher_version:
|
if self.constants.computer.oclp_version == self.constants.patcher_version:
|
||||||
logging.info("- Versions match")
|
logging.info("- Versions match")
|
||||||
return False
|
return True
|
||||||
|
|
||||||
# Check if installed version is newer than booted version
|
# Check if installed version is newer than booted version
|
||||||
if updates.check_binary_updates(self.constants).check_if_build_newer(
|
if updates.CheckBinaryUpdates(self.constants)._check_if_build_newer(
|
||||||
self.constants.computer.oclp_version.split("."), self.constants.patcher_version.split(".")
|
self.constants.computer.oclp_version.split("."), self.constants.patcher_version.split(".")
|
||||||
) is True:
|
) is True:
|
||||||
logging.info("- Installed version is newer than booted version")
|
logging.info("- Installed version is newer than booted version")
|
||||||
return False
|
return True
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
"osascript",
|
"osascript",
|
||||||
@@ -150,18 +172,25 @@ class AutomaticSysPatch:
|
|||||||
self.constants.start_build_install = True
|
self.constants.start_build_install = True
|
||||||
gui_main.wx_python_gui(self.constants).main_menu(None)
|
gui_main.wx_python_gui(self.constants).main_menu(None)
|
||||||
|
|
||||||
return True
|
return False
|
||||||
|
|
||||||
def determine_if_boot_matches(self):
|
|
||||||
# Goal of this function is to determine whether the user
|
|
||||||
# is using a USB drive to Boot OpenCore but macOS does not
|
|
||||||
# reside on the same drive as the USB.
|
|
||||||
|
|
||||||
# If we determine them to be mismatched, notify the user
|
def _determine_if_boot_matches(self):
|
||||||
# and ask if they want to install to install to disk
|
"""
|
||||||
|
Determine if the boot drive matches the macOS drive
|
||||||
|
ie. Booted from USB, but macOS is on internal disk
|
||||||
|
|
||||||
|
Goal of this function is to determine whether the user
|
||||||
|
is using a USB drive to Boot OpenCore but macOS does not
|
||||||
|
reside on the same drive as the USB.
|
||||||
|
|
||||||
|
If we determine them to be mismatched, notify the user
|
||||||
|
and ask if they want to install to install to disk.
|
||||||
|
"""
|
||||||
|
|
||||||
logging.info("- Determining if macOS drive matches boot drive")
|
logging.info("- Determining if macOS drive matches boot drive")
|
||||||
should_notify = global_settings.global_settings().read_property("AutoPatch_Notify_Mismatched_Disks")
|
|
||||||
|
should_notify = global_settings.GlobalEnviromentSettings().read_property("AutoPatch_Notify_Mismatched_Disks")
|
||||||
if should_notify is False:
|
if should_notify is False:
|
||||||
logging.info("- Skipping due to user preference")
|
logging.info("- Skipping due to user preference")
|
||||||
return
|
return
|
||||||
@@ -223,9 +252,16 @@ class AutomaticSysPatch:
|
|||||||
|
|
||||||
|
|
||||||
def install_auto_patcher_launch_agent(self):
|
def install_auto_patcher_launch_agent(self):
|
||||||
# Installs the following:
|
"""
|
||||||
# - OpenCore-Patcher.app in /Library/Application Support/Dortania/
|
Install the Auto Patcher Launch Agent
|
||||||
# - com.dortania.opencore-legacy-patcher.auto-patch.plist in /Library/LaunchAgents/
|
|
||||||
|
Installs the following:
|
||||||
|
- OpenCore-Patcher.app in /Library/Application Support/Dortania/
|
||||||
|
- com.dortania.opencore-legacy-patcher.auto-patch.plist in /Library/LaunchAgents/
|
||||||
|
|
||||||
|
See start_auto_patch() comments for more info
|
||||||
|
"""
|
||||||
|
|
||||||
if self.constants.launcher_script is not None:
|
if self.constants.launcher_script is not None:
|
||||||
logging.info("- Skipping Auto Patcher Launch Agent, not supported when running from source")
|
logging.info("- Skipping Auto Patcher Launch Agent, not supported when running from source")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -3,19 +3,39 @@
|
|||||||
# Used when supplying data to sys_patch.py
|
# Used when supplying data to sys_patch.py
|
||||||
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
|
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
|
||||||
|
|
||||||
from resources import constants, device_probe, utilities, amfi_detect, network_handler
|
|
||||||
from resources.sys_patch import sys_patch_helpers
|
|
||||||
from data import model_array, os_data, sip_data, sys_patch_dict, smbios_data, cpu_data
|
|
||||||
|
|
||||||
import py_sip_xnu
|
|
||||||
from pathlib import Path
|
|
||||||
import plistlib
|
import plistlib
|
||||||
import logging
|
import logging
|
||||||
|
import py_sip_xnu
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from resources import (
|
||||||
|
constants,
|
||||||
|
device_probe,
|
||||||
|
utilities,
|
||||||
|
amfi_detect,
|
||||||
|
network_handler,
|
||||||
|
kdk_handler
|
||||||
|
)
|
||||||
|
from data import (
|
||||||
|
model_array,
|
||||||
|
os_data,
|
||||||
|
sip_data,
|
||||||
|
sys_patch_dict,
|
||||||
|
smbios_data,
|
||||||
|
cpu_data
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DetectRootPatch:
|
||||||
|
"""
|
||||||
|
Library for querying root volume patches applicable for booted system
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, model: str, global_constants: constants.Constants):
|
||||||
|
self.model: str = model
|
||||||
|
|
||||||
|
self.constants: constants.Constants = global_constants
|
||||||
|
|
||||||
class detect_root_patch:
|
|
||||||
def __init__(self, model, versions):
|
|
||||||
self.model = model
|
|
||||||
self.constants: constants.Constants() = versions
|
|
||||||
self.computer = self.constants.computer
|
self.computer = self.constants.computer
|
||||||
|
|
||||||
# GPU Patch Detection
|
# GPU Patch Detection
|
||||||
@@ -63,7 +83,12 @@ class detect_root_patch:
|
|||||||
self.missing_nv_web_opengl = False
|
self.missing_nv_web_opengl = False
|
||||||
self.missing_nv_compat = False
|
self.missing_nv_compat = False
|
||||||
|
|
||||||
def detect_gpus(self):
|
|
||||||
|
def _detect_gpus(self):
|
||||||
|
"""
|
||||||
|
Query GPUs and set flags for applicable patches
|
||||||
|
"""
|
||||||
|
|
||||||
gpus = self.constants.computer.gpus
|
gpus = self.constants.computer.gpus
|
||||||
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):
|
||||||
@@ -75,7 +100,7 @@ class detect_root_patch:
|
|||||||
self.amfi_must_disable = True
|
self.amfi_must_disable = True
|
||||||
if os_data.os_data.ventura in self.constants.legacy_accel_support:
|
if os_data.os_data.ventura in self.constants.legacy_accel_support:
|
||||||
self.amfi_shim_bins = True
|
self.amfi_shim_bins = True
|
||||||
self.legacy_keyboard_backlight = self.check_legacy_keyboard_backlight()
|
self.legacy_keyboard_backlight = self._check_legacy_keyboard_backlight()
|
||||||
self.requires_root_kc = True
|
self.requires_root_kc = True
|
||||||
elif gpu.arch == device_probe.NVIDIA.Archs.Kepler and self.constants.force_nv_web is False:
|
elif gpu.arch == device_probe.NVIDIA.Archs.Kepler and self.constants.force_nv_web is False:
|
||||||
if self.constants.detected_os > os_data.os_data.big_sur:
|
if self.constants.detected_os > os_data.os_data.big_sur:
|
||||||
@@ -165,7 +190,7 @@ class detect_root_patch:
|
|||||||
self.amfi_must_disable = True
|
self.amfi_must_disable = True
|
||||||
if os_data.os_data.ventura in self.constants.legacy_accel_support:
|
if os_data.os_data.ventura in self.constants.legacy_accel_support:
|
||||||
self.amfi_shim_bins = True
|
self.amfi_shim_bins = True
|
||||||
self.legacy_keyboard_backlight = self.check_legacy_keyboard_backlight()
|
self.legacy_keyboard_backlight = self._check_legacy_keyboard_backlight()
|
||||||
self.requires_root_kc = True
|
self.requires_root_kc = True
|
||||||
elif gpu.arch == device_probe.Intel.Archs.Sandy_Bridge:
|
elif gpu.arch == device_probe.Intel.Archs.Sandy_Bridge:
|
||||||
if self.constants.detected_os > non_metal_os:
|
if self.constants.detected_os > non_metal_os:
|
||||||
@@ -173,7 +198,7 @@ class detect_root_patch:
|
|||||||
self.amfi_must_disable = True
|
self.amfi_must_disable = True
|
||||||
if os_data.os_data.ventura in self.constants.legacy_accel_support:
|
if os_data.os_data.ventura in self.constants.legacy_accel_support:
|
||||||
self.amfi_shim_bins = True
|
self.amfi_shim_bins = True
|
||||||
self.legacy_keyboard_backlight = self.check_legacy_keyboard_backlight()
|
self.legacy_keyboard_backlight = self._check_legacy_keyboard_backlight()
|
||||||
self.requires_root_kc = True
|
self.requires_root_kc = True
|
||||||
elif gpu.arch == device_probe.Intel.Archs.Ivy_Bridge:
|
elif gpu.arch == device_probe.Intel.Archs.Ivy_Bridge:
|
||||||
if self.constants.detected_os > os_data.os_data.big_sur:
|
if self.constants.detected_os > os_data.os_data.big_sur:
|
||||||
@@ -220,17 +245,21 @@ class detect_root_patch:
|
|||||||
self.requires_root_kc = True
|
self.requires_root_kc = True
|
||||||
else:
|
else:
|
||||||
if self.requires_root_kc is True:
|
if self.requires_root_kc is True:
|
||||||
self.missing_kdk = not self.check_kdk()
|
self.missing_kdk = not self._check_kdk()
|
||||||
|
|
||||||
self.check_networking_support()
|
self._check_networking_support()
|
||||||
|
|
||||||
|
|
||||||
def check_networking_support(self):
|
def _check_networking_support(self):
|
||||||
# On macOS Ventura, networking support is required to download KDKs.
|
"""
|
||||||
# However for machines such as BCM94322, BCM94328 and Atheros chipsets,
|
Query for network requirement, ex. KDK downloading
|
||||||
# users may only have wifi as their only supported network interface.
|
|
||||||
# Thus we'll allow for KDK-less installs for these machines on first run.
|
On macOS Ventura, networking support is required to download KDKs.
|
||||||
# On subsequent runs, we'll require networking to be enabled.
|
However for machines such as BCM94322, BCM94328 and Atheros chipsets,
|
||||||
|
users may only have wifi as their only supported network interface.
|
||||||
|
Thus we'll allow for KDK-less installs for these machines on first run.
|
||||||
|
On subsequent runs, we'll require networking to be enabled.
|
||||||
|
"""
|
||||||
|
|
||||||
if self.constants.detected_os < os_data.os_data.ventura:
|
if self.constants.detected_os < os_data.os_data.ventura:
|
||||||
return
|
return
|
||||||
@@ -272,7 +301,11 @@ class detect_root_patch:
|
|||||||
self.legacy_keyboard_backlight = False
|
self.legacy_keyboard_backlight = False
|
||||||
|
|
||||||
|
|
||||||
def check_dgpu_status(self):
|
def _check_dgpu_status(self):
|
||||||
|
"""
|
||||||
|
Query whether system has an active dGPU
|
||||||
|
"""
|
||||||
|
|
||||||
dgpu = self.constants.computer.dgpu
|
dgpu = self.constants.computer.dgpu
|
||||||
if dgpu:
|
if dgpu:
|
||||||
if dgpu.class_code and dgpu.class_code == 0xFFFFFFFF:
|
if dgpu.class_code and dgpu.class_code == 0xFFFFFFFF:
|
||||||
@@ -281,25 +314,45 @@ class detect_root_patch:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def detect_demux(self):
|
|
||||||
|
def _detect_demux(self):
|
||||||
|
"""
|
||||||
|
Query whether system has been demuxed (ex. MacBookPro8,2, disabled dGPU)
|
||||||
|
"""
|
||||||
|
|
||||||
# If GFX0 is missing, assume machine was demuxed
|
# If GFX0 is missing, assume machine was demuxed
|
||||||
# -wegnoegpu would also trigger this, so ensure arg is not present
|
# -wegnoegpu would also trigger this, so ensure arg is not present
|
||||||
if not "-wegnoegpu" in (utilities.get_nvram("boot-args", decode=True) or ""):
|
if not "-wegnoegpu" in (utilities.get_nvram("boot-args", decode=True) or ""):
|
||||||
igpu = self.constants.computer.igpu
|
igpu = self.constants.computer.igpu
|
||||||
dgpu = self.check_dgpu_status()
|
dgpu = self._check_dgpu_status()
|
||||||
if igpu and not dgpu:
|
if igpu and not dgpu:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_legacy_keyboard_backlight(self):
|
|
||||||
|
def _check_legacy_keyboard_backlight(self):
|
||||||
|
"""
|
||||||
|
Query whether system has a legacy keyboard backlight
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if legacy keyboard backlight, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
# iMac12,x+ have an 'ACPI0008' device, but it's not a keyboard backlight
|
# iMac12,x+ have an 'ACPI0008' device, but it's not a keyboard backlight
|
||||||
# Best to assume laptops will have a keyboard backlight
|
# Best to assume laptops will have a keyboard backlight
|
||||||
if self.model.startswith("MacBook"):
|
if self.model.startswith("MacBook"):
|
||||||
return self.constants.computer.ambient_light_sensor
|
return self.constants.computer.ambient_light_sensor
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_nv_web_nvram(self):
|
|
||||||
# First check boot-args, then dedicated nvram variable
|
def _check_nv_web_nvram(self):
|
||||||
|
"""
|
||||||
|
Query for Nvidia Web Driver property: nvda_drv_vrl or nvda_drv
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if property is present, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
nv_on = utilities.get_nvram("boot-args", decode=True)
|
nv_on = utilities.get_nvram("boot-args", decode=True)
|
||||||
if nv_on:
|
if nv_on:
|
||||||
if "nvda_drv_vrl=" in nv_on:
|
if "nvda_drv_vrl=" in nv_on:
|
||||||
@@ -309,8 +362,17 @@ class detect_root_patch:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_nv_web_opengl(self):
|
|
||||||
# First check boot-args, then whether property exists on GPU
|
def _check_nv_web_opengl(self):
|
||||||
|
"""
|
||||||
|
Query for Nvidia Web Driver property: ngfxgl
|
||||||
|
|
||||||
|
Verify Web Drivers will run in OpenGL mode
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if property is present, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
nv_on = utilities.get_nvram("boot-args", decode=True)
|
nv_on = utilities.get_nvram("boot-args", decode=True)
|
||||||
if nv_on:
|
if nv_on:
|
||||||
if "ngfxgl=" in nv_on:
|
if "ngfxgl=" in nv_on:
|
||||||
@@ -321,8 +383,17 @@ class detect_root_patch:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_nv_compat(self):
|
|
||||||
# Check for 'nv_web' in boot-args, then whether property exists on GPU
|
def _check_nv_compat(self):
|
||||||
|
"""
|
||||||
|
Query for Nvidia Web Driver property: ngfxcompat
|
||||||
|
|
||||||
|
Verify Web Drivers will skip NVDAStartupWeb compatibility check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if property is present, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
nv_on = utilities.get_nvram("boot-args", decode=True)
|
nv_on = utilities.get_nvram("boot-args", decode=True)
|
||||||
if nv_on:
|
if nv_on:
|
||||||
if "ngfxcompat=" in nv_on:
|
if "ngfxcompat=" in nv_on:
|
||||||
@@ -333,15 +404,37 @@ class detect_root_patch:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def check_whatevergreen(self):
|
|
||||||
|
def _check_whatevergreen(self):
|
||||||
|
"""
|
||||||
|
Query whether WhateverGreen.kext is loaded
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if loaded, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
return utilities.check_kext_loaded("WhateverGreen", self.constants.detected_os)
|
return utilities.check_kext_loaded("WhateverGreen", self.constants.detected_os)
|
||||||
|
|
||||||
def check_kdk(self):
|
|
||||||
if sys_patch_helpers.sys_patch_helpers(self.constants).determine_kdk_present() is None:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def check_sip(self):
|
def _check_kdk(self):
|
||||||
|
"""
|
||||||
|
Query whether Kernel Debug Kit is installed
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if installed, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
|
return kdk_handler.KernelDebugKitObject(self.constants, self.constants.detected_os_build, self.constants.detected_os_version, passive=True).kdk_already_installed
|
||||||
|
|
||||||
|
|
||||||
|
def _check_sip(self):
|
||||||
|
"""
|
||||||
|
Query System Integrity checks required for patching
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (list, str, str) of SIP values, SIP hex, SIP error message
|
||||||
|
"""
|
||||||
|
|
||||||
if self.constants.detected_os > os_data.os_data.catalina:
|
if self.constants.detected_os > os_data.os_data.catalina:
|
||||||
if self.nvidia_web is True:
|
if self.nvidia_web is True:
|
||||||
sip = sip_data.system_integrity_protection.root_patch_sip_big_sur_3rd_part_kexts
|
sip = sip_data.system_integrity_protection.root_patch_sip_big_sur_3rd_part_kexts
|
||||||
@@ -367,7 +460,15 @@ class detect_root_patch:
|
|||||||
sip_value = f"For Hackintoshes, please set csr-active-config to '03060000' ({sip_hex})\nFor non-OpenCore Macs, please run 'csrutil disable' in RecoveryOS"
|
sip_value = f"For Hackintoshes, please set csr-active-config to '03060000' ({sip_hex})\nFor non-OpenCore Macs, please run 'csrutil disable' in RecoveryOS"
|
||||||
return (sip, sip_value, sip_hex)
|
return (sip, sip_value, sip_hex)
|
||||||
|
|
||||||
def check_uhci_ohci(self):
|
|
||||||
|
def _check_uhci_ohci(self):
|
||||||
|
"""
|
||||||
|
Query whether host has UHCI/OHCI controllers, and requires USB 1.1 patches
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if UHCI/OHCI patches required, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
if self.constants.detected_os < os_data.os_data.ventura:
|
if self.constants.detected_os < os_data.os_data.ventura:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -401,10 +502,19 @@ class detect_root_patch:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# Entry point for patch set detection
|
||||||
def detect_patch_set(self):
|
def detect_patch_set(self):
|
||||||
|
"""
|
||||||
|
Query patch sets required for host
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Dictionary of patch sets
|
||||||
|
"""
|
||||||
|
|
||||||
self.has_network = network_handler.NetworkUtilities().verify_network_connection()
|
self.has_network = network_handler.NetworkUtilities().verify_network_connection()
|
||||||
|
|
||||||
if self.check_uhci_ohci() is True:
|
if self._check_uhci_ohci() is True:
|
||||||
self.legacy_uhci_ohci = True
|
self.legacy_uhci_ohci = True
|
||||||
self.requires_root_kc = True
|
self.requires_root_kc = True
|
||||||
|
|
||||||
@@ -437,12 +547,12 @@ class detect_root_patch:
|
|||||||
if self.constants.detected_os > os_data.os_data.high_sierra:
|
if self.constants.detected_os > os_data.os_data.high_sierra:
|
||||||
if self.model in ["MacBookPro8,2", "MacBookPro8,3"]:
|
if self.model in ["MacBookPro8,2", "MacBookPro8,3"]:
|
||||||
# Ref: https://doslabelectronics.com/Demux.html
|
# Ref: https://doslabelectronics.com/Demux.html
|
||||||
if self.detect_demux() is True:
|
if self._detect_demux() is True:
|
||||||
self.legacy_gmux = True
|
self.legacy_gmux = True
|
||||||
else:
|
else:
|
||||||
self.legacy_gmux = True
|
self.legacy_gmux = True
|
||||||
|
|
||||||
self.detect_gpus()
|
self._detect_gpus()
|
||||||
|
|
||||||
self.root_patch_dict = {
|
self.root_patch_dict = {
|
||||||
"Graphics: Nvidia Tesla": self.nvidia_tesla,
|
"Graphics: Nvidia Tesla": self.nvidia_tesla,
|
||||||
@@ -469,11 +579,11 @@ class detect_root_patch:
|
|||||||
"Settings: Supports Auxiliary Cache": not self.requires_root_kc,
|
"Settings: Supports Auxiliary Cache": not self.requires_root_kc,
|
||||||
"Settings: Kernel Debug Kit missing": self.missing_kdk if self.constants.detected_os >= os_data.os_data.ventura.value else False,
|
"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: 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,
|
||||||
f"Validation: {'AMFI' if self.constants.host_is_hackintosh is True or self.get_amfi_level_needed() > 2 else 'Library Validation'} is enabled": self.amfi_enabled if self.amfi_must_disable is True else False,
|
f"Validation: {'AMFI' if self.constants.host_is_hackintosh is True or self._get_amfi_level_needed() > 2 else 'Library Validation'} is enabled": self.amfi_enabled if self.amfi_must_disable is True else False,
|
||||||
"Validation: FileVault is enabled": self.fv_enabled,
|
"Validation: FileVault is enabled": self.fv_enabled,
|
||||||
"Validation: System is dosdude1 patched": self.dosdude_patched,
|
"Validation: System is dosdude1 patched": self.dosdude_patched,
|
||||||
"Validation: WhateverGreen.kext missing": self.missing_whatever_green if self.nvidia_web is True else False,
|
"Validation: WhateverGreen.kext missing": self.missing_whatever_green if self.nvidia_web is True else False,
|
||||||
@@ -485,30 +595,53 @@ class detect_root_patch:
|
|||||||
|
|
||||||
return self.root_patch_dict
|
return self.root_patch_dict
|
||||||
|
|
||||||
def get_amfi_level_needed(self):
|
|
||||||
if self.amfi_must_disable is True:
|
|
||||||
if self.constants.detected_os > os_data.os_data.catalina:
|
|
||||||
if self.constants.detected_os >= os_data.os_data.ventura:
|
|
||||||
if self.amfi_shim_bins is True:
|
|
||||||
# Currently we require AMFI outright disabled
|
|
||||||
# in Ventura to work with shim'd binaries
|
|
||||||
return 3
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def verify_patch_allowed(self, print_errors=False):
|
def _get_amfi_level_needed(self):
|
||||||
sip_dict = self.check_sip()
|
"""
|
||||||
|
Query the AMFI level needed for the patcher to work
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: AMFI level needed
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.amfi_must_disable is False:
|
||||||
|
return amfi_detect.AmfiConfigDetectLevel.NO_CHECK
|
||||||
|
|
||||||
|
if self.constants.detected_os < os_data.os_data.big_sur:
|
||||||
|
return amfi_detect.AmfiConfigDetectLevel.NO_CHECK
|
||||||
|
|
||||||
|
if self.constants.detected_os >= os_data.os_data.ventura:
|
||||||
|
if self.amfi_shim_bins is True:
|
||||||
|
# Currently we require AMFI outright disabled
|
||||||
|
# in Ventura to work with shim'd binaries
|
||||||
|
return amfi_detect.AmfiConfigDetectLevel.ALLOW_ALL
|
||||||
|
|
||||||
|
return amfi_detect.AmfiConfigDetectLevel.LIBRARY_VALIDATION
|
||||||
|
|
||||||
|
|
||||||
|
def verify_patch_allowed(self, print_errors: bool = False):
|
||||||
|
"""
|
||||||
|
Validate that the patcher can be run
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
print_errors (bool): Print errors to console
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if patching is allowed, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
|
sip_dict = self._check_sip()
|
||||||
sip = sip_dict[0]
|
sip = sip_dict[0]
|
||||||
sip_value = sip_dict[1]
|
sip_value = sip_dict[1]
|
||||||
|
|
||||||
self.sip_enabled, self.sbm_enabled, self.fv_enabled, self.dosdude_patched = utilities.patching_status(sip, self.constants.detected_os)
|
self.sip_enabled, self.sbm_enabled, self.fv_enabled, self.dosdude_patched = utilities.patching_status(sip, self.constants.detected_os)
|
||||||
self.amfi_enabled = not amfi_detect.amfi_configuration_detection().check_config(self.get_amfi_level_needed())
|
self.amfi_enabled = not amfi_detect.AmfiConfigurationDetection().check_config(self._get_amfi_level_needed())
|
||||||
|
|
||||||
if self.nvidia_web is True:
|
if self.nvidia_web is True:
|
||||||
self.missing_nv_web_nvram = not self.check_nv_web_nvram()
|
self.missing_nv_web_nvram = not self._check_nv_web_nvram()
|
||||||
self.missing_nv_web_opengl = not self.check_nv_web_opengl()
|
self.missing_nv_web_opengl = not self._check_nv_web_opengl()
|
||||||
self.missing_nv_compat = not self.check_nv_compat()
|
self.missing_nv_compat = not self._check_nv_compat()
|
||||||
self.missing_whatever_green = not self.check_whatevergreen()
|
self.missing_whatever_green = not self._check_whatevergreen()
|
||||||
|
|
||||||
if print_errors is True:
|
if print_errors is True:
|
||||||
if self.sip_enabled is True:
|
if self.sip_enabled is True:
|
||||||
@@ -576,28 +709,54 @@ class detect_root_patch:
|
|||||||
]
|
]
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def verify_unpatch_allowed(self, print_errors=False):
|
return True
|
||||||
# Must be called after verify_patch_allowed
|
|
||||||
|
|
||||||
|
def _verify_unpatch_allowed(self):
|
||||||
|
"""
|
||||||
|
Validate that the unpatcher can be run
|
||||||
|
|
||||||
|
Preconditions:
|
||||||
|
Must be called after verify_patch_allowed()
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if unpatching is allowed, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
return not self.sip_enabled
|
return not self.sip_enabled
|
||||||
|
|
||||||
def generate_patchset(self, hardware_details):
|
|
||||||
all_hardware_patchset = sys_patch_dict.SystemPatchDictionary(self.constants.detected_os, self.constants.detected_os_minor, self.constants.legacy_accel_support)
|
def generate_patchset(self, hardware_details: dict):
|
||||||
required_patches = {}
|
"""
|
||||||
|
Generate Patchset dictionary for the current system
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
hardware_details (dict): Dictionary of hardware details generated by detect_patch_set()
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Dictionary of patches to be applied from sys_patch_dict.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
all_hardware_patchset: dict = sys_patch_dict.SystemPatchDictionary(self.constants.detected_os, self.constants.detected_os_minor, self.constants.legacy_accel_support)
|
||||||
|
required_patches: dict = {}
|
||||||
|
|
||||||
utilities.cls()
|
utilities.cls()
|
||||||
|
|
||||||
logging.info("- The following patches will be applied:")
|
logging.info("- The following patches will be applied:")
|
||||||
|
|
||||||
if hardware_details["Graphics: Intel Ironlake"] is True:
|
if 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"]})
|
||||||
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 Ironlake": all_hardware_patchset["Graphics"]["Intel Ironlake"]})
|
required_patches.update({"Intel Ironlake": all_hardware_patchset["Graphics"]["Intel Ironlake"]})
|
||||||
|
|
||||||
if hardware_details["Graphics: Intel Sandy Bridge"] is True:
|
if hardware_details["Graphics: Intel Sandy Bridge"] is True:
|
||||||
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
|
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
|
||||||
required_patches.update({"Non-Metal ColorSync Workaround": all_hardware_patchset["Graphics"]["Non-Metal ColorSync Workaround"]})
|
required_patches.update({"Non-Metal ColorSync Workaround": all_hardware_patchset["Graphics"]["Non-Metal ColorSync Workaround"]})
|
||||||
required_patches.update({"High Sierra GVA": all_hardware_patchset["Graphics"]["High Sierra GVA"]})
|
required_patches.update({"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"]})
|
||||||
|
|
||||||
if hardware_details["Graphics: Intel Ivy Bridge"] is True:
|
if hardware_details["Graphics: Intel Ivy Bridge"] is True:
|
||||||
required_patches.update({"Metal 3802 Common": all_hardware_patchset["Graphics"]["Metal 3802 Common"]})
|
required_patches.update({"Metal 3802 Common": all_hardware_patchset["Graphics"]["Metal 3802 Common"]})
|
||||||
required_patches.update({"Catalina GVA": all_hardware_patchset["Graphics"]["Catalina GVA"]})
|
required_patches.update({"Catalina GVA": all_hardware_patchset["Graphics"]["Catalina GVA"]})
|
||||||
@@ -605,23 +764,28 @@ class detect_root_patch:
|
|||||||
required_patches.update({"Big Sur OpenCL": all_hardware_patchset["Graphics"]["Big Sur OpenCL"]})
|
required_patches.update({"Big Sur OpenCL": all_hardware_patchset["Graphics"]["Big Sur OpenCL"]})
|
||||||
required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]})
|
required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]})
|
||||||
required_patches.update({"Intel Ivy Bridge": all_hardware_patchset["Graphics"]["Intel Ivy Bridge"]})
|
required_patches.update({"Intel Ivy Bridge": all_hardware_patchset["Graphics"]["Intel Ivy Bridge"]})
|
||||||
|
|
||||||
if hardware_details["Graphics: Intel Haswell"] is True:
|
if hardware_details["Graphics: Intel Haswell"] is True:
|
||||||
required_patches.update({"Metal 3802 Common": all_hardware_patchset["Graphics"]["Metal 3802 Common"]})
|
required_patches.update({"Metal 3802 Common": all_hardware_patchset["Graphics"]["Metal 3802 Common"]})
|
||||||
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
|
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
|
||||||
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
|
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
|
||||||
required_patches.update({"Intel Haswell": all_hardware_patchset["Graphics"]["Intel Haswell"]})
|
required_patches.update({"Intel Haswell": all_hardware_patchset["Graphics"]["Intel Haswell"]})
|
||||||
|
|
||||||
if hardware_details["Graphics: Intel Broadwell"] is True:
|
if hardware_details["Graphics: Intel Broadwell"] is True:
|
||||||
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
|
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
|
||||||
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
|
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
|
||||||
required_patches.update({"Intel Broadwell": all_hardware_patchset["Graphics"]["Intel Broadwell"]})
|
required_patches.update({"Intel Broadwell": all_hardware_patchset["Graphics"]["Intel Broadwell"]})
|
||||||
|
|
||||||
if hardware_details["Graphics: Intel Skylake"] is True:
|
if hardware_details["Graphics: Intel Skylake"] is True:
|
||||||
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
|
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
|
||||||
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
|
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
|
||||||
required_patches.update({"Intel Skylake": all_hardware_patchset["Graphics"]["Intel Skylake"]})
|
required_patches.update({"Intel Skylake": all_hardware_patchset["Graphics"]["Intel Skylake"]})
|
||||||
|
|
||||||
if hardware_details["Graphics: Nvidia Tesla"] is True:
|
if hardware_details["Graphics: Nvidia Tesla"] 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"]})
|
||||||
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({"Nvidia Tesla": all_hardware_patchset["Graphics"]["Nvidia Tesla"]})
|
required_patches.update({"Nvidia Tesla": all_hardware_patchset["Graphics"]["Nvidia Tesla"]})
|
||||||
|
|
||||||
if hardware_details["Graphics: Nvidia Web Drivers"] is True:
|
if hardware_details["Graphics: Nvidia Web Drivers"] is True:
|
||||||
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
|
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
|
||||||
required_patches.update({"Non-Metal IOAccelerator Common": all_hardware_patchset["Graphics"]["Non-Metal IOAccelerator Common"]})
|
required_patches.update({"Non-Metal IOAccelerator Common": all_hardware_patchset["Graphics"]["Non-Metal IOAccelerator Common"]})
|
||||||
@@ -629,6 +793,7 @@ class detect_root_patch:
|
|||||||
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({"Nvidia Web Drivers": all_hardware_patchset["Graphics"]["Nvidia Web Drivers"]})
|
required_patches.update({"Nvidia Web Drivers": all_hardware_patchset["Graphics"]["Nvidia Web Drivers"]})
|
||||||
required_patches.update({"Non-Metal Enforcement": all_hardware_patchset["Graphics"]["Non-Metal Enforcement"]})
|
required_patches.update({"Non-Metal Enforcement": all_hardware_patchset["Graphics"]["Non-Metal Enforcement"]})
|
||||||
|
|
||||||
if hardware_details["Graphics: Nvidia Kepler"] is True:
|
if hardware_details["Graphics: Nvidia Kepler"] is True:
|
||||||
required_patches.update({"Revert Metal Downgrade": all_hardware_patchset["Graphics"]["Revert Metal Downgrade"]})
|
required_patches.update({"Revert Metal Downgrade": all_hardware_patchset["Graphics"]["Revert Metal Downgrade"]})
|
||||||
required_patches.update({"Metal 3802 Common": all_hardware_patchset["Graphics"]["Metal 3802 Common"]})
|
required_patches.update({"Metal 3802 Common": all_hardware_patchset["Graphics"]["Metal 3802 Common"]})
|
||||||
@@ -643,11 +808,13 @@ class detect_root_patch:
|
|||||||
if "Catalina GVA" in required_patches:
|
if "Catalina GVA" in required_patches:
|
||||||
del(required_patches["Catalina GVA"])
|
del(required_patches["Catalina GVA"])
|
||||||
break
|
break
|
||||||
|
|
||||||
if hardware_details["Graphics: AMD TeraScale 1"] is True:
|
if hardware_details["Graphics: AMD TeraScale 1"] is True:
|
||||||
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
|
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
|
||||||
required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]})
|
required_patches.update({"WebKit Monterey Common": all_hardware_patchset["Graphics"]["WebKit Monterey Common"]})
|
||||||
required_patches.update({"AMD TeraScale Common": all_hardware_patchset["Graphics"]["AMD TeraScale Common"]})
|
required_patches.update({"AMD TeraScale Common": all_hardware_patchset["Graphics"]["AMD TeraScale Common"]})
|
||||||
required_patches.update({"AMD TeraScale 1": all_hardware_patchset["Graphics"]["AMD TeraScale 1"]})
|
required_patches.update({"AMD TeraScale 1": all_hardware_patchset["Graphics"]["AMD TeraScale 1"]})
|
||||||
|
|
||||||
if hardware_details["Graphics: AMD TeraScale 2"] is True:
|
if hardware_details["Graphics: AMD TeraScale 2"] is True:
|
||||||
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
|
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
|
||||||
required_patches.update({"Non-Metal IOAccelerator Common": all_hardware_patchset["Graphics"]["Non-Metal IOAccelerator Common"]})
|
required_patches.update({"Non-Metal IOAccelerator Common": all_hardware_patchset["Graphics"]["Non-Metal IOAccelerator Common"]})
|
||||||
@@ -658,6 +825,7 @@ class detect_root_patch:
|
|||||||
# TeraScale 2 MacBooks with faulty GPUs are highly prone to crashing with AMDRadeonX3000 attached
|
# TeraScale 2 MacBooks with faulty GPUs are highly prone to crashing with AMDRadeonX3000 attached
|
||||||
# Additionally, AMDRadeonX3000 requires IOAccelerator downgrade which is not installed without 'Non-Metal IOAccelerator Common'
|
# Additionally, AMDRadeonX3000 requires IOAccelerator downgrade which is not installed without 'Non-Metal IOAccelerator Common'
|
||||||
del(required_patches["AMD TeraScale 2"]["Install"]["/System/Library/Extensions"]["AMDRadeonX3000.kext"])
|
del(required_patches["AMD TeraScale 2"]["Install"]["/System/Library/Extensions"]["AMDRadeonX3000.kext"])
|
||||||
|
|
||||||
if hardware_details["Graphics: AMD Legacy GCN"] is True or hardware_details["Graphics: AMD Legacy Polaris"] is True:
|
if hardware_details["Graphics: AMD Legacy GCN"] is True or hardware_details["Graphics: AMD Legacy Polaris"] is True:
|
||||||
required_patches.update({"Revert Metal Downgrade": all_hardware_patchset["Graphics"]["Revert Metal Downgrade"]})
|
required_patches.update({"Revert Metal Downgrade": all_hardware_patchset["Graphics"]["Revert Metal Downgrade"]})
|
||||||
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
|
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
|
||||||
@@ -666,8 +834,10 @@ class detect_root_patch:
|
|||||||
required_patches.update({"AMD Legacy GCN": all_hardware_patchset["Graphics"]["AMD Legacy GCN"]})
|
required_patches.update({"AMD Legacy GCN": all_hardware_patchset["Graphics"]["AMD Legacy GCN"]})
|
||||||
else:
|
else:
|
||||||
required_patches.update({"AMD Legacy Polaris": all_hardware_patchset["Graphics"]["AMD Legacy Polaris"]})
|
required_patches.update({"AMD Legacy Polaris": all_hardware_patchset["Graphics"]["AMD Legacy Polaris"]})
|
||||||
|
required_patches.update({"Revert GVA Downgrade": all_hardware_patchset["Graphics"]["Revert GVA Downgrade"]})
|
||||||
if "AVX2" not in self.constants.computer.cpu.leafs:
|
if "AVX2" not in self.constants.computer.cpu.leafs:
|
||||||
required_patches.update({"AMD OpenCL": all_hardware_patchset["Graphics"]["AMD OpenCL"]})
|
required_patches.update({"AMD OpenCL": all_hardware_patchset["Graphics"]["AMD OpenCL"]})
|
||||||
|
|
||||||
if hardware_details["Graphics: AMD Legacy Vega"] is True:
|
if hardware_details["Graphics: AMD Legacy Vega"] is True:
|
||||||
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
|
required_patches.update({"Monterey GVA": all_hardware_patchset["Graphics"]["Monterey GVA"]})
|
||||||
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
|
required_patches.update({"Monterey OpenCL": all_hardware_patchset["Graphics"]["Monterey OpenCL"]})
|
||||||
@@ -675,20 +845,28 @@ class detect_root_patch:
|
|||||||
required_patches.update({"AMD OpenCL": all_hardware_patchset["Graphics"]["AMD OpenCL"]})
|
required_patches.update({"AMD OpenCL": all_hardware_patchset["Graphics"]["AMD OpenCL"]})
|
||||||
if hardware_details["Graphics: AMD Legacy GCN"] is True:
|
if hardware_details["Graphics: AMD Legacy GCN"] is True:
|
||||||
required_patches.update({"AMD Legacy Vega Extended": all_hardware_patchset["Graphics"]["AMD Legacy Vega Extended"]})
|
required_patches.update({"AMD Legacy Vega Extended": all_hardware_patchset["Graphics"]["AMD Legacy Vega Extended"]})
|
||||||
|
else:
|
||||||
|
required_patches.update({"Revert GVA Downgrade": all_hardware_patchset["Graphics"]["Revert GVA Downgrade"]})
|
||||||
|
|
||||||
if hardware_details["Brightness: Legacy Backlight Control"] is True:
|
if hardware_details["Brightness: Legacy Backlight Control"] is True:
|
||||||
required_patches.update({"Legacy Backlight Control": all_hardware_patchset["Brightness"]["Legacy Backlight Control"]})
|
required_patches.update({"Legacy Backlight Control": all_hardware_patchset["Brightness"]["Legacy Backlight Control"]})
|
||||||
|
|
||||||
if hardware_details["Audio: Legacy Realtek"] is True:
|
if hardware_details["Audio: Legacy Realtek"] is True:
|
||||||
if self.model in ["iMac7,1", "iMac8,1"]:
|
if self.model in ["iMac7,1", "iMac8,1"]:
|
||||||
required_patches.update({"Legacy Realtek": all_hardware_patchset["Audio"]["Legacy Realtek"]})
|
required_patches.update({"Legacy Realtek": all_hardware_patchset["Audio"]["Legacy Realtek"]})
|
||||||
else:
|
else:
|
||||||
required_patches.update({"Legacy Non-GOP": all_hardware_patchset["Audio"]["Legacy Non-GOP"]})
|
required_patches.update({"Legacy Non-GOP": all_hardware_patchset["Audio"]["Legacy Non-GOP"]})
|
||||||
|
|
||||||
if hardware_details["Networking: Legacy Wireless"] is True:
|
if hardware_details["Networking: Legacy Wireless"] is True:
|
||||||
required_patches.update({"Legacy Wireless": all_hardware_patchset["Networking"]["Legacy Wireless"]})
|
required_patches.update({"Legacy Wireless": all_hardware_patchset["Networking"]["Legacy Wireless"]})
|
||||||
required_patches.update({"Legacy Wireless Extended": all_hardware_patchset["Networking"]["Legacy Wireless Extended"]})
|
required_patches.update({"Legacy Wireless Extended": all_hardware_patchset["Networking"]["Legacy Wireless Extended"]})
|
||||||
|
|
||||||
if hardware_details["Miscellaneous: Legacy GMUX"] is True:
|
if hardware_details["Miscellaneous: Legacy GMUX"] is True:
|
||||||
required_patches.update({"Legacy GMUX": all_hardware_patchset["Miscellaneous"]["Legacy GMUX"]})
|
required_patches.update({"Legacy GMUX": all_hardware_patchset["Miscellaneous"]["Legacy GMUX"]})
|
||||||
|
|
||||||
if hardware_details["Miscellaneous: Legacy Keyboard Backlight"] is True:
|
if hardware_details["Miscellaneous: Legacy Keyboard Backlight"] is True:
|
||||||
required_patches.update({"Legacy Keyboard Backlight": all_hardware_patchset["Miscellaneous"]["Legacy Keyboard Backlight"]})
|
required_patches.update({"Legacy Keyboard Backlight": all_hardware_patchset["Miscellaneous"]["Legacy Keyboard Backlight"]})
|
||||||
|
|
||||||
if hardware_details["Miscellaneous: Legacy USB 1.1"] is True:
|
if hardware_details["Miscellaneous: Legacy USB 1.1"] is True:
|
||||||
required_patches.update({"Legacy USB 1.1": all_hardware_patchset["Miscellaneous"]["Legacy USB 1.1"]})
|
required_patches.update({"Legacy USB 1.1": all_hardware_patchset["Miscellaneous"]["Legacy USB 1.1"]})
|
||||||
|
|
||||||
|
|||||||
@@ -1,58 +1,86 @@
|
|||||||
# Additional support functions for sys_patch.py
|
# Additional support functions for sys_patch.py
|
||||||
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
|
# Copyright (C) 2020-2023, Dhinak G, Mykola Grymalyuk
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
from data import os_data
|
|
||||||
from resources import generate_smbios, utilities
|
|
||||||
from pathlib import Path
|
|
||||||
from datetime import datetime
|
|
||||||
import plistlib
|
import plistlib
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from resources import constants, bplist
|
from data import os_data
|
||||||
|
from resources import bplist, constants, generate_smbios, utilities
|
||||||
class sys_patch_helpers:
|
|
||||||
|
|
||||||
def __init__(self, constants):
|
|
||||||
self.constants = constants
|
|
||||||
|
|
||||||
|
|
||||||
def snb_board_id_patch(self, source_files_path):
|
class SysPatchHelpers:
|
||||||
# AppleIntelSNBGraphicsFB hard codes the supported Board IDs for Sandy Bridge iGPUs
|
"""
|
||||||
# Because of this, the kext errors out on unsupported systems
|
Library of helper functions for sys_patch.py and related libraries
|
||||||
# This function simply patches in a supported Board ID, using 'determine_best_board_id_for_sandy()'
|
"""
|
||||||
# to supplement the ideal Board ID
|
|
||||||
|
def __init__(self, global_constants: constants.Constants):
|
||||||
|
self.constants: constants.Constants = global_constants
|
||||||
|
|
||||||
|
|
||||||
|
def snb_board_id_patch(self, source_files_path: str):
|
||||||
|
"""
|
||||||
|
Patch AppleIntelSNBGraphicsFB.kext to support unsupported Board IDs
|
||||||
|
|
||||||
|
AppleIntelSNBGraphicsFB hard codes the supported Board IDs for Sandy Bridge iGPUs
|
||||||
|
Because of this, the kext errors out on unsupported systems
|
||||||
|
This function simply patches in a supported Board ID, using 'determine_best_board_id_for_sandy()'
|
||||||
|
to supplement the ideal Board ID
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
source_files_path (str): Path to the source files
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
source_files_path = str(source_files_path)
|
source_files_path = str(source_files_path)
|
||||||
if self.constants.computer.reported_board_id not in self.constants.sandy_board_id_stock:
|
|
||||||
logging.info(f"- Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching")
|
|
||||||
board_to_patch = generate_smbios.determine_best_board_id_for_sandy(self.constants.computer.reported_board_id, self.constants.computer.gpus)
|
|
||||||
logging.info(f"- Replacing {board_to_patch} with {self.constants.computer.reported_board_id}")
|
|
||||||
|
|
||||||
board_to_patch_hex = bytes.fromhex(board_to_patch.encode('utf-8').hex())
|
if self.constants.computer.reported_board_id in self.constants.sandy_board_id_stock:
|
||||||
reported_board_hex = bytes.fromhex(self.constants.computer.reported_board_id.encode('utf-8').hex())
|
return
|
||||||
|
|
||||||
if len(board_to_patch_hex) > len(reported_board_hex):
|
logging.info(f"- Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching")
|
||||||
# Pad the reported Board ID with zeros to match the length of the board to patch
|
|
||||||
reported_board_hex = reported_board_hex + bytes(len(board_to_patch_hex) - len(reported_board_hex))
|
|
||||||
elif len(board_to_patch_hex) < len(reported_board_hex):
|
|
||||||
logging.info(f"- Error: Board ID {self.constants.computer.reported_board_id} is longer than {board_to_patch}")
|
|
||||||
raise Exception("Host's Board ID is longer than the kext's Board ID, cannot patch!!!")
|
|
||||||
|
|
||||||
path = source_files_path + "/10.13.6/System/Library/Extensions/AppleIntelSNBGraphicsFB.kext/Contents/MacOS/AppleIntelSNBGraphicsFB"
|
board_to_patch = generate_smbios.determine_best_board_id_for_sandy(self.constants.computer.reported_board_id, self.constants.computer.gpus)
|
||||||
if Path(path).exists():
|
logging.info(f"- Replacing {board_to_patch} with {self.constants.computer.reported_board_id}")
|
||||||
with open(path, 'rb') as f:
|
|
||||||
data = f.read()
|
board_to_patch_hex = bytes.fromhex(board_to_patch.encode('utf-8').hex())
|
||||||
data = data.replace(board_to_patch_hex, reported_board_hex)
|
reported_board_hex = bytes.fromhex(self.constants.computer.reported_board_id.encode('utf-8').hex())
|
||||||
with open(path, 'wb') as f:
|
|
||||||
f.write(data)
|
if len(board_to_patch_hex) > len(reported_board_hex):
|
||||||
else:
|
# Pad the reported Board ID with zeros to match the length of the board to patch
|
||||||
logging.info(f"- Error: Could not find {path}")
|
reported_board_hex = reported_board_hex + bytes(len(board_to_patch_hex) - len(reported_board_hex))
|
||||||
raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!")
|
elif len(board_to_patch_hex) < len(reported_board_hex):
|
||||||
|
logging.info(f"- Error: Board ID {self.constants.computer.reported_board_id} is longer than {board_to_patch}")
|
||||||
|
raise Exception("Host's Board ID is longer than the kext's Board ID, cannot patch!!!")
|
||||||
|
|
||||||
|
path = source_files_path + "/10.13.6/System/Library/Extensions/AppleIntelSNBGraphicsFB.kext/Contents/MacOS/AppleIntelSNBGraphicsFB"
|
||||||
|
if not Path(path).exists():
|
||||||
|
logging.info(f"- Error: Could not find {path}")
|
||||||
|
raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!")
|
||||||
|
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
data = f.read()
|
||||||
|
data = data.replace(board_to_patch_hex, reported_board_hex)
|
||||||
|
with open(path, 'wb') as f:
|
||||||
|
f.write(data)
|
||||||
|
|
||||||
|
|
||||||
def generate_patchset_plist(self, patchset, file_name, kdk_used):
|
def generate_patchset_plist(self, patchset: dict, file_name: str, kdk_used: Path):
|
||||||
|
"""
|
||||||
|
Generate patchset file for user reference
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
patchset (dict): Dictionary of patchset, see sys_patch_detect.py and sys_patch_dict.py
|
||||||
|
file_name (str): Name of the file to write to
|
||||||
|
kdk_used (Path): Path to the KDK used, if any
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False if not
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
source_path = f"{self.constants.payload_path}"
|
source_path = f"{self.constants.payload_path}"
|
||||||
source_path_file = f"{source_path}/{file_name}"
|
source_path_file = f"{source_path}/{file_name}"
|
||||||
|
|
||||||
@@ -68,93 +96,35 @@ class sys_patch_helpers:
|
|||||||
"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})",
|
||||||
}
|
}
|
||||||
|
|
||||||
data.update(patchset)
|
data.update(patchset)
|
||||||
|
|
||||||
if Path(source_path_file).exists():
|
if Path(source_path_file).exists():
|
||||||
os.remove(source_path_file)
|
os.remove(source_path_file)
|
||||||
|
|
||||||
# Need to write to a safe location
|
# Need to write to a safe location
|
||||||
plistlib.dump(data, Path(source_path_file).open("wb"), sort_keys=False)
|
plistlib.dump(data, Path(source_path_file).open("wb"), sort_keys=False)
|
||||||
|
|
||||||
if Path(source_path_file).exists():
|
if Path(source_path_file).exists():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def install_kdk(self):
|
|
||||||
if not self.constants.kdk_download_path.exists():
|
|
||||||
return
|
|
||||||
|
|
||||||
logging.info(f"- Installing downloaded KDK (this may take a while)")
|
|
||||||
with tempfile.TemporaryDirectory() as mount_point:
|
|
||||||
utilities.process_status(subprocess.run(["hdiutil", "attach", self.constants.kdk_download_path, "-mountpoint", mount_point, "-nobrowse"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
|
||||||
# Due to a permissions bug in macOS, sometimes the OS will fail on a Read-only file system error
|
|
||||||
# We don't actually need to write inside the KDK DMG, however macOS will do whatever it wants
|
|
||||||
# Thus move the KDK to another location, and run the installer from there
|
|
||||||
kdk_dst_path = Path(f"{self.constants.payload_path}/KernelDebugKit.pkg")
|
|
||||||
if kdk_dst_path.exists():
|
|
||||||
utilities.process_status(utilities.elevated(["rm", kdk_dst_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
|
||||||
utilities.process_status(subprocess.run(["cp", f"{mount_point}/KernelDebugKit.pkg", self.constants.payload_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
|
||||||
result = utilities.elevated(["installer", "-pkg", kdk_dst_path, "-target", "/"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
||||||
if result.returncode != 0:
|
|
||||||
logging.info("- Failed to install KDK:")
|
|
||||||
logging.info(result.stdout.decode('utf-8'))
|
|
||||||
if result.stderr:
|
|
||||||
logging.info(result.stderr.decode('utf-8'))
|
|
||||||
utilities.elevated(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
||||||
raise Exception("Failed to install KDK")
|
|
||||||
utilities.process_status(utilities.elevated(["rm", kdk_dst_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT))
|
|
||||||
utilities.elevated(["hdiutil", "detach", mount_point], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
||||||
logging.info("- Successfully installed KDK")
|
|
||||||
|
|
||||||
|
|
||||||
def determine_kdk_present(self, match_closest=False, override_build=None):
|
|
||||||
# Check if KDK is present
|
|
||||||
# If 'match_closest' is True, will provide the closest match to the reported KDK
|
|
||||||
|
|
||||||
kdk_array = []
|
|
||||||
|
|
||||||
search_build = self.constants.detected_os_build
|
|
||||||
if override_build:
|
|
||||||
search_build = override_build
|
|
||||||
|
|
||||||
if not Path("/Library/Developer/KDKs").exists():
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
for kdk_folder in Path("/Library/Developer/KDKs").iterdir():
|
|
||||||
if not kdk_folder.name.endswith(".kdk"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Ensure direct match
|
|
||||||
if kdk_folder.name.endswith(f"{search_build}.kdk"):
|
|
||||||
# Verify that the KDK is valid
|
|
||||||
if (kdk_folder / Path("System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern")).exists():
|
|
||||||
return kdk_folder
|
|
||||||
if match_closest is True:
|
|
||||||
# ex: KDK_13.0_22A5266r.kdk -> 22A5266r.kdk -> 22A5266r
|
|
||||||
try:
|
|
||||||
build = kdk_folder.name.split("_")[2].split(".")[0]
|
|
||||||
# Don't append if Darwin Major is different
|
|
||||||
if build.startswith(str(self.constants.detected_os)):
|
|
||||||
kdk_array.append(build)
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if match_closest is True:
|
|
||||||
result = os_data.os_conversion.find_largest_build(kdk_array)
|
|
||||||
logging.info(f"- Closest KDK match to {search_build}: {result}")
|
|
||||||
for kdk_folder in Path("/Library/Developer/KDKs").iterdir():
|
|
||||||
if kdk_folder.name.endswith(f"{result}.kdk"):
|
|
||||||
# Verify that the KDK is valid
|
|
||||||
if (kdk_folder / Path("System/Library/Extensions/System.kext/PlugIns/Libkern.kext/Libkern")).exists():
|
|
||||||
return kdk_folder
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def disable_window_server_caching(self):
|
def disable_window_server_caching(self):
|
||||||
# On legacy GCN GPUs, the WindowServer cache generated creates
|
"""
|
||||||
# corrupted Opaque shaders.
|
Disable WindowServer's asset caching
|
||||||
# To work-around this, we disable WindowServer caching
|
|
||||||
# And force macOS into properly generating the Opaque shaders
|
On legacy GCN GPUs, the WindowServer cache generated creates
|
||||||
|
corrupted Opaque shaders.
|
||||||
|
|
||||||
|
To work-around this, we disable WindowServer caching
|
||||||
|
And force macOS into properly generating the Opaque shaders
|
||||||
|
"""
|
||||||
|
|
||||||
if self.constants.detected_os < os_data.os_data.ventura:
|
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"])
|
||||||
@@ -166,11 +136,17 @@ class sys_patch_helpers:
|
|||||||
|
|
||||||
|
|
||||||
def remove_news_widgets(self):
|
def remove_news_widgets(self):
|
||||||
# On Ivy Bridge and Haswell iGPUs, RenderBox will crash the News Widgets in
|
"""
|
||||||
# Notification Centre. To ensure users can access Notifications normally,
|
Remove News Widgets from Notification Centre
|
||||||
# we manually remove all News Widgets
|
|
||||||
|
On Ivy Bridge and Haswell iGPUs, RenderBox will crash the News Widgets in
|
||||||
|
Notification Centre. To ensure users can access Notifications normally,
|
||||||
|
we manually remove all News Widgets
|
||||||
|
"""
|
||||||
|
|
||||||
if self.constants.detected_os < os_data.os_data.ventura:
|
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()
|
||||||
@@ -182,22 +158,27 @@ class sys_patch_helpers:
|
|||||||
did_find = False
|
did_find = False
|
||||||
with open(file_path, "rb") as f:
|
with open(file_path, "rb") as f:
|
||||||
data = plistlib.load(f)
|
data = plistlib.load(f)
|
||||||
if "widgets" in data:
|
if "widgets" not in data:
|
||||||
if "instances" in data["widgets"]:
|
return
|
||||||
for widget in list(data["widgets"]["instances"]):
|
|
||||||
widget_data = bplist.BPListReader(widget).parse()
|
if "instances" not in data["widgets"]:
|
||||||
for entry in widget_data:
|
return
|
||||||
if not 'widget' in entry:
|
|
||||||
continue
|
for widget in list(data["widgets"]["instances"]):
|
||||||
sub_data = bplist.BPListReader(widget_data[entry]).parse()
|
widget_data = bplist.BPListReader(widget).parse()
|
||||||
for sub_entry in sub_data:
|
for entry in widget_data:
|
||||||
if not '$object' in sub_entry:
|
if 'widget' not in entry:
|
||||||
continue
|
continue
|
||||||
if not b'com.apple.news' in sub_data[sub_entry][2]:
|
sub_data = bplist.BPListReader(widget_data[entry]).parse()
|
||||||
continue
|
for sub_entry in sub_data:
|
||||||
logging.info(f" - Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}")
|
if not '$object' in sub_entry:
|
||||||
data["widgets"]["instances"].remove(widget)
|
continue
|
||||||
did_find = True
|
if not b'com.apple.news' in sub_data[sub_entry][2]:
|
||||||
|
continue
|
||||||
|
logging.info(f" - Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}")
|
||||||
|
data["widgets"]["instances"].remove(widget)
|
||||||
|
did_find = True
|
||||||
|
|
||||||
if did_find:
|
if did_find:
|
||||||
with open(file_path, "wb") as f:
|
with open(file_path, "wb") as f:
|
||||||
plistlib.dump(data, f, sort_keys=False)
|
plistlib.dump(data, f, sort_keys=False)
|
||||||
@@ -205,16 +186,23 @@ class sys_patch_helpers:
|
|||||||
|
|
||||||
|
|
||||||
def install_rsr_repair_binary(self):
|
def install_rsr_repair_binary(self):
|
||||||
# With macOS 13.2, Apple implemented the Rapid Security Response System
|
"""
|
||||||
# However Apple added a half baked snapshot reversion system if seal was broken,
|
Installs RSRRepair
|
||||||
# which forgets to handle Preboot BootKC syncing
|
|
||||||
|
|
||||||
# Thus this application will try to re-sync the BootKC with SysKC in the event of a panic
|
RSRRepair is a utility that will sync the SysKC and BootKC in the event of a panic
|
||||||
# Reference: https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1019
|
|
||||||
|
|
||||||
# This is a (hopefully) temporary work-around, however likely to stay.
|
With macOS 13.2, Apple implemented the Rapid Security Response System
|
||||||
# RSRRepair has the added bonus of fixing desynced KCs from 'bless', so useful in Big Sur+
|
However Apple added a half baked snapshot reversion system if seal was broken,
|
||||||
# https://github.com/flagersgit/RSRRepair
|
which forgets to handle Preboot BootKC syncing.
|
||||||
|
|
||||||
|
Thus this application will try to re-sync the BootKC with SysKC in the event of a panic
|
||||||
|
Reference: https://github.com/dortania/OpenCore-Legacy-Patcher/issues/1019
|
||||||
|
|
||||||
|
This is a (hopefully) temporary work-around, however likely to stay.
|
||||||
|
RSRRepair has the added bonus of fixing desynced KCs from 'bless', so useful in Big Sur+
|
||||||
|
Source: https://github.com/flagersgit/RSRRepair
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
if self.constants.detected_os < os_data.os_data.big_sur:
|
if self.constants.detected_os < os_data.os_data.big_sur:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
# Copyright (C) 2020-2022, Dhinak G, Mykola Grymalyuk
|
|
||||||
from resources import utilities
|
|
||||||
|
|
||||||
class TUIMenu:
|
|
||||||
def __init__(self, title, prompt, options=None, return_number_instead_of_direct_call=False, add_quit=True, auto_number=False, in_between=None, top_level=False, loop=False):
|
|
||||||
self.title = title
|
|
||||||
self.prompt = prompt
|
|
||||||
self.in_between = in_between or []
|
|
||||||
self.options = options or []
|
|
||||||
self.return_number_instead_of_direct_call = return_number_instead_of_direct_call
|
|
||||||
self.auto_number = auto_number
|
|
||||||
self.add_quit = add_quit
|
|
||||||
self.top_level = top_level
|
|
||||||
self.loop = loop
|
|
||||||
self.added_quit = False
|
|
||||||
|
|
||||||
def add_menu_option(self, name, description="", function=None, key=""):
|
|
||||||
self.options.append([key, name, description, function])
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
return_option = ["Q", "Quit"] if self.top_level else ["B", "Back"]
|
|
||||||
if self.add_quit and not self.added_quit:
|
|
||||||
self.add_menu_option(return_option[1], function=None, key=return_option[0])
|
|
||||||
self.added_quit = True
|
|
||||||
|
|
||||||
while True:
|
|
||||||
utilities.cls()
|
|
||||||
utilities.header(self.title)
|
|
||||||
print()
|
|
||||||
|
|
||||||
for i in self.in_between:
|
|
||||||
print(i)
|
|
||||||
if self.in_between:
|
|
||||||
print()
|
|
||||||
|
|
||||||
for index, option in enumerate(self.options):
|
|
||||||
if self.auto_number and not (index == (len(self.options) - 1) and self.add_quit):
|
|
||||||
option[0] = str((index + 1))
|
|
||||||
print(option[0] + ". " + option[1])
|
|
||||||
for i in option[2]:
|
|
||||||
print("\t" + i)
|
|
||||||
|
|
||||||
print()
|
|
||||||
selected = input(self.prompt)
|
|
||||||
|
|
||||||
keys = [option[0].upper() for option in self.options]
|
|
||||||
if not selected or selected.upper() not in keys:
|
|
||||||
if self.loop:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
if self.add_quit and selected.upper() == return_option[0]:
|
|
||||||
return -1
|
|
||||||
elif self.return_number_instead_of_direct_call:
|
|
||||||
return self.options[keys.index(selected.upper())][0]
|
|
||||||
else:
|
|
||||||
self.options[keys.index(selected.upper())][3]() if self.options[keys.index(selected.upper())][3] else None
|
|
||||||
if not self.loop:
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
class TUIOnlyPrint:
|
|
||||||
def __init__(self, title, prompt, in_between=None):
|
|
||||||
self.title = title
|
|
||||||
self.prompt = prompt
|
|
||||||
self.in_between = in_between or []
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
utilities.cls()
|
|
||||||
utilities.header(self.title)
|
|
||||||
print()
|
|
||||||
|
|
||||||
for i in self.in_between:
|
|
||||||
print(i)
|
|
||||||
if self.in_between:
|
|
||||||
print()
|
|
||||||
|
|
||||||
return input(self.prompt)
|
|
||||||
+79
-52
@@ -5,21 +5,31 @@
|
|||||||
import requests
|
import requests
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from resources import network_handler
|
from resources import network_handler, constants
|
||||||
|
|
||||||
|
REPO_LATEST_RELEASE_URL: str = "https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest"
|
||||||
|
|
||||||
|
|
||||||
class check_binary_updates:
|
class CheckBinaryUpdates:
|
||||||
def __init__(self, constants):
|
def __init__(self, global_constants: constants.Constants):
|
||||||
self.constants = constants
|
self.constants: constants.Constants = global_constants
|
||||||
self.binary_version = self.constants.patcher_version
|
|
||||||
self.binary_version_array = self.binary_version.split(".")
|
|
||||||
self.binary_version_array = [int(x) for x in self.binary_version_array]
|
|
||||||
self.binary_url = "https://api.github.com/repos/dortania/OpenCore-Legacy-Patcher/releases/latest"
|
|
||||||
|
|
||||||
self.available_binaries = {}
|
self.binary_version = self.constants.patcher_version
|
||||||
|
self.binary_version_array = [int(x) for x in self.binary_version.split(".")]
|
||||||
|
|
||||||
|
|
||||||
def check_if_build_newer(self, remote_version=None, local_version=None):
|
def _check_if_build_newer(self, remote_version: list = None, local_version: list = None):
|
||||||
|
"""
|
||||||
|
Check if the remote version is newer than the local version
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
remote_version (list): Remote version to compare against
|
||||||
|
local_version (list): Local version to compare against
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if remote version is newer, False if not
|
||||||
|
"""
|
||||||
|
|
||||||
if remote_version is None:
|
if remote_version is None:
|
||||||
remote_version = self.remote_version_array
|
remote_version = self.remote_version_array
|
||||||
if local_version is None:
|
if local_version is None:
|
||||||
@@ -39,13 +49,32 @@ class check_binary_updates:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def determine_local_build_type(self):
|
|
||||||
|
def _determine_local_build_type(self):
|
||||||
|
"""
|
||||||
|
Check if the local build is a GUI or TUI build
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: "GUI" or "TUI"
|
||||||
|
"""
|
||||||
|
|
||||||
if self.constants.wxpython_variant is True:
|
if self.constants.wxpython_variant is True:
|
||||||
return "GUI"
|
return "GUI"
|
||||||
else:
|
else:
|
||||||
return "TUI"
|
return "TUI"
|
||||||
|
|
||||||
def determine_remote_type(self, remote_name):
|
|
||||||
|
def _determine_remote_type(self, remote_name: str):
|
||||||
|
"""
|
||||||
|
Check if the remote build is a GUI or TUI build
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
remote_name (str): Name of the remote build
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: "GUI" or "TUI"
|
||||||
|
"""
|
||||||
|
|
||||||
if "TUI" in remote_name:
|
if "TUI" in remote_name:
|
||||||
return "TUI"
|
return "TUI"
|
||||||
elif "GUI" in remote_name:
|
elif "GUI" in remote_name:
|
||||||
@@ -53,45 +82,43 @@ class check_binary_updates:
|
|||||||
else:
|
else:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
|
||||||
|
|
||||||
def check_binary_updates(self):
|
def check_binary_updates(self):
|
||||||
# logging.info("- Checking for updates...")
|
"""
|
||||||
if network_handler.NetworkUtilities(self.binary_url).verify_network_connection():
|
Check if any updates are available for the OpenCore Legacy Patcher binary
|
||||||
# logging.info("- Network connection functional")
|
|
||||||
response = requests.get(self.binary_url)
|
Returns:
|
||||||
data_set = response.json()
|
dict: Dictionary with Link and Version of the latest binary update if available
|
||||||
# logging.info("- Retrieved latest version data")
|
"""
|
||||||
self.remote_version = data_set["tag_name"]
|
|
||||||
# logging.info(f"- Latest version: {self.remote_version}")
|
available_binaries: list = {}
|
||||||
self.remote_version_array = self.remote_version.split(".")
|
|
||||||
self.remote_version_array = [
|
if not network_handler.NetworkUtilities(REPO_LATEST_RELEASE_URL).verify_network_connection():
|
||||||
int(x) for x in self.remote_version_array
|
return None
|
||||||
]
|
|
||||||
if self.check_if_build_newer() is True:
|
response = requests.get(REPO_LATEST_RELEASE_URL)
|
||||||
# logging.info("- Remote version is newer")
|
data_set = response.json()
|
||||||
for asset in data_set["assets"]:
|
|
||||||
logging.info(f"- Found asset: {asset['name']}")
|
self.remote_version = data_set["tag_name"]
|
||||||
if self.determine_remote_type(asset["name"]) == self.determine_local_build_type():
|
|
||||||
# logging.info(f"- Found matching asset: {asset['name']}")
|
self.remote_version_array = self.remote_version.split(".")
|
||||||
self.available_binaries.update({
|
self.remote_version_array = [int(x) for x in self.remote_version_array]
|
||||||
asset['name']: {
|
|
||||||
"Name":
|
if self._check_if_build_newer() is False:
|
||||||
asset["name"],
|
return None
|
||||||
"Version":
|
|
||||||
self.remote_version,
|
for asset in data_set["assets"]:
|
||||||
"Link":
|
logging.info(f"- Found asset: {asset['name']}")
|
||||||
asset["browser_download_url"],
|
if self._determine_remote_type(asset["name"]) == self._determine_local_build_type():
|
||||||
"Type":
|
available_binaries.update({
|
||||||
self.determine_remote_type(asset["name"]),
|
asset['name']: {
|
||||||
"Github Link":
|
"Name": asset["name"],
|
||||||
f"https://github.com/dortania/OpenCore-Legacy-Patcher/releases/{self.remote_version}"
|
"Version": self.remote_version,
|
||||||
}
|
"Link": asset["browser_download_url"],
|
||||||
})
|
"Type": self._determine_remote_type(asset["name"]),
|
||||||
break
|
"Github Link": f"https://github.com/dortania/OpenCore-Legacy-Patcher/releases/{self.remote_version}"
|
||||||
if self.available_binaries:
|
}
|
||||||
return self.available_binaries
|
})
|
||||||
else:
|
return available_binaries
|
||||||
# logging.info("- No matching binaries available")
|
|
||||||
return None
|
|
||||||
# else:
|
|
||||||
# logging.info("- Failed to connect to GitHub API")
|
|
||||||
return None
|
return None
|
||||||
@@ -9,7 +9,6 @@ import os
|
|||||||
import binascii
|
import binascii
|
||||||
import argparse
|
import argparse
|
||||||
import atexit
|
import atexit
|
||||||
import requests
|
|
||||||
import shutil
|
import shutil
|
||||||
import py_sip_xnu
|
import py_sip_xnu
|
||||||
|
|
||||||
|
|||||||
+135
-80
@@ -1,51 +1,70 @@
|
|||||||
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
|
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 data import example_data, model_array, sys_patch_dict, os_data
|
from data import example_data, model_array, sys_patch_dict, os_data
|
||||||
from pathlib import Path
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
def validate(settings):
|
class PatcherValidation:
|
||||||
# Runs through ocvalidate to check for errors
|
"""
|
||||||
|
Validation class for the patcher
|
||||||
|
|
||||||
valid_dumps = [
|
Primarily for Continuous Integration
|
||||||
example_data.MacBookPro.MacBookPro92_Stock,
|
"""
|
||||||
example_data.MacBookPro.MacBookPro111_Stock,
|
|
||||||
example_data.MacBookPro.MacBookPro133_Stock,
|
|
||||||
# example_data.MacBookPro.MacBookPro171_Stock,
|
|
||||||
example_data.Macmini.Macmini52_Stock,
|
|
||||||
example_data.Macmini.Macmini61_Stock,
|
|
||||||
example_data.Macmini.Macmini71_Stock,
|
|
||||||
# example_data.Macmini.Macmini91_Stock,
|
|
||||||
example_data.iMac.iMac81_Stock,
|
|
||||||
example_data.iMac.iMac112_Stock,
|
|
||||||
example_data.iMac.iMac122_Upgraded,
|
|
||||||
example_data.iMac.iMac122_Upgraded_Nvidia,
|
|
||||||
example_data.iMac.iMac151_Stock,
|
|
||||||
example_data.MacPro.MacPro31_Stock,
|
|
||||||
example_data.MacPro.MacPro31_Upgrade,
|
|
||||||
example_data.MacPro.MacPro31_Modern_AMD,
|
|
||||||
example_data.MacPro.MacPro31_Modern_Kepler,
|
|
||||||
example_data.MacPro.MacPro41_Upgrade,
|
|
||||||
example_data.MacPro.MacPro41_Modern_AMD,
|
|
||||||
example_data.MacPro.MacPro41_51__Flashed_Modern_AMD,
|
|
||||||
example_data.MacPro.MacPro41_51_Flashed_NVIDIA_WEB_DRIVERS,
|
|
||||||
]
|
|
||||||
|
|
||||||
valid_dumps_native = [
|
def __init__(self, global_constants: constants.Constants):
|
||||||
example_data.iMac.iMac201_Stock,
|
self.constants: constants.Constants = global_constants
|
||||||
example_data.MacBookPro.MacBookPro141_SSD_Upgrade,
|
|
||||||
]
|
|
||||||
|
|
||||||
settings.validate = True
|
self.constants.validate = True
|
||||||
|
|
||||||
|
self.valid_dumps = [
|
||||||
|
example_data.MacBookPro.MacBookPro92_Stock,
|
||||||
|
example_data.MacBookPro.MacBookPro111_Stock,
|
||||||
|
example_data.MacBookPro.MacBookPro133_Stock,
|
||||||
|
|
||||||
|
example_data.Macmini.Macmini52_Stock,
|
||||||
|
example_data.Macmini.Macmini61_Stock,
|
||||||
|
example_data.Macmini.Macmini71_Stock,
|
||||||
|
|
||||||
|
example_data.iMac.iMac81_Stock,
|
||||||
|
example_data.iMac.iMac112_Stock,
|
||||||
|
example_data.iMac.iMac122_Upgraded,
|
||||||
|
example_data.iMac.iMac122_Upgraded_Nvidia,
|
||||||
|
example_data.iMac.iMac151_Stock,
|
||||||
|
|
||||||
|
example_data.MacPro.MacPro31_Stock,
|
||||||
|
example_data.MacPro.MacPro31_Upgrade,
|
||||||
|
example_data.MacPro.MacPro31_Modern_AMD,
|
||||||
|
example_data.MacPro.MacPro31_Modern_Kepler,
|
||||||
|
example_data.MacPro.MacPro41_Upgrade,
|
||||||
|
example_data.MacPro.MacPro41_Modern_AMD,
|
||||||
|
example_data.MacPro.MacPro41_51__Flashed_Modern_AMD,
|
||||||
|
example_data.MacPro.MacPro41_51_Flashed_NVIDIA_WEB_DRIVERS,
|
||||||
|
]
|
||||||
|
|
||||||
|
self.valid_dumps_native = [
|
||||||
|
example_data.iMac.iMac201_Stock,
|
||||||
|
example_data.MacBookPro.MacBookPro141_SSD_Upgrade,
|
||||||
|
]
|
||||||
|
|
||||||
|
self._validate_configs()
|
||||||
|
self._validate_sys_patch()
|
||||||
|
|
||||||
|
|
||||||
|
def _build_prebuilt(self):
|
||||||
|
"""
|
||||||
|
Generate a build for each predefined model
|
||||||
|
Then validate against ocvalidate
|
||||||
|
"""
|
||||||
|
|
||||||
def build_prebuilt():
|
|
||||||
for model in model_array.SupportedSMBIOS:
|
for model in model_array.SupportedSMBIOS:
|
||||||
logging.info(f"Validating predefined model: {model}")
|
logging.info(f"Validating predefined model: {model}")
|
||||||
settings.custom_model = model
|
self.constants.custom_model = model
|
||||||
build.build_opencore(settings.custom_model, settings).build_opencore()
|
build.build_opencore(self.constants.custom_model, self.constants).build_opencore()
|
||||||
result = subprocess.run([settings.ocvalidate_path, f"{settings.opencore_release_folder}/EFI/OC/config.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
result = subprocess.run([self.constants.ocvalidate_path, f"{self.constants.opencore_release_folder}/EFI/OC/config.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
logging.info("Error on build!")
|
logging.info("Error on build!")
|
||||||
logging.info(result.stdout.decode())
|
logging.info(result.stdout.decode())
|
||||||
@@ -53,24 +72,35 @@ def validate(settings):
|
|||||||
else:
|
else:
|
||||||
logging.info(f"Validation succeeded for predefined model: {model}")
|
logging.info(f"Validation succeeded for predefined model: {model}")
|
||||||
|
|
||||||
def build_dumps():
|
|
||||||
for model in valid_dumps:
|
def _build_dumps(self):
|
||||||
settings.computer = model
|
"""
|
||||||
settings.custom_model = ""
|
Generate a build for each predefined model
|
||||||
logging.info(f"Validating dumped model: {settings.computer.real_model}")
|
Then validate against ocvalidate
|
||||||
build.build_opencore(settings.computer.real_model, settings).build_opencore()
|
"""
|
||||||
result = subprocess.run([settings.ocvalidate_path, f"{settings.opencore_release_folder}/EFI/OC/config.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
||||||
|
for model in self.valid_dumps:
|
||||||
|
self.constants.computer = model
|
||||||
|
self.constants.custom_model = ""
|
||||||
|
logging.info(f"Validating dumped model: {self.constants.computer.real_model}")
|
||||||
|
build.build_opencore(self.constants.computer.real_model, self.constants).build_opencore()
|
||||||
|
result = subprocess.run([self.constants.ocvalidate_path, f"{self.constants.opencore_release_folder}/EFI/OC/config.plist"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
logging.info("Error on build!")
|
logging.info("Error on build!")
|
||||||
logging.info(result.stdout.decode())
|
logging.info(result.stdout.decode())
|
||||||
raise Exception(f"Validation failed for predefined model: {settings.computer.real_model}")
|
raise Exception(f"Validation failed for predefined model: {self.constants.computer.real_model}")
|
||||||
else:
|
else:
|
||||||
logging.info(f"Validation succeeded for predefined model: {settings.computer.real_model}")
|
logging.info(f"Validation succeeded for predefined model: {self.constants.computer.real_model}")
|
||||||
|
|
||||||
|
|
||||||
def validate_root_patch_files(major_kernel, minor_kernel):
|
def _validate_root_patch_files(self, major_kernel, minor_kernel):
|
||||||
patchset = sys_patch_dict.SystemPatchDictionary(major_kernel, minor_kernel, settings.legacy_accel_support)
|
"""
|
||||||
|
Validate that all files in the patchset are present in the payload
|
||||||
|
"""
|
||||||
|
|
||||||
|
patchset = sys_patch_dict.SystemPatchDictionary(major_kernel, minor_kernel, self.constants.legacy_accel_support)
|
||||||
host_os_float = float(f"{major_kernel}.{minor_kernel}")
|
host_os_float = float(f"{major_kernel}.{minor_kernel}")
|
||||||
|
|
||||||
for patch_subject in patchset:
|
for patch_subject in patchset:
|
||||||
for patch_core in patchset[patch_subject]:
|
for patch_core in patchset[patch_subject]:
|
||||||
patch_os_min_float = float(f'{patchset[patch_subject][patch_core]["OS Support"]["Minimum OS Support"]["OS Major"]}.{patchset[patch_subject][patch_core]["OS Support"]["Minimum OS Support"]["OS Minor"]}')
|
patch_os_min_float = float(f'{patchset[patch_subject][patch_core]["OS Support"]["Minimum OS Support"]["OS Major"]}.{patchset[patch_subject][patch_core]["OS Support"]["Minimum OS Support"]["OS Minor"]}')
|
||||||
@@ -81,58 +111,83 @@ def validate(settings):
|
|||||||
if install_type in patchset[patch_subject][patch_core]:
|
if install_type in patchset[patch_subject][patch_core]:
|
||||||
for install_directory in patchset[patch_subject][patch_core][install_type]:
|
for install_directory in patchset[patch_subject][patch_core][install_type]:
|
||||||
for install_file in patchset[patch_subject][patch_core][install_type][install_directory]:
|
for install_file in patchset[patch_subject][patch_core][install_type][install_directory]:
|
||||||
source_file = str(settings.payload_local_binaries_root_path) + "/" + patchset[patch_subject][patch_core][install_type][install_directory][install_file] + install_directory + "/" + install_file
|
source_file = str(self.constants.payload_local_binaries_root_path) + "/" + patchset[patch_subject][patch_core][install_type][install_directory][install_file] + install_directory + "/" + install_file
|
||||||
if not Path(source_file).exists():
|
if not Path(source_file).exists():
|
||||||
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.sys_patch_helpers(settings).generate_patchset_plist(patchset, f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist", None):
|
if not sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist", None):
|
||||||
raise Exception("Failed to generate patchset plist")
|
raise Exception("Failed to generate patchset plist")
|
||||||
|
|
||||||
# Remove the plist file after validation
|
# Remove the plist file after validation
|
||||||
Path(settings.payload_path / f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist").unlink()
|
Path(self.constants.payload_path / f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist").unlink()
|
||||||
|
|
||||||
|
|
||||||
def validate_sys_patch():
|
def _validate_sys_patch(self):
|
||||||
if Path(settings.payload_local_binaries_root_path_zip).exists():
|
"""
|
||||||
|
Validates sys_patch modules
|
||||||
|
"""
|
||||||
|
|
||||||
|
if Path(self.constants.payload_local_binaries_root_path_zip).exists():
|
||||||
logging.info("Validating Root Patch File integrity")
|
logging.info("Validating Root Patch File integrity")
|
||||||
if not Path(settings.payload_local_binaries_root_path).exists():
|
if not Path(self.constants.payload_local_binaries_root_path).exists():
|
||||||
subprocess.run(["ditto", "-V", "-x", "-k", "--sequesterRsrc", "--rsrc", settings.payload_local_binaries_root_path_zip, settings.payload_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
subprocess.run(
|
||||||
|
[
|
||||||
|
"ditto", "-V", "-x", "-k", "--sequesterRsrc", "--rsrc",
|
||||||
|
self.constants.payload_local_binaries_root_path_zip,
|
||||||
|
self.constants.payload_path
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT
|
||||||
|
)
|
||||||
for supported_os in [os_data.os_data.big_sur, os_data.os_data.monterey, os_data.os_data.ventura]:
|
for 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):
|
||||||
validate_root_patch_files(supported_os, i)
|
self._validate_root_patch_files(supported_os, i)
|
||||||
logging.info("Validating SNB Board ID patcher")
|
logging.info("Validating SNB Board ID patcher")
|
||||||
settings.computer.reported_board_id = "Mac-7BA5B2DFE22DDD8C"
|
self.constants.computer.reported_board_id = "Mac-7BA5B2DFE22DDD8C"
|
||||||
sys_patch_helpers.sys_patch_helpers(settings).snb_board_id_patch(settings.payload_local_binaries_root_path)
|
sys_patch_helpers.SysPatchHelpers(self.constants).snb_board_id_patch(self.constants.payload_local_binaries_root_path)
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
"rm", "-rf", self.constants.payload_local_binaries_root_path
|
||||||
|
],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
logging.info("- Skipping Root Patch File integrity validation")
|
logging.info("- Skipping Root Patch File integrity validation")
|
||||||
|
|
||||||
|
|
||||||
def validate_configs():
|
def _validate_configs(self):
|
||||||
|
"""
|
||||||
|
Validates build modules
|
||||||
|
"""
|
||||||
|
|
||||||
# First run is with default settings
|
# First run is with default settings
|
||||||
build_prebuilt()
|
self._build_prebuilt()
|
||||||
build_dumps()
|
self._build_dumps()
|
||||||
|
|
||||||
# Second run, flip all settings
|
# Second run, flip all settings
|
||||||
settings.verbose_debug = True
|
self.constants.verbose_debug = True
|
||||||
settings.opencore_debug = True
|
self.constants.opencore_debug = True
|
||||||
settings.opencore_build = "DEBUG"
|
self.constants.opencore_build = "DEBUG"
|
||||||
settings.kext_debug = True
|
self.constants.kext_debug = True
|
||||||
settings.kext_variant = "DEBUG"
|
self.constants.kext_variant = "DEBUG"
|
||||||
settings.kext_debug = True
|
self.constants.kext_debug = True
|
||||||
settings.showpicker = False
|
self.constants.showpicker = False
|
||||||
settings.sip_status = False
|
self.constants.sip_status = False
|
||||||
settings.secure_status = True
|
self.constants.secure_status = True
|
||||||
settings.firewire_boot = True
|
self.constants.firewire_boot = True
|
||||||
settings.nvme_boot = True
|
self.constants.nvme_boot = True
|
||||||
settings.enable_wake_on_wlan = True
|
self.constants.enable_wake_on_wlan = True
|
||||||
settings.disable_tb = True
|
self.constants.disable_tb = True
|
||||||
settings.force_surplus = True
|
self.constants.force_surplus = True
|
||||||
settings.software_demux = True
|
self.constants.software_demux = True
|
||||||
settings.serial_settings = "Minimal"
|
self.constants.serial_settings = "Minimal"
|
||||||
build_prebuilt()
|
|
||||||
build_dumps()
|
|
||||||
|
|
||||||
|
self._build_prebuilt()
|
||||||
|
self._build_dumps()
|
||||||
|
|
||||||
validate_configs()
|
subprocess.run(["rm", "-rf", self.constants.build_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
validate_sys_patch()
|
|
||||||
Reference in New Issue
Block a user