├── .gitattributes ├── .github └── FUNDING.yml ├── Scripts ├── datasets │ ├── background_picker.icns │ ├── gpu_data.py │ ├── os_data.py │ ├── cpu_data.py │ ├── chipset_data.py │ ├── acpi_patch_data.py │ └── mac_model_data.py ├── integrity_checker.py ├── run.py ├── github.py ├── resource_fetcher.py ├── utils.py ├── smbios.py ├── wifi_profile_extractor.py ├── report_validator.py ├── hardware_customizer.py ├── gathering_files.py └── compatibility_checker.py ├── LICENSE ├── .gitignore ├── updater.py ├── README.md ├── OpCore-Simplify.command └── OpCore-Simplify.bat /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: [ 2 | "https://buymeacoffee.com/lzhoang2801", 3 | "https://paypal.me/lzhoang2601" 4 | ] 5 | -------------------------------------------------------------------------------- /Scripts/datasets/background_picker.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lzhoang2801/OpCore-Simplify/HEAD/Scripts/datasets/background_picker.icns -------------------------------------------------------------------------------- /Scripts/datasets/gpu_data.py: -------------------------------------------------------------------------------- 1 | # Resource: https://en.wikipedia.org/wiki/Graphics_Core_Next 2 | AMDCodenames = [ 3 | "Hainan", 4 | "Oland", 5 | "Cape Verde", 6 | "Pitcairn", 7 | "Tahiti", 8 | "Bonaire", 9 | "Hawaii", 10 | "Tonga", 11 | "Fiji" 12 | ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2024, lzhoang2601 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Scripts/datasets/os_data.py: -------------------------------------------------------------------------------- 1 | class macOSVersionInfo: 2 | def __init__(self, name, macos_version, release_status = "final"): 3 | self.name = name 4 | self.darwin_version = (int(macos_version.split(".")[1]) + 4) if "10." in macos_version else (int(macos_version.split(".")[0]) + 9) if macos_version.startswith("1") else (int(macos_version.split(".")[0]) - 1) 5 | self.macos_version = macos_version 6 | self.release_status = release_status 7 | 8 | macos_versions = [ 9 | macOSVersionInfo("High Sierra", "10.13"), 10 | macOSVersionInfo("Mojave", "10.14"), 11 | macOSVersionInfo("Catalina", "10.15"), 12 | macOSVersionInfo("Big Sur", "11"), 13 | macOSVersionInfo("Monterey", "12"), 14 | macOSVersionInfo("Ventura", "13"), 15 | macOSVersionInfo("Sonoma", "14"), 16 | macOSVersionInfo("Sequoia", "15"), 17 | macOSVersionInfo("Tahoe", "26") 18 | ] 19 | 20 | def get_latest_darwin_version(include_beta=True): 21 | for macos_version in macos_versions[::-1]: 22 | if include_beta: 23 | return "{}.{}.{}".format(macos_version.darwin_version, 99, 99) 24 | else: 25 | if macos_version.release_status == "final": 26 | return "{}.{}.{}".format(macos_version.darwin_version, 99, 99) 27 | 28 | def get_lowest_darwin_version(): 29 | return "{}.{}.{}".format(macos_versions[0].darwin_version, 0, 0) 30 | 31 | def get_macos_name_by_darwin(darwin_version): 32 | for data in macos_versions: 33 | if data.darwin_version == int(darwin_version[:2]): 34 | return "macOS {} {}{}".format(data.name, data.macos_version, "" if data.release_status == "final" else " (Beta)") 35 | return None 36 | -------------------------------------------------------------------------------- /Scripts/datasets/cpu_data.py: -------------------------------------------------------------------------------- 1 | AMDCPUGenerations = [ 2 | "Summit Ridge", 3 | "Whitehaven", 4 | "Raven Ridge", 5 | "Great Horned Owl", 6 | "Dalí", 7 | "Banded Kestrel", 8 | "River Hawk", 9 | "Pinnacle Ridge", 10 | "Colfax", 11 | "Picasso", 12 | "Grey Hawk", 13 | "Matisse", 14 | "Castle Peak", 15 | "Renoir", 16 | "Lucienne", 17 | "Vermeer", 18 | "Cezanne", 19 | "Barceló", 20 | "Rembrandt", 21 | "Mendocino", 22 | "Barceló-R", 23 | "Rembrandt-R", 24 | "V3000", 25 | "Chagall", 26 | "Raphael", 27 | "Storm Peak", 28 | "Phoenix", 29 | "Dragon Range", 30 | "Hawk Point", 31 | "Granite Ridge", 32 | "Strix Point" 33 | ] 34 | 35 | IntelCPUGenerations = [ 36 | "Arrow Lake-S", 37 | "Arrow Lake-H", 38 | "Arrow Lake-HX", 39 | "Arrow Lake-U", 40 | "Lunar Lake", 41 | "Meteor Lake-H", 42 | "Meteor Lake-U", 43 | "Raptor Lake-S", 44 | "Raptor Lake-E", 45 | "Raptor Lake-HX", 46 | "Raptor Lake-H", 47 | "Raptor Lake-PX", 48 | "Raptor Lake-P", 49 | "Raptor Lake-U", 50 | "Alder Lake-S", 51 | "Alder Lake-HX", 52 | "Alder Lake-H", 53 | "Alder Lake-P", 54 | "Alder Lake-U", 55 | "Alder Lake-N", 56 | "Lakefield", 57 | "Rocket Lake-S", 58 | "Rocket Lake-E", 59 | "Tiger Lake-H", 60 | "Tiger Lake-B", 61 | "Tiger Lake-UP3", 62 | "Tiger Lake-H35", 63 | "Tiger Lake-UP4", 64 | "Ice Lake-U", 65 | "Ice Lake-SP", 66 | "Comet Lake-S", 67 | "Comet Lake-W", 68 | "Comet Lake-H", 69 | "Comet Lake-U", 70 | "Coffee Lake-S", 71 | "Coffee Lake-E", 72 | "Coffee Lake-H", 73 | "Coffee Lake-U", 74 | "Cannon Lake-U", 75 | "Whiskey Lake-U", 76 | "Kaby Lake", 77 | "Kaby Lake-H", 78 | "Kaby Lake-X", 79 | "Kaby Lake-G", 80 | "Amber Lake-Y", 81 | "Cascade Lake-X", 82 | "Cascade Lake-P", 83 | "Cascade Lake-W", 84 | "Skylake", 85 | "Skylake-X", 86 | "Broadwell", 87 | "Broadwell-H", 88 | "Broadwell-U", 89 | "Broadwell-Y", 90 | "Broadwell-E", 91 | "Haswell", 92 | "Haswell-ULT", 93 | "Haswell-ULX", 94 | "Haswell-H", 95 | "Haswell-E", 96 | "Haswell-EP", 97 | "Haswell-EX", 98 | "Ivy Bridge", 99 | "Ivy Bridge-E", 100 | "Sandy Bridge", 101 | "Sandy Bridge-E", 102 | "Beckton", 103 | "Westmere-EX", 104 | "Gulftown", 105 | "Westmere-EP", 106 | "Clarkdale", 107 | "Arrandale", 108 | "Lynnfield", 109 | "Jasper Forest", 110 | "Clarksfield", 111 | "Gainestown", 112 | "Bloomfield" 113 | ] -------------------------------------------------------------------------------- /Scripts/integrity_checker.py: -------------------------------------------------------------------------------- 1 | import os 2 | import hashlib 3 | import json 4 | from Scripts import utils 5 | 6 | class IntegrityChecker: 7 | def __init__(self): 8 | self.utils = utils.Utils() 9 | 10 | def get_sha256(self, file_path, block_size=65536): 11 | if not os.path.exists(file_path) or os.path.isdir(file_path): 12 | return None 13 | 14 | sha256 = hashlib.sha256() 15 | with open(file_path, 'rb') as f: 16 | for block in iter(lambda: f.read(block_size), b''): 17 | sha256.update(block) 18 | return sha256.hexdigest() 19 | 20 | def generate_folder_manifest(self, folder_path, manifest_path=None): 21 | if not os.path.isdir(folder_path): 22 | return None 23 | 24 | if manifest_path is None: 25 | manifest_path = os.path.join(folder_path, "manifest.json") 26 | 27 | manifest_data = {} 28 | for root, _, files in os.walk(folder_path): 29 | for name in files: 30 | file_path = os.path.join(root, name) 31 | relative_path = os.path.relpath(file_path, folder_path).replace('\\', '/') 32 | 33 | if relative_path == os.path.basename(manifest_path): 34 | continue 35 | 36 | manifest_data[relative_path] = self.get_sha256(file_path) 37 | 38 | self.utils.write_file(manifest_path, manifest_data) 39 | return manifest_data 40 | 41 | def verify_folder_integrity(self, folder_path, manifest_path=None): 42 | if not os.path.isdir(folder_path): 43 | return None, "Folder not found." 44 | 45 | if manifest_path is None: 46 | manifest_path = os.path.join(folder_path, "manifest.json") 47 | 48 | if not os.path.exists(manifest_path): 49 | return None, "Manifest file not found." 50 | 51 | manifest_data = self.utils.read_file(manifest_path) 52 | if not isinstance(manifest_data, dict): 53 | return None, "Invalid manifest file." 54 | 55 | issues = { 56 | "modified": [], 57 | "missing": [], 58 | "untracked": [] 59 | } 60 | 61 | manifest_files = set(manifest_data.keys()) 62 | actual_files = set() 63 | 64 | for root, _, files in os.walk(folder_path): 65 | for name in files: 66 | file_path = os.path.join(root, name) 67 | relative_path = os.path.relpath(file_path, folder_path).replace('\\', '/') 68 | 69 | if relative_path == os.path.basename(manifest_path): 70 | continue 71 | 72 | actual_files.add(relative_path) 73 | 74 | if relative_path not in manifest_data: 75 | issues["untracked"].append(relative_path) 76 | else: 77 | current_hash = self.get_sha256(file_path) 78 | if current_hash != manifest_data.get(relative_path): 79 | issues["modified"].append(relative_path) 80 | 81 | missing_files = manifest_files - actual_files 82 | issues["missing"] = list(missing_files) 83 | 84 | is_valid = not any(issues.values()) 85 | 86 | return is_valid, issues 87 | -------------------------------------------------------------------------------- /Scripts/datasets/chipset_data.py: -------------------------------------------------------------------------------- 1 | IntelChipsets = [ 2 | "X58", 3 | "NM10", 4 | "P55", 5 | "PM55", 6 | "H55", 7 | "QM57", 8 | "H57", 9 | "HM55", 10 | "Q57", 11 | "HM57", 12 | "QS57", 13 | "Cougar Point", 14 | "Z68", 15 | "P67", 16 | "UM67", 17 | "HM65", 18 | "H67", 19 | "HM67", 20 | "Q65", 21 | "QS67", 22 | "Q67", 23 | "QM67", 24 | "B65", 25 | "C202", 26 | "C204", 27 | "C206", 28 | "H61", 29 | "C600/X79", 30 | "Panther Point", 31 | "Z77", 32 | "Z75", 33 | "Q77", 34 | "Q75", 35 | "B75", 36 | "H77", 37 | "C216", 38 | "QM77", 39 | "QS77", 40 | "HM77", 41 | "UM77", 42 | "HM76", 43 | "HM75", 44 | "HM70", 45 | "NM70", 46 | "Lynx Point", 47 | "Z87", 48 | "Z85", 49 | "HM86", 50 | "H87", 51 | "HM87", 52 | "Q85", 53 | "Q87", 54 | "QM87", 55 | "B85", 56 | "C222", 57 | "C224", 58 | "C226", 59 | "H81", 60 | "HM97", 61 | "Z97", 62 | "QM97", 63 | "H97", 64 | "C610/X99", 65 | "Wellsburg", 66 | "Wildcat Point-LP", 67 | "Sunrise Point-LP", 68 | "Sunrise Point", 69 | "Sunrise Point-H", 70 | "H110", 71 | "H170", 72 | "Z170", 73 | "Q170", 74 | "Q150", 75 | "B150", 76 | "C236", 77 | "C232", 78 | "QM170", 79 | "HM170", 80 | "CM236", 81 | "QMS180", 82 | "HM175", 83 | "QM175", 84 | "CM238", 85 | "QMU185", 86 | "H270", 87 | "Z270", 88 | "Q270", 89 | "Q250", 90 | "B250", 91 | "B365", 92 | "C422", 93 | "C621", 94 | "C622", 95 | "C624", 96 | "C625", 97 | "C626", 98 | "C627", 99 | "C628", 100 | "X299", 101 | "Lewisburg", 102 | "Cannon Point-LP", 103 | "C246", 104 | "B360", 105 | "C242", 106 | "QM370", 107 | "HM370", 108 | "CM246", 109 | "H310", 110 | "H370", 111 | "Z390", 112 | "Q370", 113 | "Z370", 114 | "Comet Lake", 115 | "HM470", 116 | "H310D", 117 | "H470", 118 | "Z490", 119 | "Q470", 120 | "QM480", 121 | "WM490", 122 | "W480", 123 | "H420E", 124 | "Ice Lake-LP", 125 | "Ice Lake", 126 | "B460", 127 | "H410", 128 | "QM580E", 129 | "Rocket Lake", 130 | "Q570", 131 | "Z590", 132 | "H570", 133 | "B560", 134 | "H510", 135 | "WM590", 136 | "QM580", 137 | "HM570", 138 | "C252", 139 | "C256", 140 | "W580", 141 | "RM590E", 142 | "R580E", 143 | "Tiger Lake-LP", 144 | "Gemini Lake", 145 | "Elkhart Lake", 146 | "Jasper Lake", 147 | "Alder Lake", 148 | "Alder Lake-N", 149 | "Q670", 150 | "Z690", 151 | "H670", 152 | "B660", 153 | "H610", 154 | "W680", 155 | "HM670", 156 | "WM690", 157 | "R680E", 158 | "Q670E", 159 | "H610E", 160 | "Raptor Lake-P", 161 | "Raptor Lake-PX", 162 | "Z790", 163 | "H770", 164 | "B760", 165 | "HM770", 166 | "WM790", 167 | "C266", 168 | "C262", 169 | "W790", 170 | "Meteor Lake-P", 171 | "Lunar Lake-M", 172 | "Q870", 173 | "Z890", 174 | "B860", 175 | "H810", 176 | "W880", 177 | "HM870", 178 | "WM880" 179 | ] 180 | 181 | AMDChipsets = [ 182 | "AMD", 183 | "AM1", 184 | "A68H", 185 | "A75", 186 | "A78", 187 | "A85X", 188 | "A88X", 189 | "A320", 190 | "B350", 191 | "X370", 192 | "X399", 193 | "B450", 194 | "X470", 195 | "A520", 196 | "B550", 197 | "X570", 198 | "TRX40", 199 | "TRX50", 200 | "A620", 201 | "B650", 202 | "X670", 203 | "X870", 204 | "X870E" 205 | ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | *.DS_Store 163 | OCK_Files -------------------------------------------------------------------------------- /Scripts/datasets/acpi_patch_data.py: -------------------------------------------------------------------------------- 1 | class PatchInfo: 2 | def __init__(self, name, description, function_name): 3 | self.name = name 4 | self.description = description 5 | self.function_name = function_name 6 | self.checked = False 7 | 8 | patches = [ 9 | PatchInfo( 10 | name = "ALS", 11 | description = "Fake or enable Ambient Light Sensor device for storing the current brightness/auto-brightness level", 12 | function_name = "ambient_light_sensor" 13 | ), 14 | PatchInfo( 15 | name = "APIC", 16 | description = "Avoid kernel panic by pointing the first CPU entry to an active CPU on HEDT systems", 17 | function_name = "fix_apic_processor_id" 18 | ), 19 | PatchInfo( 20 | name = "BATP", 21 | description = "Enables displaying the battery percentage on laptops", 22 | function_name = "battery_status_patch" 23 | ), 24 | PatchInfo( 25 | name = "BUS0", 26 | description = "Add a System Management Bus device to fix AppleSMBus issues", 27 | function_name = "add_system_management_bus_device" 28 | ), 29 | PatchInfo( 30 | name = "Disable Devices", 31 | description = "Disable unsupported PCI devices such as the GPU, Wi-Fi card, and SD card reader", 32 | function_name = "disable_unsupported_device" 33 | ), 34 | PatchInfo( 35 | name = "FakeEC", 36 | description = "OS-Aware Fake EC (by CorpNewt)", 37 | function_name = "fake_embedded_controller" 38 | ), 39 | PatchInfo( 40 | name = "RCSP", 41 | description = "Remove conditional ACPI scope declaration", 42 | function_name = "remove_conditional_scope" 43 | ), 44 | PatchInfo( 45 | name = "CMOS", 46 | description = "Fix HP Real-Time Clock Power Loss (005) Post Error", 47 | function_name = "fix_hp_005_post_error" 48 | ), 49 | PatchInfo( 50 | name = "FixHPET", 51 | description = "Patch Out IRQ Conflicts (by CorpNewt)", 52 | function_name = "fix_irq_conflicts" 53 | ), 54 | PatchInfo( 55 | name = "GPI0", 56 | description = "Enable GPIO device for a I2C TouchPads to function properly", 57 | function_name = "enable_gpio_device" 58 | ), 59 | PatchInfo( 60 | name = "IMEI", 61 | description = "Creates a fake IMEI device to ensure Intel iGPUs acceleration functions properly", 62 | function_name = "add_intel_management_engine" 63 | ), 64 | PatchInfo( 65 | name = "MCHC", 66 | description = "Add a Memory Controller Hub Controller device to fix AppleSMBus", 67 | function_name = "add_memory_controller_device" 68 | ), 69 | PatchInfo( 70 | name = "PMC", 71 | description = "Add a PMCR device to enable NVRAM support for 300-series mainboards", 72 | function_name = "enable_nvram_support" 73 | ), 74 | PatchInfo( 75 | name = "PM (Legacy)", 76 | description = "Block CpuPm and Cpu0Ist ACPI tables to avoid panics for Intel Ivy Bridge and older CPUs", 77 | function_name = "drop_cpu_tables" 78 | ), 79 | PatchInfo( 80 | name = "PLUG", 81 | description = "Redefines CPU Objects as Processor and sets plugin-type = 1 (by CorpNewt)", 82 | function_name = "enable_cpu_power_management" 83 | ), 84 | PatchInfo( 85 | name = "PNLF", 86 | description = "Defines a PNLF device to enable backlight controls on laptops", 87 | function_name = "enable_backlight_controls" 88 | ), 89 | PatchInfo( 90 | name = "RMNE", 91 | description = "Creates a Null Ethernet to allow macOS system access to iServices", 92 | function_name = "add_null_ethernet_device" 93 | ), 94 | PatchInfo( 95 | name = "RTC0", 96 | description = "Creates a new RTC device to resolve PCI Configuration issues on HEDT systems", 97 | function_name = "fix_system_clock_hedt" 98 | ), 99 | PatchInfo( 100 | name = "RTCAWAC", 101 | description = "Context-Aware AWAC Disable and RTC Enable/Fake/Range Fix (by CorpNewt)", 102 | function_name = "fix_system_clock_awac" 103 | ), 104 | PatchInfo( 105 | name = "PRW", 106 | description = "Fix sleep state values in _PRW methods to prevent immediate wake in macOS", 107 | function_name = "instant_wake_fix" 108 | ), 109 | PatchInfo( 110 | name = "Surface Patch", 111 | description = "Special Patch for all Surface Pro / Book / Laptop hardwares", 112 | function_name = "surface_laptop_special_patch" 113 | ), 114 | PatchInfo( 115 | name = "UNC", 116 | description = "Disables unused uncore bridges to prevent kenel panic on HEDT systems", 117 | function_name = "fix_uncore_bridge" 118 | ), 119 | PatchInfo( 120 | name = "USB Reset", 121 | description = "Disable USB Hub devices to manually rebuild the ports", 122 | function_name = "disable_usb_hub_devices" 123 | ), 124 | PatchInfo( 125 | name = "USBX", 126 | description = "Creates an USBX device to inject USB power properties", 127 | function_name = "add_usb_power_properties" 128 | ), 129 | PatchInfo( 130 | name = "WMIS", 131 | description = "Certain models forget to return result from ThermalZone", 132 | function_name = "return_thermal_zone" 133 | ), 134 | PatchInfo( 135 | name = "XOSI", 136 | description = "Spoofs the operating system to Windows, enabling devices locked behind non-Windows systems on macOS", 137 | function_name = "operating_system_patch" 138 | ) 139 | ] -------------------------------------------------------------------------------- /Scripts/run.py: -------------------------------------------------------------------------------- 1 | # Source: https://github.com/corpnewt/SSDTTime/blob/7b3fb78112bf320a1bc6a7e50dddb2b375cb70b0/Scripts/run.py 2 | 3 | import sys, subprocess, time, threading, shlex 4 | try: 5 | from Queue import Queue, Empty 6 | except: 7 | from queue import Queue, Empty 8 | 9 | ON_POSIX = 'posix' in sys.builtin_module_names 10 | 11 | class Run: 12 | 13 | def __init__(self): 14 | return 15 | 16 | def _read_output(self, pipe, q): 17 | try: 18 | for line in iter(lambda: pipe.read(1), b''): 19 | q.put(line) 20 | except ValueError: 21 | pass 22 | pipe.close() 23 | 24 | def _create_thread(self, output): 25 | # Creates a new queue and thread object to watch based on the output pipe sent 26 | q = Queue() 27 | t = threading.Thread(target=self._read_output, args=(output, q)) 28 | t.daemon = True 29 | return (q,t) 30 | 31 | def _stream_output(self, comm, shell = False): 32 | output = error = "" 33 | p = None 34 | try: 35 | if shell and type(comm) is list: 36 | comm = " ".join(shlex.quote(x) for x in comm) 37 | if not shell and type(comm) is str: 38 | comm = shlex.split(comm) 39 | p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0, universal_newlines=True, close_fds=ON_POSIX) 40 | # Setup the stdout thread/queue 41 | q,t = self._create_thread(p.stdout) 42 | qe,te = self._create_thread(p.stderr) 43 | # Start both threads 44 | t.start() 45 | te.start() 46 | 47 | while True: 48 | c = z = "" 49 | try: c = q.get_nowait() 50 | except Empty: pass 51 | else: 52 | sys.stdout.write(c) 53 | output += c 54 | sys.stdout.flush() 55 | try: z = qe.get_nowait() 56 | except Empty: pass 57 | else: 58 | sys.stderr.write(z) 59 | error += z 60 | sys.stderr.flush() 61 | if not c==z=="": continue # Keep going until empty 62 | # No output - see if still running 63 | p.poll() 64 | if p.returncode != None: 65 | # Subprocess ended 66 | break 67 | # No output, but subprocess still running - stall for 20ms 68 | time.sleep(0.02) 69 | 70 | o, e = p.communicate() 71 | return (output+o, error+e, p.returncode) 72 | except: 73 | if p: 74 | try: o, e = p.communicate() 75 | except: o = e = "" 76 | return (output+o, error+e, p.returncode) 77 | return ("", "Command not found!", 1) 78 | 79 | def _decode(self, value, encoding="utf-8", errors="ignore"): 80 | # Helper method to only decode if bytes type 81 | if sys.version_info >= (3,0) and isinstance(value, bytes): 82 | return value.decode(encoding,errors) 83 | return value 84 | 85 | def _run_command(self, comm, shell = False): 86 | c = None 87 | try: 88 | if shell and type(comm) is list: 89 | comm = " ".join(shlex.quote(x) for x in comm) 90 | if not shell and type(comm) is str: 91 | comm = shlex.split(comm) 92 | p = subprocess.Popen(comm, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 93 | c = p.communicate() 94 | except: 95 | if c == None: 96 | return ("", "Command not found!", 1) 97 | return (self._decode(c[0]), self._decode(c[1]), p.returncode) 98 | 99 | def run(self, command_list, leave_on_fail = False): 100 | # Command list should be an array of dicts 101 | if type(command_list) is dict: 102 | # We only have one command 103 | command_list = [command_list] 104 | output_list = [] 105 | for comm in command_list: 106 | args = comm.get("args", []) 107 | shell = comm.get("shell", False) 108 | stream = comm.get("stream", False) 109 | sudo = comm.get("sudo", False) 110 | stdout = comm.get("stdout", False) 111 | stderr = comm.get("stderr", False) 112 | mess = comm.get("message", None) 113 | show = comm.get("show", False) 114 | 115 | if not mess == None: 116 | print(mess) 117 | 118 | if not len(args): 119 | # nothing to process 120 | continue 121 | if sudo: 122 | # Check if we have sudo 123 | out = self._run_command(["which", "sudo"]) 124 | if "sudo" in out[0]: 125 | # Can sudo 126 | if type(args) is list: 127 | args.insert(0, out[0].replace("\n", "")) # add to start of list 128 | elif type(args) is str: 129 | args = out[0].replace("\n", "") + " " + args # add to start of string 130 | 131 | if show: 132 | print(" ".join(args)) 133 | 134 | if stream: 135 | # Stream it! 136 | out = self._stream_output(args, shell) 137 | else: 138 | # Just run and gather output 139 | out = self._run_command(args, shell) 140 | if stdout and len(out[0]): 141 | print(out[0]) 142 | if stderr and len(out[1]): 143 | print(out[1]) 144 | # Append output 145 | output_list.append(out) 146 | # Check for errors 147 | if leave_on_fail and out[2] != 0: 148 | # Got an error - leave 149 | break 150 | if len(output_list) == 1: 151 | # We only ran one command - just return that output 152 | return output_list[0] 153 | return output_list -------------------------------------------------------------------------------- /Scripts/github.py: -------------------------------------------------------------------------------- 1 | from Scripts import resource_fetcher 2 | from Scripts import utils 3 | import random 4 | import json 5 | 6 | class Github: 7 | def __init__(self): 8 | self.utils = utils.Utils() 9 | self.fetcher = resource_fetcher.ResourceFetcher() 10 | 11 | def extract_payload(self, response): 12 | for line in response.splitlines(): 13 | if "type=\"application/json\"" in line: 14 | payload = line.split(">", 1)[1].split("<", 1)[0] 15 | 16 | try: 17 | payload = json.loads(payload) 18 | payload = payload["payload"] 19 | except: 20 | continue 21 | 22 | return payload 23 | return None 24 | 25 | def get_commits(self, owner, repo, branch="main", start_commit=None, after=-1): 26 | if after > -1 and not start_commit: 27 | start_commit = self.get_commits(owner, repo, branch)["currentCommit"]["oid"] 28 | 29 | if after < 0: 30 | url = "https://github.com/{}/{}/commits/{}".format(owner, repo, branch) 31 | else: 32 | url = "https://github.com/{}/{}/commits/{}?after={}+{}".format(owner, repo, branch, start_commit, after) 33 | 34 | response = self.fetcher.fetch_and_parse_content(url) 35 | 36 | if not response: 37 | raise ValueError("Failed to fetch commit information from GitHub.") 38 | 39 | payload = self.extract_payload(response) 40 | 41 | if not "commitGroups" in payload: 42 | raise ValueError("Cannot find commit information for repository {} on branch {}.".format(repo, branch)) 43 | 44 | return payload 45 | 46 | def get_latest_release(self, owner, repo): 47 | url = "https://github.com/{}/{}/releases".format(owner, repo) 48 | response = self.fetcher.fetch_and_parse_content(url) 49 | 50 | if not response: 51 | raise ValueError("Failed to fetch release information from GitHub.") 52 | 53 | tag_name = self._extract_tag_name(response) 54 | body = self._extract_body_content(response) 55 | 56 | release_tag_url = "https://github.com/{}/{}/releases/expanded_assets/{}".format(owner, repo, tag_name) 57 | response = self.fetcher.fetch_and_parse_content(release_tag_url) 58 | 59 | if not response: 60 | raise ValueError("Failed to fetch expanded assets information from GitHub.") 61 | 62 | assets = self._extract_assets(response) 63 | 64 | return { 65 | "body": body, 66 | "assets": assets 67 | } 68 | 69 | def _extract_tag_name(self, response): 70 | for line in response.splitlines(): 71 | if "", 1)[0], 1)[1].split("", 1)[0][1:] 79 | return "" 80 | 81 | def _extract_assets(self, response): 82 | assets = [] 83 | 84 | in_li_block = False 85 | 86 | for line in response.splitlines(): 87 | 88 | if "= (3, 0): 14 | from urllib.request import urlopen, Request 15 | from urllib.error import URLError 16 | else: 17 | import urllib2 18 | from urllib2 import urlopen, Request, URLError 19 | 20 | MAX_ATTEMPTS = 3 21 | 22 | class ResourceFetcher: 23 | def __init__(self, headers=None): 24 | self.request_headers = headers or { 25 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" 26 | } 27 | self.buffer_size = 16 * 1024 28 | self.ssl_context = self.create_ssl_context() 29 | self.integrity_checker = integrity_checker.IntegrityChecker() 30 | self.utils = utils.Utils() 31 | 32 | def create_ssl_context(self): 33 | try: 34 | cafile = ssl.get_default_verify_paths().openssl_cafile 35 | if not os.path.exists(cafile): 36 | import certifi 37 | cafile = certifi.where() 38 | ssl_context = ssl.create_default_context(cafile=cafile) 39 | except Exception as e: 40 | print("Failed to create SSL context: {}".format(e)) 41 | ssl_context = ssl._create_unverified_context() 42 | return ssl_context 43 | 44 | def _make_request(self, resource_url, timeout=10): 45 | try: 46 | headers = dict(self.request_headers) 47 | headers["Accept-Encoding"] = "gzip, deflate" 48 | 49 | return urlopen(Request(resource_url, headers=headers), timeout=timeout, context=self.ssl_context) 50 | except socket.timeout as e: 51 | print("Timeout error: {}".format(e)) 52 | except ssl.SSLError as e: 53 | print("SSL error: {}".format(e)) 54 | except (URLError, socket.gaierror) as e: 55 | print("Connection error: {}".format(e)) 56 | except Exception as e: 57 | print("Request failed: {}".format(e)) 58 | 59 | return None 60 | 61 | def fetch_and_parse_content(self, resource_url, content_type=None): 62 | attempt = 0 63 | response = None 64 | 65 | while attempt < 3: 66 | response = self._make_request(resource_url) 67 | 68 | if not response: 69 | attempt += 1 70 | print("Failed to fetch content from {}. Retrying...".format(resource_url)) 71 | continue 72 | 73 | if response.getcode() == 200: 74 | break 75 | 76 | attempt += 1 77 | 78 | if not response: 79 | print("Failed to fetch content from {}".format(resource_url)) 80 | return None 81 | 82 | content = response.read() 83 | 84 | if response.info().get("Content-Encoding") == "gzip" or content.startswith(b"\x1f\x8b"): 85 | try: 86 | content = gzip.decompress(content) 87 | except Exception as e: 88 | print("Failed to decompress gzip content: {}".format(e)) 89 | elif response.info().get("Content-Encoding") == "deflate": 90 | try: 91 | content = zlib.decompress(content) 92 | except Exception as e: 93 | print("Failed to decompress deflate content: {}".format(e)) 94 | 95 | try: 96 | if content_type == "json": 97 | return json.loads(content) 98 | elif content_type == "plist": 99 | return plistlib.loads(content) 100 | else: 101 | return content.decode("utf-8") 102 | except Exception as e: 103 | print("Error parsing content as {}: {}".format(content_type, e)) 104 | 105 | return None 106 | 107 | def _download_with_progress(self, response, local_file): 108 | total_size = response.getheader("Content-Length") 109 | if total_size: 110 | total_size = int(total_size) 111 | bytes_downloaded = 0 112 | start_time = time.time() 113 | last_time = start_time 114 | last_bytes = 0 115 | speeds = [] 116 | 117 | speed_str = "-- KB/s" 118 | 119 | while True: 120 | chunk = response.read(self.buffer_size) 121 | if not chunk: 122 | break 123 | local_file.write(chunk) 124 | bytes_downloaded += len(chunk) 125 | 126 | current_time = time.time() 127 | time_diff = current_time - last_time 128 | 129 | if time_diff > 0.5: 130 | current_speed = (bytes_downloaded - last_bytes) / time_diff 131 | speeds.append(current_speed) 132 | if len(speeds) > 5: 133 | speeds.pop(0) 134 | avg_speed = sum(speeds) / len(speeds) 135 | 136 | if avg_speed < 1024*1024: 137 | speed_str = "{:.1f} KB/s".format(avg_speed/1024) 138 | else: 139 | speed_str = "{:.1f} MB/s".format(avg_speed/(1024*1024)) 140 | 141 | last_time = current_time 142 | last_bytes = bytes_downloaded 143 | 144 | if total_size: 145 | percent = int(bytes_downloaded / total_size * 100) 146 | bar_length = 40 147 | filled = int(bar_length * bytes_downloaded / total_size) 148 | bar = "█" * filled + "░" * (bar_length - filled) 149 | progress = "{} [{}] {:3d}% {:.1f}/{:.1f}MB".format(speed_str, bar, percent, bytes_downloaded/(1024*1024), total_size/(1024*1024)) 150 | else: 151 | progress = "{} {:.1f}MB downloaded".format(speed_str, bytes_downloaded/(1024*1024)) 152 | 153 | print(" " * 80, end="\r") 154 | print(progress, end="\r") 155 | 156 | print() 157 | 158 | def download_and_save_file(self, resource_url, destination_path, sha256_hash=None): 159 | attempt = 0 160 | 161 | while attempt < MAX_ATTEMPTS: 162 | attempt += 1 163 | response = self._make_request(resource_url) 164 | 165 | if not response: 166 | print("Failed to fetch content from {}. Retrying...".format(resource_url)) 167 | continue 168 | 169 | with open(destination_path, "wb") as local_file: 170 | self._download_with_progress(response, local_file) 171 | 172 | if os.path.exists(destination_path) and os.path.getsize(destination_path) > 0: 173 | if sha256_hash: 174 | print("Verifying SHA256 checksum...") 175 | downloaded_hash = self.integrity_checker.get_sha256(destination_path) 176 | if downloaded_hash.lower() == sha256_hash.lower(): 177 | print("Checksum verified successfully.") 178 | return True 179 | else: 180 | print("Checksum mismatch! Removing file and retrying download...") 181 | os.remove(destination_path) 182 | continue 183 | else: 184 | print("No SHA256 hash provided. Downloading file without verification.") 185 | return True 186 | 187 | if os.path.exists(destination_path): 188 | os.remove(destination_path) 189 | 190 | if attempt < MAX_ATTEMPTS: 191 | print("Download failed for {}. Retrying...".format(resource_url)) 192 | 193 | print("Failed to download {} after {} attempts.".format(resource_url, MAX_ATTEMPTS)) 194 | return False -------------------------------------------------------------------------------- /Scripts/datasets/mac_model_data.py: -------------------------------------------------------------------------------- 1 | from Scripts.datasets import os_data 2 | 3 | class MacDevice: 4 | def __init__(self, name, cpu, cpu_generation, discrete_gpu, initial_support, last_supported_version = None): 5 | self.name = name 6 | self.cpu = cpu 7 | self.cpu_generation = cpu_generation 8 | self.discrete_gpu = discrete_gpu 9 | self.initial_support = initial_support 10 | self.last_supported_version = last_supported_version or os_data.get_latest_darwin_version() 11 | 12 | mac_devices = [ 13 | # iMac Models 14 | MacDevice("iMac11,1", "i7-860", "Lynnfield", "ATI Radeon HD 4850", "10.2.0", "17.99.99"), 15 | MacDevice("iMac11,2", "i5-680", "Clarkdale", "ATI Radeon HD 4670", "10.3.0", "17.99.99"), 16 | MacDevice("iMac11,3", "i7-870", "Clarkdale", "ATI Radeon HD 5670", "10.3.0", "17.99.99"), 17 | MacDevice("iMac12,1", "i5-2400S", "Sandy Bridge", "AMD Radeon HD 6750M", "10.6.0", "17.99.99"), 18 | MacDevice("iMac12,2", "i7-2600", "Sandy Bridge", "AMD Radeon HD 6770M", "10.6.0", "17.99.99"), 19 | MacDevice("iMac13,1", "i7-3770S", "Ivy Bridge", "NVIDIA GeForce GT 640M", "12.2.0", "19.99.99"), 20 | MacDevice("iMac13,2", "i5-3470S", "Ivy Bridge", "NVIDIA GeForce GTX 660M", "12.2.0", "19.99.99"), 21 | MacDevice("iMac13,3", "i5-3470S", "Ivy Bridge", None, "12.2.0", "19.99.99"), 22 | MacDevice("iMac14,1", "i5-4570R", "Haswell", None, "12.4.0", "19.99.99"), 23 | MacDevice("iMac14,2", "i7-4771", "Haswell", "NVIDIA GeForce GT 750M", "12.4.0", "19.99.99"), 24 | MacDevice("iMac14,3", "i5-4570S", "Haswell", "NVIDIA GeForce GT 755M", "12.4.0", "19.99.99"), 25 | MacDevice("iMac14,4", "i5-4260U", "Haswell", None, "13.2.0", "20.99.99"), 26 | MacDevice("iMac15,1", "i7-4790K", "Haswell", "AMD Radeon R9 M290X", "14.0.0", "20.99.99"), 27 | MacDevice("iMac16,1", "i5-5250U", "Broadwell", None, "15.0.0", "21.99.99"), 28 | MacDevice("iMac16,2", "i5-5675R", "Broadwell", None, "15.0.0", "21.99.99"), 29 | MacDevice("iMac17,1", "i5-6500", "Skylake", "AMD Radeon R9 M380", "15.0.0", "21.99.99"), 30 | MacDevice("iMac18,1", "i5-7360U", "Kaby Lake", None, "16.5.0", "22.99.99"), 31 | MacDevice("iMac18,2", "i5-7400", "Kaby Lake", "AMD Radeon Pro 555", "16.5.0", "22.99.99"), 32 | MacDevice("iMac18,3", "i5-7600K", "Kaby Lake", "AMD Radeon Pro 570", "16.5.0", "22.99.99"), 33 | MacDevice("iMac19,1", "i9-9900K", "Coffee Lake", "AMD Radeon Pro 570X", "18.5.0", "24.99.99"), 34 | MacDevice("iMac19,2", "i5-8500", "Coffee Lake", "AMD Radeon Pro 555X", "18.5.0", "24.99.99"), 35 | MacDevice("iMac20,1", "i5-10500", "Comet Lake", "AMD Radeon Pro 5300", "19.6.0"), 36 | MacDevice("iMac20,2", "i9-10910", "Comet Lake", "AMD Radeon Pro 5300", "19.6.0"), 37 | # MacBook Models 38 | MacDevice("MacBook8,1", "M-5Y51", "Broadwell", None, "14.1.0", "20.99.99"), 39 | MacDevice("MacBook9,1", "m3-6Y30", "Skylake", None, "15.4.0", "21.99.99"), 40 | MacDevice("MacBook10,1", "m3-7Y32", "Kaby Lake", None, "16.6.0", "22.99.99"), 41 | # MacBookAir Models 42 | MacDevice("MacBookAir4,1", "i5-2467M", "Sandy Bridge", None, "11.0.0", "17.99.99"), 43 | MacDevice("MacBookAir4,2", "i5-2557M", "Sandy Bridge", None, "11.0.0", "17.99.99"), 44 | MacDevice("MacBookAir5,1", "i5-3317U", "Ivy Bridge", None, "11.4.0", "19.99.99"), 45 | MacDevice("MacBookAir5,2", "i5-3317U", "Ivy Bridge", None, "12.2.0", "19.99.99"), 46 | MacDevice("MacBookAir6,1", "i5-4250U", "Haswell", None, "12.4.0", "20.99.99"), 47 | MacDevice("MacBookAir6,2", "i5-4250U", "Haswell", None, "12.4.0", "20.99.99"), 48 | MacDevice("MacBookAir7,1", "i5-5250U", "Broadwell", None, "14.1.0", "21.99.99"), 49 | MacDevice("MacBookAir7,2", "i5-5250U", "Broadwell", None, "14.1.0", "21.99.99"), 50 | MacDevice("MacBookAir8,1", "i5-8210Y", "Amber Lake", None, "18.2.0", "24.99.99"), 51 | MacDevice("MacBookAir8,2", "i5-8210Y", "Amber Lake", None, "18.6.0", "24.99.99"), 52 | MacDevice("MacBookAir9,1", "i3-1000NG4", "Ice Lake", None, "19.4.0", "24.99.99"), 53 | # MacBookPro Models 54 | MacDevice("MacBookPro6,1", "i7-640M", "Arrandale", "NVIDIA GeForce GT 330M", "10.3.0", "17.99.99"), 55 | MacDevice("MacBookPro6,2", "i7-640M", "Arrandale", "NVIDIA GeForce GT 330M", "10.3.0", "17.99.99"), 56 | MacDevice("MacBookPro8,1", "i5-2415M", "Sandy Bridge", None, "10.6.0", "17.99.99"), 57 | MacDevice("MacBookPro8,2", "i7-2675QM", "Sandy Bridge", "AMD Radeon HD 6490M", "10.6.0", "17.99.99"), 58 | MacDevice("MacBookPro8,3", "i7-2820QM", "Sandy Bridge", "AMD Radeon HD 6750M", "10.6.0", "17.99.99"), 59 | MacDevice("MacBookPro9,1", "i7-3615QM", "Ivy Bridge", "NVIDIA GeForce GT 650M", "11.3.0", "19.99.99"), 60 | MacDevice("MacBookPro9,2", "i5-3210M", "Ivy Bridge", None, "11.3.0", "19.99.99"), 61 | MacDevice("MacBookPro10,1", "i7-3615QM", "Ivy Bridge", "NVIDIA GeForce GT 650M", "11.4.0", "19.99.99"), 62 | MacDevice("MacBookPro10,2", "i5-3210M", "Ivy Bridge", None, "12.2.0", "19.99.99"), 63 | MacDevice("MacBookPro11,1", "i5-4258U", "Haswell", None, "13.0.0", "20.99.99"), 64 | MacDevice("MacBookPro11,2", "i7-4770HQ", "Haswell", None, "13.0.0", "20.99.99"), 65 | MacDevice("MacBookPro11,3", "i7-4850HQ", "Haswell", "NVIDIA GeForce GT 750M", "13.0.0", "20.99.99"), 66 | MacDevice("MacBookPro11,4", "i7-4770HQ", "Haswell", None, "14.3.0", "21.99.99"), 67 | MacDevice("MacBookPro11,5", "i7-4870HQ", "Haswell", "AMD Radeon R9 M370X", "14.3.0", "21.99.99"), 68 | MacDevice("MacBookPro12,1", "i5-5257U", "Broadwell", None, "14.1.0", "21.99.99"), 69 | MacDevice("MacBookPro13,1", "i5-6360U", "Skylake", None, "16.0.0", "21.99.99"), 70 | MacDevice("MacBookPro13,2", "i7-6567U", "Skylake", None, "16.1.0", "21.99.99"), 71 | MacDevice("MacBookPro13,3", "i7-6700HQ", "Skylake", "AMD Radeon Pro 450", "16.1.0", "21.99.99"), 72 | MacDevice("MacBookPro14,1", "i5-7360U", "Kaby Lake", None, "16.6.0", "22.99.99"), 73 | MacDevice("MacBookPro14,2", "i5-7267U", "Kaby Lake", None, "16.6.0", "22.99.99"), 74 | MacDevice("MacBookPro14,3", "i7-7700HQ", "Kaby Lake", "AMD Radeon Pro 555", "16.6.0", "22.99.99"), 75 | MacDevice("MacBookPro15,1", "i7-8750H", "Coffee Lake", "AMD Radeon Pro 555X", "17.99.99", "24.99.99"), 76 | MacDevice("MacBookPro15,2", "i7-8559U", "Coffee Lake", None, "17.99.99", "24.99.99"), 77 | MacDevice("MacBookPro15,3", "i7-8850H", "Coffee Lake", "AMD Radeon Pro Vega 16", "18.2.0", "24.99.99"), 78 | MacDevice("MacBookPro15,4", "i5-8257U", "Coffee Lake", None, "18.6.0", "24.99.99"), 79 | MacDevice("MacBookPro16,1", "i7-9750H", "Coffee Lake", "AMD Radeon Pro 5300", "19.0.0"), 80 | MacDevice("MacBookPro16,2", "i5-1038NG7", "Ice Lake", None, "19.4.0"), 81 | MacDevice("MacBookPro16,3", "i5-8257U", "Coffee Lake", None, "19.4.0", "24.99.99"), 82 | MacDevice("MacBookPro16,4", "i7-9750H", "Coffee Lake", "AMD Radeon Pro 5600M", "19.0.0"), 83 | # Macmini Models 84 | MacDevice("Macmini5,1", "i5-2415M", "Sandy Bridge", None, "11.0.0", "17.99.99"), 85 | MacDevice("Macmini5,2", "i5-2520M", "Sandy Bridge", None, "11.0.0", "17.99.99"), 86 | MacDevice("Macmini5,3", "i7-2635QM", "Sandy Bridge", None, "11.0.0", "17.99.99"), 87 | MacDevice("Macmini6,1", "i5-3210M", "Ivy Bridge", None, "10.8.1", "19.99.99"), 88 | MacDevice("Macmini6,2", "i7-3615QM", "Ivy Bridge", None, "10.8.1", "19.99.99"), 89 | MacDevice("Macmini7,1", "i5-4260U", "Haswell", None, "14.0.0", "21.99.99"), 90 | MacDevice("Macmini8,1", "i7-8700B", "Coffee Lake", None, "18.0.0", "24.99.99"), 91 | # iMacPro Models 92 | MacDevice("iMacPro1,1", "W-2140B", "Skylake-W", "AMD Radeon RX Vega 56", "17.3.0", "24.99.99"), 93 | # MacPro Models 94 | MacDevice("MacPro5,1", "X5675 x2", "Nehalem/Westmere", "ATI Radeon HD 5770", "10.4.0", "18.99.99"), 95 | MacDevice("MacPro6,1", "E5-1620 v2", "Ivy Bridge EP", "AMD FirePro D300", "13.0.0", "21.99.99"), 96 | MacDevice("MacPro7,1", "W-3245M", "Cascade Lake-W", "AMD Radeon Pro 580X", "19.0.0") 97 | ] 98 | 99 | def get_mac_device_by_name(name): 100 | return next((mac_device for mac_device in mac_devices if mac_device.name == name), None) -------------------------------------------------------------------------------- /Scripts/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import plistlib 5 | import shutil 6 | import re 7 | import binascii 8 | import subprocess 9 | import pathlib 10 | import zipfile 11 | import tempfile 12 | 13 | class Utils: 14 | def __init__(self, script_name = "OpCore Simplify"): 15 | self.script_name = script_name 16 | 17 | def clean_temporary_dir(self): 18 | temporary_dir = tempfile.gettempdir() 19 | 20 | for file in os.listdir(temporary_dir): 21 | if file.startswith("ocs_"): 22 | 23 | if not os.path.isdir(os.path.join(temporary_dir, file)): 24 | continue 25 | 26 | try: 27 | shutil.rmtree(os.path.join(temporary_dir, file)) 28 | except Exception as e: 29 | pass 30 | 31 | def get_temporary_dir(self): 32 | return tempfile.mkdtemp(prefix="ocs_") 33 | 34 | def write_file(self, file_path, data): 35 | file_extension = os.path.splitext(file_path)[1] 36 | 37 | with open(file_path, "w" if file_extension == ".json" else "wb") as file: 38 | if file_extension == ".json": 39 | json.dump(data, file, indent=4) 40 | else: 41 | if file_extension == ".plist": 42 | data = plistlib.dumps(data) 43 | 44 | file.write(data) 45 | 46 | def read_file(self, file_path): 47 | if not os.path.exists(file_path): 48 | return None 49 | 50 | file_extension = os.path.splitext(file_path)[1] 51 | 52 | with open(file_path, "r" if file_extension == ".json" else "rb") as file_handle: 53 | if file_extension == ".plist": 54 | data = plistlib.load(file_handle) 55 | elif file_extension == ".json": 56 | data = json.load(file_handle) 57 | else: 58 | data = file_handle.read() 59 | return data 60 | 61 | def find_matching_paths(self, root_path, extension_filter=None, name_filter=None, type_filter=None): 62 | 63 | def is_valid_item(name): 64 | if name.startswith("."): 65 | return False 66 | if extension_filter and not name.lower().endswith(extension_filter.lower()): 67 | return False 68 | if name_filter and name_filter not in name: 69 | return False 70 | return True 71 | 72 | found_paths = [] 73 | 74 | for root, dirs, files in os.walk(root_path): 75 | relative_root = root.replace(root_path, "")[1:] 76 | 77 | if type_filter in (None, "dir"): 78 | for d in dirs: 79 | if is_valid_item(d): 80 | found_paths.append((os.path.join(relative_root, d), "dir")) 81 | 82 | if type_filter in (None, "file"): 83 | for file in files: 84 | if is_valid_item(file): 85 | found_paths.append((os.path.join(relative_root, file), "file")) 86 | 87 | return sorted(found_paths, key=lambda path: path[0]) 88 | 89 | def create_folder(self, path, remove_content=False): 90 | if os.path.exists(path): 91 | if remove_content: 92 | shutil.rmtree(path) 93 | os.makedirs(path) 94 | else: 95 | os.makedirs(path) 96 | 97 | def hex_to_bytes(self, string): 98 | try: 99 | hex_string = re.sub(r'[^0-9a-fA-F]', '', string) 100 | 101 | if len(re.sub(r"\s+", "", string)) != len(hex_string): 102 | return string 103 | 104 | return binascii.unhexlify(hex_string) 105 | except binascii.Error: 106 | return string 107 | 108 | def int_to_hex(self, number): 109 | return format(number, '02X') 110 | 111 | def to_little_endian_hex(self, hex_string): 112 | hex_string = hex_string.lower().lstrip("0x") 113 | 114 | return ''.join(reversed([hex_string[i:i+2] for i in range(0, len(hex_string), 2)])).upper() 115 | 116 | def string_to_hex(self, string): 117 | return ''.join(format(ord(char), '02X') for char in string) 118 | 119 | def extract_zip_file(self, zip_path, extraction_directory=None): 120 | if extraction_directory is None: 121 | extraction_directory = os.path.splitext(zip_path)[0] 122 | 123 | os.makedirs(extraction_directory, exist_ok=True) 124 | 125 | with zipfile.ZipFile(zip_path, 'r') as zip_ref: 126 | zip_ref.extractall(extraction_directory) 127 | 128 | def contains_any(self, data, search_item, start=0, end=None): 129 | return next((item for item in data[start:end] if item.lower() in search_item.lower()), None) 130 | 131 | def normalize_path(self, path): 132 | path = re.sub(r'^[\'"]+|[\'"]+$', '', path) 133 | 134 | path = path.strip() 135 | 136 | path = os.path.expanduser(path) 137 | 138 | if os.name == 'nt': 139 | path = path.replace('\\', '/') 140 | path = re.sub(r'/+', '/', path) 141 | else: 142 | path = path.replace('\\', '') 143 | 144 | path = os.path.normpath(path) 145 | 146 | return str(pathlib.Path(path).resolve()) 147 | 148 | def parse_darwin_version(self, darwin_version): 149 | major, minor, patch = map(int, darwin_version.split('.')) 150 | return major, minor, patch 151 | 152 | def open_folder(self, folder_path): 153 | if os.name == 'posix': 154 | if 'darwin' in os.uname().sysname.lower(): 155 | subprocess.run(['open', folder_path]) 156 | else: 157 | subprocess.run(['xdg-open', folder_path]) 158 | elif os.name == 'nt': 159 | os.startfile(folder_path) 160 | 161 | def request_input(self, prompt="Press Enter to continue..."): 162 | if sys.version_info[0] < 3: 163 | user_response = raw_input(prompt) 164 | else: 165 | user_response = input(prompt) 166 | 167 | if not isinstance(user_response, str): 168 | user_response = str(user_response) 169 | 170 | return user_response 171 | 172 | def progress_bar(self, title, steps, current_step_index, done=False): 173 | self.head(title) 174 | print("") 175 | if done: 176 | for step in steps: 177 | print(" [\033[92m✓\033[0m] {}".format(step)) 178 | else: 179 | for i, step in enumerate(steps): 180 | if i < current_step_index: 181 | print(" [\033[92m✓\033[0m] {}".format(step)) 182 | elif i == current_step_index: 183 | print(" [\033[1;93m>\033[0m] {}...".format(step)) 184 | else: 185 | print(" [ ] {}".format(step)) 186 | print("") 187 | 188 | def head(self, text = None, width = 68, resize=True): 189 | if resize: 190 | self.adjust_window_size() 191 | os.system('cls' if os.name=='nt' else 'clear') 192 | if text == None: 193 | text = self.script_name 194 | separator = "═" * (width - 2) 195 | title = " {} ".format(text) 196 | if len(title) > width - 2: 197 | title = title[:width-4] + "..." 198 | title = title.center(width - 2) 199 | 200 | print("╔{}╗\n║{}║\n╚{}╝".format(separator, title, separator)) 201 | 202 | def adjust_window_size(self, content=""): 203 | lines = content.splitlines() 204 | rows = len(lines) 205 | cols = max(len(line) for line in lines) if lines else 0 206 | print('\033[8;{};{}t'.format(max(rows+6, 30), max(cols+2, 100))) 207 | 208 | def exit_program(self): 209 | self.head() 210 | width = 68 211 | print("") 212 | print("For more information, to report errors, or to contribute to the product:".center(width)) 213 | print("") 214 | 215 | separator = "─" * (width - 4) 216 | print(f" ┌{separator}┐ ") 217 | 218 | contacts = { 219 | "Facebook": "https://www.facebook.com/macforce2601", 220 | "Telegram": "https://t.me/lzhoang2601", 221 | "GitHub": "https://github.com/lzhoang2801/OpCore-Simplify" 222 | } 223 | 224 | for platform, link in contacts.items(): 225 | line = f" * {platform}: {link}" 226 | print(f" │{line.ljust(width - 4)}│ ") 227 | 228 | print(f" └{separator}┘ ") 229 | print("") 230 | print("Thank you for using our program!".center(width)) 231 | print("") 232 | self.request_input("Press Enter to exit.".center(width)) 233 | sys.exit(0) -------------------------------------------------------------------------------- /updater.py: -------------------------------------------------------------------------------- 1 | from Scripts import resource_fetcher 2 | from Scripts import github 3 | from Scripts import run 4 | from Scripts import utils 5 | import os 6 | import tempfile 7 | import shutil 8 | 9 | class Updater: 10 | def __init__(self): 11 | self.github = github.Github() 12 | self.fetcher = resource_fetcher.ResourceFetcher() 13 | self.run = run.Run().run 14 | self.utils = utils.Utils() 15 | self.sha_version = os.path.join(os.path.dirname(os.path.realpath(__file__)), "sha_version.txt") 16 | self.download_repo_url = "https://github.com/lzhoang2801/OpCore-Simplify/archive/refs/heads/main.zip" 17 | self.temporary_dir = tempfile.mkdtemp() 18 | self.current_step = 0 19 | 20 | def get_current_sha_version(self): 21 | print("Checking current version...") 22 | try: 23 | current_sha_version = self.utils.read_file(self.sha_version) 24 | 25 | if not current_sha_version: 26 | print("SHA version information is missing.") 27 | return "missing_sha_version" 28 | 29 | return current_sha_version.decode() 30 | except Exception as e: 31 | print("Error reading current SHA version: {}".format(str(e))) 32 | return "error_reading_sha_version" 33 | 34 | def get_latest_sha_version(self): 35 | print("Fetching latest version from GitHub...") 36 | try: 37 | commits = self.github.get_commits("lzhoang2801", "OpCore-Simplify") 38 | return commits["commitGroups"][0]["commits"][0]["oid"] 39 | except Exception as e: 40 | print("Error fetching latest SHA version: {}".format(str(e))) 41 | 42 | return None 43 | 44 | def download_update(self): 45 | self.current_step += 1 46 | print("") 47 | print("Step {}: Creating temporary directory...".format(self.current_step)) 48 | try: 49 | self.utils.create_folder(self.temporary_dir) 50 | print(" Temporary directory created.") 51 | 52 | self.current_step += 1 53 | print("Step {}: Downloading update package...".format(self.current_step)) 54 | print(" ", end="") 55 | file_path = os.path.join(self.temporary_dir, os.path.basename(self.download_repo_url)) 56 | self.fetcher.download_and_save_file(self.download_repo_url, file_path) 57 | 58 | if os.path.exists(file_path) and os.path.getsize(file_path) > 0: 59 | print(" Update package downloaded ({:.1f} KB)".format(os.path.getsize(file_path)/1024)) 60 | 61 | self.current_step += 1 62 | print("Step {}: Extracting files...".format(self.current_step)) 63 | self.utils.extract_zip_file(file_path) 64 | print(" Files extracted successfully") 65 | return True 66 | else: 67 | print(" Download failed or file is empty") 68 | return False 69 | except Exception as e: 70 | print(" Error during download/extraction: {}".format(str(e))) 71 | return False 72 | 73 | def update_files(self): 74 | self.current_step += 1 75 | print("Step {}: Updating files...".format(self.current_step)) 76 | try: 77 | target_dir = os.path.join(self.temporary_dir, "OpCore-Simplify-main") 78 | if not os.path.exists(target_dir): 79 | target_dir = os.path.join(self.temporary_dir, "main", "OpCore-Simplify-main") 80 | 81 | if not os.path.exists(target_dir): 82 | print(" Could not locate extracted files directory") 83 | return False 84 | 85 | file_paths = self.utils.find_matching_paths(target_dir, type_filter="file") 86 | 87 | total_files = len(file_paths) 88 | print(" Found {} files to update".format(total_files)) 89 | 90 | updated_count = 0 91 | for index, (path, type) in enumerate(file_paths, start=1): 92 | source = os.path.join(target_dir, path) 93 | destination = source.replace(target_dir, os.path.dirname(os.path.realpath(__file__))) 94 | 95 | self.utils.create_folder(os.path.dirname(destination)) 96 | 97 | print(" Updating [{}/{}]: {}".format(index, total_files, os.path.basename(path)), end="\r") 98 | 99 | try: 100 | shutil.move(source, destination) 101 | updated_count += 1 102 | 103 | if ".command" in os.path.splitext(path)[-1] and os.name != "nt": 104 | self.run({ 105 | "args": ["chmod", "+x", destination] 106 | }) 107 | except Exception as e: 108 | print(" Failed to update {}: {}".format(path, str(e))) 109 | 110 | print("") 111 | print(" Successfully updated {}/{} files".format(updated_count, total_files)) 112 | 113 | self.current_step += 1 114 | print("Step {}: Cleaning up temporary files...".format(self.current_step)) 115 | shutil.rmtree(self.temporary_dir) 116 | print(" Cleanup complete") 117 | 118 | return True 119 | except Exception as e: 120 | print(" Error during file update: {}".format(str(e))) 121 | return False 122 | 123 | def save_latest_sha_version(self, latest_sha): 124 | try: 125 | self.utils.write_file(self.sha_version, latest_sha.encode()) 126 | self.current_step += 1 127 | print("Step {}: Version information updated.".format(self.current_step)) 128 | return True 129 | except Exception as e: 130 | print("Failed to save version information: {}".format(str(e))) 131 | return False 132 | 133 | def run_update(self): 134 | self.utils.head("Check for Updates") 135 | print("") 136 | 137 | current_sha_version = self.get_current_sha_version() 138 | latest_sha_version = self.get_latest_sha_version() 139 | 140 | print("") 141 | 142 | if latest_sha_version is None: 143 | print("Could not verify the latest version from GitHub.") 144 | print("Current script SHA version: {}".format(current_sha_version)) 145 | print("Please check your internet connection and try again later.") 146 | print("") 147 | 148 | while True: 149 | user_input = self.utils.request_input("Do you want to skip the update process? (yes/No): ").strip().lower() 150 | if user_input == "yes": 151 | print("") 152 | print("Update process skipped.") 153 | return False 154 | elif user_input == "no": 155 | print("") 156 | print("Continuing with update using default version check...") 157 | latest_sha_version = "update_forced_by_user" 158 | break 159 | else: 160 | print("\033[91mInvalid selection, please try again.\033[0m\n\n") 161 | else: 162 | print("Current script SHA version: {}".format(current_sha_version)) 163 | print("Latest script SHA version: {}".format(latest_sha_version)) 164 | 165 | print("") 166 | 167 | if latest_sha_version != current_sha_version: 168 | print("Update available!") 169 | print("Updating from version {} to {}".format(current_sha_version, latest_sha_version)) 170 | print("") 171 | print("Starting update process...") 172 | 173 | if not self.download_update(): 174 | print("") 175 | print(" Update failed: Could not download or extract update package") 176 | 177 | if os.path.exists(self.temporary_dir): 178 | self.current_step += 1 179 | print("Step {}: Cleaning up temporary files...".format(self.current_step)) 180 | shutil.rmtree(self.temporary_dir) 181 | print(" Cleanup complete") 182 | 183 | return False 184 | 185 | if not self.update_files(): 186 | print("") 187 | print(" Update failed: Could not update files") 188 | return False 189 | 190 | if not self.save_latest_sha_version(latest_sha_version): 191 | print("") 192 | print(" Update completed but version information could not be saved") 193 | 194 | print("") 195 | print("Update completed successfully!") 196 | print("") 197 | print("The program needs to restart to complete the update process.") 198 | return True 199 | else: 200 | print("You are already using the latest version") 201 | return False -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |

