This commit is contained in:
2023-09-26 13:06:48 -04:00
34 changed files with 538 additions and 2199 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+204
View File
@@ -0,0 +1,204 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleGetInfoString</key>
<string>v1.0</string>
<key>CFBundleIdentifier</key>
<string>com.corpnewt.USBMap</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>USBMap</string>
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>IOKitPersonalities</key>
<dict>
<key>iMacPro1,1-XHCI</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.apple.driver.AppleUSBHostMergeProperties</string>
<key>IOClass</key>
<string>AppleUSBHostMergeProperties</string>
<key>IOParentMatch</key>
<dict>
<key>IOPropertyMatch</key>
<dict>
<key>pcidebug</key>
<string>0:20:0</string>
</dict>
</dict>
<key>IOProviderClass</key>
<string>AppleUSBXHCIPCI</string>
<key>IOProviderMergeProperties</key>
<dict>
<key>kUSBMuxEnabled</key>
<true/>
<key>port-count</key>
<data>
DQAAAA==
</data>
<key>ports</key>
<dict>
<key>HS01</key>
<dict>
<key>UsbConnector</key>
<integer>0</integer>
<key>port</key>
<data>
AQAAAA==
</data>
</dict>
<key>HS02</key>
<dict>
<key>UsbConnector</key>
<integer>0</integer>
<key>port</key>
<data>
AgAAAA==
</data>
</dict>
<key>HS03</key>
<dict>
<key>UsbConnector</key>
<integer>0</integer>
<key>port</key>
<data>
AwAAAA==
</data>
</dict>
<key>HS04</key>
<dict>
<key>#port</key>
<data>
BAAAAA==
</data>
<key>UsbConnector</key>
<integer>3</integer>
</dict>
<key>HS05</key>
<dict>
<key>UsbConnector</key>
<integer>255</integer>
<key>port</key>
<data>
BQAAAA==
</data>
</dict>
<key>HS06</key>
<dict>
<key>UsbConnector</key>
<integer>255</integer>
<key>port</key>
<data>
BgAAAA==
</data>
</dict>
<key>HS07</key>
<dict>
<key>UsbConnector</key>
<integer>0</integer>
<key>port</key>
<data>
BwAAAA==
</data>
</dict>
<key>HS08</key>
<dict>
<key>UsbConnector</key>
<integer>0</integer>
<key>port</key>
<data>
CAAAAA==
</data>
</dict>
<key>HS09</key>
<dict>
<key>#port</key>
<data>
CQAAAA==
</data>
<key>UsbConnector</key>
<integer>3</integer>
</dict>
<key>HS10</key>
<dict>
<key>#port</key>
<data>
CgAAAA==
</data>
<key>UsbConnector</key>
<integer>3</integer>
</dict>
<key>HS11</key>
<dict>
<key>#port</key>
<data>
CwAAAA==
</data>
<key>UsbConnector</key>
<integer>3</integer>
</dict>
<key>HS12</key>
<dict>
<key>#port</key>
<data>
DAAAAA==
</data>
<key>UsbConnector</key>
<integer>3</integer>
</dict>
<key>SS01</key>
<dict>
<key>UsbConnector</key>
<integer>3</integer>
<key>port</key>
<data>
DQAAAA==
</data>
</dict>
<key>SS02</key>
<dict>
<key>#port</key>
<data>
DgAAAA==
</data>
<key>UsbConnector</key>
<integer>3</integer>
</dict>
<key>SS03</key>
<dict>
<key>#port</key>
<data>
DwAAAA==
</data>
<key>UsbConnector</key>
<integer>3</integer>
</dict>
<key>SS04</key>
<dict>
<key>#port</key>
<data>
EAAAAA==
</data>
<key>UsbConnector</key>
<integer>3</integer>
</dict>
</dict>
</dict>
<key>model</key>
<string>iMacPro1,1</string>
</dict>
</dict>
<key>OSBundleRequired</key>
<string>Root</string>
</dict>
</plist>
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildMachineOSBuild</key>
<string>18G95</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>USBWakeFixup</string>
<key>CFBundleIdentifier</key>
<string>com.osy86.USBWakeFixup</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>USBWakeFixup</string>
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>MacOSX</string>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
<string>10G8</string>
<key>DTPlatformVersion</key>
<string>GM</string>
<key>DTSDKBuild</key>
<string>18G74</string>
<key>DTSDKName</key>
<string>macosx10.14</string>
<key>DTXcode</key>
<string>1030</string>
<key>DTXcodeBuild</key>
<string>10G8</string>
<key>IOKitPersonalities</key>
<dict>
<key>Fake XHCI</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.osy86.USBWakeFixup</string>
<key>IOClass</key>
<string>USBWakeFixup</string>
<key>IONameMatch</key>
<string>PNP0D10</string>
<key>IOPropertyMatch</key>
<dict>
<key>_UID</key>
<string>WAKE</string>
</dict>
<key>IOProviderClass</key>
<string>IOACPIPlatformDevice</string>
</dict>
</dict>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 osy86. All rights reserved.</string>
<key>OSBundleLibraries</key>
<dict>
<key>com.apple.iokit.IOACPIFamily</key>
<string>1.0d1</string>
<key>com.apple.kpi.iokit</key>
<string>7.0</string>
<key>com.apple.kpi.libkern</key>
<string>8.0d0</string>
</dict>
</dict>
</plist>
+262 -24
View File
@@ -8,7 +8,7 @@
<array>
<dict>
<key>Comment</key>
<string>Read the comment in dsl sample</string>
<string>SSDT-AWAC.aml</string>
<key>Enabled</key>
<true/>
<key>Path</key>
@@ -16,15 +16,31 @@
</dict>
<dict>
<key>Comment</key>
<string>Read the comment in dsl sample</string>
<string>SSDT-EC.aml</string>
<key>Enabled</key>
<true/>
<key>Path</key>
<string>SSDT-EC-USBX-DESKTOP.aml</string>
<string>SSDT-EC.aml</string>
</dict>
<dict>
<key>Comment</key>
<string>Read the comment in dsl sample</string>
<string>SSDT-GPRW.aml</string>
<key>Enabled</key>
<true/>
<key>Path</key>
<string>SSDT-GPRW.aml</string>
</dict>
<dict>
<key>Comment</key>
<string>SSDT-HPET.aml</string>
<key>Enabled</key>
<true/>
<key>Path</key>
<string>SSDT-HPET.aml</string>
</dict>
<dict>
<key>Comment</key>
<string>SSDT-PLUG-ALT.aml</string>
<key>Enabled</key>
<true/>
<key>Path</key>
@@ -32,17 +48,214 @@
</dict>
<dict>
<key>Comment</key>
<string>Read the comment in dsl sample</string>
<string>SSDT-SBUS-MCHC.aml</string>
<key>Enabled</key>
<true/>
<key>Path</key>
<string>SSDT-RHUB.aml</string>
<string>SSDT-SBUS-MCHC.aml</string>
</dict>
<dict>
<key>Comment</key>
<string>SSDT-USBW.aml</string>
<key>Enabled</key>
<true/>
<key>Path</key>
<string>SSDT-USBW.aml</string>
</dict>
<dict>
<key>Comment</key>
<string>SSDT-USBX.aml</string>
<key>Enabled</key>
<true/>
<key>Path</key>
<string>SSDT-USBX.aml</string>
</dict>
</array>
<key>Delete</key>
<array/>
<key>Patch</key>
<array/>
<array>
<dict>
<key>Base</key>
<string></string>
<key>BaseSkip</key>
<integer>0</integer>
<key>Comment</key>
<string>HPET _STA to XSTA Rename</string>
<key>Count</key>
<integer>0</integer>
<key>Enabled</key>
<true/>
<key>Find</key>
<data>EV9TVEE=</data>
<key>Limit</key>
<integer>0</integer>
<key>Mask</key>
<data></data>
<key>OemTableId</key>
<data>AAAAAA==</data>
<key>Replace</key>
<data>EVhTVEE=</data>
<key>ReplaceMask</key>
<data></data>
<key>Skip</key>
<integer>0</integer>
<key>TableLength</key>
<integer>0</integer>
<key>TableSignature</key>
<data>AAAAAA==</data>
</dict>
<dict>
<key>Base</key>
<string></string>
<key>BaseSkip</key>
<integer>0</integer>
<key>Comment</key>
<string>HPET _CRS to XCRS Rename</string>
<key>Count</key>
<integer>0</integer>
<key>Enabled</key>
<true/>
<key>Find</key>
<data>JV9DUlM=</data>
<key>Limit</key>
<integer>0</integer>
<key>Mask</key>
<data></data>
<key>OemTableId</key>
<data>AAAAAA==</data>
<key>Replace</key>
<data>JVhDUlM=</data>
<key>ReplaceMask</key>
<data></data>
<key>Skip</key>
<integer>0</integer>
<key>TableLength</key>
<integer>0</integer>
<key>TableSignature</key>
<data>AAAAAA==</data>
</dict>
<dict>
<key>Base</key>
<string></string>
<key>BaseSkip</key>
<integer>0</integer>
<key>Comment</key>
<string>IPIC IRQ 2 Patch</string>
<key>Count</key>
<integer>0</integer>
<key>Enabled</key>
<true/>
<key>Find</key>
<data>IgQAeQA=</data>
<key>Limit</key>
<integer>0</integer>
<key>Mask</key>
<data></data>
<key>OemTableId</key>
<data>AAAAAA==</data>
<key>Replace</key>
<data>IgAAeQA=</data>
<key>ReplaceMask</key>
<data></data>
<key>Skip</key>
<integer>0</integer>
<key>TableLength</key>
<integer>0</integer>
<key>TableSignature</key>
<data>AAAAAA==</data>
</dict>
<dict>
<key>Base</key>
<string></string>
<key>BaseSkip</key>
<integer>0</integer>
<key>Comment</key>
<string>RTC IRQ 8 Patch</string>
<key>Count</key>
<integer>0</integer>
<key>Enabled</key>
<true/>
<key>Find</key>
<data>IgABeQA=</data>
<key>Limit</key>
<integer>0</integer>
<key>Mask</key>
<data></data>
<key>OemTableId</key>
<data>AAAAAA==</data>
<key>Replace</key>
<data>IgAAeQA=</data>
<key>ReplaceMask</key>
<data></data>
<key>Skip</key>
<integer>0</integer>
<key>TableLength</key>
<integer>0</integer>
<key>TableSignature</key>
<data>AAAAAA==</data>
</dict>
<dict>
<key>Base</key>
<string></string>
<key>BaseSkip</key>
<integer>0</integer>
<key>Comment</key>
<string>TIMR IRQ 0 Patch</string>
<key>Count</key>
<integer>0</integer>
<key>Enabled</key>
<true/>
<key>Find</key>
<data>IgEAeQA=</data>
<key>Limit</key>
<integer>0</integer>
<key>Mask</key>
<data></data>
<key>OemTableId</key>
<data>AAAAAA==</data>
<key>Replace</key>
<data>IgAAeQA=</data>
<key>ReplaceMask</key>
<data></data>
<key>Skip</key>
<integer>0</integer>
<key>TableLength</key>
<integer>0</integer>
<key>TableSignature</key>
<data>AAAAAA==</data>
</dict>
<dict>
<key>Base</key>
<string></string>
<key>BaseSkip</key>
<integer>0</integer>
<key>Comment</key>
<string>change Method(GPRW,2,N) to XPRW, pair with SSDT-GPRW.aml</string>
<key>Count</key>
<integer>0</integer>
<key>Enabled</key>
<true/>
<key>Find</key>
<data>R1BSVw==</data>
<key>Limit</key>
<integer>0</integer>
<key>Mask</key>
<data></data>
<key>OemTableId</key>
<data></data>
<key>Replace</key>
<data>WFBSVw==</data>
<key>ReplaceMask</key>
<data></data>
<key>Skip</key>
<integer>0</integer>
<key>TableLength</key>
<integer>0</integer>
<key>TableSignature</key>
<data></data>
</dict>
</array>
<key>Quirks</key>
<dict>
<key>FadtEnableReset</key>
@@ -113,23 +326,12 @@
<dict>
<key>Add</key>
<dict>
<key># PciRoot(0x0)/Pci(0x1B,0x2)/Pci(0x0,0x0)</key>
<key>PciRoot(0x0)/Pci(0x1F,0x3)</key>
<dict>
<key>device-id</key>
<data>8hUAAA==</data>
<key>device_type</key>
<string>Ethernet Controller</string>
<key>layout-id</key>
<integer>66</integer>
<key>model</key>
<string>Intel(R) Ethernet Controller i225-LM</string>
</dict>
<key># PciRoot(0x0)/Pci(0x1B,0x3)/Pci(0x0,0x0)</key>
<dict>
<key>device-id</key>
<data>8hUAAA==</data>
<key>device_type</key>
<string>Ethernet Controller</string>
<key>model</key>
<string>Intel(R) Ethernet Controller i225-LM</string>
<string>Realtek ALC897</string>
</dict>
</dict>
<key>Delete</key>
@@ -337,6 +539,42 @@
<key>PlistPath</key>
<string>Contents/Info.plist</string>
</dict>
<dict>
<key>Arch</key>
<string>x86_64</string>
<key>BundlePath</key>
<string>USBMap.kext</string>
<key>Comment</key>
<string></string>
<key>Enabled</key>
<true/>
<key>ExecutablePath</key>
<string></string>
<key>MaxKernel</key>
<string></string>
<key>MinKernel</key>
<string></string>
<key>PlistPath</key>
<string>Contents/Info.plist</string>
</dict>
<dict>
<key>Arch</key>
<string>Any</string>
<key>BundlePath</key>
<string>USBWakeFixup.kext</string>
<key>Comment</key>
<string>USBWakeFixup.kext</string>
<key>Enabled</key>
<true/>
<key>ExecutablePath</key>
<string>Contents/MacOS/USBWakeFixup</string>
<key>MaxKernel</key>
<string></string>
<key>MinKernel</key>
<string></string>
<key>PlistPath</key>
<string>Contents/Info.plist</string>
</dict>
</array>
<key>Block</key>
<array/>
@@ -474,7 +712,7 @@
<key>PickerAudioAssist</key>
<false/>
<key>PickerMode</key>
<string>External</string>
<string>Builtin</string>
<key>PickerVariant</key>
<string>Auto</string>
<key>PollAppleHotKeys</key>
@@ -642,7 +880,7 @@
<key>MaxBIOSVersion</key>
<false/>
<key>ProcessorType</key>
<integer>0</integer>
<integer>3841</integer>
<key>ROM</key>
<data>8LR5Nk5V</data>
<key>SpoofVendor</key>
-8
View File
@@ -1,8 +0,0 @@
## macrecovery
macrecovery is a tool that helps to automate recovery interaction. It can be used to download diagnostics and recovery as well as analyse MLB.
Requires python3 to run. Run with `-h` argument to see all available arguments.
To create a disk image for a virtual machine installation use `build-image.sh`.
-79
View File
@@ -1,79 +0,0 @@
{
"Mac-EE2EBD4B90B839A8": "13.4",
"Mac-BE0E8AC46FE800CC": "11.7.7",
"Mac-9AE82516C7C6B903": "12.6.6",
"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.7",
"Mac-7DF21CB3ED6977E5": "11.7.7",
"Mac-9F18E312C5C2BF0B": "12.6.6",
"Mac-937CB26E2E02BB01": "12.6.6",
"Mac-827FAC58A8FDFA22": "latest",
"Mac-226CB3C6A851A671": "latest",
"Mac-0CFF9C7C2B63DF8D": "latest",
"Mac-C3EC7CD22292981F": "10.15.7",
"Mac-AFD8A9D944EA4843": "10.15.7",
"Mac-189A3D4F975D5FFC": "11.7.7",
"Mac-3CBD00234E554E41": "11.7.7",
"Mac-2BD1B31983FE1663": "11.7.7",
"Mac-06F11FD93F0323C5": "12.6.6",
"Mac-06F11F11946D27C5": "12.6.6",
"Mac-E43C1C25D4880AD6": "12.6.6",
"Mac-473D31EABEB93F9B": "12.6.6",
"Mac-66E35819EE2D0D05": "12.6.6",
"Mac-A5C67F76ED83108C": "12.6.6",
"Mac-B4831CEBD52A0C4C": "13.4",
"Mac-CAD6701F7CEA0921": "13.4",
"Mac-551B86E5744E2388": "13.4",
"Mac-937A206F2EE63C01": "latest",
"Mac-827FB448E656EC26": "latest",
"Mac-1E7E29AD0135F9BC": "latest",
"Mac-53FDB3D8DB8CA971": "latest",
"Mac-E1008331FDC96864": "latest",
"Mac-5F9802EFE386AA28": "latest",
"Mac-E7203C0F68AA0004": "latest",
"Mac-A61BADE1FDAD7B05": "latest",
"Mac-F22589C8": "10.13.6",
"Mac-94245B3640C91C81": "10.13.6",
"Mac-94245A3940C91C80": "10.13.6",
"Mac-942459F5819B171B": "10.13.6",
"Mac-4B7AC7E43945597E": "10.15.7",
"Mac-6F01561E16C75D06": "10.15.7",
"Mac-F60DEB81FF30ACF6": "12.6.6",
"Mac-27AD2F918AE68F61": "latest",
"Mac-F2208EC8": "10.13.6",
"Mac-8ED6AF5B48C039E1": "10.13.6",
"Mac-4BC72D62AD45599E": "10.13.6",
"Mac-7BA5B2794B2CDB12": "10.13.6",
"Mac-031AEE4D24BFF0B1": "10.15.7",
"Mac-F65AE981FFA204ED": "10.15.7",
"Mac-35C5E08120C7EEAF": "12.6.6",
"Mac-7BA5B2DFE22DDD8C": "latest",
"Mac-942B5BF58194151B": "10.13.6",
"Mac-942B59F58194171B": "10.13.6",
"Mac-00BE6ED71E35EB86": "10.15.7",
"Mac-FC02E91DDD3FA6A4": "10.15.7",
"Mac-7DF2A3B5E5D671ED": "10.15.7",
"Mac-031B6874CF7F642A": "10.15.7",
"Mac-27ADBB7B4CEE8E61": "10.15.7",
"Mac-77EB7D7DAF985301": "10.15.7",
"Mac-81E3E92DD6088272": "11.7.7",
"Mac-42FD25EABCABB274": "11.7.7",
"Mac-A369DDC4E67F1C45": "12.6.6",
"Mac-FFE5EF870D7BA81A": "12.6.6",
"Mac-DB15BD556843C820": "12.6.6",
"Mac-65CE76090165799A": "12.6.6",
"Mac-B809C3757DA9BB8D": "12.6.6",
"Mac-4B682C642B45593E": "13.4",
"Mac-77F17D7DA9285301": "13.4",
"Mac-BE088AF8C5EB4FA2": "13.4",
"Mac-AA95B1DDAB278B95": "latest",
"Mac-63001698E7A34814": "latest",
"Mac-CFF7D910A743CAAF": "latest",
"Mac-AF89B6D9451A490B": "latest",
"Mac-7BA5B2D9E42DDD94": "latest"
}
-18
View File
@@ -1,18 +0,0 @@
#!/bin/bash -e
rm -rf Recovery.RO.dmg Recovery.RO.raw Recovery.dmg.sparseimage
hdiutil create -size 800m -layout "UNIVERSAL HD" -type SPARSE -o Recovery.dmg
newDevice=$(hdiutil attach -nomount Recovery.dmg.sparseimage | head -n 1 | awk '{print $1}')
echo newdevice "$newDevice"
diskutil partitionDisk "${newDevice}" 1 MBR fat32 RECOVERY R
N=$(echo "$newDevice" | tr -dc '0-9')
diskutil mount disk"${N}"s1
MOUNT="$(diskutil info disk"${N}"s1 | sed -n 's/.*Mount Point: *//p')"
mkdir -p "$MOUNT/com.apple.recovery.boot"
cp ./*.dmg ./*.chunklist "$MOUNT/com.apple.recovery.boot/"
diskutil umount disk"${N}"s1
hdiutil detach "$newDevice"
hdiutil convert -format UDZO Recovery.dmg.sparseimage -o Recovery.RO.dmg
rm Recovery.dmg.sparseimage
qemu-img convert -f dmg -O raw Recovery.RO.dmg Recovery.raw
rm Recovery.RO.dmg
@@ -1,11 +0,0 @@
@echo off
rem Download imagem Recovery macOS Big Sur
rem
rem Gabriel Luchina
rem Universo Hackintosh
rem https://universohackintosh.com.br
cls
python ./macrecovery.py -b Mac-42FD25EABCABB274 -m 00000000000000000 download
@@ -1,11 +0,0 @@
@echo off
rem Download imagem Recovery macOS Catalina
rem
rem Gabriel Luchina
rem Universo Hackintosh
rem https://universohackintosh.com.br
cls
python ./macrecovery.py -b Mac-00BE6ED71E35EB86 -m 00000000000000000 download
@@ -1,11 +0,0 @@
@echo off
rem Download imagem Recovery macOS Monterey
rem
rem Gabriel Luchina
rem Universo Hackintosh
rem https://universohackintosh.com.br
cls
python ./macrecovery.py -b Mac-E43C1C25D4880AD6 -m 00000000000000000 download
@@ -1,11 +0,0 @@
@echo off
rem Download imagem Recovery macOS Ventura
rem
rem Gabriel Luchina
rem Universo Hackintosh
rem https://universohackintosh.com.br
cls
python ./macrecovery.py -b Mac-B4831CEBD52A0C4C -m 00000000000000000 download
-502
View File
@@ -1,502 +0,0 @@
#!/usr/bin/env python3
"""
Gather recovery information for Macs.
Copyright (c) 2019, vit9696
"""
import argparse
import binascii
import hashlib
import json
import linecache
import os
import random
import struct
import sys
try:
from urllib.request import Request, HTTPError, urlopen
from urllib.parse import urlparse
except ImportError:
from urllib2 import Request, HTTPError, urlopen
from urlparse import urlparse
SELF_DIR = os.path.dirname(os.path.realpath(__file__))
RECENT_MAC = 'Mac-7BA5B2D9E42DDD94'
MLB_ZERO = '00000000000000000'
MLB_VALID = 'C02749200YGJ803AX'
MLB_PRODUCT = '00000000000J80300'
TYPE_SID = 16
TYPE_K = 64
TYPE_FG = 64
INFO_PRODUCT = 'AP'
INFO_IMAGE_LINK = 'AU'
INFO_IMAGE_HASH = 'AH'
INFO_IMAGE_SESS = 'AT'
INFO_SIGN_LINK = 'CU'
INFO_SIGN_HASH = 'CH'
INFO_SIGN_SESS = 'CT'
INFO_REQURED = [INFO_PRODUCT, INFO_IMAGE_LINK, INFO_IMAGE_HASH, INFO_IMAGE_SESS, INFO_SIGN_LINK, INFO_SIGN_HASH, INFO_SIGN_SESS]
def run_query(url, headers, post=None, raw=False):
if post is not None:
data = '\n'.join([entry + '=' + post[entry] for entry in post])
if sys.version_info[0] >= 3:
data = data.encode('utf-8')
else:
data = None
req = Request(url=url, headers=headers, data=data)
try:
response = urlopen(req)
if raw:
return response
return dict(response.info()), response.read()
except HTTPError as e:
print(f'ERROR: "{e}" when connecting to {url}')
sys.exit(1)
def generate_id(id_type, id_value=None):
valid_chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
return ''.join(random.choice(valid_chars) for i in range(id_type)) if not id_value else id_value
def product_mlb(mlb):
return '00000000000' + mlb[11] + mlb[12] + mlb[13] + mlb[14] + '00'
def mlb_from_eeee(eeee):
if len(eeee) != 4:
print('ERROR: Invalid EEEE code length!')
sys.exit(1)
return f'00000000000{eeee}00'
def int_from_unsigned_bytes(byte_list, byteorder):
if byteorder == 'little':
byte_list = byte_list[::-1]
encoded = binascii.hexlify(byte_list)
return int(encoded, 16)
# zhangyoufu https://gist.github.com/MCJack123/943eaca762730ca4b7ae460b731b68e7#gistcomment-3061078 2021-10-08
Apple_EFI_ROM_public_key_1 = 0xC3E748CAD9CD384329E10E25A91E43E1A762FF529ADE578C935BDDF9B13F2179D4855E6FC89E9E29CA12517D17DFA1EDCE0BEBF0EA7B461FFE61D94E2BDF72C196F89ACD3536B644064014DAE25A15DB6BB0852ECBD120916318D1CCDEA3C84C92ED743FC176D0BACA920D3FCF3158AFF731F88CE0623182A8ED67E650515F75745909F07D415F55FC15A35654D118C55A462D37A3ACDA08612F3F3F6571761EFCCBCC299AEE99B3A4FD6212CCFFF5EF37A2C334E871191F7E1C31960E010A54E86FA3F62E6D6905E1CD57732410A3EB0C6B4DEFDABE9F59BF1618758C751CD56CEF851D1C0EAA1C558E37AC108DA9089863D20E2E7E4BF475EC66FE6B3EFDCF
ChunkListHeader = struct.Struct('<4sIBBBxQQQ')
assert ChunkListHeader.size == 0x24
Chunk = struct.Struct('<I32s')
assert Chunk.size == 0x24
def verify_chunklist(cnkpath):
with open(cnkpath, 'rb') as f:
hash_ctx = hashlib.sha256()
data = f.read(ChunkListHeader.size)
hash_ctx.update(data)
magic, header_size, file_version, chunk_method, signature_method, chunk_count, chunk_offset, signature_offset = ChunkListHeader.unpack(data)
assert magic == b'CNKL'
assert header_size == ChunkListHeader.size
assert file_version == 1
assert chunk_method == 1
assert signature_method in [1, 2]
assert chunk_count > 0
assert chunk_offset == 0x24
assert signature_offset == chunk_offset + Chunk.size * chunk_count
for _ in range(chunk_count):
data = f.read(Chunk.size)
hash_ctx.update(data)
chunk_size, chunk_sha256 = Chunk.unpack(data)
yield chunk_size, chunk_sha256
digest = hash_ctx.digest()
if signature_method == 1:
data = f.read(256)
assert len(data) == 256
signature = int_from_unsigned_bytes(data, 'little')
plaintext = 0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003031300d0609608648016503040201050004200000000000000000000000000000000000000000000000000000000000000000 | int_from_unsigned_bytes(digest, 'big')
assert pow(signature, 0x10001, Apple_EFI_ROM_public_key_1) == plaintext
elif signature_method == 2:
data = f.read(32)
assert data == digest
raise RuntimeError('Chunklist missing digital signature')
else:
raise NotImplementedError
assert f.read(1) == b''
def get_session(args):
headers = {
'Host': 'osrecovery.apple.com',
'Connection': 'close',
'User-Agent': 'InternetRecovery/1.0',
}
headers, _ = run_query('http://osrecovery.apple.com/', headers)
if args.verbose:
print('Session headers:')
for header in headers:
print(f'{header}: {headers[header]}')
for header in headers:
if header.lower() == 'set-cookie':
cookies = headers[header].split('; ')
for cookie in cookies:
return cookie if cookie.startswith('session=') else ...
raise RuntimeError('No session in headers ' + str(headers))
def get_image_info(session, bid, mlb=MLB_ZERO, diag=False, os_type='default', cid=None):
headers = {
'Host': 'osrecovery.apple.com',
'Connection': 'close',
'User-Agent': 'InternetRecovery/1.0',
'Cookie': session,
'Content-Type': 'text/plain',
}
post = {
'cid': generate_id(TYPE_SID, cid),
'sn': mlb,
'bid': bid,
'k': generate_id(TYPE_K),
'fg': generate_id(TYPE_FG)
}
if diag:
url = 'http://osrecovery.apple.com/InstallationPayload/Diagnostics'
else:
url = 'http://osrecovery.apple.com/InstallationPayload/RecoveryImage'
post['os'] = os_type
headers, output = run_query(url, headers, post)
output = output.decode('utf-8')
info = {}
for line in output.split('\n'):
try:
key, value = line.split(': ')
info[key] = value
except Exception:
continue
for k in INFO_REQURED:
if k not in info:
raise RuntimeError(f'Missing key {k}')
return info
def save_image(url, sess, filename='', directory=''):
purl = urlparse(url)
headers = {
'Host': purl.hostname,
'Connection': 'close',
'User-Agent': 'InternetRecovery/1.0',
'Cookie': '='.join(['AssetToken', sess])
}
if not os.path.exists(directory):
os.mkdir(directory)
if filename == '':
filename = os.path.basename(purl.path)
if filename.find('/') >= 0 or filename == '':
raise RuntimeError('Invalid save path ' + filename)
print(f'Saving {url} to {directory}/{filename}...')
with open(os.path.join(directory, filename), 'wb') as fh:
response = run_query(url, headers, raw=True)
size = 0
while True:
chunk = response.read(2**20)
if not chunk:
break
fh.write(chunk)
size += len(chunk)
print(f'\r{size / (2**20)} MBs downloaded...', end='')
sys.stdout.flush()
print('\rDownload complete!\t\t\t\t\t')
return os.path.join(directory, os.path.basename(filename))
def verify_image(dmgpath, cnkpath):
print('Verifying image with chunklist...')
with open(dmgpath, 'rb') as dmgf:
cnkcount = 0
for cnksize, cnkhash in verify_chunklist(cnkpath):
cnkcount += 1
print(f'\rChunk {cnkcount} ({cnksize} bytes)', end='')
sys.stdout.flush()
cnk = dmgf.read(cnksize)
if len(cnk) != cnksize:
raise RuntimeError(f'Invalid chunk {cnkcount} size: expected {cnksize}, read {len(cnk)}')
if hashlib.sha256(cnk).digest() != cnkhash:
raise RuntimeError(f'Invalid chunk {cnkcount}: hash mismatch')
if dmgf.read(1) != b'':
raise RuntimeError('Invalid image: larger than chunklist')
print('\rImage verification complete!\t\t\t\t\t')
def action_download(args):
"""
Reference information for queries:
Recovery latest:
cid=3076CE439155BA14
sn=...
bid=Mac-E43C1C25D4880AD6
k=4BE523BB136EB12B1758C70DB43BDD485EBCB6A457854245F9E9FF0587FB790C
os=latest
fg=B2E6AA07DB9088BE5BDB38DB2EA824FDDFB6C3AC5272203B32D89F9D8E3528DC
Recovery default:
cid=4A35CB95FF396EE7
sn=...
bid=Mac-E43C1C25D4880AD6
k=0A385E6FFC3DDD990A8A1F4EC8B98C92CA5E19C9FF1DD26508C54936D8523121
os=default
fg=B2E6AA07DB9088BE5BDB38DB2EA824FDDFB6C3AC5272203B32D89F9D8E3528DC
Diagnostics:
cid=050C59B51497CEC8
sn=...
bid=Mac-E43C1C25D4880AD6
k=37D42A8282FE04A12A7D946304F403E56A2155B9622B385F3EB959A2FBAB8C93
fg=B2E6AA07DB9088BE5BDB38DB2EA824FDDFB6C3AC5272203B32D89F9D8E3528DC
"""
session = get_session(args)
info = get_image_info(session, bid=args.board_id, mlb=args.mlb, diag=args.diagnostics, os_type=args.os_type)
if args.verbose:
print(info)
print(f'Downloading {info[INFO_PRODUCT]}...')
dmgname = '' if args.basename == '' else args.basename + '.dmg'
dmgpath = save_image(info[INFO_IMAGE_LINK], info[INFO_IMAGE_SESS], dmgname, args.outdir)
cnkname = '' if args.basename == '' else args.basename + '.chunklist'
cnkpath = save_image(info[INFO_SIGN_LINK], info[INFO_SIGN_SESS], cnkname, args.outdir)
try:
verify_image(dmgpath, cnkpath)
return 0
except Exception as err:
if isinstance(err, AssertionError) and str(err) == '':
try:
tb = sys.exc_info()[2]
while tb.tb_next:
tb = tb.tb_next
err = linecache.getline(tb.tb_frame.f_code.co_filename, tb.tb_lineno, tb.tb_frame.f_globals).strip()
except Exception:
err = "Invalid chunklist"
print(f'\rImage verification failed. ({err})')
return 1
def action_selfcheck(args):
"""
Sanity check server logic for recovery:
if not valid(bid):
return error()
ppp = get_ppp(sn)
if not valid(ppp):
return latest_recovery(bid = bid) # Returns newest for bid.
if valid(sn):
if os == 'default':
return default_recovery(sn = sn, ppp = ppp) # Returns oldest for sn.
else:
return latest_recovery(sn = sn, ppp = ppp) # Returns newest for sn.
return default_recovery(ppp = ppp) # Returns oldest.
"""
session = get_session(args)
valid_default = get_image_info(session, bid=RECENT_MAC, mlb=MLB_VALID, diag=False, os_type='default')
valid_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_VALID, diag=False, os_type='latest')
product_default = get_image_info(session, bid=RECENT_MAC, mlb=MLB_PRODUCT, diag=False, os_type='default')
product_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_PRODUCT, diag=False, os_type='latest')
generic_default = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, diag=False, os_type='default')
generic_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, diag=False, os_type='latest')
if args.verbose:
print(valid_default)
print(valid_latest)
print(product_default)
print(product_latest)
print(generic_default)
print(generic_latest)
if valid_default[INFO_PRODUCT] == valid_latest[INFO_PRODUCT]:
# Valid MLB must give different default and latest if this is not a too new product.
print(f'ERROR: Cannot determine any previous product, got {valid_default[INFO_PRODUCT]}')
return 1
if product_default[INFO_PRODUCT] != product_latest[INFO_PRODUCT]:
# Product-only MLB must give the same value for default and latest.
print(f'ERROR: Latest and default do not match for product MLB, got {product_default[INFO_PRODUCT]} and {product_latest[INFO_PRODUCT]}')
return 1
if generic_default[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]:
# Zero MLB always give the same value for default and latest.
print(f'ERROR: Generic MLB gives different product, got {generic_default[INFO_PRODUCT]} and {generic_latest[INFO_PRODUCT]}')
return 1
if valid_latest[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]:
# Valid MLB must always equal generic MLB.
print(f'ERROR: Cannot determine unified latest product, got {valid_latest[INFO_PRODUCT]} and {generic_latest[INFO_PRODUCT]}')
return 1
if product_default[INFO_PRODUCT] != valid_default[INFO_PRODUCT]:
# Product-only MLB can give the same value with valid default MLB.
# This is not an error for all models, but for our chosen code it is.
print('ERROR: Valid and product MLB give mismatch, got {product_default[INFO_PRODUCT]} and {valid_default[INFO_PRODUCT]}')
return 1
print('SUCCESS: Found no discrepancies with MLB validation algorithm!')
return 0
def action_verify(args):
"""
Try to verify MLB serial number.
"""
session = get_session(args)
generic_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, diag=False, os_type='latest')
uvalid_default = get_image_info(session, bid=args.board_id, mlb=args.mlb, diag=False, os_type='default')
uvalid_latest = get_image_info(session, bid=args.board_id, mlb=args.mlb, diag=False, os_type='latest')
uproduct_default = get_image_info(session, bid=args.board_id, mlb=product_mlb(args.mlb), diag=False, os_type='default')
if args.verbose:
print(generic_latest)
print(uvalid_default)
print(uvalid_latest)
print(uproduct_default)
# Verify our MLB number.
if uvalid_default[INFO_PRODUCT] != uvalid_latest[INFO_PRODUCT]:
print(f'SUCCESS: {args.mlb} MLB looks valid and supported!' if uvalid_latest[INFO_PRODUCT] == generic_latest[INFO_PRODUCT] else f'SUCCESS: {args.mlb} MLB looks valid, but probably unsupported!')
return 0
print('UNKNOWN: Run selfcheck, check your board-id, or try again later!')
# Here we have matching default and latest products. This can only be true for very
# new models. These models get either latest or special builds.
if uvalid_default[INFO_PRODUCT] == generic_latest[INFO_PRODUCT]:
print(f'UNKNOWN: {args.mlb} MLB can be valid if very new!')
return 0
if uproduct_default[INFO_PRODUCT] != uvalid_default[INFO_PRODUCT]:
print(f'UNKNOWN: {args.mlb} MLB looks invalid, other models use product {uproduct_default[INFO_PRODUCT]} instead of {uvalid_default[INFO_PRODUCT]}!')
return 0
print(f'UNKNOWN: {args.mlb} MLB can be valid if very new and using special builds!')
return 0
def action_guess(args):
"""
Attempt to guess which model does this MLB belong.
"""
mlb = args.mlb
anon = mlb.startswith('000')
with open(args.board_db, 'r', encoding='utf-8') as fh:
db = json.load(fh)
supported = {}
session = get_session(args)
generic_latest = get_image_info(session, bid=RECENT_MAC, mlb=MLB_ZERO, diag=False, os_type='latest')
for model in db:
try:
if anon:
# For anonymous lookup check when given model does not match latest.
model_latest = get_image_info(session, bid=model, mlb=MLB_ZERO, diag=False, os_type='latest')
if model_latest[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]:
if db[model] == 'current':
print(f'WARN: Skipped {model} due to using latest product {model_latest[INFO_PRODUCT]} instead of {generic_latest[INFO_PRODUCT]}')
continue
user_default = get_image_info(session, bid=model, mlb=mlb, diag=False, os_type='default')
if user_default[INFO_PRODUCT] != generic_latest[INFO_PRODUCT]:
supported[model] = [db[model], user_default[INFO_PRODUCT], generic_latest[INFO_PRODUCT]]
else:
# For normal lookup check when given model has mismatching normal and latest.
user_latest = get_image_info(session, bid=model, mlb=mlb, diag=False, os_type='latest')
user_default = get_image_info(session, bid=model, mlb=mlb, diag=False, os_type='default')
if user_latest[INFO_PRODUCT] != user_default[INFO_PRODUCT]:
supported[model] = [db[model], user_default[INFO_PRODUCT], user_latest[INFO_PRODUCT]]
except Exception as e:
print(f'WARN: Failed to check {model}, exception: {e}')
if len(supported) > 0:
print(f'SUCCESS: MLB {mlb} looks supported for:')
for model in supported.items():
print(f'- {model}, up to {supported[model][0]}, default: {supported[model][1]}, latest: {supported[model][2]}')
return 0
print(f'UNKNOWN: Failed to determine supported models for MLB {mlb}!')
return None
def main():
parser = argparse.ArgumentParser(description='Gather recovery information for Macs')
parser.add_argument('action', choices=['download', 'selfcheck', 'verify', 'guess'],
help='Action to perform: "download" - performs recovery downloading,'
' "selfcheck" checks whether MLB serial validation is possible, "verify" performs'
' MLB serial verification, "guess" tries to find suitable mac model for MLB.')
parser.add_argument('-o', '--outdir', type=str, default='com.apple.recovery.boot',
help='customise output directory for downloading, defaults to com.apple.recovery.boot')
parser.add_argument('-n', '--basename', type=str, default='',
help='customise base name for downloading, defaults to remote name')
parser.add_argument('-b', '--board-id', type=str, default=RECENT_MAC,
help=f'use specified board identifier for downloading, defaults to {RECENT_MAC}')
parser.add_argument('-m', '--mlb', type=str, default=MLB_ZERO,
help=f'use specified logic board serial for downloading, defaults to {MLB_ZERO}')
parser.add_argument('-e', '--code', type=str, default='',
help='generate product logic board serial with specified product EEEE code')
parser.add_argument('-os', '--os-type', type=str, default='default', choices=['default', 'latest'],
help=f'use specified os type, defaults to default {MLB_ZERO}')
parser.add_argument('-diag', '--diagnostics', action='store_true', help='download diagnostics image')
parser.add_argument('-v', '--verbose', action='store_true', help='print debug information')
parser.add_argument('-db', '--board-db', type=str, default=os.path.join(SELF_DIR, 'boards.json'),
help='use custom board list for checking, defaults to boards.json')
args = parser.parse_args()
if args.code != '':
args.mlb = mlb_from_eeee(args.code)
if len(args.mlb) != 17:
print('ERROR: Cannot use MLBs in non 17 character format!')
sys.exit(1)
if args.action == 'download':
return action_download(args)
if args.action == 'selfcheck':
return action_selfcheck(args)
if args.action == 'verify':
return action_verify(args)
if args.action == 'guess':
return action_guess(args)
assert False
if __name__ == '__main__':
sys.exit(main())
-38
View File
@@ -1,38 +0,0 @@
# Lion (10.7):
python macrecovery.py -b Mac-2E6FAB96566FE58C -m 00000000000F25Y00 download
python macrecovery.py -b Mac-C3EC7CD22292981F -m 00000000000F0HM00 download
# Mountain Lion (10.8):
python macrecovery.py -b Mac-7DF2A3B5E5D671ED -m 00000000000F65100 download
# Mavericks (10.9):
python macrecovery.py -b Mac-F60DEB81FF30ACF6 -m 00000000000FNN100 download
# Yosemite (10.10):
python macrecovery.py -b Mac-E43C1C25D4880AD6 -m 00000000000GDVW00 download
# El Capitan (10.11):
python macrecovery.py -b Mac-FFE5EF870D7BA81A -m 00000000000GQRX00 download
# Sierra (10.12):
python macrecovery.py -b Mac-77F17D7DA9285301 -m 00000000000J0DX00 download
# High Sierra (10.13)
python macrecovery.py -b Mac-7BA5B2D9E42DDD94 -m 00000000000J80300 download
python macrecovery.py -b Mac-BE088AF8C5EB4FA2 -m 00000000000J80300 download
# Mojave (10.14)
python macrecovery.py -b Mac-7BA5B2DFE22DDD8C -m 00000000000KXPG00 download
# Catalina (10.15)
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. Ventura (13)
python macrecovery.py -b Mac-B4831CEBD52A0C4C -m 00000000000000000 download
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSServices</key>
<array>
<dict>
<key>NSBackgroundColorName</key>
<string>background</string>
<key>NSIconName</key>
<string>NSTouchBarTransferDownload</string>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Mount EFI</string>
</dict>
<key>NSMessage</key>
<string>runWorkflowAsService</string>
<key>NSSendFileTypes</key>
<array>
<string>public.folder</string>
</array>
</dict>
</array>
</dict>
</plist>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 715 B

@@ -1,411 +0,0 @@
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))]})
@@ -1,612 +0,0 @@
### ###
# 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("<plist version=\"1.0\">")
writer.writeValue(value)
writer.writeln("</plist>")
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)
@@ -1,151 +0,0 @@
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
@@ -1,146 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AMApplicationBuild</key>
<string>512</string>
<key>AMApplicationVersion</key>
<string>2.10</string>
<key>AMDocumentVersion</key>
<string>2</string>
<key>actions</key>
<array>
<dict>
<key>action</key>
<dict>
<key>AMAccepts</key>
<dict>
<key>Container</key>
<string>List</string>
<key>Optional</key>
<true/>
<key>Types</key>
<array>
<string>com.apple.applescript.object</string>
</array>
</dict>
<key>AMActionVersion</key>
<string>1.0.2</string>
<key>AMApplication</key>
<array>
<string>Automator</string>
</array>
<key>AMParameterProperties</key>
<dict>
<key>source</key>
<dict/>
</dict>
<key>AMProvides</key>
<dict>
<key>Container</key>
<string>List</string>
<key>Types</key>
<array>
<string>com.apple.applescript.object</string>
</array>
</dict>
<key>ActionBundlePath</key>
<string>/System/Library/Automator/Run AppleScript.action</string>
<key>ActionName</key>
<string>Run AppleScript</string>
<key>ActionParameters</key>
<dict>
<key>source</key>
<string>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 &amp; "which " &amp; 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 &amp; "/usr/bin/env " &amp; py &amp; " -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 &amp; " " &amp; 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) &amp; "Library/Services/"
set quick_action_list to do shell script "ls -1 " &amp; quoted form of services
set quick_actions to paragraphs of quick_action_list
repeat with qa in quick_actions
set test_path to services &amp; qa &amp; "/Contents/Scripts/" &amp; target
if my check_exists(test_path) is "0" then
# We got a valid path - let's run it
do shell script export &amp; "/usr/bin/env " &amp; python &amp; " " &amp; quoted form of test_path &amp; 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 " &amp; quoted form of path &amp; " ]]; then echo 0; else echo 1; fi"
end check_exists
on print_error(header, footer)
beep
display alert header &amp; "
" &amp; footer
end print_error</string>
</dict>
<key>BundleIdentifier</key>
<string>com.apple.Automator.RunScript</string>
<key>CFBundleVersion</key>
<string>1.0.2</string>
<key>CanShowSelectedItemsWhenRun</key>
<false/>
<key>CanShowWhenRun</key>
<true/>
<key>Category</key>
<array>
<string>AMCategoryUtilities</string>
</array>
<key>Class Name</key>
<string>RunScriptAction</string>
<key>InputUUID</key>
<string>34AED1F9-B31F-4927-A2F3-43392E378CAC</string>
<key>Keywords</key>
<array>
<string>Run</string>
</array>
<key>OutputUUID</key>
<string>EAB751F1-47E9-4256-9F92-ED4CD9CF95F5</string>
<key>UUID</key>
<string>FDE567E1-0DA9-43F5-B7DC-9D30FD0B8553</string>
<key>UnlocalizedApplications</key>
<array>
<string>Automator</string>
</array>
<key>arguments</key>
-140
View File
@@ -1,140 +0,0 @@
ocvalidate
====================
Utility to validate whether a `config.plist` matches requirements and conventions imposed by OpenCore.
## Usage
- Pass one single path to `config.plist` to verify it.
- Pass `--version` for current supported OpenCore version.
## Technical background
### At a glance
- ocvalidate firstly calls `OcSerializeLib` which performs fundamental checks in terms of syntax and semantics. After that, the following will be checked.
- The error message `<OCS: No schema for xxx>` complained by `OcSerializeLib` indicates unknown keys that can be deprecated in new versions of OpenCore. Such keys should be ***removed*** in order to avoid undefined behaviours.
- Under active development, newer versions of OpenCore hardly have backward compatibility at this moment. As a result, please first run `ocvalidate --version` to check which version of OpenCore is supported, and thus please only use the specific version.
### Global Rules
- All entries must be set once only. Duplication is strictly prohibited.
- All strings (fields with plist `String` format) throughout the whole config only accept ASCII printable characters at most. Stricter rules may apply. For instance, some fields only accept specified values, as indicated in [Configuration.pdf](https://github.com/acidanthera/OpenCorePkg/blob/master/Docs/Configuration.pdf).
- All the paths relative to OpenCore root must be less than or equal to 128 bytes (`OC_STORAGE_SAFE_PATH_MAX`) in total including '\0' terminator.
- Most binary patches must have `Find`, `Replace`, `Mask` (if used), and `ReplaceMask` (if used) identical size set. Also, `Find` requires `Mask` (or `Replace` requires `ReplaceMask`) to be active (set to non-zero) for corresponding bits.
- `MinKernel` and `MaxKernel` entries should follow conventions specified in [Configuration.pdf](https://github.com/acidanthera/OpenCorePkg/blob/master/Docs/Configuration.pdf). (TODO: Bring decent checks for this)
- `MinKernel` cannot be a value that is below macOS 10.4 (Darwin version 8).
- Entries taking file system path only accept `0-9, A-Z, a-z, '_', '-', '.', '/', and '\'`.
- Device Paths (e.g. `PciRoot(0x0)/Pci(0x1b,0x0)`) only accept strings in canonic string format.
- Paths of UEFI Drivers only accept `0-9, A-Z, a-z, '_', '-', '.', and '/'`.
- Entries requiring bitwise operations (e.g. `ConsoleAttributes`, `PickerAttributes`, or `ScanPolicy`) only allow known bits stated in [Configuration.pdf](https://github.com/acidanthera/OpenCorePkg/blob/master/Docs/Configuration.pdf) to be set.
- Entries involving GUID (mainly in Section `NVRAM`) must have correct format set.
### ACPI
#### Add
- Entry[N]->Path: Only `.aml` and `.bin` filename suffix are accepted.
### Booter
#### MmioWhitelist
- Entry[N]->Enabled: When at least one entry is enabled, `DevirtualiseMmio` in `Booter->Quirks` should be enabled.
#### Patch
- Entry[N]->Arch: Only `Any`, `i386`, or `x86_64` are accepted.
- Entry[N]->Identifier: Only `Any`, `Apple`, or a specified bootloader with `.efi` sufffix, are accepted.
#### Quirks
- When `AllowRelocationBlock` is enabled, `ProvideCustomSlide` should be enabled altogether.
- When `EnableSafeModeSlide` is enabled, `ProvideCustomSlide` should be enabled altogether.
- If `ProvideMaxSlide` is set to a number greater than zero, `ProvideCustomSlide` should be enabled altogether.
- `ResizeAppleGpuBars` must be set to `0` or `-1`.
- When `DisableVariableWrite`, `EnableWriteUnprotector`, or `ProvideCustomSlide` is enabled, `OpenRuntime.efi` should be loaded in `UEFI->Drivers`.
### DeviceProperties
- Requirements here all follow Global Rules.
### Kernel
#### Add
- Entry[N]->Arch: Only `Any`, `i386`, or `x86_64` are accepted.
- 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
- Entry[N]->Arch: Only `Any`, `i386`, or `x86_64` are accepted.
- Entry[N]->Identifier: At least one dot (`.`) should exist, because any identifier looks like a domain sequence (`vendor.product`).
#### Quirks
- `CustomSMBIOSGuid` requires `PlatformInfo->UpdateSMBIOSMode` set to `Custom`.
- `SetApfsTrimTimeout` cannot be a value that is greater than `MAX_UINT32`, or less than `-1`.
#### Scheme
- KernelArch: Only `Auto`, `i386`, `i386-user32`, or `x86_64` are accepted.
- KernelCache: Only `Auto`, `Cacheless`, `Mkext`, or `Prelinked` are accepted.
### Misc
#### BlessOverride
- Entries cannot be `\EFI\Microsoft\Boot\bootmgfw.efi` or `\System\Library\CoreServices\boot.efi` since OpenCore knows these paths.
#### Boot
- HibernateMode: Only `None`, `Auto`, `RTC`, or `NVRAM` are accepted.
- PickerMode: Only `Builtin`, `External`, or `Apple` are accepted.
- `PickerAudioAssist` requires `AudioSupport` in `UEFI->Audio` to be enabled.
- LauncherOption: Only `Disabled`, `Full`, `Short`, or `System` are accepted.
- `LauncherPath` cannot be empty string.
#### Security
- AuthRestart: If enabled, `VirtualSMC.kext` should be present in `Kernel->Add`.
- DmgLoading: Only `Disabled`, `Signed`, or `Any` are accepted.
- Vault: Only `Optional`, `Basic`, or `Secure` are accepted.
- SecureBootModel: Only `Default`, `Disabled`, `j137`, `j680`, `j132`, `j174`, `j140k`, `j780`, `j213`, `j140a`, `j152f`, `j160`, `j230k`, `j214k`, `j223`, `j215`, `j185`, `j185f`, or `x86legacy` are accepted.
#### Serial
- RegisterAccessWidth: Only `8` or `32` are accepted.
- BaudRate: Only `921600`, `460800`, `230400`, `115200`, `57600`, `38400`, `19200`, `9600`, `7200`, `4800`, `3600`, `2400`, `2000`, `1800`, `1200`, `600`, `300`, `150`, `134`, `110`, `75`, or `50` are accepted.
- PciDeviceInfo: The last byte must be `0xFF`.
- PciDeviceInfo: Excluding the last byte `0xFF`, the rest must be divisible by 4.
- PciDeviceInfo: Maximum allowed size is 41.
### NVRAM
- Requirements here all follow Global Rules. In addition, the following keys and values are checked:
#### gAppleBootVariableGuid (`7C436110-AB2A-4BBB-A880-FE41995C9F82`)
- `nvda_drv` must have type `Plist Data` with the value of `0x30` or `0x31`.
- `boot-args` must be an ASCII string (thus `Plist String`) without trailing `\0`.
- `bootercfg` must be an ASCII string (thus `Plist String`) without trailing `\0`.
- `csr-active-config` must have type `Plist Data` and have length of 4 bytes.
- `StartupMute` must have type `Plist Data` and have length of 1 byte.
- `SystemAudioVolume` must have type `Plist Data` and have length of 1 byte.
#### gAppleVendorVariableGuid (`4D1EDE05-38C7-4A6A-9CC6-4BCCA8B38C14`)
- `UIScale` must have type `Plist Data` with the value of `0x01` or `0x02`.
- `FirmwareFeatures` must have type `Plist Data` and have length of 4 bytes.
- `ExtendedFirmwareFeatures` must have type `Plist Data` and have length of 8 bytes.
- `FirmwareFeaturesMask` must have type `Plist Data` and have length of 4 bytes.
- `ExtendedFirmwareFeatures` must have type `Plist Data` and have length of 8 bytes.
- `DefaultBackgroundColor` must have type `Plist Data` and have length of 4 bytes. Also, its last byte must be `0x00`.
### PlatformInfo
- UpdateSMBIOSMode: Only `TryOverwrite`, `Create`, `Overwrite`, or `Custom` are accepted.
#### Generic
- SystemProductName: Only real Mac models are accepted.
- SystemMemoryStatus: Only `Auto`, `Upgradable`, or `Soldered` are accepted.
- SystemUUID: Only empty string, `OEM` or valid UUID are accepted.
- ProcessorType: Only known first byte can be set.
### UEFI
#### APFS
- When `EnableJumpstart` is enabled, `ScanPolicy` in `Misc->Security` should have `OC_SCAN_ALLOW_FS_APFS` (bit 8) set, together with `OC_SCAN_FILE_SYSTEM_LOCK` (bit 0) set. Or `ScanPolicy` should be `0` (failsafe value).
#### Audio
- When `AudioSupport` is enabled, `AudioDevice` must be either empty or a valid path.
- When `AudioSupport` is enabled, `AudioOutMask` must be non-zero.
#### Quirks
- When `RequestBootVarRouting` is enabled, `OpenRuntime.efi` should be loaded in `UEFI->Drivers`.
- `ResizeGpuBars` must be set to an integer value between `-1` and `19`.
#### Drivers
- When `OpenUsbKbDxe.efi` is in use, `KeySupport` in `UEFI->Input` should never be enabled altogether.
- When `Ps2KeyboardDxe.efi` is in use, `KeySupport` in `UEFI->Input` should always be enabled altogether.
- `OpenUsbKbDxe.efi` and `Ps2KeyboardDxe.efi` should never co-exist.
- When HFS+ filesystem driver or `AudioDxe.efi` is in use, `ConnectDrivers` should be enabled altogether.
- When `OpenCanopy.efi` is in use, `PickerMode` in `Misc->Boot` should be set to `External`.
- When `OpenVariableRuntimeDxe.efi` is in use, its `LoadEarly` option must be set to `TRUE`.
- `OpenRuntime.efi` must be placed after `OpenVariableRuntimeDxe.efi` when both are in use.
- `LoadEarly` for any other driver but `OpenVariableRuntimeDxe.efi` and `OpenRuntime.efi` must be set to `FALSE`.
#### Input
- KeySupportMode: Only `Auto`, `V1`, `V2`, or `AMI` are accepted.
- When `PointerSupport` is enabled, the value of `PointerSupportMode` should only be `ASUS`.
#### Output
- `ClearScreenOnModeSwitch`, `IgnoreTextInGraphics`, `ReplaceTabWithSpace`, and `SanitiseClearScreen` only apply to `System` TextRenderer
- `Resolution` should match `NUMBERxNUMBER` or `NUMBERxNUMBER@NUMBER` sequences (unless it is an `Empty string` or is set to `Max`).
- `UIScale` must be set to an integer value between `-1` and `2`.
#### ReservedMemory
- Type: Only `Reserved`, `LoaderCode`, `LoaderData`, `BootServiceCode`, `BootServiceData`, `RuntimeCode`, `RuntimeData`, `Available`, `Persistent`, `UnusableMemory`, `ACPIReclaimMemory`, `ACPIMemoryNVS`, `MemoryMappedIO`, `MemoryMappedIOPortSpace`, or `PalCode` are accepted.
Binary file not shown.
Binary file not shown.
Binary file not shown.