mirror of
https://github.com/dortania/OpenCore-Legacy-Patcher.git
synced 2026-04-13 20:28:21 +10:00
Merge branch 'main' into blurbeta-workaround
This commit is contained in:
@@ -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")
|
||||
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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("*"):
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"))
|
||||
@@ -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
|
||||
@@ -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'))
|
||||
@@ -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)}")
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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():
|
||||
|
||||
@@ -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']: {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
@@ -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:
|
||||
|
||||
338
resources/wx_gui/gui_sys_patch_display.py
Normal file
338
resources/wx_gui/gui_sys_patch_display.py
Normal 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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user