mirror of
https://github.com/dortania/OpenCore-Legacy-Patcher.git
synced 2026-04-24 03:50:14 +10:00
Restructure into package format
This commit is contained in:
156
opencore_legacy_patcher/detections/amfi_detect.py
Normal file
156
opencore_legacy_patcher/detections/amfi_detect.py
Normal file
@@ -0,0 +1,156 @@
|
||||
"""
|
||||
amfi_detect.py: Determine AppleMobileFileIntegrity's OS configuration
|
||||
"""
|
||||
|
||||
import enum
|
||||
|
||||
from ..utilities import utilities
|
||||
from ..datasets import amfi_data
|
||||
|
||||
|
||||
class AmfiConfigDetectLevel(enum.IntEnum):
|
||||
"""
|
||||
Configuration levels used by AmfiConfigurationDetection
|
||||
"""
|
||||
|
||||
NO_CHECK: int = 0
|
||||
LIBRARY_VALIDATION: int = 1 # For Ventura, use LIBRARY_VALIDATION_AND_SIG
|
||||
LIBRARY_VALIDATION_AND_SIG: int = 2
|
||||
ALLOW_ALL: int = 3
|
||||
|
||||
|
||||
class AmfiConfigurationDetection:
|
||||
"""
|
||||
Detect AppleMobileFileIntegrity's OS configuration
|
||||
|
||||
Usage:
|
||||
|
||||
>>> import amfi_detect
|
||||
>>> can_patch = amfi_detect.AmfiConfigurationDetection().check_config(amfi_detect.AmfiConfigDetectLevel.ALLOW_ALL)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.AMFI_ALLOW_TASK_FOR_PID: bool = False
|
||||
self.AMFI_ALLOW_INVALID_SIGNATURE: bool = False
|
||||
self.AMFI_LV_ENFORCE_THIRD_PARTY: bool = False
|
||||
self.AMFI_ALLOW_EVERYTHING: bool = False
|
||||
self.SKIP_LIBRARY_VALIDATION: bool = False
|
||||
|
||||
self.boot_args: list = []
|
||||
self.oclp_args: list = []
|
||||
|
||||
self._init_nvram_dicts()
|
||||
|
||||
self._parse_amfi_bitmask()
|
||||
self._parse_amfi_boot_args()
|
||||
self._parse_oclp_configuration()
|
||||
|
||||
|
||||
def _init_nvram_dicts(self) -> None:
|
||||
"""
|
||||
Initialize the boot-args and OCLP-Settings NVRAM dictionaries
|
||||
"""
|
||||
|
||||
boot_args = utilities.get_nvram("boot-args", decode=True)
|
||||
oclp_args = utilities.get_nvram("OCLP-Settings", "4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102", decode=True)
|
||||
|
||||
if boot_args:
|
||||
self.boot_args = boot_args.split(" ")
|
||||
|
||||
if oclp_args:
|
||||
self.oclp_args = oclp_args.split(" ")
|
||||
|
||||
|
||||
def _parse_amfi_bitmask(self) -> None:
|
||||
"""
|
||||
Parse the AMFI bitmask from boot-args
|
||||
See data/amfi_data.py for more information
|
||||
"""
|
||||
|
||||
amfi_value = 0
|
||||
for arg in self.boot_args:
|
||||
if not arg.startswith("amfi="):
|
||||
continue
|
||||
try:
|
||||
amfi_value = arg.split("=")
|
||||
if len(amfi_value) != 2:
|
||||
return
|
||||
amfi_value = amfi_value[1]
|
||||
if amfi_value.startswith("0x"):
|
||||
amfi_value = int(amfi_value, 16)
|
||||
else:
|
||||
amfi_value = int(amfi_value)
|
||||
except:
|
||||
return
|
||||
break
|
||||
|
||||
if amfi_value == 0:
|
||||
return
|
||||
|
||||
self.AMFI_ALLOW_TASK_FOR_PID: bool = amfi_value & amfi_data.AppleMobileFileIntegrity.AMFI_ALLOW_TASK_FOR_PID
|
||||
self.AMFI_ALLOW_INVALID_SIGNATURE: bool = amfi_value & amfi_data.AppleMobileFileIntegrity.AMFI_ALLOW_INVALID_SIGNATURE
|
||||
self.AMFI_LV_ENFORCE_THIRD_PARTY: bool = amfi_value & amfi_data.AppleMobileFileIntegrity.AMFI_LV_ENFORCE_THIRD_PARTY
|
||||
|
||||
if amfi_value & amfi_data.AppleMobileFileIntegrity.AMFI_ALLOW_EVERYTHING:
|
||||
self.AMFI_ALLOW_EVERYTHING = True
|
||||
self.SKIP_LIBRARY_VALIDATION = True
|
||||
self.AMFI_ALLOW_INVALID_SIGNATURE = True
|
||||
|
||||
|
||||
def _parse_amfi_boot_args(self) -> None:
|
||||
"""
|
||||
Parse the AMFI boot-args
|
||||
"""
|
||||
|
||||
for arg in self.boot_args:
|
||||
if arg.startswith("amfi_unrestrict_task_for_pid"):
|
||||
value = arg.split("=")
|
||||
if len(value) == 2:
|
||||
if value[1] in ["0x1", "1"]:
|
||||
self.AMFI_ALLOW_TASK_FOR_PID = True
|
||||
elif arg.startswith("amfi_allow_any_signature"):
|
||||
value = arg.split("=")
|
||||
if len(value) == 2:
|
||||
if value[1] in ["0x1", "1"]:
|
||||
self.AMFI_ALLOW_INVALID_SIGNATURE = True
|
||||
elif arg.startswith("amfi_get_out_of_my_way"):
|
||||
value = arg.split("=")
|
||||
if len(value) == 2:
|
||||
if value[1] in ["0x1", "1"]:
|
||||
self.AMFI_ALLOW_EVERYTHING = True
|
||||
self.SKIP_LIBRARY_VALIDATION = True
|
||||
self.AMFI_ALLOW_INVALID_SIGNATURE = True
|
||||
|
||||
|
||||
def _parse_oclp_configuration(self) -> None:
|
||||
"""
|
||||
Parse the OCLP configuration
|
||||
"""
|
||||
|
||||
if "-allow_amfi" in self.oclp_args:
|
||||
self.SKIP_LIBRARY_VALIDATION = True
|
||||
|
||||
|
||||
def check_config(self, level: int) -> bool:
|
||||
"""
|
||||
Check the AMFI configuration based on provided AMFI level
|
||||
See AmfiConfigLevel enum for valid levels
|
||||
|
||||
Parameters:
|
||||
level (int): The level of AMFI checks to check for
|
||||
|
||||
Returns:
|
||||
bool: True if the AMFI configuration matches the level, False otherwise
|
||||
"""
|
||||
|
||||
if level == AmfiConfigDetectLevel.NO_CHECK:
|
||||
return True
|
||||
if level == AmfiConfigDetectLevel.LIBRARY_VALIDATION:
|
||||
return self.SKIP_LIBRARY_VALIDATION
|
||||
if level == AmfiConfigDetectLevel.LIBRARY_VALIDATION_AND_SIG:
|
||||
return bool(self.SKIP_LIBRARY_VALIDATION and self.AMFI_ALLOW_INVALID_SIGNATURE)
|
||||
if level == AmfiConfigDetectLevel.ALLOW_ALL:
|
||||
return self.AMFI_ALLOW_EVERYTHING
|
||||
|
||||
return False
|
||||
1028
opencore_legacy_patcher/detections/device_probe.py
Normal file
1028
opencore_legacy_patcher/detections/device_probe.py
Normal file
File diff suppressed because it is too large
Load Diff
243
opencore_legacy_patcher/detections/ioreg.py
Normal file
243
opencore_legacy_patcher/detections/ioreg.py
Normal file
@@ -0,0 +1,243 @@
|
||||
"""
|
||||
ioreg.py: PyObjc Handling for IOKit
|
||||
"""
|
||||
|
||||
from typing import NewType, Union
|
||||
import objc
|
||||
|
||||
from CoreFoundation import CFRelease, kCFAllocatorDefault # type: ignore # pylint: disable=no-name-in-module
|
||||
from Foundation import NSBundle # type: ignore # pylint: disable=no-name-in-module
|
||||
from PyObjCTools import Conversion
|
||||
|
||||
IOKit_bundle = NSBundle.bundleWithIdentifier_("com.apple.framework.IOKit")
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
io_name_t_ref_out = b"[128c]" # io_name_t is char[128]
|
||||
const_io_name_t_ref_in = b"r*"
|
||||
CFStringRef = b"^{__CFString=}"
|
||||
CFDictionaryRef = b"^{__CFDictionary=}"
|
||||
CFAllocatorRef = b"^{__CFAllocator=}"
|
||||
# pylint: enable=invalid-name
|
||||
|
||||
# https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
|
||||
functions = [
|
||||
("IORegistryEntryCreateCFProperties", b"IIo^@" + CFAllocatorRef + b"I"),
|
||||
("IOServiceMatching", CFDictionaryRef + b"r*"),
|
||||
("IOServiceGetMatchingServices", b"II" + CFDictionaryRef + b"o^I"),
|
||||
("IOIteratorNext", b"II"),
|
||||
("IORegistryEntryGetParentEntry", b"IIr*o^I"),
|
||||
("IOObjectRelease", b"II"),
|
||||
("IORegistryEntryGetName", b"IIo" + io_name_t_ref_out),
|
||||
("IOObjectGetClass", b"IIo" + io_name_t_ref_out),
|
||||
("IOObjectCopyClass", CFStringRef + b"I"),
|
||||
("IOObjectCopySuperclassForClass", CFStringRef + CFStringRef),
|
||||
("IORegistryEntryGetChildIterator", b"IIr*o^I"),
|
||||
("IORegistryCreateIterator", b"IIr*Io^I"),
|
||||
("IORegistryEntryCreateIterator", b"IIr*Io^I"),
|
||||
("IORegistryIteratorEnterEntry", b"II"),
|
||||
("IORegistryIteratorExitEntry", b"II"),
|
||||
("IORegistryEntryCreateCFProperty", b"@I" + CFStringRef + CFAllocatorRef + b"I"),
|
||||
("IORegistryEntryGetPath", b"IIr*oI"),
|
||||
("IORegistryEntryCopyPath", CFStringRef + b"Ir*"),
|
||||
("IOObjectConformsTo", b"II" + const_io_name_t_ref_in),
|
||||
("IORegistryEntryGetLocationInPlane", b"II" + const_io_name_t_ref_in + b"o" + io_name_t_ref_out),
|
||||
("IOServiceNameMatching", CFDictionaryRef + b"r*"),
|
||||
("IORegistryEntryGetRegistryEntryID", b"IIo^Q"),
|
||||
("IORegistryEntryIDMatching", CFDictionaryRef + b"Q"),
|
||||
("IORegistryEntryFromPath", b"II" + const_io_name_t_ref_in),
|
||||
]
|
||||
|
||||
variables = [("kIOMasterPortDefault", b"I")]
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
pointer = type(None)
|
||||
|
||||
kern_return_t = NewType("kern_return_t", int)
|
||||
boolean_t = int
|
||||
|
||||
io_object_t = NewType("io_object_t", object)
|
||||
io_name_t = bytes
|
||||
io_string_t = bytes
|
||||
|
||||
# io_registry_entry_t = NewType("io_registry_entry_t", io_object_t)
|
||||
io_registry_entry_t = io_object_t
|
||||
io_iterator_t = NewType("io_iterator_t", io_object_t)
|
||||
|
||||
CFTypeRef = Union[int, float, bytes, dict, list]
|
||||
|
||||
IOOptionBits = int
|
||||
mach_port_t = int
|
||||
CFAllocatorType = type(kCFAllocatorDefault)
|
||||
|
||||
NULL = 0
|
||||
|
||||
kIOMasterPortDefault: mach_port_t
|
||||
kNilOptions: IOOptionBits = NULL
|
||||
|
||||
# IOKitLib.h
|
||||
kIORegistryIterateRecursively = 1
|
||||
kIORegistryIterateParents = 2
|
||||
|
||||
# pylint: enable=invalid-name
|
||||
|
||||
|
||||
# kern_return_t IORegistryEntryCreateCFProperties(io_registry_entry_t entry, CFMutableDictionaryRef * properties, CFAllocatorRef allocator, IOOptionBits options);
|
||||
def IORegistryEntryCreateCFProperties(entry: io_registry_entry_t, properties: pointer, allocator: CFAllocatorType, options: IOOptionBits) -> tuple[kern_return_t, dict]: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# CFMutableDictionaryRef IOServiceMatching(const char * name);
|
||||
def IOServiceMatching(name: bytes) -> dict: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# kern_return_t IOServiceGetMatchingServices(mach_port_t masterPort, CFDictionaryRef matching CF_RELEASES_ARGUMENT, io_iterator_t * existing);
|
||||
def IOServiceGetMatchingServices(masterPort: mach_port_t, matching: dict, existing: pointer) -> tuple[kern_return_t, io_iterator_t]: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# io_object_t IOIteratorNext(io_iterator_t iterator);
|
||||
def IOIteratorNext(iterator: io_iterator_t) -> io_object_t: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# kern_return_t IORegistryEntryGetParentEntry(io_registry_entry_t entry, const io_name_t plane, io_registry_entry_t * parent);
|
||||
def IORegistryEntryGetParentEntry(entry: io_registry_entry_t, plane: io_name_t, parent: pointer) -> tuple[kern_return_t, io_registry_entry_t]: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# kern_return_t IOObjectRelease(io_object_t object);
|
||||
def IOObjectRelease(object: io_object_t) -> kern_return_t: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# kern_return_t IORegistryEntryGetName(io_registry_entry_t entry, io_name_t name);
|
||||
def IORegistryEntryGetName(entry: io_registry_entry_t, name: pointer) -> tuple[kern_return_t, bytes]: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# kern_return_t IOObjectGetClass(io_object_t object, io_name_t className);
|
||||
def IOObjectGetClass(object: io_object_t, className: pointer) -> tuple[kern_return_t, bytes]: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# CFStringRef IOObjectCopyClass(io_object_t object);
|
||||
def IOObjectCopyClass(object: io_object_t) -> str: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# CFStringRef IOObjectCopySuperclassForClass(CFStringRef classname)
|
||||
def IOObjectCopySuperclassForClass(classname: str) -> str: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# kern_return_t IORegistryEntryGetChildIterator(io_registry_entry_t entry, const io_name_t plane, io_iterator_t * iterator);
|
||||
def IORegistryEntryGetChildIterator(entry: io_registry_entry_t, plane: io_name_t, iterator: pointer) -> tuple[kern_return_t, io_iterator_t]: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# kern_return_t IORegistryCreateIterator(mach_port_t masterPort, const io_name_t plane, IOOptionBits options, io_iterator_t * iterator)
|
||||
def IORegistryCreateIterator(masterPort: mach_port_t, plane: io_name_t, options: IOOptionBits, iterator: pointer) -> tuple[kern_return_t, io_iterator_t]: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# kern_return_t IORegistryEntryCreateIterator(io_registry_entry_t entry, const io_name_t plane, IOOptionBits options, io_iterator_t * iterator)
|
||||
def IORegistryEntryCreateIterator(entry: io_registry_entry_t, plane: io_name_t, options: IOOptionBits, iterator: pointer) -> tuple[kern_return_t, io_iterator_t]: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# kern_return_t IORegistryIteratorEnterEntry(io_iterator_t iterator)
|
||||
def IORegistryIteratorEnterEntry(iterator: io_iterator_t) -> kern_return_t: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# kern_return_t IORegistryIteratorExitEntry(io_iterator_t iterator)
|
||||
def IORegistryIteratorExitEntry(iterator: io_iterator_t) -> kern_return_t: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# CFTypeRef IORegistryEntryCreateCFProperty(io_registry_entry_t entry, CFStringRef key, CFAllocatorRef allocator, IOOptionBits options);
|
||||
def IORegistryEntryCreateCFProperty(entry: io_registry_entry_t, key: str, allocator: CFAllocatorType, options: IOOptionBits) -> CFTypeRef: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# kern_return_t IORegistryEntryGetPath(io_registry_entry_t entry, const io_name_t plane, io_string_t path);
|
||||
def IORegistryEntryGetPath(entry: io_registry_entry_t, plane: io_name_t, path: pointer) -> tuple[kern_return_t, io_string_t]: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# CFStringRef IORegistryEntryCopyPath(io_registry_entry_t entry, const io_name_t plane)
|
||||
def IORegistryEntryCopyPath(entry: io_registry_entry_t, plane: bytes) -> str: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# boolean_t IOObjectConformsTo(io_object_t object, const io_name_t className)
|
||||
def IOObjectConformsTo(object: io_object_t, className: bytes) -> boolean_t: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# kern_return_t IORegistryEntryGetLocationInPlane(io_registry_entry_t entry, const io_name_t plane, io_name_t location)
|
||||
def IORegistryEntryGetLocationInPlane(entry: io_registry_entry_t, plane: io_name_t, location: pointer) -> tuple[kern_return_t, bytes]: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# CFMutableDictionaryRef IOServiceNameMatching(const char * name);
|
||||
def IOServiceNameMatching(name: bytes) -> dict: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# kern_return_t IORegistryEntryGetRegistryEntryID(io_registry_entry_t entry, uint64_t * entryID)
|
||||
def IORegistryEntryGetRegistryEntryID(entry: io_registry_entry_t, entryID: pointer) -> tuple[kern_return_t, int]: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# CFMutableDictionaryRef IORegistryEntryIDMatching(uint64_t entryID);
|
||||
def IORegistryEntryIDMatching(entryID: int) -> dict: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# io_registry_entry_t IORegistryEntryFromPath(mach_port_t mainPort, const io_string_t path)
|
||||
def IORegistryEntryFromPath(mainPort: mach_port_t, path: io_string_t) -> io_registry_entry_t: # pylint: disable=invalid-name
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
objc.loadBundleFunctions(IOKit_bundle, globals(), functions) # type: ignore # pylint: disable=no-member
|
||||
objc.loadBundleVariables(IOKit_bundle, globals(), variables) # type: ignore # pylint: disable=no-member
|
||||
|
||||
|
||||
def ioiterator_to_list(iterator: io_iterator_t):
|
||||
# items = []
|
||||
item = IOIteratorNext(iterator) # noqa: F821
|
||||
while item:
|
||||
# items.append(next)
|
||||
yield item
|
||||
item = IOIteratorNext(iterator) # noqa: F821
|
||||
IOObjectRelease(iterator) # noqa: F821
|
||||
# return items
|
||||
|
||||
|
||||
def corefoundation_to_native(collection):
|
||||
if collection is None: # nullptr
|
||||
return None
|
||||
native = Conversion.pythonCollectionFromPropertyList(collection)
|
||||
CFRelease(collection)
|
||||
return native
|
||||
|
||||
|
||||
def native_to_corefoundation(native):
|
||||
return Conversion.propertyListFromPythonCollection(native)
|
||||
|
||||
|
||||
def io_name_t_to_str(name):
|
||||
return name.partition(b"\0")[0].decode()
|
||||
|
||||
|
||||
def get_class_inheritance(io_object):
|
||||
classes = []
|
||||
cls = IOObjectCopyClass(io_object)
|
||||
while cls:
|
||||
# yield cls
|
||||
classes.append(cls)
|
||||
CFRelease(cls)
|
||||
cls = IOObjectCopySuperclassForClass(cls)
|
||||
return classes
|
||||
83
opencore_legacy_patcher/detections/os_probe.py
Normal file
83
opencore_legacy_patcher/detections/os_probe.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
os_probe.py: OS Host information
|
||||
"""
|
||||
|
||||
import platform
|
||||
import plistlib
|
||||
import subprocess
|
||||
|
||||
|
||||
class OSProbe:
|
||||
"""
|
||||
Library for querying OS information specific to macOS
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.uname_data = platform.uname()
|
||||
|
||||
|
||||
def detect_kernel_major(self) -> int:
|
||||
"""
|
||||
Detect the booted major kernel version
|
||||
|
||||
Returns:
|
||||
int: Major kernel version (ex. 21, from 21.1.0)
|
||||
"""
|
||||
|
||||
return int(self.uname_data.release.partition(".")[0])
|
||||
|
||||
|
||||
def detect_kernel_minor(self) -> int:
|
||||
"""
|
||||
Detect the booted minor kernel version
|
||||
|
||||
Returns:
|
||||
int: Minor kernel version (ex. 1, from 21.1.0)
|
||||
"""
|
||||
|
||||
return int(self.uname_data.release.partition(".")[2].partition(".")[0])
|
||||
|
||||
|
||||
def detect_os_version(self) -> str:
|
||||
"""
|
||||
Detect the booted OS version
|
||||
|
||||
Returns:
|
||||
str: OS version (ex. 12.0)
|
||||
"""
|
||||
|
||||
result = subprocess.run(["/usr/bin/sw_vers", "-productVersion"], stdout=subprocess.PIPE)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError("Failed to detect OS version")
|
||||
|
||||
return result.stdout.decode().strip()
|
||||
|
||||
|
||||
def detect_os_build(self, rsr: bool = False) -> str:
|
||||
"""
|
||||
Detect the booted OS build
|
||||
|
||||
Implementation note:
|
||||
With macOS 13.2, Apple implemented the Rapid Security Response system which
|
||||
will change the reported build to the RSR version and not the original host
|
||||
|
||||
To get the proper versions:
|
||||
- Host: /System/Library/CoreServices/SystemVersion.plist
|
||||
- RSR: /System/Volumes/Preboot/Cryptexes/OS/System/Library/CoreServices/SystemVersion.plist
|
||||
|
||||
|
||||
Parameters:
|
||||
rsr (bool): Whether to use the RSR version of the build
|
||||
|
||||
Returns:
|
||||
str: OS build (ex. 21A5522h)
|
||||
"""
|
||||
|
||||
file_path = "/System/Library/CoreServices/SystemVersion.plist"
|
||||
if rsr is True:
|
||||
file_path = f"/System/Volumes/Preboot/Cryptexes/OS{file_path}"
|
||||
|
||||
try:
|
||||
return plistlib.load(open(file_path, "rb"))["ProductBuildVersion"]
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Failed to detect OS build: {e}")
|
||||
Reference in New Issue
Block a user