patchsets: Implement new patch detection architecture

Greatly streamlines future patch set development
This commit is contained in:
Mykola Grymalyuk
2024-09-01 21:00:05 -06:00
parent d9e91bd17e
commit a329e80082
55 changed files with 4439 additions and 2940 deletions

View File

@@ -12,9 +12,11 @@ import threading
from pathlib import Path
from .. import constants
from ..support import kdk_handler, utilities
from ..support import kdk_handler, utilities, metallib_handler
from ..wx_gui import gui_support, gui_download
from ..sys_patch.patchsets import HardwarePatchsetDetection, HardwarePatchsetSettings
class OSUpdateFrame(wx.Frame):
"""
@@ -40,29 +42,68 @@ class OSUpdateFrame(wx.Frame):
logging.info(f"Staged update found: {os_data[0]} ({os_data[1]})")
self.os_data = os_data
# Check if we need to patch the system volume
results = HardwarePatchsetDetection(
constants=self.constants,
xnu_major=int(self.os_data[1][:2]),
xnu_minor=0, # We can't determine this from the build number
os_build=self.os_data[1],
os_version=self.os_data[0],
).device_properties
if results[HardwarePatchsetSettings.KERNEL_DEBUG_KIT_REQUIRED] is True:
logging.info("KDK required")
if results[HardwarePatchsetSettings.METALLIB_SUPPORT_PKG_REQUIRED] is True:
# TODO: Download MetalLibSupportPkg
logging.info("MetallibSupportPkg required")
if not any([results[HardwarePatchsetSettings.KERNEL_DEBUG_KIT_REQUIRED], results[HardwarePatchsetSettings.METALLIB_SUPPORT_PKG_REQUIRED]]):
logging.info("No additional resources required")
self._exit()
self._generate_ui()
self.kdk_obj: kdk_handler.KernelDebugKitObject = None
def _kdk_thread_spawn():
self.kdk_obj = kdk_handler.KernelDebugKitObject(self.constants, self.os_data[1], self.os_data[0], passive=True, check_backups_only=True)
kdk_thread = threading.Thread(target=_kdk_thread_spawn)
kdk_thread.start()
while kdk_thread.is_alive():
wx.Yield()
self.metallib_obj: metallib_handler.MetalLibraryObject = None
def _metallib_thread_spawn():
self.metallib_obj = metallib_handler.MetalLibraryObject(self.constants, self.os_data[1], self.os_data[0])
if self.kdk_obj.success is False:
if results[HardwarePatchsetSettings.KERNEL_DEBUG_KIT_REQUIRED] is True:
kdk_thread = threading.Thread(target=_kdk_thread_spawn)
kdk_thread.start()
while kdk_thread.is_alive():
wx.Yield()
if results[HardwarePatchsetSettings.METALLIB_SUPPORT_PKG_REQUIRED] is True:
metallib_thread = threading.Thread(target=_metallib_thread_spawn)
metallib_thread.start()
while metallib_thread.is_alive():
wx.Yield()
download_objects = {
# Name: xxx
# download_obj: xxx
}
if self.kdk_obj:
if self.kdk_obj.success is True:
result = self.kdk_obj.retrieve_download()
if result is not None:
download_objects[f"KDK Build {self.kdk_obj.kdk_url_build}"] = result
if self.metallib_obj:
if self.metallib_obj.success is True:
result = self.metallib_obj.retrieve_download()
if result is not None:
download_objects[f"Metallib Build {self.metallib_obj.metallib_url_build}"] = result
if len(download_objects) == 0:
self._exit()
kdk_download_obj = self.kdk_obj.retrieve_download()
if not kdk_download_obj:
# KDK is already downloaded
# Return false since we didn't display anything
self._exit()
self.kdk_download_obj = kdk_download_obj
self.frame.Show()
self.did_cancel = -1
@@ -76,20 +117,34 @@ class OSUpdateFrame(wx.Frame):
if self.did_cancel == -1:
time.sleep(1)
gui_download.DownloadFrame(
self,
title=self.title,
global_constants=self.constants,
download_obj=kdk_download_obj,
item_name=f"KDK Build {self.kdk_obj.kdk_url_build}"
)
if kdk_download_obj.download_complete is False:
self._exit()
for item in download_objects:
name = item
download_obj = download_objects[item]
self.download_obj = download_obj
gui_download.DownloadFrame(
self,
title=self.title,
global_constants=self.constants,
download_obj=download_obj,
item_name=name
)
if download_obj.download_complete is True:
if item.startswith("KDK"):
self._handle_kdk(self.kdk_obj)
if item.startswith("Metallib"):
self._handle_metallib(self.metallib_obj)
self._exit()
def _handle_kdk(self, kdk_obj: kdk_handler.KernelDebugKitObject) -> None:
"""
Handle KDK installation
"""
logging.info("KDK download complete, validating with hdiutil")
self.kdk_checksum_result = False
def _validate_kdk_checksum_thread():
self.kdk_checksum_result = self.kdk_obj.validate_kdk_checksum()
self.kdk_checksum_result = kdk_obj.validate_kdk_checksum()
kdk_checksum_thread = threading.Thread(target=_validate_kdk_checksum_thread)
kdk_checksum_thread.start()
@@ -99,15 +154,16 @@ class OSUpdateFrame(wx.Frame):
if self.kdk_checksum_result is False:
logging.error("KDK checksum validation failed")
logging.error(self.kdk_obj.error_msg)
logging.error(kdk_obj.error_msg)
self._exit()
logging.info("KDK checksum validation passed")
logging.info("Mounting KDK")
if not Path(self.constants.kdk_download_path).exists():
logging.error("KDK download path does not exist")
self._exit()
return
self.kdk_install_result = False
def _install_kdk_thread():
@@ -121,10 +177,31 @@ class OSUpdateFrame(wx.Frame):
if self.kdk_install_result is False:
logging.info("Failed to install KDK")
self._exit()
return
logging.info("KDK installed successfully")
self._exit()
def _handle_metallib(self, metallib_obj: metallib_handler.MetalLibraryObject) -> None:
"""
Handle Metallib installation
"""
self.metallib_install_result = False
def _install_metallib_thread():
self.metallib_install_result = metallib_obj.install_metallib()
metallib_install_thread = threading.Thread(target=_install_metallib_thread)
metallib_install_thread.start()
while metallib_install_thread.is_alive():
wx.Yield()
if self.metallib_install_result is False:
logging.info("Failed to install Metallib")
return
logging.info("Metallib installed successfully")
def _generate_ui(self) -> None:
@@ -179,7 +256,8 @@ class OSUpdateFrame(wx.Frame):
result = dlg.ShowModal()
if result == wx.ID_NO:
logging.info("User cancelled OS caching")
self.kdk_download_obj.stop()
if hasattr(self, "download_obj"):
self.download_obj.stop()
self.did_cancel = 1
else:
self.did_cancel = 0

View File

@@ -12,7 +12,7 @@ from Cocoa import NSApp, NSApplication
from .. import constants
from ..sys_patch.detections import DetectRootPatch
from ..sys_patch.patchsets import HardwarePatchsetDetection
from ..wx_gui import (
gui_cache_os_update,
@@ -64,7 +64,7 @@ class EntryPoint:
if "--gui_patch" in sys.argv or "--gui_unpatch" in sys.argv or start_patching is True :
entry = gui_sys_patch_start.SysPatchStartFrame
patches = DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set()
patches = HardwarePatchsetDetection(constants=self.constants).device_properties
logging.info(f"Entry point set: {entry.__name__}")

View File

@@ -11,7 +11,7 @@ from pathlib import Path
from .. import constants
from ..sys_patch.detections import DetectRootPatch
from ..sys_patch.patchsets import HardwarePatchsetDetection, HardwarePatchsetValidation
from ..wx_gui import (
gui_main_menu,
@@ -86,7 +86,7 @@ class SysPatchDisplayFrame(wx.Frame):
patches: dict = {}
def _fetch_patches(self) -> None:
nonlocal patches
patches = DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set()
patches = HardwarePatchsetDetection(constants=self.constants).device_properties
thread = threading.Thread(target=_fetch_patches, args=(self,))
thread.start()
@@ -106,7 +106,7 @@ class SysPatchDisplayFrame(wx.Frame):
available_label.Centre(wx.HORIZONTAL)
can_unpatch: bool = patches["Validation: Unpatching Possible"]
can_unpatch: bool = patches[HardwarePatchsetValidation.UNPATCHING_NOT_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")
@@ -152,7 +152,7 @@ class SysPatchDisplayFrame(wx.Frame):
patch_label.SetLabel(patch_label.GetLabel().replace("-", ""))
patch_label.Centre(wx.HORIZONTAL)
if patches["Validation: Patching Possible"] is False:
if patches[HardwarePatchsetValidation.PATCHING_NOT_POSSIBLE] is True:
# 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(gui_support.font_factory(13, wx.FONTWEIGHT_BOLD))
@@ -164,7 +164,7 @@ class SysPatchDisplayFrame(wx.Frame):
continue
if patches[patch] is False:
continue
if patch == "Validation: Unpatching Possible":
if patch in [HardwarePatchsetValidation.PATCHING_NOT_POSSIBLE, HardwarePatchsetValidation.UNPATCHING_NOT_POSSIBLE]:
continue
if len(patch) > len(longest_patch):
@@ -180,7 +180,7 @@ class SysPatchDisplayFrame(wx.Frame):
continue
if patches[patch] is False:
continue
if patch == "Validation: Unpatching Possible":
if patch in [HardwarePatchsetValidation.PATCHING_NOT_POSSIBLE, HardwarePatchsetValidation.UNPATCHING_NOT_POSSIBLE]:
continue
patch_label = wx.StaticText(frame, label=f"- {patch.split('Validation: ')[1]}", pos=(anchor.GetPosition()[0], anchor.GetPosition()[1] + i))
@@ -231,7 +231,7 @@ class SysPatchDisplayFrame(wx.Frame):
start_button.Disable()
else:
self.available_patches = True
if patches["Validation: Patching Possible"] is False:
if patches[HardwarePatchsetValidation.PATCHING_NOT_POSSIBLE] is True:
start_button.Disable()
elif no_new_patches is False:
start_button.SetDefault()
@@ -320,7 +320,7 @@ class SysPatchDisplayFrame(wx.Frame):
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
# See sys_patch/patchsets/base.py for more info
patch_installed = False
for key in oclp_plist_data:
if isinstance(oclp_plist_data[key], (bool, int)):

View File

@@ -30,7 +30,7 @@ from ..wx_gui import (
gui_download,
)
from ..sys_patch.detections import DetectRootPatch
from ..sys_patch.patchsets import HardwarePatchsetDetection, HardwarePatchsetSettings
@@ -54,7 +54,7 @@ class SysPatchStartFrame(wx.Frame):
self.Centre()
if self.patches == {}:
self.patches = DetectRootPatch(self.constants.computer.real_model, self.constants).detect_patch_set()
self.patches = HardwarePatchsetDetection(constants=self.constants).device_properties
def _kdk_download(self, frame: wx.Frame = None) -> bool:
@@ -315,11 +315,11 @@ class SysPatchStartFrame(wx.Frame):
while gui_support.PayloadMount(self.constants, self).is_unpack_finished() is False:
wx.Yield()
if self.patches["Settings: Kernel Debug Kit missing"] is True:
if self.patches[HardwarePatchsetSettings.KERNEL_DEBUG_KIT_REQUIRED] is True:
if self._kdk_download(self) is False:
sys.exit(1)
if self.patches["Settings: MetallibSupportPkg missing"] is True:
if self.patches[HardwarePatchsetSettings.METALLIB_SUPPORT_PKG_REQUIRED] is True:
if self._metallib_download(self) is False:
sys.exit(1)
@@ -459,7 +459,7 @@ class SysPatchStartFrame(wx.Frame):
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
# See sys_patch/patchsets/base.py for more info
patch_installed = False
for key in oclp_plist_data:
if isinstance(oclp_plist_data[key], (bool, int)):