Radically revamp disk/partition selection process

Co-authored-by: Michael Eckhoff <mreckhof@gmail.com>
This commit is contained in:
Dhinak G
2021-01-31 13:22:37 -05:00
parent 5bfa2afc38
commit 8917368988
4 changed files with 147 additions and 73 deletions

View File

@@ -1,4 +1,4 @@
[flake8]
ignore = E501
per-file-ignores =
Resources/Versions.py:E704
Resources/Constants.py:E704

View File

@@ -4,7 +4,7 @@ from __future__ import print_function
import subprocess
from Resources import build, ModelArray, Versions, utilities
from Resources import build, ModelArray, Constants, utilities
PATCHER_VERSION = "0.0.9"
@@ -26,19 +26,7 @@ class OpenCoreLegacyPatcher():
build.OpenCoreMenus(self.constants).build_opencore_menu(self.custom_model or self.current_model)
def install_opencore(self):
utilities.cls()
utilities.header(["Installing OpenCore to Drive"])
if self.constants.opencore_release_folder.exists():
print("\nFound OpenCore in Build Folder")
build.BuildOpenCore(self.custom_model or self.current_model, self.constants).copy_efi()
input("Press [Enter] to go back.")
else:
utilities.TUIOnlyPrint(["Installing OpenCore to Drive"],
"Press [Enter] to go back.\n",
["""OpenCore folder missing!
Please build OpenCore first!"""]).start()
build.BuildOpenCore(self.custom_model or self.current_model, self.constants).copy_efi()
def change_model(self):
utilities.cls()

View File