OpCore Simplify

4 | 5 |

6 | A specialized tool that streamlines OpenCore EFI creation by automating the essential setup process and providing standardized configurations. Designed to reduce manual effort while ensuring accuracy in your Hackintosh journey. 7 |
8 |
9 | Features • 10 | How To Use • 11 | Contributing • 12 | License • 13 | Credits • 14 | Contact 15 |

16 | 17 |

18 | lzhoang2801%2FOpCore-Simplify | Trendshift 19 |

20 |
21 | 22 | > [!NOTE] 23 | > **OpenCore Legacy Patcher 3.0.0 – Now Supports macOS Tahoe 26!** 24 | > 25 | > The long awaited version 3.0.0 of OpenCore Legacy Patcher is here, bringing **initial support for macOS Tahoe 26** to the community! 26 | > 27 | > 🚨 **Please Note:** 28 | > - Only OpenCore-Patcher 3.0.0 **from the [lzhoang2801/OpenCore-Legacy-Patcher](https://github.com/lzhoang2801/OpenCore-Legacy-Patcher/releases/tag/3.0.0)** repository provides support for macOS Tahoe 26 with early patches. 29 | > - Official Dortania releases or older patches **will NOT work** with macOS Tahoe 26. 30 | 31 | > [!WARNING] 32 | > While OpCore Simplify significantly reduces setup time, the Hackintosh journey still requires: 33 | > - Understanding basic concepts from the [Dortania Guide](https://dortania.github.io/OpenCore-Install-Guide/) 34 | > - Testing and troubleshooting during the installation process 35 | > - Patience and persistence in resolving any issues that arise 36 | > 37 | > Our tool does not guarantee a successful installation in the first attempt, but it should help you get started. 38 | 39 | ## ✨ **Features** 40 | 41 | 1. **Comprehensive Hardware and macOS Support** 42 | Fully supports modern hardware. Use `Compatibility Checker` to check supported/unsupported devices and macOS version supported. 43 | 44 | | **Component** | **Supported** | 45 | |----------------|-----------------------------------------------------------------------------------------------------| 46 | | **CPU** | Intel: Nehalem and Westmere (1st Gen) → Arrow Lake (15th Gen/Core Ultra Series 2)
AMD: Ryzen and Threadripper with [AMD Vanilla](https://github.com/AMD-OSX/AMD_Vanilla) | 47 | | **GPU** | Intel iGPU: Iron Lake (1st Gen) → Ice Lake (10th Gen)
AMD APU: The entire Vega Raven ASIC family (Ryzen 1xxx → 5xxx, 7x30 series)
AMD dGPU: Navi 23, Navi 22, Navi 21 generations, and older series
NVIDIA: Kepler, Pascal, Maxwell, Fermi, Tesla generations | 48 | | **macOS** | macOS High Sierra → macOS Tahoe | 49 | 50 | 2. **ACPI Patches and Kexts** 51 | Automatically detects and adds ACPI patches and kexts based on hardware configuration. 52 | 53 | - Integrated with [SSDTTime](https://github.com/corpnewt/SSDTTime) for common patches (e.g., FakeEC, FixHPET, PLUG, RTCAWAC). 54 | - Includes custom patches: 55 | - Prevent kernel panics by directing the first CPU entry to an active CPU, disabling the UNC0 device, and creating a new RTC device for HEDT systems. 56 | - Disable unsupported or unused PCI devices, such as the GPU (using Optimus and Bumblebee methods or adding the disable-gpu property), Wi-Fi card, and NVMe storage controller. 57 | - Fix sleep state values in _PRW methods (GPRW, UPRW, HP special) to prevent immediate wake. 58 | - Add devices including ALS0, BUS0, MCHC, PMCR, PNLF, RMNE, IMEI, USBX, XOSI, along with a Surface Patch. 59 | - Enable ALSD and GPI0 devices. 60 | 61 | 3. **Automatic Updates** 62 | Automatically checks for and updates OpenCorePkg and kexts from [Dortania Builds](https://dortania.github.io/builds/) and GitHub releases before each EFI build. 63 | 64 | 4. **EFI Configuration** 65 | Apply additional customization based on both widely used sources and personal experience. 66 | 67 | - Spoof GPU IDs for certain AMD GPUs not recognized in macOS. 68 | - Use CpuTopologyRebuild kext for Intel CPUs with P-cores and E-cores to enhance performance. 69 | - Disable System Integrity Protection (SIP). 70 | - Spoof CPU IDs for Intel Pentium, Celeron, Core, and Xeon processors. 71 | - Add custom CPU names for AMD CPUs, as well as Intel Pentium, Celeron, Xeon, and Core lines from the Rocket Lake (11th) generation and newer. 72 | - Add a patch to allow booting macOS with unsupported SMBIOS. 73 | - Add NVRAM entries to bypass checking the internal Bluetooth controller. 74 | - Properly configure ResizeAppleGpuBars based on specific Resizable BAR information. 75 | - Allow flexible iGPU configuration between headless and driving a display when a supported discrete GPU is present. 76 | - Force Intel GPUs into VESA mode with HDMI and DVI connectors to simplify installation process. 77 | - Provide configuration required for using OpenCore Legacy Patcher. 78 | - Add built-in device property for network devices (fix 'Could not communicate with the server' when using iServices) and storage controllers (fix internal drives shown as external). 79 | - Prioritize SMBIOS optimized for both power management and performance. 80 | - Re-enable CPU power management on legacy Intel CPUs in macOS Ventura 13 and newer. 81 | - Apply WiFi profiles for itlwm kext to enable auto WiFi connections at boot time. 82 | 83 | and more... 84 | 85 | 5. **Easy Customization** 86 | In addition to the default settings applied, users can easily make further customizations if desired. 87 | 88 | - Custom ACPI patches, kexts, and SMBIOS adjustments (**not recommended**). 89 | - Force load kexts on unsupported macOS versions. 90 | 91 | ## 🚀 **How To Use** 92 | 93 | 1. **Download OpCore Simplify**: 94 | - Click **Code** → **Download ZIP**, or download directly via this [link](https://github.com/lzhoang2801/OpCore-Simplify/archive/refs/heads/main.zip). 95 | - Extract the downloaded ZIP file to your desired location. 96 | 97 | ![Download OpCore Simplify](https://i.imgur.com/mcE7OSX.png) 98 | 99 | 2. **Running OpCore Simplify**: 100 | - On **Windows**, run `OpCore-Simplify.bat`. 101 | - On **macOS**, run `OpCore-Simplify.command`. 102 | - On **Linux**, run `OpCore-Simplify.py` with existing Python interpreter. 103 | 104 | ![OpCore Simplify Menu](https://i.imgur.com/vTr1V9D.png) 105 | 106 | 3. **Selecting hardware report**: 107 | - On Windows, there will be an option for `E. Export hardware report`. It's recommended to use this for the best results with your hardware configuration and BIOS at the time of building. 108 | - Alternatively, use [**Hardware Sniffer**](https://github.com/lzhoang2801/Hardware-Sniffer) to create a `Report.json` and ACPI dump for configuration manully. 109 | 110 | ![Selecting hardware report](https://i.imgur.com/MbRmIGJ.png) 111 | 112 | ![Loading ACPI Tables](https://i.imgur.com/SbL6N6v.png) 113 | 114 | ![Compatibility Checker](https://i.imgur.com/kuDGMmp.png) 115 | 116 | 4. **Selecting macOS Version and Customizing OpenCore EFI**: 117 | - By default, the latest compatible macOS version will be selected for your hardware. 118 | - OpCore Simplify will automatically apply essential ACPI patches and kexts. 119 | - You can manually review and customize these settings as needed. 120 | 121 | ![OpCore Simplify Menu](https://i.imgur.com/TSk9ejy.png) 122 | 123 | 5. **Building OpenCore EFI**: 124 | - Once you've customized all options, select **Build OpenCore EFI** to generate your EFI. 125 | - The tool will automatically download the necessary bootloader and kexts, which may take a few minutes. 126 | 127 | ![WiFi Profile Extractor](https://i.imgur.com/71TkJkD.png) 128 | 129 | ![Choosing Codec Layout ID](https://i.imgur.com/Mcm20EQ.png) 130 | 131 | ![Building OpenCore EFI](https://i.imgur.com/deyj5de.png) 132 | 133 | 6. **USB Mapping**: 134 | - After building your EFI, follow the steps for mapping USB ports. 135 | 136 | ![Results](https://i.imgur.com/MIPigPF.png) 137 | 138 | 7. **Create USB and Install macOS**: 139 | - Use [**UnPlugged**](https://github.com/corpnewt/UnPlugged) on Windows to create a USB macOS installer, or follow [this guide](https://dortania.github.io/OpenCore-Install-Guide/installer-guide/mac-install.html) for macOS. 140 | - For troubleshooting, refer to the [OpenCore Troubleshooting Guide](https://dortania.github.io/OpenCore-Install-Guide/troubleshooting/troubleshooting.html). 141 | 142 | > [!NOTE] 143 | > 1. After a successful installation, if OpenCore Legacy Patcher is required, simply apply root patches to activate the missing features (such as modern Broadcom Wi-Fi card and graphics acceleration). 144 | > 145 | > 2. For AMD GPUs, after applying root patches from OpenCore Legacy Patcher, you need to remove the boot argument `-radvesa`/`-amd_no_dgpu_accel` for graphics acceleration to work. 146 | 147 | ## 🤝 **Contributing** 148 | 149 | Contributions are **highly appreciated**! If you have ideas to improve this project, feel free to fork the repo and create a pull request, or open an issue with the "enhancement" tag. 150 | 151 | Don't forget to ⭐ star the project! Thank you for your support! 🌟 152 | 153 | ## 📜 **License** 154 | 155 | Distributed under the BSD 3-Clause License. See `LICENSE` for more information. 156 | 157 | ## 🙌 **Credits** 158 | 159 | - [OpenCorePkg](https://github.com/acidanthera/OpenCorePkg) and [kexts](https://github.com/lzhoang2801/OpCore-Simplify/blob/main/Scripts/datasets/kext_data.py) – The backbone of this project. 160 | - [SSDTTime](https://github.com/corpnewt/SSDTTime) – SSDT patching utilities. 161 | 162 | ## 📞 **Contact** 163 | 164 | **Hoang Hong Quan** 165 | > Facebook [@macforce2601](https://facebook.com/macforce2601)  ·  166 | > Telegram [@lzhoang2601](https://t.me/lzhoang2601)  ·  167 | > Email: lzhoang2601@gmail.com 168 | 169 | ## 🌟 **Star History** 170 | 171 | [![Star History Chart](https://api.star-history.com/svg?repos=lzhoang2801/OpCore-Simplify&type=Date)](https://star-history.com/#lzhoang2801/OpCore-Simplify&Date) 172 | -------------------------------------------------------------------------------- /OpCore-Simplify.command: -------------------------------------------------------------------------------- 1 | # Source: https://github.com/corpnewt/SSDTTime/blob/97a3963e40a153a8df5ae61a73e150cd7a119b3c/SSDTTime.command 2 | 3 | #!/usr/bin/env bash 4 | 5 | # Get the curent directory, the script name 6 | # and the script name with "py" substituted for the extension. 7 | args=( "$@" ) 8 | dir="$(cd -- "$(dirname "$0")" >/dev/null 2>&1; pwd -P)" 9 | script="${0##*/}" 10 | target="${script%.*}.py" 11 | 12 | # use_py3: 13 | # TRUE = Use if found, use py2 otherwise 14 | # FALSE = Use py2 15 | # FORCE = Use py3 16 | use_py3="TRUE" 17 | 18 | # We'll parse if the first argument passed is 19 | # --install-python and if so, we'll just install 20 | just_installing="FALSE" 21 | 22 | tempdir="" 23 | 24 | compare_to_version () { 25 | # Compares our OS version to the passed OS version, and 26 | # return a 1 if we match the passed compare type, or a 0 if we don't. 27 | # $1 = 0 (equal), 1 (greater), 2 (less), 3 (gequal), 4 (lequal) 28 | # $2 = OS version to compare ours to 29 | if [ -z "$1" ] || [ -z "$2" ]; then 30 | # Missing info - bail. 31 | return 32 | fi 33 | local current_os= comp= 34 | current_os="$(sw_vers -productVersion)" 35 | comp="$(vercomp "$current_os" "$2")" 36 | # Check gequal and lequal first 37 | if [[ "$1" == "3" && ("$comp" == "1" || "$comp" == "0") ]] || [[ "$1" == "4" && ("$comp" == "2" || "$comp" == "0") ]] || [[ "$comp" == "$1" ]]; then 38 | # Matched 39 | echo "1" 40 | else 41 | # No match 42 | echo "0" 43 | fi 44 | } 45 | 46 | set_use_py3_if () { 47 | # Auto sets the "use_py3" variable based on 48 | # conditions passed 49 | # $1 = 0 (equal), 1 (greater), 2 (less), 3 (gequal), 4 (lequal) 50 | # $2 = OS version to compare 51 | # $3 = TRUE/FALSE/FORCE in case of match 52 | if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then 53 | # Missing vars - bail with no changes. 54 | return 55 | fi 56 | if [ "$(compare_to_version "$1" "$2")" == "1" ]; then 57 | use_py3="$3" 58 | fi 59 | } 60 | 61 | get_remote_py_version () { 62 | local pyurl= py_html= py_vers= py_num="3" 63 | pyurl="https://www.python.org/downloads/macos/" 64 | py_html="$(curl -L $pyurl --compressed 2>&1)" 65 | if [ -z "$use_py3" ]; then 66 | use_py3="TRUE" 67 | fi 68 | if [ "$use_py3" == "FALSE" ]; then 69 | py_num="2" 70 | fi 71 | py_vers="$(echo "$py_html" | grep -i "Latest Python $py_num Release" | awk '{print $8}' | cut -d'<' -f1)" 72 | echo "$py_vers" 73 | } 74 | 75 | download_py () { 76 | local vers="$1" url= 77 | clear 78 | echo " ### ###" 79 | echo " # Downloading Python #" 80 | echo "### ###" 81 | echo 82 | if [ -z "$vers" ]; then 83 | echo "Gathering latest version..." 84 | vers="$(get_remote_py_version)" 85 | fi 86 | if [ -z "$vers" ]; then 87 | # Didn't get it still - bail 88 | print_error 89 | fi 90 | echo "Located Version: $vers" 91 | echo 92 | echo "Building download url..." 93 | url="$(curl -L https://www.python.org/downloads/release/python-${vers//./}/ --compressed 2>&1 | grep -iE "python-$vers-macos.*.pkg\"" | awk -F'"' '{ print $2 }')" 94 | if [ -z "$url" ]; then 95 | # Couldn't get the URL - bail 96 | print_error 97 | fi 98 | echo " - $url" 99 | echo 100 | echo "Downloading..." 101 | echo 102 | # Create a temp dir and download to it 103 | tempdir="$(mktemp -d 2>/dev/null || mktemp -d -t 'tempdir')" 104 | curl "$url" -o "$tempdir/python.pkg" 105 | if [ "$?" != "0" ]; then 106 | echo 107 | echo " - Failed to download python installer!" 108 | echo 109 | exit $? 110 | fi 111 | echo 112 | echo "Running python install package..." 113 | echo 114 | sudo installer -pkg "$tempdir/python.pkg" -target / 115 | if [ "$?" != "0" ]; then 116 | echo 117 | echo " - Failed to install python!" 118 | echo 119 | exit $? 120 | fi 121 | # Now we expand the package and look for a shell update script 122 | pkgutil --expand "$tempdir/python.pkg" "$tempdir/python" 123 | if [ -e "$tempdir/python/Python_Shell_Profile_Updater.pkg/Scripts/postinstall" ]; then 124 | # Run the script 125 | echo 126 | echo "Updating PATH..." 127 | echo 128 | "$tempdir/python/Python_Shell_Profile_Updater.pkg/Scripts/postinstall" 129 | fi 130 | vers_folder="Python $(echo "$vers" | cut -d'.' -f1 -f2)" 131 | if [ -f "/Applications/$vers_folder/Install Certificates.command" ]; then 132 | # Certs script exists - let's execute that to make sure our certificates are updated 133 | echo 134 | echo "Updating Certificates..." 135 | echo 136 | "/Applications/$vers_folder/Install Certificates.command" 137 | fi 138 | echo 139 | echo "Cleaning up..." 140 | cleanup 141 | echo 142 | if [ "$just_installing" == "TRUE" ]; then 143 | echo "Done." 144 | else 145 | # Now we check for py again 146 | echo "Rechecking py..." 147 | downloaded="TRUE" 148 | clear 149 | main 150 | fi 151 | } 152 | 153 | cleanup () { 154 | if [ -d "$tempdir" ]; then 155 | rm -Rf "$tempdir" 156 | fi 157 | } 158 | 159 | print_error() { 160 | clear 161 | cleanup 162 | echo " ### ###" 163 | echo " # Python Not Found #" 164 | echo "### ###" 165 | echo 166 | echo "Python is not installed or not found in your PATH var." 167 | echo 168 | if [ "$kernel" == "Darwin" ]; then 169 | echo "Please go to https://www.python.org/downloads/macos/ to" 170 | echo "download and install the latest version, then try again." 171 | else 172 | echo "Please install python through your package manager and" 173 | echo "try again." 174 | fi 175 | echo 176 | exit 1 177 | } 178 | 179 | print_target_missing() { 180 | clear 181 | cleanup 182 | echo " ### ###" 183 | echo " # Target Not Found #" 184 | echo "### ###" 185 | echo 186 | echo "Could not locate $target!" 187 | echo 188 | exit 1 189 | } 190 | 191 | format_version () { 192 | local vers="$1" 193 | echo "$(echo "$1" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }')" 194 | } 195 | 196 | vercomp () { 197 | # Modified from: https://apple.stackexchange.com/a/123408/11374 198 | local ver1="$(format_version "$1")" ver2="$(format_version "$2")" 199 | if [ $ver1 -gt $ver2 ]; then 200 | echo "1" 201 | elif [ $ver1 -lt $ver2 ]; then 202 | echo "2" 203 | else 204 | echo "0" 205 | fi 206 | } 207 | 208 | get_local_python_version() { 209 | # $1 = Python bin name (defaults to python3) 210 | # Echoes the path to the highest version of the passed python bin if any 211 | local py_name="$1" max_version= python= python_version= python_path= 212 | if [ -z "$py_name" ]; then 213 | py_name="python3" 214 | fi 215 | py_list="$(which -a "$py_name" 2>/dev/null)" 216 | # Walk that newline separated list 217 | while read python; do 218 | if [ -z "$python" ]; then 219 | # Got a blank line - skip 220 | continue 221 | fi 222 | if [ "$check_py3_stub" == "1" ] && [ "$python" == "/usr/bin/python3" ]; then 223 | # See if we have a valid developer path 224 | xcode-select -p > /dev/null 2>&1 225 | if [ "$?" != "0" ]; then 226 | # /usr/bin/python3 path - but no valid developer dir 227 | continue 228 | fi 229 | fi 230 | python_version="$(get_python_version $python)" 231 | if [ -z "$python_version" ]; then 232 | # Didn't find a py version - skip 233 | continue 234 | fi 235 | # Got the py version - compare to our max 236 | if [ -z "$max_version" ] || [ "$(vercomp "$python_version" "$max_version")" == "1" ]; then 237 | # Max not set, or less than the current - update it 238 | max_version="$python_version" 239 | python_path="$python" 240 | fi 241 | done <<< "$py_list" 242 | echo "$python_path" 243 | } 244 | 245 | get_python_version() { 246 | local py_path="$1" py_version= 247 | # Get the python version by piping stderr into stdout (for py2), then grepping the output for 248 | # the word "python", getting the second element, and grepping for an alphanumeric version number 249 | py_version="$($py_path -V 2>&1 | grep -i python | cut -d' ' -f2 | grep -E "[A-Za-z\d\.]+")" 250 | if [ ! -z "$py_version" ]; then 251 | echo "$py_version" 252 | fi 253 | } 254 | 255 | prompt_and_download() { 256 | if [ "$downloaded" != "FALSE" ] || [ "$kernel" != "Darwin" ]; then 257 | # We already tried to download, or we're not on macOS - just bail 258 | print_error 259 | fi 260 | clear 261 | echo " ### ###" 262 | echo " # Python Not Found #" 263 | echo "### ###" 264 | echo 265 | target_py="Python 3" 266 | printed_py="Python 2 or 3" 267 | if [ "$use_py3" == "FORCE" ]; then 268 | printed_py="Python 3" 269 | elif [ "$use_py3" == "FALSE" ]; then 270 | target_py="Python 2" 271 | printed_py="Python 2" 272 | fi 273 | echo "Could not locate $printed_py!" 274 | echo 275 | echo "This script requires $printed_py to run." 276 | echo 277 | while true; do 278 | read -p "Would you like to install the latest $target_py now? (y/n): " yn 279 | case $yn in 280 | [Yy]* ) download_py;break;; 281 | [Nn]* ) print_error;; 282 | esac 283 | done 284 | } 285 | 286 | main() { 287 | local python= version= 288 | # Verify our target exists 289 | if [ ! -f "$dir/$target" ]; then 290 | # Doesn't exist 291 | print_target_missing 292 | fi 293 | if [ -z "$use_py3" ]; then 294 | use_py3="TRUE" 295 | fi 296 | if [ "$use_py3" != "FALSE" ]; then 297 | # Check for py3 first 298 | python="$(get_local_python_version python3)" 299 | fi 300 | if [ "$use_py3" != "FORCE" ] && [ -z "$python" ]; then 301 | # We aren't using py3 explicitly, and we don't already have a path 302 | python="$(get_local_python_version python2)" 303 | if [ -z "$python" ]; then 304 | # Try just looking for "python" 305 | python="$(get_local_python_version python)" 306 | fi 307 | fi 308 | if [ -z "$python" ]; then 309 | # Didn't ever find it - prompt 310 | prompt_and_download 311 | return 1 312 | fi 313 | # Found it - start our script and pass all args 314 | "$python" "$dir/$target" "${args[@]}" 315 | } 316 | 317 | # Keep track of whether or not we're on macOS to determine if 318 | # we can download and install python for the user as needed. 319 | kernel="$(uname -s)" 320 | # Check to see if we need to force based on 321 | # macOS version. 10.15 has a dummy python3 version 322 | # that can trip up some py3 detection in other scripts. 323 | # set_use_py3_if "3" "10.15" "FORCE" 324 | downloaded="FALSE" 325 | # Check for the aforementioned /usr/bin/python3 stub if 326 | # our OS version is 10.15 or greater. 327 | check_py3_stub="$(compare_to_version "3" "10.15")" 328 | trap cleanup EXIT 329 | if [ "$1" == "--install-python" ] && [ "$kernel" == "Darwin" ]; then 330 | just_installing="TRUE" 331 | download_py 332 | else 333 | main 334 | fi -------------------------------------------------------------------------------- /Scripts/smbios.py: -------------------------------------------------------------------------------- 1 | from Scripts.datasets.mac_model_data import mac_devices 2 | from Scripts.datasets import kext_data 3 | from Scripts.datasets import os_data 4 | from Scripts import gathering_files 5 | from Scripts import run 6 | from Scripts import utils 7 | import os 8 | import uuid 9 | import random 10 | import platform 11 | 12 | os_name = platform.system() 13 | 14 | class SMBIOS: 15 | def __init__(self): 16 | self.g = gathering_files.gatheringFiles() 17 | self.run = run.Run().run 18 | self.utils = utils.Utils() 19 | self.script_dir = os.path.dirname(os.path.realpath(__file__)) 20 | 21 | def check_macserial(self, retry_count=0): 22 | max_retries = 3 23 | 24 | if os_name == "Windows": 25 | macserial_binary = ["macserial.exe"] 26 | elif os_name == "Linux": 27 | macserial_binary = ["macserial.linux", "macserial"] 28 | elif os_name == "Darwin": 29 | macserial_binary = ["macserial"] 30 | else: 31 | raise Exception("Unknown OS for macserial") 32 | 33 | for binary in macserial_binary: 34 | macserial_path = os.path.join(self.script_dir, binary) 35 | if os.path.exists(macserial_path): 36 | return macserial_path 37 | 38 | if retry_count >= max_retries: 39 | raise Exception("Failed to find macserial after {} attempts".format(max_retries)) 40 | 41 | download_history = self.utils.read_file(self.g.download_history_file) 42 | 43 | if download_history: 44 | product_index = self.g.get_product_index(download_history, "OpenCorePkg") 45 | 46 | if product_index is not None: 47 | download_history.pop(product_index) 48 | self.utils.write_file(self.g.download_history_file, download_history) 49 | 50 | self.g.gather_bootloader_kexts([], "") 51 | return self.check_macserial(retry_count + 1) 52 | 53 | def generate_random_mac(self): 54 | random_mac = ''.join([format(random.randint(0, 255), '02X') for _ in range(6)]) 55 | return random_mac 56 | 57 | def generate_smbios(self, smbios_model): 58 | macserial = self.check_macserial() 59 | 60 | random_mac_address = self.generate_random_mac() 61 | 62 | output = self.run({ 63 | "args":[macserial, "-g", "--model", smbios_model] 64 | }) 65 | 66 | if not output or output[-1] != 0 or not output[0] or " | " not in output[0]: 67 | serial = [] 68 | else: 69 | serial = output[0].splitlines()[0].split(" | ") 70 | 71 | return { 72 | "MLB": "A" + "0"*15 + "Z" if not serial else serial[-1], 73 | "ROM": random_mac_address, 74 | "SystemProductName": smbios_model, 75 | "SystemSerialNumber": "A" + "0"*10 + "9" if not serial else serial[0], 76 | "SystemUUID": str(uuid.uuid4()).upper(), 77 | } 78 | 79 | def smbios_specific_options(self, hardware_report, smbios_model, macos_version, acpi_patches, kext_maestro): 80 | for patch in acpi_patches: 81 | if patch.name == "MCHC": 82 | patch.checked = "Intel" in hardware_report.get("CPU").get("Manufacturer") and not "MacPro" in smbios_model 83 | 84 | selected_kexts = [] 85 | 86 | if "MacPro7,1" in smbios_model: 87 | selected_kexts.append("RestrictEvents") 88 | 89 | if smbios_model in (device.name for device in mac_devices[31:34] + mac_devices[48:50] + mac_devices[51:61]): 90 | selected_kexts.append("NoTouchID") 91 | 92 | for name in selected_kexts: 93 | kext_maestro.check_kext(kext_data.kext_index_by_name.get(name), macos_version, "Beta" in os_data.get_macos_name_by_darwin(macos_version)) 94 | 95 | def select_smbios_model(self, hardware_report, macos_version): 96 | platform = "NUC" if "NUC" in hardware_report.get("Motherboard").get("Name") else hardware_report.get("Motherboard").get("Platform") 97 | codename = hardware_report.get("CPU").get("Codename") 98 | 99 | smbios_model = "MacBookPro16,2" if "Laptop" in platform else "iMacPro1,1" if self.utils.parse_darwin_version(macos_version) < self.utils.parse_darwin_version("25.0.0") else "MacPro7,1" 100 | 101 | if codename in ("Lynnfield", "Clarkdale") and "Xeon" not in hardware_report.get("CPU").get("Processor Name") and self.utils.parse_darwin_version(macos_version) < self.utils.parse_darwin_version("19.0.0"): 102 | smbios_model = "iMac11,1" if codename == "Lynnfield" else "iMac11,2" 103 | elif codename in ("Beckton", "Westmere-EX", "Gulftown", "Westmere-EP", "Clarkdale", "Lynnfield", "Jasper Forest", "Gainestown", "Bloomfield"): 104 | smbios_model = "MacPro5,1" if self.utils.parse_darwin_version(macos_version) < self.utils.parse_darwin_version("19.0.0") else "MacPro6,1" 105 | elif ("Sandy Bridge" in codename or "Ivy Bridge" in codename) and self.utils.parse_darwin_version(macos_version) < self.utils.parse_darwin_version("22.0.0"): 106 | smbios_model = "MacPro6,1" 107 | 108 | if platform != "Laptop" and list(hardware_report.get("GPU").items())[-1][-1].get("Device Type") != "Integrated GPU": 109 | return smbios_model 110 | 111 | if codename in ("Arrandale", "Clarksfield"): 112 | smbios_model = "MacBookPro6,1" 113 | elif "Sandy Bridge" in codename: 114 | if "Desktop" in platform: 115 | smbios_model = "iMac12,2" 116 | elif "NUC" in platform: 117 | smbios_model = "Macmini5,1" if int(hardware_report.get("CPU").get("Core Count")) < 4 else "Macmini5,3" 118 | elif "Laptop" in platform: 119 | smbios_model = "MacBookPro8,1" if int(hardware_report.get("CPU").get("Core Count")) < 4 else "MacBookPro8,2" 120 | elif "Ivy Bridge" in codename: 121 | if "Desktop" in platform: 122 | smbios_model = "iMac13,1" if "Integrated GPU" in list(hardware_report.get("GPU").items())[0][-1].get("Device Type") else "iMac13,2" 123 | elif "NUC" in platform: 124 | smbios_model = "Macmini6,1" if int(hardware_report.get("CPU").get("Core Count")) < 4 else "Macmini6,2" 125 | elif "Laptop" in platform: 126 | smbios_model = "MacBookPro10,2" if int(hardware_report.get("CPU").get("Core Count")) < 4 else "MacBookPro10,1" 127 | elif "Haswell" in codename: 128 | if "Desktop" in platform: 129 | smbios_model = "iMac14,4" if "Integrated GPU" in list(hardware_report.get("GPU").items())[0][-1].get("Device Type") else "iMac15,1" 130 | elif "NUC" in platform: 131 | smbios_model = "Macmini7,1" 132 | elif "Laptop" in platform: 133 | smbios_model = "MacBookPro11,1" if int(hardware_report.get("CPU").get("Core Count")) < 4 else "MacBookPro11,5" 134 | elif "Broadwell" in codename: 135 | if "Desktop" in platform: 136 | smbios_model = "iMac16,2" if "Integrated GPU" in list(hardware_report.get("GPU").items())[0][-1].get("Device Type") else "iMac17,1" 137 | elif "NUC" in platform: 138 | smbios_model = "iMac16,1" 139 | elif "Laptop" in platform: 140 | smbios_model = "MacBookPro12,1" if int(hardware_report.get("CPU").get("Core Count")) < 4 else "MacBookPro11,5" 141 | elif "Skylake" in codename: 142 | smbios_model = "iMac17,1" 143 | if "Laptop" in platform: 144 | smbios_model = "MacBookPro13,1" if int(hardware_report.get("CPU").get("Core Count")) < 4 else "MacBookPro13,3" 145 | elif "Amber Lake" in codename or "Kaby Lake" in codename: 146 | smbios_model = "iMac18,1" if "Integrated GPU" in list(hardware_report.get("GPU").items())[0][-1].get("Device Type") else "iMac18,3" 147 | if "Laptop" in platform: 148 | smbios_model = "MacBookPro14,1" if int(hardware_report.get("CPU").get("Core Count")) < 4 else "MacBookPro14,3" 149 | elif "Cannon Lake" in codename or "Whiskey Lake" in codename or "Coffee Lake" in codename or "Comet Lake" in codename: 150 | smbios_model = "Macmini8,1" 151 | if "Desktop" in platform: 152 | smbios_model = "iMac18,3" if self.utils.parse_darwin_version(macos_version) < self.utils.parse_darwin_version("18.0.0") else "iMac19,1" 153 | if "Comet Lake" in codename: 154 | smbios_model = "iMac20,1" if int(hardware_report.get("CPU").get("Core Count")) < 10 else "iMac20,2" 155 | elif "Laptop" in platform: 156 | if "-8" in hardware_report.get("CPU").get("Processor Name"): 157 | smbios_model = "MacBookPro15,2" if int(hardware_report.get("CPU").get("Core Count")) < 6 else "MacBookPro15,3" 158 | else: 159 | smbios_model = "MacBookPro16,3" if int(hardware_report.get("CPU").get("Core Count")) < 6 else "MacBookPro16,1" 160 | elif "Ice Lake" in codename: 161 | smbios_model = "MacBookAir9,1" 162 | 163 | return smbios_model 164 | 165 | def customize_smbios_model(self, hardware_report, selected_smbios_model, macos_version): 166 | current_category = None 167 | default_smbios_model = self.select_smbios_model(hardware_report, macos_version) 168 | show_all_models = False 169 | is_laptop = "Laptop" == hardware_report.get("Motherboard").get("Platform") 170 | 171 | while True: 172 | incompatible_models_by_index = [] 173 | contents = [] 174 | contents.append("") 175 | if show_all_models: 176 | contents.append("List of available SMBIOS:") 177 | else: 178 | contents.append("List of compatible SMBIOS:") 179 | for index, device in enumerate(mac_devices, start=1): 180 | isSupported = self.utils.parse_darwin_version(device.initial_support) <= self.utils.parse_darwin_version(macos_version) <= self.utils.parse_darwin_version(device.last_supported_version) 181 | if device.name not in (default_smbios_model, selected_smbios_model) and not show_all_models and (not isSupported or (is_laptop and not device.name.startswith("MacBook")) or (not is_laptop and device.name.startswith("MacBook"))): 182 | incompatible_models_by_index.append(index - 1) 183 | continue 184 | 185 | category = "" 186 | for char in device.name: 187 | if char.isdigit(): 188 | break 189 | category += char 190 | if category != current_category: 191 | current_category = category 192 | category_header = "Category: {}".format(current_category if current_category else "Uncategorized") 193 | contents.append(f"\n{category_header}\n" + "=" * len(category_header)) 194 | checkbox = "[*]" if device.name == selected_smbios_model else "[ ]" 195 | 196 | line = "{} {:2}. {:15} - {:10} {:20}{}".format(checkbox, index, device.name, device.cpu, "({})".format(device.cpu_generation), "" if not device.discrete_gpu else " - {}".format(device.discrete_gpu)) 197 | if device.name == selected_smbios_model: 198 | line = "\033[1;32m{}\033[0m".format(line) 199 | elif not isSupported: 200 | line = "\033[90m{}\033[0m".format(line) 201 | contents.append(line) 202 | contents.append("") 203 | contents.append("\033[1;93mNote:\033[0m") 204 | contents.append("- Lines in gray indicate mac models that are not officially supported by {}.".format(os_data.get_macos_name_by_darwin(macos_version))) 205 | contents.append("") 206 | if not show_all_models: 207 | contents.append("A. Show all models") 208 | else: 209 | contents.append("C. Show compatible models only") 210 | if selected_smbios_model != default_smbios_model: 211 | contents.append("R. Restore default SMBIOS model ({})".format(default_smbios_model)) 212 | contents.append("") 213 | contents.append("B. Back") 214 | contents.append("Q. Quit") 215 | contents.append("") 216 | content = "\n".join(contents) 217 | 218 | self.utils.adjust_window_size(content) 219 | self.utils.head("Customize SMBIOS Model", resize=False) 220 | print(content) 221 | option = self.utils.request_input("Select your option: ") 222 | if option.lower() == "q": 223 | self.utils.exit_program() 224 | if option.lower() == "b": 225 | return selected_smbios_model 226 | if option.lower() == "r" and selected_smbios_model != default_smbios_model: 227 | return default_smbios_model 228 | if option.lower() in ("a", "c"): 229 | show_all_models = not show_all_models 230 | continue 231 | 232 | if option.strip().isdigit(): 233 | index = int(option) - 1 234 | if index >= 0 and index < len(mac_devices): 235 | if not show_all_models and index in incompatible_models_by_index: 236 | continue 237 | 238 | selected_smbios_model = mac_devices[index].name -------------------------------------------------------------------------------- /Scripts/wifi_profile_extractor.py: -------------------------------------------------------------------------------- 1 | from Scripts import run 2 | from Scripts import utils 3 | import platform 4 | import json 5 | 6 | os_name = platform.system() 7 | 8 | class WifiProfileExtractor: 9 | def __init__(self): 10 | self.run = run.Run().run 11 | self.utils = utils.Utils() 12 | 13 | def get_authentication_type(self, authentication_type): 14 | authentication_type = authentication_type.lower() 15 | 16 | open_types = ("none", "owe", "open") 17 | for open_type in open_types: 18 | if open_type in authentication_type: 19 | return "open" 20 | 21 | if "wep" in authentication_type or "shared" in authentication_type: 22 | return "wep" 23 | 24 | if "wpa" in authentication_type or "sae" in authentication_type: 25 | return "wpa" 26 | 27 | return None 28 | 29 | def validate_wifi_password(self, authentication_type=None, password=None): 30 | print("Validating password with authentication type: {}".format(authentication_type)) 31 | 32 | if password is None: 33 | return None 34 | 35 | if authentication_type is None: 36 | return password 37 | 38 | if authentication_type == "open": 39 | return "" 40 | 41 | try: 42 | password.encode('ascii') 43 | except UnicodeEncodeError: 44 | return None 45 | 46 | if 8 <= len(password) <= 63 and all(32 <= ord(c) <= 126 for c in password): 47 | return password 48 | 49 | return None 50 | 51 | def get_wifi_password_macos(self, ssid): 52 | output = self.run({ 53 | "args": ["security", "find-generic-password", "-wa", ssid] 54 | }) 55 | 56 | if output[-1] != 0: 57 | return None 58 | 59 | try: 60 | ssid_info = json.loads(output[0].strip()) 61 | password = ssid_info.get("password") 62 | except: 63 | password = output[0].strip() if output[0].strip() else None 64 | 65 | return self.validate_wifi_password("wpa", password) 66 | 67 | def get_wifi_password_windows(self, ssid): 68 | output = self.run({ 69 | "args": ["netsh", "wlan", "show", "profile", ssid, "key=clear"] 70 | }) 71 | 72 | if output[-1] != 0: 73 | return None 74 | 75 | authentication_type = None 76 | password = None 77 | 78 | for line in output[0].splitlines(): 79 | if authentication_type is None and "Authentication" in line: 80 | authentication_type = self.get_authentication_type(line.split(":")[1].strip()) 81 | elif "Key Content" in line: 82 | password = line.split(":")[1].strip() 83 | 84 | return self.validate_wifi_password(authentication_type, password) 85 | 86 | def get_wifi_password_linux(self, ssid): 87 | output = self.run({ 88 | "args": ["nmcli", "--show-secrets", "connection", "show", ssid] 89 | }) 90 | 91 | if output[-1] != 0: 92 | return None 93 | 94 | authentication_type = None 95 | password = None 96 | 97 | for line in output[0].splitlines(): 98 | if "802-11-wireless-security.key-mgmt:" in line: 99 | authentication_type = self.get_authentication_type(line.split(":")[1].strip()) 100 | elif "802-11-wireless-security.psk:" in line: 101 | password = line.split(":")[1].strip() 102 | 103 | return self.validate_wifi_password(authentication_type, password) 104 | 105 | def ask_network_count(self, total_networks): 106 | self.utils.head("WiFi Network Retrieval") 107 | print("") 108 | print("Found {} WiFi networks on this device.".format(total_networks)) 109 | print("") 110 | print("How many networks would you like to process?") 111 | print(" 1-{} - Specific number (default: 5)".format(total_networks)) 112 | print(" A - All available networks") 113 | print("") 114 | 115 | num_choice = self.utils.request_input("Enter your choice: ").strip().lower() or "5" 116 | 117 | if num_choice == "a": 118 | print("Will process all available networks.") 119 | return total_networks 120 | 121 | try: 122 | max_networks = min(int(num_choice), total_networks) 123 | print("Will process up to {} networks.".format(max_networks)) 124 | return max_networks 125 | except: 126 | max_networks = min(5, total_networks) 127 | print("Invalid choice. Will process up to {} networks.".format(max_networks)) 128 | return max_networks 129 | 130 | def process_networks(self, ssid_list, max_networks, get_password_func): 131 | networks = [] 132 | processed_count = 0 133 | consecutive_failures = 0 134 | max_consecutive_failures = 3 135 | 136 | while len(networks) < max_networks and processed_count < len(ssid_list): 137 | ssid = ssid_list[processed_count] 138 | 139 | try: 140 | print("") 141 | print("Processing {}/{}: {}".format(processed_count + 1, len(ssid_list), ssid)) 142 | if os_name == "Darwin": 143 | print("Please enter your administrator name and password or click 'Deny' to skip this network.") 144 | 145 | password = get_password_func(ssid) 146 | if password is not None: 147 | if (ssid, password) not in networks: 148 | consecutive_failures = 0 149 | networks.append((ssid, password)) 150 | print("Successfully retrieved password.") 151 | 152 | if len(networks) == max_networks: 153 | break 154 | else: 155 | consecutive_failures += 1 if os_name == "Darwin" else 0 156 | print("Could not retrieve password for this network.") 157 | 158 | if consecutive_failures >= max_consecutive_failures: 159 | continue_input = self.utils.request_input("\nUnable to retrieve passwords. Continue trying? (Yes/no): ").strip().lower() or "yes" 160 | 161 | if continue_input != "yes": 162 | break 163 | 164 | consecutive_failures = 0 165 | except Exception as e: 166 | consecutive_failures += 1 if os_name == "Darwin" else 0 167 | print("Error processing network '{}': {}".format(ssid, str(e))) 168 | 169 | if consecutive_failures >= max_consecutive_failures: 170 | continue_input = self.utils.request_input("\nUnable to retrieve passwords. Continue trying? (Yes/no): ").strip().lower() or "yes" 171 | 172 | if continue_input != "yes": 173 | break 174 | 175 | consecutive_failures = 0 176 | finally: 177 | processed_count += 1 178 | 179 | if processed_count >= max_networks and len(networks) < max_networks and processed_count < len(ssid_list): 180 | continue_input = self.utils.request_input("\nOnly retrieved {}/{} networks. Try more to reach your target? (Yes/no): ".format(len(networks), max_networks)).strip().lower() or "yes" 181 | 182 | if continue_input != "yes": 183 | break 184 | 185 | consecutive_failures = 0 186 | 187 | return networks 188 | 189 | def get_preferred_networks_macos(self, interface): 190 | output = self.run({ 191 | "args": ["networksetup", "-listpreferredwirelessnetworks", interface] 192 | }) 193 | 194 | if output[-1] != 0 or "Preferred networks on" not in output[0]: 195 | return [] 196 | 197 | ssid_list = [network.strip() for network in output[0].splitlines()[1:] if network.strip()] 198 | 199 | if not ssid_list: 200 | return [] 201 | 202 | max_networks = self.ask_network_count(len(ssid_list)) 203 | 204 | self.utils.head("Administrator Authentication Required") 205 | print("") 206 | print("To retrieve WiFi passwords from the Keychain, macOS will prompt") 207 | print("you for administrator credentials for each WiFi network.") 208 | 209 | return self.process_networks(ssid_list, max_networks, self.get_wifi_password_macos) 210 | 211 | def get_preferred_networks_windows(self): 212 | output = self.run({ 213 | "args": ["netsh", "wlan", "show", "profiles"] 214 | }) 215 | 216 | if output[-1] != 0: 217 | return [] 218 | 219 | ssid_list = [] 220 | 221 | for line in output[0].splitlines(): 222 | if "All User Profile" in line: 223 | try: 224 | ssid = line.split(":")[1].strip() 225 | if ssid: 226 | ssid_list.append(ssid) 227 | except: 228 | continue 229 | 230 | if not ssid_list: 231 | return [] 232 | 233 | max_networks = len(ssid_list) 234 | 235 | self.utils.head("WiFi Profile Extractor") 236 | print("") 237 | print("Retrieving passwords for {} network(s)...".format(len(ssid_list))) 238 | 239 | return self.process_networks(ssid_list, max_networks, self.get_wifi_password_windows) 240 | 241 | def get_preferred_networks_linux(self): 242 | output = self.run({ 243 | "args": ["nmcli", "-t", "-f", "NAME", "connection", "show"] 244 | }) 245 | 246 | if output[-1] != 0: 247 | return [] 248 | 249 | ssid_list = [network.strip() for network in output[0].splitlines() if network.strip()] 250 | 251 | if not ssid_list: 252 | return [] 253 | 254 | max_networks = len(ssid_list) 255 | 256 | self.utils.head("WiFi Profile Extractor") 257 | print("") 258 | print("Retrieving passwords for {} network(s)...".format(len(ssid_list))) 259 | 260 | return self.process_networks(ssid_list, max_networks, self.get_wifi_password_linux) 261 | 262 | def get_wifi_interfaces(self): 263 | output = self.run({ 264 | "args": ["networksetup", "-listallhardwareports"] 265 | }) 266 | 267 | if output[-1] != 0: 268 | return [] 269 | 270 | interfaces = [] 271 | 272 | for interface_info in output[0].split("\n\n"): 273 | if "Device: en" in interface_info: 274 | try: 275 | interface = "en{}".format(int(interface_info.split("Device: en")[1].split("\n")[0])) 276 | 277 | test_output = self.run({ 278 | "args": ["networksetup", "-listpreferredwirelessnetworks", interface] 279 | }) 280 | 281 | if test_output[-1] == 0 and "Preferred networks on" in test_output[0]: 282 | interfaces.append(interface) 283 | except: 284 | continue 285 | 286 | return interfaces 287 | 288 | def get_profiles(self): 289 | os_name = platform.system() 290 | 291 | self.utils.head("WiFi Profile Extractor") 292 | print("") 293 | print("\033[1;93mNote:\033[0m") 294 | print("- When using itlwm kext, WiFi appears as Ethernet in macOS") 295 | print("- You'll need Heliport app to manage WiFi connections in macOS") 296 | print("- This step will enable auto WiFi connections at boot time") 297 | print(" and is useful for users installing macOS via Recovery OS") 298 | print("") 299 | 300 | while True: 301 | user_input = self.utils.request_input("Would you like to scan for WiFi profiles? (Yes/no): ").strip().lower() 302 | 303 | if user_input == "yes": 304 | break 305 | elif user_input == "no": 306 | return [] 307 | else: 308 | print("\033[91mInvalid selection, please try again.\033[0m\n\n") 309 | 310 | profiles = [] 311 | self.utils.head("Detecting WiFi Profiles") 312 | print("") 313 | print("Scanning for WiFi profiles...") 314 | 315 | if os_name == "Windows": 316 | profiles = self.get_preferred_networks_windows() 317 | elif os_name == "Linux": 318 | profiles = self.get_preferred_networks_linux() 319 | elif os_name == "Darwin": 320 | wifi_interfaces = self.get_wifi_interfaces() 321 | 322 | if wifi_interfaces: 323 | for interface in wifi_interfaces: 324 | print("Checking interface: {}".format(interface)) 325 | interface_profiles = self.get_preferred_networks_macos(interface) 326 | if interface_profiles: 327 | profiles = interface_profiles 328 | break 329 | else: 330 | print("No WiFi interfaces detected.") 331 | 332 | if not profiles: 333 | self.utils.head("WiFi Profile Extractor") 334 | print("") 335 | print("No WiFi profiles with saved passwords were found.") 336 | self.utils.request_input() 337 | 338 | self.utils.head("WiFi Profile Extractor") 339 | print("") 340 | print("Found the following WiFi profiles with saved passwords:") 341 | print("") 342 | print("Index SSID Password") 343 | print("-------------------------------------------------------") 344 | for index, (ssid, password) in enumerate(profiles, start=1): 345 | print("{:<6} {:<32} {:<8}".format(index, ssid[:31] + "..." if len(ssid) > 31 else ssid, password[:12] + "..." if len(password) > 12 else password)) 346 | print("") 347 | print("Successfully applied {} WiFi profiles.".format(len(profiles))) 348 | print("") 349 | 350 | self.utils.request_input() 351 | return profiles -------------------------------------------------------------------------------- /OpCore-Simplify.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Source: https://github.com/corpnewt/SSDTTime/blob/97a3963e40a153a8df5ae61a73e150cd7a119b3c/SSDTTime.bat 3 | REM Get our local path before delayed expansion - allows ! in path 4 | set "thisDir=%~dp0" 5 | 6 | setlocal enableDelayedExpansion 7 | REM Setup initial vars 8 | set "script_name=" 9 | set /a tried=0 10 | set "toask=yes" 11 | set "pause_on_error=yes" 12 | set "py2v=" 13 | set "py2path=" 14 | set "py3v=" 15 | set "py3path=" 16 | set "pypath=" 17 | set "targetpy=3" 18 | 19 | REM use_py3: 20 | REM TRUE = Use if found, use py2 otherwise 21 | REM FALSE = Use py2 22 | REM FORCE = Use py3 23 | set "use_py3=TRUE" 24 | 25 | REM We'll parse if the first argument passed is 26 | REM --install-python and if so, we'll just install 27 | set "just_installing=FALSE" 28 | 29 | REM Get the system32 (or equivalent) path 30 | call :getsyspath "syspath" 31 | 32 | REM Make sure the syspath exists 33 | if "!syspath!" == "" ( 34 | if exist "%SYSTEMROOT%\system32\cmd.exe" ( 35 | if exist "%SYSTEMROOT%\system32\reg.exe" ( 36 | if exist "%SYSTEMROOT%\system32\where.exe" ( 37 | REM Fall back on the default path if it exists 38 | set "ComSpec=%SYSTEMROOT%\system32\cmd.exe" 39 | set "syspath=%SYSTEMROOT%\system32\" 40 | ) 41 | ) 42 | ) 43 | if "!syspath!" == "" ( 44 | cls 45 | echo ### ### 46 | echo # Warning # 47 | echo ### ### 48 | echo. 49 | echo Could not locate cmd.exe, reg.exe, or where.exe 50 | echo. 51 | echo Please ensure your ComSpec environment variable is properly configured and 52 | echo points directly to cmd.exe, then try again. 53 | echo. 54 | echo Current CompSpec Value: "%ComSpec%" 55 | echo. 56 | echo Press [enter] to quit. 57 | pause > nul 58 | exit /b 1 59 | ) 60 | ) 61 | 62 | if "%~1" == "--install-python" ( 63 | set "just_installing=TRUE" 64 | goto installpy 65 | ) 66 | 67 | goto checkscript 68 | 69 | :checkscript 70 | REM Check for our script first 71 | set "looking_for=!script_name!" 72 | if "!script_name!" == "" ( 73 | set "looking_for=%~n0.py or %~n0.command" 74 | set "script_name=%~n0.py" 75 | if not exist "!thisDir!\!script_name!" ( 76 | set "script_name=%~n0.command" 77 | ) 78 | ) 79 | if not exist "!thisDir!\!script_name!" ( 80 | echo Could not find !looking_for!. 81 | echo Please make sure to run this script from the same directory 82 | echo as !looking_for!. 83 | echo. 84 | echo Press [enter] to quit. 85 | pause > nul 86 | exit /b 1 87 | ) 88 | goto checkpy 89 | 90 | :checkpy 91 | call :updatepath 92 | for /f "USEBACKQ tokens=*" %%x in (`!syspath!where.exe python 2^> nul`) do ( call :checkpyversion "%%x" "py2v" "py2path" "py3v" "py3path" ) 93 | for /f "USEBACKQ tokens=*" %%x in (`!syspath!where.exe python3 2^> nul`) do ( call :checkpyversion "%%x" "py2v" "py2path" "py3v" "py3path" ) 94 | for /f "USEBACKQ tokens=*" %%x in (`!syspath!where.exe py 2^> nul`) do ( call :checkpylauncher "%%x" "py2v" "py2path" "py3v" "py3path" ) 95 | REM Walk our returns to see if we need to install 96 | if /i "!use_py3!" == "FALSE" ( 97 | set "targetpy=2" 98 | set "pypath=!py2path!" 99 | ) else if /i "!use_py3!" == "FORCE" ( 100 | set "pypath=!py3path!" 101 | ) else if /i "!use_py3!" == "TRUE" ( 102 | set "pypath=!py3path!" 103 | if "!pypath!" == "" set "pypath=!py2path!" 104 | ) 105 | if not "!pypath!" == "" ( 106 | goto runscript 107 | ) 108 | if !tried! lss 1 ( 109 | if /i "!toask!"=="yes" ( 110 | REM Better ask permission first 111 | goto askinstall 112 | ) else ( 113 | goto installpy 114 | ) 115 | ) else ( 116 | cls 117 | echo ### ### 118 | echo # Warning # 119 | echo ### ### 120 | echo. 121 | REM Couldn't install for whatever reason - give the error message 122 | echo Python is not installed or not found in your PATH var. 123 | echo Please install it from https://www.python.org/downloads/windows/ 124 | echo. 125 | echo Make sure you check the box labeled: 126 | echo. 127 | echo "Add Python X.X to PATH" 128 | echo. 129 | echo Where X.X is the py version you're installing. 130 | echo. 131 | echo Press [enter] to quit. 132 | pause > nul 133 | exit /b 1 134 | ) 135 | goto runscript 136 | 137 | :checkpylauncher 138 | REM Attempt to check the latest python 2 and 3 versions via the py launcher 139 | for /f "USEBACKQ tokens=*" %%x in (`%~1 -2 -c "import sys; print(sys.executable)" 2^> nul`) do ( call :checkpyversion "%%x" "%~2" "%~3" "%~4" "%~5" ) 140 | for /f "USEBACKQ tokens=*" %%x in (`%~1 -3 -c "import sys; print(sys.executable)" 2^> nul`) do ( call :checkpyversion "%%x" "%~2" "%~3" "%~4" "%~5" ) 141 | goto :EOF 142 | 143 | :checkpyversion 144 | set "version="&for /f "tokens=2* USEBACKQ delims= " %%a in (`"%~1" -V 2^>^&1`) do ( 145 | REM Ensure we have a version number 146 | call :isnumber "%%a" 147 | if not "!errorlevel!" == "0" goto :EOF 148 | set "version=%%a" 149 | ) 150 | if not defined version goto :EOF 151 | if "!version:~0,1!" == "2" ( 152 | REM Python 2 153 | call :comparepyversion "!version!" "!%~2!" 154 | if "!errorlevel!" == "1" ( 155 | set "%~2=!version!" 156 | set "%~3=%~1" 157 | ) 158 | ) else ( 159 | REM Python 3 160 | call :comparepyversion "!version!" "!%~4!" 161 | if "!errorlevel!" == "1" ( 162 | set "%~4=!version!" 163 | set "%~5=%~1" 164 | ) 165 | ) 166 | goto :EOF 167 | 168 | :isnumber 169 | set "var="&for /f "delims=0123456789." %%i in ("%~1") do set var=%%i 170 | if defined var (exit /b 1) 171 | exit /b 0 172 | 173 | :comparepyversion 174 | REM Exits with status 0 if equal, 1 if v1 gtr v2, 2 if v1 lss v2 175 | for /f "tokens=1,2,3 delims=." %%a in ("%~1") do ( 176 | set a1=%%a 177 | set a2=%%b 178 | set a3=%%c 179 | ) 180 | for /f "tokens=1,2,3 delims=." %%a in ("%~2") do ( 181 | set b1=%%a 182 | set b2=%%b 183 | set b3=%%c 184 | ) 185 | if not defined a1 set a1=0 186 | if not defined a2 set a2=0 187 | if not defined a3 set a3=0 188 | if not defined b1 set b1=0 189 | if not defined b2 set b2=0 190 | if not defined b3 set b3=0 191 | if %a1% gtr %b1% exit /b 1 192 | if %a1% lss %b1% exit /b 2 193 | if %a2% gtr %b2% exit /b 1 194 | if %a2% lss %b2% exit /b 2 195 | if %a3% gtr %b3% exit /b 1 196 | if %a3% lss %b3% exit /b 2 197 | exit /b 0 198 | 199 | :askinstall 200 | cls 201 | echo ### ### 202 | echo # Python Not Found # 203 | echo ### ### 204 | echo. 205 | echo Python !targetpy! was not found on the system or in the PATH var. 206 | echo. 207 | set /p "menu=Would you like to install it now? [y/n]: " 208 | if /i "!menu!"=="y" ( 209 | REM We got the OK - install it 210 | goto installpy 211 | ) else if "!menu!"=="n" ( 212 | REM No OK here... 213 | set /a tried=!tried!+1 214 | goto checkpy 215 | ) 216 | REM Incorrect answer - go back 217 | goto askinstall 218 | 219 | :installpy 220 | REM This will attempt to download and install python 221 | REM First we get the html for the python downloads page for Windows 222 | set /a tried=!tried!+1 223 | cls 224 | echo ### ### 225 | echo # Installing Python # 226 | echo ### ### 227 | echo. 228 | echo Gathering info from https://www.python.org/downloads/windows/... 229 | powershell -command "[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;(new-object System.Net.WebClient).DownloadFile('https://www.python.org/downloads/windows/','%TEMP%\pyurl.txt')" 230 | REM Extract it if it's gzip compressed 231 | powershell -command "$infile='%TEMP%\pyurl.txt';$outfile='%TEMP%\pyurl.temp';try{$input=New-Object System.IO.FileStream $infile,([IO.FileMode]::Open),([IO.FileAccess]::Read),([IO.FileShare]::Read);$output=New-Object System.IO.FileStream $outfile,([IO.FileMode]::Create),([IO.FileAccess]::Write),([IO.FileShare]::None);$gzipStream=New-Object System.IO.Compression.GzipStream $input,([IO.Compression.CompressionMode]::Decompress);$buffer=New-Object byte[](1024);while($true){$read=$gzipstream.Read($buffer,0,1024);if($read -le 0){break};$output.Write($buffer,0,$read)};$gzipStream.Close();$output.Close();$input.Close();Move-Item -Path $outfile -Destination $infile -Force}catch{}" 232 | if not exist "%TEMP%\pyurl.txt" ( 233 | if /i "!just_installing!" == "TRUE" ( 234 | echo Failed to get info 235 | exit /b 1 236 | ) else ( 237 | goto checkpy 238 | ) 239 | ) 240 | echo Parsing for latest... 241 | pushd "%TEMP%" 242 | :: Version detection code slimmed by LussacZheng (https://github.com/corpnewt/gibMacOS/issues/20) 243 | for /f "tokens=9 delims=< " %%x in ('findstr /i /c:"Latest Python !targetpy! Release" pyurl.txt') do ( set "release=%%x" ) 244 | popd 245 | if "!release!" == "" ( 246 | if /i "!just_installing!" == "TRUE" ( 247 | echo Failed to get python version 248 | exit /b 1 249 | ) else ( 250 | goto checkpy 251 | ) 252 | ) 253 | echo Found Python !release! - Downloading... 254 | REM Let's delete our txt file now - we no longer need it 255 | del "%TEMP%\pyurl.txt" 256 | REM At this point - we should have the version number. 257 | REM We can build the url like so: "https://www.python.org/ftp/python/[version]/python-[version]-amd64.exe" 258 | set "url=https://www.python.org/ftp/python/!release!/python-!release!-amd64.exe" 259 | set "pytype=exe" 260 | if "!targetpy!" == "2" ( 261 | set "url=https://www.python.org/ftp/python/!release!/python-!release!.amd64.msi" 262 | set "pytype=msi" 263 | ) 264 | REM Now we download it with our slick powershell command 265 | powershell -command "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; (new-object System.Net.WebClient).DownloadFile('!url!','%TEMP%\pyinstall.!pytype!')" 266 | REM If it doesn't exist - we bail 267 | if not exist "%TEMP%\pyinstall.!pytype!" ( 268 | if /i "!just_installing!" == "TRUE" ( 269 | echo Failed to download installer 270 | exit /b 1 271 | ) else ( 272 | goto checkpy 273 | ) 274 | ) 275 | REM It should exist at this point - let's run it to install silently 276 | echo Installing... 277 | pushd "%TEMP%" 278 | if /i "!pytype!" == "exe" ( 279 | echo pyinstall.exe /quiet PrependPath=1 Include_test=0 Shortcuts=0 Include_launcher=0 280 | pyinstall.exe /quiet PrependPath=1 Include_test=0 Shortcuts=0 Include_launcher=0 281 | ) else ( 282 | set "foldername=!release:.=!" 283 | echo msiexec /i pyinstall.msi /qb ADDLOCAL=ALL TARGETDIR="%LocalAppData%\Programs\Python\Python!foldername:~0,2!" 284 | msiexec /i pyinstall.msi /qb ADDLOCAL=ALL TARGETDIR="%LocalAppData%\Programs\Python\Python!foldername:~0,2!" 285 | ) 286 | popd 287 | echo Installer finished with %ERRORLEVEL% status. 288 | REM Now we should be able to delete the installer and check for py again 289 | del "%TEMP%\pyinstall.!pytype!" 290 | REM If it worked, then we should have python in our PATH 291 | REM this does not get updated right away though - let's try 292 | REM manually updating the local PATH var 293 | call :updatepath 294 | if /i "!just_installing!" == "TRUE" ( 295 | echo. 296 | echo Done. 297 | ) else ( 298 | goto checkpy 299 | ) 300 | exit /b 301 | 302 | :runscript 303 | REM Python found 304 | cls 305 | set "args=%*" 306 | set "args=!args:"=!" 307 | if "!args!"=="" ( 308 | "!pypath!" "!thisDir!!script_name!" 309 | ) else ( 310 | "!pypath!" "!thisDir!!script_name!" %* 311 | ) 312 | if /i "!pause_on_error!" == "yes" ( 313 | if not "%ERRORLEVEL%" == "0" ( 314 | echo. 315 | echo Script exited with error code: %ERRORLEVEL% 316 | echo. 317 | echo Press [enter] to exit... 318 | pause > nul 319 | ) 320 | ) 321 | goto :EOF 322 | 323 | :undouble 324 | REM Helper function to strip doubles of a single character out of a string recursively 325 | set "string_value=%~2" 326 | :undouble_continue 327 | set "check=!string_value:%~3%~3=%~3!" 328 | if not "!check!" == "!string_value!" ( 329 | set "string_value=!check!" 330 | goto :undouble_continue 331 | ) 332 | set "%~1=!check!" 333 | goto :EOF 334 | 335 | :updatepath 336 | set "spath=" 337 | set "upath=" 338 | for /f "USEBACKQ tokens=2* delims= " %%i in (`!syspath!reg.exe query "HKCU\Environment" /v "Path" 2^> nul`) do ( if not "%%j" == "" set "upath=%%j" ) 339 | for /f "USEBACKQ tokens=2* delims= " %%i in (`!syspath!reg.exe query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v "Path" 2^> nul`) do ( if not "%%j" == "" set "spath=%%j" ) 340 | if not "%spath%" == "" ( 341 | REM We got something in the system path 342 | set "PATH=%spath%" 343 | if not "%upath%" == "" ( 344 | REM We also have something in the user path 345 | set "PATH=%PATH%;%upath%" 346 | ) 347 | ) else if not "%upath%" == "" ( 348 | set "PATH=%upath%" 349 | ) 350 | REM Remove double semicolons from the adjusted PATH 351 | call :undouble "PATH" "%PATH%" ";" 352 | goto :EOF 353 | 354 | :getsyspath 355 | REM Helper method to return a valid path to cmd.exe, reg.exe, and where.exe by 356 | REM walking the ComSpec var - will also repair it in memory if need be 357 | REM Strip double semi-colons 358 | call :undouble "temppath" "%ComSpec%" ";" 359 | 360 | REM Dirty hack to leverage the "line feed" approach - there are some odd side 361 | REM effects with this. Do not use this variable name in comments near this 362 | REM line - as it seems to behave erradically. 363 | (set LF=^ 364 | %=this line is empty=% 365 | ) 366 | REM Replace instances of semi-colons with a line feed and wrap 367 | REM in parenthesis to work around some strange batch behavior 368 | set "testpath=%temppath:;=!LF!%" 369 | 370 | REM Let's walk each path and test if cmd.exe, reg.exe, and where.exe exist there 371 | set /a found=0 372 | for /f "tokens=* delims=" %%i in ("!testpath!") do ( 373 | REM Only continue if we haven't found it yet 374 | if not "%%i" == "" ( 375 | if !found! lss 1 ( 376 | set "checkpath=%%i" 377 | REM Remove "cmd.exe" from the end if it exists 378 | if /i "!checkpath:~-7!" == "cmd.exe" ( 379 | set "checkpath=!checkpath:~0,-7!" 380 | ) 381 | REM Pad the end with a backslash if needed 382 | if not "!checkpath:~-1!" == "\" ( 383 | set "checkpath=!checkpath!\" 384 | ) 385 | REM Let's see if cmd, reg, and where exist there - and set it if so 386 | if EXIST "!checkpath!cmd.exe" ( 387 | if EXIST "!checkpath!reg.exe" ( 388 | if EXIST "!checkpath!where.exe" ( 389 | set /a found=1 390 | set "ComSpec=!checkpath!cmd.exe" 391 | set "%~1=!checkpath!" 392 | ) 393 | ) 394 | ) 395 | ) 396 | ) 397 | ) 398 | goto :EOF -------------------------------------------------------------------------------- /Scripts/report_validator.py: -------------------------------------------------------------------------------- 1 | from Scripts import utils 2 | import json 3 | import os 4 | import re 5 | 6 | class ReportValidator: 7 | def __init__(self): 8 | self.errors = [] 9 | self.warnings = [] 10 | self.u = utils.Utils() 11 | 12 | self.PATTERNS = { 13 | "not_empty": r".+", 14 | "platform": r"^(Desktop|Laptop)$", 15 | "firmware_type": r"^(UEFI|BIOS)$", 16 | "bus_type": r"^(PCI|USB|ACPI|ROOT)$", 17 | "cpu_manufacturer": r"^(Intel|AMD)$", 18 | "gpu_manufacturer": r"^(Intel|AMD|NVIDIA)$", 19 | "gpu_device_type": r"^(Integrated GPU|Discrete GPU|Unknown)$", 20 | "hex_id": r"^(?:0x)?[0-9a-fA-F]+$", 21 | "device_id": r"^[0-9A-F]{4}(?:-[0-9A-F]{4})?$", 22 | "resolution": r"^\d+x\d+$", 23 | "pci_path": r"^PciRoot\(0x[0-9a-fA-F]+\)(?:/Pci\(0x[0-9a-fA-F]+,0x[0-9a-fA-F]+\))+$", 24 | "acpi_path": r"^[\\]?_SB(\.[A-Z0-9_]+)+$", 25 | "core_count": r"^\d+$", 26 | "connector_type": r"^(VGA|DVI|HDMI|LVDS|DP|eDP|Internal|Uninitialized)$", 27 | "enabled_disabled": r"^(Enabled|Disabled)$" 28 | } 29 | 30 | self.SCHEMA = { 31 | "type": dict, 32 | "schema": { 33 | "Motherboard": { 34 | "type": dict, 35 | "required": True, 36 | "schema": { 37 | "Name": {"type": str}, 38 | "Chipset": {"type": str}, 39 | "Platform": {"type": str, "pattern": self.PATTERNS["platform"]} 40 | } 41 | }, 42 | "BIOS": { 43 | "type": dict, 44 | "required": True, 45 | "schema": { 46 | "Version": {"type": str, "required": False}, 47 | "Release Date": {"type": str, "required": False}, 48 | "System Type": {"type": str, "required": False}, 49 | "Firmware Type": {"type": str, "pattern": self.PATTERNS["firmware_type"]}, 50 | "Secure Boot": {"type": str, "pattern": self.PATTERNS["enabled_disabled"]} 51 | } 52 | }, 53 | "CPU": { 54 | "type": dict, 55 | "required": True, 56 | "schema": { 57 | "Manufacturer": {"type": str, "pattern": self.PATTERNS["cpu_manufacturer"]}, 58 | "Processor Name": {"type": str}, 59 | "Codename": {"type": str}, 60 | "Core Count": {"type": str, "pattern": self.PATTERNS["core_count"]}, 61 | "CPU Count": {"type": str, "pattern": self.PATTERNS["core_count"]}, 62 | "SIMD Features": {"type": str} 63 | } 64 | }, 65 | "GPU": { 66 | "type": dict, 67 | "required": True, 68 | "values_rule": { 69 | "type": dict, 70 | "schema": { 71 | "Manufacturer": {"type": str, "pattern": self.PATTERNS["gpu_manufacturer"]}, 72 | "Codename": {"type": str}, 73 | "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]}, 74 | "Device Type": {"type": str, "pattern": self.PATTERNS["gpu_device_type"]}, 75 | "Subsystem ID": {"type": str, "required": False, "pattern": self.PATTERNS["hex_id"]}, 76 | "PCI Path": {"type": str, "required": False, "pattern": self.PATTERNS["pci_path"]}, 77 | "ACPI Path": {"type": str, "required": False, "pattern": self.PATTERNS["acpi_path"]}, 78 | "Resizable BAR": {"type": str, "required": False, "pattern": self.PATTERNS["enabled_disabled"]} 79 | } 80 | } 81 | }, 82 | "Monitor": { 83 | "type": dict, 84 | "required": False, 85 | "values_rule": { 86 | "type": dict, 87 | "schema": { 88 | "Connector Type": {"type": str, "pattern": self.PATTERNS["connector_type"]}, 89 | "Resolution": {"type": str, "pattern": self.PATTERNS["resolution"]}, 90 | "Connected GPU": {"type": str, "required": False} 91 | } 92 | } 93 | }, 94 | "Network": { 95 | "type": dict, 96 | "required": True, 97 | "values_rule": { 98 | "type": dict, 99 | "schema": { 100 | "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, 101 | "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]}, 102 | "Subsystem ID": {"type": str, "required": False, "pattern": self.PATTERNS["hex_id"]}, 103 | "PCI Path": {"type": str, "required": False, "pattern": self.PATTERNS["pci_path"]}, 104 | "ACPI Path": {"type": str, "required": False, "pattern": self.PATTERNS["acpi_path"]} 105 | } 106 | } 107 | }, 108 | "Sound": { 109 | "type": dict, 110 | "required": False, 111 | "values_rule": { 112 | "type": dict, 113 | "schema": { 114 | "Bus Type": {"type": str}, 115 | "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]}, 116 | "Subsystem ID": {"type": str, "required": False, "pattern": self.PATTERNS["hex_id"]}, 117 | "Audio Endpoints": {"type": list, "required": False, "item_rule": {"type": str}}, 118 | "Controller Device ID": {"type": str, "required": False, "pattern": self.PATTERNS["device_id"]} 119 | } 120 | } 121 | }, 122 | "USB Controllers": { 123 | "type": dict, 124 | "required": True, 125 | "values_rule": { 126 | "type": dict, 127 | "schema": { 128 | "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, 129 | "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]}, 130 | "Subsystem ID": {"type": str, "required": False, "pattern": self.PATTERNS["hex_id"]}, 131 | "PCI Path": {"type": str, "required": False, "pattern": self.PATTERNS["pci_path"]}, 132 | "ACPI Path": {"type": str, "required": False, "pattern": self.PATTERNS["acpi_path"]} 133 | } 134 | } 135 | }, 136 | "Input": { 137 | "type": dict, 138 | "required": True, 139 | "values_rule": { 140 | "type": dict, 141 | "schema": { 142 | "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, 143 | "Device": {"type": str, "required": False}, 144 | "Device ID": {"type": str, "required": False, "pattern": self.PATTERNS["device_id"]}, 145 | "Device Type": {"type": str, "required": False} 146 | } 147 | } 148 | }, 149 | "Storage Controllers": { 150 | "type": dict, 151 | "required": True, 152 | "values_rule": { 153 | "type": dict, 154 | "schema": { 155 | "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, 156 | "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]}, 157 | "Subsystem ID": {"type": str, "required": False, "pattern": self.PATTERNS["hex_id"]}, 158 | "PCI Path": {"type": str, "required": False, "pattern": self.PATTERNS["pci_path"]}, 159 | "ACPI Path": {"type": str, "required": False, "pattern": self.PATTERNS["acpi_path"]}, 160 | "Disk Drives": {"type": list, "required": False, "item_rule": {"type": str}} 161 | } 162 | } 163 | }, 164 | "Biometric": { 165 | "type": dict, 166 | "required": False, 167 | "values_rule": { 168 | "type": dict, 169 | "schema": { 170 | "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, 171 | "Device": {"type": str, "required": False}, 172 | "Device ID": {"type": str, "required": False, "pattern": self.PATTERNS["device_id"]} 173 | } 174 | } 175 | }, 176 | "Bluetooth": { 177 | "type": dict, 178 | "required": False, 179 | "values_rule": { 180 | "type": dict, 181 | "schema": { 182 | "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, 183 | "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]} 184 | } 185 | } 186 | }, 187 | "SD Controller": { 188 | "type": dict, 189 | "required": False, 190 | "values_rule": { 191 | "type": dict, 192 | "schema": { 193 | "Bus Type": {"type": str, "pattern": self.PATTERNS["bus_type"]}, 194 | "Device ID": {"type": str, "pattern": self.PATTERNS["device_id"]}, 195 | "Subsystem ID": {"type": str, "required": False, "pattern": self.PATTERNS["hex_id"]}, 196 | "PCI Path": {"type": str, "required": False, "pattern": self.PATTERNS["pci_path"]}, 197 | "ACPI Path": {"type": str, "required": False, "pattern": self.PATTERNS["acpi_path"]} 198 | } 199 | } 200 | }, 201 | "System Devices": { 202 | "type": dict, 203 | "required": False, 204 | "values_rule": { 205 | "type": dict, 206 | "schema": { 207 | "Bus Type": {"type": str}, 208 | "Device": {"type": str, "required": False}, 209 | "Device ID": {"type": str, "required": False, "pattern": self.PATTERNS["device_id"]}, 210 | "Subsystem ID": {"type": str, "required": False, "pattern": self.PATTERNS["hex_id"]}, 211 | "PCI Path": {"type": str, "required": False, "pattern": self.PATTERNS["pci_path"]}, 212 | "ACPI Path": {"type": str, "required": False, "pattern": self.PATTERNS["acpi_path"]} 213 | } 214 | } 215 | } 216 | } 217 | } 218 | 219 | def validate_report(self, report_path): 220 | self.errors = [] 221 | self.warnings = [] 222 | data = None 223 | 224 | if not os.path.exists(report_path): 225 | self.errors.append("File does not exist: {}".format(report_path)) 226 | return False, self.errors, self.warnings, None 227 | 228 | try: 229 | data = self.u.read_file(report_path) 230 | except json.JSONDecodeError as e: 231 | self.errors.append("Invalid JSON format: {}".format(str(e))) 232 | return False, self.errors, self.warnings, None 233 | except Exception as e: 234 | self.errors.append("Error reading file: {}".format(str(e))) 235 | return False, self.errors, self.warnings, None 236 | 237 | cleaned_data = self._validate_node(data, self.SCHEMA, "Root") 238 | 239 | is_valid = len(self.errors) == 0 240 | return is_valid, self.errors, self.warnings, cleaned_data 241 | 242 | def _validate_node(self, data, rule, path): 243 | expected_type = rule.get("type") 244 | if expected_type: 245 | if not isinstance(data, expected_type): 246 | type_name = expected_type.__name__ if hasattr(expected_type, "__name__") else str(expected_type) 247 | self.errors.append(f"{path}: Expected type {type_name}, got {type(data).__name__}") 248 | return None 249 | 250 | if isinstance(data, str): 251 | pattern = rule.get("pattern") 252 | if pattern is not None: 253 | if not re.match(pattern, data): 254 | self.errors.append(f"{path}: Value '{data}' does not match pattern '{pattern}'") 255 | return None 256 | elif not re.match(self.PATTERNS["not_empty"], data): 257 | self.errors.append(f"{path}: Value '{data}' does not match pattern '{self.PATTERNS['not_empty']}'") 258 | return None 259 | 260 | cleaned_data = data 261 | 262 | if isinstance(data, dict): 263 | cleaned_data = {} 264 | schema_keys = rule.get("schema", {}) 265 | 266 | for key, value in data.items(): 267 | if key in schema_keys: 268 | cleaned_val = self._validate_node(value, schema_keys[key], f"{path}.{key}") 269 | if cleaned_val is not None: 270 | cleaned_data[key] = cleaned_val 271 | elif "values_rule" in rule: 272 | cleaned_val = self._validate_node(value, rule["values_rule"], f"{path}.{key}") 273 | if cleaned_val is not None: 274 | cleaned_data[key] = cleaned_val 275 | else: 276 | if schema_keys: 277 | self.warnings.append(f"{path}: Unknown key '{key}'") 278 | 279 | for key, key_rule in schema_keys.items(): 280 | if key_rule.get("required", True) and key not in cleaned_data: 281 | self.errors.append(f"{path}: Missing required key '{key}'") 282 | 283 | elif isinstance(data, list): 284 | item_rule = rule.get("item_rule") 285 | if item_rule: 286 | cleaned_data = [] 287 | for i, item in enumerate(data): 288 | cleaned_val = self._validate_node(item, item_rule, f"{path}[{i}]") 289 | if cleaned_val is not None: 290 | cleaned_data.append(cleaned_val) 291 | else: 292 | cleaned_data = list(data) 293 | 294 | return cleaned_data 295 | 296 | def show_validation_report(self, report_path, is_valid, errors, warnings): 297 | self.u.head("Validation Report") 298 | print("") 299 | print("Validation report for: {}".format(report_path)) 300 | print("") 301 | 302 | if is_valid: 303 | print("Hardware report is valid!") 304 | else: 305 | print("Hardware report is not valid! Please check the errors and warnings below.") 306 | 307 | if errors: 308 | print("") 309 | print("\033[31mErrors ({}):\033[0m".format(len(errors))) 310 | for i, error in enumerate(errors, 1): 311 | print(" {}. {}".format(i, error)) 312 | 313 | if warnings: 314 | print("") 315 | print("\033[33mWarnings ({}):\033[0m".format(len(warnings))) 316 | for i, warning in enumerate(warnings, 1): 317 | print(" {}. {}".format(i, warning)) -------------------------------------------------------------------------------- /Scripts/hardware_customizer.py: -------------------------------------------------------------------------------- 1 | from Scripts.datasets import os_data 2 | from Scripts.datasets import pci_data 3 | from Scripts import compatibility_checker 4 | from Scripts import utils 5 | 6 | class HardwareCustomizer: 7 | def __init__(self): 8 | self.compatibility_checker = compatibility_checker.CompatibilityChecker() 9 | self.utils = utils.Utils() 10 | 11 | def hardware_customization(self, hardware_report, macos_version): 12 | self.hardware_report = hardware_report 13 | self.macos_version = macos_version 14 | self.customized_hardware = {} 15 | self.disabled_devices = {} 16 | self.selected_devices = {} 17 | needs_oclp = False 18 | 19 | self.utils.head("Hardware Customization") 20 | 21 | for device_type, devices in self.hardware_report.items(): 22 | if not device_type in ("BIOS", "GPU", "Sound", "Biometric", "Network", "Storage Controllers", "Bluetooth", "SD Controller"): 23 | self.customized_hardware[device_type] = devices 24 | continue 25 | 26 | self.customized_hardware[device_type] = {} 27 | 28 | if device_type == "BIOS": 29 | self.customized_hardware[device_type] = devices.copy() 30 | if devices.get("Firmware Type") != "UEFI": 31 | print("\n*** BIOS Firmware Type is not UEFI") 32 | print("") 33 | print("Do you want to build the EFI for UEFI?") 34 | print("If yes, please make sure to update your BIOS and enable UEFI Boot Mode in your BIOS settings.") 35 | print("You can still proceed with Legacy if you prefer.") 36 | print("") 37 | 38 | while True: 39 | answer = self.utils.request_input("Build EFI for UEFI? (Yes/no): ").strip().lower() 40 | if answer == "yes": 41 | self.customized_hardware[device_type]["Firmware Type"] = "UEFI" 42 | break 43 | elif answer == "no": 44 | self.customized_hardware[device_type]["Firmware Type"] = "Legacy" 45 | break 46 | else: 47 | print("\033[91mInvalid selection, please try again.\033[0m\n\n") 48 | continue 49 | 50 | for device_name in devices: 51 | device_props = devices[device_name].copy() 52 | if device_props.get("OCLP Compatibility") and self.utils.parse_darwin_version(device_props.get("OCLP Compatibility")[0]) >= self.utils.parse_darwin_version(macos_version) >= self.utils.parse_darwin_version(device_props.get("OCLP Compatibility")[-1]): 53 | self.customized_hardware[device_type][device_name] = device_props 54 | needs_oclp = True 55 | continue 56 | 57 | device_compatibility = device_props.get("Compatibility", (os_data.get_latest_darwin_version(), os_data.get_lowest_darwin_version())) 58 | 59 | try: 60 | if self.utils.parse_darwin_version(device_compatibility[0]) >= self.utils.parse_darwin_version(macos_version) >= self.utils.parse_darwin_version(device_compatibility[-1]): 61 | self.customized_hardware[device_type][device_name] = device_props 62 | except: 63 | self.disabled_devices["{}: {}{}".format(device_props["Device Type"] if not "Unknown" in device_props.get("Device Type", "Unknown") else device_type, device_name, "" if not device_props.get("Audio Endpoints") else " ({})".format(", ".join(device_props.get("Audio Endpoints"))))] = device_props 64 | 65 | if self.customized_hardware[device_type].get(device_name) and self.customized_hardware[device_type][device_name].get("OCLP Compatibility"): 66 | del self.customized_hardware[device_type][device_name]["OCLP Compatibility"] 67 | 68 | if not self.customized_hardware[device_type]: 69 | del self.customized_hardware[device_type] 70 | else: 71 | if device_type in ("GPU", "Network", "Bluetooth"): 72 | self._handle_device_selection(device_type if device_type != "Network" else "WiFi") 73 | 74 | if self.selected_devices: 75 | self.utils.head("Device Selection Summary") 76 | print("") 77 | print("Selected devices:") 78 | print("") 79 | print("Type Device Device ID") 80 | print("------------------------------------------------------------------") 81 | for device_type, device_dict in self.selected_devices.items(): 82 | for device_name, device_props in device_dict.items(): 83 | device_id = device_props.get("Device ID", "Unknown") 84 | print("{:<13} {:<42} {}".format(device_type, device_name[:38], device_id)) 85 | print("") 86 | print("All other devices of the same type have been disabled.") 87 | print("") 88 | self.utils.request_input() 89 | 90 | return self.customized_hardware, self.disabled_devices, needs_oclp 91 | 92 | def _get_device_combinations(self, device_indices): 93 | devices = sorted(list(device_indices)) 94 | n = len(devices) 95 | all_combinations = [] 96 | 97 | if n == 0: 98 | return [] 99 | 100 | for i in range(1, 1 << n): 101 | current_combination = [] 102 | for j in range(n): 103 | if (i >> j) & 1: 104 | current_combination.append(devices[j]) 105 | 106 | if 1 <= len(current_combination) <= n: 107 | all_combinations.append(current_combination) 108 | 109 | all_combinations.sort(key=lambda combo: (len(combo), combo)) 110 | 111 | return all_combinations 112 | 113 | def _handle_device_selection(self, device_type): 114 | devices = self._get_compatible_devices(device_type) 115 | device_groups = None 116 | 117 | if len(devices) > 1: 118 | print("\n*** Multiple {} Devices Detected".format(device_type)) 119 | if device_type == "WiFi" or device_type == "Bluetooth": 120 | print(f"macOS works best with only one {device_type} device enabled.") 121 | elif device_type == "GPU": 122 | _apu_index = None 123 | _navi_22_indices = set() 124 | _navi_indices = set() 125 | _intel_gpu_indices = set() 126 | _other_indices = set() 127 | 128 | for index, (gpu_name, gpu_props) in enumerate(devices.items()): 129 | gpu_manufacturer = gpu_props.get("Manufacturer") 130 | gpu_codename = gpu_props.get("Codename") 131 | gpu_type = gpu_props.get("Device Type") 132 | 133 | if gpu_manufacturer == "AMD": 134 | if gpu_type == "Integrated GPU": 135 | _apu_index = index 136 | continue 137 | elif gpu_type == "Discrete GPU": 138 | if gpu_codename.startswith("Navi"): 139 | if gpu_codename == "Navi 22": 140 | _navi_22_indices.add(index) 141 | else: 142 | _navi_indices.add(index) 143 | continue 144 | elif gpu_manufacturer == "Intel": 145 | _intel_gpu_indices.add(index) 146 | continue 147 | 148 | _other_indices.add(index) 149 | 150 | if _apu_index or _navi_22_indices: 151 | print("Multiple active GPUs can cause kext conflicts in macOS.") 152 | 153 | device_groups = [] 154 | if _apu_index: 155 | device_groups.append({_apu_index} | _other_indices) 156 | if _navi_22_indices: 157 | device_groups.append(_navi_22_indices | _other_indices) 158 | if _navi_indices or _intel_gpu_indices or _other_indices: 159 | device_groups.append(_navi_indices | _intel_gpu_indices | _other_indices) 160 | 161 | selected_devices = self._select_device(device_type, devices, device_groups) 162 | if selected_devices: 163 | for selected_device in selected_devices: 164 | if not device_type in self.selected_devices: 165 | self.selected_devices[device_type] = {} 166 | 167 | self.selected_devices[device_type][selected_device] = devices[selected_device] 168 | 169 | def _get_compatible_devices(self, device_type): 170 | compatible_devices = {} 171 | 172 | if device_type == "WiFi": 173 | hardware_category = "Network" 174 | else: 175 | hardware_category = device_type 176 | 177 | for device_name, device_props in self.customized_hardware.get(hardware_category, {}).items(): 178 | if device_type == "WiFi": 179 | device_id = device_props.get("Device ID") 180 | 181 | if device_id not in pci_data.WirelessCardIDs: 182 | continue 183 | 184 | compatible_devices[device_name] = device_props 185 | 186 | return compatible_devices 187 | 188 | def _select_device(self, device_type, devices, device_groups=None): 189 | print("") 190 | if device_groups: 191 | print("Please select a {} combination configuration:".format(device_type)) 192 | else: 193 | print("Please select which {} device you want to use:".format(device_type)) 194 | print("") 195 | 196 | if device_groups: 197 | valid_combinations = [] 198 | 199 | for group in device_groups: 200 | device_combinations = self._get_device_combinations(group) 201 | for device_combination in device_combinations: 202 | group_devices = [] 203 | group_compatibility = None 204 | group_indices = set() 205 | has_oclp_required = False 206 | 207 | for index in device_combination: 208 | device_name = list(devices.keys())[index] 209 | device_props = devices[device_name] 210 | group_devices.append(device_name) 211 | group_indices.add(index) 212 | compatibility = device_props.get("Compatibility") 213 | if compatibility: 214 | if group_compatibility is None: 215 | group_compatibility = compatibility 216 | else: 217 | if self.utils.parse_darwin_version(compatibility[0]) < self.utils.parse_darwin_version(group_compatibility[0]): 218 | group_compatibility = (compatibility[0], group_compatibility[1]) 219 | if self.utils.parse_darwin_version(compatibility[1]) > self.utils.parse_darwin_version(group_compatibility[1]): 220 | group_compatibility = (group_compatibility[0], compatibility[1]) 221 | 222 | if device_props.get("OCLP Compatibility"): 223 | has_oclp_required = True 224 | 225 | if has_oclp_required and len(device_combination) > 1: 226 | continue 227 | 228 | if group_devices and (group_devices, group_indices, group_compatibility) not in valid_combinations: 229 | valid_combinations.append((group_devices, group_indices, group_compatibility)) 230 | 231 | valid_combinations.sort(key=lambda x: (len(x[0]), x[2][0])) 232 | 233 | for idx, (group_devices, _, group_compatibility) in enumerate(valid_combinations, start=1): 234 | print("{}. {}".format(idx, " + ".join(group_devices))) 235 | if group_compatibility: 236 | print(" Compatibility: {}".format(self.compatibility_checker.show_macos_compatibility(group_compatibility))) 237 | if len(group_devices) == 1: 238 | device_props = devices[group_devices[0]] 239 | if device_props.get("OCLP Compatibility"): 240 | oclp_compatibility = device_props.get("OCLP Compatibility") 241 | if self.utils.parse_darwin_version(oclp_compatibility[0]) > self.utils.parse_darwin_version(group_compatibility[0]): 242 | print(" OCLP Compatibility: {}".format(self.compatibility_checker.show_macos_compatibility((oclp_compatibility[0], os_data.get_lowest_darwin_version())))) 243 | print("") 244 | 245 | while True: 246 | choice = self.utils.request_input(f"Select a {device_type} combination (1-{len(valid_combinations)}): ") 247 | 248 | try: 249 | choice_num = int(choice) 250 | if 1 <= choice_num <= len(valid_combinations): 251 | selected_devices, _, _ = valid_combinations[choice_num - 1] 252 | 253 | for device in devices: 254 | if device not in selected_devices: 255 | self._disable_device(device_type, device, devices[device]) 256 | 257 | return selected_devices 258 | else: 259 | print("Invalid option. Please try again.") 260 | except ValueError: 261 | print("Please enter a valid number.") 262 | else: 263 | for index, device_name in enumerate(devices, start=1): 264 | device_props = devices[device_name] 265 | compatibility = device_props.get("Compatibility") 266 | 267 | print("{}. {}".format(index, device_name)) 268 | print(" Device ID: {}".format(device_props.get("Device ID", "Unknown"))) 269 | print(" Compatibility: {}".format(self.compatibility_checker.show_macos_compatibility(compatibility))) 270 | 271 | if device_props.get("OCLP Compatibility"): 272 | oclp_compatibility = device_props.get("OCLP Compatibility") 273 | if self.utils.parse_darwin_version(oclp_compatibility[0]) > self.utils.parse_darwin_version(compatibility[0]): 274 | print(" OCLP Compatibility: {}".format(self.compatibility_checker.show_macos_compatibility((oclp_compatibility[0], os_data.get_lowest_darwin_version())))) 275 | print() 276 | 277 | while True: 278 | choice = self.utils.request_input(f"Select a {device_type} device (1-{len(devices)}): ") 279 | 280 | try: 281 | choice_num = int(choice) 282 | if 1 <= choice_num <= len(devices): 283 | selected_device = list(devices)[choice_num - 1] 284 | 285 | for device in devices: 286 | if device != selected_device: 287 | self._disable_device(device_type, device, devices[device]) 288 | 289 | return [selected_device] 290 | else: 291 | print("Invalid option. Please try again.") 292 | except ValueError: 293 | print("Please enter a valid number.") 294 | 295 | def _disable_device(self, device_type, device_name, device_props): 296 | if device_type == "WiFi": 297 | device_id = device_props.get("Device ID") 298 | if not device_id or device_id not in pci_data.WirelessCardIDs: 299 | return 300 | 301 | hardware_category = "Network" 302 | else: 303 | hardware_category = device_type 304 | 305 | if (hardware_category in self.customized_hardware and device_name in self.customized_hardware[hardware_category]): 306 | del self.customized_hardware[hardware_category][device_name] 307 | 308 | if not self.customized_hardware[hardware_category]: 309 | del self.customized_hardware[hardware_category] 310 | 311 | self.disabled_devices["{}: {}".format(hardware_category, device_name)] = device_props -------------------------------------------------------------------------------- /Scripts/gathering_files.py: -------------------------------------------------------------------------------- 1 | from Scripts import github 2 | from Scripts import kext_maestro 3 | from Scripts import integrity_checker 4 | from Scripts import resource_fetcher 5 | from Scripts import utils 6 | import os 7 | import shutil 8 | import subprocess 9 | import platform 10 | 11 | os_name = platform.system() 12 | 13 | class gatheringFiles: 14 | def __init__(self): 15 | self.utils = utils.Utils() 16 | self.github = github.Github() 17 | self.kext = kext_maestro.KextMaestro() 18 | self.fetcher = resource_fetcher.ResourceFetcher() 19 | self.integrity_checker = integrity_checker.IntegrityChecker() 20 | self.dortania_builds_url = "https://raw.githubusercontent.com/dortania/build-repo/builds/latest.json" 21 | self.ocbinarydata_url = "https://github.com/acidanthera/OcBinaryData/archive/refs/heads/master.zip" 22 | self.amd_vanilla_patches_url = "https://raw.githubusercontent.com/AMD-OSX/AMD_Vanilla/beta/patches.plist" 23 | self.aquantia_macos_patches_url = "https://raw.githubusercontent.com/CaseySJ/Aquantia-macOS-Patches/refs/heads/main/CaseySJ-Aquantia-Patch-Sets-1-and-2.plist" 24 | self.hyper_threading_patches_url = "https://github.com/b00t0x/CpuTopologyRebuild/raw/refs/heads/master/patches_ht.plist" 25 | self.temporary_dir = self.utils.get_temporary_dir() 26 | self.ock_files_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "OCK_Files") 27 | self.download_history_file = os.path.join(self.ock_files_dir, "history.json") 28 | 29 | def get_product_index(self, product_list, product_name_name): 30 | for index, product in enumerate(product_list): 31 | if product_name_name == product.get("product_name"): 32 | return index 33 | return None 34 | 35 | def update_download_database(self, kexts, download_history): 36 | download_database = download_history.copy() 37 | dortania_builds_data = self.fetcher.fetch_and_parse_content(self.dortania_builds_url, "json") 38 | seen_repos = set() 39 | 40 | def add_product_to_download_database(products): 41 | if isinstance(products, dict): 42 | products = [products] 43 | 44 | for product in products: 45 | if not product or not product.get("product_name"): 46 | continue 47 | 48 | product_index = self.get_product_index(download_database, product.get("product_name")) 49 | 50 | if product_index is None: 51 | download_database.append(product) 52 | else: 53 | download_database[product_index].update(product) 54 | 55 | for kext in kexts: 56 | if not kext.checked: 57 | continue 58 | 59 | if kext.download_info: 60 | if not kext.download_info.get("sha256"): 61 | kext.download_info["sha256"] = None 62 | add_product_to_download_database({"product_name": kext.name, **kext.download_info}) 63 | elif kext.github_repo and kext.github_repo.get("repo") not in seen_repos: 64 | name = kext.github_repo.get("repo") 65 | seen_repos.add(name) 66 | if name != "IntelBluetoothFirmware" and name in dortania_builds_data: 67 | add_product_to_download_database({ 68 | "product_name": name, 69 | "id": dortania_builds_data[name]["versions"][0]["release"]["id"], 70 | "url": dortania_builds_data[name]["versions"][0]["links"]["release"], 71 | "sha256": dortania_builds_data[name]["versions"][0]["hashes"]["release"]["sha256"] 72 | }) 73 | else: 74 | latest_release = self.github.get_latest_release(kext.github_repo.get("owner"), kext.github_repo.get("repo")) or {} 75 | add_product_to_download_database(latest_release.get("assets")) 76 | 77 | add_product_to_download_database({ 78 | "product_name": "OpenCorePkg", 79 | "id": dortania_builds_data["OpenCorePkg"]["versions"][0]["release"]["id"], 80 | "url": dortania_builds_data["OpenCorePkg"]["versions"][0]["links"]["release"], 81 | "sha256": dortania_builds_data["OpenCorePkg"]["versions"][0]["hashes"]["release"]["sha256"] 82 | }) 83 | 84 | return sorted(download_database, key=lambda x:x["product_name"]) 85 | 86 | def move_bootloader_kexts_to_product_directory(self, product_name): 87 | if not os.path.exists(self.temporary_dir): 88 | raise FileNotFoundError("The directory {} does not exist.".format(self.temporary_dir)) 89 | 90 | temp_product_dir = os.path.join(self.temporary_dir, product_name) 91 | 92 | if not "OpenCore" in product_name: 93 | kext_paths = self.utils.find_matching_paths(temp_product_dir, extension_filter=".kext") 94 | for kext_path, type in kext_paths: 95 | source_kext_path = os.path.join(self.temporary_dir, product_name, kext_path) 96 | destination_kext_path = os.path.join(self.ock_files_dir, product_name, os.path.basename(kext_path)) 97 | 98 | if "debug" in kext_path.lower() or "Contents" in kext_path or not self.kext.process_kext(temp_product_dir, kext_path): 99 | continue 100 | 101 | shutil.move(source_kext_path, destination_kext_path) 102 | else: 103 | source_bootloader_path = os.path.join(self.temporary_dir, product_name, "X64", "EFI") 104 | if os.path.exists(source_bootloader_path): 105 | destination_efi_path = os.path.join(self.ock_files_dir, product_name, os.path.basename(source_bootloader_path)) 106 | shutil.move(source_bootloader_path, destination_efi_path) 107 | source_config_path = os.path.join(os.path.dirname(os.path.dirname(source_bootloader_path)), "Docs", "Sample.plist") 108 | destination_config_path = os.path.join(destination_efi_path, "OC", "config.plist") 109 | shutil.move(source_config_path, destination_config_path) 110 | 111 | ocbinarydata_dir = os.path.join(self.temporary_dir, "OcBinaryData", "OcBinaryData-master") 112 | if os.path.exists(ocbinarydata_dir): 113 | background_picker_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "datasets", "background_picker.icns") 114 | product_dir = os.path.join(self.ock_files_dir, product_name) 115 | efi_dirs = self.utils.find_matching_paths(product_dir, name_filter="EFI", type_filter="dir") 116 | 117 | for efi_dir, _ in efi_dirs: 118 | for dir_name in os.listdir(ocbinarydata_dir): 119 | source_dir = os.path.join(ocbinarydata_dir, dir_name) 120 | destination_dir = os.path.join(destination_efi_path, "OC", dir_name) 121 | if os.path.isdir(destination_dir): 122 | shutil.copytree(source_dir, destination_dir, dirs_exist_ok=True) 123 | 124 | resources_image_dir = os.path.join(product_dir, efi_dir, "OC", "Resources", "Image") 125 | picker_variants = self.utils.find_matching_paths(resources_image_dir, type_filter="dir") 126 | for picker_variant, _ in picker_variants: 127 | if ".icns" in ", ".join(os.listdir(os.path.join(resources_image_dir, picker_variant))): 128 | shutil.copy(background_picker_path, os.path.join(resources_image_dir, picker_variant, "Background.icns")) 129 | 130 | macserial_paths = self.utils.find_matching_paths(temp_product_dir, name_filter="macserial", type_filter="file") 131 | if macserial_paths: 132 | for macserial_path, _ in macserial_paths: 133 | source_macserial_path = os.path.join(self.temporary_dir, product_name, macserial_path) 134 | destination_macserial_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.path.basename(macserial_path)) 135 | shutil.move(source_macserial_path, destination_macserial_path) 136 | if os.name != "nt": 137 | subprocess.run(["chmod", "+x", destination_macserial_path]) 138 | 139 | return True 140 | 141 | def gather_bootloader_kexts(self, kexts, macos_version): 142 | self.utils.head("Gathering Files") 143 | print("") 144 | print("Please wait for download OpenCorePkg, kexts and macserial...") 145 | 146 | download_history = self.utils.read_file(self.download_history_file) 147 | if not isinstance(download_history, list): 148 | download_history = [] 149 | 150 | download_database = self.update_download_database(kexts, download_history) 151 | 152 | self.utils.create_folder(self.temporary_dir) 153 | 154 | seen_download_urls = set() 155 | 156 | for product in kexts + [{"Name": "OpenCorePkg"}]: 157 | if not isinstance(product, dict) and not product.checked: 158 | continue 159 | 160 | product_name = product.name if not isinstance(product, dict) else product.get("Name") 161 | 162 | if product_name == "AirportItlwm": 163 | version = macos_version[:2] 164 | if all((kexts[kext_maestro.kext_data.kext_index_by_name.get("IOSkywalkFamily")].checked, kexts[kext_maestro.kext_data.kext_index_by_name.get("IO80211FamilyLegacy")].checked)) or self.utils.parse_darwin_version("24.0.0") <= self.utils.parse_darwin_version(macos_version): 165 | version = "22" 166 | elif self.utils.parse_darwin_version("23.4.0") <= self.utils.parse_darwin_version(macos_version): 167 | version = "23.4" 168 | elif self.utils.parse_darwin_version("23.0.0") <= self.utils.parse_darwin_version(macos_version): 169 | version = "23.0" 170 | product_name += version 171 | elif "VoodooPS2" in product_name: 172 | product_name = "VoodooPS2" 173 | elif product_name == "BlueToolFixup" or product_name.startswith("Brcm"): 174 | product_name = "BrcmPatchRAM" 175 | elif product_name.startswith("Ath3kBT"): 176 | product_name = "Ath3kBT" 177 | elif product_name.startswith("IntelB"): 178 | product_name = "IntelBluetoothFirmware" 179 | elif product_name.startswith("VoodooI2C"): 180 | product_name = "VoodooI2C" 181 | elif product_name == "UTBDefault": 182 | product_name = "USBToolBox" 183 | 184 | product_download_index = self.get_product_index(download_database, product_name) 185 | if product_download_index is None: 186 | if hasattr(product, 'github_repo') and product.github_repo: 187 | product_download_index = self.get_product_index(download_database, product.github_repo.get("repo")) 188 | 189 | if product_download_index is None: 190 | print("\n") 191 | print("Could not find download URL for {}.".format(product_name)) 192 | continue 193 | 194 | product_info = download_database[product_download_index] 195 | product_id = product_info.get("id") 196 | product_download_url = product_info.get("url") 197 | sha256_hash = product_info.get("sha256") 198 | 199 | if product_download_url in seen_download_urls: 200 | continue 201 | seen_download_urls.add(product_download_url) 202 | 203 | product_history_index = self.get_product_index(download_history, product_name) 204 | asset_dir = os.path.join(self.ock_files_dir, product_name) 205 | manifest_path = os.path.join(asset_dir, "manifest.json") 206 | 207 | if product_history_index is not None: 208 | history_item = download_history[product_history_index] 209 | is_latest_id = (product_id == history_item.get("id")) 210 | folder_is_valid, _ = self.integrity_checker.verify_folder_integrity(asset_dir, manifest_path) 211 | 212 | if is_latest_id and folder_is_valid: 213 | print(f"\nLatest version of {product_name} already downloaded.") 214 | continue 215 | 216 | print("") 217 | print("Updating" if product_history_index is not None else "Please wait for download", end=" ") 218 | print("{}...".format(product_name)) 219 | if product_download_url: 220 | print("from {}".format(product_download_url)) 221 | print("") 222 | else: 223 | print("") 224 | print("Could not find download URL for {}.".format(product_name)) 225 | print("") 226 | self.utils.request_input() 227 | shutil.rmtree(self.temporary_dir, ignore_errors=True) 228 | return False 229 | 230 | zip_path = os.path.join(self.temporary_dir, product_name) + ".zip" 231 | if not self.fetcher.download_and_save_file(product_download_url, zip_path, sha256_hash): 232 | folder_is_valid, _ = self.integrity_checker.verify_folder_integrity(asset_dir, manifest_path) 233 | if product_history_index is not None and folder_is_valid: 234 | print("Using previously downloaded version of {}.".format(product_name)) 235 | continue 236 | else: 237 | raise Exception("Could not download {} at this time. Please try again later.".format(product_name)) 238 | 239 | self.utils.extract_zip_file(zip_path) 240 | self.utils.create_folder(asset_dir, remove_content=True) 241 | 242 | while True: 243 | nested_zip_files = self.utils.find_matching_paths(os.path.join(self.temporary_dir, product_name), extension_filter=".zip") 244 | if not nested_zip_files: 245 | break 246 | for zip_file, _ in nested_zip_files: 247 | full_zip_path = os.path.join(self.temporary_dir, product_name, zip_file) 248 | self.utils.extract_zip_file(full_zip_path) 249 | os.remove(full_zip_path) 250 | 251 | if "OpenCore" in product_name: 252 | oc_binary_data_zip_path = os.path.join(self.temporary_dir, "OcBinaryData.zip") 253 | print("") 254 | print("Please wait for download OcBinaryData...") 255 | print("from {}".format(self.ocbinarydata_url)) 256 | print("") 257 | self.fetcher.download_and_save_file(self.ocbinarydata_url, oc_binary_data_zip_path) 258 | 259 | if not os.path.exists(oc_binary_data_zip_path): 260 | print("") 261 | print("Could not download OcBinaryData at this time.") 262 | print("Please try again later.\n") 263 | self.utils.request_input() 264 | shutil.rmtree(self.temporary_dir, ignore_errors=True) 265 | return False 266 | 267 | self.utils.extract_zip_file(oc_binary_data_zip_path) 268 | 269 | if self.move_bootloader_kexts_to_product_directory(product_name): 270 | self.integrity_checker.generate_folder_manifest(asset_dir, manifest_path) 271 | self._update_download_history(download_history, product_name, product_id, product_download_url, sha256_hash) 272 | 273 | shutil.rmtree(self.temporary_dir, ignore_errors=True) 274 | return True 275 | 276 | def get_kernel_patches(self, patches_name, patches_url): 277 | try: 278 | response = self.fetcher.fetch_and_parse_content(patches_url, "plist") 279 | 280 | return response["Kernel"]["Patch"] 281 | except: 282 | print("") 283 | print("Unable to download {} at this time".format(patches_name)) 284 | print("from " + patches_url) 285 | print("") 286 | print("Please try again later or apply them manually.") 287 | print("") 288 | self.utils.request_input() 289 | return [] 290 | 291 | def _update_download_history(self, download_history, product_name, product_id, product_url, sha256_hash): 292 | product_history_index = self.get_product_index(download_history, product_name) 293 | 294 | entry = { 295 | "product_name": product_name, 296 | "id": product_id, 297 | "url": product_url, 298 | "sha256": sha256_hash 299 | } 300 | 301 | if product_history_index is None: 302 | download_history.append(entry) 303 | else: 304 | download_history[product_history_index].update(entry) 305 | 306 | self.utils.create_folder(os.path.dirname(self.download_history_file)) 307 | self.utils.write_file(self.download_history_file, download_history) 308 | 309 | def gather_hardware_sniffer(self): 310 | if os_name != "Windows": 311 | return 312 | 313 | self.utils.head("Gathering Hardware Sniffer") 314 | 315 | PRODUCT_NAME = "Hardware-Sniffer-CLI.exe" 316 | REPO_OWNER = "lzhoang2801" 317 | REPO_NAME = "Hardware-Sniffer" 318 | 319 | destination_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), PRODUCT_NAME) 320 | 321 | latest_release = self.github.get_latest_release(REPO_OWNER, REPO_NAME) or {} 322 | 323 | product_id = None 324 | product_download_url = None 325 | sha256_hash = None 326 | 327 | asset_name = PRODUCT_NAME.split('.')[0] 328 | for asset in latest_release.get("assets", []): 329 | if asset.get("product_name") == asset_name: 330 | product_id = asset.get("id") 331 | product_download_url = asset.get("url") 332 | sha256_hash = asset.get("sha256") 333 | break 334 | 335 | if not all([product_id, product_download_url, sha256_hash]): 336 | print("") 337 | print("Could not find release information for {}.".format(PRODUCT_NAME)) 338 | print("Please try again later.") 339 | print("") 340 | self.utils.request_input() 341 | raise Exception("Could not find release information for {}.".format(PRODUCT_NAME)) 342 | 343 | download_history = self.utils.read_file(self.download_history_file) 344 | if not isinstance(download_history, list): 345 | download_history = [] 346 | 347 | product_history_index = self.get_product_index(download_history, PRODUCT_NAME) 348 | 349 | if product_history_index is not None: 350 | history_item = download_history[product_history_index] 351 | is_latest_id = (product_id == history_item.get("id")) 352 | 353 | file_is_valid = False 354 | if os.path.exists(destination_path): 355 | local_hash = self.integrity_checker.get_sha256(destination_path) 356 | file_is_valid = (sha256_hash == local_hash) 357 | 358 | if is_latest_id and file_is_valid: 359 | print("") 360 | print("Latest version of {} already downloaded.".format(PRODUCT_NAME)) 361 | return destination_path 362 | 363 | print("") 364 | print("Updating" if product_history_index is not None else "Please wait for download", end=" ") 365 | print("{}...".format(PRODUCT_NAME)) 366 | print("") 367 | print("from {}".format(product_download_url)) 368 | print("") 369 | 370 | if not self.fetcher.download_and_save_file(product_download_url, destination_path, sha256_hash): 371 | manual_download_url = f"https://github.com/{REPO_OWNER}/{REPO_NAME}/releases/latest" 372 | print("Go to {} to download {} manually.".format(manual_download_url, PRODUCT_NAME)) 373 | print("") 374 | self.utils.request_input() 375 | raise Exception("Failed to download {}.".format(PRODUCT_NAME)) 376 | 377 | self._update_download_history(download_history, PRODUCT_NAME, product_id, product_download_url, sha256_hash) 378 | 379 | return destination_path -------------------------------------------------------------------------------- /Scripts/compatibility_checker.py: -------------------------------------------------------------------------------- 1 | from Scripts.datasets import gpu_data 2 | from Scripts.datasets import os_data 3 | from Scripts.datasets import pci_data 4 | from Scripts.datasets import codec_layouts 5 | from Scripts import utils 6 | import time 7 | 8 | class CompatibilityChecker: 9 | def __init__(self): 10 | self.utils = utils.Utils() 11 | 12 | def show_macos_compatibility(self, device_compatibility): 13 | if not device_compatibility: 14 | return "\033[90mUnchecked\033[0m" 15 | 16 | if not device_compatibility[0]: 17 | return "\033[0;31mUnsupported\033[0m" 18 | 19 | max_compatibility = self.utils.parse_darwin_version(device_compatibility[0])[0] 20 | min_compatibility = self.utils.parse_darwin_version(device_compatibility[-1])[0] 21 | max_version = self.utils.parse_darwin_version(os_data.get_latest_darwin_version())[0] 22 | min_version = self.utils.parse_darwin_version(os_data.get_lowest_darwin_version())[0] 23 | 24 | if max_compatibility == min_version: 25 | return "\033[1;36mMaximum support up to {}\033[0m".format( 26 | os_data.get_macos_name_by_darwin(device_compatibility[-1]) 27 | ) 28 | 29 | if min_version < min_compatibility or max_compatibility < max_version: 30 | return "\033[1;32m{} to {}\033[0m".format( 31 | os_data.get_macos_name_by_darwin(device_compatibility[-1]), 32 | os_data.get_macos_name_by_darwin(device_compatibility[0]) 33 | ) 34 | 35 | return "\033[1;36mUp to {}\033[0m".format( 36 | os_data.get_macos_name_by_darwin(device_compatibility[0]) 37 | ) 38 | 39 | def is_low_end_intel_cpu(self, processor_name): 40 | return any(cpu_branding in processor_name for cpu_branding in ("Celeron", "Pentium")) 41 | 42 | def check_cpu_compatibility(self): 43 | max_version = os_data.get_latest_darwin_version() 44 | min_version = os_data.get_lowest_darwin_version() 45 | 46 | if "SSE4" not in self.hardware_report.get("CPU").get("SIMD Features"): 47 | max_version = min_version = None 48 | else: 49 | if "SSE4.2" not in self.hardware_report.get("CPU").get("SIMD Features"): 50 | min_version = "18.0.0" 51 | if "SSE4.1" in self.hardware_report.get("CPU").get("SIMD Features"): 52 | max_version = "21.99.99" 53 | 54 | self.hardware_report["CPU"]["Compatibility"] = (max_version, min_version) 55 | 56 | print("{}- {}: {}".format(" "*3, self.hardware_report.get("CPU").get("Processor Name"), self.show_macos_compatibility(self.hardware_report["CPU"].get("Compatibility")))) 57 | 58 | if max_version == min_version and max_version == None: 59 | print("") 60 | print("Missing required SSE4.x instruction set.") 61 | print("Your CPU is not supported by macOS versions newer than Sierra (10.12).") 62 | print("") 63 | self.utils.request_input() 64 | self.utils.exit_program() 65 | 66 | self.max_native_macos_version = max_version 67 | self.min_native_macos_version = min_version 68 | 69 | def check_gpu_compatibility(self): 70 | if not self.hardware_report.get("GPU"): 71 | print("") 72 | print("No GPU found!") 73 | print("Please make sure to export the hardware report with the GPU information") 74 | print("and try again.") 75 | print("") 76 | self.utils.request_input() 77 | self.utils.exit_program() 78 | 79 | for gpu_name, gpu_props in self.hardware_report["GPU"].items(): 80 | gpu_manufacturer = gpu_props.get("Manufacturer") 81 | gpu_codename = gpu_props.get("Codename") 82 | device_id = gpu_props.get("Device ID")[5:] 83 | 84 | max_version = os_data.get_latest_darwin_version() 85 | min_version = os_data.get_lowest_darwin_version() 86 | ocl_patched_max_version = "24.99.99" 87 | ocl_patched_min_version = "20.0.0" 88 | 89 | if "Intel" in gpu_manufacturer: 90 | if device_id.startswith(("0042", "0046")) and self.hardware_report.get("Motherboard").get("Platform") != "Desktop": 91 | max_version = "17.99.99" 92 | elif device_id.startswith("01") and not device_id[-2] in ("5", "6") and not device_id in ("0102", "0106", "010A"): 93 | max_version = "17.99.99" 94 | elif device_id.startswith("01") and not device_id in ("0152", "0156"): 95 | max_version = "20.99.99" 96 | elif device_id.startswith(("04", "0A", "0C", "0D", "0B", "16")): 97 | max_version = "21.99.99" 98 | elif device_id.startswith(("09", "19", "59", "3E", "87", "9B")) and not device_id in ("3E90", "3E93", "3E99", "3E9C", "3EA1", "3EA4", "9B21", "9BA0", "9BA2", "9BA4", "9BA5", "9BA8", "9BAA", "9BAB", "9BAC"): 99 | pass 100 | elif device_id.startswith("8A"): 101 | min_version = "19.4.0" 102 | else: 103 | max_version = min_version = None 104 | 105 | if self.is_low_end_intel_cpu(self.hardware_report.get("CPU").get("Processor Name")): 106 | max_version = min_version = None 107 | elif "AMD" in gpu_manufacturer: 108 | if "Navi 2" in gpu_codename: 109 | if not "AVX2" in self.hardware_report.get("CPU").get("SIMD Features"): 110 | max_version = "21.99.99" 111 | ocl_patched_max_version = max_version 112 | else: 113 | if gpu_codename in ("Navi 23", "Navi 22"): 114 | min_version = "21.2.0" 115 | elif "Navi 21" in gpu_codename: 116 | min_version = "20.5.0" 117 | else: 118 | max_version = min_version = None 119 | elif "Navi 1" in gpu_codename: 120 | if not "AVX2" in self.hardware_report.get("CPU").get("SIMD Features"): 121 | max_version = "21.99.99" 122 | ocl_patched_min_version = "22.0.0" 123 | min_version = "19.0.0" 124 | elif "Vega 20" in gpu_codename: 125 | if not "AVX2" in self.hardware_report.get("CPU").get("SIMD Features"): 126 | max_version = "21.99.99" 127 | ocl_patched_min_version = "22.0.0" 128 | min_version = "18.6.0" 129 | elif gpu_codename in ("Vega 10", "Polaris 22", "Polaris 20", "Baffin", "Ellesmere") or device_id in ("6995", "699F"): 130 | if not "AVX2" in self.hardware_report.get("CPU").get("SIMD Features"): 131 | max_version = "21.99.99" 132 | ocl_patched_min_version = "22.0.0" 133 | min_version = "17.0.0" 134 | elif self.utils.contains_any(gpu_data.AMDCodenames, gpu_codename): 135 | max_version = "21.99.99" 136 | elif device_id in ("15D8", "15DD", "15E7", "1636", "1638", "164C"): 137 | min_version = "19.0.0" 138 | else: 139 | max_version = min_version = None 140 | elif "NVIDIA" in gpu_manufacturer: 141 | if "Kepler" in gpu_codename: 142 | max_version = "20.99.99" 143 | elif gpu_codename in ("Pascal", "Maxwell", "Fermi", "Tesla"): 144 | max_version = "17.99.99" 145 | min_version = "17.0.0" 146 | else: 147 | max_version = min_version = None 148 | 149 | if (max_version == min_version and max_version == None) or \ 150 | ( "Intel" in gpu_manufacturer and device_id.startswith(("01", "04", "0A", "0C", "0D")) and \ 151 | all(monitor_info.get("Connector Type") == "VGA" and monitor_info.get("Connected GPU", gpu_name) == gpu_name for monitor_name, monitor_info in self.hardware_report.get("Monitor", {}).items())): 152 | gpu_props["Compatibility"] = (None, None) 153 | else: 154 | gpu_props["Compatibility"] = (max_version, min_version) 155 | if self.utils.parse_darwin_version(max_version) < self.utils.parse_darwin_version(ocl_patched_max_version): 156 | gpu_props["OCLP Compatibility"] = (ocl_patched_max_version, ocl_patched_min_version if self.utils.parse_darwin_version(ocl_patched_min_version) > self.utils.parse_darwin_version("{}.{}.{}".format(int(max_version[:2]) + 1, 0, 0)) else "{}.{}.{}".format(int(max_version[:2]) + 1, 0, 0)) 157 | 158 | print("{}- {}: {}".format(" "*3, gpu_name, self.show_macos_compatibility(gpu_props.get("Compatibility")))) 159 | 160 | if "OCLP Compatibility" in gpu_props: 161 | print("{}- OCLP Compatibility: {}".format(" "*6, self.show_macos_compatibility(gpu_props.get("OCLP Compatibility")))) 162 | 163 | connected_monitors = [] 164 | for monitor_name, monitor_info in self.hardware_report.get("Monitor", {}).items(): 165 | if monitor_info.get("Connected GPU") == gpu_name: 166 | connected_monitors.append("{} ({})".format(monitor_name, monitor_info.get("Connector Type"))) 167 | if "Intel" in gpu_manufacturer and device_id.startswith(("01", "04", "0A", "0C", "0D")): 168 | if monitor_info.get("Connector Type") == "VGA": 169 | connected_monitors[-1] = "\033[0;31m{}{}\033[0m".format(connected_monitors[-1][:-1], ", unsupported)") 170 | if connected_monitors: 171 | print("{}- Connected Monitor{}: {}".format(" "*6, "s" if len(connected_monitors) > 1 else "", ", ".join(connected_monitors))) 172 | 173 | max_supported_gpu_version = min_supported_gpu_version = None 174 | 175 | for gpu_name, gpu_props in self.hardware_report.get("GPU").items(): 176 | if gpu_props.get("Compatibility") != (None, None): 177 | if all(other_gpu_props.get("Compatibility") == (None, None) for other_gpu_props in self.hardware_report.get("GPU").values() if other_gpu_props != gpu_props): 178 | pass 179 | elif any(monitor_info.get("Connected GPU", gpu_name) != gpu_name for monitor_info in self.hardware_report.get("Monitor", {}).values() if monitor_info.get("Connector Type") == "Internal"): 180 | gpu_props["Compatibility"] = (None, None) 181 | if gpu_props.get("OCLP Compatibility"): 182 | del gpu_props["OCLP Compatibility"] 183 | 184 | max_version, min_version = gpu_props.get("Compatibility") 185 | max_supported_gpu_version = max_version if not max_supported_gpu_version else max_version if self.utils.parse_darwin_version(max_version) > self.utils.parse_darwin_version(max_supported_gpu_version) else max_supported_gpu_version 186 | min_supported_gpu_version = min_version if not min_supported_gpu_version else min_version if self.utils.parse_darwin_version(min_version) < self.utils.parse_darwin_version(min_supported_gpu_version) else min_supported_gpu_version 187 | 188 | if gpu_props.get("OCLP Compatibility"): 189 | self.ocl_patched_macos_version = (gpu_props.get("OCLP Compatibility")[0], self.ocl_patched_macos_version[-1] if self.ocl_patched_macos_version and self.utils.parse_darwin_version(self.ocl_patched_macos_version[-1]) < self.utils.parse_darwin_version(gpu_props.get("OCLP Compatibility")[-1]) else gpu_props.get("OCLP Compatibility")[-1]) 190 | 191 | if max_supported_gpu_version == min_supported_gpu_version and max_supported_gpu_version == None: 192 | print("") 193 | print("You cannot install macOS without a supported GPU.") 194 | print("Please do NOT spam my inbox or issue tracker about this issue anymore!") 195 | print("") 196 | self.utils.request_input() 197 | self.utils.exit_program() 198 | 199 | self.max_native_macos_version = max_supported_gpu_version if self.utils.parse_darwin_version(max_supported_gpu_version) < self.utils.parse_darwin_version(self.max_native_macos_version) else self.max_native_macos_version 200 | self.min_native_macos_version = min_supported_gpu_version if self.utils.parse_darwin_version(min_supported_gpu_version) > self.utils.parse_darwin_version(self.min_native_macos_version) else self.min_native_macos_version 201 | 202 | def check_sound_compatibility(self): 203 | for audio_device, audio_props in self.hardware_report.get("Sound", {}).items(): 204 | codec_id = audio_props.get("Device ID") 205 | 206 | max_version = min_version = None 207 | 208 | if "USB" in audio_props.get("Bus Type") or \ 209 | codec_id.startswith("1002") or \ 210 | codec_id.startswith("8086") and not codec_id in pci_data.IntelSSTIDs or \ 211 | codec_id in codec_layouts.data: 212 | max_version, min_version = os_data.get_latest_darwin_version(), os_data.get_lowest_darwin_version() 213 | 214 | audio_props["Compatibility"] = (max_version, min_version) 215 | 216 | print("{}- {}: {}".format(" "*3, audio_device, self.show_macos_compatibility(audio_props.get("Compatibility")))) 217 | 218 | audio_endpoints = audio_props.get("Audio Endpoints") 219 | if audio_endpoints: 220 | print("{}- Audio Endpoint{}: {}".format(" "*6, "s" if len(audio_endpoints) > 1 else "", ", ".join(audio_endpoints))) 221 | 222 | def check_biometric_compatibility(self): 223 | print(" \033[1;93mNote:\033[0m Biometric authentication in macOS requires Apple T2 Chip,") 224 | print(" which is not available for Hackintosh systems.") 225 | print("") 226 | for biometric_device, biometric_props in self.hardware_report.get("Biometric", {}).items(): 227 | biometric_props["Compatibility"] = (None, None) 228 | print("{}- {}: {}".format(" "*3, biometric_device, self.show_macos_compatibility(biometric_props.get("Compatibility")))) 229 | 230 | def check_network_compatibility(self): 231 | for device_name, device_props in self.hardware_report.get("Network", {}).items(): 232 | bus_type = device_props.get("Bus Type") 233 | device_id = device_props.get("Device ID") 234 | 235 | max_version = os_data.get_latest_darwin_version() 236 | min_version = os_data.get_lowest_darwin_version() 237 | ocl_patched_max_version = os_data.get_latest_darwin_version(include_beta=False) 238 | ocl_patched_min_version = "20.0.0" 239 | 240 | if device_id in pci_data.BroadcomWiFiIDs: 241 | device_index = pci_data.BroadcomWiFiIDs.index(device_id) 242 | 243 | if device_index == 13 or 17 < device_index < 21: 244 | max_version = "22.99.99" 245 | ocl_patched_min_version = "23.0.0" 246 | elif device_index < 12: 247 | max_version = "17.99.99" 248 | elif device_id in pci_data.AtherosWiFiIDs[:8]: 249 | max_version = "17.99.99" 250 | elif device_id in pci_data.AtherosWiFiIDs[8:]: 251 | max_version = "20.99.99" 252 | elif device_id in pci_data.IntelI22XIDs: 253 | min_version = "19.0.0" 254 | elif device_id in pci_data.AquantiaAqtionIDs: 255 | min_version = "21.0.0" 256 | 257 | if device_id in pci_data.WirelessCardIDs: 258 | if not device_id in pci_data.IntelWiFiIDs and not device_id in pci_data.AtherosWiFiIDs[8:]: 259 | device_props["OCLP Compatibility"] = (ocl_patched_max_version, ocl_patched_min_version) 260 | self.ocl_patched_macos_version = (ocl_patched_max_version, self.ocl_patched_macos_version[-1] if self.ocl_patched_macos_version and self.utils.parse_darwin_version(self.ocl_patched_macos_version[-1]) < self.utils.parse_darwin_version(device_props.get("OCLP Compatibility")[-1]) else device_props.get("OCLP Compatibility")[-1]) 261 | device_props["Compatibility"] = (max_version, min_version) 262 | elif device_id in pci_data.EthernetIDs + pci_data.WirelessUSBIDs: 263 | device_props["Compatibility"] = (max_version, min_version) 264 | 265 | if bus_type.startswith("PCI") and not device_props.get("Compatibility"): 266 | device_props["Compatibility"] = (None, None) 267 | 268 | print("{}- {}: {}".format(" "*3, device_name, self.show_macos_compatibility(device_props.get("Compatibility")))) 269 | 270 | if device_id in pci_data.WirelessCardIDs: 271 | if device_id in pci_data.BroadcomWiFiIDs: 272 | print("{}- Continuity Support: \033[1;32mFull\033[0m (AirDrop, Handoff, Universal Clipboard, Instant Hotspot,...)".format(" "*6)) 273 | elif device_id in pci_data.IntelWiFiIDs: 274 | print("{}- Continuity Support: \033[1;33mPartial\033[0m (Handoff and Universal Clipboard with AirportItlwm)".format(" "*6)) 275 | print("{}\033[1;93mNote:\033[0m AirDrop, Universal Clipboard, Instant Hotspot,... not available".format(" "*6)) 276 | elif device_id in pci_data.AtherosWiFiIDs: 277 | print("{}- Continuity Support: \033[1;31mLimited\033[0m (No Continuity features available)".format(" "*6)) 278 | print("{}\033[1;93mNote:\033[0m Atheros cards are not recommended for macOS".format(" "*6)) 279 | 280 | if "OCLP Compatibility" in device_props: 281 | print("{}- OCLP Compatibility: {}".format(" "*6, self.show_macos_compatibility(device_props.get("OCLP Compatibility")))) 282 | 283 | def check_storage_compatibility(self): 284 | if not self.hardware_report.get("Storage Controllers"): 285 | print("") 286 | print("No storage controller found!") 287 | print("Please make sure to export the hardware report with the storage controller information") 288 | print("and try again.") 289 | print("") 290 | self.utils.request_input() 291 | self.utils.exit_program() 292 | 293 | for controller_name, controller_props in self.hardware_report["Storage Controllers"].items(): 294 | if controller_props.get("Bus Type") != "PCI": 295 | continue 296 | 297 | device_id = controller_props.get("Device ID") 298 | subsystem_id = controller_props.get("Subsystem ID", "0"*8) 299 | 300 | max_version = os_data.get_latest_darwin_version() 301 | min_version = os_data.get_lowest_darwin_version() 302 | 303 | if device_id in pci_data.IntelVMDIDs: 304 | print("") 305 | print("Intel VMD controllers are not supported in macOS.") 306 | print("Please disable Intel VMD in the BIOS settings and try again with new hardware report.") 307 | print("") 308 | self.utils.request_input() 309 | self.utils.exit_program() 310 | 311 | if next((device for device in pci_data.UnsupportedNVMeSSDIDs if device_id == device[0] and subsystem_id in device[1]), None): 312 | max_version = min_version = None 313 | 314 | controller_props["Compatibility"] = (max_version, min_version) 315 | 316 | print("{}- {}: {}".format(" "*3, controller_name, self.show_macos_compatibility(controller_props.get("Compatibility")))) 317 | 318 | if all(controller_props.get("Compatibility") == (None, None) for controller_name, controller_props in self.hardware_report["Storage Controllers"].items()): 319 | print("") 320 | print("No compatible storage controller for macOS was found!") 321 | print("Consider purchasing a compatible SSD NVMe for your system.") 322 | print("Western Digital NVMe SSDs are generally recommended for good macOS compatibility.") 323 | print("") 324 | self.utils.request_input() 325 | self.utils.exit_program() 326 | 327 | def check_bluetooth_compatibility(self): 328 | for bluetooth_name, bluetooth_props in self.hardware_report.get("Bluetooth", {}).items(): 329 | device_id = bluetooth_props.get("Device ID") 330 | 331 | max_version = os_data.get_latest_darwin_version() 332 | min_version = os_data.get_lowest_darwin_version() 333 | 334 | if device_id in pci_data.AtherosBluetoothIDs: 335 | max_version = "20.99.99" 336 | elif device_id in pci_data.BluetoothIDs: 337 | pass 338 | else: 339 | max_version = min_version = None 340 | 341 | bluetooth_props["Compatibility"] = (max_version, min_version) 342 | 343 | print("{}- {}: {}".format(" "*3, bluetooth_name, self.show_macos_compatibility(bluetooth_props.get("Compatibility")))) 344 | 345 | def check_sd_controller_compatibility(self): 346 | for controller_name, controller_props in self.hardware_report.get("SD Controller", {}).items(): 347 | device_id = controller_props.get("Device ID") 348 | 349 | max_version = os_data.get_latest_darwin_version() 350 | min_version = os_data.get_lowest_darwin_version() 351 | 352 | if device_id in pci_data.RealtekCardReaderIDs: 353 | if device_id in pci_data.RealtekCardReaderIDs[:5]: 354 | max_version = "23.99.99" 355 | else: 356 | max_version = min_version = None 357 | 358 | controller_props["Compatibility"] = (max_version, min_version) 359 | 360 | print("{}- {}: {}".format(" "*3, controller_name, self.show_macos_compatibility(controller_props.get("Compatibility")))) 361 | 362 | def check_compatibility(self, hardware_report): 363 | self.hardware_report = hardware_report 364 | self.ocl_patched_macos_version = None 365 | 366 | self.utils.head("Compatibility Checker") 367 | print("") 368 | print("Checking compatibility with macOS for the following devices:") 369 | print("") 370 | 371 | steps = [ 372 | ('CPU', self.check_cpu_compatibility), 373 | ('GPU', self.check_gpu_compatibility), 374 | ('Sound', self.check_sound_compatibility), 375 | ('Biometric', self.check_biometric_compatibility), 376 | ('Network', self.check_network_compatibility), 377 | ('Storage Controllers', self.check_storage_compatibility), 378 | ('Bluetooth', self.check_bluetooth_compatibility), 379 | ('SD Controller', self.check_sd_controller_compatibility) 380 | ] 381 | 382 | index = 0 383 | for device_type, function in steps: 384 | if self.hardware_report.get(device_type): 385 | index += 1 386 | print("{}. {}:".format(index, device_type)) 387 | time.sleep(0.25) 388 | function() 389 | 390 | print("") 391 | self.utils.request_input() 392 | 393 | return hardware_report, (self.min_native_macos_version, self.max_native_macos_version), self.ocl_patched_macos_version --------------------------------------------------------------------------------