Merge branch 'main' into blurbeta-workaround

This commit is contained in:
Mykola Grymalyuk
2023-05-30 13:46:54 -07:00
committed by GitHub
34 changed files with 897 additions and 514 deletions

View File

@@ -30,7 +30,7 @@ class CreateBinary:
def __init__(self):
start = time.time()
print("- Starting build script")
print("Starting build script")
self.args = self._parse_arguments()
@@ -39,7 +39,7 @@ class CreateBinary:
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):
@@ -48,7 +48,7 @@ class CreateBinary:
"""
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):
@@ -88,7 +88,7 @@ class CreateBinary:
pyinstaller_path = f"{python_bin_dir}pyinstaller"
if not Path(pyinstaller_path).exists():
print(f" - pyinstaller not found:\n\t{pyinstaller_path}")
print(f"- pyinstaller not found:\n\t{pyinstaller_path}")
raise Exception("pyinstaller not found")
self.pyinstaller_path = pyinstaller_path
@@ -99,7 +99,7 @@ class CreateBinary:
Start preflight processes
"""
print("- Starting preflight processes")
print("Starting preflight processes")
self._setup_pathing()
self._delete_extra_binaries()
self._download_resources()
@@ -111,7 +111,7 @@ class CreateBinary:
Start postflight processes
"""
print("- Starting postflight processes")
print("Starting postflight processes")
self._patch_load_command()
self._add_commit_data()
self._post_flight_cleanup()
@@ -124,19 +124,19 @@ class CreateBinary:
"""
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", "-rf", "./dist/OpenCore-Patcher.app"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
if rm_output.returncode != 0:
print("- Remove failed")
print("Remove failed")
print(rm_output.stderr.decode('utf-8'))
raise Exception("Remove failed")
self._embed_key()
print("- Building GUI binary...")
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)
@@ -144,12 +144,12 @@ class CreateBinary:
self._strip_key()
if build_result.returncode != 0:
print("- Build failed")
print("Build failed")
print(build_result.stderr.decode('utf-8'))
raise Exception("Build failed")
# Next embed support icns into ./Resources
print("- Embedding icns...")
print("Embedding icns...")
for file in Path("payloads/Icon/AppIcons").glob("*.icns"):
subprocess.run(
["cp", str(file), "./dist/OpenCore-Patcher.app/Contents/Resources/"],
@@ -165,15 +165,15 @@ class CreateBinary:
"""
if not self.args.key:
print("- No developer key provided, skipping...")
print("No developer key provided, skipping...")
return
if not self.args.site:
print("- No site provided, skipping...")
print("No site provided, skipping...")
return
print("- Embedding developer key...")
print("Embedding developer key...")
if not Path("./resources/analytics_handler.py").exists():
print("- analytics_handler.py not found")
print("analytics_handler.py not found")
return
lines = []
@@ -196,15 +196,15 @@ class CreateBinary:
"""
if not self.args.key:
print("- No developer key provided, skipping...")
print("No developer key provided, skipping...")
return
if not self.args.site:
print("- No site provided, skipping...")
print("No site provided, skipping...")
return
print("- Stripping developer key...")
print("Stripping developer key...")
if not Path("./resources/analytics_handler.py").exists():
print("- analytics_handler.py not found")
print("analytics_handler.py not found")
return
lines = []
@@ -247,17 +247,17 @@ class CreateBinary:
]
print("- Deleting extra binaries...")
print("Deleting extra binaries...")
for file in Path("payloads").glob(pattern="*"):
if file.is_dir():
if file.name in whitelist_folders:
continue
print(f" - Deleting {file.name}")
print(f"- Deleting {file.name}")
subprocess.run(["rm", "-rf", file])
else:
if file.name in whitelist_files:
continue
print(f" - Deleting {file.name}")
print(f"- Deleting {file.name}")
subprocess.run(["rm", "-f", file])
@@ -271,23 +271,23 @@ class CreateBinary:
"Universal-Binaries.dmg"
]
print("- Downloading required resources...")
print("Downloading required resources...")
for resource in required_resources:
if Path(f"./{resource}").exists():
if self.args.reset_binaries:
print(f" - Removing old {resource}")
print(f"- Removing old {resource}")
rm_output = subprocess.run(
["rm", "-rf", f"./{resource}"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
if rm_output.returncode != 0:
print("- Remove failed")
print("Remove failed")
print(rm_output.stderr.decode('utf-8'))
raise Exception("Remove failed")
else:
print(f" - {resource} already exists, skipping download")
print(f"- {resource} already exists, skipping download")
continue
print(f" - Downloading {resource}...")
print(f"- Downloading {resource}...")
download_result = subprocess.run(
[
@@ -298,11 +298,11 @@ class CreateBinary:
)
if download_result.returncode != 0:
print(" - Download failed")
print("- Download failed")
print(download_result.stderr.decode('utf-8'))
raise Exception("Download failed")
if not Path(f"./{resource}").exists():
print(f" - {resource} not found")
print(f"- {resource} not found")
raise Exception(f"{resource} not found")
@@ -315,20 +315,20 @@ class CreateBinary:
if Path("./payloads.dmg").exists():
if not self.args.reset_binaries:
print(" - payloads.dmg already exists, skipping creation")
print("- payloads.dmg already exists, skipping creation")
return
print(" - Removing old payloads.dmg")
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("Remove failed")
print(rm_output.stderr.decode('utf-8'))
raise Exception("Remove failed")
print(" - Generating DMG...")
print("- Generating DMG...")
dmg_output = subprocess.run([
'hdiutil', 'create', './payloads.dmg',
'-megabytes', '32000', # Overlays can only be as large as the disk image allows
@@ -339,11 +339,11 @@ class CreateBinary:
'-passphrase', 'password', '-encryption'
], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if dmg_output.returncode != 0:
print(" - DMG generation failed")
print("- DMG generation failed")
print(dmg_output.stderr.decode('utf-8'))
raise Exception("DMG generation failed")
print(" - DMG generation complete")
print("- DMG generation complete")
def _add_commit_data(self):
@@ -352,7 +352,7 @@ class CreateBinary:
"""
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"
commit_url = ""
commit_date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
@@ -360,7 +360,7 @@ class CreateBinary:
branch = self.args.branch
commit_url = self.args.commit
commit_date = self.args.commit_date
print(" - Adding commit data to Info.plist")
print("- Adding commit data to Info.plist")
plist_path = Path("./dist/OpenCore-Patcher.app/Contents/Info.plist")
plist = plistlib.load(Path(plist_path).open("rb"))
plist["Github"] = {
@@ -389,7 +389,7 @@ class CreateBinary:
"""
print(" - Patching LC_VERSION_MIN_MACOSX")
print("- Patching LC_VERSION_MIN_MACOSX")
path = './dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher'
find = b'\x00\x0D\x0A\x00' # 10.13 (0xA0D)
replace = b'\x00\x0A\x0A\x00' # 10.10 (0xA0A)
@@ -406,13 +406,13 @@ class CreateBinary:
"""
path = "./dist/OpenCore-Patcher"
print(f" - Removing {path}")
print(f"- Removing {path}")
rm_output = subprocess.run(
["rm", "-rf", path],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
if rm_output.returncode != 0:
print(f" - Remove failed: {path}")
print(f"- Remove failed: {path}")
print(rm_output.stderr.decode('utf-8'))
raise Exception(f"Remove failed: {path}")
@@ -422,13 +422,13 @@ class CreateBinary:
Validate generated binary
"""
print(" - Validating binary")
print("- Validating binary")
validate_output = subprocess.run(
["./dist/OpenCore-Patcher.app/Contents/MacOS/OpenCore-Patcher", "--build", "--model", "MacPro3,1"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
if validate_output.returncode != 0:
print(" - Validation failed")
print("- Validation failed")
print(validate_output.stderr.decode('utf-8'))
raise Exception("Validation failed")

View File

@@ -7,11 +7,33 @@
- Allows for Live Text support on systems with3802 GPUs
- ie. Intel Ivy Bridge and Haswell, Nvidia Kepler
- Previously disabled due to high instability in Photos with Face Scanning, now resolved
- Resolve crashing after patching with MenuBar2 implementation enabled
- Work-around crashing after patching with MenuBar2 implementation enabled
- Setting must be re-enabled after patching
- Update non-Metal Binaries:
- Resolve window placement defaulting past top of screen for some apps
- ex. OpenCore-Patcher.app during root patching
- Resolve indeterminate progress bars not rendering with wxWidgets in Monterey and later
- ex. OpenCore-Patcher.app
- UI changes:
- Add "Show Log File" button to menubar
- Backend changes:
- Call `setpgrp()` to prevent app from being killed if parent process is killed (ie. LaunchAgents)
- Rework logging handler:
- Implement formatted logging
- Allowing easier debugging
- Implement per-version, per-run file logging
- ex. OpenCore-Patcher (0.6.7) (2021-12-31-12-34-56).log
- Keep only 10 latest log files
- Reveal log file in Finder on main thread crash
- Avoid writing username to log file
- Resolve SharedSupport.dmg pathing error during macOS Installer Verification
- Applicable to systems with 2 (or more) USB Installers with the same name plugged in
- Resolve payloads path being mis-routed during CLI calls
- Add UI when fetching root patches for host
- Remove progress bar work-around for non-Metal in Monterey and later
- Requires host to have been patched with PatcherSupportPkg 1.1.2 or newer
- Increment Binaries:
- PatcherSupportPkg 1.1.2 - release
## 0.6.6
- Implement option to disable ColorSync downgrade on HD 3000 Macs

View File

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

View File

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

View File

@@ -136,7 +136,7 @@ class BuildSupport:
for acpi in config_plist["ACPI"]["Add"]:
if not Path(self.constants.opencore_release_folder / Path("EFI/OC/ACPI") / Path(acpi["Path"])).exists():
logging.info(f" - Missing ACPI Table: {acpi['Path']}")
logging.info(f"- Missing ACPI Table: {acpi['Path']}")
raise Exception(f"Missing ACPI Table: {acpi['Path']}")
for kext in config_plist["Kernel"]["Add"]:
@@ -155,19 +155,19 @@ class BuildSupport:
for tool in config_plist["Misc"]["Tools"]:
if not Path(self.constants.opencore_release_folder / Path("EFI/OC/Tools") / Path(tool["Path"])).exists():
logging.info(f" - Missing tool: {tool['Path']}")
logging.info(f"- Missing tool: {tool['Path']}")
raise Exception(f"Missing tool: {tool['Path']}")
for driver in config_plist["UEFI"]["Drivers"]:
if not Path(self.constants.opencore_release_folder / Path("EFI/OC/Drivers") / Path(driver["Path"])).exists():
logging.info(f" - Missing driver: {driver['Path']}")
logging.info(f"- Missing driver: {driver['Path']}")
raise Exception(f"Missing driver: {driver['Path']}")
# Validating local files
# Report if they have no associated config.plist entry (i.e. they're not being used)
for tool_files in Path(self.constants.opencore_release_folder / Path("EFI/OC/Tools")).glob("*"):
if tool_files.name not in [x["Path"] for x in config_plist["Misc"]["Tools"]]:
logging.info(f" - Missing tool from config: {tool_files.name}")
logging.info(f"- Missing tool from config: {tool_files.name}")
raise Exception(f"Missing tool from config: {tool_files.name}")
for driver_file in Path(self.constants.opencore_release_folder / Path("EFI/OC/Drivers")).glob("*"):

View File

@@ -13,7 +13,7 @@ class Constants:
def __init__(self) -> None:
# Patcher Versioning
self.patcher_version: str = "0.6.7" # OpenCore-Legacy-Patcher
self.patcher_support_pkg_version: str = "1.1.0" # PatcherSupportPkg
self.patcher_support_pkg_version: str = "1.1.2" # PatcherSupportPkg
self.copyright_date: str = "Copyright © 2020-2023 Dortania"
self.patcher_name: str = "OpenCore Legacy Patcher"
@@ -134,6 +134,7 @@ class Constants:
self.booted_oc_disk: str = None # Determine current disk OCLP booted from
self.unpack_thread = None # Determine if unpack thread finished (threading.Thread)
self.update_stage: int = 0 # Determine update stage (see gui_support.py)
self.log_filepath: Path = None # Path to log file
self.commit_info: tuple = (None, None, None) # Commit info (Branch, Commit Date, Commit URL)

View File

@@ -48,7 +48,7 @@ class GlobalEnviromentSettings:
try:
plistlib.dump(plist, Path(self.global_settings_plist).open("wb"))
except PermissionError:
logging.info("- Failed to write to global settings file")
logging.info("Failed to write to global settings file")
def _generate_settings_file(self) -> None:
@@ -57,7 +57,7 @@ class GlobalEnviromentSettings:
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")
logging.info("Permission error: Unable to write to global settings file")
def _convert_defaults_to_global_settings(self) -> None:
@@ -76,14 +76,14 @@ class GlobalEnviromentSettings:
try:
plistlib.dump(global_settings_plist, Path(self.global_settings_plist).open("wb"))
except PermissionError:
logging.info("- Permission error: Unable to write to global settings file")
logging.info("Permission error: Unable to write to global settings file")
return
# delete defaults plist
try:
Path(defaults_path).unlink()
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) -> None:
@@ -100,6 +100,6 @@ class GlobalEnviromentSettings:
# 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:")
logging.warning("Failed to fix settings file permissions:")
if result.stderr:
logging.warning(result.stderr.decode("utf-8"))

View File

@@ -88,13 +88,13 @@ class tui_disk_installation:
def install_opencore(self, full_disk_identifier: str):
# TODO: Apple Script fails in Yosemite(?) and older
logging.info(f"- Mounting partition: {full_disk_identifier}")
logging.info(f"Mounting partition: {full_disk_identifier}")
if self.constants.detected_os >= os_data.os_data.el_capitan and not self.constants.recovery_status:
try:
applescript.AppleScript(f'''do shell script "diskutil mount {full_disk_identifier}" with prompt "OpenCore Legacy Patcher needs administrator privileges to mount this volume." with administrator privileges without altering line endings''').run()
except applescript.ScriptError as e:
if "User canceled" in str(e):
logging.info("- Mount cancelled by user")
logging.info("Mount cancelled by user")
return
logging.info(f"An error occurred: {e}")
if utilities.check_boot_mode() == "safe_boot":
@@ -104,7 +104,7 @@ class tui_disk_installation:
else:
result = subprocess.run(f"diskutil mount {full_disk_identifier}".split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
logging.info("- Mount failed")
logging.info("Mount failed")
logging.info(result.stderr.decode())
return
@@ -124,18 +124,18 @@ class tui_disk_installation:
return False
if (mount_path / Path("EFI/OC")).exists():
logging.info("- Removing preexisting EFI/OC folder")
logging.info("Removing preexisting EFI/OC folder")
subprocess.run(["rm", "-rf", mount_path / Path("EFI/OC")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if (mount_path / Path("System")).exists():
logging.info("- Removing preexisting System folder")
logging.info("Removing preexisting System folder")
subprocess.run(["rm", "-rf", mount_path / Path("System")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if (mount_path / Path("boot.efi")).exists():
logging.info("- Removing preexisting boot.efi")
logging.info("Removing preexisting boot.efi")
subprocess.run(["rm", mount_path / Path("boot.efi")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
logging.info("- Copying OpenCore onto EFI partition")
logging.info("Copying OpenCore onto EFI partition")
subprocess.run(["mkdir", "-p", mount_path / Path("EFI")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run(["cp", "-r", self.constants.opencore_release_folder / Path("EFI/OC"), mount_path / Path("EFI/OC")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
subprocess.run(["cp", "-r", self.constants.opencore_release_folder / Path("System"), mount_path / Path("System")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -144,7 +144,7 @@ class tui_disk_installation:
subprocess.run(["cp", self.constants.opencore_release_folder / Path("boot.efi"), mount_path / Path("boot.efi")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if self.constants.boot_efi is True:
logging.info("- Converting Bootstrap to BOOTx64.efi")
logging.info("Converting Bootstrap to BOOTx64.efi")
if (mount_path / Path("EFI/BOOT")).exists():
subprocess.run(["rm", "-rf", mount_path / Path("EFI/BOOT")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Path(mount_path / Path("EFI/BOOT")).mkdir()
@@ -152,23 +152,23 @@ class tui_disk_installation:
subprocess.run(["rm", "-rf", mount_path / Path("System")], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if self._determine_sd_card(sd_type) is True:
logging.info("- Adding SD Card icon")
logging.info("Adding SD Card icon")
subprocess.run(["cp", self.constants.icon_path_sd, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
elif ssd_type is True:
logging.info("- Adding SSD icon")
logging.info("Adding SSD icon")
subprocess.run(["cp", self.constants.icon_path_ssd, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
elif disk_type == "USB":
logging.info("- Adding External USB Drive icon")
logging.info("Adding External USB Drive icon")
subprocess.run(["cp", self.constants.icon_path_external, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
else:
logging.info("- Adding Internal Drive icon")
logging.info("Adding Internal Drive icon")
subprocess.run(["cp", self.constants.icon_path_internal, mount_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
logging.info("- Cleaning install location")
logging.info("Cleaning install location")
if not self.constants.recovery_status:
logging.info("- Unmounting EFI partition")
logging.info("Unmounting EFI partition")
subprocess.run(["diskutil", "umount", mount_path], stdout=subprocess.PIPE).stdout.decode().strip().encode()
logging.info("- OpenCore transfer complete")
logging.info("OpenCore transfer complete")
return True

View File

@@ -94,7 +94,7 @@ class KernelDebugKitObject:
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
@@ -107,11 +107,11 @@ class KernelDebugKitObject:
timeout=5
)
except (requests.exceptions.Timeout, requests.exceptions.TooManyRedirects, requests.exceptions.ConnectionError):
logging.info("- Could not contact KDK API")
logging.info("Could not contact KDK API")
return None
if results.status_code != 200:
logging.info("- Could not fetch KDK list")
logging.info("Could not fetch KDK list")
return None
KDK_ASSET_LIST = sorted(results.json(), key=lambda x: (packaging.version.parse(x["version"]), datetime.datetime.fromisoformat(x["date"])), reverse=True)
@@ -138,12 +138,12 @@ class KernelDebugKitObject:
if os_data.os_conversion.os_to_kernel(str(parsed_version.major)) < os_data.os_data.ventura:
self.error_msg = "KDKs are not required for macOS Monterey or older"
logging.warning(f"- {self.error_msg}")
logging.warning(f"{self.error_msg}")
return
self.kdk_installed_path = self._local_kdk_installed()
if self.kdk_installed_path:
logging.info(f"- KDK already installed ({Path(self.kdk_installed_path).name}), skipping")
logging.info(f"KDK already installed ({Path(self.kdk_installed_path).name}), skipping")
self.kdk_already_installed = True
self.success = True
return
@@ -151,29 +151,29 @@ class KernelDebugKitObject:
remote_kdk_version = self._get_remote_kdks()
if remote_kdk_version is None:
logging.warning("- Failed to fetch KDK list, falling back to local KDK matching")
logging.warning("Failed to fetch KDK list, falling back to local KDK matching")
# First check if a KDK matching the current macOS version is installed
# ex. 13.0.1 vs 13.0
loose_version = f"{parsed_version.major}.{parsed_version.minor}"
logging.info(f"- Checking for KDKs loosely matching {loose_version}")
logging.info(f"Checking for KDKs loosely matching {loose_version}")
self.kdk_installed_path = self._local_kdk_installed(match=loose_version, check_version=True)
if self.kdk_installed_path:
logging.info(f"- Found matching KDK: {Path(self.kdk_installed_path).name}")
logging.info(f"Found matching KDK: {Path(self.kdk_installed_path).name}")
self.kdk_already_installed = True
self.success = True
return
older_version = f"{parsed_version.major}.{parsed_version.minor - 1 if parsed_version.minor > 0 else 0}"
logging.info(f"- Checking for KDKs matching {older_version}")
logging.info(f"Checking for KDKs matching {older_version}")
self.kdk_installed_path = self._local_kdk_installed(match=older_version, check_version=True)
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
return
logging.warning(f"- Couldn't find KDK matching {host_version} or {older_version}, please install one manually")
logging.warning(f"Couldn't find KDK matching {host_version} or {older_version}, please install one manually")
self.error_msg = f"Could not contact KdkSupportPkg API, and no KDK matching {host_version} ({host_build}) or {older_version} was installed.\nPlease ensure you have a network connection or manually install a KDK."
@@ -211,32 +211,32 @@ class KernelDebugKitObject:
if self.kdk_url == "":
if self.kdk_closest_match_url == "":
logging.warning(f"- No KDKs found for {host_build} ({host_version})")
logging.warning(f"No KDKs found for {host_build} ({host_version})")
self.error_msg = f"No KDKs found for {host_build} ({host_version})"
return
logging.info(f"- No direct match found for {host_build}, falling back to closest match")
logging.info(f"- Closest Match: {self.kdk_closest_match_url_build} ({self.kdk_closest_match_url_version})")
logging.info(f"No direct match found for {host_build}, falling back to closest match")
logging.info(f"Closest Match: {self.kdk_closest_match_url_build} ({self.kdk_closest_match_url_version})")
self.kdk_url = self.kdk_closest_match_url
self.kdk_url_build = self.kdk_closest_match_url_build
self.kdk_url_version = self.kdk_closest_match_url_version
self.kdk_url_expected_size = self.kdk_closest_match_url_expected_size
else:
logging.info(f"- Direct match found for {host_build} ({host_version})")
logging.info(f"Direct match found for {host_build} ({host_version})")
# Check if this KDK is already installed
self.kdk_installed_path = self._local_kdk_installed(match=self.kdk_url_build)
if self.kdk_installed_path:
logging.info(f"- KDK already installed ({Path(self.kdk_installed_path).name}), skipping")
logging.info(f"KDK already installed ({Path(self.kdk_installed_path).name}), skipping")
self.kdk_already_installed = True
self.success = True
return
logging.info("- Following KDK is recommended:")
logging.info(f"- KDK Build: {self.kdk_url_build}")
logging.info(f"- KDK Version: {self.kdk_url_version}")
logging.info(f"- KDK URL: {self.kdk_url}")
logging.info("Following KDK is recommended:")
logging.info(f"- KDK Build: {self.kdk_url_build}")
logging.info(f"- KDK Version: {self.kdk_url_version}")
logging.info(f"- KDK URL: {self.kdk_url}")
self.success = True
@@ -256,7 +256,7 @@ class KernelDebugKitObject:
self.error_msg = ""
if self.kdk_already_installed:
logging.info("- No download required, KDK already installed")
logging.info("No download required, KDK already installed")
self.success = True
return None
@@ -265,7 +265,7 @@ class KernelDebugKitObject:
logging.error(self.error_msg)
return None
logging.info(f"- Returning DownloadObject for KDK: {Path(self.kdk_url).name}")
logging.info(f"Returning DownloadObject for KDK: {Path(self.kdk_url).name}")
self.success = True
kdk_download_path = self.constants.kdk_download_path if override_path == "" else Path(override_path)
@@ -294,7 +294,7 @@ class KernelDebugKitObject:
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}")
logging.error(f"Failed to generate KDK Info.plist: {e}")
def _local_kdk_valid(self, kdk_path: Path) -> bool:
@@ -314,14 +314,14 @@ class KernelDebugKitObject:
"""
if not Path(f"{kdk_path}/System/Library/CoreServices/SystemVersion.plist").exists():
logging.info(f"- Corrupted KDK found ({kdk_path.name}), removing due to missing SystemVersion.plist")
logging.info(f"Corrupted KDK found ({kdk_path.name}), removing due to missing SystemVersion.plist")
self._remove_kdk(kdk_path)
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")
logging.info(f"Corrupted KDK found ({kdk_path.name}), removing due to missing ProductBuildVersion")
self._remove_kdk(kdk_path)
return False
@@ -331,7 +331,7 @@ class KernelDebugKitObject:
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")
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
@@ -339,7 +339,7 @@ class KernelDebugKitObject:
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}")
logging.info(f"Corrupted KDK found ({kdk_path.name}), removing due to missing file: {line}")
self._remove_kdk(kdk_path)
return False
@@ -368,7 +368,7 @@ class KernelDebugKitObject:
for kext in KEXT_CATALOG:
if not Path(f"{kdk_path}/System/Library/Extensions/{kext}").exists():
logging.info(f"- Corrupted KDK found, removing due to missing: {kdk_path}/System/Library/Extensions/{kext}")
logging.info(f"Corrupted KDK found, removing due to missing: {kdk_path}/System/Library/Extensions/{kext}")
self._remove_kdk(kdk_path)
return False
@@ -427,15 +427,15 @@ class KernelDebugKitObject:
if not kdk_pkg.name.endswith(f"{match}.pkg"):
continue
logging.info(f"- Found KDK backup: {kdk_pkg.name}")
logging.info(f"Found KDK backup: {kdk_pkg.name}")
if self.passive is False:
logging.info("- Attempting KDK restoration")
logging.info("Attempting KDK restoration")
if KernelDebugKitUtilities().install_kdk_pkg(kdk_pkg):
logging.info("- Successfully restored KDK")
logging.info("Successfully restored KDK")
return self._local_kdk_installed(match=match, check_version=check_version)
else:
# When in passive mode, we're just checking if a KDK could be restored
logging.info("- KDK restoration skipped, running in passive mode")
logging.info("KDK restoration skipped, running in passive mode")
return kdk_pkg
return None
@@ -453,22 +453,22 @@ class KernelDebugKitObject:
return
if os.getuid() != 0:
logging.warning("- Cannot remove KDK, not running as root")
logging.warning("Cannot remove KDK, not running as root")
return
if not Path(kdk_path).exists():
logging.warning(f"- KDK does not exist: {kdk_path}")
logging.warning(f"KDK does not exist: {kdk_path}")
return
rm_args = ["rm", "-rf" if Path(kdk_path).is_dir() else "-f", kdk_path]
result = utilities.elevated(rm_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0:
logging.warning(f"- Failed to remove KDK: {kdk_path}")
logging.warning(f"- {result.stdout.decode('utf-8')}")
logging.warning(f"Failed to remove KDK: {kdk_path}")
logging.warning(f"{result.stdout.decode('utf-8')}")
return
logging.info(f"- Successfully removed KDK: {kdk_path}")
logging.info(f"Successfully removed KDK: {kdk_path}")
def _remove_unused_kdks(self, exclude_builds: list = None) -> None:
@@ -495,7 +495,7 @@ class KernelDebugKitObject:
if not Path(KDK_INSTALL_PATH).exists():
return
logging.info("- Cleaning unused KDKs")
logging.info("Cleaning unused KDKs")
for kdk_folder in Path(KDK_INSTALL_PATH).iterdir():
if kdk_folder.name.endswith(".kdk") or kdk_folder.name.endswith(".pkg"):
should_remove = True
@@ -532,17 +532,17 @@ class KernelDebugKitObject:
# TODO: should we use the checksum from the API?
result = subprocess.run(["hdiutil", "verify", self.constants.kdk_download_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
logging.info("- Error: Kernel Debug Kit checksum verification failed!")
logging.info(f"- Output: {result.stderr.decode('utf-8')}")
logging.info("Error: Kernel Debug Kit checksum verification failed!")
logging.info(f"Output: {result.stderr.decode('utf-8')}")
msg = "Kernel Debug Kit checksum verification failed, please try again.\n\nIf this continues to fail, ensure you're downloading on a stable network connection (ie. Ethernet)"
logging.info(f"- {msg}")
logging.info(f"{msg}")
self.error_msg = msg
return False
self._remove_unused_kdks()
self.success = True
logging.info("- Kernel Debug Kit checksum verified")
logging.info("Kernel Debug Kit checksum verified")
return True
@@ -568,17 +568,17 @@ class KernelDebugKitUtilities:
"""
if os.getuid() != 0:
logging.warning("- Cannot install KDK, not running as root")
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...")
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("Failed to install KDK:")
logging.info(result.stdout.decode('utf-8'))
if result.stderr:
logging.info(result.stderr.decode('utf-8'))
@@ -599,21 +599,21 @@ class KernelDebugKitUtilities:
"""
if os.getuid() != 0:
logging.warning("- Cannot install KDK, not running as root")
logging.warning("Cannot install KDK, not running as root")
return False
logging.info(f"- Extracting downloaded KDK disk image")
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("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!!!")
logging.warning("Failed to find KDK package in DMG, likely corrupted!!!")
self._unmount_disk_image(mount_point)
return False
@@ -624,7 +624,7 @@ class KernelDebugKitUtilities:
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")
logging.info("Successfully installed KDK")
return True
def _unmount_disk_image(self, mount_point) -> None:
@@ -647,31 +647,31 @@ class KernelDebugKitUtilities:
"""
if not kdk_path.exists():
logging.warning("- KDK does not exist, cannot create backup")
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")
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")
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")
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}")
logging.info(f"Creating backup: {kdk_dst_name}")
if kdk_dst_path.exists():
logging.info("- Backup already exists, skipping")
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("Failed to create KDK backup:")
logging.info(result.stdout.decode('utf-8'))

View File

@@ -1,5 +1,7 @@
import os
import sys
import time
import pprint
import logging
import threading
import traceback
@@ -8,7 +10,7 @@ import applescript
from pathlib import Path
from resources import constants
from resources import constants, analytics_handler
class InitializeLoggingSupport:
@@ -32,22 +34,23 @@ class InitializeLoggingSupport:
"""
def __init__(self, global_constants: constants.Constants) -> None:
self.log_filename: str = "OpenCore-Patcher.log"
self.log_filepath: Path = None
self.constants: constants.Constants = global_constants
self.log_filename: str = f"OpenCore-Patcher_{self.constants.patcher_version}_{time.strftime('%Y-%m-%d_%H-%M-%S')}.log"
self.log_filepath: Path = None
self.original_excepthook: sys = sys.excepthook
self.original_thread_excepthook: threading = threading.excepthook
self.max_file_size: int = 1024 * 1024 * 10 # 10 MB
self.file_size_redline: int = 1024 * 1024 * 9 # 9 MB, when to start cleaning log file
self.max_file_size: int = 1024 * 1024 # 1 MB
self.file_size_redline: int = 1024 * 1024 - 1024 * 100 # 900 KB, when to start cleaning log file
self._initialize_logging_path()
self._clean_log_file()
self._attempt_initialize_logging_configuration()
self._start_logging()
self._implement_custom_traceback_handler()
self._fix_file_permission()
self._clean_prior_version_logs()
def _initialize_logging_path(self) -> None:
@@ -55,39 +58,57 @@ class InitializeLoggingSupport:
Initialize logging framework storage path
"""
self.log_filepath = Path(f"~/Library/Logs/{self.log_filename}").expanduser()
base_path = Path("~/Library/Logs").expanduser()
if not base_path.exists():
# Likely in an installer environment, store in /Users/Shared
base_path = Path("/Users/Shared")
else:
# create Dortania folder if it doesn't exist
base_path = base_path / "Dortania"
if not base_path.exists():
try:
base_path.mkdir()
except Exception as e:
logging.error(f"Failed to create Dortania folder: {e}")
base_path = Path("/Users/Shared")
if not self.log_filepath.parent.exists():
# Likely in an installer environment, store in /Users/Shared
self.log_filepath = Path("/Users/Shared") / self.log_filename
self.log_filepath = Path(f"{base_path}/{self.log_filename}").expanduser()
self.constants.log_filepath = self.log_filepath
print("- Initializing logging framework...")
print(f" - Log file: {self.log_filepath}")
def _clean_log_file(self) -> None:
def _clean_prior_version_logs(self) -> None:
"""
Determine if log file should be cleaned
Clean logs from old Patcher versions
We check if we're near the max file size, and if so, we clean the log file
Keep 10 latest logs
"""
if not self.log_filepath.exists():
return
paths = [
self.log_filepath.parent, # ~/Library/Logs/Dortania
self.log_filepath.parent.parent, # ~/Library/Logs (old location)
]
if self.log_filepath.stat().st_size < self.file_size_redline:
return
logs = []
# Check if backup log file exists
backup_log_filepath = self.log_filepath.with_suffix(".old.log")
try:
if backup_log_filepath.exists():
backup_log_filepath.unlink()
for path in paths:
for file in path.glob("OpenCore-Patcher*"):
if not file.is_file():
continue
# Rename current log file to backup log file
self.log_filepath.rename(backup_log_filepath)
except Exception as e:
print(f"- Failed to clean log file: {e}")
if not file.name.endswith(".log"):
continue
if file.name == self.log_filename:
continue
logs.append(file)
logs.sort(key=lambda x: x.stat().st_mtime, reverse=True)
for log in logs[9:]:
try:
log.unlink()
except Exception as e:
logging.error(f"Failed to delete log file: {e}")
def _fix_file_permission(self) -> None:
@@ -101,11 +122,21 @@ class InitializeLoggingSupport:
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"))
paths = [
self.log_filepath, # ~/Library/Logs/Dortania/OpenCore-Patcher_{version}_{date}.log
self.log_filepath.parent, # ~/Library/Logs/Dortania
]
for path in paths:
result = subprocess.run(["chmod", "777", path], capture_output=True)
if result.returncode != 0:
logging.error(f"Failed to fix log file permissions")
if result.stdout:
logging.error("STDOUT:")
logging.error(result.stdout.decode("utf-8"))
if result.stderr:
logging.error("STDERR:")
logging.error(result.stderr.decode("utf-8"))
def _initialize_logging_configuration(self, log_to_file: bool = True) -> None:
@@ -122,7 +153,7 @@ class InitializeLoggingSupport:
logging.basicConfig(
level=logging.NOTSET,
format="%(asctime)s - %(filename)s (%(lineno)d): %(message)s",
format="[%(asctime)s] [%(filename)-32s] [%(lineno)-4d]: %(message)s",
handlers=[
logging.StreamHandler(stream = sys.stdout),
logging.FileHandler(self.log_filepath) if log_to_file is True else logging.NullHandler()
@@ -143,11 +174,32 @@ class InitializeLoggingSupport:
try:
self._initialize_logging_configuration()
except Exception as e:
print(f"- Failed to initialize logging framework: {e}")
print("- Retrying without logging to file...")
print(f"Failed to initialize logging framework: {e}")
print("Retrying without logging to file...")
self._initialize_logging_configuration(log_to_file=False)
def _start_logging(self):
"""
Start logging, used as easily identifiable start point in logs
"""
str_msg = f"# OpenCore Legacy Patcher ({self.constants.patcher_version}) #"
str_len = len(str_msg)
logging.info('#' * str_len)
logging.info(str_msg)
logging.info('#' * str_len)
logging.info("Log file set:")
# Display relative path to avoid disclosing user's username
try:
path = self.log_filepath.relative_to(Path.home())
logging.info(f"~/{path}")
except ValueError:
logging.info(self.log_filepath)
def _implement_custom_traceback_handler(self) -> None:
"""
Reroute traceback to logging module
@@ -158,6 +210,7 @@ class InitializeLoggingSupport:
Reroute traceback in main thread to logging module
"""
logging.error("Uncaught exception in main thread", exc_info=(type, value, tb))
self._display_debug_properties()
if self.constants.cli_mode is True:
return
@@ -166,9 +219,19 @@ class InitializeLoggingSupport:
error_msg += f"{type.__name__}: {value}"
if tb:
error_msg += f"\n\n{traceback.extract_tb(tb)[-1]}"
error_msg += "\n\nPlease report this error on our Discord server."
error_msg += "\n\nSend crash report to Dortania?"
applescript.AppleScript(f'display dialog "{error_msg}" with title "OpenCore Legacy Patcher ({self.constants.patcher_version})" buttons {{"OK"}} default button "OK" with icon caution giving up after 30').run()
# Ask user if they want to send crash report
try:
result = applescript.AppleScript(f'display dialog "{error_msg}" with title "OpenCore Legacy Patcher ({self.constants.patcher_version})" buttons {{"Yes", "No"}} default button "Yes" with icon caution').run()
except Exception as e:
logging.error(f"Failed to display crash report dialog: {e}")
return
if result[applescript.AEType(b'bhit')] != "Yes":
return
threading.Thread(target=analytics_handler.Analytics(self.constants).send_crash_report, args=(self.log_filepath,)).start()
def custom_thread_excepthook(args) -> None:
@@ -188,3 +251,23 @@ class InitializeLoggingSupport:
sys.excepthook = self.original_excepthook
threading.excepthook = self.original_thread_excepthook
def _display_debug_properties(self) -> None:
"""
Display debug properties, primarily after main thread crash
"""
logging.info("Host Properties:")
logging.info(f" XNU Version: {self.constants.detected_os}.{self.constants.detected_os_minor}")
logging.info(f" XNU Build: {self.constants.detected_os_build}")
logging.info(f" macOS Version: {self.constants.detected_os_version}")
logging.info("Debug Properties:")
logging.info(f" Effective User ID: {os.geteuid()}")
logging.info(f" Effective Group ID: {os.getegid()}")
logging.info(f" Real User ID: {os.getuid()}")
logging.info(f" Real Group ID: {os.getgid()}")
logging.info(" Arguments passed to Patcher:")
for arg in sys.argv:
logging.info(f" {arg}")
logging.info(f"Host Properties:\n{pprint.pformat(self.constants.computer.__dict__, indent=4)}")

View File

@@ -53,7 +53,7 @@ class InstallerCreation():
bool: True if successful, False otherwise
"""
logging.info("- Extracting macOS installer from InstallAssistant.pkg\n This may take some time")
logging.info("Extracting macOS installer from InstallAssistant.pkg")
try:
applescript.AppleScript(
f'''do shell script "installer -pkg {Path(download_path)}/InstallAssistant.pkg -target /"'''
@@ -62,11 +62,11 @@ class InstallerCreation():
" without altering line endings",
).run()
except Exception as e:
logging.info("- Failed to install InstallAssistant")
logging.info("Failed to install InstallAssistant")
logging.info(f" Error Code: {e}")
return False
logging.info("- InstallAssistant installed")
logging.info("InstallAssistant installed")
return True
@@ -201,7 +201,6 @@ fi
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"],

View File

@@ -32,8 +32,6 @@ class OpenCoreLegacyPatcher:
logging_handler.InitializeLoggingSupport(self.constants)
logging.info(f"- Loading OpenCore Legacy Patcher v{self.constants.patcher_version}...")
self._generate_base_data()
if utilities.check_cli_args() is None:
@@ -94,14 +92,13 @@ class OpenCoreLegacyPatcher:
# Generate defaults
defaults.GenerateDefaults(self.computer.real_model, True, self.constants)
threading.Thread(target=analytics_handler.Analytics, args=(self.constants,)).start()
threading.Thread(target=analytics_handler.Analytics(self.constants).send_analytics).start()
if utilities.check_cli_args() is None:
self.constants.cli_mode = False
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")
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", "--update_installed"]

View File

@@ -204,10 +204,10 @@ class DownloadObject:
"""
self.status = DownloadStatus.DOWNLOADING
logging.info(f"- Starting download: {self.filename}")
logging.info(f"Starting download: {self.filename}")
if spawn_thread:
if self.active_thread:
logging.error("- Download already in progress")
logging.error("Download already in progress")
return
self.should_checksum = verify_checksum
self.active_thread = threading.Thread(target=self._download, args=(display_progress,))
@@ -267,8 +267,8 @@ class DownloadObject:
else:
raise Exception("Content-Length missing from headers")
except Exception as e:
logging.error(f"- Error determining file size {self.url}: {str(e)}")
logging.error("- Assuming file size is 0")
logging.error(f"Error determining file size {self.url}: {str(e)}")
logging.error("Assuming file size is 0")
self.total_file_size = 0.0
@@ -295,17 +295,17 @@ class DownloadObject:
try:
if Path(path).exists():
logging.info(f"- Deleting existing file: {path}")
logging.info(f"Deleting existing file: {path}")
Path(path).unlink()
return True
if not Path(path).parent.exists():
logging.info(f"- Creating directory: {Path(path).parent}")
logging.info(f"Creating directory: {Path(path).parent}")
Path(path).parent.mkdir(parents=True, exist_ok=True)
available_space = utilities.get_free_space(Path(path).parent)
if self.total_file_size > available_space:
msg = f"- Not enough free space to download {self.filename}, need {utilities.human_fmt(self.total_file_size)}, have {utilities.human_fmt(available_space)}"
msg = f"Not enough free space to download {self.filename}, need {utilities.human_fmt(self.total_file_size)}, have {utilities.human_fmt(available_space)}"
logging.error(msg)
raise Exception(msg)
@@ -313,7 +313,7 @@ class DownloadObject:
self.error = True
self.error_msg = str(e)
self.status = DownloadStatus.ERROR
logging.error(f"- Error preparing working directory {path}: {self.error_msg}")
logging.error(f"Error preparing working directory {path}: {self.error_msg}")
return False
logging.info(f"- Directory ready: {path}")
@@ -354,21 +354,21 @@ class DownloadObject:
if display_progress and i % 100:
# Don't use logging here, as we'll be spamming the log file
if self.total_file_size == 0.0:
print(f"- Downloaded {utilities.human_fmt(self.downloaded_file_size)} of {self.filename}")
print(f"Downloaded {utilities.human_fmt(self.downloaded_file_size)} of {self.filename}")
else:
print(f"- Downloaded {self.get_percent():.2f}% of {self.filename} ({utilities.human_fmt(self.get_speed())}/s) ({self.get_time_remaining():.2f} seconds remaining)")
print(f"Downloaded {self.get_percent():.2f}% of {self.filename} ({utilities.human_fmt(self.get_speed())}/s) ({self.get_time_remaining():.2f} seconds remaining)")
self.download_complete = True
logging.info(f"- Download complete: {self.filename}")
logging.info("- Stats:")
logging.info(f"- Downloaded size: {utilities.human_fmt(self.downloaded_file_size)}")
logging.info(f"- Time elapsed: {(time.time() - self.start_time):.2f} seconds")
logging.info(f"- Speed: {utilities.human_fmt(self.downloaded_file_size / (time.time() - self.start_time))}/s")
logging.info(f"- Location: {self.filepath}")
logging.info(f"Download complete: {self.filename}")
logging.info("Stats:")
logging.info(f"- Downloaded size: {utilities.human_fmt(self.downloaded_file_size)}")
logging.info(f"- Time elapsed: {(time.time() - self.start_time):.2f} seconds")
logging.info(f"- Speed: {utilities.human_fmt(self.downloaded_file_size / (time.time() - self.start_time))}/s")
logging.info(f"- Location: {self.filepath}")
except Exception as e:
self.error = True
self.error_msg = str(e)
self.status = DownloadStatus.ERROR
logging.error(f"- Error downloading {self.url}: {self.error_msg}")
logging.error(f"Error downloading {self.url}: {self.error_msg}")
self.status = DownloadStatus.COMPLETE
utilities.enable_sleep_after_running()

View File

@@ -28,10 +28,10 @@ class RoutePayloadDiskImage:
"""
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()
logging.info(f"- New payloads location: {self.temp_dir.name}")
logging.info("- Creating payloads directory")
logging.info(f"New payloads location: {self.temp_dir.name}")
logging.info("Creating payloads directory")
Path(self.temp_dir.name / Path("payloads")).mkdir(parents=True, exist_ok=True)
self._unmount_active_dmgs(unmount_all_active=False)
output = subprocess.run(
@@ -45,12 +45,12 @@ class RoutePayloadDiskImage:
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
if output.returncode == 0:
logging.info("- Mounted payloads.dmg")
logging.info("Mounted payloads.dmg")
self.constants.current_path = Path(self.temp_dir.name)
self.constants.payload_path = Path(self.temp_dir.name) / Path("payloads")
atexit.register(self._unmount_active_dmgs, unmount_all_active=False)
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"Return Code: {output.returncode}")
@@ -78,13 +78,13 @@ class RoutePayloadDiskImage:
# Check that only our personal payloads.dmg is unmounted
if "shadow-path" in image:
if self.temp_dir.name in image["shadow-path"]:
logging.info(f"- Unmounting personal {variant}")
logging.info(f"Unmounting personal {variant}")
subprocess.run(
["hdiutil", "detach", image["system-entities"][0]["dev-entry"], "-force"],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
else:
logging.info(f"- Unmounting {variant} at: {image['system-entities'][0]['dev-entry']}")
logging.info(f"Unmounting {variant} at: {image['system-entities'][0]['dev-entry']}")
subprocess.run(
["hdiutil", "detach", image["system-entities"][0]["dev-entry"], "-force"],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT

View File

@@ -92,7 +92,7 @@ class DetectRootPatch:
non_metal_os = os_data.os_data.catalina
for i, gpu in enumerate(gpus):
if gpu.class_code and gpu.class_code != 0xFFFFFFFF:
logging.info(f"- Found GPU ({i}): {utilities.friendly_hex(gpu.vendor_id)}:{utilities.friendly_hex(gpu.device_id)}")
logging.info(f"Found GPU ({i}): {utilities.friendly_hex(gpu.vendor_id)}:{utilities.friendly_hex(gpu.device_id)}")
if gpu.arch in [device_probe.NVIDIA.Archs.Tesla] and self.constants.force_nv_web is False:
if self.constants.detected_os > non_metal_os:
self.nvidia_tesla = True

View File

@@ -40,7 +40,7 @@ class GenerateRootPatchSets:
utilities.cls()
logging.info("- The following patches will be applied:")
logging.info("The following patches will be applied:")
if self.hardware_details["Graphics: Intel Ironlake"] is True:
required_patches.update({"Non-Metal Common": all_hardware_patchset["Graphics"]["Non-Metal Common"]})
@@ -184,8 +184,8 @@ class GenerateRootPatchSets:
del(required_patches[patch_name])
else:
if required_patches[patch_name]["Display Name"]:
logging.info(f" - {required_patches[patch_name]['Display Name']}")
logging.info(f"- {required_patches[patch_name]['Display Name']}")
else:
logging.info(" - No patch sets found for booted model")
logging.info("- No patch sets found for booted model")
return required_patches

View File

@@ -42,10 +42,10 @@ class SysPatchHelpers:
if self.constants.computer.reported_board_id in self.constants.sandy_board_id_stock:
return
logging.info(f"- Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching")
logging.info(f"Found unsupported Board ID {self.constants.computer.reported_board_id}, performing AppleIntelSNBGraphicsFB bin patching")
board_to_patch = generate_smbios.determine_best_board_id_for_sandy(self.constants.computer.reported_board_id, self.constants.computer.gpus)
logging.info(f"- Replacing {board_to_patch} with {self.constants.computer.reported_board_id}")
logging.info(f"Replacing {board_to_patch} with {self.constants.computer.reported_board_id}")
board_to_patch_hex = bytes.fromhex(board_to_patch.encode('utf-8').hex())
reported_board_hex = bytes.fromhex(self.constants.computer.reported_board_id.encode('utf-8').hex())
@@ -54,12 +54,12 @@ class SysPatchHelpers:
# Pad the reported Board ID with zeros to match the length of the board to patch
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}")
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}")
logging.info(f"Error: Could not find {path}")
raise Exception("Failed to find AppleIntelSNBGraphicsFB.kext, cannot patch!!!")
with open(path, 'rb') as f:
@@ -128,7 +128,7 @@ class SysPatchHelpers:
if self.constants.detected_os < os_data.os_data.ventura:
return
logging.info("- Disabling WindowServer Caching")
logging.info("Disabling WindowServer Caching")
# Invoke via 'bash -c' to resolve pathing
utilities.elevated(["bash", "-c", "rm -rf /private/var/folders/*/*/*/WindowServer/com.apple.WindowServer"])
# Disable writing to WindowServer folder
@@ -150,12 +150,12 @@ class SysPatchHelpers:
if self.constants.detected_os < os_data.os_data.ventura:
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 = Path(file_path).expanduser()
if not file_path.exists():
logging.info(" - Defaults file not found, skipping")
logging.info("- Defaults file not found, skipping")
return
did_find = False
@@ -178,7 +178,7 @@ class SysPatchHelpers:
continue
if not b'com.apple.news' in sub_data[sub_entry][2]:
continue
logging.info(f" - Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}")
logging.info(f"- Found News Widget to remove: {sub_data[sub_entry][2].decode('ascii')}")
data["widgets"]["instances"].remove(widget)
did_find = True
@@ -210,10 +210,10 @@ class SysPatchHelpers:
if self.constants.detected_os < os_data.os_data.big_sur:
return
logging.info("- Installing Kernel Collection syncing utility")
logging.info("Installing Kernel Collection syncing utility")
result = utilities.elevated([self.constants.rsrrepair_userspace_path, "--install"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
if result.returncode != 0:
logging.info(f" - Failed to install RSRRepair: {result.stdout.decode()}")
logging.info(f"- Failed to install RSRRepair: {result.stdout.decode()}")
def patch_gpu_compiler_libraries(self, mount_point: Union[str, Path]):
@@ -262,7 +262,7 @@ class SysPatchHelpers:
if not file.name.startswith("31001."):
continue
logging.info(f"- Merging GPUCompiler.framework libraries to match binary")
logging.info(f"Merging GPUCompiler.framework libraries to match binary")
src_dir = f"{LIBRARY_DIR}/{file.name}"
if not Path(f"{DEST_DIR}/lib").exists():

View File

@@ -116,7 +116,7 @@ class CheckBinaryUpdates:
return None
for asset in data_set["assets"]:
logging.info(f"- Found asset: {asset['name']}")
logging.info(f"Found asset: {asset['name']}")
if self._determine_remote_type(asset["name"]) == self._determine_local_build_type():
available_binaries.update({
asset['name']: {

View File

@@ -158,7 +158,7 @@ sleep_process = None
def disable_sleep_while_running():
global sleep_process
logging.info("- Disabling Idle Sleep")
logging.info("Disabling Idle Sleep")
if sleep_process is None:
# If sleep_process is active, we'll just keep it running
sleep_process = subprocess.Popen(["caffeinate", "-d", "-i", "-s"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -168,7 +168,7 @@ def disable_sleep_while_running():
def enable_sleep_after_running():
global sleep_process
if sleep_process:
logging.info("- Re-enabling Idle Sleep")
logging.info("Re-enabling Idle Sleep")
sleep_process.kill()
sleep_process = None
@@ -394,9 +394,6 @@ def get_firmware_vendor(*, decode: bool = False):
value = value.strip("\0")
return value
def dump_constants(constants):
with open(os.path.join(os.path.expanduser('~'), 'Desktop', 'internal_data.txt'), 'w') as f:
f.write(str(vars(constants)))
def find_apfs_physical_volume(device):
# ex: disk3s1s1
@@ -516,7 +513,7 @@ def block_os_updaters():
for bad_process in bad_processes:
if bad_process in current_process:
if pid != "":
logging.info(f"- Killing Process: {pid} - {current_process.split('/')[-1]}")
logging.info(f"Killing Process: {pid} - {current_process.split('/')[-1]}")
subprocess.run(["kill", "-9", pid])
break

View File

@@ -120,7 +120,7 @@ class PatcherValidation:
logging.info(f"File not found: {source_file}")
raise Exception(f"Failed to find {source_file}")
logging.info(f"- Validating against Darwin {major_kernel}.{minor_kernel}")
logging.info(f"Validating against Darwin {major_kernel}.{minor_kernel}")
if not sys_patch_helpers.SysPatchHelpers(self.constants).generate_patchset_plist(patchset, f"OpenCore-Legacy-Patcher-{major_kernel}.{minor_kernel}.plist", None):
raise Exception("Failed to generate patchset plist")
@@ -153,13 +153,13 @@ class PatcherValidation:
)
if output.returncode != 0:
logging.info("- Failed to mount Universal-Binaries.dmg")
logging.info("Failed to mount Universal-Binaries.dmg")
logging.info(f"Output: {output.stdout.decode()}")
logging.info(f"Return Code: {output.returncode}")
raise Exception("Failed to mount Universal-Binaries.dmg")
logging.info("- Mounted Universal-Binaries.dmg")
logging.info("Mounted Universal-Binaries.dmg")
for supported_os in [os_data.os_data.big_sur, os_data.os_data.monterey, os_data.os_data.ventura]:
@@ -179,7 +179,7 @@ class PatcherValidation:
)
if output.returncode != 0:
logging.info("- Failed to unmount Universal-Binaries.dmg")
logging.info("Failed to unmount Universal-Binaries.dmg")
logging.info(f"Output: {output.stdout.decode()}")
logging.info(f"Return Code: {output.returncode}")

View File

@@ -2,6 +2,9 @@
import wx
import wx.adv
import logging
from resources.wx_gui import gui_support
from resources import constants
@@ -12,6 +15,7 @@ class AboutFrame(wx.Frame):
if wx.FindWindowByName("About"):
return
logging.info("Generating About frame")
super(AboutFrame, self).__init__(None, title="About", size=(350, 350), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
self.constants: constants.Constants = global_constants
self.Centre()

View File

@@ -19,6 +19,7 @@ class BuildFrame(wx.Frame):
Uses a Modal Dialog for smoother transition from other frames
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None) -> None:
logging.info("Initializing Build Frame")
super(BuildFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
gui_support.GenerateMenubar(self, global_constants).generate()
@@ -121,7 +122,7 @@ class BuildFrame(wx.Frame):
try:
build.BuildOpenCore(self.constants.custom_model or self.constants.computer.real_model, self.constants)
except:
logging.error("- An internal error occurred while building:\n")
logging.error("An internal error occurred while building:\n")
logging.error(traceback.format_exc())
logger.removeHandler(logger.handlers[2])

View File

@@ -1,5 +1,6 @@
# Generate UI for downloading files
import wx
import logging
from resources import (
constants,
@@ -13,6 +14,7 @@ class DownloadFrame(wx.Frame):
Update provided frame with download stats
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, download_obj: network_handler.DownloadObject, item_name: str) -> None:
logging.info("Initializing Download Frame")
self.constants: constants.Constants = global_constants
self.title: str = title
self.parent: wx.Frame = parent
@@ -91,6 +93,7 @@ class DownloadFrame(wx.Frame):
Terminate download
"""
if wx.MessageBox("Are you sure you want to cancel the download?", "Cancel Download", wx.YES_NO | wx.ICON_QUESTION | wx.NO_DEFAULT) == wx.YES:
logging.info("User cancelled download")
self.user_cancelled = True
self.download_obj.stop()

View File

@@ -9,8 +9,7 @@ from resources.wx_gui import (
gui_main_menu,
gui_build,
gui_install_oc,
gui_sys_patch,
gui_support,
gui_sys_patch_start,
gui_update,
)
from resources.sys_patch import sys_patch_detect
@@ -23,7 +22,7 @@ class SupportedEntryPoints:
MAIN_MENU = gui_main_menu.MainFrame
BUILD_OC = gui_build.BuildFrame
INSTALL_OC = gui_install_oc.InstallOCFrame
SYS_PATCH = gui_sys_patch.SysPatchFrame
SYS_PATCH = gui_sys_patch_start.SysPatchStartFrame
UPDATE_APP = gui_update.UpdateFrame
@@ -49,10 +48,10 @@ class EntryPoint:
self._generate_base_data()
if "--gui_patch" in sys.argv or "--gui_unpatch" in sys.argv:
entry = gui_sys_patch.SysPatchFrame
entry = gui_sys_patch_start.SysPatchStartFrame
patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set()
logging.info(f"- Loading wxPython GUI: {entry.__name__}")
logging.info(f"Entry point set: {entry.__name__}")
self.frame: wx.Frame = entry(
None,
title=f"{self.constants.patcher_name} ({self.constants.patcher_version})",
@@ -64,9 +63,9 @@ class EntryPoint:
atexit.register(self.OnCloseFrame)
if "--gui_patch" in sys.argv:
self.frame.start_root_patching(patches)
self.frame.start_root_patching()
elif "--gui_unpatch" in sys.argv:
self.frame.revert_root_patching(patches)
self.frame.revert_root_patching()
self.app.MainLoop()
@@ -79,7 +78,7 @@ class EntryPoint:
if not self.frame:
return
logging.info("- Cleaning up wxPython GUI")
logging.info("Cleaning up wxPython GUI")
self.frame.SetTransparent(0)
wx.Yield()

View File

@@ -1,5 +1,6 @@
# Generate UI for help menu
import wx
import logging
import webbrowser
from resources import constants
@@ -10,7 +11,7 @@ class HelpFrame(wx.Frame):
Append to main menu through a modal dialog
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None) -> None:
logging.info("Initializing Help Frame")
self.dialog = wx.Dialog(parent, title=title, size=(300, 200))
self.constants: constants.Constants = global_constants

View File

@@ -3,7 +3,7 @@ import threading
import logging
import traceback
from resources.wx_gui import gui_main_menu, gui_support, gui_sys_patch
from resources.wx_gui import gui_main_menu, gui_support, gui_sys_patch_display
from resources import constants, install
from data import os_data
@@ -13,6 +13,7 @@ class InstallOCFrame(wx.Frame):
Create a frame for installing OpenCore to disk
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None):
logging.info("Initializing Install OpenCore Frame")
super(InstallOCFrame, self).__init__(parent, title=title, size=(300, 120), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
gui_support.GenerateMenubar(self, global_constants).generate()
@@ -124,13 +125,14 @@ class InstallOCFrame(wx.Frame):
# disk6s1 -> disk6
disk_root = self.constants.booted_oc_disk.strip("disk")
disk_root = "disk" + disk_root.split("s")[0]
logging.info(f"- Checking if booted disk is present: {disk_root}")
logging.info(f"Checking if booted disk is present: {disk_root}")
# Add buttons for each disk
items = len(self.available_disks)
longest_label = max((len(self.available_disks[disk]['disk']) + len(self.available_disks[disk]['name']) + len(str(self.available_disks[disk]['size']))) for disk in self.available_disks)
longest_label = longest_label * 9
spacer = 0
logging.info("Available disks:")
for disk in self.available_disks:
# Create a button for each disk
logging.info(f"- {self.available_disks[disk]['disk']} - {self.available_disks[disk]['name']} - {self.available_disks[disk]['size']}")
@@ -196,6 +198,7 @@ class InstallOCFrame(wx.Frame):
longest_label = max((len(partitions[partition]['partition']) + len(partitions[partition]['name']) + len(str(partitions[partition]['size']))) for partition in partitions)
longest_label = longest_label * 10
spacer = 0
logging.info(f"Available partitions for {disk}:")
for partition in partitions:
logging.info(f"- {partitions[partition]['partition']} - {partitions[partition]['name']} - {partitions[partition]['size']}")
disk_button = wx.Button(dialog, label=f"{partitions[partition]['partition']} - {partitions[partition]['name']} - {partitions[partition]['size']}", size=(longest_label,30), pos=(-1, text_label.GetPosition()[1] + text_label.GetSize()[1] + 5 + spacer))
@@ -281,7 +284,7 @@ class InstallOCFrame(wx.Frame):
popup_message.ShowModal()
if popup_message.GetReturnCode() == wx.ID_YES:
self.Hide()
gui_sys_patch.SysPatchFrame(
gui_sys_patch_display.SysPatchDisplayFrame(
parent=None,
title=self.title,
global_constants=self.constants,
@@ -308,14 +311,14 @@ class InstallOCFrame(wx.Frame):
"""
Install OpenCore to disk
"""
logging.info(f"- Installing OpenCore to {partition}")
logging.info(f"Installing OpenCore to {partition}")
logger = logging.getLogger()
logger.addHandler(gui_support.ThreadHandler(self.text_box))
try:
self.result = install.tui_disk_installation(self.constants).install_opencore(partition)
except:
logging.error("- An internal error occurred while installing:\n")
logging.error("An internal error occurred while installing:\n")
logging.error(traceback.format_exc())
logger.removeHandler(logger.handlers[2])

View File

@@ -28,7 +28,7 @@ class macOSInstallerDownloadFrame(wx.Frame):
Note: Flashing installers is passed to gui_macos_installer_flash.py
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None):
logging.info("Initializing macOS Installer Download Frame")
self.constants: constants.Constants = global_constants
self.title: str = title
self.parent: wx.Frame = parent
@@ -36,6 +36,8 @@ class macOSInstallerDownloadFrame(wx.Frame):
self.available_installers = None
self.available_installers_latest = None
self.catalog_seed: macos_installer_handler.SeedType = macos_installer_handler.SeedType.DeveloperSeed
self.frame_modal = wx.Dialog(parent, title=title, size=(330, 200))
self._generate_elements(self.frame_modal)
@@ -102,7 +104,8 @@ class macOSInstallerDownloadFrame(wx.Frame):
# Grab installer catalog
def _fetch_installers():
remote_obj = macos_installer_handler.RemoteInstallerCatalog(seed_override=macos_installer_handler.SeedType.DeveloperSeed)
logging.info(f"Fetching installer catalog: {macos_installer_handler.SeedType(self.catalog_seed).name}")
remote_obj = macos_installer_handler.RemoteInstallerCatalog(seed_override=self.catalog_seed)
self.available_installers = remote_obj.available_apps
self.available_installers_latest = remote_obj.available_apps_latest
@@ -138,8 +141,9 @@ class macOSInstallerDownloadFrame(wx.Frame):
installers = self.available_installers_latest if show_full is False else self.available_installers
if installers:
spacer = 0
logging.info(f"Available installers on SUCatalog ({'All entries' if show_full else 'Latest only'}):")
for app in installers:
logging.info(f"macOS {installers[app]['Version']} ({installers[app]['Build']}):\n - Size: {utilities.human_fmt(installers[app]['Size'])}\n - Source: {installers[app]['Source']}\n - Variant: {installers[app]['Variant']}\n - Link: {installers[app]['Link']}\n")
logging.info(f"- macOS {installers[app]['Version']} ({installers[app]['Build']}):\n - Size: {utilities.human_fmt(installers[app]['Size'])}\n - Source: {installers[app]['Source']}\n - Variant: {installers[app]['Variant']}\n - Link: {installers[app]['Link']}\n")
extra = " Beta" if installers[app]['Variant'] in ["DeveloperSeed" , "PublicSeed"] else ""
installer_button = wx.Button(dialog, label=f"macOS {installers[app]['Version']}{extra} ({installers[app]['Build']} - {utilities.human_fmt(installers[app]['Size'])})", pos=(-1, subtitle_label.GetPosition()[1] + subtitle_label.GetSize()[1] + 5 + spacer), size=(270, 30))
@@ -172,6 +176,7 @@ class macOSInstallerDownloadFrame(wx.Frame):
"""
Download macOS installer
"""
logging.info(f"Selected macOS {app['Version']} ({app['Build']})")
# Notify user whether their model is compatible with the selected installer
problems = []
@@ -185,6 +190,7 @@ class macOSInstallerDownloadFrame(wx.Frame):
problems.append("Lack of internal Keyboard/Mouse in macOS installer.")
if problems:
logging.warning(f"Potential issues with {model} and {app['Version']} ({app['Build']}): {problems}")
problems = "\n".join(problems)
dlg = wx.MessageDialog(self.frame_modal, f"Your model ({model}) may not be fully supported by this installer. You may encounter the following issues:\n\n{problems}\n\nFor more information, see associated page. Otherwise, we recommend using macOS Monterey", "Potential Issues", wx.YES_NO | wx.CANCEL | wx.ICON_WARNING)
dlg.SetYesNoCancelLabels("View Github Issue", "Download Anyways", "Cancel")
@@ -198,6 +204,7 @@ class macOSInstallerDownloadFrame(wx.Frame):
host_space = utilities.get_free_space()
needed_space = app['Size'] * 2
if host_space < needed_space:
logging.error(f"Insufficient space to download and extract: {utilities.human_fmt(host_space)} available vs {utilities.human_fmt(needed_space)} required")
dlg = wx.MessageDialog(self.frame_modal, f"You do not have enough free space to download and extract this installer. Please free up some space and try again\n\n{utilities.human_fmt(host_space)} available vs {utilities.human_fmt(needed_space)} required", "Insufficient Space", wx.OK | wx.ICON_WARNING)
dlg.ShowModal()
return
@@ -249,6 +256,7 @@ class macOSInstallerDownloadFrame(wx.Frame):
chunklist_stream = network_handler.NetworkUtilities().get(chunklist_link).content
if chunklist_stream:
logging.info("Validating macOS installer")
utilities.disable_sleep_while_running()
chunk_obj = integrity_verification.ChunklistVerification(self.constants.payload_path / Path("InstallAssistant.pkg"), chunklist_stream)
if chunk_obj.chunks:
@@ -265,10 +273,13 @@ class macOSInstallerDownloadFrame(wx.Frame):
wx.App.Get().Yield()
if chunk_obj.status == integrity_verification.ChunklistStatus.FAILURE:
wx.MessageBox("Chunklist validation failed.\n\nThis generally happens when downloading on unstable connections such as WiFi or cellular.\n\nPlease try redownloading again on a stable connection (ie. Ethernet)", "Corrupted Installer!", wx.OK | wx.ICON_ERROR)
logging.error(f"Chunklist validation failed: Hash mismatch on {chunk_obj.current_chunk}")
wx.MessageBox(f"Chunklist validation failed: Hash mismatch on {chunk_obj.current_chunk}\n\nThis generally happens when downloading on unstable connections such as WiFi or cellular.\n\nPlease try redownloading again on a stable connection (ie. Ethernet)", "Corrupted Installer!", wx.OK | wx.ICON_ERROR)
self.on_return_to_main_menu()
return
logging.info("macOS installer validated")
# Extract installer
title_label.SetLabel("Extracting macOS Installer")
title_label.Centre(wx.HORIZONTAL)

View File

@@ -22,6 +22,7 @@ from data import os_data
class macOSInstallerFlashFrame(wx.Frame):
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None):
logging.info("Initializing macOS Installer Flash Frame")
super(macOSInstallerFlashFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
gui_support.GenerateMenubar(self, global_constants).generate()
@@ -78,7 +79,6 @@ class macOSInstallerFlashFrame(wx.Frame):
wx.Yield()
frame_modal = wx.Dialog(self, title=self.title, size=(350, 200))
frame_modal.Centre(wx.HORIZONTAL)
# Title: Select macOS Installer
title_label = wx.StaticText(frame_modal, label="Select local macOS Installer", pos=(-1,5))
@@ -131,6 +131,7 @@ class macOSInstallerFlashFrame(wx.Frame):
def on_select(self, installer: dict) -> None:
logging.info(f"Selected installer: {installer['Short Name']} ({installer['Version']} ({installer['Build']}))")
self.frame_modal.Destroy()
for child in self.GetChildren():
@@ -185,8 +186,9 @@ class macOSInstallerFlashFrame(wx.Frame):
if self.available_disks:
spacer = 5
entries = len(self.available_disks)
logging.info("Available disks:")
for disk in self.available_disks:
logging.info(f"{disk}: {self.available_disks[disk]['name']} - {utilities.human_fmt(self.available_disks[disk]['size'])}")
logging.info(f" - {disk}: {self.available_disks[disk]['name']} - {utilities.human_fmt(self.available_disks[disk]['size'])}")
disk_button = wx.Button(self.frame_modal, label=f"{disk}: {self.available_disks[disk]['name']} - {utilities.human_fmt(self.available_disks[disk]['size'])}", pos=(-1, warning_label.GetPosition()[1] + warning_label.GetSize()[1] + spacer), size=(300, 30))
disk_button.Bind(wx.EVT_BUTTON, lambda event, temp=disk: self.on_select_disk(self.available_disks[temp], installer))
disk_button.Centre(wx.HORIZONTAL)
@@ -221,6 +223,8 @@ class macOSInstallerFlashFrame(wx.Frame):
if answer != wx.YES:
return
logging.info(f"Selected disk: {disk['name']}")
self.frame_modal.Destroy()
for child in self.GetChildren():
@@ -261,6 +265,7 @@ class macOSInstallerFlashFrame(wx.Frame):
# Prepare resources
if self._prepare_resources(installer['Path'], disk['identifier']) is False:
logging.error("Failed to prepare resources, cannot continue.")
wx.MessageBox("Failed to prepare resources, cannot continue.", "Error", wx.OK | wx.ICON_ERROR)
self.on_return_to_main_menu()
return
@@ -280,6 +285,7 @@ class macOSInstallerFlashFrame(wx.Frame):
initial_bytes_written = float(utilities.monitor_disk_output(root_disk))
self.result = False
def _flash():
logging.info(f"Flashing {installer['Path']} to {root_disk}")
self.result = self._flash_installer(root_disk)
thread = threading.Thread(target=_flash)
@@ -293,10 +299,15 @@ class macOSInstallerFlashFrame(wx.Frame):
total_bytes_written = initial_bytes_written
bytes_written = total_bytes_written - initial_bytes_written
wx.CallAfter(bytes_written_label.SetLabel, f"Bytes Written: {bytes_written:.2f} MB")
wx.CallAfter(progress_bar.SetValue, int(bytes_written))
try:
bytes_written = int(bytes_written)
except:
bytes_written = 0
wx.CallAfter(progress_bar.SetValue, bytes_written)
wx.Yield()
if self.result is False:
logging.error("Failed to flash installer, cannot continue.")
self.on_return_to_main_menu()
return
@@ -354,14 +365,14 @@ class macOSInstallerFlashFrame(wx.Frame):
def _flash_installer(self, disk) -> bool:
utilities.disable_sleep_while_running()
logging.info("- Creating macOS installer")
logging.info("Creating macOS installer")
thread = threading.Thread(target=self._auto_package_handler)
thread.start()
# print contents of installer.sh
with open(self.constants.installer_sh_path, "r") as f:
logging.info(f"- installer.sh contents:\n{f.read()}")
logging.info(f"installer.sh contents:\n{f.read()}")
args = [self.constants.oclp_helper_path, "/bin/sh", self.constants.installer_sh_path]
result = subprocess.run(args, capture_output=True, text=True)
@@ -369,17 +380,17 @@ class macOSInstallerFlashFrame(wx.Frame):
error = result.stderr if result.stderr else ""
if "Install media now available at" not in output:
logging.info("- Failed to create macOS installer")
logging.info("Failed to create macOS installer")
popup = wx.MessageDialog(self, f"Failed to create macOS installer\n\nOutput: {output}\n\nError: {error}", "Error", wx.OK | wx.ICON_ERROR)
popup.ShowModal()
return False
logging.info("- Successfully created macOS installer")
logging.info("Successfully created macOS installer")
while thread.is_alive():
# wait for download_thread to finish
# though highly unlikely this thread is still alive (flashing an Installer will take a while)
time.sleep(0.1)
logging.info("- Installing Root Patcher to drive")
logging.info("Installing Root Patcher to drive")
self._install_installer_pkg(disk)
utilities.enable_sleep_after_running()
@@ -396,7 +407,7 @@ class macOSInstallerFlashFrame(wx.Frame):
"""
link = self.constants.installer_pkg_url
if network_handler.NetworkUtilities(link).validate_link() is False:
logging.info("- Stock Install.pkg is missing on Github, falling back to Nightly")
logging.info("Stock Install.pkg is missing on Github, falling back to Nightly")
link = self.constants.installer_pkg_url_nightly
if link.endswith(".zip"):
@@ -408,7 +419,7 @@ class macOSInstallerFlashFrame(wx.Frame):
autopkg_download.download(spawn_thread=False)
if autopkg_download.download_complete is False:
logging.warning("- Failed to download Install.pkg")
logging.warning("Failed to download Install.pkg")
logging.warning(autopkg_download.error_msg)
return
@@ -434,7 +445,7 @@ class macOSInstallerFlashFrame(wx.Frame):
os_version = plistlib.load(Path(path + "/System/Library/CoreServices/SystemVersion.plist").open("rb"))
kernel_version = os_data.os_conversion.os_to_kernel(os_version["ProductVersion"])
if int(kernel_version) < os_data.os_data.big_sur:
logging.info("- Installer unsupported, requires Big Sur or newer")
logging.info("Installer unsupported, requires Big Sur or newer")
return
subprocess.run(["mkdir", "-p", f"{path}/Library/Packages/"])
@@ -460,29 +471,29 @@ class macOSInstallerFlashFrame(wx.Frame):
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}")
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("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("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")
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")
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
@@ -490,43 +501,46 @@ class macOSInstallerFlashFrame(wx.Frame):
kdk_download_obj.download(spawn_thread=False)
if kdk_download_obj.download_complete is False:
logging.info("- Failed to download KDK")
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}")
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")
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("Failed to mount KDK")
logging.info(result.stdout.decode("utf-8"))
return
logging.info("- Copying KDK")
logging.info("Copying KDK")
subprocess.run(["cp", "-r", f"{mount_point}/KernelDebugKit.pkg", kdk_pkg_path])
logging.info("- Unmounting KDK")
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("Failed to unmount KDK")
logging.info(result.stdout.decode("utf-8"))
return
logging.info("- Removing KDK Disk Image")
logging.info("Removing KDK Disk Image")
kdk_dmg_path.unlink()
def _validate_installer_pkg(self, disk: str) -> bool:
verification_success = False
logging.info("Validating installer pkg")
error_message = ""
def _integrity_check():
nonlocal error_message
path = utilities.grab_mount_point_from_disk(disk + "s2")
dmg_path = path + f"/{path.split('/')[2]}.app/Contents/SharedSupport/SharedSupport.dmg"
for folder in Path(utilities.grab_mount_point_from_disk(disk + "s2")).glob("*.app"):
if folder.is_dir():
dmg_path = folder / "Contents" / "SharedSupport" / "SharedSupport.dmg"
break
if not Path(dmg_path).exists():
logging.error(f"Failed to find {dmg_path}")
error_message = f"Failed to find {dmg_path}"
@@ -546,10 +560,10 @@ class macOSInstallerFlashFrame(wx.Frame):
while thread.is_alive():
wx.Yield()
if verification_success:
if error_message == "":
logging.info("Installer pkg validated")
return error_message
logging.error(error_message)
return error_message

View File

@@ -8,10 +8,11 @@ import webbrowser
from resources.wx_gui import (
gui_build,
gui_macos_installer_download,
gui_sys_patch,
gui_support,
gui_help,
gui_settings,
gui_sys_patch_start,
gui_sys_patch_display,
gui_update,
)
from resources import (
@@ -24,6 +25,7 @@ from data import os_data
class MainFrame(wx.Frame):
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None):
logging.info("Initializing Main Menu Frame")
super(MainFrame, self).__init__(parent, title=title, size=(600, 400), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
gui_support.GenerateMenubar(self, global_constants).generate()
@@ -40,6 +42,7 @@ class MainFrame(wx.Frame):
self.Centre()
self.Show()
self._preflight_checks()
@@ -213,11 +216,11 @@ class MainFrame(wx.Frame):
pop_up.ShowModal()
if pop_up.GetReturnCode() != wx.ID_YES:
print("- Skipping OpenCore and root volume patch update...")
print("Skipping OpenCore and root volume patch update...")
return
print("- Updating OpenCore and root volume patches...")
print("Updating OpenCore and root volume patches...")
self.constants.update_stage = gui_support.AutoUpdateStages.CHECKING
self.Hide()
pos = self.GetPosition()
@@ -277,7 +280,7 @@ class MainFrame(wx.Frame):
def on_post_install_root_patch(self, event: wx.Event = None):
gui_sys_patch.SysPatchFrame(
gui_sys_patch_display.SysPatchDisplayFrame(
parent=self,
title=self.title,
global_constants=self.constants,

View File

@@ -32,6 +32,7 @@ class SettingsFrame(wx.Frame):
Modal-based Settings Frame
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None):
logging.info("Initializing Settings Frame")
self.constants: constants.Constants = global_constants
self.title: str = title
self.parent: wx.Frame = parent
@@ -823,6 +824,12 @@ class SettingsFrame(wx.Frame):
"Check CHANGELOG before blindly updating.",
],
},
"Trigger Exception": {
"type": "button",
"function": self.on_test_exception,
"description": [
],
},
"wrap_around 1": {
"type": "wrap_around",
},
@@ -1257,11 +1264,16 @@ Hardware Information:
def on_export_constants(self, event: wx.Event) -> None:
# Throw pop up to get save location
with wx.FileDialog(self.parent, "Save Constants File", wildcard="JSON files (*.txt)|*.txt", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) as fileDialog:
with wx.FileDialog(self.parent, "Save Constants File", wildcard="JSON files (*.txt)|*.txt", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, defaultFile=f"constants-{self.constants.patcher_version}.txt") as fileDialog:
if fileDialog.ShowModal() == wx.ID_CANCEL:
return
# Save the current contents in the file
pathname = fileDialog.GetPath()
logging.info(f"Saving constants to {pathname}")
with open(pathname, 'w') as file:
file.write(pprint.pformat(vars(self.constants), indent=4))
file.write(pprint.pformat(vars(self.constants), indent=4))
def on_test_exception(self, event: wx.Event) -> None:
raise Exception("Test Exception")

View File

@@ -3,10 +3,13 @@ import os
import sys
import time
import logging
import plistlib
import threading
import subprocess
import applescript
import packaging.version
from pathlib import Path
from resources.wx_gui import gui_about
@@ -37,12 +40,15 @@ class GenerateMenubar:
aboutItem = fileMenu.Append(wx.ID_ABOUT, "&About OpenCore Legacy Patcher")
fileMenu.AppendSeparator()
relaunchItem = fileMenu.Append(wx.ID_ANY, "&Relaunch as Root")
fileMenu.AppendSeparator()
revealLogItem = fileMenu.Append(wx.ID_ANY, "&Reveal Log File")
menubar.Append(fileMenu, "&File")
self.frame.SetMenuBar(menubar)
self.frame.Bind(wx.EVT_MENU, lambda event: gui_about.AboutFrame(self.constants), aboutItem)
self.frame.Bind(wx.EVT_MENU, lambda event: RelaunchApplicationAsRoot(self.frame, self.constants).relaunch(None), relaunchItem)
self.frame.Bind(wx.EVT_MENU, lambda event: subprocess.run(["open", "-R", self.constants.log_filepath]), revealLogItem)
if os.geteuid() == 0:
relaunchItem.Enable(False)
@@ -50,8 +56,10 @@ class GenerateMenubar:
class GaugePulseCallback:
"""
Uses an alternative Pulse() method for wx.Gauge() on macOS Monterey+
Uses an alternative Pulse() method for wx.Gauge() on macOS Monterey+ with non-Metal GPUs
Dirty hack, however better to display some form of animation than none at all
Note: This work-around is no longer needed on hosts using PatcherSupportPkg 1.1.2 or newer
"""
def __init__(self, global_constants: constants.Constants, gauge: wx.Gauge) -> None:
@@ -66,6 +74,9 @@ class GaugePulseCallback:
self.max_value: int = gauge.GetRange()
self.non_metal_alternative: bool = CheckProperties(global_constants).host_is_non_metal()
if self.non_metal_alternative is True:
if CheckProperties(global_constants).host_psp_version() >= packaging.version.Version("1.1.2"):
self.non_metal_alternative = False
def start_pulse(self) -> None:
@@ -106,6 +117,7 @@ class CheckProperties:
def __init__(self, global_constants: constants.Constants) -> None:
self.constants: constants.Constants = global_constants
def host_can_build(self):
"""
Check if host supports building OpenCore configs
@@ -138,6 +150,7 @@ class CheckProperties:
return True
def host_has_cpu_gen(self, gen: int) -> bool:
"""
Check if host has a CPU generation equal to or greater than the specified generation
@@ -149,6 +162,24 @@ class CheckProperties:
return False
def host_psp_version(self) -> packaging.version.Version:
"""
Grab PatcherSupportPkg version from OpenCore-Legacy-Patcher.plist
"""
oclp_plist_path = "/System/Library/CoreServices/OpenCore-Legacy-Patcher.plist"
if not Path(oclp_plist_path).exists():
return packaging.version.Version("0.0.0")
oclp_plist = plistlib.load(open(oclp_plist_path, "rb"))
if "PatcherSupportPkg" not in oclp_plist:
return packaging.version.Version("0.0.0")
if oclp_plist["PatcherSupportPkg"].startswith("v"):
oclp_plist["PatcherSupportPkg"] = oclp_plist["PatcherSupportPkg"][1:]
return packaging.version.parse(oclp_plist["PatcherSupportPkg"])
class PayloadMount:
def __init__(self, global_constants: constants.Constants, frame: wx.Frame) -> None:
@@ -289,7 +320,7 @@ class RelaunchApplicationAsRoot:
wx.Yield()
logging.info(f"- Relaunching as root with command: {program_arguments}")
logging.info(f"Relaunching as root with command: {program_arguments}")
subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:

View File

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

View File

@@ -1,6 +1,5 @@
import wx
import os
import sys
import time
import logging
@@ -14,6 +13,7 @@ from pathlib import Path
from resources import (
constants,
kdk_handler,
global_settings,
)
from resources.sys_patch import (
sys_patch,
@@ -27,38 +27,27 @@ from resources.wx_gui import (
from data import os_data
class SysPatchFrame(wx.Frame):
class SysPatchStartFrame(wx.Frame):
"""
Create a frame for root patching
Uses a Modal Dialog for smoother transition from other frames
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: tuple = None, patches: dict = {}):
self.frame = parent
self.initiated_with_parent = False
if not self.frame and patches == {}:
super(SysPatchFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
self.frame = self
self.frame.Centre()
else:
self.initiated_with_parent = True
logging.info("Initializing Root Patching Frame")
self.title = title
self.constants: constants.Constants = global_constants
self.frame_modal: wx.Dialog = None
self.return_button: wx.Button = None
self.available_patches: bool = False
self.patches: dict = patches
self.frame_modal = wx.Dialog(self.frame, title=title, size=(360, 200))
super(SysPatchStartFrame, self).__init__(parent, title=title, size=(350, 200), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
gui_support.GenerateMenubar(self, self.constants).generate()
self.Centre()
if patches:
return
self._generate_elements_display_patches(self.frame_modal, patches)
self.frame_modal.ShowWindowModal()
if self.constants.update_stage != gui_support.AutoUpdateStages.INACTIVE:
if self.available_patches is False:
gui_support.RestartHost(self.frame).restart(message="No root patch updates needed!\n\nWould you like to reboot to apply the new OpenCore build?")
if self.patches == {}:
self.patches = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set()
def _kdk_download(self, frame: wx.Frame = None) -> bool:
@@ -116,6 +105,7 @@ class SysPatchFrame(wx.Frame):
if kdk_download_obj.download_complete is False:
return False
logging.info("KDK download complete, validating with hdiutil")
header.SetLabel(f"Validating KDK: {self.kdk_obj.kdk_url_build}")
header.Centre(wx.HORIZONTAL)
@@ -136,177 +126,13 @@ class SysPatchFrame(wx.Frame):
progress_bar.SetValue(100)
logging.info("KDK download complete")
for child in frame.GetChildren():
child.Destroy()
return True
def _generate_elements_display_patches(self, frame: wx.Frame = None, patches: dict = {}) -> None:
"""
Generate UI elements for root patching frame
Format:
- Title label: Post-Install Menu
- Label: Available patches:
- Labels: {patch name}
- Button: Start Root Patching
- Button: Revert Root Patches
- Button: Return to Main Menu
"""
frame = self if not frame else frame
title_label = wx.StaticText(frame, label="Post-Install Menu", pos=(-1, 10))
title_label.SetFont(wx.Font(19, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
title_label.Centre(wx.HORIZONTAL)
# Label: Available patches:
available_label = wx.StaticText(frame, label="Available patches for your system:", pos=(-1, title_label.GetPosition()[1] + title_label.GetSize()[1] + 10))
available_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
available_label.Centre(wx.HORIZONTAL)
# Labels: {patch name}
patches: dict = sys_patch_detect.DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set() if not patches else patches
can_unpatch: bool = patches["Validation: Unpatching Possible"]
if not any(not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True for patch in patches):
logging.info("- No applicable patches available")
patches = []
# Check if OCLP has already applied the same patches
no_new_patches = not self._check_if_new_patches_needed(patches) if patches else False
if not patches:
# Prompt user with no patches found
patch_label = wx.StaticText(frame, label="No patches required", pos=(-1, available_label.GetPosition()[1] + 20))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
patch_label.Centre(wx.HORIZONTAL)
else:
# Add Label for each patch
i = 0
if no_new_patches is True:
patch_label = wx.StaticText(frame, label="All applicable patches already installed", pos=(-1, available_label.GetPosition()[1] + 20))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
patch_label.Centre(wx.HORIZONTAL)
i = i + 20
else:
longest_patch = ""
for patch in patches:
if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True):
if len(patch) > len(longest_patch):
longest_patch = patch
anchor = wx.StaticText(frame, label=longest_patch, pos=(-1, available_label.GetPosition()[1] + 20))
anchor.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
anchor.Centre(wx.HORIZONTAL)
anchor.Hide()
for patch in patches:
if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True):
i = i + 20
logging.info(f"- Adding patch: {patch} - {patches[patch]}")
patch_label = wx.StaticText(frame, label=f"- {patch}", pos=(anchor.GetPosition()[0], available_label.GetPosition()[1] + i))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
if i == 20:
patch_label.SetLabel(patch_label.GetLabel().replace("-", ""))
patch_label.Centre(wx.HORIZONTAL)
if patches["Validation: Patching Possible"] is False:
# Cannot patch due to the following reasons:
patch_label = wx.StaticText(frame, label="Cannot patch due to the following reasons:", pos=(-1, patch_label.GetPosition()[1] + 25))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
patch_label.Centre(wx.HORIZONTAL)
longest_patch = ""
for patch in patches:
if not patch.startswith("Validation"):
continue
if patches[patch] is False:
continue
if patch == "Validation: Unpatching Possible":
continue
if len(patch) > len(longest_patch):
longest_patch = patch
anchor = wx.StaticText(frame, label=longest_patch.split('Validation: ')[1], pos=(-1, patch_label.GetPosition()[1] + 20))
anchor.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
anchor.Centre(wx.HORIZONTAL)
anchor.Hide()
i = 0
for patch in patches:
if not patch.startswith("Validation"):
continue
if patches[patch] is False:
continue
if patch == "Validation: Unpatching Possible":
continue
patch_label = wx.StaticText(frame, label=f"- {patch.split('Validation: ')[1]}", pos=(anchor.GetPosition()[0], anchor.GetPosition()[1] + i))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
i = i + 20
if i == 20:
patch_label.SetLabel(patch_label.GetLabel().replace("-", ""))
patch_label.Centre(wx.HORIZONTAL)
else:
if self.constants.computer.oclp_sys_version and self.constants.computer.oclp_sys_date:
date = self.constants.computer.oclp_sys_date.split(" @")
date = date[0] if len(date) == 2 else ""
patch_text = f"{self.constants.computer.oclp_sys_version}, {date}"
patch_label = wx.StaticText(frame, label="Root Volume last patched:", pos=(-1, patch_label.GetPosition().y + 25))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
patch_label.Centre(wx.HORIZONTAL)
patch_label = wx.StaticText(frame, label=patch_text, pos=(available_label.GetPosition().x - 10, patch_label.GetPosition().y + 20))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
patch_label.Centre(wx.HORIZONTAL)
# Button: Start Root Patching
start_button = wx.Button(frame, label="Start Root Patching", pos=(10, patch_label.GetPosition().y + 25), size=(170, 30))
start_button.Bind(wx.EVT_BUTTON, lambda event: self.start_root_patching(patches))
start_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
start_button.Centre(wx.HORIZONTAL)
# Button: Revert Root Patches
revert_button = wx.Button(frame, label="Revert Root Patches", pos=(10, start_button.GetPosition().y + start_button.GetSize().height - 5), size=(170, 30))
revert_button.Bind(wx.EVT_BUTTON, lambda event: self.revert_root_patching(patches))
revert_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
revert_button.Centre(wx.HORIZONTAL)
# Button: Return to Main Menu
return_button = wx.Button(frame, label="Return to Main Menu", pos=(10, revert_button.GetPosition().y + revert_button.GetSize().height), size=(150, 30))
return_button.Bind(wx.EVT_BUTTON, self.on_return_dismiss if self.initiated_with_parent else self.on_return_to_main_menu)
return_button.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, ".AppleSystemUIFont"))
return_button.Centre(wx.HORIZONTAL)
self.return_button = return_button
# Disable buttons if unsupported
if not patches:
start_button.Disable()
else:
self.available_patches = True
if patches["Validation: Patching Possible"] is False:
start_button.Disable()
elif no_new_patches is False:
start_button.SetDefault()
else:
self.available_patches = False
if can_unpatch is False:
revert_button.Disable()
# Relaunch as root if not root
uid = os.geteuid()
if uid != 0:
start_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch)
revert_button.Bind(wx.EVT_BUTTON, gui_support.RelaunchApplicationAsRoot(frame, self.constants).relaunch)
# Set frame size
frame.SetSize((-1, return_button.GetPosition().y + return_button.GetSize().height + 35))
def _generate_modal(self, patches: dict = {}, variant: str = "Root Patching"):
"""
Create UI for root patching/unpatching
@@ -346,9 +172,10 @@ class SysPatchFrame(wx.Frame):
# Labels
i = 0
logging.info("Available patches:")
for patch in patches:
if (not patch.startswith("Settings") and not patch.startswith("Validation") and patches[patch] is True):
logging.info(f"- Adding patch: {patch} - {patches[patch]}")
logging.info(f"- {patch}")
patch_label = wx.StaticText(dialog, label=f"- {patch}", pos=(anchor.GetPosition()[0], label.GetPosition()[1] + 20 + i))
patch_label.SetFont(wx.Font(13, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, ".AppleSystemUIFont"))
i = i + 20
@@ -386,27 +213,20 @@ class SysPatchFrame(wx.Frame):
dialog.ShowWindowModal()
def start_root_patching(self, patches: dict):
self.frame.Close() if self.frame else None
super(SysPatchFrame, self).__init__(None, title=self.title, size=(350, 260), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
gui_support.GenerateMenubar(self, self.constants).generate()
self.Centre()
self.return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) if self.return_button else None
def start_root_patching(self):
logging.info("Starting root patching")
while gui_support.PayloadMount(self.constants, self).is_unpack_finished() is False:
wx.Yield()
if patches["Settings: Kernel Debug Kit missing"] is True:
if self.patches["Settings: Kernel Debug Kit missing"] is True:
if self._kdk_download(self) is False:
self.on_return_to_main_menu()
return
sys.exit(1)
self._generate_modal(patches, "Root Patching")
self._generate_modal(self.patches, "Root Patching")
self.return_button.Disable()
thread = threading.Thread(target=self._start_root_patching, args=(patches,))
thread = threading.Thread(target=self._start_root_patching, args=(self.patches,))
thread.start()
while thread.is_alive():
@@ -422,23 +242,18 @@ class SysPatchFrame(wx.Frame):
try:
sys_patch.PatchSysVolume(self.constants.computer.real_model, self.constants, patches).start_patch()
except:
logging.error("- An internal error occurred while running the Root Patcher:\n")
logging.error("An internal error occurred while running the Root Patcher:\n")
logging.error(traceback.format_exc())
logger.removeHandler(logger.handlers[2])
def revert_root_patching(self, patches: dict):
self.frame.Close() if self.frame else None
super(SysPatchFrame, self).__init__(None, title=self.title, size=(350, 260), style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
gui_support.GenerateMenubar(self, self.constants).generate()
self.Centre()
self.return_button.Bind(wx.EVT_BUTTON, self.on_return_to_main_menu) if self.return_button else None
def revert_root_patching(self):
logging.info("Reverting root patches")
self._generate_modal(patches, "Revert Root Patches")
self._generate_modal(self.patches, "Revert Root Patches")
self.return_button.Disable()
thread = threading.Thread(target=self._revert_root_patching, args=(patches,))
thread = threading.Thread(target=self._revert_root_patching, args=(self.patches,))
thread.start()
while thread.is_alive():
@@ -454,7 +269,7 @@ class SysPatchFrame(wx.Frame):
try:
sys_patch.PatchSysVolume(self.constants.computer.real_model, self.constants, patches).start_unpatch()
except:
logging.error("- An internal error occurred while running the Root Patcher:\n")
logging.error("An internal error occurred while running the Root Patcher:\n")
logging.error(traceback.format_exc())
logger.removeHandler(logger.handlers[2])
@@ -525,6 +340,8 @@ class SysPatchFrame(wx.Frame):
Thus we'll need to see if the exact same OCLP build was used already
"""
logging.info("Checking if new patches are needed")
if self.constants.commit_info[0] in ["Running from source", "Built from source"]:
return True
@@ -557,5 +374,5 @@ class SysPatchFrame(wx.Frame):
logging.info(f"- Patch {patch} not installed")
return True
logging.info("- No new patches detected for system")
logging.info("No new patches detected for system")
return False

View File

@@ -22,6 +22,7 @@ class UpdateFrame(wx.Frame):
Create a frame for updating the patcher
"""
def __init__(self, parent: wx.Frame, title: str, global_constants: constants.Constants, screen_location: wx.Point, url: str = "", version_label: str = "") -> None:
logging.info("Initializing Update Frame")
if parent:
self.parent: wx.Frame = parent
@@ -55,6 +56,9 @@ class UpdateFrame(wx.Frame):
self.version_label = version_label
self.url = url
logging.info(f"Update URL: {url}")
logging.info(f"Update Version: {version_label}")
self.frame: wx.Frame = wx.Frame(
parent=parent if parent else self,
title=self.title,
@@ -180,6 +184,7 @@ class UpdateFrame(wx.Frame):
["ditto", "-xk", str(self.constants.payload_path / "OpenCore-Patcher-GUI.app.zip"), str(self.constants.payload_path)], capture_output=True
)
if result.returncode != 0:
logging.error(f"Failed to extract update. Error: {result.stderr.decode('utf-8')}")
wx.CallAfter(self.progress_bar_animation.stop_pulse)
wx.CallAfter(self.progress_bar.SetValue, 0)
wx.CallAfter(wx.MessageBox, f"Failed to extract update. Error: {result.stderr.decode('utf-8')}", "Critical Error!", wx.OK | wx.ICON_ERROR)
@@ -190,6 +195,7 @@ class UpdateFrame(wx.Frame):
break
if i == 1:
logging.error("Failed to extract update. Error: Update file does not exist")
wx.CallAfter(self.progress_bar_animation.stop_pulse)
wx.CallAfter(self.progress_bar.SetValue, 0)
wx.CallAfter(wx.MessageBox, "Failed to extract update. Error: Update file does not exist", "Critical Error!", wx.OK | wx.ICON_ERROR)
@@ -251,8 +257,10 @@ EOF
wx.CallAfter(self.progress_bar_animation.stop_pulse)
wx.CallAfter(self.progress_bar.SetValue, 0)
if "User cancelled" in result.stderr.decode("utf-8"):
logging.info("User cancelled update")
wx.CallAfter(wx.MessageBox, "User cancelled update", "Update Cancelled", wx.OK | wx.ICON_INFORMATION)
else:
logging.critical(f"Failed to install update. Error: {result.stderr.decode('utf-8')}")
wx.CallAfter(wx.MessageBox, f"Failed to install update. Error: {result.stderr.decode('utf-8')}", "Critical Error!", wx.OK | wx.ICON_ERROR)
wx.CallAfter(sys.exit, 1)