diff --git a/Docs/Changelog.md b/Docs/Changelog.md
index 062cd42..8d6572e 100644
--- a/Docs/Changelog.md
+++ b/Docs/Changelog.md
@@ -1,5 +1,18 @@
OpenCore Changelog
==================
+#### v0.8.6
+- Updated NVRAM save script for compatibilty with earlier macOS (Snow Leopard+ tested)
+- Updated NVRAM save script to automatically install as launch daemon (Yosemite+) or logout hook (older macOS)
+- Fixed maximum click duration and double click speed for non-standard poll frequencies
+- Added support for pointer dwell-clicking
+- Fixed recursive loop crash at first non-early log line on some systems
+- Fixed early log preservation when using unsafe fast file logging
+- Updated builtin firmware versions for SMBIOS and the rest
+- Resolved wake-from-sleep failure on EFI 1.1 systems (including earlier Macs) with standalone emulated NVRAM driver
+- Updated macrecovery commands with macOS 12 and 13, thx @Core-i99
+- Updates SSDT-BRG0 with macOS-specific STA to avoid compatibility issues on Windows, thx @Lorys89
+- Fixed memory issues in OpenLinuxBoot causing crashes on 32-bit UEFI firmware
+
#### v0.8.5
- Updated builtin firmware versions for SMBIOS and the rest
- Moved CPU objects that exist only in Windows Server 2022 into `SSDT-HV-DEV-WS2022.dsl`
diff --git a/Docs/Configuration.pdf b/Docs/Configuration.pdf
index b3ab196..6a6cfd0 100644
Binary files a/Docs/Configuration.pdf and b/Docs/Configuration.pdf differ
diff --git a/Docs/Sample.plist b/Docs/Sample.plist
index ced5e3f..bc0c1f8 100644
--- a/Docs/Sample.plist
+++ b/Docs/Sample.plist
@@ -1379,6 +1379,12 @@
50
KeySubsequentDelay
5
+ PointerDwellClickTimeout
+ 0
+ PointerDwellDoubleClickTimeout
+ 0
+ PointerDwellRadius
+ 0
PointerPollMask
-1
PointerPollMax
diff --git a/EFI-Debug/BOOT/BOOTx64.efi b/EFI-Debug/BOOT/BOOTx64.efi
index 0e6f3eb..c328780 100644
Binary files a/EFI-Debug/BOOT/BOOTx64.efi and b/EFI-Debug/BOOT/BOOTx64.efi differ
diff --git a/EFI-Debug/OC/Drivers/AudioDxe.efi b/EFI-Debug/OC/Drivers/AudioDxe.efi
index 106021a..12fe04f 100644
Binary files a/EFI-Debug/OC/Drivers/AudioDxe.efi and b/EFI-Debug/OC/Drivers/AudioDxe.efi differ
diff --git a/EFI-Debug/OC/Drivers/OpenRuntime.efi b/EFI-Debug/OC/Drivers/OpenRuntime.efi
index c01e272..461ff78 100644
Binary files a/EFI-Debug/OC/Drivers/OpenRuntime.efi and b/EFI-Debug/OC/Drivers/OpenRuntime.efi differ
diff --git a/EFI-Debug/OC/Drivers/ResetNvramEntry.efi b/EFI-Debug/OC/Drivers/ResetNvramEntry.efi
index 7216407..1c59070 100644
Binary files a/EFI-Debug/OC/Drivers/ResetNvramEntry.efi and b/EFI-Debug/OC/Drivers/ResetNvramEntry.efi differ
diff --git a/EFI-Debug/OC/OpenCore.efi b/EFI-Debug/OC/OpenCore.efi
index 7c794de..8dfac83 100644
Binary files a/EFI-Debug/OC/OpenCore.efi and b/EFI-Debug/OC/OpenCore.efi differ
diff --git a/EFI-Debug/OC/config.plist b/EFI-Debug/OC/config.plist
index 933d9f8..bbedb8c 100644
--- a/EFI-Debug/OC/config.plist
+++ b/EFI-Debug/OC/config.plist
@@ -2,7 +2,7 @@
- # BASE EFI INTEL 11th ALDER LAKE (doest not support iGPU) - DEBUG Version
+ # BASE EFI INTEL 12h ALDER LAKE (doest not support iGPU) - DEBUG Version
# DISCORD: https://discord.universohackintosh.com
@@ -555,6 +555,12 @@
50
KeySubsequentDelay
5
+ PointerDwellClickTimeout
+ 0
+ PointerDwellDoubleClickTimeout
+ 0
+ PointerDwellRadius
+ 0
PointerPollMask
-1
PointerPollMax
diff --git a/EFI-Release/BOOT/BOOTx64.efi b/EFI-Release/BOOT/BOOTx64.efi
index 7be4f13..ceeee84 100644
Binary files a/EFI-Release/BOOT/BOOTx64.efi and b/EFI-Release/BOOT/BOOTx64.efi differ
diff --git a/EFI-Release/OC/Drivers/AudioDxe.efi b/EFI-Release/OC/Drivers/AudioDxe.efi
index 454f740..f2374fa 100644
Binary files a/EFI-Release/OC/Drivers/AudioDxe.efi and b/EFI-Release/OC/Drivers/AudioDxe.efi differ
diff --git a/EFI-Release/OC/Drivers/OpenCanopy.efi b/EFI-Release/OC/Drivers/OpenCanopy.efi
index cc889ec..2124962 100644
Binary files a/EFI-Release/OC/Drivers/OpenCanopy.efi and b/EFI-Release/OC/Drivers/OpenCanopy.efi differ
diff --git a/EFI-Release/OC/Drivers/ResetNvramEntry.efi b/EFI-Release/OC/Drivers/ResetNvramEntry.efi
index 824797b..0cccc6b 100644
Binary files a/EFI-Release/OC/Drivers/ResetNvramEntry.efi and b/EFI-Release/OC/Drivers/ResetNvramEntry.efi differ
diff --git a/EFI-Release/OC/OpenCore.efi b/EFI-Release/OC/OpenCore.efi
index 67dec35..ef96a86 100644
Binary files a/EFI-Release/OC/OpenCore.efi and b/EFI-Release/OC/OpenCore.efi differ
diff --git a/EFI-Release/OC/config.plist b/EFI-Release/OC/config.plist
index a1414ab..cea2beb 100644
--- a/EFI-Release/OC/config.plist
+++ b/EFI-Release/OC/config.plist
@@ -555,6 +555,12 @@
50
KeySubsequentDelay
5
+ PointerDwellClickTimeout
+ 0
+ PointerDwellDoubleClickTimeout
+ 0
+ PointerDwellRadius
+ 0
PointerPollMask
-1
PointerPollMax
diff --git a/README.md b/README.md
index 1eada91..7b7101b 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,8 @@ Note|Description
:----|:----
Initial macOS Support|macOS 10.15, Catalina.
-- Opencore version: 0.8.5
-- Release date: 04/10/2022 (late 1 day)
+- Opencore version: 0.8.6
+- Release date: 07/11/2022
# Basic Steps
diff --git a/Utils/macrecovery/boards.json b/Utils/macrecovery/boards.json
index 6bccf2b..95837aa 100644
--- a/Utils/macrecovery/boards.json
+++ b/Utils/macrecovery/boards.json
@@ -1,31 +1,31 @@
{
"Mac-EE2EBD4B90B839A8": "latest",
- "Mac-BE0E8AC46FE800CC": "11.7",
- "Mac-9AE82516C7C6B903": "12.6",
+ "Mac-BE0E8AC46FE800CC": "11.7.1",
+ "Mac-9AE82516C7C6B903": "12.6.1",
"Mac-942452F5819B1C1B": "10.13.6",
"Mac-942C5DF58193131B": "10.13.6",
"Mac-C08A6BB70A942AC2": "10.13.6",
"Mac-742912EFDBEE19B3": "10.13.6",
"Mac-66F35F19FE2A0D05": "10.15.7",
"Mac-2E6FAB96566FE58C": "10.15.7",
- "Mac-35C1E88140C3E6CF": "11.7",
- "Mac-7DF21CB3ED6977E5": "11.7",
- "Mac-9F18E312C5C2BF0B": "12.6",
- "Mac-937CB26E2E02BB01": "12.6",
+ "Mac-35C1E88140C3E6CF": "11.7.1",
+ "Mac-7DF21CB3ED6977E5": "11.7.1",
+ "Mac-9F18E312C5C2BF0B": "12.6.1",
+ "Mac-937CB26E2E02BB01": "12.6.1",
"Mac-827FAC58A8FDFA22": "latest",
"Mac-226CB3C6A851A671": "latest",
"Mac-0CFF9C7C2B63DF8D": "latest",
"Mac-C3EC7CD22292981F": "10.15.7",
"Mac-AFD8A9D944EA4843": "10.15.7",
- "Mac-189A3D4F975D5FFC": "11.7",
- "Mac-3CBD00234E554E41": "11.7",
- "Mac-2BD1B31983FE1663": "11.7",
- "Mac-06F11FD93F0323C5": "12.6",
- "Mac-06F11F11946D27C5": "12.6",
- "Mac-E43C1C25D4880AD6": "12.6",
- "Mac-473D31EABEB93F9B": "12.6",
- "Mac-66E35819EE2D0D05": "12.6",
- "Mac-A5C67F76ED83108C": "12.6",
+ "Mac-189A3D4F975D5FFC": "11.7.1",
+ "Mac-3CBD00234E554E41": "11.7.1",
+ "Mac-2BD1B31983FE1663": "11.7.1",
+ "Mac-06F11FD93F0323C5": "12.6.1",
+ "Mac-06F11F11946D27C5": "12.6.1",
+ "Mac-E43C1C25D4880AD6": "12.6.1",
+ "Mac-473D31EABEB93F9B": "12.6.1",
+ "Mac-66E35819EE2D0D05": "12.6.1",
+ "Mac-A5C67F76ED83108C": "12.6.1",
"Mac-B4831CEBD52A0C4C": "latest",
"Mac-CAD6701F7CEA0921": "latest",
"Mac-551B86E5744E2388": "latest",
@@ -43,7 +43,7 @@
"Mac-942459F5819B171B": "10.13.6",
"Mac-4B7AC7E43945597E": "10.15.7",
"Mac-6F01561E16C75D06": "10.15.7",
- "Mac-F60DEB81FF30ACF6": "12.6",
+ "Mac-F60DEB81FF30ACF6": "12.6.1",
"Mac-27AD2F918AE68F61": "latest",
"Mac-F2208EC8": "10.13.6",
"Mac-8ED6AF5B48C039E1": "10.13.6",
@@ -51,7 +51,7 @@
"Mac-7BA5B2794B2CDB12": "10.13.6",
"Mac-031AEE4D24BFF0B1": "10.15.7",
"Mac-F65AE981FFA204ED": "10.15.7",
- "Mac-35C5E08120C7EEAF": "12.6",
+ "Mac-35C5E08120C7EEAF": "12.6.1",
"Mac-7BA5B2DFE22DDD8C": "latest",
"Mac-942B5BF58194151B": "10.13.6",
"Mac-942B59F58194171B": "10.13.6",
@@ -61,13 +61,13 @@
"Mac-031B6874CF7F642A": "10.15.7",
"Mac-27ADBB7B4CEE8E61": "10.15.7",
"Mac-77EB7D7DAF985301": "10.15.7",
- "Mac-81E3E92DD6088272": "11.7",
- "Mac-42FD25EABCABB274": "11.7",
- "Mac-A369DDC4E67F1C45": "12.6",
- "Mac-FFE5EF870D7BA81A": "12.6",
- "Mac-DB15BD556843C820": "12.6",
- "Mac-65CE76090165799A": "12.6",
- "Mac-B809C3757DA9BB8D": "12.6",
+ "Mac-81E3E92DD6088272": "11.7.1",
+ "Mac-42FD25EABCABB274": "11.7.1",
+ "Mac-A369DDC4E67F1C45": "12.6.1",
+ "Mac-FFE5EF870D7BA81A": "12.6.1",
+ "Mac-DB15BD556843C820": "12.6.1",
+ "Mac-65CE76090165799A": "12.6.1",
+ "Mac-B809C3757DA9BB8D": "12.6.1",
"Mac-4B682C642B45593E": "latest",
"Mac-77F17D7DA9285301": "latest",
"Mac-BE088AF8C5EB4FA2": "latest",
diff --git a/Utils/macrecovery/recovery_urls.txt b/Utils/macrecovery/recovery_urls.txt
index 61afe5a..0aea577 100644
--- a/Utils/macrecovery/recovery_urls.txt
+++ b/Utils/macrecovery/recovery_urls.txt
@@ -30,6 +30,9 @@ python macrecovery.py -b Mac-00BE6ED71E35EB86 -m 00000000000000000 download
# Big Sur (11)
python macrecovery.py -b Mac-42FD25EABCABB274 -m 00000000000000000 download
+# Monterey (12)
+python macrecovery.py -b Mac-E43C1C25D4880AD6 -m 00000000000000000 download
+
# Latest version
-# ie. Monterey (12)
-python ./macrecovery.py -b Mac-E43C1C25D4880AD6 -m 00000000000000000 download
+# ie. Ventura (13)
+python ./macrecovery.py -b Mac-B4831CEBD52A0C4C -m 00000000000000000 download
diff --git a/Utils/mountefi/Mount EFI.workflow/Contents/Scripts/disk.py b/Utils/mountefi/Mount EFI.workflow/Contents/Scripts/disk.py
new file mode 100755
index 0000000..3a8d621
--- /dev/null
+++ b/Utils/mountefi/Mount EFI.workflow/Contents/Scripts/disk.py
@@ -0,0 +1,411 @@
+import os, sys
+sys.path.append(os.path.abspath(os.path.dirname(os.path.realpath(__file__))))
+import run, plist
+
+class Disk:
+ def __init__(self):
+ self.r = run.Run()
+ self.diskdump = self.check_diskdump()
+ self.full_os_version = self.r.run({"args":["sw_vers", "-productVersion"]})[0]
+ if len(self.full_os_version.split(".")) < 3:
+ # Ensure the format is XX.YY.ZZ
+ self.full_os_version += ".0"
+ self.os_version = ".".join(self.full_os_version.split(".")[:2])
+ self.sudo_mount_version = "10.13.6"
+ self.efi_guids = ["C12A7328-F81F-11D2-BA4B-00A0C93EC93B"]
+ self.disks = self.get_disks()
+
+ def check_diskdump(self):
+ ddpath = os.path.join(os.path.dirname(os.path.realpath(__file__)),"diskdump")
+ if not os.path.exists(ddpath):
+ raise FileNotFoundError("Could not locate diskdump")
+ if "com.apple.quarantine" in self.r.run({"args":["xattr",ddpath]})[0]:
+ self.r.run({"args":["xattr","-d","com.apple.quarantine",ddpath]})
+ return ddpath
+
+ def update(self):
+ # Refresh our disk list
+ self.disks = self.get_disks()
+ return self.disks
+
+ def get_disks(self):
+ # Check for our binary - and ensure it's setup to run
+ ddpath = os.path.join(os.path.dirname(os.path.realpath(__file__)),"diskdump")
+ if not os.path.exists(ddpath): return {}
+ # Get our "diskutil list" and diskdump info. Run diskutil list first
+ # as it takes longer - but will stall while waiting for disks to appear,
+ # meaning our diskdump output will be better reflected.
+ diskutil_list = self.r.run({"args":["diskutil","list"]})[0]
+ diskstring = self.r.run({"args":[ddpath]})[0]
+ if not diskstring: return {}
+ diskdump = plist.loads(diskstring)
+ last_disk = None
+ for line in diskutil_list.split("\n"):
+ if line.startswith("/dev/disk"):
+ last_disk = line.split()[0].split("/")[-1]
+ elif not last_disk:
+ continue
+ elif line.strip().startswith("Logical Volume on"):
+ # Core Storage
+ ps = line.split("Logical Volume on")[1].strip().split(", ")
+ disk = self.get_disk(last_disk,disk_dict=diskdump)
+ if disk: # Update parent disk
+ disk["container"] = True
+ disk["core_storage"] = True
+ disk["physical_stores"] = ps
+ # Save a reference to the physical stores
+ for s in ps:
+ store = self.get_disk(s,disk_dict=diskdump)
+ if store:
+ store["container_for"] = last_disk
+ store["core_storage_container_for"] = last_disk
+ elif line.strip().startswith("Physical Store"):
+ # APFS
+ ps = line.split("Physical Store")[1].strip().split(", ")
+ disk = self.get_disk(last_disk,disk_dict=diskdump)
+ if disk: # Update parent disk
+ disk["container"] = True
+ disk["apfs"] = True
+ disk["physical_stores"] = ps
+ # Save a reference to the physical stores
+ for s in ps:
+ store = self.get_disk(s,disk_dict=diskdump)
+ if store:
+ store["container_for"] = last_disk
+ store["apfs_container_for"] = last_disk
+ return diskdump
+
+ def get_identifier(self, disk = None, disk_dict = None):
+ # Should be able to take a mount point, disk name, or disk identifier,
+ # and return the disk's identifier
+ if isinstance(disk,dict): disk = disk.get("DAMediaBSDName")
+ if not disk: return
+ disk_dict = disk_dict or self.disks # Normalize the dict
+ disk = disk[6:] if disk.lower().startswith("/dev/rdisk") else disk[5:] if disk.lower().startswith("/dev/disk") else disk
+ if disk.lower() in disk_dict.get("AllDisks",[]): return disk
+ for d in disk_dict.get("AllDisksAndPartitions", []):
+ # Check the parent disk
+ if any((disk.lower()==d.get(x,"").lower() for x in ("DAMediaBSDName","DAVolumeName","DAVolumeUUID","DAMediaUUID","DAVolumePath"))):
+ return d.get("DAMediaBSDName")
+ # Check the partitions
+ for p in d.get("Partitions", []):
+ if any((disk.lower()==p.get(x,"").lower() for x in ("DAMediaBSDName","DAVolumeName","DAVolumeUUID","DAMediaUUID","DAVolumePath"))):
+ return p.get("DAMediaBSDName")
+ # At this point, we didn't find it
+ return None
+
+ def get_parent(self, disk = None, disk_dict = None):
+ # For backward compatibility with the old disk.py approach
+ return self.get_physical_parent_identifiers(disk,disk_dict=disk_dict)
+
+ def get_parent_identifier(self, disk = None, disk_dict = None):
+ # Resolves the passed disk value and returns the parent disk/container.
+ # i.e. Passing disk5s2s1 would return disk5
+ disk = self.get_identifier(disk,disk_dict=disk_dict)
+ if not disk: return
+ return "disk"+disk.lower().split("disk")[1].split("s")[0]
+
+ def get_physical_parent_identifier(self, disk = None, disk_dict = None):
+ # Returns the first hit from get_physical_parent_identifiers()
+ return next(iter(self.get_physical_parent_identifiers(disk, disk_dict=disk_dict) or []), None)
+
+ def get_physical_parent_identifiers(self, disk = None, disk_dict = None):
+ # Resolves the passed disk to the physical parent disk identifiers. Useful for APFS
+ # and Core Storage volumes which are logical - and can span multiple disks.
+ # If you have an APFS container on disk4 and its Physical Store lists
+ # disk2s2, disk3s2 - this would return [disk2, disk3]
+ return [self.get_identifier(x,disk_dict=disk_dict) for x in self.get_physical_parent_disks(disk,disk_dict=disk_dict)]
+
+ def get_physical_parent_disks(self, disk = None, disk_dict = None):
+ # Resolves the passed disk to the physical parent disk dicts. Useful for APFS
+ # and Core Storage volumes which are logcial and can span multiple physical
+ # disks. If you have an APFS container on disk4 and its Physical Store is on
+ # disk2s2 and disk3s2 - this would return the disk dicts for disk2 and disk3.
+ parent = self.get_parent_disk(disk, disk_dict=disk_dict)
+ if not parent: return []
+ if not "physical_stores" in parent: return [parent]
+ return [self.get_parent_disk(x,disk_dict=disk_dict) for x in parent.get("physical_stores",[])]
+
+ def get_parent_disk(self, disk = None, disk_dict = None):
+ # Returns the dict info for the parent of the passed mount point, name, identifier, etc
+ return self.get_disk(self.get_parent_identifier(disk,disk_dict=disk_dict),disk_dict=disk_dict)
+
+ def get_disk(self, disk = None, disk_dict = None):
+ # Returns the dict info for the passed mount point, name, identifier, etc
+ disk = self.get_identifier(disk,disk_dict=disk_dict)
+ if not disk: return
+ parent = self.get_parent_identifier(disk,disk_dict=disk_dict)
+ # Walk AllDisksAndPartitions, and return the first hit
+ for d in (disk_dict or self.disks).get("AllDisksAndPartitions",[]):
+ d_ident = d.get("DAMediaBSDName")
+ if d_ident == disk:
+ return d # Got the disk
+ elif d_ident == parent:
+ # Got the parent - iterate the partitions
+ return next((p for p in d.get("Partitions",[]) if p.get("DAMediaBSDName")==disk),None)
+ return None # Didn't find it
+
+ def get_efis(self, disk = None, disk_dict = None):
+ # Returns the identifiers for any EFI partitions attached to the
+ # parent disk(s) of the passed disk
+ efis = []
+ for parent in self.get_physical_parent_identifiers(disk,disk_dict=disk_dict):
+ parent_dict = self.get_disk(parent,disk_dict=disk_dict)
+ if not parent_dict: continue
+ for part in parent_dict.get("Partitions",[]):
+ # Use the GUID instead of media name - as that can vary
+ if part.get("DAMediaContent","").upper() in self.efi_guids:
+ efis.append(part["DAMediaBSDName"])
+ # Normalize case for the DAMediaName;
+ # macOS disks: "EFI System Partition", Windows disks: "EFI system partition"
+ # Maybe use this approach as a fallback at some point - but for now, just use the GUID
+ # if part.get("DAMediaName").lower() == "efi system partition":
+ # efis.append(part["DAMediaBSDName"])
+ return efis
+
+ def get_efi(self, disk = None, disk_dict = None):
+ # Returns the identifier for the first EFI partition found for
+ # the passed disk
+ return next(iter(self.get_efis(disk,disk_dict=disk_dict) or []), None)
+
+ def get_mounted_volumes(self, disk_dict = None):
+ # Returns a list of mounted volumes
+ return (disk_dict or self.disks).get("MountPointsFromDisks",[])
+
+ def get_mounted_volume_dicts(self, disk_dict = None):
+ # Returns a list of dicts of name, identifier, mount point dicts
+ vol_list = []
+ for v in (disk_dict or self.disks).get("MountPointsFromDisks"):
+ i = self.get_disk(v,disk_dict=disk_dict)
+ if not i: continue # Skip - as it didn't resolve
+ mount_point = self.get_mount_point(i,disk_dict=disk_dict)
+ # Check if we're either not mounted - or not mounted in /Volumes/
+ if not v or not (v == "/" or v.lower().startswith("/volumes/")):
+ continue
+ vol = {
+ "name": self.get_volume_name(i,disk_dict=disk_dict),
+ "identifier": self.get_identifier(i,disk_dict=disk_dict),
+ "mount_point": v,
+ "disk_uuid": self.get_disk_uuid(i,disk_dict=disk_dict),
+ "volume_uuid": self.get_volume_uuid(i,disk_dict=disk_dict)
+ }
+ if "container_for" in i: vol["container_for"] = i["container_for"]
+ vol_list.append(vol)
+ return sorted(vol_list,key=lambda x:x["identifier"])
+
+ def get_disks_and_partitions_dict(self, disk_dict = None):
+ # Returns a list of dictionaries like so:
+ # { "disk0" : {
+ # "container": true/false,
+ # "physical_stores": [
+ # "diskAsB",
+ # "diskXsY"
+ # ],
+ # "scheme": "Guid_partition_scheme",
+ # "partitions" : [
+ # {
+ # "identifier" : "disk0s1",
+ # "name" : "EFI",
+ # "mount_point" : "/Volumes/EFI",
+ # "container_for": "diskCsD"
+ # }
+ # ] } }
+ disks = {}
+ for d in sorted((disk_dict or self.disks).get("AllDisksAndPartitions"),key=lambda x:x.get("DAMediaBSDName")):
+ if not "DAMediaBSDName" in d: continue # Malformed
+ parent = d["DAMediaBSDName"]
+ disks[parent] = {"partitions":[]}
+ # Save if the disk is logical - and a l)ist of its physical stores
+ for x in ("container","physical_stores"):
+ if x in d: disks[parent][x] = d[x]
+ disks[parent]["scheme"] = self.get_readable_partition_scheme(d,disk_dict=disk_dict)
+ # Check if this disk is also a volume - i.e. also a leaf, and insert it in the partitions list
+ partitions = d.get("Partitions",[])
+ if d.get("DAMediaLeaf"):
+ partitions.insert(0,d)
+ for p in d.get("Partitions",[]):
+ part = {
+ "name": self.get_volume_name(p,disk_dict=disk_dict),
+ "identifier": self.get_identifier(p,disk_dict=disk_dict),
+ "mount_point": self.get_mount_point(p,disk_dict=disk_dict),
+ "disk_uuid": self.get_disk_uuid(p,disk_dict=disk_dict),
+ "volume_uuid": self.get_volume_uuid(p,disk_dict=disk_dict)
+ }
+ if "container_for" in p: part["container_for"] = p["container_for"]
+ disks[parent]["partitions"].append(part)
+ disks[parent]["partitions"].sort(key=lambda x:x["identifier"])
+ return disks
+
+ def _get_value(self, disk = None, value = None, disk_dict = None):
+ if not disk or not value: return # Missing info
+ if isinstance(disk,dict): return disk.get(value)
+ try: return self.get_disk(disk,disk_dict=disk_dict).get(value)
+ except: return
+
+ def _is_uuid(self, value):
+ # Helper to return whether a passed value is a UUID
+ # 7C3CFDDF-920A-4924-AED6-7CD4AF6E4512
+ if not isinstance(value,str): return False # Wrong type
+ value = value.lower()
+ # Check that all chars are hex or the separator
+ if not all((x in "-0123456789abcdef" for x in value)): return False
+ len_list = (8,4,4,4,12)
+ chunks = value.split("-")
+ # Make sure we have the right number of chunks - and
+ # each chunk is the right length.
+ if not len(chunks)==len(len_list): return False
+ for i,chunk in enumerate(chunks):
+ if not len(chunk)==len_list[i]: return False
+ # Passed all the checks
+ return True
+
+ def get_partition_scheme(self, disk, allow_logical = True, disk_dict = None):
+ # let's resolve the disk to its physical parents
+ comm = self.get_parent_disk if allow_logical else self.get_physical_parent_disks
+ p = comm(disk,disk_dict=disk_dict)
+ if p:
+ if isinstance(p,(list,tuple)): p = p[0] # Extract the first parent if need be
+ if p.get("apfs"): return "APFS_container_scheme"
+ elif p.get("core_storage"): return "Core_Storage_container_scheme"
+ content = self.get_content(p,disk_dict=disk_dict)
+ if content.lower().endswith("scheme"):
+ return content
+
+ def get_readable_partition_scheme(self, disk, allow_logical = True, disk_dict = None):
+ s = self.get_partition_scheme(disk,disk_dict=disk_dict)
+ if not s: return
+ # We want to convert GUID_partition_scheme to GUID
+ # We also want to translate FDisk to MBR
+ joined = " ".join(["MBR" if x.lower() == "fdisk" else x.capitalize() if x!=x.upper() else x for x in s.replace("_"," ").split() if x])
+ return joined
+
+ def get_content(self, disk, disk_dict = None):
+ return self._get_value(disk,"DAMediaContent",disk_dict=disk_dict)
+
+ def get_volume_name(self, disk, disk_dict = None):
+ return self._get_value(disk,"DAVolumeName",disk_dict=disk_dict)
+
+ def get_volume_uuid(self, disk, disk_dict = None):
+ return self._get_value(disk,"DAVolumeUUID",disk_dict=disk_dict)
+
+ def get_disk_uuid(self, disk, disk_dict = None):
+ return self._get_value(disk,"DAMediaUUID",disk_dict=disk_dict)
+
+ def get_mount_point(self, disk, disk_dict = None):
+ return self._get_value(disk,"DAVolumePath",disk_dict=disk_dict)
+
+ def open_mount_point(self, disk, new_window = False, disk_dict = None):
+ disk = self.get_identifier(disk,disk_dict=disk_dict)
+ if not disk: return
+ mount = self.get_mount_point(disk)
+ if not mount: return
+ return self.r.run({"args":["open", mount]})[2] == 0
+
+ def compare_version(self, v1, v2):
+ # Splits the version numbers by periods and compare each value
+ # Allows 0.0.10 > 0.0.9 where normal string comparison would return false
+ # Also strips out any non-numeric values from each segment to avoid conflicts
+ #
+ # Returns True if v1 > v2, None if v1 == v2, and False if v1 < v2
+ if not all((isinstance(x,str) for x in (v1,v2))):
+ # Wrong types
+ return False
+ v1_seg = v1.split(".")
+ v2_seg = v2.split(".")
+ # Pad with 0s to ensure common length
+ v1_seg += ["0"]*(len(v2_seg)-len(v1_seg))
+ v2_seg += ["0"]*(len(v1_seg)-len(v2_seg))
+ # Compare each segment - stripping non-numbers as needed
+ for i in range(len(v1_seg)):
+ a,b = v1_seg[i],v2_seg[i]
+ try: a = int("".join([x for x in a if x.isdigit()]))
+ except: a = 0
+ try: b = int("".join([x for x in b if x.isdigit()]))
+ except: b = 0
+ if a > b: return True
+ if a < b: return False
+ # If we're here, both versions are the same
+ return None
+
+ def needs_sudo(self, disk = None, disk_dict = None):
+ # Default to EFI if we didn't pass a disk
+ if not disk: return self.compare_version(self.full_os_version,self.sudo_mount_version) in (True,None)
+ return self.compare_version(self.full_os_version,self.sudo_mount_version) in (True,None) and self.get_content(disk,disk_dict=disk_dict).upper() in self.efi_guids
+
+ def mount_partition(self, disk, disk_dict = None):
+ disk = self.get_identifier(disk,disk_dict=disk_dict)
+ if not disk: return
+ sudo = self.needs_sudo(disk,disk_dict=disk_dict)
+ out = self.r.run({"args":["diskutil","mount",disk],"sudo":sudo})
+ self.update()
+ return out
+
+ def unmount_partition(self, disk, disk_dict = None):
+ disk = self.get_identifier(disk,disk_dict=disk_dict)
+ if not disk: return
+ out = self.r.run({"args":["diskutil","unmount",disk]})
+ self.update()
+ return out
+
+ def is_mounted(self, disk, disk_dict = None):
+ disk = self.get_identifier(disk,disk_dict=disk_dict)
+ if not disk: return
+ m = self.get_mount_point(disk,disk_dict=disk_dict)
+ return (m != None and len(m))
+
+ def get_volumes(self, disk_dict = None):
+ # Returns a list object with all volumes from disks
+ return sorted((disk_dict or self.disks).get("VolumesFromDisks",[]))
+
+if __name__ == '__main__':
+ d = Disk()
+ # Gather the args
+ errors = []
+ args = []
+ for x in sys.argv[1:]:
+ if x == "/":
+ args.append(x)
+ continue
+ if x.endswith("/"):
+ x = x[:-1]
+ if not x.lower().startswith("/volumes/") or len(x.split("/")) > 3:
+ errors.append("'{}' is not a volume.".format(x))
+ continue
+ if not os.path.exists(x):
+ # Doesn't exist, skip it
+ errors.append("'{}' does not exist.".format(x))
+ continue
+ args.append(x)
+ mount_list = []
+ needs_sudo = d.needs_sudo()
+ for x in args:
+ name = d.get_volume_name(x)
+ if not name: name = "Untitled"
+ name = name.replace('"','\\"') # Escape double quotes in names
+ efi = d.get_efi(x)
+ if efi: mount_list.append((efi,name,d.is_mounted(efi),"diskutil mount {}".format(efi)))
+ else: errors.append("'{}' has no ESP.".format(name))
+ if mount_list:
+ # We have something to mount
+ efis = [x[-1] for x in mount_list if not x[2]] # Only mount those that aren't mounted
+ names = [x[1] for x in mount_list if not x[2]]
+ if efis: # We have something to mount here
+ command = "do shell script \"{}\" with prompt \"MountEFI would like to mount the ESP{} on {}\"{}".format(
+ "; ".join(efis),
+ "s" if len(names) > 1 else "",
+ ", ".join(names),
+ " with administrator privileges" if needs_sudo else "")
+ o,e,r = d.r.run({"args":["osascript","-e",command]})
+ if r > 0 and len(e.strip()) and e.strip().lower().endswith("(-128)"): exit() # User canceled, bail
+ # Update the disks
+ d.update()
+ # Walk the mounts and find out which aren't mounted
+ for efi,name,mounted,comm in mount_list:
+ mounted_at = d.get_mount_point(efi)
+ if mounted_at: d.open_mount_point(mounted_at)
+ else: errors.append("ESP for '{}' failed to mount.".format(name))
+ else:
+ errors.append("No disks with ESPs selected.")
+ if errors:
+ # Display our errors before we leave
+ d.r.run({"args":["osascript","-e","display dialog \"{}\" buttons {{\"OK\"}} default button \"OK\" with icon caution".format("\n".join(errors))]})
\ No newline at end of file
diff --git a/Utils/mountefi/Mount EFI.workflow/Contents/Scripts/diskdump b/Utils/mountefi/Mount EFI.workflow/Contents/Scripts/diskdump
new file mode 100755
index 0000000..b81958f
Binary files /dev/null and b/Utils/mountefi/Mount EFI.workflow/Contents/Scripts/diskdump differ
diff --git a/Utils/mountefi/Mount EFI.workflow/Contents/Scripts/plist.py b/Utils/mountefi/Mount EFI.workflow/Contents/Scripts/plist.py
new file mode 100755
index 0000000..e47ddd4
--- /dev/null
+++ b/Utils/mountefi/Mount EFI.workflow/Contents/Scripts/plist.py
@@ -0,0 +1,612 @@
+### ###
+# Imports #
+### ###
+
+import datetime, os, plistlib, struct, sys, itertools
+from io import BytesIO
+
+if sys.version_info < (3,0):
+ # Force use of StringIO instead of cStringIO as the latter
+ # has issues with Unicode strings
+ from StringIO import StringIO
+
+try:
+ basestring # Python 2
+ unicode
+except NameError:
+ basestring = str # Python 3
+ unicode = str
+
+try:
+ FMT_XML = plistlib.FMT_XML
+ FMT_BINARY = plistlib.FMT_BINARY
+except AttributeError:
+ FMT_XML = "FMT_XML"
+ FMT_BINARY = "FMT_BINARY"
+
+### ###
+# Helper Methods #
+### ###
+
+def wrap_data(value):
+ if not _check_py3(): return plistlib.Data(value)
+ return value
+
+def extract_data(value):
+ if not _check_py3() and isinstance(value,plistlib.Data): return value.data
+ return value
+
+def _check_py3():
+ return sys.version_info >= (3, 0)
+
+def _is_binary(fp):
+ if isinstance(fp, basestring):
+ return fp.startswith(b"bplist00")
+ header = fp.read(32)
+ fp.seek(0)
+ return header[:8] == b'bplist00'
+
+### ###
+# Deprecated Functions - Remapped #
+### ###
+
+def readPlist(pathOrFile):
+ if not isinstance(pathOrFile, basestring):
+ return load(pathOrFile)
+ with open(pathOrFile, "rb") as f:
+ return load(f)
+
+def writePlist(value, pathOrFile):
+ if not isinstance(pathOrFile, basestring):
+ return dump(value, pathOrFile, fmt=FMT_XML, sort_keys=True, skipkeys=False)
+ with open(pathOrFile, "wb") as f:
+ return dump(value, f, fmt=FMT_XML, sort_keys=True, skipkeys=False)
+
+### ###
+# Remapped Functions #
+### ###
+
+def load(fp, fmt=None, use_builtin_types=None, dict_type=dict):
+ if _check_py3():
+ use_builtin_types = True if use_builtin_types == None else use_builtin_types
+ # We need to monkey patch this to allow for hex integers - code taken/modified from
+ # https://github.com/python/cpython/blob/3.8/Lib/plistlib.py
+ if fmt is None:
+ header = fp.read(32)
+ fp.seek(0)
+ for info in plistlib._FORMATS.values():
+ if info['detect'](header):
+ P = info['parser']
+ break
+ else:
+ raise plistlib.InvalidFileException()
+ else:
+ P = plistlib._FORMATS[fmt]['parser']
+ try:
+ p = P(use_builtin_types=use_builtin_types, dict_type=dict_type)
+ except:
+ # Python 3.9 removed use_builtin_types
+ p = P(dict_type=dict_type)
+ if isinstance(p,plistlib._PlistParser):
+ # Monkey patch!
+ def end_integer():
+ d = p.get_data()
+ value = int(d,16) if d.lower().startswith("0x") else int(d)
+ if -1 << 63 <= value < 1 << 63:
+ p.add_object(value)
+ else:
+ raise OverflowError("Integer overflow at line {}".format(p.parser.CurrentLineNumber))
+ def end_data():
+ try:
+ p.add_object(plistlib._decode_base64(p.get_data()))
+ except Exception as e:
+ raise Exception("Data error at line {}: {}".format(p.parser.CurrentLineNumber,e))
+ p.end_integer = end_integer
+ p.end_data = end_data
+ return p.parse(fp)
+ elif not _is_binary(fp):
+ # Is not binary - assume a string - and try to load
+ # We avoid using readPlistFromString() as that uses
+ # cStringIO and fails when Unicode strings are detected
+ # Don't subclass - keep the parser local
+ from xml.parsers.expat import ParserCreate
+ # Create a new PlistParser object - then we need to set up
+ # the values and parse.
+ p = plistlib.PlistParser()
+ parser = ParserCreate()
+ parser.StartElementHandler = p.handleBeginElement
+ parser.EndElementHandler = p.handleEndElement
+ parser.CharacterDataHandler = p.handleData
+ # We also need to monkey patch this to allow for other dict_types, hex int support
+ # proper line output for data errors, and for unicode string decoding
+ def begin_dict(attrs):
+ d = dict_type()
+ p.addObject(d)
+ p.stack.append(d)
+ def end_integer():
+ d = p.getData()
+ value = int(d,16) if d.lower().startswith("0x") else int(d)
+ if -1 << 63 <= value < 1 << 63:
+ p.addObject(value)
+ else:
+ raise OverflowError("Integer overflow at line {}".format(parser.CurrentLineNumber))
+ def end_data():
+ try:
+ p.addObject(plistlib.Data.fromBase64(p.getData()))
+ except Exception as e:
+ raise Exception("Data error at line {}: {}".format(parser.CurrentLineNumber,e))
+ def end_string():
+ d = p.getData()
+ if isinstance(d,unicode):
+ d = d.encode("utf-8")
+ p.addObject(d)
+ p.begin_dict = begin_dict
+ p.end_integer = end_integer
+ p.end_data = end_data
+ p.end_string = end_string
+ if isinstance(fp, unicode):
+ # Encode unicode -> string; use utf-8 for safety
+ fp = fp.encode("utf-8")
+ if isinstance(fp, basestring):
+ # It's a string - let's wrap it up
+ fp = StringIO(fp)
+ # Parse it
+ parser.ParseFile(fp)
+ return p.root
+ else:
+ use_builtin_types = False if use_builtin_types == None else use_builtin_types
+ try:
+ p = _BinaryPlistParser(use_builtin_types=use_builtin_types, dict_type=dict_type)
+ except:
+ # Python 3.9 removed use_builtin_types
+ p = _BinaryPlistParser(dict_type=dict_type)
+ return p.parse(fp)
+
+def loads(value, fmt=None, use_builtin_types=None, dict_type=dict):
+ if _check_py3() and isinstance(value, basestring):
+ # If it's a string - encode it
+ value = value.encode()
+ try:
+ return load(BytesIO(value),fmt=fmt,use_builtin_types=use_builtin_types,dict_type=dict_type)
+ except:
+ # Python 3.9 removed use_builtin_types
+ return load(BytesIO(value),fmt=fmt,dict_type=dict_type)
+def dump(value, fp, fmt=FMT_XML, sort_keys=True, skipkeys=False):
+ if _check_py3():
+ plistlib.dump(value, fp, fmt=fmt, sort_keys=sort_keys, skipkeys=skipkeys)
+ else:
+ if fmt == FMT_XML:
+ # We need to monkey patch a bunch here too in order to avoid auto-sorting
+ # of keys
+ writer = plistlib.PlistWriter(fp)
+ def writeDict(d):
+ if d:
+ writer.beginElement("dict")
+ items = sorted(d.items()) if sort_keys else d.items()
+ for key, value in items:
+ if not isinstance(key, basestring):
+ if skipkeys:
+ continue
+ raise TypeError("keys must be strings")
+ writer.simpleElement("key", key)
+ writer.writeValue(value)
+ writer.endElement("dict")
+ else:
+ writer.simpleElement("dict")
+ writer.writeDict = writeDict
+ writer.writeln("")
+ writer.writeValue(value)
+ writer.writeln("")
+ elif fmt == FMT_BINARY:
+ # Assume binary at this point
+ writer = _BinaryPlistWriter(fp, sort_keys=sort_keys, skipkeys=skipkeys)
+ writer.write(value)
+ else:
+ # Not a proper format
+ raise ValueError("Unsupported format: {}".format(fmt))
+
+def dumps(value, fmt=FMT_XML, skipkeys=False, sort_keys=True):
+ if _check_py3():
+ return plistlib.dumps(value, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys).decode("utf-8")
+ else:
+ # We avoid using writePlistToString() as that uses
+ # cStringIO and fails when Unicode strings are detected
+ f = StringIO()
+ dump(value, f, fmt=fmt, skipkeys=skipkeys, sort_keys=sort_keys)
+ return f.getvalue()
+
+### ###
+# Binary Plist Stuff For Py2 #
+### ###
+
+# From the python 3 plistlib.py source: https://github.com/python/cpython/blob/3.7/Lib/plistlib.py
+# Tweaked to function on Python 2
+
+class InvalidFileException (ValueError):
+ def __init__(self, message="Invalid file"):
+ ValueError.__init__(self, message)
+
+_BINARY_FORMAT = {1: 'B', 2: 'H', 4: 'L', 8: 'Q'}
+
+_undefined = object()
+
+class _BinaryPlistParser:
+ """
+ Read or write a binary plist file, following the description of the binary
+ format. Raise InvalidFileException in case of error, otherwise return the
+ root object.
+ see also: http://opensource.apple.com/source/CF/CF-744.18/CFBinaryPList.c
+ """
+ def __init__(self, use_builtin_types, dict_type):
+ self._use_builtin_types = use_builtin_types
+ self._dict_type = dict_type
+
+ def parse(self, fp):
+ try:
+ # The basic file format:
+ # HEADER
+ # object...
+ # refid->offset...
+ # TRAILER
+ self._fp = fp
+ self._fp.seek(-32, os.SEEK_END)
+ trailer = self._fp.read(32)
+ if len(trailer) != 32:
+ raise InvalidFileException()
+ (
+ offset_size, self._ref_size, num_objects, top_object,
+ offset_table_offset
+ ) = struct.unpack('>6xBBQQQ', trailer)
+ self._fp.seek(offset_table_offset)
+ self._object_offsets = self._read_ints(num_objects, offset_size)
+ self._objects = [_undefined] * num_objects
+ return self._read_object(top_object)
+
+ except (OSError, IndexError, struct.error, OverflowError,
+ UnicodeDecodeError):
+ raise InvalidFileException()
+
+ def _get_size(self, tokenL):
+ """ return the size of the next object."""
+ if tokenL == 0xF:
+ m = ord(self._fp.read(1)[0]) & 0x3
+ s = 1 << m
+ f = '>' + _BINARY_FORMAT[s]
+ return struct.unpack(f, self._fp.read(s))[0]
+
+ return tokenL
+
+ def _read_ints(self, n, size):
+ data = self._fp.read(size * n)
+ if size in _BINARY_FORMAT:
+ return struct.unpack('>' + _BINARY_FORMAT[size] * n, data)
+ else:
+ if not size or len(data) != size * n:
+ raise InvalidFileException()
+ return tuple(int.from_bytes(data[i: i + size], 'big')
+ for i in range(0, size * n, size))
+
+ def _read_refs(self, n):
+ return self._read_ints(n, self._ref_size)
+
+ def _read_object(self, ref):
+ """
+ read the object by reference.
+ May recursively read sub-objects (content of an array/dict/set)
+ """
+ result = self._objects[ref]
+ if result is not _undefined:
+ return result
+
+ offset = self._object_offsets[ref]
+ self._fp.seek(offset)
+ token = ord(self._fp.read(1)[0])
+ tokenH, tokenL = token & 0xF0, token & 0x0F
+
+ if token == 0: # \x00 or 0x00
+ result = None
+
+ elif token == 8: # \x08 or 0x08
+ result = False
+
+ elif token == 9: # \x09 or 0x09
+ result = True
+
+ # The referenced source code also mentions URL (0x0c, 0x0d) and
+ # UUID (0x0e), but neither can be generated using the Cocoa libraries.
+
+ elif token == 15: # \x0f or 0x0f
+ result = b''
+
+ elif tokenH == 0x10: # int
+ result = 0
+ for k in range((2 << tokenL) - 1):
+ result = (result << 8) + ord(self._fp.read(1))
+ # result = int.from_bytes(self._fp.read(1 << tokenL),
+ # 'big', signed=tokenL >= 3)
+
+ elif token == 0x22: # real
+ result = struct.unpack('>f', self._fp.read(4))[0]
+
+ elif token == 0x23: # real
+ result = struct.unpack('>d', self._fp.read(8))[0]
+
+ elif token == 0x33: # date
+ f = struct.unpack('>d', self._fp.read(8))[0]
+ # timestamp 0 of binary plists corresponds to 1/1/2001
+ # (year of Mac OS X 10.0), instead of 1/1/1970.
+ result = (datetime.datetime(2001, 1, 1) +
+ datetime.timedelta(seconds=f))
+
+ elif tokenH == 0x40: # data
+ s = self._get_size(tokenL)
+ if self._use_builtin_types:
+ result = self._fp.read(s)
+ else:
+ result = plistlib.Data(self._fp.read(s))
+
+ elif tokenH == 0x50: # ascii string
+ s = self._get_size(tokenL)
+ result = self._fp.read(s).decode('ascii')
+ result = result
+
+ elif tokenH == 0x60: # unicode string
+ s = self._get_size(tokenL)
+ result = self._fp.read(s * 2).decode('utf-16be')
+
+ # tokenH == 0x80 is documented as 'UID' and appears to be used for
+ # keyed-archiving, not in plists.
+
+ elif tokenH == 0xA0: # array
+ s = self._get_size(tokenL)
+ obj_refs = self._read_refs(s)
+ result = []
+ self._objects[ref] = result
+ result.extend(self._read_object(x) for x in obj_refs)
+
+ # tokenH == 0xB0 is documented as 'ordset', but is not actually
+ # implemented in the Apple reference code.
+
+ # tokenH == 0xC0 is documented as 'set', but sets cannot be used in
+ # plists.
+
+ elif tokenH == 0xD0: # dict
+ s = self._get_size(tokenL)
+ key_refs = self._read_refs(s)
+ obj_refs = self._read_refs(s)
+ result = self._dict_type()
+ self._objects[ref] = result
+ for k, o in zip(key_refs, obj_refs):
+ key = self._read_object(k)
+ if isinstance(key, plistlib.Data):
+ key = key.data
+ result[key] = self._read_object(o)
+
+ else:
+ raise InvalidFileException()
+
+ self._objects[ref] = result
+ return result
+
+def _count_to_size(count):
+ if count < 1 << 8:
+ return 1
+
+ elif count < 1 << 16:
+ return 2
+
+ elif count << 1 << 32:
+ return 4
+
+ else:
+ return 8
+
+_scalars = (str, int, float, datetime.datetime, bytes)
+
+class _BinaryPlistWriter (object):
+ def __init__(self, fp, sort_keys, skipkeys):
+ self._fp = fp
+ self._sort_keys = sort_keys
+ self._skipkeys = skipkeys
+
+ def write(self, value):
+
+ # Flattened object list:
+ self._objlist = []
+
+ # Mappings from object->objectid
+ # First dict has (type(object), object) as the key,
+ # second dict is used when object is not hashable and
+ # has id(object) as the key.
+ self._objtable = {}
+ self._objidtable = {}
+
+ # Create list of all objects in the plist
+ self._flatten(value)
+
+ # Size of object references in serialized containers
+ # depends on the number of objects in the plist.
+ num_objects = len(self._objlist)
+ self._object_offsets = [0]*num_objects
+ self._ref_size = _count_to_size(num_objects)
+
+ self._ref_format = _BINARY_FORMAT[self._ref_size]
+
+ # Write file header
+ self._fp.write(b'bplist00')
+
+ # Write object list
+ for obj in self._objlist:
+ self._write_object(obj)
+
+ # Write refnum->object offset table
+ top_object = self._getrefnum(value)
+ offset_table_offset = self._fp.tell()
+ offset_size = _count_to_size(offset_table_offset)
+ offset_format = '>' + _BINARY_FORMAT[offset_size] * num_objects
+ self._fp.write(struct.pack(offset_format, *self._object_offsets))
+
+ # Write trailer
+ sort_version = 0
+ trailer = (
+ sort_version, offset_size, self._ref_size, num_objects,
+ top_object, offset_table_offset
+ )
+ self._fp.write(struct.pack('>5xBBBQQQ', *trailer))
+
+ def _flatten(self, value):
+ # First check if the object is in the object table, not used for
+ # containers to ensure that two subcontainers with the same contents
+ # will be serialized as distinct values.
+ if isinstance(value, _scalars):
+ if (type(value), value) in self._objtable:
+ return
+
+ elif isinstance(value, plistlib.Data):
+ if (type(value.data), value.data) in self._objtable:
+ return
+
+ elif id(value) in self._objidtable:
+ return
+
+ # Add to objectreference map
+ refnum = len(self._objlist)
+ self._objlist.append(value)
+ if isinstance(value, _scalars):
+ self._objtable[(type(value), value)] = refnum
+ elif isinstance(value, plistlib.Data):
+ self._objtable[(type(value.data), value.data)] = refnum
+ else:
+ self._objidtable[id(value)] = refnum
+
+ # And finally recurse into containers
+ if isinstance(value, dict):
+ keys = []
+ values = []
+ items = value.items()
+ if self._sort_keys:
+ items = sorted(items)
+
+ for k, v in items:
+ if not isinstance(k, basestring):
+ if self._skipkeys:
+ continue
+ raise TypeError("keys must be strings")
+ keys.append(k)
+ values.append(v)
+
+ for o in itertools.chain(keys, values):
+ self._flatten(o)
+
+ elif isinstance(value, (list, tuple)):
+ for o in value:
+ self._flatten(o)
+
+ def _getrefnum(self, value):
+ if isinstance(value, _scalars):
+ return self._objtable[(type(value), value)]
+ elif isinstance(value, plistlib.Data):
+ return self._objtable[(type(value.data), value.data)]
+ else:
+ return self._objidtable[id(value)]
+
+ def _write_size(self, token, size):
+ if size < 15:
+ self._fp.write(struct.pack('>B', token | size))
+
+ elif size < 1 << 8:
+ self._fp.write(struct.pack('>BBB', token | 0xF, 0x10, size))
+
+ elif size < 1 << 16:
+ self._fp.write(struct.pack('>BBH', token | 0xF, 0x11, size))
+
+ elif size < 1 << 32:
+ self._fp.write(struct.pack('>BBL', token | 0xF, 0x12, size))
+
+ else:
+ self._fp.write(struct.pack('>BBQ', token | 0xF, 0x13, size))
+
+ def _write_object(self, value):
+ ref = self._getrefnum(value)
+ self._object_offsets[ref] = self._fp.tell()
+ if value is None:
+ self._fp.write(b'\x00')
+
+ elif value is False:
+ self._fp.write(b'\x08')
+
+ elif value is True:
+ self._fp.write(b'\x09')
+
+ elif isinstance(value, int):
+ if value < 0:
+ try:
+ self._fp.write(struct.pack('>Bq', 0x13, value))
+ except struct.error:
+ raise OverflowError(value) # from None
+ elif value < 1 << 8:
+ self._fp.write(struct.pack('>BB', 0x10, value))
+ elif value < 1 << 16:
+ self._fp.write(struct.pack('>BH', 0x11, value))
+ elif value < 1 << 32:
+ self._fp.write(struct.pack('>BL', 0x12, value))
+ elif value < 1 << 63:
+ self._fp.write(struct.pack('>BQ', 0x13, value))
+ elif value < 1 << 64:
+ self._fp.write(b'\x14' + value.to_bytes(16, 'big', signed=True))
+ else:
+ raise OverflowError(value)
+
+ elif isinstance(value, float):
+ self._fp.write(struct.pack('>Bd', 0x23, value))
+
+ elif isinstance(value, datetime.datetime):
+ f = (value - datetime.datetime(2001, 1, 1)).total_seconds()
+ self._fp.write(struct.pack('>Bd', 0x33, f))
+
+ elif isinstance(value, plistlib.Data):
+ self._write_size(0x40, len(value.data))
+ self._fp.write(value.data)
+
+ elif isinstance(value, basestring):
+ try:
+ t = value.encode('ascii')
+ self._write_size(0x50, len(value))
+ except UnicodeEncodeError:
+ t = value.encode('utf-16be')
+ self._write_size(0x60, len(t) // 2)
+ self._fp.write(t)
+
+ elif isinstance(value, (bytes, bytearray)):
+ self._write_size(0x40, len(value))
+ self._fp.write(value)
+
+ elif isinstance(value, (list, tuple)):
+ refs = [self._getrefnum(o) for o in value]
+ s = len(refs)
+ self._write_size(0xA0, s)
+ self._fp.write(struct.pack('>' + self._ref_format * s, *refs))
+
+ elif isinstance(value, dict):
+ keyRefs, valRefs = [], []
+
+ if self._sort_keys:
+ rootItems = sorted(value.items())
+ else:
+ rootItems = value.items()
+
+ for k, v in rootItems:
+ if not isinstance(k, basestring):
+ if self._skipkeys:
+ continue
+ raise TypeError("keys must be strings")
+ keyRefs.append(self._getrefnum(k))
+ valRefs.append(self._getrefnum(v))
+
+ s = len(keyRefs)
+ self._write_size(0xD0, s)
+ self._fp.write(struct.pack('>' + self._ref_format * s, *keyRefs))
+ self._fp.write(struct.pack('>' + self._ref_format * s, *valRefs))
+
+ else:
+ raise TypeError(value)
diff --git a/Utils/mountefi/Mount EFI.workflow/Contents/Scripts/run.py b/Utils/mountefi/Mount EFI.workflow/Contents/Scripts/run.py
new file mode 100755
index 0000000..b586adc
--- /dev/null
+++ b/Utils/mountefi/Mount EFI.workflow/Contents/Scripts/run.py
@@ -0,0 +1,151 @@
+import sys, subprocess, time, threading, shlex
+try:
+ from Queue import Queue, Empty
+except:
+ from queue import Queue, Empty
+
+ON_POSIX = 'posix' in sys.builtin_module_names
+
+class Run:
+
+ def __init__(self):
+ return
+
+ def _read_output(self, pipe, q):
+ try:
+ for line in iter(lambda: pipe.read(1), b''):
+ q.put(line)
+ except ValueError:
+ pass
+ pipe.close()
+
+ def _create_thread(self, output):
+ # Creates a new queue and thread object to watch based on the output pipe sent
+ q = Queue()
+ t = threading.Thread(target=self._read_output, args=(output, q))
+ t.daemon = True
+ return (q,t)
+
+ def _stream_output(self, comm, shell = False):
+ output = error = ""
+ p = None
+ try:
+ if shell and type(comm) is list:
+ comm = " ".join(shlex.quote(x) for x in comm)
+ if not shell and type(comm) is str:
+ comm = shlex.split(comm)
+ p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0, universal_newlines=True, close_fds=ON_POSIX)
+ # Setup the stdout thread/queue
+ q,t = self._create_thread(p.stdout)
+ qe,te = self._create_thread(p.stderr)
+ # Start both threads
+ t.start()
+ te.start()
+
+ while True:
+ c = z = ""
+ try: c = q.get_nowait()
+ except Empty: pass
+ else:
+ sys.stdout.write(c)
+ output += c
+ sys.stdout.flush()
+ try: z = qe.get_nowait()
+ except Empty: pass
+ else:
+ sys.stderr.write(z)
+ error += z
+ sys.stderr.flush()
+ if not c==z=="": continue # Keep going until empty
+ # No output - see if still running
+ p.poll()
+ if p.returncode != None:
+ # Subprocess ended
+ break
+ # No output, but subprocess still running - stall for 20ms
+ time.sleep(0.02)
+
+ o, e = p.communicate()
+ return (output+o, error+e, p.returncode)
+ except:
+ if p:
+ try: o, e = p.communicate()
+ except: o = e = ""
+ return (output+o, error+e, p.returncode)
+ return ("", "Command not found!", 1)
+
+ def _decode(self, value, encoding="utf-8", errors="ignore"):
+ # Helper method to only decode if bytes type
+ if sys.version_info >= (3,0) and isinstance(value, bytes):
+ return value.decode(encoding,errors)
+ return value
+
+ def _run_command(self, comm, shell = False):
+ c = None
+ try:
+ if shell and type(comm) is list:
+ comm = " ".join(shlex.quote(x) for x in comm)
+ if not shell and type(comm) is str:
+ comm = shlex.split(comm)
+ p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ c = p.communicate()
+ except:
+ if c == None:
+ return ("", "Command not found!", 1)
+ return (self._decode(c[0]), self._decode(c[1]), p.returncode)
+
+ def run(self, command_list, leave_on_fail = False):
+ # Command list should be an array of dicts
+ if type(command_list) is dict:
+ # We only have one command
+ command_list = [command_list]
+ output_list = []
+ for comm in command_list:
+ args = comm.get("args", [])
+ shell = comm.get("shell", False)
+ stream = comm.get("stream", False)
+ sudo = comm.get("sudo", False)
+ stdout = comm.get("stdout", False)
+ stderr = comm.get("stderr", False)
+ mess = comm.get("message", None)
+ show = comm.get("show", False)
+
+ if not mess == None:
+ print(mess)
+
+ if not len(args):
+ # nothing to process
+ continue
+ if sudo:
+ # Check if we have sudo
+ out = self._run_command(["which", "sudo"])
+ if "sudo" in out[0]:
+ # Can sudo
+ if type(args) is list:
+ args.insert(0, out[0].replace("\n", "")) # add to start of list
+ elif type(args) is str:
+ args = out[0].replace("\n", "") + " " + args # add to start of string
+
+ if show:
+ print(" ".join(args))
+
+ if stream:
+ # Stream it!
+ out = self._stream_output(args, shell)
+ else:
+ # Just run and gather output
+ out = self._run_command(args, shell)
+ if stdout and len(out[0]):
+ print(out[0])
+ if stderr and len(out[1]):
+ print(out[1])
+ # Append output
+ output_list.append(out)
+ # Check for errors
+ if leave_on_fail and out[2] != 0:
+ # Got an error - leave
+ break
+ if len(output_list) == 1:
+ # We only ran one command - just return that output
+ return output_list[0]
+ return output_list
diff --git a/Utils/mountefi/Mount EFI.workflow/Contents/document.wflow b/Utils/mountefi/Mount EFI.workflow/Contents/document.wflow
index e96d9f2..6a5b8a5 100644
--- a/Utils/mountefi/Mount EFI.workflow/Contents/document.wflow
+++ b/Utils/mountefi/Mount EFI.workflow/Contents/document.wflow
@@ -3,7 +3,7 @@
AMApplicationBuild
- 509
+ 512
AMApplicationVersion
2.10
AMDocumentVersion
@@ -51,7 +51,7 @@
ActionParameters
source
- on run {input, parameters}
set the_list to {}
repeat with the_path in input
set the_text to POSIX path of (the_path as text)
set end of the_list to the_text
end repeat
return the_list
end run
+ on run {input, parameters}
# Hacky nonsense to export paths that aren't normally available
set export to "export PATH=/usr/local/bin:/usr/local/sbin/:$PATH; "
# Try to get the system python versions - starting with py 3
set python to ""
repeat with py in {"python3", "python"}
try
set pypath to do shell script export & "which " & py
if pypath is "/usr/bin/python3" then
# Check if we're running 10.15 or newer - and verify "xcode-select -p"
# runs properly before allowing the stub
if (do shell script "sw_vers -productVersion") ≥ "10.15" then
do shell script "xcode-select -p"
end if
end if
do shell script export & "/usr/bin/env " & py & " -V"
set python to py
exit repeat
on error errorMsg
# Didn't resolve - onto the next
end try
end repeat
if python is "" then
my print_error("Could not locate python3 or python via /usr/bin/env!", "Please download and install the latest from python.org")
return
end if
# If we got here - we have a python version to run.
# Organize the input paths
set passed_paths to ""
repeat with the_path in input
set the_text to quoted form of POSIX path of (the_path as text) as string
set passed_paths to passed_paths & " " & the_text
end repeat
# Let's make sure we have disk.py file
set target to "disk.py"
# Get the contents of the ~/Library/Services folder, and look for disk.py to run
set services to POSIX path of (path to home folder) & "Library/Services/"
set quick_action_list to do shell script "ls -1 " & quoted form of services
set quick_actions to paragraphs of quick_action_list
repeat with qa in quick_actions
set test_path to services & qa & "/Contents/Scripts/" & target
if my check_exists(test_path) is "0" then
# We got a valid path - let's run it
do shell script export & "/usr/bin/env " & python & " " & quoted form of test_path & passed_paths
return
end if
end repeat
# If we got here - then we didn't find the quick action
my print_error("Required files were not found!", "Please reinstall the latest Mount EFI quick action from https://github.com/corpnewt/MountEFI")
return
end run
on check_exists(path)
# Quick one-liner to abuse bash and see if a file/folder exists
return do shell script "if [[ -e " & quoted form of path & " ]]; then echo 0; else echo 1; fi"
end check_exists
on print_error(header, footer)
beep
display alert header & "
" & footer
end print_error
BundleIdentifier
com.apple.Automator.RunScript
@@ -67,18 +67,16 @@
Class Name
RunScriptAction
- IgnoresInput
-
InputUUID
- 75C1496C-F97C-463E-8C60-9352C5A4B9C5
+ 34AED1F9-B31F-4927-A2F3-43392E378CAC
Keywords
Run
OutputUUID
- E8D5BFA7-19E8-444A-A035-17C400DBCEFA
+ EAB751F1-47E9-4256-9F92-ED4CD9CF95F5
UUID
- D6C847AF-3800-48CD-B4BA-DBFB7C59F37A
+ FDE567E1-0DA9-43F5-B7DC-9D30FD0B8553
UnlocalizedApplications
Automator
@@ -105,529 +103,18 @@ end run
isViewVisible
-
+ 1
location
- 784.000000:368.000000
+ 301.500000:558.000000
nibPath
/System/Library/Automator/Run AppleScript.action/Contents/Resources/Base.lproj/main.nib
isViewVisible
-
-
-
- action
-
- AMAccepts
-
- Container
- List
- Optional
-
- Types
-
- com.apple.cocoa.string
-
-
- AMActionVersion
- 2.0.3
- AMApplication
-
- Automator
-
- AMParameterProperties
-
- COMMAND_STRING
-
- CheckedForUserDefaultShell
-
- inputMethod
-
- shell
-
- source
-
-
- AMProvides
-
- Container
- List
- Types
-
- com.apple.cocoa.string
-
-
- ActionBundlePath
- /System/Library/Automator/Run Shell Script.action
- ActionName
- Run Shell Script
- ActionParameters
-
- COMMAND_STRING
- import subprocess, plistlib, sys, os, shlex
-
-class Disk:
-
- def __init__(self):
- self.diskutil = "diskutil" # self.get_diskutil()
- self.os_version = self.run(["sw_vers", "-productVersion"])[0].strip()
- self.sudo_mount_version = "10.13.6"
- self.sudo_mount_types = ["efi"]
- self.apfs = {}
- self._update_disks()
-
- def run(self, comm, shell = False):
- c = None
- try:
- if shell and type(comm) is list: comm = " ".join(shlex.quote(x) for x in comm)
- if not shell and type(comm) is str: comm = shlex.split(comm)
- p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- c = p.communicate()
- except:
- if c == None: return ("", "Command not found!", 1)
- return (self._get_str(c[0]), self._get_str(c[1]), p.returncode)
-
- def _get_str(self, val):
- # Helper method to return a string value based on input type
- if sys.version_info >= (3,0) and isinstance(val, bytes): return val.encode("utf-8")
- return val
-
- def _get_plist(self, s):
- p = {}
- try:
- if sys.version_info >= (3, 0):
- p = plistlib.loads(s.encode("utf-8"))
- else:
- # We avoid using readPlistFromString() as that uses
- # cStringIO and fails when Unicode strings are detected
- # Don't subclass - keep the parser local
- from xml.parsers.expat import ParserCreate
- # Create a new PlistParser object - then we need to set up
- # the values and parse.
- pa = plistlib.PlistParser()
- # We also monkey patch this to encode unicode as utf-8
- def end_string():
- d = pa.getData()
- if isinstance(d,unicode):
- d = d.encode("utf-8")
- pa.addObject(d)
- pa.end_string = end_string
- parser = ParserCreate()
- parser.StartElementHandler = pa.handleBeginElement
- parser.EndElementHandler = pa.handleEndElement
- parser.CharacterDataHandler = pa.handleData
- if isinstance(s, unicode):
- # Encode unicode -> string; use utf-8 for safety
- s = s.encode("utf-8")
- # Parse the string
- parser.Parse(s, 1)
- p = pa.root
- except Exception as e:
- print(e)
- return p
-
- def update(self):
- self._update_disks()
-
- def _update_disks(self):
- self.disks = self.get_disks()
- self.disk_text = self.get_disk_text()
- self.apfs = self.get_apfs() if self.os_version >= "10.12" else {}
-
- def get_disks(self):
- # Returns a dictionary object of connected disks
- disk_list = self.run([self.diskutil, "list", "-plist"])[0]
- return self._get_plist(disk_list)
-
- def get_disk_text(self):
- # Returns plain text listing connected disks
- return self.run([self.diskutil, "list"])[0]
-
- def get_apfs(self):
- # Returns a dictionary object of apfs disks
- output = self.run("echo y | " + self.diskutil + " apfs list -plist", True)
- if not output[2] == 0: return {} # Error getting apfs info - return an empty dict
- disk_list = output[0]
- p_list = disk_list.split("<?xml")
- if len(p_list) > 1: disk_list = "<?xml" + p_list[-1] # We had text before the start - get only the plist info
- return self._get_plist(disk_list)
-
- def is_apfs(self, disk):
- disk_id = self.get_identifier(disk)
- if not disk_id: return None
- # Takes a disk identifier, and returns whether or not it's apfs
- for d in self.disks.get("AllDisksAndPartitions", []):
- if not "APFSVolumes" in d: continue
- if d.get("DeviceIdentifier", "").lower() == disk_id.lower():
- return True
- for a in d.get("APFSVolumes", []):
- if a.get("DeviceIdentifier", "").lower() == disk_id.lower():
- return True
- return False
-
- def is_apfs_container(self, disk):
- disk_id = self.get_identifier(disk)
- if not disk_id: return None
- # Takes a disk identifier, and returns whether or not that specific
- # disk/volume is an APFS Container
- for d in self.disks.get("AllDisksAndPartitions", []):
- # Only check partitions
- for p in d.get("Partitions", []):
- if disk_id.lower() == p.get("DeviceIdentifier", "").lower():
- return p.get("Content", "").lower() == "apple_apfs"
- return False
-
- def is_cs_container(self, disk):
- disk_id = self.get_identifier(disk)
- if not disk_id: return None
- # Takes a disk identifier, and returns whether or not that specific
- # disk/volume is an CoreStorage Container
- for d in self.disks.get("AllDisksAndPartitions", []):
- # Only check partitions
- for p in d.get("Partitions", []):
- if disk_id.lower() == p.get("DeviceIdentifier", "").lower():
- return p.get("Content", "").lower() == "apple_corestorage"
- return False
-
- def is_core_storage(self, disk):
- disk_id = self.get_identifier(disk)
- if not disk_id: return None
- if self._get_physical_disk(disk_id, "Logical Volume on "): return True
- return False
-
- def get_identifier(self, disk):
- # Should be able to take a mount point, disk name, or disk identifier,
- # and return the disk's identifier
- # Iterate!!
- if not disk or not len(self._get_str(disk)): return None
- disk = disk.lower()
- if disk.startswith("/dev/r"): disk = disk[len("/dev/r"):]
- elif disk.startswith("/dev/"): disk = disk[len("/dev/"):]
- if disk in self.disks.get("AllDisks", []): return disk
- for d in self.disks.get("AllDisksAndPartitions", []):
- for a in d.get("APFSVolumes", []):
- if disk in [ a.get(x, "").lower() for x in ["DeviceIdentifier", "VolumeName", "VolumeUUID", "DiskUUID", "MountPoint"] ]:
- return a.get("DeviceIdentifier", None)
- for a in d.get("Partitions", []):
- if disk in [ a.get(x, "").lower() for x in ["DeviceIdentifier", "VolumeName", "VolumeUUID", "DiskUUID", "MountPoint"] ]:
- return a.get("DeviceIdentifier", None)
- # At this point, we didn't find it
- return None
-
- def get_top_identifier(self, disk):
- disk_id = self.get_identifier(disk)
- if not disk_id: return None
- return disk_id.replace("disk", "didk").split("s")[0].replace("didk", "disk")
-
- def _get_physical_disk(self, disk, search_term):
- # Change disk0s1 to disk0
- our_disk = self.get_top_identifier(disk)
- our_term = "/dev/" + our_disk
- found_disk = False
- our_text = ""
- for line in self.disk_text.split("\n"):
- if line.lower().startswith(our_term):
- found_disk = True
- continue
- if not found_disk: continue
- if line.lower().startswith("/dev/disk"):
- # At the next disk - bail
- break
- if search_term.lower() in line.lower():
- our_text = line
- break
- if not len(our_text): return None # Nothing found
- our_stores = "".join(our_text.strip().split(search_term)[1:]).split(" ,")
- if not len(our_stores): return None
- for store in our_stores:
- efi = self.get_efi(store)
- if efi: return store
- return None
-
- def get_physical_store(self, disk):
- # Returns the physical store containing the EFI
- disk_id = self.get_identifier(disk)
- if not disk_id or not self.is_apfs(disk_id): return None
- return self._get_physical_disk(disk_id, "Physical Store ")
-
- def get_core_storage_pv(self, disk):
- # Returns the core storage physical volume containing the EFI
- disk_id = self.get_identifier(disk)
- if not disk_id or not self.is_core_storage(disk_id): return None
- return self._get_physical_disk(disk_id, "Logical Volume on ")
-
- def get_parent(self, disk):
- # Disk can be a mount point, disk name, or disk identifier
- disk_id = self.get_identifier(disk)
- if self.is_apfs(disk_id): disk_id = self.get_physical_store(disk_id)
- elif self.is_core_storage(disk_id): disk_id = self.get_core_storage_pv(disk_id)
- if not disk_id: return None
- if self.is_apfs(disk_id):
- # We have apfs - let's get the container ref
- for a in self.apfs.get("Containers", []):
- # Check if it's the whole container
- if a.get("ContainerReference", "").lower() == disk_id.lower():
- return a["ContainerReference"]
- # Check through each volume and return the parent's container ref
- for v in a.get("Volumes", []):
- if v.get("DeviceIdentifier", "").lower() == disk_id.lower():
- return a.get("ContainerReference", None)
- else:
- # Not apfs - go through all volumes and whole disks
- for d in self.disks.get("AllDisksAndPartitions", []):
- if d.get("DeviceIdentifier", "").lower() == disk_id.lower():
- return d["DeviceIdentifier"]
- for p in d.get("Partitions", []):
- if p.get("DeviceIdentifier", "").lower() == disk_id.lower():
- return d["DeviceIdentifier"]
- # Didn't find anything
- return None
-
- def get_efi(self, disk):
- disk_id = self.get_parent(self.get_identifier(disk))
- if not disk_id: return None
- # At this point - we should have the parent
- for d in self.disks["AllDisksAndPartitions"]:
- if d.get("DeviceIdentifier", "").lower() == disk_id.lower():
- # Found our disk
- for p in d.get("Partitions", []):
- if p.get("Content", "").lower() == "efi":
- return p.get("DeviceIdentifier", None)
- return None
-
- def needs_sudo(self, disk_id = None):
- content = "EFI" # Default to EFI content
- if disk_id: content = self.get_content(disk_id)
- return self.os_version >= self.sudo_mount_version and content.lower() in self.sudo_mount_types
-
- def is_mounted(self, disk):
- disk_id = self.get_identifier(disk)
- if not disk_id: return None
- m = self.get_mount_point(disk_id)
- return (m != None and len(m)>0)
-
- def _get_value(self, disk, field, default = None, apfs_only = False):
- disk_id = self.get_identifier(disk)
- if not disk_id:
- return None
- # Takes a disk identifier, and returns the requested value
- for d in self.disks.get("AllDisksAndPartitions", []):
- for a in d.get("APFSVolumes", []):
- if a.get("DeviceIdentifier", "").lower() == disk_id.lower():
- return a.get(field, default)
- if apfs_only:
- # Skip looking at regular partitions
- continue
- if d.get("DeviceIdentifier", "").lower() == disk_id.lower():
- return d.get(field, default)
- for a in d.get("Partitions", []):
- if a.get("DeviceIdentifier", "").lower() == disk_id.lower():
- return a.get(field, default)
- return None
-
- # Getter methods
- def get_content(self, disk):
- return self._get_value(disk, "Content")
-
- def get_mount_point(self, disk):
- return self._get_value(disk, "MountPoint")
-
- def get_volume_name(self, disk):
- return self._get_value(disk, "VolumeName")
-
- def open_mount_point(self, disk, new_window = False):
- disk_id = self.get_identifier(disk)
- if not disk_id: return None
- mount = self.get_mount_point(disk_id)
- if not mount: return None
- out = self.run(["open", mount])
- return out[2] == 0
-
-if __name__ == '__main__':
- d = Disk()
- # Gather the args
- errors = []
- args = []
- for x in sys.argv[1:]:
- if x == "/":
- args.append(x)
- continue
- if not x.lower().startswith("/volumes/"):
- errors.append("'{}' is not a volume.".format(x))
- continue
- if x.endswith("/"):
- x = x[:-1]
- if len(x.split("/")) > 3:
- # Too nested - not a volume
- errors.append("'{}' is not a volume.".format(x))
- continue
- if not os.path.exists(x):
- # Doesn't exist, skip it
- errors.append("'{}' does not exist.".format(x))
- continue
- args.append(x)
- mount_list = []
- needs_sudo = d.needs_sudo()
- for x in args:
- name = d.get_volume_name(x)
- if not name: name = "Untitled"
- name = name.replace('"','\\"') # Escape double quotes in names
- efi = d.get_efi(x)
- if efi: mount_list.append((efi,name,d.is_mounted(efi),"diskutil mount {}".format(efi)))
- else: errors.append("'{}' has no ESP.".format(name))
- if len(mount_list):
- # We have something to mount
- efis = [x[-1] for x in mount_list if not x[2]] # Only mount those that aren't mounted
- names = [x[1] for x in mount_list if not x[2]]
- if len(efis): # We have something to mount here
- command = "do shell script \"{}\" with prompt \"MountEFI would like to mount the ESP{} on {}\"{}".format(
- "; ".join(efis),
- "s" if len(names) > 1 else "",
- ", ".join(names),
- " with administrator privileges" if needs_sudo else "")
- o,e,r = d.run(["osascript","-e",command])
- if r > 0 and len(e.strip()) and e.strip().lower().endswith("(-128)"): exit() # User canceled, bail
- # Update the disks
- d.update()
- # Walk the mounts and find out which aren't mounted
- for efi,name,mounted,comm in mount_list:
- mounted_at = d.get_mount_point(efi)
- if mounted_at: d.open_mount_point(mounted_at)
- else: errors.append("ESP for '{}' failed to mount.".format(name))
- else:
- errors.append("No disks with ESPs selected.")
- if len(errors):
- # Display our errors before we leave
- d.run(["osascript","-e","display dialog \"{}\" buttons {{\"OK\"}} default button \"OK\" with icon caution".format("\n".join(errors))])
- CheckedForUserDefaultShell
-
- inputMethod
- 1
- shell
- /usr/bin/python
- source
-
-
- BundleIdentifier
- com.apple.RunShellScript
- CFBundleVersion
- 2.0.3
- CanShowSelectedItemsWhenRun
-
- CanShowWhenRun
-
- Category
-
- AMCategoryUtilities
-
- Class Name
- RunShellScriptAction
- InputUUID
- 1EF3F5D2-601A-45CB-A0F3-2073CDF75D6D
- Keywords
-
- Shell
- Script
- Command
- Run
- Unix
-
- OutputUUID
- A943503D-31E8-4E6D-91EB-046A35336942
- UUID
- 9AE9FCB9-5186-4567-A3FB-08D7175D30F6
- UnlocalizedApplications
-
- Automator
-
- arguments
-
- 0
-
- default value
- 0
- name
- inputMethod
- required
- 0
- type
- 0
- uuid
- 0
-
- 1
-
- default value
-
- name
- CheckedForUserDefaultShell
- required
- 0
- type
- 0
- uuid
- 1
-
- 2
-
- default value
-
- name
- source
- required
- 0
- type
- 0
- uuid
- 2
-
- 3
-
- default value
-
- name
- COMMAND_STRING
- required
- 0
- type
- 0
- uuid
- 3
-
- 4
-
- default value
- /bin/sh
- name
- shell
- required
- 0
- type
- 0
- uuid
- 4
-
-
- isViewVisible
-
- location
- 784.000000:1486.000000
- nibPath
- /System/Library/Automator/Run Shell Script.action/Contents/Resources/Base.lproj/main.nib
-
- isViewVisible
-
+ 1
connectors
-
- EFDA46C2-B5B5-4365-A046-D576516A3332
-
- from
- D6C847AF-3800-48CD-B4BA-DBFB7C59F37A - D6C847AF-3800-48CD-B4BA-DBFB7C59F37A
- to
- 9AE9FCB9-5186-4567-A3FB-08D7175D30F6 - 9AE9FCB9-5186-4567-A3FB-08D7175D30F6
-
-
+
workflowMetaData
applicationBundleIDsByPath
diff --git a/Utils/ocvalidate/README.md b/Utils/ocvalidate/README.md
index 1cef087..0aaf5c6 100644
--- a/Utils/ocvalidate/README.md
+++ b/Utils/ocvalidate/README.md
@@ -52,6 +52,7 @@ Utility to validate whether a `config.plist` matches requirements and convention
- Entry[N]->BundlePath: Filename should have `.kext` suffix.
- Entry[N]->PlistPath: Filename should have `.plist` suffix.
- Entry[N]: If `Lilu.kext` is used, `DisableLinkeditJettison` should be enabled in `Kernel->Quirks`.
+- `BrcmFirmwareRepo.kext` must not be injected by OpenCore.
- For some known kexts, their `BundlePath`, `ExecutablePath`, and `PlistPath` must match against each other. Current list of rules can be found [here](https://github.com/acidanthera/OpenCorePkg/blob/master/Utilities/ocvalidate/KextInfo.c).
- Plugin kext must be placed after parent kext. For example, [plugins of Lilu](https://github.com/acidanthera/Lilu/blob/master/KnownPlugins.md) must be placed after `Lilu.kext`.
#### Delete
diff --git a/Utils/ocvalidate/ocvalidate b/Utils/ocvalidate/ocvalidate
index 0db3dcb..055016c 100755
Binary files a/Utils/ocvalidate/ocvalidate and b/Utils/ocvalidate/ocvalidate differ
diff --git a/Utils/ocvalidate/ocvalidate.exe b/Utils/ocvalidate/ocvalidate.exe
index ed23353..47f7de7 100755
Binary files a/Utils/ocvalidate/ocvalidate.exe and b/Utils/ocvalidate/ocvalidate.exe differ
diff --git a/Utils/ocvalidate/ocvalidate.linux b/Utils/ocvalidate/ocvalidate.linux
index a7e5dea..e288d6f 100755
Binary files a/Utils/ocvalidate/ocvalidate.linux and b/Utils/ocvalidate/ocvalidate.linux differ