Esempio n. 1
0
    def make_plist(self, oc_acpi, cl_acpi, patches):
        # if not len(patches): return # No patches to add - bail
        repeat = False
        print("Building patches_OC and patches_Clover plists...")
        output = self.d.check_output(self.output)
        oc_plist = {}
        cl_plist = {}

        # Check for the plists
        if os.path.isfile(os.path.join(output,"patches_OC.plist")): 
            e = os.path.join(output,"patches_OC.plist")
            with open(e, "rb") as f:
                oc_plist = plist.load(f)
        if os.path.isfile(os.path.join(output,"patches_Clover.plist")): 
            e = os.path.join(output,"patches_Clover.plist")
            with open(e,"rb") as f:
                cl_plist = plist.load(f)
        
        # Ensure all the pathing is where it needs to be
        oc_plist = self.ensure_path(oc_plist,("ACPI","Add"))
        oc_plist = self.ensure_path(oc_plist,("ACPI","Patch"))
        cl_plist = self.ensure_path(cl_plist,("ACPI","SortedOrder"))
        cl_plist = self.ensure_path(cl_plist,("ACPI","DSDT","Patches"))

        # Add the .aml references
        if any(oc_acpi["Comment"] == x["Comment"] for x in oc_plist["ACPI"]["Add"]):
            print(" -> Add \"{}\" already in OC plist!".format(oc_acpi["Comment"]))
        else:
            oc_plist["ACPI"]["Add"].append(oc_acpi)
        if any(cl_acpi == x for x in cl_plist["ACPI"]["SortedOrder"]):
            print(" -> \"{}\" already in Clover plist!".format(cl_acpi))
        else:
            cl_plist["ACPI"]["SortedOrder"].append(cl_acpi)

        # Iterate the patches
        for p in patches:
            if any(x["Comment"] == p["Comment"] for x in oc_plist["ACPI"]["Patch"]):
                print(" -> Patch \"{}\" already in OC plist!".format(p["Comment"]))
            else:
                print(" -> Adding Patch \"{}\" to OC plist!".format(p["Comment"]))
                oc_plist["ACPI"]["Patch"].append(self.get_oc_patch(p))
            if any(x["Comment"] == p["Comment"] for x in cl_plist["ACPI"]["DSDT"]["Patches"]):
                print(" -> Patch \"{}\" already in Clover plist!".format(p["Comment"]))
            else:
                print(" -> Adding Patch \"{}\" to Clover plist!".format(p["Comment"]))
                cl_plist["ACPI"]["DSDT"]["Patches"].append(self.get_clover_patch(p))         
        # Write the plists
        with open(os.path.join(output,"patches_OC.plist"),"wb") as f:
            plist.dump(oc_plist,f)
        with open(os.path.join(output,"patches_Clover.plist"),"wb") as f:
            plist.dump(cl_plist,f)
Esempio n. 2
0
 def select_plist(self):
     while True:
         self.u.head("Select Plist")
         print("")
         print("M. Return To Menu")
         print("Q. Quit")
         print("")
         plist_path = self.u.grab(
             "Please drag and drop your config.plist here:  ")
         if not len(plist_path): continue
         elif plist_path.lower() == "m": return
         elif plist_path.lower() == "q": self.u.custom_quit()
         path_checked = self.u.check_path(plist_path)
         if not path_checked: continue
         # Got a valid path here - let's try to load it
         try:
             with open(path_checked, "rb") as f:
                 plist_data = plist.load(f)
             if not isinstance(plist_data, dict):
                 raise Exception("Plist root is not a dictionary")
         except Exception as e:
             self.u.head("Error Loading Plist")
             print("\nCould not load {}:\n\n{}\n\n".format(
                 path_checked, repr(e)))
             self.u.grab("Press [enter] to return...")
             continue
         # Got valid plist data - let's store the vars and return
         self.plist_path = path_checked
         self.plist_data = plist_data
         return (path_checked, plist_data)
Esempio n. 3
0
 def __init__(self):
     self.u = utils.Utils("OC Snapshot")
     self.snapshot_data = {}
     self.safe_path_length = 128  # OC_STORAGE_SAFE_PATH_MAX from Include/Acidanthera/Library/OcStorageLib.h in OpenCorePkg
     if os.path.exists("Scripts/snapshot.plist"):
         try:
             with open("Scripts/snapshot.plist", "rb") as f:
                 self.snapshot_data = plist.load(f)
         except:
             pass