@@ -11,10 +11,18 @@ import uuid
import zipfile
from pathlib import Path
from Resources import ModelArray, Versions, utilities
from Resources import Constants, ModelArray, utilities
class BuildOpenCore():
def human_fmt(num):
for unit in ["B", "KB", "MB", "GB", "TB", "PB"]:
if abs(num) < 1000.0:
return "%3.1f %s" % (num, unit)
num /= 1000.0
return "%.1f %s" % (num, "EB")
class BuildOpenCore:
def __init__(self, model, versions):
self.model = model
self.config = None
@@ -59,7 +67,7 @@ class BuildOpenCore():
("MarvelYukonEthernet.kext", self.constants.marvel_version, self.constants.marvel_path, lambda: self.model in ModelArray.EthernetMarvell),
("CatalinaBCM5701Ethernet.kext", self.constants.bcm570_version, self.constants.bcm570_path, lambda: self.model in ModelArray.EthernetBroadcom),
# Legacy audio
("VoodooHDA.kext", self.constants.voodoohda_version, self.constants.voodoohda_path, lambda: self.model in ModelArray.LegacyAudio)
("VoodooHDA.kext", self.constants.voodoohda_version, self.constants.voodoohda_path, lambda: self.model in ModelArray.LegacyAudio),
]:
self.enable_kext(name, version, path, check)
@@ -88,10 +96,7 @@ class BuildOpenCore():
# Assumes we have a laptop with Intel chipset
property_path = "PciRoot(0x0)/Pci(0x1C,0x1)/Pci(0x0,0x0)"
print("- Applying fake ID for WiFi")
self.config["DeviceProperties"]["Add"][property_path] = {
"device-id": binascii.unhexlify("ba430000"),
"compatible": "pci14e4,43ba"
}
self.config["DeviceProperties"]["Add"][property_path] = {"device-id": binascii.unhexlify("ba430000"), "compatible": "pci14e4,43ba"}
# HID patches
if self.model in ModelArray.LegacyHID:
@@ -155,7 +160,7 @@ class BuildOpenCore():
elif self.model in ModelArray.MacPro71:
print("- Spoofing to MacPro7,1")
spoofed_model = "MacPro7,1"
macserial_output = subprocess.run(([self.constants.macserial_path] + f"-g -m {spoofed_model} -n 1").split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
macserial_output = subprocess.run([self.constants.macserial_path] + f"-g -m {spoofed_model} -n 1".split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
macserial_output = macserial_output.stdout.decode().strip().split(" | ")
self.config["PlatformInfo"]["Generic"]["SystemProductName"] = spoofed_model
self.config["PlatformInfo"]["Generic"]["SystemSerialNumber"] = macserial_output[0]
@@ -215,33 +220,108 @@ class BuildOpenCore():
input("Press enter to go back")
def copy_efi(self):
diskutil = [subprocess.run("diskutil list".split(), stdout=subprocess.PIPE).stdout.decode().strip()]
menu = utilities.TUIMenu(["Select Disk"], "Please select the disk you want to install OpenCore to: ", in_between=diskutil, return_number_instead_of_direct_call=True, add_quit=False)
for disk in [i for i in Path("/dev").iterdir() if re.fullmatch("disk[0-9]+", i.stem)]:
menu.add_menu_option(disk.stem, key=disk.stem[4:])
disk_num = menu.start()
print(subprocess.run(("sudo diskutil mount disk" + disk_num).split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout)
utilities.cls()
utilities.header(["Installing OpenCore to Drive"])
if not self.constants.opencore_release_folder.exists():
utilities.TUIOnlyPrint(
["Installing OpenCore to Drive"],
"Press [Enter] to go back.\n",
[
"""OpenCore folder missing!
Please build OpenCore first!"""
],
).start()
return
print("\nDisk picker is loading...")
all_disks = {}
disks = plistlib.loads(subprocess.run("diskutil list -plist physical".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
for disk in disks["AllDisksAndPartitions"]:
disk_info = plistlib.loads(subprocess.run(f"diskutil info -plist {disk['DeviceIdentifier']}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
all_disks[disk["DeviceIdentifier"]] = {"identifier": disk_info['DeviceNode'], "name": disk_info['MediaName'], "size": disk_info['Size'], "partitions": {}}
for partition in disk["Partitions"]:
partition_info = plistlib.loads(subprocess.run(f"diskutil info -plist {partition['DeviceIdentifier']}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
all_disks[disk["DeviceIdentifier"]]["partitions"][partition["DeviceIdentifier"]] = {
"fs": partition_info.get("FilesystemType", partition_info["Content"]),
"type": partition_info["Content"],
"name": partition_info.get("VolumeName", ""),
"size": partition_info["Size"],
}
# TODO: Advanced mode
menu = utilities.TUIMenu(["Select Disk"], "Please select the disk you would like to install OpenCore to: ", in_between=["Missing disks? Ensure they have an EFI or FAT32 partition."], return_number_instead_of_direct_call=True, loop=True)
for disk in all_disks:
if not any(all_disks[disk]["partitions"][partition]["fs"] == "msdos" for partition in all_disks[disk]["partitions"]):
continue
menu.add_menu_option(f"{disk}: {all_disks[disk]['name']} ({human_fmt(all_disks[disk]['size'])})", key=disk[4:])
response = menu.start()
if response == -1:
return
disk_identifier = "disk" + response
selected_disk = all_disks[disk_identifier]
menu = utilities.TUIMenu(["Select Partition"], "Please select the partition you would like to install OpenCore to: ", return_number_instead_of_direct_call=True, loop=True, in_between=["Missing partitions? Ensure they are formatted as an EFI or FAT32.", "", "* denotes likely candidate."])
for partition in selected_disk["partitions"]:
if selected_disk["partitions"][partition]["fs"] != "msdos":
continue
text = f"{partition}: {selected_disk['partitions'][partition]['name']} ({human_fmt(selected_disk['partitions'][partition]['size'])})"
if selected_disk["partitions"][partition]["type"] == "EFI" or (selected_disk["partitions"][partition]["type"] == "Microsoft Basic Data" and selected_disk["partitions"][partition]["size"] < 1024 * 1024 * 512): # 512 megabytes:
text += " *"
menu.add_menu_option(text, key=partition[len(disk_identifier) + 1:])
response = menu.start()
if response == -1:
return
args = [
"osascript",
"-e",
f'''do shell script "diskutil mount {disk_identifier}s{response}"'''
' with prompt "OpenCore Legacy Patcher needs administrator privileges to mount your EFI."'
" with administrator privileges"
" without altering line endings"
]
result = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode != 0:
if "execution error" in result.stderr.decode() and result.stderr.decode()[-5:-1] == "-128":
# cancelled prompt
return
else:
utilities.TUIOnlyPrint(["Copying OpenCore"], "Press [Enter] to continue", ["An error occurred!"] + result.stderr.decode().split("\n") + ["", "Please report this to the devs at GitHub."]).start()
return
# TODO: Remount if readonly
partition_info = plistlib.loads(subprocess.run(f"diskutil info -plist {disk_identifier}s{response}".split(), stdout=subprocess.PIPE).stdout.decode().strip().encode())
mount_path = Path(partition_info["MountPoint"])
utilities.cls()
utilities.header(["Copying OpenCore"])
efi_dir = Path("/Volumes/EFI")
if efi_dir.exists():
if mount_path.exists():
print("- Coping OpenCore onto EFI partition")
if (efi_dir / Path("EFI")).exists():
if (mount_path / Path("EFI")).exists():
print("Removing preexisting EFI folder")
shutil.rmtree(efi_dir / Path("EFI"))
if Path(self.constants.opencore_release_folder).exists():
shutil.copytree(self.constants.opencore_release_folder, efi_dir)
shutil.copy(self.constants.icon_path, efi_dir)
print("OpenCore transfer complete")
print("")
shutil.rmtree(mount_path / Path("EFI"))
shutil.copytree(self.constants.opencore_release_folder, mount_path / Path("EFI"))
shutil.copy(self.constants.icon_path, mount_path)
print("OpenCore transfer complete")
print("\nPress [Enter] to continue")
input()
else:
print("Couldn't find EFI partition")
print("Please ensure your drive is formatted as GUID Partition Table")
print("")
utilities.TUIOnlyPrint(["Copying OpenCore"], "Press [Enter] to continue", ["EFI failed to mount!", "Please report this to the devs at GitHub."]).start()
class OpenCoreMenus():
class OpenCoreMenus:
def __init__(self, versions):
self.constants: Constants.Constants = versions
@@ -264,10 +344,7 @@ class OpenCoreMenus():
def build_opencore_menu(self, model):
response = None
while not (response and response == -1):
title = [
f"Build OpenCore v{self.constants.opencore_version} EFI",
"Selected Model: " + model
]
title = [f"Build OpenCore v{self.constants.opencore_version} EFI", "Selected Model: " + model]
menu = utilities.TUIMenu(title, "Please select an option: ", auto_number=True)
options = [

View File

@@ -46,7 +46,7 @@ def cls():
class TUIMenu():
def __init__(self, title, prompt, options=None, return_number_instead_of_direct_call=False, add_quit=True, auto_number=False, in_between=None, top_level=False):
def __init__(self, title, prompt, options=None, return_number_instead_of_direct_call=False, add_quit=True, auto_number=False, in_between=None, top_level=False, loop=False):
self.title = title
self.prompt = prompt
self.in_between = in_between or []
@@ -55,44 +55,53 @@ class TUIMenu():
self.auto_number = auto_number
self.add_quit = add_quit
self.top_level = top_level
self.loop = loop
self.added_quit = False
def add_menu_option(self, name, description="", function=None, key=""):
self.options.append([key, name, description, function])
def start(self):
return_option = ["Q", "Quit"] if self.top_level else ["B", "Back"]
if self.add_quit:
if self.add_quit and not self.added_quit:
self.add_menu_option(
return_option[1], function=None, key=return_option[0])
self.added_quit = True
cls()
header(self.title)
print()
for i in self.in_between:
print(i)
if self.in_between:
while True:
cls()
header(self.title)
print()
for index, option in enumerate(self.options):
if self.auto_number and not (index == (len(self.options) - 1) and self.add_quit):
option[0] = str((index + 1))
print(option[0] + ". " + option[1])
for i in option[2]:
print("\t" + i)
for i in self.in_between:
print(i)
if self.in_between:
print()
print()
selected = input(self.prompt)
for index, option in enumerate(self.options):
if self.auto_number and not (index == (len(self.options) - 1) and self.add_quit):
option[0] = str((index + 1))
print(option[0] + ". " + option[1])
for i in option[2]:
print("\t" + i)
keys = [option[0].upper() for option in self.options]
if not selected or selected.upper() not in keys:
return
if self.add_quit and selected.upper() == return_option[0]:
return -1
elif self.return_number_instead_of_direct_call:
return self.options[keys.index(selected.upper())][0]
else:
self.options[keys.index(selected.upper())][3]() if self.options[keys.index(selected.upper())][3] else None
print()
selected = input(self.prompt)
keys = [option[0].upper() for option in self.options]
if not selected or selected.upper() not in keys:
if self.loop:
continue
else:
return
if self.add_quit and selected.upper() == return_option[0]:
return -1
elif self.return_number_instead_of_direct_call:
return self.options[keys.index(selected.upper())][0]
else:
self.options[keys.index(selected.upper())][3]() if self.options[keys.index(selected.upper())][3] else None
if not self.loop:
return
class TUIOnlyPrint():
@@ -111,4 +120,4 @@ class TUIOnlyPrint():
if self.in_between:
print()
input(self.prompt)
return input(self.prompt)