From 1bb41d3dac8d75e8e18b281c054dd01f5d4f565a Mon Sep 17 00:00:00 2001 From: CorpNewt Date: Sat, 6 Oct 2018 22:07:20 -0500 Subject: [PATCH] Add files via upload --- MakeInstall.bat | 60 ++++ MakeInstall.py | 373 ++++++++++++++++++++++++ gibMacOS.command | 743 ++++++++++++++++++++++++----------------------- 3 files changed, 810 insertions(+), 366 deletions(-) create mode 100644 MakeInstall.bat create mode 100644 MakeInstall.py diff --git a/MakeInstall.bat b/MakeInstall.bat new file mode 100644 index 0000000..d2e6dbf --- /dev/null +++ b/MakeInstall.bat @@ -0,0 +1,60 @@ +::::::::::::::::::::::::::::::::::::::::: +:: Automatically check & get admin rights +::::::::::::::::::::::::::::::::::::::::: +@echo off + +:checkPrivileges +NET FILE 1>NUL 2>NUL +if '%errorlevel%' == '0' ( goto gotPrivileges ) else ( goto getPrivileges ) + +:getPrivileges +if '%~1'=='ELEV' (shift & goto main) +ECHO. + +setlocal DisableDelayedExpansion +set "batchPath=%~0" +setlocal EnableDelayedExpansion +ECHO Set UAC = CreateObject^("Shell.Application"^) > "%temp%\OEgetPrivileges.vbs" +ECHO UAC.ShellExecute "!batchPath!", "ELEV", "", "runas", 1 >> "%temp%\OEgetPrivileges.vbs" +"%temp%\OEgetPrivileges.vbs" +exit /B + +:gotPrivileges +:::::::::::::::::::::::::::: +::START +:::::::::::::::::::::::::::: + +@echo off +setlocal enableDelayedExpansion + +REM Setup initial vars +set "script_name=MakeInstall.py" +set "thisDir=%~dp0" + +REM Get python location +FOR /F "tokens=* USEBACKQ" %%F IN (`where python 2^> nul`) DO ( + SET "python=%%F" +) + +REM Check for py and give helpful hints! +if /i "!python!"=="" ( + echo Python is not installed or not found in your PATH var. + echo Please install it from https://www.python.org/downloads/windows/ + echo. + echo Make sure you check the box labeled: + echo. + echo "Add Python X.X to PATH" + echo. + echo Where X.X is the py version you're installing. + echo. + echo Press [enter] to quit. + pause > nul + exit /b +) + +REM Python found +if "%*"=="" ( + "!python!" "!thisDir!!script_name!" +) else ( + "!python!" "!thisDir!!script_name!" %* +) \ No newline at end of file diff --git a/MakeInstall.py b/MakeInstall.py new file mode 100644 index 0000000..066be3f --- /dev/null +++ b/MakeInstall.py @@ -0,0 +1,373 @@ +from Scripts import * +import os, sys, tempfile, shutil, zipfile + +class WinUSB: + + def __init__(self): + # Make sure we're on windows + if not os.name=="nt": + print("This script is only for Windows!") + exit(1) + # Setup initial vars + self.d = diskwin.Disk() + self.u = utils.Utils("BinaryDestructionPls") + self.dl = downloader.Downloader() + self.r = run.Run() + self.scripts = "Scripts" + self.s_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), self.scripts) + self.dd_url = "http://www.chrysocome.net/downloads/ddrelease64.exe" + self.dd_name = os.path.basename(self.dd_url) + self.z_url = "https://www.7-zip.org/a/7z1805-x64.msi" + self.z_name = "7z.exe" + # From Tim Sutton's brigadier: https://github.com/timsutton/brigadier/blob/master/brigadier + self.z_path = os.path.join(os.environ['SYSTEMDRIVE'] + "\\", "Program Files", "7-Zip", "7z.exe") + self.recovery_suffixes = ( + "recoveryhdupdate.pkg", + "recoveryhdmetadmg.pkg" + ) + + def get_disks_of_type(self, disk_list, disk_type=(0,2)): + disks = {} + for disk in disk_list: + if disk_list[disk].get("type",0) in disk_type: + disks[disk] = disk_list[disk] + return disks + + def check_dd(self): + # Checks if dd.exe exists in our Scripts dir + # and if not - downloads it + # + # Returns True if exists/downloaded successfully + # or False if issues. + # Check for dd.exe in the current dir + if os.path.exists(os.path.join(self.s_path, self.dd_name)): + print("Located {}!".format(self.dd_name)) + # Got it + return True + print("Couldn't locate {} - downloading...".format(self.dd_name)) + # Now we need to download + self.dl.stream_to_file(self.dd_url, os.path.join(self.s_path, self.dd_name)) + print("") + return os.path.exists(os.path.join(self.s_path, self.dd_name)) + + def check_7z(self): + # Check for 7za.exe and if not - download 7zr msi and install + # + # Returns True if found, False if not + if os.path.exists(self.z_path): + print("Located {}!".format(self.z_name)) + # Got it + return True + print("Didn't locate {} - downloading...".format(self.z_name)) + # Didn't find it - let's do some stupid stuff + temp = tempfile.mkdtemp() + self.dl.stream_to_file(self.z_url, os.path.join(temp, self.z_name)) + print("") + print("Installing 7zip...") + # From Tim Sutton's brigadier: https://github.com/timsutton/brigadier/blob/master/brigadier + self.r.run({"args":["msiexec", "/qn", "/i", os.path.join(temp, self.z_name)],"stream":True}) + print("") + return os.path.exists(self.z_path) + + def get_size(self, size): + # Returns the size passed as human readable + if size == -1: + return "Unknown" + ext = ["B","KB","MB","GB","PB"] + s = float(size) + s_dict = {} + # Iterate the ext list, and divide by 1000 each time + for e in ext: + s_dict[e] = s + s /= 1000 + # Get the maximum >= 1 type + biggest = next((x for x in ext[::-1] if s_dict[x] >= 1), "B") + # Round to 2 decimal places + bval = round(s_dict[biggest], 2) + # Strip any orphaned, trailing 0's + non_zero = False + z_list = [] + for z in str(bval).split(".")[1][::-1]: + if z == "0" and not non_zero: + # We have a zero - and haven't hit a non-zero yet + continue + # Either got a non-zero, or non_zero is already set + non_zero = True # Set again - just in case + z_list.append(z) + if len(z_list): + return "{}.{} {}".format(str(bval).split(".")[0], "".join(z_list[::-1]), biggest) + else: + return "{} {}".format(str(bval).split(".")[0], biggest) + + def diskpart_erase(self, disk): + # Generate a script that we can pipe to diskpart to erase our disk + self.u.head("Creating DiskPart Script") + print("") + # Then we'll re-gather our disk info on success and move forward + dp_script = "\n".join([ + "select disk {}".format(disk.get("index",-1)), + "clean", + "convert mbr", + "create partition primary size=200", + "format quick fs=fat32 label='CLOVER'", + "create partition primary id=AB", # AF = HFS, AB = Recovery + "active" + # "format quick fs=ntfs", + # "set id=AB" + # "assign" + ]) + temp = tempfile.mkdtemp() + script = os.path.join(temp, "diskpart.txt") + try: + with open(script,"w") as f: + f.write(dp_script) + except: + shutil.rmtree(temp) + print("Error creating script!") + print("") + self.u.grab("Press [enter] to return...") + return + # Let's try to run it! + out = self.r.run({"args":["diskpart","/s",script],"stream":True}) + # Ditch our script regardless of whether diskpart worked or not + shutil.rmtree(temp) + if out[2] != 0: + # Error city! + print("") + print("DiskPart exited with non-zero status ({}). Aborting.".format(out[2])) + print("") + self.u.grab("Press [enter] to return...") + return + # We should now have a fresh drive to work with + # Let's write an image or something + self.u.head("Updating Disk Information") + print("") + print("Re-populating list...") + self.d.update() + print("Relocating disk {}".format(disk["index"])) + disk = self.d.disks[str(disk["index"])] + self.select_package(disk) + + def select_package(self, disk): + self.u.head("Select Recovery Package") + print("") + print("{}. {} - {} ({})".format( + disk.get("index",-1), + disk.get("model","Unknown"), + self.get_size(disk.get("size",-1)), + ["Unknown","No Root Dir","Removable","Local","Network","Disc","RAM Disk"][disk.get("type",0)] + )) + print("") + print("M. Main Menu") + print("Q. Quit") + print("") + menu = self.u.grab("Please paste the recovery update pkg path to extract: ") + if menu.lower() == "q": + self.u.custom_quit() + if menu.lower() == "m": + return + path = self.u.check_path(menu) + if not path: + self.select_package(disk) + return + # Got the package - let's make sure it's named right - just in case + if os.path.basename(path).lower() == "4.hfs": + # We have an hfs image already - bypass extraction + self.dd_image(disk, path) + return + # If it's a directory, find the first recovery hit + if os.path.isdir(path): + for f in os.listdir(path): + if f.lower().endswith(self.recovery_suffixes): + path = os.path.join(path, f) + break + # Make sure it's named right for recovery stuffs + if not path.lower().endswith(self.recovery_suffixes): + self.u.head("Invalid Package") + print("") + print("{} is not in the available recovery package names:\n{}".format(os.path.basename(path), ", ".join(self.recovery_suffixes))) + print("") + print("Ensure you're passing a proper recovery package.") + print("") + self.u.grab("Press [enter] to return to package selection...") + self.select_package(disk) + return + temp = tempfile.mkdtemp() + print(temp) + cwd = os.getcwd() + os.chdir(temp) + # Extract in sections and remove any files we run into + out = self.r.run({"args":[self.z_path, "e", "-txar", path, "*.dmg"],"stream":True}) + if out[2] != 0: + shutil.rmtree(temp,ignore_errors=True) + print("An error occurred extracting: {}".format(out[2])) + print("") + self.u.grab("Press [enter] to return...") + return + # No files to delete here - let's extract the next part + out = self.r.run({"args":[self.z_path, "e", "*.dmg", "*/Base*.dmg"],"stream":True}) + if out[2] != 0: + shutil.rmtree(temp,ignore_errors=True) + print("An error occurred extracting: {}".format(out[2])) + print("") + self.u.grab("Press [enter] to return...") + return + # If we got here - we should delete everything in the temp folder except + # for a .dmg that starts with Base + del_list = [x for x in os.listdir(temp) if not (x.lower().startswith("base") and x.lower().endswith(".dmg"))] + for d in del_list: + os.remove(os.path.join(temp, d)) + # shutil.rmtree(os.path.join(temp, d),ignore_errors=True) + # Onto the last command + out = self.r.run({"args":[self.z_path, "e", "-tdmg", "Base*.dmg", "*.hfs"],"stream":True}) + if out[2] != 0: + shutil.rmtree(temp,ignore_errors=True) + print("An error occurred extracting: {}".format(out[2])) + print("") + self.u.grab("Press [enter] to return...") + return + # If we got here - we should delete everything in the temp folder except + # for a .dmg that starts with Base + del_list = [x for x in os.listdir(temp) if not x.lower().endswith(".hfs")] + for d in del_list: + os.remove(os.path.join(temp, d)) + # shutil.rmtree(os.path.join(temp, d),ignore_errors=True) + print("Extracted successfully!") + hfs = next((x for x in os.listdir(temp) if x.lower().endswith(".hfs")),None) + # Now to dd our image - if it exists + if not hfs: + print("Missing the .hfs file! Aborting.") + print("") + self.u.grab("Press [enter] to return...") + else: + self.dd_image(disk, os.path.join(temp, hfs)) + shutil.rmtree(temp,ignore_errors=True) + + def dd_image(self, disk, image): + # Let's dd the shit out of our disk + self.u.head("Copying Image To Drive") + print("") + print("Image: {}".format(image)) + print("") + print("Disk {}. {} - {} ({})".format( + disk.get("index",-1), + disk.get("model","Unknown"), + self.get_size(disk.get("size",-1)), + ["Unknown","No Root Dir","Removable","Local","Network","Disc","RAM Disk"][disk.get("type",0)] + )) + print("") + args = [ + os.path.join(self.s_path, self.dd_name), + "if={}".format(image), + "of=\\\\?\\Device\Harddisk{}\Partition2".format(disk.get("index",-1)), + "bs=8M", + "--progress" + ] + print(" ".join(args)) + print("") + print("This may take some time!") + print("") + out = self.r.run({"args":args}) + if len(out[1].split("Error")) > 1: + # We had some error text + print("An error occurred:\n\n{}".format("Error"+out[1].split("Error")[1])) + else: + print("Done!") + print("") + self.u.grab("Press [enter] to return to the main menu...") + + def main(self): + # Let's make sure we have the required files needed + self.u.head("Checking Required Tools") + print("") + if not self.check_dd(): + print("Couldn't find or install {} - aborting!".format(self.dd_name)) + exit(1) + if not self.check_7z(): + print("Couldn't find or install {} - aborting!".format(self.z_name)) + exit(1) + # Let's just setup a real simple interface and try to write some data + self.u.head("Gathering Disk Info") + print("") + print("Populating list...") + self.d.update() + print("") + print("Done!") + # Let's serve up a list of *only* removable media + self.u.head("Potential Removable Media") + print("") + # rem_disks = self.get_disks_of_type(self.d.disks) + + # SHOWING ALL DISKS CURRENTLY - CHANGE THIS FOR RELEASE!!!! + rem_disks = self.d.disks + + # Types: 0 = Unknown, 1 = No Root Dir, 2 = Removable, 3 = Local, 4 = Network, 5 = Disc, 6 = RAM disk + + print("!WARNING! This list includes both Removable AND") + print("!WARNING! Unknown disk types. Be ABSOLUTELY sure") + print("!WARNING! before selecting a disk!") + print("") + for disk in sorted(rem_disks): + print("{}. {} - {} ({})".format( + disk, + rem_disks[disk].get("model","Unknown"), + self.get_size(rem_disks[disk].get("size",-1)), + ["Unknown","No Root Dir","Removable","Local","Network","Disc","RAM Disk"][rem_disks[disk].get("type",0)] + )) + if not len(rem_disks[disk].get("partitions",{})): + print(" No Mounted Partitions") + else: + parts = rem_disks[disk]["partitions"] + for p in sorted(parts): + print(" {}. {} ({}) {} - {}".format( + p, + parts[p].get("letter","No Letter"), + "No Name" if not parts[p].get("name",None) else parts[p].get("name","No Name"), + parts[p].get("file system","Unknown FS"), + self.get_size(parts[p].get("size",-1)) + )) + print("") + print("Q. Quit") + print("") + menu = self.u.grab("Please select a disk: ") + if not len(menu): + self.main() + return + if menu.lower() == "q": + self.u.custom_quit() + selected_disk = rem_disks.get(menu,None) + if not selected_disk: + self.u.head("Invalid Choice") + print("") + print("Disk {} is not an option.".format(menu)) + print("") + self.u.grab("Returning in 5 seconds...", timeout=5) + self.main() + return + # Got a disk! + while True: + self.u.head("Erase {}".format(selected_disk.get("model","Unknown"))) + print("") + print("{}. {} - {} ({})".format( + selected_disk.get("index",-1), + selected_disk.get("model","Unknown"), + self.get_size(selected_disk.get("size",-1)), + ["Unknown","No Root Dir","Removable","Local","Network","Disc","RAM Disk"][selected_disk.get("type",0)] + )) + print("") + print("If you continue - THIS DISK WILL BE ERASED") + print("ALL DATA WILL BE LOST AND ALL PARTITIONS WILL") + print("BE REMOVED!!!!!!!") + print("") + yn = self.u.grab("Continue? (y/n): ") + if yn.lower() == "n": + self.main() + return + if yn.lower() == "y": + break + # Got the OK to erase! Let's format a diskpart script! + self.diskpart_erase(selected_disk) + self.main() + +w = WinUSB() +w.main() \ No newline at end of file diff --git a/gibMacOS.command b/gibMacOS.command index 66538d9..2dc47a6 100755 --- a/gibMacOS.command +++ b/gibMacOS.command @@ -1,366 +1,377 @@ -#!/usr/bin/python -from Scripts import * -import os, datetime, shutil, time, sys, argparse - -class gibMacOS: - def __init__(self): - self.d = downloader.Downloader() - self.u = utils.Utils("gibMacOS") - self.min_w = 80 - self.min_h = 24 - self.u.resize(self.min_w, self.min_h) - - self.catalog_suffix = { - "public" : "beta", - "publicrelease" : "", - "customer" : "customerseed", - "developer" : "seed" - } - self.current_macos = 14 - self.min_macos = 5 - self.mac_os_names_url = { - "8" : "mountainlion", - "7" : "lion", - "6" : "snowleopard", - "5" : "leopard" - } - self.current_catalog = "publicrelease" - self.catalog_data = None - self.scripts = "Scripts" - self.plist = "cat.plist" - self.saves = "macOS Downloads" - self.save_local = False - self.find_recovery = False - self.recovery_suffixes = ( - "RecoveryHDUpdate.pkg", - "RecoveryHDMetaDmg.pkg" - ) - - def resize(self, width=0, height=0): - if os.name=="nt": - # Winders resizing is dumb... bail - return - width = width if width > self.min_w else self.min_w - height = height if height > self.min_h else self.min_h - self.u.resize(width, height) - - def set_prods(self): - self.resize() - if not self.get_catalog_data(self.save_local): - self.u.head("Catalog Data Error") - print("") - print("The currently selected catalog ({}) was not reachable".format(self.current_catalog)) - if self.save_local: - print("and I was unable to locate a valid {} file in the\n{} directory.".format(self.plist, self.scripts)) - print("Please ensure you have a working internet connection.") - print("") - self.u.grab("Press [enter] to exit...") - self.mac_prods = self.get_dict_for_prods(self.get_installers()) - - def set_catalog(self, catalog): - self.current_catalog = catalog.lower() if catalog.lower() in self.catalog_suffix else "publicrelease" - - def build_url(self, **kwargs): - catalog = kwargs.get("catalog", self.current_catalog).lower() - catalog = catalog if catalog.lower() in self.catalog_suffix else "publicrelease" - version = int(kwargs.get("version", self.current_macos)) - url = "https://swscan.apple.com/content/catalogs/others/index-" - url += "-".join([self.mac_os_names_url[str(x)] if str(x) in self.mac_os_names_url else "10."+str(x) for x in reversed(range(self.min_macos, version+1))]) - url += ".merged-1.sucatalog" - ver_s = self.mac_os_names_url[str(version)] if str(version) in self.mac_os_names_url else "10."+str(version) - url = url.replace(ver_s, ver_s+self.catalog_suffix[catalog]) - return url - - def get_catalog_data(self, local = False): - # Gets the data based on our current_catalog - url = self.build_url(catalog=self.current_catalog, version=self.current_macos) - self.u.head("Downloading Catalog") - print("") - print("Currently downloading {} catalog from\n\n{}\n".format(self.current_catalog, url)) - try: - b = self.d.get_bytes(url) - self.catalog_data = plist.loads(b) - # Assume it's valid data - dump it to a local file - if local: - cwd = os.getcwd() - os.chdir(os.path.dirname(os.path.realpath(__file__))) - with open(os.path.join(os.getcwd(), self.scripts, self.plist), "wb") as f: - plist.dump(self.catalog_data, f) - os.chdir(cwd) - except: - if local: - # Check if we have one locally in our scripts directory - if not os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)), self.scripts, self.plist)): - return False - # It does - try to load it - try: - cwd = os.getcwd() - os.chdir(os.path.dirname(os.path.realpath(__file__))) - with open(os.path.join(os.getcwd(), self.scripts, self.plist), "rb") as f: - self.catalog_data = plist.load(f) - os.chdir(cwd) - except: - return False - return True - - def get_installers(self, plist_dict = None): - if not plist_dict: - plist_dict = self.catalog_data - if not plist_dict: - return [] - mac_prods = [] - for p in plist_dict.get("Products", {}): - if not self.find_recovery: - if plist_dict.get("Products",{}).get(p,{}).get("ExtendedMetaInfo",{}).get("InstallAssistantPackageIdentifiers",{}).get("OSInstall",{}) == "com.apple.mpkg.OSInstall": - mac_prods.append(p) - else: - # Find out if we have any of the recovert_suffixes - if any(x for x in plist_dict.get("Products",{}).get(p,{}).get("Packages",[]) if x["URL"].endswith(self.recovery_suffixes)): - mac_prods.append(p) - return mac_prods - - def get_dict_for_prods(self, prods, plist_dict = None): - if plist_dict==self.catalog_data==None: - plist_dict = {} - else: - plist_dict = self.catalog_data if plist_dict == None else plist_dict - - prod_list = [] - for prod in prods: - # Grab the ServerMetadataURL for the passed product key if it exists - prodd = {"product":prod} - try: - b = self.d.get_bytes(plist_dict.get("Products",{}).get(prod,{}).get("ServerMetadataURL",""), False) - smd = plist.loads(b) - except: - smd = {} - # Populate some info! - prodd["date"] = plist_dict.get("Products",{}).get(prod,{}).get("PostDate","") - prodd["installer"] = False - if plist_dict.get("Products",{}).get(prod,{}).get("ExtendedMetaInfo",{}).get("InstallAssistantPackageIdentifiers",{}).get("OSInstall",{}) == "com.apple.mpkg.OSInstall": - prodd["installer"] = True - prodd["time"] = time.mktime(prodd["date"].timetuple()) + prodd["date"].microsecond / 1E6 - prodd["title"] = smd.get("localization",{}).get("English",{}).get("title","Unknown") - prodd["version"] = smd.get("CFBundleShortVersionString","Unknown") - if prodd["version"] == " ": - prodd["version"] = "" - # Try to get the description too - try: - desc = smd.get("localization",{}).get("English",{}).get("description","").decode("utf-8") - desctext = desc.split('"p1">')[1].split("")[0] - except: - desctext = None - prodd["description"] = desctext - # Iterate the available packages and save their urls and sizes - if self.find_recovery: - # Only get the recovery packages - prodd["packages"] = [x for x in plist_dict.get("Products",{}).get(prod,{}).get("Packages",[]) if x["URL"].endswith(self.recovery_suffixes)] - else: - # Add them all! - prodd["packages"] = plist_dict.get("Products",{}).get(prod,{}).get("Packages",[]) - prod_list.append(prodd) - # Sort by newest - prod_list = sorted(prod_list, key=lambda x:x["time"], reverse=True) - return prod_list - - def download_prod(self, prod, dmg = False): - # Takes a dictonary of details and downloads it - self.resize() - cwd = os.getcwd() - os.chdir(os.path.dirname(os.path.realpath(__file__))) - name = "{} - {} {}".format(prod["product"], prod["version"], prod["title"]) - if os.path.exists(os.path.join(os.getcwd(), self.saves, self.current_catalog, name)): - while True: - self.u.head("Already Exists") - print("") - print("It looks like you've already downloaded {}".format(name)) - print("") - menu = self.u.grab("Redownload? (y/n): ") - if not len(menu): - continue - if menu[0].lower() == "n": - return - if menu[0].lower() == "y": - break - # Remove the old copy, then re-download - shutil.rmtree(os.path.join(os.getcwd(), self.saves, self.current_catalog, name)) - # Make it new - os.makedirs(os.path.join(os.getcwd(), self.saves, self.current_catalog, name)) - dl_list = [] - for x in prod["packages"]: - if not x.get("URL",None): - continue - if dmg and not x.get("URL","").lower().endswith(".dmg"): - continue - # add it to the list - dl_list.append(x["URL"]) - if not len(dl_list): - self.u.head("Error") - print("") - print("There were no files to download") - print("") - self.u.grab("Press [enter] to return...") - return - c = 0 - done = [] - for x in dl_list: - c += 1 - self.u.head("Downloading File {} of {}".format(c, len(dl_list))) - print("") - if len(done): - print("\n".join(["{} --> {}".format(y["name"], "Succeeded" if y["status"] else "Failed") for y in done])) - print("") - if dmg: - print("NOTE: Only Downloading DMG Files") - print("") - print("Downloading {} for {}...".format(os.path.basename(x), name)) - print("") - try: - self.d.stream_to_file(x, os.path.join(os.getcwd(), self.saves, self.current_catalog, name, os.path.basename(x))) - done.append({"name":os.path.basename(x), "status":True}) - except: - done.append({"name":os.path.basename(x), "status":False}) - succeeded = [x for x in done if x["status"]] - failed = [x for x in done if not x["status"]] - self.u.head("Downloaded {} of {}".format(len(succeeded), len(dl_list))) - print("") - print("Succeeded:") - if len(succeeded): - for x in succeeded: - print(" {}".format(x["name"])) - else: - print(" None") - print("") - print("Failed:") - if len(failed): - for x in failed: - print(" {}".format(x["name"])) - else: - print(" None") - print("") - print("Files saved to:") - print(" {}".format(os.path.join(os.getcwd(), self.saves, self.current_catalog, name))) - print("") - self.u.grab("Press [enter] to return...") - - def show_catalog_url(self): - self.resize() - self.u.head() - print("") - print("Current Catalog: {}".format(self.current_catalog)) - print("Max macOS Version: 10.{}".format(self.current_macos)) - print("") - print("{}".format(self.build_url())) - print("") - menu = self.u.grab("Press [enter] to return...") - return - - def main(self, dmg = False): - self.u.head() - print("") - print("Available Products:") - print("") - num = 0 - w = 0 - if not len(self.mac_prods): - print("No installers in catalog!") - print("") - exit() - for p in self.mac_prods: - num += 1 - var1 = "{}. {} {}".format(num, p["title"], p["version"]) - var2 = " - {} - Added {}".format(p["product"], p["date"]) - if self.find_recovery and p["installer"]: - # Show that it's a full installer - var2 += " - FULL Install" - w = len(var1) if len(var1) > w else w - w = len(var2) if len(var2) > w else w - print(var1) - print(var2) - print("") - print("U. Show Catalog URL") - print("Q. Quit") - self.resize(w, (num*2)+11) - menu = self.u.grab("Please select an option: ") - if not len(menu): - return - if menu[0].lower() == "q": - self.resize() - self.u.custom_quit() - elif menu[0].lower() == "u": - self.show_catalog_url() - return - - # Assume we picked something - try: - menu = int(menu) - except: - return - if menu < 1 or menu > len(self.mac_prods): - return - self.download_prod(self.mac_prods[menu-1], dmg) - - def get_latest(self, dmg = False): - self.u.head("Downloading Latest") - print("") - self.download_prod(self.mac_prods[-1], dmg) - - def get_for_product(self, prod, dmg = False): - self.u.head("Downloading for {}".format(prod)) - print("") - for p in self.mac_prods: - if p["product"] == prod: - self.download_prod(p, dmg) - return - print("{} not found".format(prod)) - - def get_for_version(self, vers, dmg = False): - self.u.head("Downloading for {}".format(vers)) - print("") - for p in self.mac_prods: - if p["version"] == vers: - self.download_prod(p, dmg) - return - print("10.{} not found".format(vers)) - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument("-l", "--latest", help="downloads the version avaialble in the current catalog (overrides --version and --product)", action="store_true") - parser.add_argument("-r", "--recovery", help="looks for RecoveryHDUpdate.pkg and RecoveryHDMetaDmg.pkg in lieu of com.apple.mpkg.OSInstall (overrides --dmg)", action="store_true") - parser.add_argument("-d", "--dmg", help="downloads only the .dmg files", action="store_true") - parser.add_argument("-c", "--catalog", help="sets the CATALOG to use - publicrelease, public, customer, developer") - parser.add_argument("-p", "--product", help="sets the product id to search for (overrides --version)") - parser.add_argument("-v", "--version", help="sets the version of macOS to target - eg 10.14") - parser.add_argument("-m", "--maxos", help="sets the max macOS version to consider when building the url - eg 10.14") - args = parser.parse_args() - - g = gibMacOS() - if args.recovery: - args.dmg = False - g.find_recovery = args.recovery - - if args.maxos: - try: - m = int(str(args.maxos).replace("10.","")) - g.current_macos = m - except: - pass - if args.catalog: - # Set the catalog - g.set_catalog(args.catalog) - - # Done setting up pre-requisites - g.set_prods() - - if args.latest: - g.get_latest(args.dmg) - exit() - if args.product != None: - g.get_for_product(args.product, args.dmg) - exit() - if args.version != None: - g.get_for_version(args.version, args.dmg) - exit() - - while True: - g.main(args.dmg) +#!/usr/bin/python +from Scripts import * +import os, datetime, shutil, time, sys, argparse + +class gibMacOS: + def __init__(self): + self.d = downloader.Downloader() + self.u = utils.Utils("gibMacOS") + self.min_w = 80 + self.min_h = 24 + self.u.resize(self.min_w, self.min_h) + + self.catalog_suffix = { + "public" : "beta", + "publicrelease" : "", + "customer" : "customerseed", + "developer" : "seed" + } + self.current_macos = 14 + self.min_macos = 5 + self.mac_os_names_url = { + "8" : "mountainlion", + "7" : "lion", + "6" : "snowleopard", + "5" : "leopard" + } + self.current_catalog = "publicrelease" + self.catalog_data = None + self.scripts = "Scripts" + self.plist = "cat.plist" + self.saves = "macOS Downloads" + self.save_local = False + self.find_recovery = False + self.recovery_suffixes = ( + "RecoveryHDUpdate.pkg", + "RecoveryHDMetaDmg.pkg" + ) + + def resize(self, width=0, height=0): + if os.name=="nt": + # Winders resizing is dumb... bail + return + width = width if width > self.min_w else self.min_w + height = height if height > self.min_h else self.min_h + self.u.resize(width, height) + + def set_prods(self): + self.resize() + if not self.get_catalog_data(self.save_local): + self.u.head("Catalog Data Error") + print("") + print("The currently selected catalog ({}) was not reachable".format(self.current_catalog)) + if self.save_local: + print("and I was unable to locate a valid {} file in the\n{} directory.".format(self.plist, self.scripts)) + print("Please ensure you have a working internet connection.") + print("") + self.u.grab("Press [enter] to exit...") + self.mac_prods = self.get_dict_for_prods(self.get_installers()) + + def set_catalog(self, catalog): + self.current_catalog = catalog.lower() if catalog.lower() in self.catalog_suffix else "publicrelease" + + def build_url(self, **kwargs): + catalog = kwargs.get("catalog", self.current_catalog).lower() + catalog = catalog if catalog.lower() in self.catalog_suffix else "publicrelease" + version = int(kwargs.get("version", self.current_macos)) + url = "https://swscan.apple.com/content/catalogs/others/index-" + url += "-".join([self.mac_os_names_url[str(x)] if str(x) in self.mac_os_names_url else "10."+str(x) for x in reversed(range(self.min_macos, version+1))]) + url += ".merged-1.sucatalog" + ver_s = self.mac_os_names_url[str(version)] if str(version) in self.mac_os_names_url else "10."+str(version) + url = url.replace(ver_s, ver_s+self.catalog_suffix[catalog]) + return url + + def get_catalog_data(self, local = False): + # Gets the data based on our current_catalog + url = self.build_url(catalog=self.current_catalog, version=self.current_macos) + self.u.head("Downloading Catalog") + print("") + print("Currently downloading {} catalog from\n\n{}\n".format(self.current_catalog, url)) + try: + b = self.d.get_bytes(url) + self.catalog_data = plist.loads(b) + # Assume it's valid data - dump it to a local file + if local: + cwd = os.getcwd() + os.chdir(os.path.dirname(os.path.realpath(__file__))) + with open(os.path.join(os.getcwd(), self.scripts, self.plist), "wb") as f: + plist.dump(self.catalog_data, f) + os.chdir(cwd) + except: + if local: + # Check if we have one locally in our scripts directory + if not os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)), self.scripts, self.plist)): + return False + # It does - try to load it + try: + cwd = os.getcwd() + os.chdir(os.path.dirname(os.path.realpath(__file__))) + with open(os.path.join(os.getcwd(), self.scripts, self.plist), "rb") as f: + self.catalog_data = plist.load(f) + os.chdir(cwd) + except: + return False + return True + + def get_installers(self, plist_dict = None): + if not plist_dict: + plist_dict = self.catalog_data + if not plist_dict: + return [] + mac_prods = [] + for p in plist_dict.get("Products", {}): + if not self.find_recovery: + if plist_dict.get("Products",{}).get(p,{}).get("ExtendedMetaInfo",{}).get("InstallAssistantPackageIdentifiers",{}).get("OSInstall",{}) == "com.apple.mpkg.OSInstall": + mac_prods.append(p) + else: + # Find out if we have any of the recovert_suffixes + if any(x for x in plist_dict.get("Products",{}).get(p,{}).get("Packages",[]) if x["URL"].endswith(self.recovery_suffixes)): + mac_prods.append(p) + return mac_prods + + def get_dict_for_prods(self, prods, plist_dict = None): + if plist_dict==self.catalog_data==None: + plist_dict = {} + else: + plist_dict = self.catalog_data if plist_dict == None else plist_dict + + prod_list = [] + for prod in prods: + # Grab the ServerMetadataURL for the passed product key if it exists + prodd = {"product":prod} + try: + b = self.d.get_bytes(plist_dict.get("Products",{}).get(prod,{}).get("ServerMetadataURL",""), False) + smd = plist.loads(b) + except: + smd = {} + # Populate some info! + prodd["date"] = plist_dict.get("Products",{}).get(prod,{}).get("PostDate","") + prodd["installer"] = False + if plist_dict.get("Products",{}).get(prod,{}).get("ExtendedMetaInfo",{}).get("InstallAssistantPackageIdentifiers",{}).get("OSInstall",{}) == "com.apple.mpkg.OSInstall": + prodd["installer"] = True + prodd["time"] = time.mktime(prodd["date"].timetuple()) + prodd["date"].microsecond / 1E6 + prodd["title"] = smd.get("localization",{}).get("English",{}).get("title","Unknown") + prodd["version"] = smd.get("CFBundleShortVersionString","Unknown") + if prodd["version"] == " ": + prodd["version"] = "" + # Try to get the description too + try: + desc = smd.get("localization",{}).get("English",{}).get("description","").decode("utf-8") + desctext = desc.split('"p1">')[1].split("")[0] + except: + desctext = None + prodd["description"] = desctext + # Iterate the available packages and save their urls and sizes + if self.find_recovery: + # Only get the recovery packages + prodd["packages"] = [x for x in plist_dict.get("Products",{}).get(prod,{}).get("Packages",[]) if x["URL"].endswith(self.recovery_suffixes)] + else: + # Add them all! + prodd["packages"] = plist_dict.get("Products",{}).get(prod,{}).get("Packages",[]) + prod_list.append(prodd) + # Sort by newest + prod_list = sorted(prod_list, key=lambda x:x["time"], reverse=True) + return prod_list + + def download_prod(self, prod, dmg = False): + # Takes a dictonary of details and downloads it + self.resize() + cwd = os.getcwd() + os.chdir(os.path.dirname(os.path.realpath(__file__))) + name = "{} - {} {}".format(prod["product"], prod["version"], prod["title"]) + if os.path.exists(os.path.join(os.getcwd(), self.saves, self.current_catalog, name)): + while True: + self.u.head("Already Exists") + print("") + print("It looks like you've already downloaded {}".format(name)) + print("") + menu = self.u.grab("Redownload? (y/n): ") + if not len(menu): + continue + if menu[0].lower() == "n": + return + if menu[0].lower() == "y": + break + # Remove the old copy, then re-download + shutil.rmtree(os.path.join(os.getcwd(), self.saves, self.current_catalog, name)) + # Make it new + os.makedirs(os.path.join(os.getcwd(), self.saves, self.current_catalog, name)) + dl_list = [] + for x in prod["packages"]: + if not x.get("URL",None): + continue + if dmg and not x.get("URL","").lower().endswith(".dmg"): + continue + # add it to the list + dl_list.append(x["URL"]) + if not len(dl_list): + self.u.head("Error") + print("") + print("There were no files to download") + print("") + self.u.grab("Press [enter] to return...") + return + c = 0 + done = [] + for x in dl_list: + c += 1 + self.u.head("Downloading File {} of {}".format(c, len(dl_list))) + print("") + if len(done): + print("\n".join(["{} --> {}".format(y["name"], "Succeeded" if y["status"] else "Failed") for y in done])) + print("") + if dmg: + print("NOTE: Only Downloading DMG Files") + print("") + print("Downloading {} for {}...".format(os.path.basename(x), name)) + print("") + try: + self.d.stream_to_file(x, os.path.join(os.getcwd(), self.saves, self.current_catalog, name, os.path.basename(x))) + done.append({"name":os.path.basename(x), "status":True}) + except: + done.append({"name":os.path.basename(x), "status":False}) + succeeded = [x for x in done if x["status"]] + failed = [x for x in done if not x["status"]] + self.u.head("Downloaded {} of {}".format(len(succeeded), len(dl_list))) + print("") + print("Succeeded:") + if len(succeeded): + for x in succeeded: + print(" {}".format(x["name"])) + else: + print(" None") + print("") + print("Failed:") + if len(failed): + for x in failed: + print(" {}".format(x["name"])) + else: + print(" None") + print("") + print("Files saved to:") + print(" {}".format(os.path.join(os.getcwd(), self.saves, self.current_catalog, name))) + print("") + self.u.grab("Press [enter] to return...") + + def show_catalog_url(self): + self.resize() + self.u.head() + print("") + print("Current Catalog: {}".format(self.current_catalog)) + print("Max macOS Version: 10.{}".format(self.current_macos)) + print("") + print("{}".format(self.build_url())) + print("") + menu = self.u.grab("Press [enter] to return...") + return + + def main(self, dmg = False): + self.u.head() + print("") + print("Available Products:") + print("") + num = 0 + w = 0 + if not len(self.mac_prods): + print("No installers in catalog!") + print("") + exit() + for p in self.mac_prods: + num += 1 + var1 = "{}. {} {}".format(num, p["title"], p["version"]) + var2 = " - {} - Added {}".format(p["product"], p["date"]) + if self.find_recovery and p["installer"]: + # Show that it's a full installer + var2 += " - FULL Install" + w = len(var1) if len(var1) > w else w + w = len(var2) if len(var2) > w else w + print(var1) + print(var2) + print("") + print("R. Toggle Recovery-Only (Currently {})".format("On" if self.find_recovery else "Off")) + print("U. Show Catalog URL") + print("Q. Quit") + self.resize(w, (num*2)+11) + if os.name=="nt": + # Formatting differences.. + print("") + menu = self.u.grab("Please select an option: ") + if not len(menu): + return + if menu[0].lower() == "q": + self.resize() + self.u.custom_quit() + elif menu[0].lower() == "u": + self.show_catalog_url() + return + elif menu[0].lower() == "r": + self.find_recovery ^= True + self.u.head("Parsing Data") + print("") + print("Re-scanning products after recovery preference toggled...") + self.mac_prods = self.get_dict_for_prods(self.get_installers()) + return + + # Assume we picked something + try: + menu = int(menu) + except: + return + if menu < 1 or menu > len(self.mac_prods): + return + self.download_prod(self.mac_prods[menu-1], dmg) + + def get_latest(self, dmg = False): + self.u.head("Downloading Latest") + print("") + self.download_prod(self.mac_prods[-1], dmg) + + def get_for_product(self, prod, dmg = False): + self.u.head("Downloading for {}".format(prod)) + print("") + for p in self.mac_prods: + if p["product"] == prod: + self.download_prod(p, dmg) + return + print("{} not found".format(prod)) + + def get_for_version(self, vers, dmg = False): + self.u.head("Downloading for {}".format(vers)) + print("") + for p in self.mac_prods: + if p["version"] == vers: + self.download_prod(p, dmg) + return + print("10.{} not found".format(vers)) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-l", "--latest", help="downloads the version avaialble in the current catalog (overrides --version and --product)", action="store_true") + parser.add_argument("-r", "--recovery", help="looks for RecoveryHDUpdate.pkg and RecoveryHDMetaDmg.pkg in lieu of com.apple.mpkg.OSInstall (overrides --dmg)", action="store_true") + parser.add_argument("-d", "--dmg", help="downloads only the .dmg files", action="store_true") + parser.add_argument("-c", "--catalog", help="sets the CATALOG to use - publicrelease, public, customer, developer") + parser.add_argument("-p", "--product", help="sets the product id to search for (overrides --version)") + parser.add_argument("-v", "--version", help="sets the version of macOS to target - eg 10.14") + parser.add_argument("-m", "--maxos", help="sets the max macOS version to consider when building the url - eg 10.14") + args = parser.parse_args() + + g = gibMacOS() + if args.recovery: + args.dmg = False + g.find_recovery = args.recovery + + if args.maxos: + try: + m = int(str(args.maxos).replace("10.","")) + g.current_macos = m + except: + pass + if args.catalog: + # Set the catalog + g.set_catalog(args.catalog) + + # Done setting up pre-requisites + g.set_prods() + + if args.latest: + g.get_latest(args.dmg) + exit() + if args.product != None: + g.get_for_product(args.product, args.dmg) + exit() + if args.version != None: + g.get_for_version(args.version, args.dmg) + exit() + + while True: + g.main(args.dmg)