Esempio n. 4
0
 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("")
     if local:
         print("Checking locally for {}".format(self.plist))
         cwd = os.getcwd()
         os.chdir(os.path.dirname(os.path.realpath(__file__)))
         if os.path.exists(
                 os.path.join(os.path.dirname(os.path.realpath(__file__)),
                              self.scripts, self.plist)):
             print(" - Found - loading...")
             try:
                 with open(
                         os.path.join(os.getcwd(), self.scripts,
                                      self.plist), "rb") as f:
                     self.catalog_data = plist.load(f)
                 os.chdir(cwd)
                 return True
             except:
                 print(" - Error loading - downloading instead...\n")
                 os.chdir(cwd)
         else:
             print(" - Not found - downloading instead...\n")
     print("Currently downloading {} catalog from\n\n{}\n".format(
         self.current_catalog, url))
     try:
         b = self.d.get_bytes(url)
         print("")
         self.catalog_data = plist.loads(b)
     except:
         print("Error downloading!")
         return False
     try:
         # Assume it's valid data - dump it to a local file
         if local or self.force_local:
             print(" - Saving to {}...".format(self.plist))
             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:
         print(" - Error saving!")
         return False
     return True
Esempio n. 5
0
 def main(self):
     self.u.resize(self.w, self.h)
     self.u.head()
     print("")
     print("Q. Quit")
     print("")
     print("Please drag and drop a USBMap(Legacy).kext, Info.plist,")
     menu = self.u.grab("or UsbDumpEfi.efi output here to continue:  ")
     if not len(menu): return
     if menu.lower() == "q": self.u.custom_quit()
     # Check the path
     path = self.u.check_path(menu)
     try:
         # Ensure we have a valid path
         if not path: raise Exception("{} does not exist!".format(menu))
         if os.path.isdir(path):
             path = os.path.join(path, "Contents", "Info.plist")
         if not os.path.exists(path):
             raise Exception("{} does not exist!".format(path))
         if not os.path.isfile(path):
             raise Exception("{} is a directory!".format(path))
     except Exception as e:
         return self.show_error("Error Selecting Target", e)
     try:
         # Load it and ensure the plist is valid
         with open(path, "rb") as f:
             raw = f.read().replace(b"\x00", b"").decode("utf-8",
                                                         errors="ignore")
             if "UsbDumpEfi start" in raw:
                 return self.parse_usb_txt(raw)
             else:
                 f.seek(0)
                 plist_data = plist.load(f, dict_type=OrderedDict)
     except Exception as e:
         return self.show_error(
             "Error Loading {}".format(os.path.basename(path)), e)
     if not len(plist_data.get("IOKitPersonalities", {})):
         return self.show_error(
             "Missing Personalities",
             "No IOKitPersonalities found in {}!".format(
                 os.path.basename(path)))
     self.plist_path = path
     self.plist_data = plist_data
     self.pick_personality()
Esempio n. 6
0
 def _get_plist(self):
     self.u.head("Select Plist")
     print("")
     print("Current: {}".format(self.plist))
     print("")
     print("C. Clear Selection")
     print("M. Main Menu")
     print("Q. Quit")
     print("")
     p = self.u.grab("Please drag and drop the target plist:  ")
     if p.lower() == "q":
         self.u.custom_quit()
     elif p.lower() == "m":
         return
     elif p.lower() == "c":
         self.plist = None
         self.plist_data = None
         return
     
     pc = self.u.check_path(p)
     if not pc:
         self.u.head("File Missing")
         print("")
         print("Plist file not found:\n\n{}".format(p))
         print("")
         self.u.grab("Press [enter] to return...")
         self._get_plist()
     try:
         with open(pc, "rb") as f:
             self.plist = p
             self.plist_data = plist.load(f)
     except Exception as e:
         self.u.head("Plist Malformed")
         print("")
         print("Plist file malformed:\n\n{}".format(e))
         print("")
         self.u.grab("Press [enter] to return...")
         self._get_plist()
