gibMacOS/MakeInstall.py

1010 lines
47 KiB
Python
Executable File

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()