diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d5a1c380..d4f25f01e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - Verify host's disk space before downloading macOS Installers - Remove duplicate OS builds in macOS downloader - Avoids Apple's odd bug of publishing 2 different 12.5.1 products +- Implement deeper macOS installer parsing + - Provides better version detection than Apple provides in .app - Ventura Specific Updates: diff --git a/gui/gui_main.py b/gui/gui_main.py index 6c2b310d3..4e8cb8cbe 100644 --- a/gui/gui_main.py +++ b/gui/gui_main.py @@ -1678,7 +1678,7 @@ class wx_python_gui: self.header.SetFont(wx.Font(18, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)) self.header.Centre(wx.HORIZONTAL) # Subheader: Installers found in /Applications - self.subheader = wx.StaticText(self.frame, label="Installers found in Applications folder") + self.subheader = wx.StaticText(self.frame, label="Searching for Installers in /Applications") self.subheader.SetFont(wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)) self.subheader.SetPosition( wx.Point( @@ -1688,12 +1688,46 @@ class wx_python_gui: ) self.subheader.Centre(wx.HORIZONTAL) + self.available_installers = None + + # Spawn thread to get list of installers + def get_installers(): + self.available_installers = installer.list_local_macOS_installers() + + thread_get_installers = threading.Thread(target=get_installers) + thread_get_installers.start() + + # Progress bar + self.progress_bar = wx.Gauge(self.frame, range=100, size=(self.WINDOW_WIDTH_MAIN - 50, -1)) + self.progress_bar.SetPosition( + wx.Point( + self.header.GetPosition().x, + self.subheader.GetPosition().y + self.subheader.GetSize().height + 10 + ) + ) + self.progress_bar.Centre(wx.HORIZONTAL) + self.progress_bar.Pulse() + + # Set window size + self.frame.SetSize(-1, self.progress_bar.GetPosition().y + self.progress_bar.GetSize().height + 40) + + while thread_get_installers.is_alive(): + self.pulse_alternative(self.progress_bar) + wx.App.Get().Yield() + + # Remove progress bar + self.progress_bar.Destroy() + + self.subheader.SetLabel("Installers found in Applications folder") + self.subheader.Centre(wx.HORIZONTAL) + + available_installers = self.available_installers + i = -7 - available_installers = installer.list_local_macOS_installers() if available_installers: - print("Installer found") + print("Installer(s) found:") for app in available_installers: - print(f"{available_installers[app]['Short Name']}: {available_installers[app]['Version']} ({available_installers[app]['Build']})") + print(f"- {available_installers[app]['Short Name']}: {available_installers[app]['Version']} ({available_installers[app]['Build']})") self.install_selection = wx.Button(self.frame, label=f"{available_installers[app]['Short Name']}: {available_installers[app]['Version']} ({available_installers[app]['Build']})", size=(320, 30)) i = i + 25 self.install_selection.SetPosition( diff --git a/resources/installer.py b/resources/installer.py index a79b5daa7..b5f006a4c 100644 --- a/resources/installer.py +++ b/resources/installer.py @@ -47,6 +47,14 @@ def list_local_macOS_installers(): can_add = False else: can_add = True + + # Check SharedSupport.dmg's data + results = parse_sharedsupport_version(Path("/Applications") / Path(application)/ Path("Contents/SharedSupport/SharedSupport.dmg")) + if results[0] is not None: + app_sdk = results[0] + if results[1] is not None: + app_version = results[1] + if can_add is True: application_list.update({ application: { @@ -64,6 +72,46 @@ def list_local_macOS_installers(): application_list = {k: v for k, v in sorted(application_list.items(), key=lambda item: item[1]["Version"])} return application_list +def parse_sharedsupport_version(sharedsupport_path): + detected_build = None + detected_os = None + sharedsupport_path = Path(sharedsupport_path) + + if not sharedsupport_path.exists(): + return (detected_build, detected_os) + + if not sharedsupport_path.name.endswith(".dmg"): + return (detected_build, detected_os) + + + # Create temporary directory to extract SharedSupport.dmg to + with tempfile.TemporaryDirectory() as tmpdir: + output = subprocess.run( + [ + "hdiutil", "attach", "-noverify", sharedsupport_path, + "-mountpoint", tmpdir, + "-nobrowse", + ], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) + if output.returncode != 0: + return (detected_build, detected_os) + + ss_info = Path("SFR/com_apple_MobileAsset_SFRSoftwareUpdate/com_apple_MobileAsset_SFRSoftwareUpdate.xml") + + if Path(tmpdir / ss_info).exists(): + plist = plistlib.load((tmpdir / ss_info).open("rb")) + if "Build" in plist["Assets"][0]: + detected_build = plist["Assets"][0]["Build"] + if "OSVersion" in plist["Assets"][0]: + detected_os = plist["Assets"][0]["OSVersion"] + + # Unmount SharedSupport.dmg + output = subprocess.run(["hdiutil", "detach", tmpdir], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + + return (detected_build, detected_os) + + def create_installer(installer_path, volume_name): # Creates a macOS installer # Takes a path to the installer and the Volume