Esempio n. 7
0
 def open_plist_with_path(self,
                          event=None,
                          path=None,
                          current_window=None,
                          plist_type="XML"):
     if path == None:
         # Uh... wut?
         return
     path = os.path.realpath(os.path.expanduser(path))
     # Let's try to load the plist
     try:
         with open(path, "rb") as f:
             plist_type = "Binary" if plist._is_binary(f) else "XML"
             plist_data = plist.load(
                 f,
                 dict_type=dict
                 if self.settings.get("sort_dict", False) else OrderedDict)
     except Exception as e:
         # Had an issue, throw up a display box
         self.tk.bell()
         mb.showerror("An Error Occurred While Opening {}".format(
             os.path.basename(path)), str(e))  # ,parent=current_window)
         return None
     # Opened it correctly - let's load it, and set our values
     if current_window:
         current_window.open_plist(
             path, plist_data, plist_type,
             self.settings.get("expand_all_items_on_open", True))
     else:
         # Need to create one first
         current_window = plistwindow.PlistWindow(self, self.tk)
         current_window.open_plist(
             path, plist_data, plist_type,
             self.settings.get("expand_all_items_on_open", True))
     current_window.focus_force()
     current_window.update()
     return True
Esempio n. 8
0
    def snapshot(self,
                 in_file=None,
                 out_file=None,
                 oc_folder=None,
                 clean=False):
        oc_folder = self.u.check_path(oc_folder)
        if not oc_folder:
            print("OC folder passed does not exist!")
            exit(1)
        if not os.path.isdir(oc_folder):
            print("OC folder passed is not a directory!")
            exit(1)
        if in_file:
            in_file = self.u.check_path(in_file)
            if not in_file:
                print("Input plist passed does not exist!")
                exit(1)
            if os.path.isdir(in_file):
                print("Input plist passed is a directory!")
                exit(1)
            try:
                with open(in_file, "rb") as f:
                    tree_dict = plist.load(f)
            except Exception as e:
                print("Error loading plist: {}".format(e))
                exit(1)
        else:
            if not out_file:
                print("At least one input or output file must be provided.")
                exit(1)
            # We got an out file at least - create an empty dict for the in_file
            tree_dict = {}
        if not out_file: out_file = in_file

        # Verify folder structure - should be as follows:
        # OC
        #  +- ACPI
        #  | +- SSDT.aml
        #  +- Drivers
        #  | +- EfiDriver.efi
        #  +- Kexts
        #  | +- Something.kext
        #  +- config.plist
        #  +- Tools (Optional)
        #  | +- SomeTool.efi
        #  | +- SomeFolder
        #  | | +- SomeOtherTool.efi

        oc_acpi = os.path.normpath(os.path.join(oc_folder, "ACPI"))
        oc_drivers = os.path.normpath(os.path.join(oc_folder, "Drivers"))
        oc_kexts = os.path.normpath(os.path.join(oc_folder, "Kexts"))
        oc_tools = os.path.normpath(os.path.join(oc_folder, "Tools"))
        oc_efi = os.path.normpath(os.path.join(oc_folder, "OpenCore.efi"))

        for x in (oc_acpi, oc_drivers, oc_kexts):
            if not os.path.exists(x):
                print("Incorrect OC Folder Struction - {} does not exist.".
                      format(x))
                exit(1)
            if x != oc_efi and not os.path.isdir(x):
                print(
                    "Incorrect OC Folder Struction - {} exists, but is not a directory."
                    .format(x))
                exit(1)

        # Folders are valid - lets work through each section

        # Let's get the hash of OpenCore.efi, compare to a known list, and then compare that version to our snapshot_version if found
        hasher = hashlib.md5()
        try:
            with open(oc_efi, "rb") as f:
                hasher.update(f.read())
            oc_hash = hasher.hexdigest()
        except:
            oc_hash = ""  # Couldn't determine hash :(
        # Let's get the version of the snapshot that matches our target, and that matches our hash if any
        latest_snap = {}  # Highest min_version
        target_snap = {}  # Matches our hash
        for snap in self.snapshot_data:
            hashes = snap.get("release_hashes", [])
            hashes.extend(snap.get("debug_hashes", []))
            # Retain the highest version we see
            if snap.get("min_version", "0.0.0") > latest_snap.get(
                    "min_version", "0.0.0"):
                latest_snap = snap
            # Also retain the last snap that matches our hash
            if len(oc_hash) and (oc_hash in snap.get("release_hashes", [])
                                 or oc_hash in snap.get("debug_hashes", [])):
                target_snap = snap
        if not target_snap: target_snap = latest_snap
        # Apply our snapshot values
        acpi_add = target_snap.get("acpi_add", {})
        kext_add = target_snap.get("kext_add", {})
        tool_add = target_snap.get("tool_add", {})
        driver_add = target_snap.get("driver_add", {})

        long_paths = [
        ]  # We'll add any paths that exceed the OC_STORAGE_SAFE_PATH_MAX of 128 chars

        # ACPI is first, we'll iterate the .aml files we have and add what is missing
        # while also removing what exists in the plist and not in the folder.
        # If something exists in the table already, we won't touch it.  This leaves the
        # enabled and comment properties untouched.
        #
        # Let's make sure we have the ACPI -> Add sections in our config

        # We're going to replace the whole list
        if not "ACPI" in tree_dict or not isinstance(tree_dict["ACPI"], dict):
            tree_dict["ACPI"] = {"Add": []}
        if not "Add" in tree_dict["ACPI"] or not isinstance(
                tree_dict["ACPI"]["Add"], list):
            tree_dict["ACPI"]["Add"] = []
        # Now we walk the existing add values
        new_acpi = []
        for path, subdirs, files in os.walk(oc_acpi):
            for name in files:
                if not name.startswith(".") and name.lower().endswith(".aml"):
                    new_acpi.append(
                        os.path.join(path, name)[len(oc_acpi):].replace(
                            "\\", "/").lstrip("/"))
        add = [] if clean else tree_dict["ACPI"]["Add"]
        for aml in sorted(new_acpi, key=lambda x: x.lower()):
            if aml.lower() in [
                    x.get("Path", "").lower() for x in add
                    if isinstance(x, dict)
            ]:
                # Found it - skip
                continue
            # Doesn't exist, add it
            new_aml_entry = {
                "Comment": os.path.basename(aml),
                "Enabled": True,
                "Path": aml
            }
            # Add our snapshot custom entries, if any
            for x in acpi_add:
                new_aml_entry[x] = acpi_add[x]
            add.append(new_aml_entry)
        new_add = []
        for aml in add:
            if not isinstance(aml, dict):
                # Not the right type - skip it
                continue
            if not aml.get("Path",
                           "").lower() in [x.lower() for x in new_acpi]:
                # Not there, skip
                continue
            new_add.append(aml)
            # Check path length
            long_paths.extend(self.check_path_length(aml))
        tree_dict["ACPI"]["Add"] = new_add

        # Now we need to walk the kexts
        if not "Kernel" in tree_dict or not isinstance(tree_dict["Kernel"],
                                                       dict):
            tree_dict["Kernel"] = {"Add": []}
        if not "Add" in tree_dict["Kernel"] or not isinstance(
                tree_dict["Kernel"]["Add"], list):
            tree_dict["Kernel"]["Add"] = []

        kext_list = []
        # We need to gather a list of all the files inside that and with .efi
        for path, subdirs, files in os.walk(oc_kexts):
            for name in sorted(subdirs, key=lambda x: x.lower()):
                if name.startswith(".") or not name.lower().endswith(".kext"):
                    continue
                kdict = {
                    # "Arch":"Any",
                    "BundlePath":
                    os.path.join(path, name)[len(oc_kexts):].replace(
                        "\\", "/").lstrip("/"),
                    "Comment":
                    name,
                    "Enabled":
                    True,
                    # "MaxKernel":"",
                    # "MinKernel":"",
                    "ExecutablePath":
                    ""
                }
                # Add our entries from kext_add as needed
                for y in kext_add:
                    kdict[y] = kext_add[y]
                # Get the Info.plist
                plist_full_path = plist_rel_path = None
                for kpath, ksubdirs, kfiles in os.walk(os.path.join(
                        path, name)):
                    for kname in kfiles:
                        if kname.lower() == "info.plist":
                            plist_full_path = os.path.join(kpath, kname)
                            plist_rel_path = plist_full_path[
                                len(os.path.join(path, name)):].replace(
                                    "\\", "/").lstrip("/")
                            break
                    if plist_full_path: break  # Found it - break
                else:
                    # Didn't find it - skip
                    continue
                kdict["PlistPath"] = plist_rel_path
                # Let's load the plist and check for other info
                try:
                    with open(plist_full_path, "rb") as f:
                        info_plist = plist.load(f)
                    kinfo = {
                        "CFBundleIdentifier":
                        info_plist.get("CFBundleIdentifier", None),
                        "OSBundleLibraries":
                        info_plist.get("OSBundleLibraries", [])
                    }
                    if info_plist.get("CFBundleExecutable", None):
                        if not os.path.exists(
                                os.path.join(
                                    path, name, "Contents", "MacOS",
                                    info_plist["CFBundleExecutable"])):
                            continue  # Requires an executable that doesn't exist - bail
                        kdict[
                            "ExecutablePath"] = "Contents/MacOS/" + info_plist[
                                "CFBundleExecutable"]
                except Exception as e:
                    continue  # Something else broke here - bail
                # Should have something valid here
                kext_list.append((kdict, kinfo))

        bundle_list = [x[0].get("BundlePath", "") for x in kext_list]
        kexts = [] if clean else tree_dict["Kernel"]["Add"]
        original_kexts = [
            x for x in kexts if x.get("BundlePath", "") in bundle_list
        ]  # get the original load order for comparison purposes - but omit any that no longer exist
        for kext, info in kext_list:
            if kext["BundlePath"].lower() in [
                    x.get("BundlePath", "").lower() for x in kexts
                    if isinstance(x, dict)
            ]:
                # Already have it, skip
                continue
            # We need it, it seems
            kexts.append(kext)
        new_kexts = []
        for kext in kexts:
            if not isinstance(kext, dict):
                # Not a dict - skip it
                continue
            if not kext.get("BundlePath", "").lower() in [
                    x[0]["BundlePath"].lower() for x in kext_list
            ]:
                # Not there, skip it
                continue
            new_kexts.append(kext)
        # Let's check inheritance via the info
        # We need to ensure that no 2 kexts consider each other as parents
        unordered_kexts = []
        for x in new_kexts:
            x = next(
                (y for y in kext_list
                 if y[0].get("BundlePath", "") == x.get("BundlePath", "")),
                None)
            if not x: continue
            parents = [
                next(
                    (z for z in new_kexts
                     if z.get("BundlePath", "") == y[0].get("BundlePath", "")),
                    []) for y in kext_list
                if y[1].get("CFBundleIdentifier", None) in x[1].get(
                    "OSBundleLibraries", [])
            ]
            children = [
                next(
                    (z for z in new_kexts
                     if z.get("BundlePath", "") == y[0].get("BundlePath", "")),
                    []) for y in kext_list
                if x[1].get("CFBundleIdentifier", None) in y[1].get(
                    "OSBundleLibraries", [])
            ]
            parents = [
                y for y in parents if not y in children
                and not y.get("BundlePath", "") == x[0].get("BundlePath", "")
            ]
            unordered_kexts.append({"kext": x[0], "parents": parents})
        ordered_kexts = []
        disabled_parents = []
        while len(
                unordered_kexts
        ):  # This could be dangerous if things aren't properly prepared above
            kext = unordered_kexts.pop(0)
            if len(kext["parents"]):
                disabled_parents.extend([
                    x.get("BundlePath", "") for x in kext["parents"]
                    if x.get("Enabled", True) == False
                    and not x.get("BundlePath", "") in disabled_parents
                ])
                if not all(x in ordered_kexts for x in kext["parents"]):
                    unordered_kexts.append(kext)
                    continue
            ordered_kexts.append(
                next(x for x in new_kexts
                     if x.get("BundlePath", "") == kext["kext"].get(
                         "BundlePath", "")))
        # Let's compare against the original load order - to prevent mis-prompting
        missing_kexts = [x for x in ordered_kexts if not x in original_kexts]
        original_kexts.extend(missing_kexts)
        # Let's walk both lists and gather all kexts that are in different spots
        rearranged = []
        while True:
            check1 = [
                x.get("BundlePath", "") for x in ordered_kexts
                if not x.get("BundlePath", "") in rearranged
            ]
            check2 = [
                x.get("BundlePath", "") for x in original_kexts
                if not x.get("BundlePath", "") in rearranged
            ]
            out_of_place = next(
                (x for x in range(len(check1)) if check1[x] != check2[x]),
                None)
            if out_of_place == None: break
            rearranged.append(check2[out_of_place])
        # Verify if the load order changed - and prompt the user if need be
        if len(rearranged):
            print(
                "\nIncorrect kext load order has been corrected:\n\n{}".format(
                    "\n".join(rearranged)))
            ordered_kexts = original_kexts  # We didn't want to update it
        if len(disabled_parents):
            print("\nDisabled parent kexts have been enabled:\n\n{}".format(
                "\n".join(disabled_parents)))
            for x in ordered_kexts:  # Walk our kexts and enable the parents
                if x.get("BundlePath", "") in disabled_parents:
                    x["Enabled"] = True
        # Finally - we walk the kexts and ensure that we're not loading the same CFBundleIdentifier more than once
        enabled_kexts = []
        duplicate_bundles = []
        duplicates_disabled = []
        for kext in ordered_kexts:
            # Check path length
            long_paths.extend(self.check_path_length(kext))
            temp_kext = {}
            # Shallow copy the kext entry to avoid changing it in ordered_kexts
            for x in kext:
                temp_kext[x] = kext[x]
            duplicates_disabled.append(temp_kext)
            # Ignore if alreday disabled
            if not temp_kext.get("Enabled", False): continue
            # Get the original info
            info = next((x for x in kext_list if x[0].get("BundlePath", "") ==
                         temp_kext.get("BundlePath", "")), None)
            if not info or not info[1].get("CFBundleIdentifier", None):
                continue  # Broken info
            # Let's see if it's already in enabled_kexts - and compare the Min/Max/Match Kernel options
            temp_min, temp_max = self.get_min_max_from_kext(
                temp_kext, "MatchKernel" in kext_add)
            # Gather a list of like IDs
            comp_kexts = [
                x for x in enabled_kexts
                if x[1]["CFBundleIdentifier"] == info[1]["CFBundleIdentifier"]
            ]
            # Walk the comp_kexts, and disable if we find an overlap
            for comp_info in comp_kexts:
                comp_kext = comp_info[0]
                # Gather our min/max
                comp_min, comp_max = self.get_min_max_from_kext(
                    comp_kext, "MatchKernel" in kext_add)
                # Let's see if we don't overlap
                if temp_min > comp_max or temp_max < comp_min:  # We're good, continue
                    continue
                # We overlapped - let's disable it
                temp_kext["Enabled"] = False
                # Add it to the list - then break out of this loop
                duplicate_bundles.append(temp_kext.get("BundlePath", ""))
                break
            # Check if we ended up disabling temp_kext, and if not - add it to the enabled_kexts list
            if temp_kext.get("Enabled", False):
                enabled_kexts.append((temp_kext, info[1]))
        # Check if we have duplicates - and offer to disable them
        if len(duplicate_bundles):
            print("\nDuplicate CFBundleIdentifiers have been disabled:\n\n{}".
                  format("\n".join(duplicate_bundles)))
            ordered_kexts = duplicates_disabled

        tree_dict["Kernel"]["Add"] = ordered_kexts

        # Let's walk the Tools folder if it exists
        if not "Misc" in tree_dict or not isinstance(tree_dict["Misc"], dict):
            tree_dict["Misc"] = {"Tools": []}
        if not "Tools" in tree_dict["Misc"] or not isinstance(
                tree_dict["Misc"]["Tools"], list):
            tree_dict["Misc"]["Tools"] = []
        if os.path.exists(oc_tools) and os.path.isdir(oc_tools):
            tools_list = []
            # We need to gather a list of all the files inside that and with .efi
            for path, subdirs, files in os.walk(oc_tools):
                for name in files:
                    if not name.startswith(".") and name.lower().endswith(
                            ".efi"):
                        # Save it
                        new_tool_entry = {
                            # "Arguments":"",
                            # "Auxiliary":True,
                            "Name":
                            name,
                            "Comment":
                            name,
                            "Enabled":
                            True,
                            "Path":
                            os.path.join(path, name)[len(oc_tools):].replace(
                                "\\",
                                "/").lstrip("/")  # Strip the /Volumes/EFI/
                        }
                        # Add our snapshot custom entries, if any
                        for x in tool_add:
                            new_tool_entry[x] = tool_add[x]
                        tools_list.append(new_tool_entry)
            tools = [] if clean else tree_dict["Misc"]["Tools"]
            for tool in sorted(tools_list,
                               key=lambda x: x.get("Path", "").lower()):
                if tool["Path"].lower() in [
                        x.get("Path", "").lower() for x in tools
                        if isinstance(x, dict)
                ]:
                    # Already have it, skip
                    continue
                # We need it, it seems
                tools.append(tool)
            new_tools = []
            for tool in tools:
                if not isinstance(tool, dict):
                    # Not a dict - skip it
                    continue
                if not tool.get("Path", "").lower() in [
                        x["Path"].lower() for x in tools_list
                ]:
                    # Not there, skip it
                    continue
                new_tools.append(tool)
                # Check path length
            long_paths.extend(self.check_path_length(tool))
            tree_dict["Misc"]["Tools"] = new_tools
        else:
            # Make sure our Tools list is empty
            tree_dict["Misc"]["Tools"] = []

        # Last we need to walk the .efi drivers
        if not "UEFI" in tree_dict or not isinstance(tree_dict["UEFI"], dict):
            tree_dict["UEFI"] = {"Drivers": []}
        if not "Drivers" in tree_dict["UEFI"] or not isinstance(
                tree_dict["UEFI"]["Drivers"], list):
            tree_dict["UEFI"]["Drivers"] = []
        if os.path.exists(oc_drivers) and os.path.isdir(oc_drivers):
            drivers_list = []
            # We need to gather a list of all the files inside that and with .efi
            for path, subdirs, files in os.walk(oc_drivers):
                for name in files:
                    if not name.startswith(".") and name.lower().endswith(
                            ".efi"):
                        # Check if we're using the new approach - or just listing the paths
                        if not driver_add:
                            drivers_list.append(
                                os.path.join(
                                    path, name)[len(oc_drivers):].replace(
                                        "\\", "/").lstrip(
                                            "/"))  # Strip the /Volumes/EFI/
                        else:
                            new_driver_entry = {
                                # "Arguments": "",
                                "Enabled":
                                True,
                                "Path":
                                os.path.join(
                                    path, name)[len(oc_drivers):].replace(
                                        "\\", "/").lstrip(
                                            "/")  # Strip the /Volumes/EFI/
                            }
                            # Add our snapshot custom entries, if any
                            for x in driver_add:
                                new_driver_entry[x] = name if x.lower(
                                ) == "comment" else driver_add[x]
                            drivers_list.append(new_driver_entry)
            drivers = [] if clean else tree_dict["UEFI"]["Drivers"]
            for driver in sorted(drivers_list,
                                 key=lambda x: x.get("Path", "").lower()
                                 if driver_add else x):
                if not driver_add:  # Old way
                    if not isinstance(driver,
                                      (str, unicode)) or driver.lower() in [
                                          x.lower() for x in drivers
                                          if isinstance(x, (str, unicode))
                                      ]:
                        continue
                else:
                    if driver["Path"].lower() in [
                            x.get("Path", "").lower() for x in drivers
                            if isinstance(x, dict)
                    ]:
                        # Already have it, skip
                        continue
                # We need it, it seems
                drivers.append(driver)
            new_drivers = []
            for driver in drivers:
                if not driver_add:  # Old way
                    if not isinstance(
                            driver, (str, unicode)) or not driver.lower() in [
                                x.lower() for x in drivers_list
                                if isinstance(x, (str, unicode))
                            ]:
                        continue
                else:
                    if not isinstance(driver, dict):
                        # Not a dict - skip it
                        continue
                    if not driver.get("Path", "").lower() in [
                            x["Path"].lower() for x in drivers_list
                    ]:
                        # Not there, skip it
                        continue
                new_drivers.append(driver)
                # Check path length
            long_paths.extend(self.check_path_length(driver))
            tree_dict["UEFI"]["Drivers"] = new_drivers
        else:
            # Make sure our Drivers list is empty
            tree_dict["UEFI"]["Drivers"] = []

        # Check if we have any paths that are too long
        if long_paths:
            formatted = []
            for entry in long_paths:
                item, name, keys = entry
                if isinstance(item, str):  # It's an older string path
                    formatted.append(name)
                elif isinstance(item, dict):
                    formatted.append("{} -> {}".format(name, ", ".join(keys)))
            # Show the warning of lengthy paths
            print(
                "\nThe following exceed the {:,} character safe path max declared by OpenCore\nand may not work as intended:\n\n{}"
                .format(self.safe_path_length, "\n".join(formatted)))

        try:
            with open(out_file, "wb") as f:
                plist.dump(tree_dict, f)
            print("\nOutput saved to: {}".format(out_file))
        except Exception as e:
            print("Failed to write output plist: {}".format(e))
            exit(1)