def restore_old_backup(self, params=None): if (util.get_cemu_dir() / "bcml_backups").exists(): open_dir = util.get_cemu_dir() / "bcml_backups" else: open_dir = Path.home() try: file = Path( self.window.create_file_dialog( directory=str(open_dir), file_types=("BCML Backups (*.7z)", "All Files (*.*)"), )[0]) except IndexError: return tmp_dir = Path(mkdtemp()) x_args = [ZPATH, "x", str(file), f"-o{str(tmp_dir)}"] if system() == "Windows": run( x_args, capture_output=True, creationflags=util.CREATE_NO_WINDOW, check=True, ) else: run(x_args, capture_output=True, check=True) upgrade.convert_old_mods(tmp_dir)
def launch_cemu(self, params=None): if not params: params = {"run_game": True} cemu = next( iter({ f for f in util.get_cemu_dir().glob("*.exe") if "cemu" in f.name.lower() })) uking = util.get_game_dir().parent / "code" / "U-King.rpx" try: assert uking.exists() except AssertionError: raise FileNotFoundError("Your BOTW executable could not be found") cemu_args: List[str] if SYSTEM == "Windows": cemu_args = [str(cemu)] if params["run_game"]: cemu_args.extend(("-g", str(uking))) else: cemu_args = ["wine", str(cemu)] if params["run_game"]: cemu_args.extend(( "-g", "Z:\\" + str(uking).replace("/", "\\"), )) Popen(cemu_args, cwd=str(util.get_cemu_dir()))
def enable_bcml_gfx(): if util.get_settings("no_cemu"): return settings = util.parse_cemu_settings() try: gpack = settings.getElementsByTagName("GraphicPack")[0] except IndexError: gpack = settings.createElement("GraphicPack") settings.appendChild(gpack) new_cemu = True def create_entry(path: str): def entry_matches(entry): try: return (path == entry.getElementsByTagName("filename") [0].childNodes[0].data) except IndexError: return path == entry.getAttribute("filename") if any( entry_matches(entry) for entry in gpack.getElementsByTagName("Entry")): return entry: minidom.Element = settings.createElement("Entry") if new_cemu: entry.setAttribute("filename", path) else: entryfile = settings.createElement("filename") entryfile.appendChild(settings.createTextNode(path)) entry.appendChild(entryfile) entrypreset = settings.createElement("preset") entrypreset.appendChild(settings.createTextNode("")) entry.appendChild(entrypreset) gpack.appendChild(entry) create_entry("graphicPacks\\BreathOfTheWild_BCML\\rules.txt") if (util.get_cemu_dir() / "graphicPacks" / "bcmlPatches").exists(): for rules in (util.get_cemu_dir() / "graphicPacks" / "bcmlPatches").rglob("rules.txt"): create_entry(str(rules.relative_to(util.get_cemu_dir()))) settings.writexml( (util.get_cemu_dir() / "settings.xml").open("w", encoding="utf-8"), addindent=" ", newl="\n", )
def restore_backup(backup: Union[str, Path]): """ Restores a BCML mod configuration backup :param backup: The backup to restore, either by name or by path :type backup: Union[str, Path] """ if isinstance(backup, str): backup = util.get_cemu_dir() / 'bcml_backups' / f'{backup}.7z' if not backup.exists(): raise FileNotFoundError(f'The backup "{backup.name}" does not exist.') print('Clearing installed mods...') for folder in [ item for item in util.get_modpack_dir().glob('*') if item.is_dir() ]: shutil.rmtree(str(folder)) print('Extracting backup...') x_args = [ str(util.get_exec_dir() / 'helpers' / '7z.exe'), 'x', str(backup), f'-o{str(util.get_modpack_dir())}' ] subprocess.run(x_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=util.CREATE_NO_WINDOW) print('Re-enabling mods in Cemu...') refresh_cemu_mods() print(f'Backup "{backup.name}" restored')
def create_backup(name: str = ''): """ Creates a backup of the current mod installations. Saves it as a 7z file in `CEMU_DIR/bcml_backups`. :param name: The name to give the backup, defaults to "BCML_Backup_YYYY-MM-DD" :type name: str, optional """ import re if not name: name = f'BCML_Backup_{datetime.datetime.now().strftime("%Y-%m-%d")}' else: name = re.sub(r'(?u)[^-\w.]', '', name.strip().replace(' ', '_')) output = util.get_cemu_dir() / 'bcml_backups' / f'{name}.7z' output.parent.mkdir(parents=True, exist_ok=True) print(f'Saving backup {name}...') x_args = [ str(util.get_exec_dir() / 'helpers' / '7z.exe'), 'a', str(output), f'{str(util.get_modpack_dir() / "*")}' ] subprocess.run(x_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=util.CREATE_NO_WINDOW) print(f'Backup "{name}" created')
def uninstall_all(self): for folder in {d for d in util.get_modpack_dir().glob("*") if d.is_dir()}: rmtree(folder, onerror=install.force_del) if not util.get_settings("no_cemu"): shutil.rmtree( util.get_cemu_dir() / "graphicPacks" / "bcmlPatches", ignore_errors=True )
def disable_bcml_gfx(): if not util.get_settings("no_cemu"): settings = util.parse_cemu_settings() try: gpack = settings.getElementsByTagName("GraphicPack")[0] except IndexError: gpack = settings.createElement("GraphicPack") settings.appendChild(gpack) new_cemu = True entry: minidom.Element for entry in gpack.getElementsByTagName("Entry"): if new_cemu and entry.getElementsByTagName("filename"): new_cemu = False try: if ("bcml" in entry.getElementsByTagName("filename") [0].childNodes[0].data.lower()): gpack.removeChild(entry) except IndexError: if "bcml" in entry.getAttribute("filename").lower(): gpack.removeChild(entry) settings.writexml( (util.get_cemu_dir() / "settings.xml").open("w", encoding="utf-8"), addindent=" ", newl="\n", )
def uninstall_mod(mod: BcmlMod, wait_merge: bool = False): has_patches = (mod.path / "patches").exists() try: shutil.rmtree(str(mod.path), onerror=force_del) except (OSError, PermissionError, WindowsError) as err: raise RuntimeError( f"The folder for {mod.name} could not be removed. " "You may need to delete it manually and remerge, or " "close all open programs (including BCML and Windows Explorer) " "and try again. The location of the folder is " f"<code>{str(mod.path)}</code>.") from err for fall_mod in [ m for m in util.get_installed_mods(True) if m.priority > mod.priority ]: fall_mod.change_priority(fall_mod.priority - 1) if not util.get_installed_mods(): shutil.rmtree(util.get_master_modpack_dir()) util.create_bcml_graphicpack_if_needed() else: if not wait_merge: refresh_merges() if has_patches and not util.get_settings("no_cemu"): shutil.rmtree( util.get_cemu_dir() / "graphicPacks" / "bcmlPatches" / util.get_safe_pathname(mod.name), ignore_errors=True, ) print(f"{mod.name} has been uninstalled.")
def get_old_mods(self): return len( { d for d in (util.get_cemu_dir() / "graphicPacks" / "BCML").glob("*") if d.is_dir() } )
def link_master_mod(output: Path = None): util.create_bcml_graphicpack_if_needed() if not output: if util.get_settings("no_cemu"): return output = util.get_cemu_dir() / "graphicPacks" / "BreathOfTheWild_BCML" if output.exists(): shutil.rmtree(output, ignore_errors=True) try: output.mkdir(parents=True, exist_ok=True) if not util.get_settings("no_cemu"): shutil.copy(util.get_master_modpack_dir() / "rules.txt", output / "rules.txt") except (OSError, PermissionError, FileExistsError, FileNotFoundError) as err: raise RuntimeError( "There was a problem creating the master BCML graphic pack. " "It may be a one time fluke, so try remerging and/or restarting BCML. " "This can also happen if BCML and/or Cemu are installed into Program " "Files or any folder which requires administrator (or root) permissions. " "You can try running BCML as administrator or root, but bear in mind this " "is not officially supported. If the problem persists, good luck, because " "it's something wonky about your PC, I guess.") from err mod_folders: List[Path] = sorted( [ item for item in util.get_modpack_dir().glob("*") if item.is_dir() and not (item / ".disabled").exists() ], reverse=True, ) util.vprint(mod_folders) link_or_copy: Any = os.link if not util.get_settings( "no_hardlinks") else copyfile for mod_folder in mod_folders: for item in mod_folder.rglob("**/*"): rel_path = item.relative_to(mod_folder) exists = (output / rel_path).exists() is_log = str(rel_path).startswith("logs") is_opt = str(rel_path).startswith("options") is_meta = str(rel_path).startswith("meta") is_extra = (len(rel_path.parts) == 1 and rel_path.suffix != ".txt" and not item.is_dir()) if exists or is_log or is_extra or is_meta or is_opt: continue if item.is_dir(): (output / rel_path).mkdir(parents=True, exist_ok=True) elif item.is_file(): try: link_or_copy(str(item), str(output / rel_path)) except OSError: if link_or_copy is os.link: link_or_copy = copyfile link_or_copy(str(item), str(output / rel_path)) else: raise
def refresh_cemu_mods(): """ Updates Cemu's enabled graphic packs """ setpath = util.get_cemu_dir() / 'settings.xml' if not setpath.exists(): raise FileNotFoundError('The Cemu settings file could not be found.') setread = '' with setpath.open('r') as setfile: for line in setfile: setread += line.strip() settings = minidom.parseString(setread) try: gpack = settings.getElementsByTagName('GraphicPack')[0] except IndexError: gpack = settings.createElement('GraphicPack') settings.appendChild(gpack) # Issue #33 - check for new cemu Entry layout new_cemu_version = False for entry in gpack.getElementsByTagName('Entry'): if len(entry.getElementsByTagName('filename')) == 0: new_cemu_version = True break # Issue #33 - end Entry layout check for entry in gpack.getElementsByTagName('Entry'): # Issue #33 - handle BCML node by Cemu version if new_cemu_version: if 'BCML' in entry.getAttribute('filename'): gpack.removeChild(entry) else: try: if 'BCML' in entry.getElementsByTagName( 'filename')[0].childNodes[0].data: gpack.removeChild(entry) except IndexError: pass bcmlentry = create_settings_mod_node(settings, new_cemu_version) # Issue #33 - end BCML node gpack.appendChild(bcmlentry) for mod in util.get_installed_mods(): # Issue #33 - handle new mod nodes by Cemu version modentry = create_settings_mod_node(settings, new_cemu_version, mod) # Issue #33 - end handling new mod nodes gpack.appendChild(modentry) settings.writexml(setpath.open('w', encoding='utf-8'), addindent=' ', newl='\n')
def refresh_master_export(): print("Exporting merged mod pack...") link_master_mod() if not util.get_settings("no_cemu"): settings = util.parse_cemu_settings() try: gpack = settings.getElementsByTagName("GraphicPack")[0] except IndexError: gpack = settings.createElement("GraphicPack") settings.appendChild(gpack) new_cemu = True entry: minidom.Element for entry in gpack.getElementsByTagName("Entry"): if new_cemu and entry.getElementsByTagName("filename"): new_cemu = False try: if "BCML" in entry.getElementsByTagName( "filename")[0].childNodes[0].data: break except IndexError: if "BCML" in entry.getAttribute("filename"): break else: bcmlentry = settings.createElement("Entry") if new_cemu: bcmlentry.setAttribute( "filename", "graphicPacks\\BreathOfTheWild_BCML\\rules.txt") else: entryfile = settings.createElement("filename") entryfile.appendChild( settings.createTextNode( "graphicPacks\\BreathOfTheWild_BCML\\rules.txt")) bcmlentry.appendChild(entryfile) entrypreset = settings.createElement("preset") entrypreset.appendChild(settings.createTextNode("")) bcmlentry.appendChild(entrypreset) gpack.appendChild(bcmlentry) settings.writexml( (util.get_cemu_dir() / "settings.xml").open("w", encoding="utf-8"), addindent=" ", newl="\n", )
def link_master_mod(output: Path = None): if not output: output = util.get_cemu_dir() / 'graphicPacks' / 'BreathOfTheWild_BCML' if output.exists(): shutil.rmtree(str(output), ignore_errors=True) output.mkdir(parents=True, exist_ok=True) mod_folders: List[Path] = sorted( [item for item in util.get_modpack_dir().glob('*') if item.is_dir()], reverse=True) shutil.copy(str(util.get_master_modpack_dir() / 'rules.txt'), str(output / 'rules.txt')) for mod_folder in mod_folders: for item in mod_folder.rglob('**/*'): rel_path = item.relative_to(mod_folder) if (output / rel_path).exists()\ or (str(rel_path).startswith('logs'))\ or (len(rel_path.parts) == 1 and rel_path.suffix != '.txt'): continue if item.is_dir(): (output / rel_path).mkdir(parents=True, exist_ok=True) elif item.is_file(): os.link(str(item), str(output / rel_path))
def convert_old_mods(source: Path = None): mod_dir = util.get_modpack_dir() old_path = source or util.get_cemu_dir() / "graphicPacks" / "BCML" print("Copying old mods...") shutil.rmtree(mod_dir, ignore_errors=True) shutil.copytree(old_path, mod_dir) print("Converting old mods...") for i, mod in enumerate( sorted({ d for d in mod_dir.glob("*") if d.is_dir() and d.name != "9999_BCML" })): print(f"Converting {mod.name[4:]}") try: convert_old_mod(mod, True) except Exception as err: shutil.rmtree(mod) install.refresh_merges() raise RuntimeError( f"BCML was unable to convert {mod.name[4:]}. Error: {str(err)}. Your old " f"mods have not been modified. {i} mod(s) were successfully imported." ) from err shutil.rmtree(old_path, ignore_errors=True)
def install_mod( mod: Path, options: dict = None, selects: dict = None, pool: Optional[multiprocessing.pool.Pool] = None, insert_priority: int = 0, merge_now: bool = False, updated: bool = False, ): if not insert_priority: insert_priority = get_next_priority() try: if isinstance(mod, str): mod = Path(mod) if mod.is_file(): print("Opening mod...") tmp_dir = open_mod(mod) elif mod.is_dir(): if not ((mod / "rules.txt").exists() or (mod / "info.json").exists()): print( f"Cannot open mod at {str(mod)}, no rules.txt or info.json found" ) return print(f"Loading mod from {str(mod)}...") tmp_dir = Path(mkdtemp()) if tmp_dir.exists(): shutil.rmtree(tmp_dir) shutil.copytree(str(mod), str(tmp_dir)) if (mod / "rules.txt").exists() and not (mod / "info.json").exists(): print("Upgrading old mod format...") upgrade.convert_old_mod(mod, delete_old=True) else: print(f"Error: {str(mod)} is neither a valid file nor a directory") return except Exception as err: # pylint: disable=broad-except raise util.InstallError(err) from err if not options: options = {"options": {}, "disable": []} this_pool: Optional[multiprocessing.pool.Pool] = None # type: ignore try: rules = json.loads((tmp_dir / "info.json").read_text("utf-8")) mod_name = rules["name"].strip(" '\"").replace("_", "") print(f"Identified mod: {mod_name}") if rules["depends"]: try: installed_metas = { v[0]: v[1] for m in util.get_installed_mods() for v in util.BcmlMod.meta_from_id(m.id) } except (IndexError, TypeError) as err: raise RuntimeError( f"This BNP has invalid or corrupt dependency data.") for depend in rules["depends"]: depend_name, depend_version = util.BcmlMod.meta_from_id(depend) if (depend_name not in installed_metas) or ( depend_name in installed_metas and depend_version > installed_metas[depend_name]): raise RuntimeError( f"{mod_name} requires {depend_name} version {depend_version}, " f"but it is not installed. Please install {depend_name} and " "try again.") friendly_plaform = lambda p: "Wii U" if p == "wiiu" else "Switch" user_platform = "wiiu" if util.get_settings("wiiu") else "switch" if rules["platform"] != user_platform: raise ValueError( f'"{mod_name}" is for {friendly_plaform(rules["platform"])}, not ' f" {friendly_plaform(user_platform)}.'") if "priority" in rules and rules["priority"] == "base": insert_priority = 100 logs = tmp_dir / "logs" if logs.exists(): print("Loading mod logs...") for merger in [ merger() # type: ignore for merger in mergers.get_mergers() if merger.NAME in options["disable"] ]: if merger.is_mod_logged(BcmlMod(tmp_dir)): (tmp_dir / "logs" / merger.log_name).unlink() else: this_pool = pool or Pool(maxtasksperchild=500) dev._pack_sarcs(tmp_dir, util.get_hash_table(util.get_settings("wiiu")), this_pool) generate_logs(tmp_dir=tmp_dir, options=options, pool=this_pool) if not util.get_settings("strip_gfx"): (tmp_dir / ".processed").touch() except Exception as err: # pylint: disable=broad-except try: name = mod_name except NameError: name = "your mod, the name of which could not be detected" raise util.InstallError(err, name) from err if selects: for opt_dir in { d for d in (tmp_dir / "options").glob("*") if d.is_dir() }: if opt_dir.name not in selects: shutil.rmtree(opt_dir, ignore_errors=True) else: file: Path for file in { f for f in opt_dir.rglob("**/*") if ("logs" not in f.parts and f.is_file()) }: out = tmp_dir / file.relative_to(opt_dir) out.parent.mkdir(parents=True, exist_ok=True) try: os.link(file, out) except FileExistsError: if file.suffix in util.SARC_EXTS: try: old_sarc = oead.Sarc( util.unyaz_if_needed(out.read_bytes())) except (ValueError, oead.InvalidDataError, RuntimeError): out.unlink() os.link(file, out) try: link_sarc = oead.Sarc( util.unyaz_if_needed(file.read_bytes())) except (ValueError, oead.InvalidDataError, RuntimeError): del old_sarc continue new_sarc = oead.SarcWriter.from_sarc(link_sarc) link_files = { f.name for f in link_sarc.get_files() } for sarc_file in old_sarc.get_files(): if sarc_file.name not in link_files: new_sarc.files[sarc_file.name] = bytes( sarc_file.data) del old_sarc del link_sarc out.write_bytes(new_sarc.write()[1]) del new_sarc else: out.unlink() os.link(file, out) rstb_path = (tmp_dir / util.get_content_path() / "System" / "Resource" / "ResourceSizeTable.product.srsizetable") if rstb_path.exists(): rstb_path.unlink() priority = insert_priority print(f"Assigned mod priority of {priority}") mod_id = util.get_mod_id(mod_name, priority) mod_dir = util.get_modpack_dir() / mod_id try: if not updated: for existing_mod in util.get_installed_mods(True): if existing_mod.priority >= priority: existing_mod.change_priority(existing_mod.priority + 1) if (tmp_dir / "patches").exists() and not util.get_settings("no_cemu"): patch_dir = (util.get_cemu_dir() / "graphicPacks" / f"bcmlPatches" / util.get_safe_pathname(rules["name"])) patch_dir.mkdir(parents=True, exist_ok=True) for file in { f for f in (tmp_dir / "patches").rglob("*") if f.is_file() }: out = patch_dir / file.relative_to(tmp_dir / "patches") out.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(file, out) mod_dir.parent.mkdir(parents=True, exist_ok=True) print(f"Moving mod to {str(mod_dir)}...") if mod.is_file(): try: shutil.move(str(tmp_dir), str(mod_dir)) except Exception: # pylint: disable=broad-except try: shutil.rmtree(str(mod_dir)) shutil.copytree(str(tmp_dir), str(mod_dir)) shutil.rmtree(str(tmp_dir), ignore_errors=True) except Exception: # pylint: disable=broad-except raise OSError( "BCML could not transfer your mod from the temp directory to the" " BCML directory.") elif mod.is_dir(): shutil.copytree(str(tmp_dir), str(mod_dir)) shutil.rmtree(tmp_dir, ignore_errors=True) rules["priority"] = priority (mod_dir / "info.json").write_text(json.dumps(rules, ensure_ascii=False, indent=2), encoding="utf-8") (mod_dir / "options.json").write_text(json.dumps(options, ensure_ascii=False, indent=2), encoding="utf-8") output_mod = BcmlMod(mod_dir) try: util.get_mod_link_meta(rules) util.get_mod_preview(output_mod) except Exception: # pylint: disable=broad-except pass except Exception as err: # pylint: disable=broad-except if mod_dir.exists(): try: uninstall_mod(mod_dir, wait_merge=True) except Exception: # pylint: disable=broad-except shutil.rmtree(str(mod_dir)) raise util.InstallError(err, mod_name) from err try: if merge_now: for merger in [m() for m in mergers.get_mergers()]: if this_pool or pool: merger.set_pool(this_pool or pool) if merger.NAME in options["options"]: merger.set_options(options["options"][merger.NAME]) merger.perform_merge() except Exception as err: # pylint: disable=broad-except raise util.MergeError(err) from err if this_pool and not pool: this_pool.close() this_pool.join() return output_mod
def get_backups() -> List[Path]: """ Gets a list of BCML mod configuration backups """ return list((util.get_cemu_dir() / 'bcml_backups').glob('*.7z'))
def delete_old_mods(self): rmtree(util.get_cemu_dir() / "graphicPacks" / "BCML")