Merge branch 'main' of https://github.com/hykilpikonna/Erying-ITX-12-OpenCore
This commit is contained in:
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.
@@ -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>
|
||||
Binary file not shown.
+262
-24
@@ -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>
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
@@ -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())
|
||||
@@ -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))]})
|
||||
Binary file not shown.
@@ -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 & "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</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>
|
||||
@@ -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.
Reference in New Issue
Block a user