from Scripts import utils, diskwin, downloader, run import os, sys, tempfile, shutil, zipfile, platform, json, time class WinUSB: def __init__(self): self.u = utils.Utils("MakeInstall") if not self.u.check_admin(): # Try to self-elevate self.u.elevate(os.path.realpath(__file__)) exit() self.min_plat = 9600 # Make sure we're on windows self.verify_os() # Setup initial vars self.d = diskwin.Disk() 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_url = "https://github.com/corpnewt/gibMacOS/files/4573241/ddrelease64.exe.zip" # Rehost due to download issues self.dd_name = ".".join(os.path.basename(self.dd_url).split(".")[:-1]) # Get the name without the last extension self.z_json = "https://sourceforge.net/projects/sevenzip/best_release.json" self.z_url2 = "https://www.7-zip.org/a/7z1806-x64.msi" self.z_url = "https://www.7-zip.org/a/7z[[vers]]-x64.msi" self.z_name = "7z.exe" self.bi_url = "https://raw.githubusercontent.com/corpnewt/gibMacOS/master/Scripts/BOOTICEx64.exe" self.bi_name = "BOOTICEx64.exe" self.clover_url = "https://api.github.com/repos/CloverHackyColor/CloverBootloader/releases" self.dids_url = "https://api.github.com/repos/dids/clover-builder/releases" self.oc_url = "https://api.github.com/repos/acidanthera/OpenCorePkg/releases" self.oc_boot = "boot" self.oc_boot_alt = "bootX64" self.oc_boot0 = "boot0" self.oc_boot1 = "boot1f32" # self.oc_boot_url = "https://github.com/acidanthera/OpenCorePkg/raw/master/Utilities/LegacyBoot/" self.oc_boot_url = "https://github.com/acidanthera/OpenCorePkg/raw/870017d0e5d53abeaf0347997da912c3e382a04a/Utilities/LegacyBoot/" self.diskpart = os.path.join(os.environ['SYSTEMDRIVE'] + "\\", "Windows", "System32", "diskpart.exe") # From Tim Sutton's brigadier: https://github.com/timsutton/brigadier/blob/master/brigadier self.z_path = None self.z_path64 = os.path.join(os.environ['SYSTEMDRIVE'] + "\\", "Program Files", "7-Zip", "7z.exe") self.z_path32 = os.path.join(os.environ['SYSTEMDRIVE'] + "\\", "Program Files (x86)", "7-Zip", "7z.exe") self.recovery_suffixes = ( "recoveryhdupdate.pkg", "recoveryhdmetadmg.pkg", "basesystem.dmg", "recoveryimage.dmg" ) self.dd_bootsector = True self.boot0 = "boot0af" self.boot1 = "boot1f32alt" self.boot = "boot6" self.efi_id = "c12a7328-f81f-11d2-ba4b-00a0c93ec93b" # EFI self.bas_id = "ebd0a0a2-b9e5-4433-87c0-68b6b72699c7" # Microsoft Basic Data self.hfs_id = "48465300-0000-11AA-AA11-00306543ECAC" # HFS+ self.rec_id = "426F6F74-0000-11AA-AA11-00306543ECAC" # Apple Boot partition (Recovery HD) self.show_all_disks = False def verify_os(self): self.u.head("Verifying OS") print("") print("Verifying OS name...") if not os.name=="nt": print("") print("This script is only for Windows!") print("") self.u.grab("Press [enter] to exit...") exit(1) print(" - Name = NT") print("Verifying OS version...") # Verify we're at version 9600 or greater try: # Set plat to the last item of the output split by . - looks like: # Windows-8.1-6.3.9600 # or this: # Windows-10-10.0.17134-SP0 plat = int(platform.platform().split(".")[-1].split("-")[0]) except: plat = 0 if plat < self.min_plat: print("") print("Currently running {}, this script requires version {} or newer.".format(platform.platform(), self.min_plat)) print("") self.u.grab("Press [enter] to exit...") exit(1) print(" - Version = {}".format(plat)) print("") print("{} >= {}, continuing...".format(plat, self.min_plat)) 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 ddrelease64.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)) temp = tempfile.mkdtemp() z_file = os.path.basename(self.dd_url) # Now we need to download self.dl.stream_to_file(self.dd_url, os.path.join(temp,z_file)) print(" - Extracting...") # Extract with built-in tools \o/ cwd = os.getcwd() os.chdir(temp) with zipfile.ZipFile(os.path.join(temp,z_file)) as z: z.extractall(temp) for x in os.listdir(temp): if self.dd_name.lower() == x.lower(): # Found it print(" - Found {}".format(x)) print(" - Copying to {} directory...".format(self.scripts)) shutil.copy(os.path.join(temp,x), os.path.join(self.s_path,x)) # Return to prior cwd os.chdir(cwd) # Remove the temp folder shutil.rmtree(temp,ignore_errors=True) print("") return os.path.exists(os.path.join(self.s_path, self.dd_name)) def check_7z(self): # Check the PATH var first z_path = self.r.run({"args":["where.exe","7z.exe"]})[0].split("\n")[0].rstrip("\r") self.z_path = next((x for x in (z_path,self.z_path64,self.z_path32) if x and os.path.isfile(x)),None) if self.z_path: return True print("Didn't locate {} - downloading...".format(self.z_name)) # Didn't find it - let's do some stupid stuff # First we get our json response - or rather, try to, then parse it # looking for the current version dl_url = None try: json_data = json.loads(self.dl.get_string(self.z_json)) v_num = json_data.get("release",{}).get("filename","").split("/")[-1].lower().split("-")[0].replace("7z","").replace(".exe","") if len(v_num): dl_url = self.z_url.replace("[[vers]]",v_num) except: pass if not dl_url: dl_url = self.z_url2 temp = tempfile.mkdtemp() dl_file = self.dl.stream_to_file(dl_url, os.path.join(temp, self.z_name)) if not dl_file: # Didn't download right shutil.rmtree(temp,ignore_errors=True) return False print("") print("Installing 7zip...") # From Tim Sutton's brigadier: https://github.com/timsutton/brigadier/blob/master/brigadier out = self.r.run({"args":["msiexec", "/qn", "/i", os.path.join(temp, self.z_name)],"stream":True}) if out[2] != 0: shutil.rmtree(temp,ignore_errors=True) print("Error ({})".format(out[2])) print("") self.u.grab("Press [enter] to exit...") exit(1) print("") self.z_path = self.z_path64 if os.path.exists(self.z_path64) else self.z_path32 if os.path.exists(self.z_path32) else None return self.z_path and os.path.exists(self.z_path) def check_bi(self): # Checks for BOOTICEx64.exe in our scripts dir # and downloads it if need be if os.path.exists(os.path.join(self.s_path, self.bi_name)): # print("Located {}!".format(self.bi_name)) # Got it return True print("Couldn't locate {} - downloading...".format(self.bi_name)) self.dl.stream_to_file(self.bi_url, os.path.join(self.s_path, self.bi_name)) print("") return os.path.exists(os.path.join(self.s_path,self.bi_name)) def get_dl_url_from_json(self,json_data,suffix=(".lzma",".iso.7z")): try: j_list = json.loads(json_data) except: return None j_list = j_list if isinstance(j_list,list) else [j_list] for j in j_list: dl_link = next((x.get("browser_download_url", None) for x in j.get("assets", []) if x.get("browser_download_url", "").endswith(suffix)), None) if dl_link: break if not dl_link: return None return { "url" : dl_link, "name" : os.path.basename(dl_link), "info" : j.get("body", None) } def get_dl_info(self,clover_version=None): # Returns the latest download package and info in a # dictionary: { "url" : dl_url, "name" : name, "info" : update_info } # Attempt Dids' repo first - falling back on Clover's official repo as needed clover_urls = (self.clover_url,self.dids_url) try: assert int(clover_version) <= 5122 # Check if we're trying to get r5122 or prior # If we didn't throw an exception, we can reverse the order of the URLs to check # Dids' builder first clover_urls = (self.dids_url,self.clover_url) except: pass # Wasn't a proper int, or was above 5122 for url in clover_urls: # Tag is e.g. 5098 on Slice's repo, and e.g. v2.5k_r5098 or v5.0_r5xxx on Dids' # accommodate as needed if not clover_version: search_url = url elif url == self.clover_url: # Using CloverHackyColor's repo - set the tag to the version search_url = "{}/tags/{}".format(url,clover_version) else: # Using Dids' clover builder - figure out the prefix based on the version search_url = "{}/tags/v{}_r{}".format(url,"5.0" if clover_version >= "5118" else "2.5k",clover_version) print(" - Checking {}".format(search_url)) json_data = self.dl.get_string(search_url, False) if not json_data: print(" --> Not found!") else: return self.get_dl_url_from_json(json_data) return None def get_oc_dl_info(self): json_data = self.dl.get_string(self.oc_url, False) if not json_data: print(" --> Not found!") else: return self.get_dl_url_from_json(json_data,suffix="-RELEASE.zip") def diskpart_flag(self, disk, as_efi=False): # Sets and unsets the GUID needed for a GPT EFI partition ID self.u.head("Changing ID With DiskPart") print("") print("Setting type as {}...".format("EFI" if as_efi else "Basic Data")) print("") # - EFI system partition: c12a7328-f81f-11d2-ba4b-00a0c93ec93b # - Basic data partition: ebd0a0a2-b9e5-4433-87c0-68b6b72699c7 dp_script = "\n".join([ "select disk {}".format(disk.get("index",-1)), "sel part 1", "set id={}".format(self.efi_id if as_efi else self.bas_id) ]) 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":[self.diskpart,"/s",script],"stream":True}) # Ditch our script regardless of whether diskpart worked or not shutil.rmtree(temp) print("") if out[2] != 0: # Error city! print("DiskPart exited with non-zero status ({}). Aborting.".format(out[2])) else: print("Done - You may need to replug your drive for the") print("changes to take effect.") print("") self.u.grab("Press [enter] to return...") def diskpart_erase(self, disk, gpt=False, clover_version = None, local_file = None): # Generate a script that we can pipe to diskpart to erase our disk self.u.head("Erasing With DiskPart") print("") # Then we'll re-gather our disk info on success and move forward # Using MBR to effectively set the individual partition types # Keeps us from having issues mounting the EFI on Windows - # and also lets us explicitly set the partition id for the main # data partition. if not gpt: print("Using MBR...") dp_script = "\n".join([ "select disk {}".format(disk.get("index",-1)), "clean", "convert mbr", "create partition primary size=200", "format quick fs=fat32 label='BOOT'", "active", "create partition primary", "select part 2", "set id=AB", # AF = HFS, AB = Recovery "select part 1", "assign" ]) else: print("Using GPT...") dp_script = "\n".join([ "select disk {}".format(disk.get("index",-1)), "clean", "convert gpt", "create partition primary size=200", "format quick fs=fat32 label='BOOT'", "create partition primary id={}".format(self.hfs_id) ]) 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":[self.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, clover_version, local_file=local_file) def select_package(self, disk, clover_version = None, local_file = None): self.u.head("Select Recovery Package") print("") print("{}. {} - {} ({})".format( disk.get("index",-1), disk.get("model","Unknown"), self.dl.get_size(disk.get("size",-1),strip_zeroes=True), ["Unknown","No Root Dir","Removable","Local","Network","Disc","RAM Disk"][disk.get("type",0)] )) print("") print("M. Main Menu") print("Q. Quit") print("") print("(To copy a file's path, shift + right-click in Explorer and select 'Copy as path')\n") menu = self.u.grab("Please paste the recovery update pkg/dmg 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, clover_version, local_file=local_file) return # Got the package - let's make sure it's named right - just in case if os.path.basename(path).lower().endswith(".hfs"): # We have an hfs image already - bypass extraction self.dd_image(disk, path, clover_version, local_file=local_file) 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, clover_version, local_file=local_file) return self.u.head("Extracting Package") print("") temp = tempfile.mkdtemp() cwd = os.getcwd() os.chdir(temp) print("Located {}...".format(os.path.basename(path))) if not path.lower().endswith(".dmg"): # Extract in sections and remove any files we run into print("Extracting Recovery dmg...") out = self.r.run({"args":[self.z_path, "e", "-txar", path, "*.dmg"]}) 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 print("Extracting BaseSystem.dmg...") # No files to delete here - let's extract the next part out = self.r.run({"args":[self.z_path, "e", "*.dmg", "*/Base*.dmg"]}) 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)) # Onto the last command print("Extracting hfs...") out = self.r.run({"args":[self.z_path, "e", "-tdmg", path if path.lower().endswith(".dmg") else "Base*.dmg", "*.hfs"]}) 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)) 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), clover_version, local_file=local_file) shutil.rmtree(temp,ignore_errors=True) def dd_image(self, disk, image, clover_version = None, local_file = None): # 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.dl.get_size(disk.get("size",-1),strip_zeroes=True), ["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 - dd, even when failing likes to give us a 0 # status code. It also sends a ton of text through stderr - so we comb # that for "Error" then split by that to skip the extra fluff and show only # the error. print("An error occurred:\n\n{}".format("Error"+out[1].split("Error")[1])) print("") self.u.grab("Press [enter] to return to the main menu...") return # Install Clover/OC to the target drive if clover_version == "OpenCore": self.install_oc(disk, local_file=local_file) else: self.install_clover(disk, clover_version, local_file=local_file) def install_oc(self, disk, local_file = None): self.u.head("Installing OpenCore") print("") print("Gathering info...") if not local_file: o = self.get_oc_dl_info() if o is None: print(" - Error communicating with github!") print("") self.u.grab("Press [enter] to return...") return print(" - Got {}".format(o.get("name","Unknown Version"))) print("Downloading...") temp = tempfile.mkdtemp() os.chdir(temp) self.dl.stream_to_file(o["url"], os.path.join(temp, o["name"])) else: print("Using local file: {}".format(local_file)) temp = tempfile.mkdtemp() os.chdir(temp) o = {"name":os.path.basename(local_file)} # Copy to the temp folder shutil.copy(local_file,os.path.join(temp,o["name"])) print("") # Empty space to clear the download progress if not os.path.exists(os.path.join(temp, o["name"])): shutil.rmtree(temp,ignore_errors=True) print(" - Download failed. Aborting...") print("") self.u.grab("Press [enter] to return...") return oc_zip = o["name"] # Got a valid file in our temp dir print("Extracting {}...".format(oc_zip)) out = self.r.run({"args":[self.z_path, "x", os.path.join(temp,oc_zip)]}) 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 # We need to also gather our boot, boot0af, and boot1f32 files print("Gathering DUET boot files...") uefi_only = False duet_loc = os.path.join(temp,"Utilities","LegacyBoot") for x in (self.oc_boot,self.oc_boot_alt,self.oc_boot0,self.oc_boot1): # Check the local dir first if os.path.exists(os.path.join(duet_loc,x)): print(" - {}".format(x)) # Copy it over target_name = self.oc_boot if x == self.oc_boot_alt else x shutil.copy(os.path.join(duet_loc,x), os.path.join(temp,target_name)) missing_list = [x for x in (self.oc_boot,self.oc_boot0,self.oc_boot1) if not os.path.exists(os.path.join(temp,x))] if missing_list: print(" - Missing: {}".format(", ".join(missing_list))) print("Attempting to download...") for x in missing_list: print(" - {}".format(x)) self.dl.stream_to_file(self.oc_boot_url + x, os.path.join(temp,x),False) if not all((os.path.exists(os.path.join(temp,x)) for x in missing_list)): print("Could not located all required DUET files - USB will be UEFI ONLY") uefi_only = True # At this point, we should have a boot0xx file and an EFI folder in the temp dir # We need to udpate the disk list though - to reflect the current file system on part 1 # of our current disk self.d.update() # assumes our disk number stays the same # Some users are having issues with the "partitions" key not populating - possibly a 3rd party disk management soft? # Possibly a bad USB? # We'll see if the key exists - if not, we'll throw an error. if self.d.disks[str(disk["index"])].get("partitions",None) is None: # No partitions found. shutil.rmtree(temp,ignore_errors=True) print("No partitions located on disk!") print("") self.u.grab("Press [enter] to return...") return part = self.d.disks[str(disk["index"])]["partitions"].get("0",{}).get("letter",None) # get the first partition's letter if part is None: shutil.rmtree(temp,ignore_errors=True) print("Lost original disk - or formatting failed!") print("") self.u.grab("Press [enter] to return...") return # Here we have our disk and partitions and such - the BOOT partition # will be the first partition # Let's copy over the EFI folder and then dd the boot0xx file print("Copying EFI folder to {}/EFI...".format(part)) source_efi = None if os.path.exists(os.path.join(temp,"EFI")): source_efi = os.path.join(temp,"EFI") elif os.path.exists(os.path.join(temp,"X64","EFI")): source_efi = os.path.join(temp,"X64","EFI") if not source_efi: print(" - Source EFI not found!") print("") self.u.grab("Press [enter] to return...") return if os.path.exists("{}/EFI".format(part)): print(" - EFI exists - removing...") shutil.rmtree("{}/EFI".format(part),ignore_errors=True) time.sleep(1) # Added because windows is dumb shutil.copytree(source_efi, "{}/EFI".format(part)) if not uefi_only: # Copy boot over to the root of the EFI volume print("Copying {} to {}/boot...".format(self.oc_boot,part)) shutil.copy(os.path.join(temp,self.oc_boot),"{}/boot".format(part)) # Use bootice to update the MBR and PBR - always on the first # partition (which is 0 in bootice) print("Updating the MBR with {}...".format(self.oc_boot0)) args = [ os.path.join(self.s_path,self.bi_name), "/device={}".format(disk.get("index",-1)), "/mbr", "/restore", "/file={}".format(os.path.join(temp,self.oc_boot0)), "/keep_dpt", "/quiet" ] out = self.r.run({"args":args}) if out[2] != 0: shutil.rmtree(temp,ignore_errors=True) print(" - An error occurred updating the MBR: {}".format(out[2])) print("") self.u.grab("Press [enter] to return...") return print("Updating the PBR with {}...".format(self.oc_boot1)) args = [ os.path.join(self.s_path,self.bi_name), "/device={}:0".format(disk.get("index",-1)), "/pbr", "/restore", "/file={}".format(os.path.join(temp,self.oc_boot1)), "/keep_bpb", "/quiet" ] out = self.r.run({"args":args}) if out[2] != 0: shutil.rmtree(temp,ignore_errors=True) print(" - An error occurred updating the PBR: {}".format(out[2])) print("") self.u.grab("Press [enter] to return...") return print("Cleaning up...") shutil.rmtree(temp,ignore_errors=True) print("") print("Done.") print("") self.u.grab("Press [enter] to return to the main menu...") def install_clover(self, disk, clover_version = None, local_file = None): self.u.head("Installing Clover - {}".format("Latest" if not clover_version else "r"+clover_version)) print("") print("Gathering info...") if not local_file: c = self.get_dl_info(clover_version) if c is None: if clover_version is None: print(" - Error communicating with github!") else: print(" - Error gathering info for Clover r{}".format(clover_version)) print("") self.u.grab("Press [enter] to return...") return print(" - Got {}".format(c.get("name","Unknown Version"))) print("Downloading...") temp = tempfile.mkdtemp() os.chdir(temp) self.dl.stream_to_file(c["url"], os.path.join(temp, c["name"])) else: print("Using local file: {}".format(local_file)) temp = tempfile.mkdtemp() os.chdir(temp) c = {"name":os.path.basename(local_file)} # Copy to the temp folder shutil.copy(local_file,os.path.join(temp,c["name"])) print("") # Empty space to clear the download progress if not os.path.exists(os.path.join(temp, c["name"])): shutil.rmtree(temp,ignore_errors=True) print(" - Download failed. Aborting...") print("") self.u.grab("Press [enter] to return...") return clover_archive = c["name"] # Got a valid file in our temp dir print("Extracting {}...".format(clover_archive)) out = self.r.run({"args":[self.z_path, "e", os.path.join(temp,clover_archive)]}) 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 # Should result in a .tar file clover_tar = next((x for x in os.listdir(temp) if x.lower().endswith(".tar")),None) if clover_tar: # Got a .tar archive - get the .iso print("Extracting {}...".format(clover_tar)) out = self.r.run({"args":[self.z_path, "e", os.path.join(temp,clover_tar)]}) 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 # Should result in a .iso file clover_iso = next((x for x in os.listdir(temp) if x.lower().endswith(".iso")),None) if not clover_iso: shutil.rmtree(temp,ignore_errors=True) print(" - No .iso found - aborting...") print("") self.u.grab("Press [enter] to return...") return # Got the .iso - let's extract the needed parts print("Extracting EFI from {}...".format(clover_iso)) out = self.r.run({"args":[self.z_path, "x", os.path.join(temp,clover_iso), "EFI*"]}) 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 print("Extracting {} from {}...".format(self.boot0,clover_iso)) out = self.r.run({"args":[self.z_path, "e", os.path.join(temp,clover_iso), self.boot0, "-r"]}) 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 print("Extracting {} from {}...".format(self.boot1,clover_iso)) out = self.r.run({"args":[self.z_path, "e", os.path.join(temp,clover_iso), self.boot1, "-r"]}) 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 print("Extracting {} from {}...".format(self.boot,clover_iso)) out = self.r.run({"args":[self.z_path, "e", os.path.join(temp,clover_iso), self.boot, "-r"]}) 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 # At this point, we should have a boot0xx file and an EFI folder in the temp dir # We need to udpate the disk list though - to reflect the current file system on part 1 # of our current disk self.d.update() # assumes our disk number stays the same # Some users are having issues with the "partitions" key not populating - possibly a 3rd party disk management soft? # Possibly a bad USB? # We'll see if the key exists - if not, we'll throw an error. if self.d.disks[str(disk["index"])].get("partitions",None) is None: # No partitions found. shutil.rmtree(temp,ignore_errors=True) print("No partitions located on disk!") print("") self.u.grab("Press [enter] to return...") return part = self.d.disks[str(disk["index"])]["partitions"].get("0",{}).get("letter",None) # get the first partition's letter if part is None: shutil.rmtree(temp,ignore_errors=True) print("Lost original disk - or formatting failed!") print("") self.u.grab("Press [enter] to return...") return # Here we have our disk and partitions and such - the CLOVER partition # will be the first partition # Let's copy over the EFI folder and then dd the boot0xx file print("Copying EFI folder to {}/EFI...".format(part)) if os.path.exists("{}/EFI".format(part)): print(" - EFI exists - removing...") shutil.rmtree("{}/EFI".format(part),ignore_errors=True) time.sleep(1) # Added because windows is dumb shutil.copytree(os.path.join(temp,"EFI"), "{}/EFI".format(part)) # Copy boot6 over to the root of the EFI volume - and rename it to boot print("Copying {} to {}/boot...".format(self.boot,part)) shutil.copy(os.path.join(temp,self.boot),"{}/boot".format(part)) # Use bootice to update the MBR and PBR - always on the first # partition (which is 0 in bootice) print("Updating the MBR with {}...".format(self.boot0)) args = [ os.path.join(self.s_path,self.bi_name), "/device={}".format(disk.get("index",-1)), "/mbr", "/restore", "/file={}".format(os.path.join(temp,self.boot0)), "/keep_dpt", "/quiet" ] out = self.r.run({"args":args}) if out[2] != 0: shutil.rmtree(temp,ignore_errors=True) print(" - An error occurred updating the MBR: {}".format(out[2])) print("") self.u.grab("Press [enter] to return...") return print("Updating the PBR with {}...".format(self.boot1)) args = [ os.path.join(self.s_path,self.bi_name), "/device={}:0".format(disk.get("index",-1)), "/pbr", "/restore", "/file={}".format(os.path.join(temp,self.boot1)), "/keep_bpb", "/quiet" ] out = self.r.run({"args":args}) if out[2] != 0: shutil.rmtree(temp,ignore_errors=True) print(" - An error occurred updating the PBR: {}".format(out[2])) print("") self.u.grab("Press [enter] to return...") return print("Cleaning up...") shutil.rmtree(temp,ignore_errors=True) print("") print("Done.") print("") self.u.grab("Press [enter] to return to the main menu...") def main(self): # Start out with our cd in the right spot. os.chdir(os.path.dirname(os.path.realpath(__file__))) # 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!\n".format(self.dd_name)) self.u.grab("Press [enter] to exit...") exit(1) if not self.check_7z(): print("Couldn't find or install {} - aborting!\n".format(self.z_name)) self.u.grab("Press [enter] to exit...") exit(1) if not self.check_bi(): print("Couldn't find or install {} - aborting!\n".format(self.bi_name)) self.u.grab("Press [enter] to exit...") 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) if not self.show_all_disks else self.d.disks # Types: 0 = Unknown, 1 = No Root Dir, 2 = Removable, 3 = Local, 4 = Network, 5 = Disc, 6 = RAM disk if self.show_all_disks: print("!WARNING! This list includes ALL disk types.") print("!WARNING! Be ABSOLUTELY sure before selecting") print("!WARNING! a disk!") else: 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,key=lambda x:int(x)): print("{}. {} - {} ({})".format( disk, rem_disks[disk].get("model","Unknown"), self.dl.get_size(rem_disks[disk].get("size",-1),strip_zeroes=True), ["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,key=lambda x:int(x)): 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.dl.get_size(parts[p].get("size",-1),strip_zeroes=True) )) print("") print("Q. Quit") print("") print("Usage: [drive number][options] r[Clover revision (optional), requires C]\n (eg. 1B C r5092)") print(" Options are as follows with precedence B > F > E > U > G:") print(" B = Only install the boot manager to the drive's first partition.") print(" F = Skip formatting the disk - will install the boot manager to the first") print(" partition, and dd the recovery image to the second.") print(" E = Sets the type of the drive's first partition to EFI.") print(" U = Similar to E, but sets the type to Basic Data (useful for editing).") print(" G = Format as GPT (default is MBR).") print(" C = Use Clover instead of OpenCore.") print(" L = Provide a local archive for the boot manager - must still use C if Clover.") print(" D = Used without a drive number, toggles showing all disks (currently {}).".format("ENABLED" if self.show_all_disks else "DISABLED")) print("") menu = self.u.grab("Please select a disk or press [enter] with no options to refresh: ") if not len(menu): self.main() return if menu.lower() == "q": self.u.custom_quit() if menu.lower() == "d": self.show_all_disks ^= True self.main() return only_boot = set_efi = unset_efi = use_gpt = user_provided = no_format = False local_file = None use_oc = True if "b" in menu.lower(): only_boot = True menu = menu.lower().replace("b","") if "c" in menu.lower(): use_oc = False menu = menu.lower().replace("c","") if "o" in menu.lower(): # Remove legacy "o" value menu = menu.lower().replace("o","") if "e" in menu.lower(): set_efi = True menu = menu.lower().replace("e","") if "u" in menu.lower(): unset_efi = True menu = menu.lower().replace("u","") if "g" in menu.lower(): use_gpt = True menu = menu.lower().replace("g","") if "l" in menu.lower(): user_provided = True menu = menu.lower().replace("l","") if "f" in menu.lower(): no_format = True menu = menu.lower().replace("f","") # Extract Clover version from args if found clover_list = [x for x in menu.split() if x.lower().startswith("r") and all(y in "0123456789" for y in x[1:])] menu = " ".join([x for x in menu.split() if not x in clover_list]) clover_version = None if not len(clover_list) else clover_list[0][1:] # Skip the "r" prefix # Prepare for OC if need be if use_oc: clover_version = "OpenCore" selected_disk = rem_disks.get(menu.strip(),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! if user_provided: # Prompt the user for the target archive while True: self.u.head("Local Archive") print("") if use_oc: print("NOTE: OpenCore archives are expected to be .zip!") else: print("NOTE: Clover archives are expected to be an ISO packed in either .tar.lzma or .7z!") print("") print("M. Return to the menu") print("Q. Quit") print("") print("(To copy a file's path, shift + right-click in Explorer and select 'Copy as path')\n") l = self.u.grab("Please {} archive path here: ".format("OpenCore" if use_oc else "Clover")) if not len(l): continue if l.lower() == "m": break elif l.lower() == "q": self.u.custom_quit() l_check = self.u.check_path(l) if not l_check or not l_check.lower().endswith(".zip" if use_oc else (".tar.lzma",".7z")): continue # Got a valid path that ends with the proper extension local_file = l_check break # Check if we got something if not local_file: self.main() return if only_boot: if use_oc: self.install_oc(selected_disk, local_file=local_file) else: self.install_clover(selected_disk, clover_version, local_file=local_file) elif no_format: # Make sure we warn the user that the second partition **NEEDS** to be a RAW # partition for dd to properly work while True: self.u.head("WARNING") print("") print("{}. {} - {} ({})".format( selected_disk.get("index",-1), selected_disk.get("model","Unknown"), self.dl.get_size(selected_disk.get("size",-1),strip_zeroes=True), ["Unknown","No Root Dir","Removable","Local","Network","Disc","RAM Disk"][selected_disk.get("type",0)] )) print("") print("In order to continue without formatting, the selected disk's first") print("partition MUST be FAT32, and the second MUST be RAW. If that is not") print("the case, the operation WILL fail.") print("") yn = self.u.grab("Continue? (y/n): ") if yn.lower() == "n": self.main() return if yn.lower() == "y": break self.select_package(selected_disk, clover_version, local_file=local_file) elif set_efi: self.diskpart_flag(selected_disk, True) elif unset_efi: self.diskpart_flag(selected_disk, False) else: # Check erase 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.dl.get_size(selected_disk.get("size",-1),strip_zeroes=True), ["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, use_gpt, clover_version, local_file=local_file) self.main() if __name__ == '__main__': w = WinUSB() w.main()