diff --git a/OpCore-Simplify.py b/OpCore-Simplify.py index 3764f80..7802519 100644 --- a/OpCore-Simplify.py +++ b/OpCore-Simplify.py @@ -6,6 +6,7 @@ from Scripts import config_prodigy from Scripts import gathering_files from Scripts import hardware_customizer from Scripts import kext_maestro +from Scripts import report_validator from Scripts import run from Scripts import smbios from Scripts import utils @@ -28,6 +29,7 @@ class OCPE: self.h = hardware_customizer.HardwareCustomizer() self.k = kext_maestro.KextMaestro() self.s = smbios.SMBIOS() + self.v = report_validator.ReportValidator() self.r = run.Run() self.result_dir = self.u.get_temporary_dir() @@ -93,17 +95,18 @@ class OCPE: return report_path, report_data path = self.u.normalize_path(user_input) - data = self.u.read_file(path) - if not path or os.path.splitext(path)[1].lower() != ".json" or not isinstance(data, dict): - print("") - print("Invalid file. Please ensure it is a valid \"Report.json\" file.") - print("") - self.u.request_input() - continue + is_valid, errors, warnings, data = self.v.validate_report(path) + + self.v.show_validation_report(path, is_valid, errors, warnings) + if not is_valid or errors: + print("") + print("\033[32mSuggestion:\033[0m Please re-export the hardware report and try again.") + print("") + self.u.request_input("Press Enter to go back...") + else: + return path, data - return path, data - def show_oclp_warning(self): while True: self.u.head("OpenCore Legacy Patcher Warning") diff --git a/Scripts/report_validator.py b/Scripts/report_validator.py new file mode 100644 index 0000000..c3f44f3 --- /dev/null +++ b/Scripts/report_validator.py @@ -0,0 +1,317 @@ +from Scripts import utils +import json +import os +import re + +class ReportValidator: + def __init__(self): + self.errors = [] + self.warnings = [] + self.u = utils.Utils() + + self.PATTERNS = { + "not_empty": r".+", + "platform": r"^(Desktop|Laptop)$", + "firmware_type": r"^(UEFI|BIOS)$", + "bus_type": r"^(PCI|USB|ACPI|ROOT)$", + "cpu_manufacturer": r"^(Intel|AMD)$", + "gpu_manufacturer": r"^(Intel|AMD|NVIDIA)$", + "gpu_device_type": r"^(Integrated GPU|Discrete GPU|Unknown)$", + "hex_id": r"^(?:0x)?[0-9a-fA-F]+$", + "device_id": r"^[0-9A-F]{4}(?:-[0-9A-F]{4})?$", + "resolution": r"^\d+x\d+$", + "pci_path": r"^PciRoot\(0x[0-9a-fA-F]+\)(?:/Pci\(0x[0-9a-fA-F]+,0x[0-9a-fA-F]+\))+$", + "acpi_path": r"^[\\]?_SB(\.[A-Z0-9_]+)+$", + "core_count": r"^\d+$", + "connector_type": r"^(VGA|DVI|HDMI|LVDS|DP|eDP|Internal|Uninitialized)$", + "enabled_disabled": r"^(Enabled|Disabled)$" + } + + self.SCHEMA = { + "type": dict, + "schema": { + "Motherboard": { + "type": dict, + "required": True, + "schema": { + "Name": {"type": str}, + "Chipset": {"type": str}, + "Platform": {"type": str, "pattern": self.PATTERNS["platform"]} + } + }, + "BIOS": { + "type": dict, + "required": True, + "schema": { + "Version": {"type": str, "required": False}, + "Release Date": {"type": str, "required": False}, + "System Type": {"type": str, "required": False}, + "Firmware Type": {"type": str, "pattern": self.PATTERNS["firmware_type"]}, + "Secure Boot": {"type": str, "pattern": self.PATTERNS["enabled_disabled"]} + } + }, + "CPU": { + "type": dict, + "required": True, + "schema": { + "Manufacturer": {"type": str, "pattern": self.PATTERNS["cpu_manufacturer"]}, + "Processor Name": {"type": str}, + "Codename": {"type": str}, + "Core Count": {"type": str, "pattern": self.PATTERNS["core_count"]}, + "CPU Count": {"type": str, "pattern": self.PATTERNS["core_count"]}, + "SIMD Features": {"type": str} + } + }, + "GPU": { + "type": dict, + "required": True, + "values_rule": { + "type": dict, + "schema": { + "Manufacturer": {"type": str, "pattern": self.PATTERNS["gpu_manufacturer"]}, + "Codename": {"type": str}, + "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]}, + "Device Type": {"type": str, "pattern": self.PATTERNS["gpu_device_type"]}, + "Subsystem ID": {"type": str, "pattern": self.PATTERNS["hex_id"]}, + "PCI Path": {"type": str, "required": False, "pattern": self.PATTERNS["pci_path"]}, + "ACPI Path": {"type": str, "required": False, "pattern": self.PATTERNS["acpi_path"]}, + "Resizable BAR": {"type": str, "required": False, "pattern": self.PATTERNS["enabled_disabled"]} + } + } + }, + "Monitor": { + "type": dict, + "required": False, + "values_rule": { + "type": dict, + "schema": { + "Connector Type": {"type": str, "pattern": self.PATTERNS["connector_type"]}, + "Resolution": {"type": str, "pattern": self.PATTERNS["resolution"]}, + "Connected GPU": {"type": str, "required": False} + } + } + }, + "Network": { + "type": dict, + "required": True, + "values_rule": { + "type": dict, + "schema": { + "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, + "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]}, + "Subsystem ID": {"type": str, "pattern": self.PATTERNS["hex_id"]}, + "PCI Path": {"type": str, "required": False, "pattern": self.PATTERNS["pci_path"]}, + "ACPI Path": {"type": str, "required": False, "pattern": self.PATTERNS["acpi_path"]} + } + } + }, + "Sound": { + "type": dict, + "required": False, + "values_rule": { + "type": dict, + "schema": { + "Bus Type": {"type": str}, + "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]}, + "Subsystem ID": {"type": str, "required": False, "pattern": self.PATTERNS["hex_id"]}, + "Audio Endpoints": {"type": list, "required": False, "item_rule": {"type": str}}, + "Controller Device ID": {"type": str, "required": False, "pattern": self.PATTERNS["device_id"]} + } + } + }, + "USB Controllers": { + "type": dict, + "required": True, + "values_rule": { + "type": dict, + "schema": { + "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, + "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]}, + "Subsystem ID": {"type": str, "pattern": self.PATTERNS["hex_id"]}, + "PCI Path": {"type": str, "required": False, "pattern": self.PATTERNS["pci_path"]}, + "ACPI Path": {"type": str, "required": False, "pattern": self.PATTERNS["acpi_path"]} + } + } + }, + "Input": { + "type": dict, + "required": True, + "values_rule": { + "type": dict, + "schema": { + "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, + "Device": {"type": str, "required": False}, + "Device ID": {"type": str, "required": False, "pattern": self.PATTERNS["device_id"]}, + "Device Type": {"type": str, "required": False} + } + } + }, + "Storage Controllers": { + "type": dict, + "required": True, + "values_rule": { + "type": dict, + "schema": { + "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, + "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]}, + "Subsystem ID": {"type": str, "pattern": self.PATTERNS["hex_id"]}, + "PCI Path": {"type": str, "required": False, "pattern": self.PATTERNS["pci_path"]}, + "ACPI Path": {"type": str, "required": False, "pattern": self.PATTERNS["acpi_path"]}, + "Disk Drives": {"type": list, "required": False, "item_rule": {"type": str}} + } + } + }, + "Biometric": { + "type": dict, + "required": False, + "values_rule": { + "type": dict, + "schema": { + "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, + "Device": {"type": str, "required": False}, + "Device ID": {"type": str, "required": False, "pattern": self.PATTERNS["device_id"]} + } + } + }, + "Bluetooth": { + "type": dict, + "required": False, + "values_rule": { + "type": dict, + "schema": { + "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, + "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]} + } + } + }, + "SD Controller": { + "type": dict, + "required": False, + "values_rule": { + "type": dict, + "schema": { + "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, + "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]}, + "Subsystem ID": {"type": str, "pattern": self.PATTERNS["hex_id"]}, + "PCI Path": {"type": str, "required": False, "pattern": self.PATTERNS["pci_path"]}, + "ACPI Path": {"type": str, "required": False, "pattern": self.PATTERNS["acpi_path"]} + } + } + }, + "System Devices": { + "type": dict, + "required": False, + "values_rule": { + "type": dict, + "schema": { + "Bus Type": {"type": str}, + "Device": {"type": str, "required": False}, + "Device ID": {"type": str, "required": False, "pattern": self.PATTERNS["device_id"]}, + "Subsystem ID": {"type": str, "required": False, "pattern": self.PATTERNS["hex_id"]}, + "PCI Path": {"type": str, "required": False, "pattern": self.PATTERNS["pci_path"]}, + "ACPI Path": {"type": str, "required": False, "pattern": self.PATTERNS["acpi_path"]} + } + } + } + } + } + + def validate_report(self, report_path): + self.errors = [] + self.warnings = [] + data = None + + if not os.path.exists(report_path): + self.errors.append("File does not exist: {}".format(report_path)) + return False, self.errors, self.warnings, None + + try: + data = self.u.read_file(report_path) + except json.JSONDecodeError as e: + self.errors.append("Invalid JSON format: {}".format(str(e))) + return False, self.errors, self.warnings, None + except Exception as e: + self.errors.append("Error reading file: {}".format(str(e))) + return False, self.errors, self.warnings, None + + cleaned_data = self._validate_node(data, self.SCHEMA, "Root") + + is_valid = len(self.errors) == 0 + return is_valid, self.errors, self.warnings, cleaned_data + + def _validate_node(self, data, rule, path): + expected_type = rule.get("type") + if expected_type: + if not isinstance(data, expected_type): + type_name = expected_type.__name__ if hasattr(expected_type, "__name__") else str(expected_type) + self.errors.append(f"{path}: Expected type {type_name}, got {type(data).__name__}") + return None + + if isinstance(data, str): + pattern = rule.get("pattern") + if pattern is not None: + if not re.match(pattern, data): + self.errors.append(f"{path}: Value '{data}' does not match pattern '{pattern}'") + return None + elif not re.match(self.PATTERNS["not_empty"], data): + self.errors.append(f"{path}: Value '{data}' does not match pattern '{self.PATTERNS['not_empty']}'") + return None + + cleaned_data = data + + if isinstance(data, dict): + cleaned_data = {} + schema_keys = rule.get("schema", {}) + + for key, value in data.items(): + if key in schema_keys: + cleaned_val = self._validate_node(value, schema_keys[key], f"{path}.{key}") + if cleaned_val is not None: + cleaned_data[key] = cleaned_val + elif "values_rule" in rule: + cleaned_val = self._validate_node(value, rule["values_rule"], f"{path}.{key}") + if cleaned_val is not None: + cleaned_data[key] = cleaned_val + else: + if schema_keys: + self.warnings.append(f"{path}: Unknown key '{key}'") + + for key, key_rule in schema_keys.items(): + if key_rule.get("required", True) and key not in cleaned_data: + self.errors.append(f"{path}: Missing required key '{key}'") + + elif isinstance(data, list): + item_rule = rule.get("item_rule") + if item_rule: + cleaned_data = [] + for i, item in enumerate(data): + cleaned_val = self._validate_node(item, item_rule, f"{path}[{i}]") + if cleaned_val is not None: + cleaned_data.append(cleaned_val) + else: + cleaned_data = list(data) + + return cleaned_data + + def show_validation_report(self, report_path, is_valid, errors, warnings): + self.u.head("Validation Report") + print("") + print("Validation report for: {}".format(report_path)) + print("") + + if is_valid: + print("Hardware report is valid!") + else: + print("Hardware report is not valid! Please check the errors and warnings below.") + + if errors: + print("") + print("\033[31mErrors ({}):\033[0m".format(len(errors))) + for i, error in enumerate(errors, 1): + print(" {}. {}".format(i, error)) + + if warnings: + print("") + print("\033[33mWarnings ({}):\033[0m".format(len(warnings))) + for i, warning in enumerate(warnings, 1): + print(" {}. {}".format(i, warning)) \ No newline at end of file