# Original source: https://github.com/corpnewt/SSDTTime/blob/44aadf01b7fe75cb4a3eab5590e7b6c458265c6f/SSDTTime.py from Scripts.datasets import acpi_patch_data from Scripts.datasets import chipset_data from Scripts.datasets import cpu_data from Scripts.datasets import pci_data from Scripts import smbios from Scripts import dsdt from Scripts import run from Scripts import utils import os import binascii import re import tempfile import shutil import sys import plistlib class ACPIGuru: def __init__(self): self.acpi = dsdt.DSDT() self.smbios = smbios.SMBIOS() self.run = run.Run().run self.utils = utils.Utils() self.patches = acpi_patch_data.patches self.hardware_report = None self.disabled_devices = None self.acpi_directory = None self.smbios_model = None self.dsdt = None self.lpc_bus_device = None self.osi_strings = { "Windows 2000": "Windows 2000", "Windows XP": "Windows 2001", "Windows XP SP1": "Windows 2001 SP1", "Windows Server 2003": "Windows 2001.1", "Windows XP SP2": "Windows 2001 SP2", "Windows Server 2003 SP1": "Windows 2001.1 SP1", "Windows Vista": "Windows 2006", "Windows Vista SP1": "Windows 2006 SP1", "Windows Server 2008": "Windows 2006.1", "Windows 7, Win Server 2008 R2": "Windows 2009", "Windows 8, Win Server 2012": "Windows 2012", "Windows 8.1": "Windows 2013", "Windows 10": "Windows 2015", "Windows 10, version 1607": "Windows 2016", "Windows 10, version 1703": "Windows 2017", "Windows 10, version 1709": "Windows 2017.2", "Windows 10, version 1803": "Windows 2018", "Windows 10, version 1809": "Windows 2018.2", "Windows 10, version 1903": "Windows 2019", "Windows 10, version 2004": "Windows 2020", "Windows 11": "Windows 2021", "Windows 11, version 22H2": "Windows 2022" } self.pre_patches = ( { "PrePatch":"GPP7 duplicate _PRW methods", "Comment" :"GPP7._PRW to XPRW to fix Gigabyte's Mistake", "Find" :"3708584847500A021406535245470214065350525701085F505257", "Replace" :"3708584847500A0214065352454702140653505257010858505257" }, { "PrePatch":"GPP7 duplicate UP00 devices", "Comment" :"GPP7.UP00 to UPXX to fix Gigabyte's Mistake", "Find" :"1047052F035F53425F50434930475050375B82450455503030", "Replace" :"1047052F035F53425F50434930475050375B82450455505858" }, { "PrePatch":"GPP6 duplicate _PRW methods", "Comment" :"GPP6._PRW to XPRW to fix ASRock's Mistake", "Find" :"47505036085F4144520C04000200140F5F505257", "Replace" :"47505036085F4144520C04000200140F58505257" }, { "PrePatch":"GPP1 duplicate PTXH devices", "Comment" :"GPP1.PTXH to XTXH to fix MSI's Mistake", "Find" :"50545848085F41445200140F", "Replace" :"58545848085F41445200140F" } ) self.target_irqs = [0, 2, 8, 11] self.illegal_names = ("XHC1", "EHC1", "EHC2", "PXSX") self.dsdt_patches = [] def get_unique_name(self,name,target_folder,name_append="-Patched"): # Get a new file name in the Results folder so we don't override the original name = os.path.basename(name) ext = "" if not "." in name else name.split(".")[-1] if ext: name = name[:-len(ext)-1] if name_append: name = name+str(name_append) check_name = ".".join((name,ext)) if ext else name if not os.path.exists(os.path.join(target_folder,check_name)): return check_name # We need a unique name num = 1 while True: check_name = "{}-{}".format(name,num) if ext: check_name += "."+ext if not os.path.exists(os.path.join(target_folder,check_name)): return check_name num += 1 # Increment our counter def get_unique_device(self, path, base_name, starting_number=0, used_names = []): # Appends a hex number until a unique device is found while True: hex_num = hex(starting_number).replace("0x","").upper() name = base_name[:-1*len(hex_num)]+hex_num if not len(self.acpi.get_device_paths("."+name)) and not name in used_names: return (name,starting_number) starting_number += 1 def sorted_nicely(self, l): convert = lambda text: int(text) if text.isdigit() else text alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key.lower()) ] return sorted(l, key = alphanum_key) def read_acpi_tables(self, path): if not path: return self.utils.head("Loading ACPI Table(s)") print("by CorpNewt") print("") tables = [] trouble_dsdt = None fixed = False temp = None prior_tables = self.acpi.acpi_tables # Retain in case of failure # Clear any existing tables so we load anew self.acpi.acpi_tables = {} if os.path.isdir(path): print("Gathering valid tables from {}...\n".format(os.path.basename(path))) for t in self.sorted_nicely(os.listdir(path)): if not "Patched" in t and self.acpi.table_is_valid(path,t): print(" - {}".format(t)) tables.append(t) if not tables: # Check if there's an ACPI directory within the passed # directory - this may indicate SysReport was dropped if os.path.isdir(os.path.join(path,"ACPI")): # Rerun this function with that updated path return self.read_acpi_tables(os.path.join(path,"ACPI")) print(" - No valid .aml files were found!") print("") #self.u.grab("Press [enter] to return...") self.utils.request_input() # Restore any prior tables self.acpi.acpi_tables = prior_tables return print("") # We got at least one file - let's look for the DSDT specifically # and try to load that as-is. If it doesn't load, we'll have to # manage everything with temp folders dsdt_list = [x for x in tables if self.acpi._table_signature(path,x) == "DSDT"] if len(dsdt_list) > 1: print("Multiple files with DSDT signature passed:") for d in self.sorted_nicely(dsdt_list): print(" - {}".format(d)) print("\nOnly one is allowed at a time. Please remove one of the above and try again.") print("") #self.u.grab("Press [enter] to return...") self.utils.request_input() # Restore any prior tables self.acpi.acpi_tables = prior_tables return # Get the DSDT, if any dsdt = dsdt_list[0] if len(dsdt_list) else None if dsdt: # Try to load it and see if it causes problems print("Disassembling {} to verify if pre-patches are needed...".format(dsdt)) if not self.acpi.load(os.path.join(path,dsdt))[0]: trouble_dsdt = dsdt else: print("\nDisassembled successfully!\n") elif not "Patched" in path and os.path.isfile(path): print("Loading {}...".format(os.path.basename(path))) if self.acpi.load(path)[0]: print("\nDone.") # If it loads fine - just return the path # to the parent directory return os.path.dirname(path) if not self.acpi._table_signature(path) == "DSDT": # Not a DSDT, we aren't applying pre-patches print("\n{} could not be disassembled!".format(os.path.basename(path))) print("") #self.u.grab("Press [enter] to return...") self.utils.request_input() # Restore any prior tables self.acpi.acpi_tables = prior_tables return # It didn't load - set it as the trouble file trouble_dsdt = os.path.basename(path) # Put the table in the tables list, and adjust # the path to represent the parent dir tables.append(os.path.basename(path)) path = os.path.dirname(path) else: print("Passed file/folder does not exist!") print("") #self.u.grab("Press [enter] to return...") self.utils.request_input() # Restore any prior tables self.acpi.acpi_tables = prior_tables return # If we got here - check if we have a trouble_dsdt. if trouble_dsdt: # We need to move our ACPI files to a temp folder # then try patching the DSDT there temp = tempfile.mkdtemp() for table in tables: shutil.copy( os.path.join(path,table), temp ) # Get a reference to the new trouble file trouble_path = os.path.join(temp,trouble_dsdt) # Now we try patching it print("Checking available pre-patches...") print("Loading {} into memory...".format(trouble_dsdt)) with open(trouble_path,"rb") as f: d = f.read() res = self.acpi.check_output(path) target_name = self.get_unique_name(trouble_dsdt,res,name_append="-Patched") self.dsdt_patches = [] print("Iterating patches...\n") for p in self.pre_patches: if not all(x in p for x in ("PrePatch","Comment","Find","Replace")): continue print(" - {}".format(p["PrePatch"])) find = binascii.unhexlify(p["Find"]) if d.count(find) == 1: self.dsdt_patches.append(p) # Retain the patch repl = binascii.unhexlify(p["Replace"]) print(" --> Located - applying...") d = d.replace(find,repl) # Replace it in memory with open(trouble_path,"wb") as f: f.write(d) # Write the updated file # Attempt to load again if self.acpi.load(trouble_path)[0]: fixed = True # We got it to load - let's write the patches print("\nDisassembled successfully!\n") #self.make_plist(None, None, patches) # Save to the local file #with open(os.path.join(res,target_name),"wb") as f: # f.write(d) #print("\n!! Patches applied to modified file in Results folder:\n {}".format(target_name)) #self.patch_warn() break if not fixed: print("\n{} could not be disassembled!".format(trouble_dsdt)) print("") #self.u.grab("Press [enter] to return...") self.utils.request_input() if temp: shutil.rmtree(temp,ignore_errors=True) # Restore any prior tables self.acpi.acpi_tables = prior_tables return # Let's load the rest of the tables if len(tables) > 1: print("Loading valid tables in {}...".format(path)) loaded_tables,failed = self.acpi.load(temp or path) if not loaded_tables or failed: print("\nFailed to load tables in {}{}\n".format( os.path.dirname(path) if os.path.isfile(path) else path, ":" if failed else "" )) for t in self.sorted_nicely(failed): print(" - {}".format(t)) # Restore any prior tables if not loaded_tables: self.acpi.acpi_tables = prior_tables else: if len(tables) > 1: print("") # Newline for readability print("Done.") # If we had to patch the DSDT, or if not all tables loaded, # make sure we get interaction from the user to continue if trouble_dsdt or not loaded_tables or failed: print("") #self.u.grab("Press [enter] to return...") #self.utils.request_input() if temp: shutil.rmtree(temp,ignore_errors=True) self.dsdt = self.acpi.get_dsdt_or_only() return path def _ensure_dsdt(self, allow_any=False): # Helper to check conditions for when we have valid tables return self.dsdt and ((allow_any and self.acpi.acpi_tables) or (not allow_any and self.acpi.get_dsdt_or_only())) def ensure_dsdt(self, allow_any=False): if self._ensure_dsdt(allow_any=allow_any): # Got it already return True # Need to prompt self.select_acpi_tables() self.dsdt = self.acpi.get_dsdt_or_only() if self._ensure_dsdt(allow_any=allow_any): return True return False def get_sta_var(self,var="STAS",device=None,dev_hid="ACPI000E",dev_name="AWAC",log_locate=False,table=None): # Helper to check for a device, check for (and qualify) an _STA method, # and look for a specific variable in the _STA scope # # Returns a dict with device info - only "valid" parameter is # guaranteed. has_var = False patches = [] root = None if device: dev_list = self.acpi.get_device_paths(device,table=table) if not len(dev_list): if log_locate: print(" - Could not locate {}".format(device)) return {"value":False} else: if log_locate: print("Locating {} ({}) devices...".format(dev_hid,dev_name)) dev_list = self.acpi.get_device_paths_with_hid(dev_hid,table=table) if not len(dev_list): if log_locate: print(" - Could not locate any {} devices".format(dev_hid)) return {"valid":False} dev = dev_list[0] if log_locate: print(" - Found {}".format(dev[0])) root = dev[0].split(".")[0] #print(" --> Verifying _STA...") # Check Method first - then Name sta_type = "MethodObj" sta = self.acpi.get_method_paths(dev[0]+"._STA",table=table) xsta = self.acpi.get_method_paths(dev[0]+".XSTA",table=table) if not sta and not xsta: # Check for names sta_type = "IntObj" sta = self.acpi.get_name_paths(dev[0]+"._STA",table=table) xsta = self.acpi.get_name_paths(dev[0]+".XSTA",table=table) if xsta and not sta: #print(" --> _STA already renamed to XSTA! Skipping other checks...") #print(" Please disable _STA to XSTA renames for this device, reboot, and try again.") #print("") return {"valid":False,"break":True,"device":dev,"dev_name":dev_name,"dev_hid":dev_hid,"sta_type":sta_type} if sta: if var: scope = "\n".join(self.acpi.get_scope(sta[0][1],strip_comments=True,table=table)) has_var = var in scope #print(" --> {} {} variable".format("Has" if has_var else "Does NOT have",var)) #else: #print(" --> No _STA method/name found") # Let's find out of we need a unique patch for _STA -> XSTA if sta and not has_var: #print(" --> Generating _STA to XSTA rename") sta_index = self.acpi.find_next_hex(sta[0][1],table=table)[1] #print(" ----> Found at index {}".format(sta_index)) sta_hex = "5F535441" # _STA xsta_hex = "58535441" # XSTA padl,padr = self.acpi.get_shortest_unique_pad(sta_hex,sta_index,table=table) patches.append({"Comment":"{} _STA to XSTA Rename".format(dev_name),"Find":padl+sta_hex+padr,"Replace":padl+xsta_hex+padr}) return {"valid":True,"has_var":has_var,"sta":sta,"patches":patches,"device":dev,"dev_name":dev_name,"dev_hid":dev_hid,"root":root,"sta_type":sta_type} def get_lpc_name(self,log=False,skip_ec=False,skip_common_names=False): # Intel devices appear to use _ADR, 0x001F0000 # AMD devices appear to use _ADR, 0x00140003 if log: print("Locating LPC(B)/SBRG...") for table_name in self.sorted_nicely(list(self.acpi.acpi_tables)): table = self.acpi.acpi_tables[table_name] # The LPCB device will always be the parent of the PNP0C09 device # if found if not skip_ec: ec_list = self.acpi.get_device_paths_with_hid("PNP0C09",table=table) if len(ec_list): lpc_name = ".".join(ec_list[0][0].split(".")[:-1]) if log: print(" - Found {} in {}".format(lpc_name,table_name)) return lpc_name # Maybe try common names if we haven't found it yet if not skip_common_names: for x in ("LPCB", "LPC0", "LPC", "SBRG", "PX40"): try: lpc_name = self.acpi.get_device_paths(x,table=table)[0][0] if log: print(" - Found {} in {}".format(lpc_name,table_name)) return lpc_name except: pass # Finally check by address - some Intel tables have devices at # 0x00140003 paths = self.acpi.get_path_of_type(obj_type="Name",obj="_ADR",table=table) for path in paths: adr = self.get_address_from_line(path[1],table=table) if adr in (0x001F0000, 0x00140003): # Get the path minus ._ADR lpc_name = path[0][:-5] # Make sure the LPCB device does not have an _HID lpc_hid = lpc_name+"._HID" if any(x[0]==lpc_hid for x in table.get("paths",[])): continue if log: print(" - Found {} in {}".format(lpc_name,table_name)) return lpc_name if log: print(" - Could not locate LPC(B)! Aborting!") print("") return None # Didn't find it def get_address_from_line(self, line, split_by="_ADR, ", table=None): if table is None: table = self.acpi.get_dsdt_or_only() try: return int(table["lines"][line].split(split_by)[1].split(")")[0].replace("Zero","0x0").replace("One","0x1"),16) except: return None def enable_cpu_power_management(self): #if not self.ensure_dsdt(allow_any=True): # return #self.u.head("Plugin Type") #print("") #print("Determining CPU name scheme...") for table_name in self.sorted_nicely(list(self.acpi.acpi_tables)): ssdt_name = "SSDT-PLUG" table = self.acpi.acpi_tables[table_name] if not table.get("signature") in (b"DSDT",b"SSDT"): continue # We're not checking data tables #print(" Checking {}...".format(table_name)) try: cpu_name = self.acpi.get_processor_paths(table=table)[0][0] except: cpu_name = None if cpu_name: #print(" - Found Processor: {}".format(cpu_name)) #oc = {"Comment":"Sets plugin-type to 1 on first Processor object","Enabled":True,"Path":ssdt_name+".aml"} #print("Creating SSDT-PLUG...") ssdt = """// // Based on the sample found at https://github.com/acidanthera/OpenCorePkg/blob/master/Docs/AcpiSamples/SSDT-PLUG.dsl // DefinitionBlock ("", "SSDT", 2, "ZPSS", "CpuPlug", 0x00003000) { External ([[CPUName]], ProcessorObj) Scope ([[CPUName]]) { If (_OSI ("Darwin")) { Method (_DSM, 4, NotSerialized) // _DSM: Device-Specific Method { If (LNot (Arg2)) { Return (Buffer (One) { 0x03 }) } Return (Package (0x02) { "plugin-type", One }) } } } }""".replace("[[CPUName]]",cpu_name) else: ssdt_name += "-ALT" #print(" - No Processor objects found...") procs = self.acpi.get_device_paths_with_hid(hid="ACPI0007",table=table) if not procs: #print(" - No ACPI0007 devices found...") continue #print(" - Located {:,} ACPI0007 device{}".format( # len(procs), "" if len(procs)==1 else "s" #)) parent = procs[0][0].split(".")[0] #print(" - Got parent at {}, iterating...".format(parent)) proc_list = [] for proc in procs: #print(" - Checking {}...".format(proc[0].split(".")[-1])) uid = self.acpi.get_path_of_type(obj_type="Name",obj=proc[0]+"._UID",table=table) if not uid: #print(" --> Not found! Skipping...") continue # Let's get the actual _UID value try: _uid = table["lines"][uid[0][1]].split("_UID, ")[1].split(")")[0] #print(" --> _UID: {}".format(_uid)) proc_list.append((proc[0],_uid)) except: pass #print(" --> Not found! Skipping...") if not proc_list: continue #print("Iterating {:,} valid processor device{}...".format(len(proc_list),"" if len(proc_list)==1 else "s")) ssdt = """// // Based on the sample found at https://github.com/acidanthera/OpenCorePkg/blob/master/Docs/AcpiSamples/Source/SSDT-PLUG-ALT.dsl // DefinitionBlock ("", "SSDT", 2, "ZPSS", "CpuPlugA", 0x00003000) { External ([[parent]], DeviceObj) Scope ([[parent]]) {""".replace("[[parent]]",parent) # Ensure our name scheme won't conflict schemes = ("C000","CP00","P000","PR00","CX00","PX00") # Walk the processor objects, and add them to the SSDT for i,proc_uid in enumerate(proc_list): proc,uid = proc_uid adr = hex(i)[2:].upper() name = None for s in schemes: name_check = s[:-len(adr)]+adr check_path = "{}.{}".format(parent,name_check) if self.acpi.get_path_of_type(obj_type="Device",obj=check_path,table=table): continue # Already defined - skip # If we got here - we found an unused name name = name_check break if not name: #print(" - Could not find an available name scheme! Aborting.") #print("") #self.u.grab("Press [enter] to return to main menu...") return ssdt+=""" Processor ([[name]], [[uid]], 0x00000510, 0x06) { // [[proc]] Name (_HID, "ACPI0007" /* Processor Device */) // _HID: Hardware ID Name (_UID, [[uid]]) Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return (Zero) } }""".replace("[[name]]",name).replace("[[uid]]",uid).replace("[[proc]]",proc) if i == 0: # Got the first, add plugin-type as well ssdt += """ Method (_DSM, 4, NotSerialized) { If (LNot (Arg2)) { Return (Buffer (One) { 0x03 }) } Return (Package (0x02) { "plugin-type", One }) }""" # Close up the SSDT ssdt += """ }""" ssdt += """ } }""" # oc = {"Comment":"Redefines modern CPU Devices as legacy Processor objects and sets plugin-type to 1 on the first","Enabled":True,"Path":ssdt_name+".aml"} #self.make_plist(oc, ssdt_name+".aml", ()) #self.write_ssdt(ssdt_name,ssdt) #print("") #print("Done.") #self.patch_warn() #self.u.grab("Press [enter] to return...") #return # If we got here - we reached the end #print("No valid processor devices found!") #print("") #self.u.grab("Press [enter] to return...") #return return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt), "Path": ssdt_name + ".aml" } ] } def list_irqs(self): # Walks the DSDT keeping track of the current device and # saving the IRQNoFlags if found devices = {} current_device = None current_hid = None irq = False last_irq = False irq_index = 0 for index,line in enumerate(self.dsdt["lines"]): if self.acpi.is_hex(line): # Skip all hex lines continue if irq: # Get the values num = line.split("{")[1].split("}")[0].replace(" ","") num = "#" if not len(num) else num if current_device in devices: if last_irq: # In a row devices[current_device]["irq"] += ":"+num else: # Skipped at least one line irq_index = self.acpi.find_next_hex(index)[1] devices[current_device]["irq"] += "-"+str(irq_index)+"|"+num else: irq_index = self.acpi.find_next_hex(index)[1] devices[current_device] = {"irq":str(irq_index)+"|"+num} irq = False last_irq = True elif "Device (" in line: # Check if we retain the _HID here if current_device and current_device in devices and current_hid: # Save it devices[current_device]["hid"] = current_hid last_irq = False current_hid = None try: current_device = line.split("(")[1].split(")")[0] except: current_device = None continue elif "_HID, " in line and current_device: try: current_hid = line.split('"')[1] except: pass elif "IRQNoFlags" in line and current_device: # Next line has our interrupts irq = True # Check if just a filler line elif len(line.replace("{","").replace("}","").replace("(","").replace(")","").replace(" ","").split("//")[0]): # Reset last IRQ as it's not in a row last_irq = False # Retain the final _HID if needed if current_device and current_device in devices and current_hid: devices[current_device]["hid"] = current_hid return devices def get_irq_choice(self, irqs): names_and_hids = [ "PIC", "IPIC", "TMR", "TIMR", "RTC", "RTC0", "RTC1", "PNPC0000", "PNP0100", "PNP0B00" ] defaults = [x for x in irqs if x.upper() in names_and_hids or irqs[x].get("hid","").upper() in names_and_hids] d = {} for x in defaults: d[x] = self.target_irqs return d def get_hex_from_irqs(self, irq, rem_irq = None): # We need to search for a few different types: # # 22 XX XX 22 XX XX 22 XX XX (multiples on different lines) # 22 XX XX (summed multiples in the same bracket - {0,8,11}) # 22 XX XX (single IRQNoFlags entry) # # Can end with 79 [00] (end of method), 86 09 (middle of method) or 47 01 (unknown) lines = [] remd = [] for a in irq.split("-"): index,i = a.split("|") # Get the index index = int(index) find = self.get_int_for_line(i) repl = [0]*len(find) # Now we need to verify if we're patching *all* IRQs, or just some specifics if rem_irq: repl = [x for x in find] matched = [] for x in rem_irq: # Get the int rem = self.convert_irq_to_int(x) repl1 = [y&(rem^0xFFFF) if y >= rem else y for y in repl] if repl1 != repl: # Changes were made remd.append(x) repl = [y for y in repl1] # Get the hex d = { "irq":i, "find": "".join(["22"+self.acpi.get_hex_from_int(x) for x in find]), "repl": "".join(["22"+self.acpi.get_hex_from_int(x) for x in repl]), "remd": remd, "index": index } d["changed"] = not (d["find"]==d["repl"]) lines.append(d) return lines def get_int_for_line(self, irq): irq_list = [] for i in irq.split(":"): irq_list.append(self.same_line_irq(i)) return irq_list def convert_irq_to_int(self, irq): b = "0"*(16-irq)+"1"+"0"*(irq) return int(b,2) def same_line_irq(self, irq): # We sum the IRQ values and return the int total = 0 for i in irq.split(","): if i == "#": continue # Null value try: i=int(i) except: continue # Not an int if i > 15 or i < 0: continue # Out of range total = total | self.convert_irq_to_int(i) return total def fix_irq_conflicts(self): hpets = self.acpi.get_device_paths_with_hid("PNP0103") hpet_fake = not hpets hpet_sta = False sta = None patches = [] if hpets: name = hpets[0][0] sta = self.get_sta_var(var=None,dev_hid="PNP0103",dev_name="HPET",log_locate=False) if sta.get("patches"): hpet_sta = True patches.extend(sta.get("patches",[])) hpet = self.acpi.get_method_paths(name+"._CRS") or self.acpi.get_name_paths(name+"._CRS") if not hpet: return crs_index = self.acpi.find_next_hex(hpet[0][1])[1] mem_base = mem_length = primed = None for line in self.acpi.get_scope(hpets[0][1],strip_comments=True): if "Memory32Fixed (" in line: primed = True continue if not primed: continue elif ")" in line: # Reached the end of the scope break # We're primed, and not at the end - let's try to get the base and length try: val = line.strip().split(",")[0].replace("Zero","0x0").replace("One","0x1") check = int(val,16) except: break # Set them in order if mem_base is None: mem_base = val else: mem_length = val break # Leave after we get both values # Check if we found the values got_mem = mem_base and mem_length if not got_mem: mem_base = "0xFED00000" mem_length = "0x00000400" crs = "5F435253" xcrs = "58435253" padl,padr = self.acpi.get_shortest_unique_pad(crs, crs_index) patches.append({"Comment":"{} _CRS to XCRS Rename".format(name.split(".")[-1].lstrip("\\")),"Find":padl+crs+padr,"Replace":padl+xcrs+padr}) else: ec_list = self.acpi.get_device_paths_with_hid("PNP0C09") name = None if len(ec_list): name = ".".join(ec_list[0][0].split(".")[:-1]) if name == None: for x in ("LPCB", "LPC0", "LPC", "SBRG", "PX40"): try: name = self.acpi.get_device_paths(x)[0][0] break except: pass if not name: return devs = self.list_irqs() target_irqs = self.get_irq_choice(devs) if target_irqs is None: return # Bailed, going to the main menu # Let's apply patches as we go saved_dsdt = self.dsdt.get("raw") unique_patches = {} generic_patches = [] for dev in devs: if not dev in target_irqs: continue irq_patches = self.get_hex_from_irqs(devs[dev]["irq"],target_irqs[dev]) i = [x for x in irq_patches if x["changed"]] for a,t in enumerate(i): if not t["changed"]: # Nothing patched - skip continue # Try our endings here - 7900, 8609, and 4701 - also allow for up to 8 chars of pad (thanks MSI) matches = re.findall("("+t["find"]+"(.{0,8})(7900|4701|8609))",self.acpi.get_hex_starting_at(t["index"])[0]) if not len(matches): continue if len(matches) > 1: # Found too many matches! # Add them all as find/replace entries for x in matches: generic_patches.append({ "remd":",".join([str(y) for y in set(t["remd"])]), "orig":t["find"], "find":t["find"]+"".join(x[1:]), "repl":t["repl"]+"".join(x[1:]) }) continue ending = "".join(matches[0][1:]) padl,padr = self.acpi.get_shortest_unique_pad(t["find"]+ending, t["index"]) t_patch = padl+t["find"]+ending+padr r_patch = padl+t["repl"]+ending+padr if not dev in unique_patches: unique_patches[dev] = [] unique_patches[dev].append({ "dev":dev, "remd":",".join([str(y) for y in set(t["remd"])]), "orig":t["find"], "find":t_patch, "repl":r_patch }) # Walk the unique patches if any if len(unique_patches): for x in unique_patches: for i,p in enumerate(unique_patches[x]): patch_name = "{} IRQ {} Patch".format(x, p["remd"]) if len(unique_patches[x]) > 1: patch_name += " - {} of {}".format(i+1, len(unique_patches[x])) patches.append({ "Comment": patch_name, "Find": p["find"], "Replace": p["repl"] }) # Walk the generic patches if any if len(generic_patches): generic_set = [] # Make sure we don't repeat find values for x in generic_patches: if x in generic_set: continue generic_set.append(x) for i,x in enumerate(generic_set): patch_name = "Generic IRQ Patch {} of {} - {} - {}".format(i+1,len(generic_set),x["remd"],x["orig"]) patches.append({ "Comment": patch_name, "Find": x["find"], "Replace": x["repl"], "Enabled": False }) # Restore the original DSDT in memory self.dsdt["raw"] = saved_dsdt ssdt_name = "SSDT-HPET" if hpet_fake: ssdt_content = """// Fake HPET device // DefinitionBlock ("", "SSDT", 2, "ZPSS", "HPET", 0x00000000) { External ([[name]], DeviceObj) Scope ([[name]]) { Device (HPET) { Name (_HID, EisaId ("PNP0103") /* HPET System Timer */) // _HID: Hardware ID Name (_CID, EisaId ("PNP0C01") /* System Board */) // _CID: Compatible ID Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return (Zero) } } Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings { IRQNoFlags () {0,8,11} Memory32Fixed (ReadWrite, 0xFED00000, // Address Base 0x00000400, // Address Length ) }) } } }""".replace("[[name]]",name) else: ssdt_content = """// // Supplementary HPET _CRS from Goldfish64 // Requires the HPET's _CRS to XCRS rename // DefinitionBlock ("", "SSDT", 2, "ZPSS", "HPET", 0x00000000) { External ([[name]], DeviceObj) External ([[name]].XCRS, [[type]]) Scope ([[name]]) { Name (BUFX, ResourceTemplate () { IRQNoFlags () {0,8,11} Memory32Fixed (ReadWrite, // [[mem]] [[mem_base]], // Address Base [[mem_length]], // Address Length ) }) Method (_CRS, 0, Serialized) // _CRS: Current Resource Settings { // Return our buffer if booting macOS or the XCRS method // no longer exists for some reason If (LOr (_OSI ("Darwin"), LNot(CondRefOf ([[name]].XCRS)))) { Return (BUFX) } // Not macOS and XCRS exists - return its result Return ([[name]].XCRS[[method]]) }""" \ .replace("[[name]]",name) \ .replace("[[type]]","MethodObj" if hpet[0][-1] == "Method" else "BuffObj") \ .replace("[[mem]]","Base/Length pulled from DSDT" if got_mem else "Default Base/Length - verify with your DSDT!") \ .replace("[[mem_base]]",mem_base) \ .replace("[[mem_length]]",mem_length) \ .replace("[[method]]"," ()" if hpet[0][-1]=="Method" else "") if hpet_sta: # Inject our external reference to the renamed XSTA method ssdt_parts = [] external = False for line in ssdt_content.split("\n"): if "External (" in line: external = True elif external: ssdt_parts.append(" External ({}.XSTA, {})".format(name,sta["sta_type"])) external = False ssdt_parts.append(line) ssdt_content = "\n".join(ssdt_parts) # Add our method ssdt_content += """ Method (_STA, 0, NotSerialized) // _STA: Status { // Return 0x0F if booting macOS or the XSTA method // no longer exists for some reason If (LOr (_OSI ("Darwin"), LNot (CondRefOf ([[name]].XSTA)))) { Return (0x0F) } // Not macOS and XSTA exists - return its result Return ([[name]].XSTA[[called]]) }""".replace("[[name]]",name).replace("[[called]]"," ()" if sta["sta_type"]=="MethodObj" else "") ssdt_content += """ } }""" return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ], "Patch": patches } def fix_system_clock_awac(self): #if not self.ensure_dsdt(): # return #self.u.head("SSDT RTCAWAC") #print("") rtc_range_needed = False rtc_crs_type = None crs_lines = [] lpc_name = None awac_dict = self.get_sta_var(var="STAS",dev_hid="ACPI000E",dev_name="AWAC") rtc_dict = self.get_sta_var(var="STAS",dev_hid="PNP0B00",dev_name="RTC") # At this point - we should have any info about our AWAC and RTC devices # we need. Let's see if we need an RTC fake - then build the SSDT. if not rtc_dict.get("valid"): #print(" - Fake needed!") lpc_name = self.get_lpc_name() if lpc_name is None: #self.u.grab("Press [enter] to return to main menu...") return else: # Let's check if our RTC device has a _CRS variable - and if so, let's look for any skipped ranges #print(" --> Checking for _CRS...") rtc_crs = self.acpi.get_method_paths(rtc_dict["device"][0]+"._CRS") or self.acpi.get_name_paths(rtc_dict["device"][0]+"._CRS") if rtc_crs: #print(" ----> {}".format(rtc_crs[0][0])) rtc_crs_type = "MethodObj" if rtc_crs[0][-1] == "Method" else "BuffObj" # Only check for the range if it's a buffobj if rtc_crs_type.lower() == "buffobj": #print(" --> _CRS is a Buffer - checking RTC range...") last_adr = last_len = last_ind = None crs_scope = self.acpi.get_scope(rtc_crs[0][1]) # Let's try and clean up the scope - it's often a jumbled mess pad_len = len(crs_scope[0])-len(crs_scope[0].lstrip()) pad = crs_scope[0][:pad_len] fixed_scope = [] for line in crs_scope: if line.startswith(pad): # Got a full line - strip the pad, and save it fixed_scope.append(line[pad_len:]) else: # Likely a part of the prior line fixed_scope[-1] = fixed_scope[-1]+line for i,line in enumerate(fixed_scope): if "Name (_CRS, " in line: # Rename _CRS to BUFX for later - and strip any comments to avoid confusion line = line.replace("Name (_CRS, ","Name (BUFX, ").split(" //")[0] if "IO (Decode16," in line: # We have our start - get the the next line, and 4th line try: curr_adr = int(fixed_scope[i+1].strip().split(",")[0],16) curr_len = int(fixed_scope[i+4].strip().split(",")[0],16) curr_ind = i+4 # Save the value we may pad except: # Bad values? Bail... #print(" ----> Failed to gather values - could not verify RTC range.") rtc_range_needed = False break if last_adr is not None: # Compare our range values adjust = curr_adr - (last_adr + last_len) if adjust: # We need to increment the previous length by our adjust value rtc_range_needed = True #print(" ----> Adjusting IO range {} length to {}".format(self.hexy(last_adr,pad_to=4),self.hexy(last_len+adjust,pad_to=2))) try: hex_find,hex_repl = self.hexy(last_len,pad_to=2),self.hexy(last_len+adjust,pad_to=2) crs_lines[last_ind] = crs_lines[last_ind].replace(hex_find,hex_repl) except: #print(" ----> Failed to adjust values - could not verify RTC range.") rtc_range_needed = False break # Save our last values last_adr,last_len,last_ind = curr_adr,curr_len,curr_ind crs_lines.append(line) if rtc_range_needed: # We need to generate a rename for _CRS -> XCRS #print(" --> Generating _CRS to XCRS rename...") crs_index = self.acpi.find_next_hex(rtc_crs[0][1])[1] #print(" ----> Found at index {}".format(crs_index)) crs_hex = "5F435253" # _CRS xcrs_hex = "58435253" # XCRS padl,padr = self.acpi.get_shortest_unique_pad(crs_hex, crs_index) patches = rtc_dict.get("patches",[]) patches.append({"Comment":"{} _CRS to XCRS Rename".format(rtc_dict["dev_name"]),"Find":padl+crs_hex+padr,"Replace":padl+xcrs_hex+padr}) rtc_dict["patches"] = patches rtc_dict["crs"] = True #else: # print(" ----> Not found") # Let's see if we even need an SSDT # Not required if AWAC is not present; RTC is present, doesn't have an STAS var, and doesn't have an _STA method, and no range fixes are needed if not awac_dict.get("valid") and rtc_dict.get("valid") and not rtc_dict.get("has_var") and not rtc_dict.get("sta") and not rtc_range_needed: #print("") #print("Valid PNP0B00 (RTC) device located and qualified, and no ACPI000E (AWAC) devices found.") #print("No patching or SSDT needed.") #print("") #self.u.grab("Press [enter] to return to main menu...") return suffix = [] for x in (awac_dict,rtc_dict): if not x.get("valid"): continue val = "" if x.get("sta") and not x.get("has_var"): val = "{} _STA to XSTA".format(x["dev_name"]) if x.get("crs"): val += "{} _CRS to XCRS".format(" and " if val else x["dev_name"]) if val: suffix.append(val) #if suffix: # comment += " - Requires {} Rename".format(", ".join(suffix)) # At this point - we need to do the following: # 1. Change STAS if needed # 2. Setup _STA with _OSI and call XSTA if needed # 3. Fake RTC if needed #oc = {"Comment":comment,"Enabled":True,"Path":"SSDT-RTCAWAC.aml"} #self.make_plist(oc, "SSDT-RTCAWAC.aml", awac_dict.get("patches",[])+rtc_dict.get("patches",[]), replace=True) #print("Creating SSDT-RTCAWAC...") ssdt_name = "SSDT-RTCAWAC" ssdt = """// // Original sources from Acidanthera: // - https://github.com/acidanthera/OpenCorePkg/blob/master/Docs/AcpiSamples/SSDT-AWAC.dsl // - https://github.com/acidanthera/OpenCorePkg/blob/master/Docs/AcpiSamples/SSDT-RTC0.dsl // // Uses the ZPSS name to denote where this was created for troubleshooting purposes. // DefinitionBlock ("", "SSDT", 2, "ZPSS", "RTCAWAC", 0x00000000) { """ if any(x.get("has_var") for x in (awac_dict,rtc_dict)): ssdt += """ External (STAS, IntObj) Scope (\\) { Method (_INI, 0, NotSerialized) // _INI: Initialize { If (_OSI ("Darwin")) { Store (One, STAS) } } } """ for x in (awac_dict,rtc_dict): if not x.get("valid") or x.get("has_var") or not x.get("device"): continue # Device was found, and it doesn't have the STAS var - check if we # have an _STA (which would be renamed) macos,original = ("Zero","0x0F") if x.get("dev_hid") == "ACPI000E" else ("0x0F","Zero") if x.get("sta"): ssdt += """ External ([[DevPath]], DeviceObj) External ([[DevPath]].XSTA, [[sta_type]]) Scope ([[DevPath]]) { Name (ZSTA, [[Original]]) Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return ([[macOS]]) } // Default to [[Original]] - but return the result of the renamed XSTA if possible If (CondRefOf ([[DevPath]].XSTA)) { Store ([[DevPath]].XSTA[[called]], ZSTA) } Return (ZSTA) } } """.replace("[[DevPath]]",x["device"][0]).replace("[[Original]]",original).replace("[[macOS]]",macos).replace("[[sta_type]]",x["sta_type"]).replace("[[called]]"," ()" if x["sta_type"]=="MethodObj" else "") elif x.get("dev_hid") == "ACPI000E": # AWAC device with no STAS, and no _STA - let's just add one ssdt += """ External ([[DevPath]], DeviceObj) Scope ([[DevPath]]) { Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (Zero) } Else { Return (0x0F) } } } """.replace("[[DevPath]]",x["device"][0]) # Check if we need to setup an RTC range correction if rtc_range_needed and rtc_crs_type.lower() == "buffobj" and crs_lines and rtc_dict.get("valid"): ssdt += """ External ([[DevPath]], DeviceObj) External ([[DevPath]].XCRS, [[type]]) Scope ([[DevPath]]) { // Adjusted and renamed _CRS buffer ripped from DSDT with corrected range [[NewCRS]] // End of adjusted _CRS and renamed buffer // Create a new _CRS method that returns the result of the renamed XCRS Method (_CRS, 0, Serialized) // _CRS: Current Resource Settings { If (LOr (_OSI ("Darwin"), LNot (CondRefOf ([[DevPath]].XCRS)))) { // Return our buffer if booting macOS or the XCRS method // no longer exists for some reason Return (BUFX) } // Not macOS and XCRS exists - return its result Return ([[DevPath]].XCRS[[method]]) } } """.replace("[[DevPath]]",rtc_dict["device"][0]) \ .replace("[[type]]",rtc_crs_type) \ .replace("[[method]]"," ()" if rtc_crs_type == "Method" else "") \ .replace("[[NewCRS]]","\n".join([(" "*8)+x for x in crs_lines])) # Check if we do not have an RTC device at all if not rtc_dict.get("valid") and lpc_name: ssdt += """ External ([[LPCName]], DeviceObj) // (from opcode) Scope ([[LPCName]]) { Device (RTC0) { Name (_HID, EisaId ("PNP0B00")) // _HID: Hardware ID Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings { IO (Decode16, 0x0070, // Range Minimum 0x0070, // Range Maximum 0x01, // Alignment 0x08, // Length ) IRQNoFlags () {8} }) Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return (0) } } } } """.replace("[[LPCName]]",lpc_name) ssdt += "}" #self.write_ssdt("SSDT-RTCAWAC",ssdt) #print("") #print("Done.") # See if we just generated a failsafe - and encourage manual checking # Would require only an RTC device (no AWAC) that has an _STA with no STAS var #if rtc_dict.get("valid") and not awac_dict.get("valid") and rtc_dict.get("sta") and not rtc_dict.get("has_var") and not rtc_range_needed: # print("\n {}!! NOTE !!{} Only RTC (no AWAC) detected with an _STA method and no STAS".format(self.yel,self.rst)) # print(" variable! Patch(es) and SSDT-RTCAWAC created as a failsafe,") # print(" but verify you need them by checking the RTC._STA conditions!") #self.patch_warn() #self.u.grab("Press [enter] to return...") if self.write_ssdt(ssdt_name, ssdt): return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt), "Path": ssdt_name + ".aml" } ], "Patch": awac_dict.get("patches",[])+rtc_dict.get("patches",[]) } def fake_embedded_controller(self): ssdt_name = "SSDT-EC" laptop = "Laptop" in self.hardware_report.get("Motherboard").get("Platform") #if not self.ensure_dsdt(): # return #self.u.head("Fake EC") #print("") #print("Locating PNP0C09 (EC) devices...") # Set up a helper method to determine # if an _STA needs patching based on # the type and returns. def sta_needs_patching(sta): if not isinstance(sta,dict) or not sta.get("sta"): return False # Check if we have an IntObj or MethodObj # _STA, and scrape for values if possible. if sta.get("sta_type") == "IntObj": # We got an int - see if it's force-enabled try: sta_scope = table["lines"][sta["sta"][0][1]] if not "Name (_STA, 0x0F)" in sta_scope: return True except Exception as e: #print(e) return True elif sta.get("sta_type") == "MethodObj": # We got a method - if we have more than one # "Return (", or not a single "Return (0x0F)", # then we need to patch this out and replace try: sta_scope = "\n".join(self.acpi.get_scope(sta["sta"][0][1],strip_comments=True,table=table)) if sta_scope.count("Return (") > 1 or not "Return (0x0F)" in sta_scope: # More than one return, or our return isn't force-enabled return True except Exception as e: return True # If we got here - it's not a recognized type, or # it was fullly qualified and doesn't need patching return False rename = False named_ec = False ec_to_patch = [] ec_to_enable = [] ec_sta = {} ec_enable_sta = {} patches = [] lpc_name = None ec_located = False for table_name in self.sorted_nicely(list(self.acpi.acpi_tables)): table = self.acpi.acpi_tables[table_name] ec_list = self.acpi.get_device_paths_with_hid("PNP0C09",table=table) if len(ec_list): lpc_name = ".".join(ec_list[0][0].split(".")[:-1]) #print(" - Got {:,} in {}".format(len(ec_list),table_name)) #print(" - Validating...") for x in ec_list: device = orig_device = x[0] #print(" --> {}".format(device)) if device.split(".")[-1] == "EC": named_ec = True if not laptop: # Only rename if we're trying to replace it #print(" ----> PNP0C09 (EC) called EC. Renaming") device = ".".join(device.split(".")[:-1]+["EC0"]) rename = True scope = "\n".join(self.acpi.get_scope(x[1],strip_comments=True,table=table)) # We need to check for _HID, _CRS, and _GPE if all(y in scope for y in ["_HID","_CRS","_GPE"]): #print(" ----> Valid PNP0C09 (EC) Device") ec_located = True sta = self.get_sta_var( var=None, device=orig_device, dev_hid="PNP0C09", dev_name=orig_device.split(".")[-1], log_locate=False, table=table ) if not laptop: ec_to_patch.append(device) # Only unconditionally override _STA methods # if not building for a laptop if sta.get("patches"): patches.extend(sta.get("patches",[])) ec_sta[device] = sta elif sta.get("patches"): if sta_needs_patching(sta): # Retain the info as we need to override it ec_to_enable.append(device) ec_enable_sta[device] = sta # Disable the patches by default and add to the list for patch in sta.get("patches",[]): patch["Enabled"] = False patch["Disabled"] = True patches.append(patch) #else: # print(" --> _STA properly enabled - skipping rename") #else: # print(" ----> NOT Valid PNP0C09 (EC) Device") #if not ec_located: #print(" - No valid PNP0C09 (EC) devices found - only needs a Fake EC device") if laptop and named_ec and not patches: #print(" ----> Named EC device located - no fake needed.") #print("") #self.u.grab("Press [enter] to return to main menu...") return if lpc_name is None: lpc_name = self.get_lpc_name(skip_ec=True,skip_common_names=True) if lpc_name is None: #self.u.grab("Press [enter] to return to main menu...") return #comment = "Faked Embedded Controller" if rename == True: patches.insert(0,{ "Comment":"EC to EC0{}".format("" if not ec_sta else " - must come before any EC _STA to XSTA renames!"), "Find":"45435f5f", "Replace":"4543305f" }) # comment += " - Needs EC to EC0 {}".format( # "and EC _STA to XSTA renames" if ec_sta else "rename" # ) #elif ec_sta: # comment += " - Needs EC _STA to XSTA renames" #oc = {"Comment":comment,"Enabled":True,"Path":"SSDT-EC.aml"} #self.make_plist(oc, "SSDT-EC.aml", patches, replace=True) #print("Creating SSDT-EC...") ssdt = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "EC", 0x00001000) { External ([[LPCName]], DeviceObj) """.replace("[[LPCName]]",lpc_name) for x in ec_to_patch: ssdt += " External ({}, DeviceObj)\n".format(x) if x in ec_sta: ssdt += " External ({}.XSTA, {})\n".format(x,ec_sta[x].get("sta_type","MethodObj")) # Walk the ECs to enable for x in ec_to_enable: ssdt += " External ({}, DeviceObj)\n".format(x) if x in ec_enable_sta: # Add the _STA and XSTA refs as the patch may not be enabled ssdt += " External ({0}._STA, {1})\n External ({0}.XSTA, {1})\n".format(x,ec_enable_sta[x].get("sta_type","MethodObj")) # Walk them again and add the _STAs for x in ec_to_patch: ssdt += """ Scope ([[ECName]]) { Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0) } Else { Return ([[XSTA]]) } } } """.replace("[[LPCName]]",lpc_name).replace("[[ECName]]",x) \ .replace("[[XSTA]]","{}.XSTA{}".format(x," ()" if ec_sta[x].get("sta_type","MethodObj")=="MethodObj" else "") if x in ec_sta else "0x0F") # Walk them yet again - and force enable as needed for x in ec_to_enable: ssdt += """ If (LAnd (CondRefOf ([[ECName]].XSTA), LNot (CondRefOf ([[ECName]]._STA)))) { Scope ([[ECName]]) { Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return ([[XSTA]]) } } } } """.replace("[[LPCName]]",lpc_name).replace("[[ECName]]",x) \ .replace("[[XSTA]]","{}.XSTA{}".format(x," ()" if ec_enable_sta[x].get("sta_type","MethodObj")=="MethodObj" else "") if x in ec_enable_sta else "Zero") # Create the faked EC if not laptop or not named_ec: ssdt += """ Scope ([[LPCName]]) { Device (EC) { Name (_HID, "ACID0001") // _HID: Hardware ID Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return (Zero) } } } }""".replace("[[LPCName]]",lpc_name) # Close the SSDT scope ssdt += """ }""" #self.write_ssdt("SSDT-EC",ssdt) #print("") #print("Done.") #self.patch_warn() #self.u.grab("Press [enter] to return...") return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt), "Path": ssdt_name + ".aml" } ], "Patch": patches } def get_data(self, data, pad_to=0): if sys.version_info >= (3, 0): if not isinstance(data,bytes): data = data.encode() return data+b"\x00"*(max(pad_to-len(data),0)) else: return plistlib.Data(data+b"\x00"*(max(pad_to-len(data),0))) def write_ssdt(self, ssdt_name, ssdt_content, compile=True): dsl_path = os.path.join(self.acpi_directory, ssdt_name + ".dsl") aml_path = os.path.join(self.acpi_directory, ssdt_name + ".aml") if not os.path.exists(self.acpi_directory): os.makedirs(self.acpi_directory) with open(dsl_path,"w") as f: f.write(ssdt_content) if not compile: return False output = self.run({ "args":[self.acpi.iasl, dsl_path] }) if output[-1] != 0: return False else: os.remove(dsl_path) return os.path.exists(aml_path) def apply_acpi_patches(self, acpi_patches): acpi_patches = [ { "Base": acpi_patch.get("Base", ""), "BaseSkip": acpi_patch.get("BaseSkip", 0), "Comment": acpi_patch.get("Comment", ""), "Count": acpi_patch.get("Count", 0), "Enabled": True, "Find": self.utils.hex_to_bytes(acpi_patch["Find"]), "Limit": acpi_patch.get("Limit", 0), "Mask": self.utils.hex_to_bytes(acpi_patch.get("Mask", "")), "OemTableId": self.utils.hex_to_bytes(acpi_patch.get("OemTableId", "")), "Replace": self.utils.hex_to_bytes(acpi_patch["Replace"]), "ReplaceMask": self.utils.hex_to_bytes(acpi_patch.get("ReplaceMask", "")), "Skip": acpi_patch.get("Skip", 0), "TableLength": acpi_patch.get("TableLength", 0), "TableSignature": self.utils.hex_to_bytes(acpi_patch.get("TableSignature", "")), } for acpi_patch in acpi_patches ] return sorted(acpi_patches, key=lambda x: x["Comment"]) def add_intel_management_engine(self): ssdt_name = "SSDT-IMEI" ssdt_content = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "IMEI", 0x00000000) { External (_SB_.PCI0, DeviceObj) Scope (_SB.PCI0) { Device (IMEI) { Name (_ADR, 0x00160000) // _ADR: Address Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return (Zero) } } } } }""" imei_device = self.acpi.get_device_paths_with_hid("0x00160000", self.dsdt) if not imei_device: return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ] } def add_memory_controller_device(self): if not self.lpc_bus_device: return ssdt_name = "SSDT-MCHC" ssdt_content = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "MCHC", 0) { External ([[PCIName]], DeviceObj) Scope ([[PCIName]]) { Device (MCHC) { Name (_ADR, Zero) Method (_STA, 0, NotSerialized) { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return (Zero) } } } } }""" mchc_device = self.acpi.get_device_paths("MCHC", self.dsdt) if mchc_device: return pci_bus_device = ".".join(self.lpc_bus_device.split(".")[:2]) ssdt_content = ssdt_content.replace("[[PCIName]]", pci_bus_device) return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ] } def add_system_management_bus_device(self): if not self.lpc_bus_device: return try: smbus_device_name = self.acpi.get_device_paths_with_hid("0x001F0003" if self.hardware_report.get("CPU").get("Codename") in cpu_data.IntelCPUGenerations[50:] else "0x001F0004", self.dsdt)[0][0].split(".")[-1] except: smbus_device_name = "SBUS" pci_bus_device = ".".join(self.lpc_bus_device.split(".")[:2]) smbus_device_path = "{}.{}".format(pci_bus_device, smbus_device_name) ssdt_name = "SSDT-{}".format(smbus_device_name) ssdt_content = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "[[SMBUSName]]", 0) { External ([[SMBUSDevice]], DeviceObj) Scope ([[SMBUSDevice]]) { Device (BUS0) { Name (_CID, "smbus") Name (_ADR, Zero) Method (_STA, 0, NotSerialized) { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return (Zero) } } } } }""".replace("[[SMBUSName]]", smbus_device_name).replace("[[SMBUSDevice]]", smbus_device_path) return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ] } def add_usb_power_properties(self): ssdt_name = "SSDT-USBX" ssdt_content = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "USBX", 0x00001000) { Scope (\\_SB) { Device (USBX) { Name (_ADR, Zero) // _ADR: Address Method (_DSM, 4, NotSerialized) // _DSM: Device-Specific Method { If (LNot (Arg2)) { Return (Buffer () { 0x03 }) } Return (Package () {[[USBX_PROPS]] }) } Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return (Zero) } } } } }""" usb_power_properties = None if self.utils.contains_any(["MacPro7,1", "iMacPro1,1", "iMac20,", "iMac19,", "iMac18,", "iMac17,", "iMac16,"], self.smbios_model): usb_power_properties = { "kUSBSleepPowerSupply":"0x13EC", "kUSBSleepPortCurrentLimit":"0x0834", "kUSBWakePowerSupply":"0x13EC", "kUSBWakePortCurrentLimit":"0x0834" } elif "MacMini8,1" in self.smbios_model: usb_power_properties = { "kUSBSleepPowerSupply":"0x0C80", "kUSBSleepPortCurrentLimit":"0x0834", "kUSBWakePowerSupply":"0x0C80", "kUSBWakePortCurrentLimit":"0x0834" } elif self.utils.contains_any(["MacBookPro16,", "MacBookPro15,", "MacBookPro14,", "MacBookPro13,", "MacBookAir9,1"], self.smbios_model): usb_power_properties = { "kUSBSleepPortCurrentLimit":"0x0BB8", "kUSBWakePortCurrentLimit":"0x0BB8" } elif "MacBook9,1" in self.smbios_model: usb_power_properties = { "kUSBSleepPowerSupply":"0x05DC", "kUSBSleepPortCurrentLimit":"0x05DC", "kUSBWakePowerSupply":"0x05DC", "kUSBWakePortCurrentLimit":"0x05DC" } if usb_power_properties: ssdt_content = ssdt_content.replace("[[USBX_PROPS]]", ",".join("\n \"{}\",\n {}".format(key, usb_power_properties[key]) for key in usb_power_properties)) return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ] } def ambient_light_sensor(self): ssdt_name = "SSDT-ALS0" ssdt_content = """ // Resource: https://github.com/acidanthera/OpenCorePkg/blob/master/Docs/AcpiSamples/Source/SSDT-ALS0.dsl /* * Starting with macOS 10.15 Ambient Light Sensor presence is required for backlight functioning. * Here we create an Ambient Light Sensor ACPI Device, which can be used by SMCLightSensor kext * to report either dummy (when no device is present) or valid values through SMC interface. */ DefinitionBlock ("", "SSDT", 2, "ZPSS", "ALS0", 0x00000000) { Scope (_SB) { Device (ALS0) { Name (_HID, "ACPI0008" /* Ambient Light Sensor Device */) // _HID: Hardware ID Name (_CID, "smc-als") // _CID: Compatible ID Name (_ALI, 0x012C) // _ALI: Ambient Light Illuminance Name (_ALR, Package (0x01) // _ALR: Ambient Light Response { Package (0x02) { 0x64, 0x012C } }) Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return (Zero) } } } } }""" try: als_device = self.acpi.get_device_paths_with_hid("ACPI0008", self.dsdt)[0][0] except: als_device = None patches = [] if als_device: als_device_name = als_device.split(".")[-1] if "." not in als_device: als_device_name = als_device_name[1:] sta = self.get_sta_var(var=None, device=None, dev_hid="ACPI0008", dev_name=als_device_name, table=self.dsdt) patches.extend(sta.get("patches", [])) ssdt_name = "SSDT-{}".format(als_device_name) ssdt_content = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "[[ALSName]]", 0x00000000) { External ([[ALSDevice]], DeviceObj) External ([[ALSDevice]].XSTA, [[STAType]]) Scope ([[ALSDevice]]) { Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return ([[XSTA]]) } } } }""".replace("[[ALSName]]", als_device_name) \ .replace("[[ALSDevice]]", als_device) \ .replace("[[STAType]]", sta.get("sta_type","MethodObj")) \ .replace("[[XSTA]]", "{}.XSTA{}".format(als_device," ()" if sta.get("sta_type","MethodObj") == "MethodObj" else "") if sta else "0x0F") return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ], "Patch": patches } def findall_power_resource_blocks(self, table_lines): power_resource_blocks = [] i = 0 while i < len(table_lines): line = table_lines[i].strip() if line.startswith("PowerResource"): start_index = i open_brackets = 1 i += 1 while i < len(table_lines) and open_brackets > 0: if '{' in table_lines[i]: open_brackets += table_lines[i].count('{') if '}' in table_lines[i]: open_brackets -= table_lines[i].count('}') i += 1 end_index = i - 1 power_resource_blocks.append((start_index, end_index)) else: i += 1 return power_resource_blocks def is_method_in_power_resource(self, method, table_lines): power_resource_blocks = self.findall_power_resource_blocks(table_lines) for start, end in power_resource_blocks: if start <= method[1] <= end: return True return False def disable_unsupported_device(self): results = { "Add": [] } for device_name, device_props in self.disabled_devices.items(): if not device_props.get("Bus Type", "PCI") == "PCI" or not device_props.get("ACPI Path"): continue ssdt_name = None if "GPU" in device_name and device_props.get("Device Type") != "Integrated GPU": ssdt_name = "SSDT-Disable_GPU_{}".format(device_props.get("ACPI Path").split(".")[2]) target_device = device_props.get("ACPI Path") off_method_found = ps3_method_found = False for table_name, table_data in self.acpi.acpi_tables.items(): off_methods = self.acpi.get_method_paths("_OFF", table_data) ps3_methods = self.acpi.get_method_paths("_PS3", table_data) off_method_found = off_method_found or any(method[0].startswith(target_device) and not self.is_method_in_power_resource(method, table_data.get("lines")) for method in off_methods) ps3_method_found = ps3_method_found or any(method[0].startswith(target_device) for method in ps3_methods) if not off_method_found and not ps3_method_found: continue if off_method_found: ps3_method_found = False device_props["Disabled"] = True ssdt_content = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "DGPU", 0x00000000) {""" if off_method_found: ssdt_content += """ External ([[DevicePath]]._OFF, MethodObj) External ([[DevicePath]]._ON_, MethodObj)""" if ps3_method_found: ssdt_content += """ External ([[DevicePath]]._PS0, MethodObj) External ([[DevicePath]]._PS3, MethodObj) External ([[DevicePath]]._DSM, MethodObj) """ ssdt_content += """ Device (DGPU) { Name (_HID, "DGPU1000") Method (_INI, 0, NotSerialized) { _OFF () } Method (_STA, 0, NotSerialized) { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return (Zero) } } Method (_ON, 0, NotSerialized) { """ if off_method_found: ssdt_content += """ [[DevicePath]]._ON () """ if ps3_method_found: ssdt_content += """ [[DevicePath]]._PS0 () """ ssdt_content += """ } Method (_OFF, 0, NotSerialized) { """ if off_method_found: ssdt_content += """ [[DevicePath]]._OFF () """ if ps3_method_found: ssdt_content += """ [[DevicePath]]._DSM (ToUUID ("a486d8f8-0bda-471b-a72b-6042a6b5bee0") /* Unknown UUID */, 0x0100, 0x1A, Buffer (0x04) { 0x01, 0x00, 0x00, 0x03 // .... }) [[DevicePath]]._PS3 () """ ssdt_content += """\n }\n }\n}""" elif "Network" in device_name and device_props.get("Bus Type") == "PCI": ssdt_name = "SSDT-Disable_Network_{}".format(device_props.get("ACPI Path").split(".")[2]) ssdt_content = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "DNET", 0x00000000) { External ([[DevicePath]], DeviceObj) Method ([[DevicePath]]._DSM, 4, NotSerialized) // _DSM: Device-Specific Method { If ((!Arg2 || (_OSI ("Darwin") == Zero))) { Return (Buffer (One) { 0x03 // . }) } Return (Package (0x0A) { "name", Buffer (0x09) { "#network" }, "IOName", "#display", "class-code", Buffer (0x04) { 0xFF, 0xFF, 0xFF, 0xFF // .... }, "vendor-id", Buffer (0x04) { 0xFF, 0xFF, 0x00, 0x00 // .... }, "device-id", Buffer (0x04) { 0xFF, 0xFF, 0x00, 0x00 // .... } }) } } """ elif "Storage" in device_name: ssdt_name = "SSDT-Disable_NVMe_{}".format(device_props.get("ACPI Path").split(".")[-2]) ssdt_content = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "DNVMe", 0x00000000) { External ([[DevicePath]], DeviceObj) Method ([[DevicePath]]._DSM, 4, NotSerialized) // _DSM: Device-Specific Method { If (_OSI ("Darwin")) { If (!Arg2) { Return (Buffer (One) { 0x03 // . }) } Return (Package (0x02) { "class-code", Buffer (0x04) { 0xFF, 0x08, 0x01, 0x00 // .... } }) } } } """ if ssdt_name: ssdt_content = ssdt_content.replace("[[DevicePath]]", device_props.get("ACPI Path")) results["Add"].append( { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ) return results def enable_backlight_controls(self): patches = [] integrated_gpu = list(self.hardware_report.get("GPU").items())[-1][-1] uid_value = 19 if integrated_gpu.get("Codename") in ("Iron Lake", "Sandy Bridge", "Ivy Bridge"): uid_value = 14 elif integrated_gpu.get("Codename") in ("Haswell", "Broadwell"): uid_value = 15 elif integrated_gpu.get("Codename") in ("Skylake", "Kaby Lake"): uid_value = 16 if "PNLF" in self.dsdt.get("table"): patches.append({ "Comment": "PNLF to XNLF Rename", "Find": "504E4C46", "Replace": "584E4C46" }) for table_name in self.sorted_nicely(list(self.acpi.acpi_tables)): table = self.acpi.acpi_tables[table_name] if binascii.unhexlify("084E4243460A00") in table.get("raw"): patches.append({ "Comment": "NBCF 0x00 to 0x01", "Find": "084E4243460A00", "Replace": "084E4243460A01" }) break elif binascii.unhexlify("084E42434600") in table.get("raw"): patches.append({ "Comment": "NBCF Zero to One", "Find": "084E42434600", "Replace": "084E42434601" }) break ssdt_name = "SSDT-PNLF" ssdt_content = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "PNLF", 0x00000000) {""" if integrated_gpu.get("ACPI Path"): ssdt_content += """\n External ([[DevicePath]], DeviceObj)\n Device ([[DevicePath]].PNLF)""" else: ssdt_content += """\n Device (PNLF)""" ssdt_content += """ { Name (_HID, EisaId ("APP0002")) // _HID: Hardware ID Name (_CID, "backlight") // _CID: Compatible ID Name (_UID, [[uid_value]]) // _UID: Unique ID Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0B) } Else { Return (Zero) } }""" if integrated_gpu.get("ACPI Path") and uid_value == 14: ssdt_content += """ Method (_INI, 0, Serialized) { If (_OSI ("Darwin")) { OperationRegion ([[DevicePath]].RMP3, PCI_Config, Zero, 0x14) Field ([[DevicePath]].RMP3, AnyAcc, NoLock, Preserve) { Offset (0x02), GDID,16, Offset (0x10), BAR1,32, } // IGPU PWM backlight register descriptions: // LEV2 not currently used // LEVL level of backlight in Sandy/Ivy // P0BL counter, when zero is vertical blank // GRAN see description below in INI1 method // LEVW should be initialized to 0xC0000000 // LEVX PWMMax except FBTYPE_HSWPLUS combo of max/level (Sandy/Ivy stored in MSW) // LEVD level of backlight for Coffeelake // PCHL not currently used OperationRegion (RMB1, SystemMemory, BAR1 & ~0xF, 0xe1184) Field(RMB1, AnyAcc, Lock, Preserve) { Offset (0x48250), LEV2, 32, LEVL, 32, Offset (0x70040), P0BL, 32, Offset (0xc2000), GRAN, 32, Offset (0xc8250), LEVW, 32, LEVX, 32, LEVD, 32, Offset (0xe1180), PCHL, 32, } // Now fixup the backlight PWM depending on the framebuffer type // At this point: // Local4 is RMCF.BLKT value (unused here), if specified (default is 1) // Local0 is device-id for IGPU // Local2 is LMAX, if specified (Ones means based on device-id) // Local3 is framebuffer type // Adjustment required when using WhateverGreen.kext Local0 = GDID Local2 = Ones Local3 = 0 // check Sandy/Ivy // #define FBTYPE_SANDYIVY 1 If (LOr (LEqual (1, Local3), LNotEqual (Match (Package() { // Sandy HD3000 0x010b, 0x0102, 0x0106, 0x1106, 0x1601, 0x0116, 0x0126, 0x0112, 0x0122, // Ivy 0x0152, 0x0156, 0x0162, 0x0166, 0x016a, // Arrandale 0x0046, 0x0042, }, MEQ, Local0, MTR, 0, 0), Ones))) { if (LEqual (Local2, Ones)) { // #define SANDYIVY_PWMMAX 0x710 Store (0x710, Local2) } // change/scale only if different than current... Store (LEVX >> 16, Local1) If (LNot (Local1)) { Store (Local2, Local1) } If (LNotEqual (Local2, Local1)) { // set new backlight PWMMax but retain current backlight level by scaling Store ((LEVL * Local2) / Local1, Local0) Store (Local2 << 16, Local3) If (LGreater (Local2, Local1)) { // PWMMax is getting larger... store new PWMMax first Store (Local3, LEVX) Store (Local0, LEVL) } Else { // otherwise, store new brightness level, followed by new PWMMax Store (Local0, LEVL) Store (Local3, LEVX) } } } } }""" ssdt_content += """ } }""" ssdt_content = ssdt_content.replace("[[uid_value]]", str(uid_value)) if integrated_gpu.get("ACPI Path"): ssdt_content = ssdt_content.replace("[[DevicePath]]", integrated_gpu.get("ACPI Path")) return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ], "Patch": patches } def enable_gpio_device(self): try: gpio_device = self.acpi.get_device_paths("GPI0", self.dsdt)[0][0] or self.acpi.get_device_paths("GPIO", self.dsdt)[0][0] except: return sta = self.get_sta_var(var=None, device=gpio_device, dev_hid=None, dev_name=gpio_device.split(".")[-1], table=self.dsdt) ssdt_name = "SSDT-GPI0" ssdt_content = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "GPI0", 0x00000000) { External ([[GPI0Path]], DeviceObj) External ([[GPI0Path]].XSTA, [[STAType]]) Scope ([[GPI0Path]]) { Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return ([[XSTA]]) } } } }""".replace("[[GPI0Path]]", gpio_device) \ .replace("[[STAType]]", sta.get("sta_type","MethodObj")) \ .replace("[[XSTA]]", "{}.XSTA{}".format(gpio_device," ()" if sta.get("sta_type","MethodObj") == "MethodObj" else "") if sta else "0x0F") return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ], "Patch": sta.get("patches", []) } def enable_nvram_support(self): if not self.lpc_bus_device: return ssdt_name = "SSDT-PMC" ssdt_content = """ // Resource: https://github.com/acidanthera/OpenCorePkg/blob/master/Docs/AcpiSamples/Source/SSDT-PMC.dsl /* * Intel 300-series PMC support for macOS * * Starting from Z390 chipsets PMC (D31:F2) is only available through MMIO. * Since there is no standard device for PMC in ACPI, Apple introduced its * own naming "APP9876" to access this device from AppleIntelPCHPMC driver. * To avoid confusion we disable this device for all other operating systems, * as they normally use another non-standard device with "PNP0C02" HID and * "PCHRESV" UID. * * On certain implementations, including APTIO V, PMC initialisation is * required for NVRAM access. Otherwise it will freeze in SMM mode. * The reason for this is rather unclear. Note, that PMC and SPI are * located in separate memory regions and PCHRESV maps both, yet only * PMC region is used by AppleIntelPCHPMC: * 0xFE000000~0xFE00FFFF - PMC MBAR * 0xFE010000~0xFE010FFF - SPI BAR0 * 0xFE020000~0xFE035FFF - SerialIo BAR in ACPI mode * * PMC device has nothing to do to LPC bus, but is added to its scope for * faster initialisation. If we add it to PCI0, where it normally exists, * it will start in the end of PCI configuration, which is too late for * NVRAM support. */ DefinitionBlock ("", "SSDT", 2, "ACDT", "PMCR", 0x00001000) { External ([[LPCPath]], DeviceObj) Scope ([[LPCPath]]) { Device (PMCR) { Name (_HID, EisaId ("APP9876")) // _HID: Hardware ID Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0B) } Else { Return (Zero) } } Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings { Memory32Fixed (ReadWrite, 0xFE000000, // Address Base 0x00010000, // Address Length ) }) } } }""".replace("[[LPCPath]]", self.lpc_bus_device) return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ], } def remove_conditional_scope(self): return { "Patch": [ { "Comment": "Remove conditional ACPI scope declaration", "Find": "A000000092935043484100", "Replace": "A3A3A3A3A3A3A3A3A3A3A3", "Mask": "FF000000FFFFFFFFFFFFFF", "Count": 1, "TableSignature": "44534454" } ] } def fix_hp_005_post_error(self): if binascii.unhexlify("4701700070000108") in self.dsdt.get("raw"): return { "Patch": [ { "Comment": "Fix HP Real-Time Clock Power Loss (005) Post Error", "Find": "4701700070000108", "Replace": "4701700070000102" } ] } def add_null_ethernet_device(self): random_mac_address = self.smbios.generate_random_mac() mac_address_byte = ", ".join([f'0x{random_mac_address[i:i+2]}' for i in range(0, len(random_mac_address), 2)]) ssdt_name = "SSDT-RMNE" ssdt_content = """ // Resource: https://github.com/RehabMan/OS-X-Null-Ethernet/blob/master/SSDT-RMNE.dsl /* ssdt.dsl -- SSDT injector for NullEthernet * * Copyright (c) 2014 RehabMan * All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * */ // Use this SSDT as an alternative to patching your DSDT... DefinitionBlock("", "SSDT", 2, "ZPSS", "RMNE", 0x00001000) { Device (RMNE) { Name (_ADR, Zero) // The NullEthernet kext matches on this HID Name (_HID, "NULE0000") // This is the MAC address returned by the kext. Modify if necessary. Name (MAC, Buffer() { [[MACAddress]] }) Method (_DSM, 4, NotSerialized) { If (LEqual (Arg2, Zero)) { Return (Buffer() { 0x03 } ) } Return (Package() { "built-in", Buffer() { 0x00 }, "IOName", "ethernet", "name", Buffer() { "ethernet" }, "model", Buffer() { "RM-NullEthernet-1001" }, "device_type", Buffer() { "ethernet" }, }) } Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return (Zero) } } } }""".replace("[[MACAddress]]", mac_address_byte) return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ] } def is_intel_hedt_cpu(self, processor_name, cpu_codename): if cpu_codename in cpu_data.IntelCPUGenerations[45:66]: return cpu_codename.endswith(("-X", "-P", "-W", "-E", "-EP", "-EX")) if cpu_codename in cpu_data.IntelCPUGenerations[66:]: return "Xeon" in processor_name return False def fix_system_clock_hedt(self): awac_device = self.acpi.get_device_paths_with_hid("ACPI000E", self.dsdt) try: rtc_device = self.acpi.get_device_paths_with_hid("PNP0B00", self.dsdt)[0][0] if rtc_device.endswith("RTC"): rtc_device += "_" except: if not self.lpc_bus_device: return rtc_device = self.lpc_bus_device + ".RTC0" new_rtc_device = ".".join(rtc_device.split(".")[:-1] + [self.get_unique_device(rtc_device, rtc_device.split(".")[-1])[0]]) patches = [] ssdt_name = "SSDT-RTC0-RANGE" ssdt_content = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "RtcRange", 0x00000000) {""" if not awac_device: sta = self.get_sta_var(var=None, device=rtc_device, dev_hid=None, dev_name=rtc_device.split(".")[-1], table=self.dsdt) patches.extend(sta.get("patches", [])) ssdt_content += """ External ([[device_path]], DeviceObj) Scope ([[device_path]]) { Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (Zero) } Else { Return (0x0F) } } }""".replace("[[device_path]]", rtc_device) ssdt_content += """ External ([[parent_path]], DeviceObj) Device ([[device_path]]) { Name (_HID, EisaId ("PNP0B00") /* AT Real-Time Clock */) // _HID: Hardware ID Name (_CRS, ResourceTemplate () // _CRS: Current Resource Settings { IO (Decode16, 0x0070, // Range Minimum 0x0070, // Range Maximum 0x01, // Alignment 0x04, // Length ) IO (Decode16, 0x0074, // Range Minimum 0x0074, // Range Maximum 0x01, // Alignment 0x04, // Length ) IRQNoFlags () {8} }) Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (0x0F) } Else { Return (Zero) } } } }""".replace("[[parent_path]]", ".".join(rtc_device.split(".")[:-1])).replace("[[device_path]]", new_rtc_device) return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ], "Patch": patches } def instant_wake_fix(self): ssdt_name = "SSDT-PRW" uswe_object = "9355535745" wole_object = "93574F4C45" gprw_method = "4750525702" uprw_method = "5550525702" xprw_method = "5850525702" patches = [] if binascii.unhexlify(gprw_method) in self.dsdt.get("raw"): patches.append({ "Comment": "GPRW to XPRW Rename", "Find": gprw_method, "Replace": xprw_method }) else: gprw_method = None if binascii.unhexlify(uprw_method) in self.dsdt.get("raw"): patches.append({ "Comment": "UPRW to XPRW Rename", "Find": uprw_method, "Replace": xprw_method }) else: uprw_method = None if not binascii.unhexlify(uswe_object) in self.dsdt.get("raw"): uswe_object = None if not binascii.unhexlify(wole_object) in self.dsdt.get("raw"): wole_object = None ssdt_content = """ // Resource: https://github.com/5T33Z0/OC-Little-Translated/blob/main/04_Fixing_Sleep_and_Wake_Issues/060D_Instant_Wake_Fix/README.md DefinitionBlock ("", "SSDT", 2, "ZPSS", "_PRW", 0x00000000) {""" if gprw_method or uprw_method: ssdt_content += """\n External(XPRW, MethodObj)""" if uswe_object: ssdt_content += "\n External (USWE, FieldUnitObj)" if wole_object: ssdt_content += "\n External (WOLE, FieldUnitObj)" if uswe_object or wole_object: ssdt_content += """\n Scope (\\) { If (_OSI ("Darwin")) {""" if uswe_object: ssdt_content += "\n USWE = Zero" if wole_object: ssdt_content += "\n WOLE = Zero" ssdt_content += """ } }""" if gprw_method: ssdt_content += """ Method (GPRW, 2, NotSerialized) { If (_OSI ("Darwin")) { If ((0x6D == Arg0)) { Return (Package () { 0x6D, Zero }) } If ((0x0D == Arg0)) { Return (Package () { 0x0D, Zero }) } } Return (XPRW (Arg0, Arg1)) }""" if uprw_method: ssdt_content += """ Method (UPRW, 2, NotSerialized) { If (_OSI ("Darwin")) { If ((0x6D == Arg0)) { Return (Package () { 0x6D, Zero }) } If ((0x0D == Arg0)) { Return (Package () { 0x0D, Zero }) } } Return (XPRW (Arg0, Arg1)) }""" ssdt_content += "\n}" if gprw_method or uprw_method or uswe_object or wole_object: return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ], "Patch": patches } def fix_uncore_bridge(self): unc0_device = self.acpi.get_device_paths("UNC0", self.dsdt) if not unc0_device: return ssdt_name = "SSDT-UNC" ssdt_content = """ // Resource: https://github.com/acidanthera/OpenCorePkg/blob/master/Docs/AcpiSamples/Source/SSDT-UNC.dsl /* * Discovered on X99-series. * These platforms have uncore PCI bridges for 4 CPU sockets * present in ACPI despite having none physically. * * Under normal conditions these are disabled depending on * CPU presence in the socket via Processor Bit Mask (PRBM), * but on X99 this code is unused or broken as such bridges * simply do not exist. We fix that by writing 0 to PRBM. * * Doing so is important as starting with macOS 11 IOPCIFamily * will crash as soon as it sees non-existent PCI bridges. */ DefinitionBlock ("", "SSDT", 2, "ZPSS", "UNC", 0x00000000) { External (_SB.UNC0, DeviceObj) External (PRBM, IntObj) Scope (_SB.UNC0) { Method (_INI, 0, NotSerialized) { // In most cases this patch does benefit all operating systems, // yet on select pre-Windows 10 it may cause issues. // Remove If (_OSI ("Darwin")) in case you have none. If (_OSI ("Darwin")) { PRBM = 0 } } } }""" return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ] } def operating_system_patch(self): ssdt_name = "SSDT-XOSI" ssdt_content = """ // Resource: https://github.com/dortania/Getting-Started-With-ACPI/blob/master/extra-files/decompiled/SSDT-XOSI.dsl DefinitionBlock ("", "SSDT", 2, "ZPSS", "XOSI", 0x00001000) { Method (XOSI, 1, NotSerialized) { // Based off of: // https://docs.microsoft.com/en-us/windows-hardware/drivers/acpi/winacpi-osi#_osi-strings-for-windows-operating-systems // Add OSes from the below list as needed, most only check up to Windows 2015 // but check what your DSDT looks for Store (Package () { [[OSIStrings]] }, Local0) If (_OSI ("Darwin")) { Return (LNotEqual (Match (Local0, MEQ, Arg0, MTR, Zero, Zero), Ones)) } Else { Return (_OSI (Arg0)) } } }""".replace("[[OSIStrings]]", "\n,".join([" \"{}\"".format(osi_string) for target_os, osi_string in self.osi_strings.items() if osi_string in self.dsdt.get("table")])) patches = [] osid = self.acpi.get_method_paths("OSID", self.dsdt) if osid: patches.append({ "Comment": "OSID to XSID rename - must come before _OSI to XOSI rename!", "Find": "4F534944", "Replace": "58534944" }) osif = self.acpi.get_method_paths("OSIF", self.dsdt) if osif: patches.append({ "Comment": "OSIF to XSIF rename - must come before _OSI to XOSI rename!", "Find": "4F534946", "Replace": "58534946" }) patches.append({ "Comment": "_OSI to XOSI rename - requires SSDT-XOSI.aml", "Find": "5F4F5349", "Replace": "584F5349" }) return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ], "Patch": patches } def surface_laptop_special_patch(self): ssdt_name = "SSDT-SURFACE" ssdt_content = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "SURFACE", 0x00001000) { External (_SB_.PCI0, DeviceObj) External (GPRW, MethodObj) // 2 Arguments If (_OSI ("Darwin")) { Scope (_SB) { Device (ALS0) { Name (_HID, "ACPI0008" /* Ambient Light Sensor Device */) // _HID: Hardware ID Name (_CID, "smc-als") // _CID: Compatible ID Name (_ALI, 0x012C) // _ALI: Ambient Light Illuminance Name (_ALR, Package (0x05) // _ALR: Ambient Light Response { Package (0x02) { 0x46, Zero }, Package (0x02) { 0x49, 0x0A }, Package (0x02) { 0x55, 0x50 }, Package (0x02) { 0x64, 0x012C }, Package (0x02) { 0x96, 0x03E8 } }) Method (XALI, 1, Serialized) { _ALI = Arg0 } } Device (ADP0) { Name (_HID, "ACPI0003" /* Power Source Device */) // _HID: Hardware ID Name (SPSR, Zero) Method (_PRW, 0, NotSerialized) // _PRW: Power Resources for Wake { Return (GPRW (0x6D, 0x04)) } Method (_STA, 0, NotSerialized) // _STA: Status { Return (0x0F) } Method (XPSR, 1, Serialized) { If ((Arg0 == Zero)) { SPSR = Zero } ElseIf ((Arg0 == One)) { SPSR = One } Notify (ADP0, 0x80) // Status Change } Method (_PSR, 0, Serialized) // _PSR: Power Source { Return (SPSR) /* \\_SB_.ADP0.SPSR */ } Method (_PCL, 0, NotSerialized) // _PCL: Power Consumer List { Return (\\_SB) } } Device (BAT0) { Name (_HID, EisaId ("PNP0C0A") /* Control Method Battery */) // _HID: Hardware ID Name (_UID, Zero) // _UID: Unique ID Name (_PCL, Package (0x01) // _PCL: Power Consumer List { _SB }) Method (_STA, 0, NotSerialized) // _STA: Status { Return (0x1F) } } } Scope (_SB.PCI0) { Device (IPTS) { Name (_ADR, 0x00160004) // _ADR: Address } } } } """ return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ] } def find_line_start(self, text, index): current_idx = index while current_idx > 0: if text[current_idx] == '\n': return current_idx + 1 current_idx -= 1 return 0 def extract_line(self, text, index): start_idx = self.find_line_start(text, index) end_idx = text.index("\n", start_idx) + 1 if "\n" in text[start_idx:] else len(text) return text[start_idx:end_idx].strip(), start_idx, end_idx def extract_block_content(self, text, start_idx): try: block_start = text.index("{", start_idx) brace_count = 1 pos = block_start + 1 while brace_count > 0 and pos < len(text): if text[pos] == '{': brace_count += 1 elif text[pos] == '}': brace_count -= 1 pos += 1 if brace_count == 0: return text[block_start:pos] except ValueError as e: pass return "" def parse_field_line(self, line): try: if "//" in line: line = line.split("//")[0].strip() parts = line.split(",") if len(parts) >= 2: field_name = parts[0].strip() size_part = parts[1].strip() try: field_size = int(size_part) except ValueError: return None, None return field_name, field_size except (ValueError, IndexError) as e: pass return None, None def process_embedded_control_region(self, table, start_idx): try: embed_control_idx = table.index("EmbeddedControl", start_idx) line, start_line_idx, end_line_idx = self.extract_line(table, embed_control_idx) region_name = line.split("(")[1].split(",")[0].strip() return region_name, end_line_idx except (ValueError, IndexError) as e: return None, start_idx + 1 def process_field_definition(self, table, region_name, start_idx): fields = [] try: field_pattern = f"Field ({region_name}" if field_pattern not in table[start_idx:]: return fields, len(table) field_start_idx = table.index(field_pattern, start_idx) field_line, field_start_line_idx, field_end_line_idx = self.extract_line(table, field_start_idx) field_block = self.extract_block_content(table, field_end_line_idx) for line in field_block.splitlines(): line = line.strip() if not line or line in ["{", "}"]: continue field_name, field_size = self.parse_field_line(line) if field_name and field_size is not None: field_info = { "name": field_name, "size": field_size, } fields.append(field_info) return fields, field_end_line_idx except (ValueError, IndexError) as e: return fields, start_idx + 1 def battery_status_patch(self): if not self.dsdt: return False search_start_idx = 0 all_fields = [] while "EmbeddedControl" in self.dsdt.get("table")[search_start_idx:]: region_name, search_start_idx = self.process_embedded_control_region(self.dsdt.get("table"), search_start_idx) if not region_name: continue current_idx = search_start_idx region_fields = [] while True: fields, next_idx = self.process_field_definition(self.dsdt.get("table"), region_name, current_idx) if not fields or next_idx <= current_idx: break region_fields.extend(fields) current_idx = next_idx if f"Field ({region_name}" not in self.dsdt.get("table")[current_idx:]: break all_fields.extend(region_fields) return any(f["size"] > 8 for f in all_fields) def dropping_the_table(self, signature=None, oemtableid=None): table_data = self.acpi.get_table_with_signature(signature) or self.acpi.get_table_with_id(oemtableid) if not table_data: return return { "All": True, "Comment": "Delete {}".format((signature or oemtableid).rstrip(b"\x00").decode()), "Enabled": True, "OemTableId": self.utils.hex_to_bytes(binascii.hexlify(table_data.get("id")).decode()), "TableLength": table_data.get("length"), "TableSignature": self.utils.hex_to_bytes(binascii.hexlify(table_data.get("signature")).decode()) } def fix_apic_processor_id(self): self.apic = self.acpi.get_table_with_signature("APIC") new_apic = "" if not self.apic: return for table_name in self.sorted_nicely(list(self.acpi.acpi_tables)): table = self.acpi.acpi_tables[table_name] processors = self.acpi.get_processor_paths(table=table) if not processors: continue processor_index = -1 apic_length = len(self.apic.get("lines")) skip_unknown_subtable = False for index in range(apic_length): line = self.apic.get("lines")[index] if "Unknown" in line: skip_unknown_subtable = not skip_unknown_subtable continue if skip_unknown_subtable: continue if "Subtable Type" in line and "[Processor Local APIC]" in line: processor_index += 1 apic_processor_id = self.apic["lines"][index + 2][-2:] try: processor_id = table.get("lines")[processors[processor_index][1]].split(", ")[1][2:] except: return if processor_index == 0 and apic_processor_id == processor_id: return self.apic["lines"][index + 2] = self.apic["lines"][index + 2][:-2] + processor_id new_apic += line + "\n" if processor_index != -1: return { "Add": [ { "Comment": "APIC.aml", "Enabled": self.write_ssdt("APIC", new_apic), "Path": "APIC.aml" } ], "Delete": [ self.dropping_the_table("APIC") ] } def disable_usb_hub_devices(self): ssdt_name = "SSDT-USB-Reset" patches = [] ssdt_content = """ DefinitionBlock ("", "SSDT", 2, "ZPSS", "UsbReset", 0x00001000) {""" rhub_devices = self.acpi.get_device_paths("RHUB") rhub_devices.extend(self.acpi.get_device_paths("HUBN")) rhub_devices.extend(self.acpi.get_device_paths("URTH")) if not rhub_devices: return for device in rhub_devices: device_path = device[0] sta = self.get_sta_var(var=None, device=device_path, dev_hid=None, dev_name=device_path.split(".")[-1], table=self.dsdt) patches.extend(sta.get("patches", [])) ssdt_content += """ External ([[device_path]], DeviceObj) Scope ([[device_path]]) { Method (_STA, 0, NotSerialized) // _STA: Status { If (_OSI ("Darwin")) { Return (Zero) } Else { Return (0x0F) } } } """.replace("[[device_path]]", device_path) ssdt_content += "\n}" return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ], "Patch": patches } def return_thermal_zone(self): ssdt_name = "SSDT-WMIS" ssdt_content = """ // Resource: https://github.com/zhen-zen/YogaSMC/blob/master/YogaSMC/SSDTSample/SSDT-WMIS.dsl /* * Sample SSDT to fix sensor return * * Certain models forget to return result from ThermalZone: * * Method (WQBI, 1, NotSerialized) * { * \\_TZ.WQBI (Arg0) * } * * So we have to patch it for correct reporting. * Rename Method (WQBI, 1, N) to XQBI * (ThermalZone one usually has Serialized type) * * Find: 57514249 01 // WQBI * Repl: 58514249 01 // XQBI * * MethodFlags := * bit 0-2: ArgCount (0-7) * bit 3: SerializeFlag * 0 NotSerialized * 1 Serialized */ DefinitionBlock ("", "SSDT", 2, "ZPSS", "WMIS", 0x00000000) { External (_TZ.WQBI, MethodObj) // Method in ThermalZone Method (_SB.WMIS.WQBI, 1, NotSerialized) { Return (\\_TZ.WQBI (Arg0)) } } """ for table_name in self.sorted_nicely(list(self.acpi.acpi_tables)): table = self.acpi.acpi_tables[table_name] wqbi_method = self.acpi.get_method_paths("WQBI", table=table) if not wqbi_method: continue return { "Add": [ { "Comment": ssdt_name + ".aml", "Enabled": self.write_ssdt(ssdt_name, ssdt_content), "Path": ssdt_name + ".aml" } ], "Patch": [ { "Comment": "WQBI to XQBI Rename", "Find": "5751424901", "Replace": "5851424901" } ] } def drop_cpu_tables(self): cpu_tables = ["CpuPm", "Cpu0Ist"] deletes = [] for table_name in cpu_tables: padded_table_id = self.get_data(table_name, pad_to=8) table_entry = self.dropping_the_table(oemtableid=padded_table_id) if table_entry: deletes.append(table_entry) return { "Delete": deletes } def select_acpi_tables(self): while True: self.utils.head("Select ACPI Tables") print("") print("Q. Quit") print(" ") menu = self.utils.request_input("Please drag and drop ACPI Tables folder here: ") if menu.lower() == "q": self.utils.exit_program() path = self.utils.normalize_path(menu) if not path: continue return self.read_acpi_tables(path) def get_patch_index(self, name): for index, patch in enumerate(self.patches): if patch.name == name: return index return None def select_acpi_patches(self, hardware_report, disabled_devices): selected_patches = [] if "Laptop" in hardware_report.get("Motherboard").get("Platform") and \ "Integrated GPU" in list(hardware_report.get("GPU").items())[-1][-1].get("Device Type") and \ not "SURFACE" in hardware_report.get("Motherboard").get("Name"): selected_patches.append("ALS") selected_patches.append("PNLF") if self.is_intel_hedt_cpu(hardware_report.get("CPU").get("Processor Name"), hardware_report.get("CPU").get("Codename")): selected_patches.append("APIC") for device_name, device_info in disabled_devices.items(): if "PCI" in device_info.get("Bus Type", "PCI"): selected_patches.append("Disable Devices") selected_patches.append("FakeEC") if "HP " in hardware_report.get("Motherboard").get("Name"): selected_patches.append("CMOS") if hardware_report.get("Motherboard").get("Chipset") in chipset_data.IntelChipsets[-7:]: selected_patches.append("RCSP") if "Laptop" in hardware_report.get("Motherboard").get("Platform") and hardware_report.get("CPU").get("Codename") in cpu_data.IntelCPUGenerations[50:]: selected_patches.append("FixHPET") for device_name, device_info in hardware_report.get("System Devices", {}).items(): device_id = device_info.get("Device ID") if not device_id in ("8086-1C3A", "8086-1E3A"): continue if "Sandy Bridge" in hardware_report.get("CPU").get("Codename") and device_id in "8086-1E3A" or \ "Ivy Bridge" in hardware_report.get("CPU").get("Codename") and device_id in "8086-1C3A": selected_patches.append("IMEI") if hardware_report.get("Motherboard").get("Chipset") in chipset_data.IntelChipsets[100:112]: selected_patches.append("PMC") if "Sandy Bridge" in hardware_report.get("CPU").get("Codename") or "Ivy Bridge" in hardware_report.get("CPU").get("Codename"): selected_patches.append("PM (Legacy)") else: selected_patches.append("PLUG") if all(network_props.get("Bus Type") == "USB" for network_props in hardware_report.get("Network", {}).values()): selected_patches.append("RMNE") if hardware_report.get("Motherboard").get("Chipset") in ("C610/X99", "Wellsburg", "X299"): selected_patches.append("RTC0") if "AMD" in hardware_report.get("CPU").get("Manufacturer") or hardware_report.get("CPU").get("Codename") in cpu_data.IntelCPUGenerations[:40]: selected_patches.append("RTCAWAC") if "Intel" in hardware_report.get("CPU").get("Manufacturer"): selected_patches.append("BUS0") if "SURFACE" in hardware_report.get("Motherboard").get("Name"): selected_patches.append("Surface Patch") else: if "Intel" in hardware_report.get("CPU").get("Manufacturer"): for device_name, device_info in hardware_report.get("Input", {}).items(): if "I2C" in device_info.get("Device Type", "None"): selected_patches.append("GPI0") if hardware_report.get("Motherboard").get("Chipset") in ("C600/X79", "C610/X99", "Wellsburg"): selected_patches.append("UNC") if "AMD" in hardware_report.get("CPU").get("Manufacturer") or hardware_report.get("Motherboard").get("Chipset") in chipset_data.IntelChipsets[112:]: selected_patches.append("USB Reset") selected_patches.append("USBX") if "Laptop" in hardware_report.get("Motherboard").get("Platform"): selected_patches.append("BATP") selected_patches.append("XOSI") for device_name, device_info in hardware_report.get("System Devices", {}).items(): if device_info.get("Bus Type") == "ACPI" and device_info.get("Device") in pci_data.YogaHIDs: selected_patches.append("WMIS") for patch in self.patches: patch.checked = patch.name in selected_patches def customize_patch_selection(self): while True: contents = [] contents.append("") contents.append("List of available patches:") contents.append("") for index, kext in enumerate(self.patches, start=1): checkbox = "[*]" if kext.checked else "[ ]" line = "{} {:2}. {:15} - {:60}".format(checkbox, index, kext.name, kext.description) if kext.checked: line = "\033[1;32m{}\033[0m".format(line) contents.append(line) contents.append("\033[1;93mNote:\033[0m You can select multiple kexts by entering their indices separated by commas (e.g., '1, 2, 3').") contents.append("B. Back") contents.append("Q. Quit") contents.append("") content = "\n".join(contents) self.utils.adjust_window_size(content) self.utils.head("Customize ACPI Patch Selections", resize=False) print(content) option = self.utils.request_input("Select your option: ") if option.lower() == "q": self.utils.exit_program() if option.lower() == "b": return indices = [int(i.strip()) -1 for i in option.split(",") if i.strip().isdigit()] for index in indices: if index >= 0 and index < len(self.patches): patch = self.patches[index] patch.checked = not patch.checked