def perform_merge(self): # pylint: disable=unsupported-assignment-operation user_langs = ( {util.get_settings("lang")} if not self._options["all_langs"] else util.get_user_languages() ) print("Loading text mods...") diffs = self.consolidate_diffs(self.get_all_diffs()) if not diffs: print("No text merge necessary") for bootup in util.get_master_modpack_dir().rglob("**/Bootup_????.pack"): bootup.unlink() return # find a mod lang for each user lang lang_map = map_languages(user_langs, set(diffs.keys())) for user_lang, mod_lang in lang_map.items(): print(f"Merging modded texts for {mod_lang} into {user_lang}...") rsext.mergers.texts.merge_language( json.dumps(diffs[mod_lang]), str(util.get_game_file(f"Pack/Bootup_{user_lang}.pack")), str( util.get_master_modpack_dir() / util.get_content_path() / "Pack" / f"Bootup_{user_lang}.pack" ), util.get_settings("wiiu"), ) print(f"{user_lang} texts merged successfully")
def find_modded_files( tmp_dir: Path, pool: Optional[multiprocessing.pool.Pool] = None ) -> List[Union[Path, str]]: modded_files = [] if isinstance(tmp_dir, str): tmp_dir = Path(tmp_dir) if (tmp_dir / util.get_dlc_path()).exists(): try: util.get_aoc_dir() except FileNotFoundError: raise FileNotFoundError( "This mod uses DLC files, but BCML cannot locate the DLC folder in " "your game dump.") aoc_field = (tmp_dir / util.get_dlc_path() / ("0010" if util.get_settings("wiiu") else "") / "Pack" / "AocMainField.pack") if aoc_field.exists() and aoc_field.stat().st_size > 0: if not list((tmp_dir / util.get_dlc_path() / ("0010" if util.get_settings("wiiu") else "") ).rglob("Map/**/?-?_*.smubin")): aoc_pack = oead.Sarc(aoc_field.read_bytes()) for file in aoc_pack.get_files(): ex_out = (tmp_dir / util.get_dlc_path() / ("0010" if util.get_settings("wiiu") else "") / file.name) ex_out.parent.mkdir(parents=True, exist_ok=True) ex_out.write_bytes(file.data) aoc_field.write_bytes(b"") this_pool = pool or Pool(maxtasksperchild=500) results = this_pool.map( partial(_check_modded, tmp_dir=tmp_dir), { f for f in tmp_dir.rglob("**/*") if f.is_file() and "options" not in f.relative_to(tmp_dir).parts }, ) for result in results: if result: modded_files.append(result) total = len(modded_files) print(f'Found {total} modified file{"s" if total > 1 else ""}') total = 0 sarc_files = {f for f in modded_files if f.suffix in util.SARC_EXTS} if sarc_files: print("Scanning files packed in SARCs...") for files in this_pool.imap_unordered( partial(find_modded_sarc_files, tmp_dir=tmp_dir), sarc_files): total += len(files) modded_files.extend(files) print(f'Found {total} modified packed file{"s" if total > 1 else ""}') if not pool: this_pool.close() this_pool.join() return modded_files
def generate_logs( tmp_dir: Path, options: dict = None, pool: Optional[multiprocessing.pool.Pool] = None, ) -> List[Union[Path, str]]: if isinstance(tmp_dir, str): tmp_dir = Path(tmp_dir) if not options: options = {"disable": [], "options": {}} if "disable" not in options: options["disable"] = [] util.vprint(options) this_pool = pool or Pool(maxtasksperchild=500) print("Scanning for modified files...") modded_files = find_modded_files(tmp_dir, pool=pool) if not (modded_files or (tmp_dir / "patches").exists() or (tmp_dir / "logs").exists()): if "options" in tmp_dir.parts: message = ( f"No modified files were found in {str(tmp_dir)}. " f"This may mean that this option's files are identical to the " f"base mod's, or that the folder has an improper structure.") else: message = ( f"No modified files were found in {str(tmp_dir)}. " f"This probably means this mod is not in a supported format.") raise RuntimeError(message) (tmp_dir / "logs").mkdir(parents=True, exist_ok=True) try: for i, merger_class in enumerate([ merger_class for merger_class in mergers.get_mergers() if merger_class.NAME not in options["disable"] ]): merger = merger_class() # type: ignore util.vprint( f"Merger {merger.NAME}, #{i+1} of {len(mergers.get_mergers())}" ) if options is not None and merger.NAME in options["options"]: merger.set_options(options["options"][merger.NAME]) merger.set_pool(this_pool) merger.log_diff(tmp_dir, modded_files) if util.get_settings("strip_gfx"): dev._clean_sarcs(tmp_dir, util.get_hash_table(util.get_settings("wiiu")), this_pool) except: # pylint: disable=bare-except this_pool.close() this_pool.join() this_pool.terminate() raise if not pool: this_pool.close() this_pool.join() util.vprint(modded_files) return modded_files
def link_master_mod(output: Path = None): util.create_bcml_graphicpack_if_needed() if not output: if not util.get_settings("export_dir"): return output = Path(util.get_settings("export_dir")) if output.exists(): shutil.rmtree(output, ignore_errors=True) try: output.mkdir(parents=True, exist_ok=True) if not util.get_settings("no_cemu"): output.mkdir(parents=True, exist_ok=True) 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 export(output: Path): print("Loading files...") tmp_dir = Path(mkdtemp()) if tmp_dir.exists(): try: rmtree(tmp_dir) except (OSError, FileNotFoundError, PermissionError) as err: raise RuntimeError( "There was a problem cleaning the temporary export directory. This may be" " a fluke, so consider restarting BCML and trying again." ) from err link_master_mod(tmp_dir) print("Adding rules.txt...") rules_path = tmp_dir / "rules.txt" mods = util.get_installed_mods() if util.get_settings("wiiu"): rules_path.write_text( "[Definition]\n" "titleIds = 00050000101C9300,00050000101C9400,00050000101C9500\n" "name = Exported BCML Mod\n" "path = The Legend of Zelda: Breath of the Wild/Mods/Exported BCML\n" f'description = Exported merge of {", ".join([mod.name for mod in mods])}\n' "version = 4\n", encoding="utf-8", ) if output.suffix == ".bnp" or output.name.endswith(".bnp.7z"): print("Exporting BNP...") dev.create_bnp_mod( mod=tmp_dir, meta={}, output=output, options={"rstb": { "no_guess": util.get_settings("no_guess") }}, ) else: print("Exporting as graphic pack mod...") x_args = [get_7z_path(), "a", str(output), f'{str(tmp_dir / "*")}'] result: subprocess.CompletedProcess if os.name == "nt": result = subprocess.run( x_args, creationflags=util.CREATE_NO_WINDOW, check=False, capture_output=True, universal_newlines=True, ) else: result = subprocess.run(x_args, check=False, capture_output=True, universal_newlines=True) if result.stderr: raise RuntimeError( f"There was an error exporting your mod(s). {result.stderr}") rmtree(tmp_dir, True)
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. " "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 = 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 set_size(entry: str, size: int): rstb_path = (util.get_master_modpack_dir() / util.get_content_path() / "System" / "Resource" / "ResourceSizeTable.product.srsizetable") if rstb_path.exists(): table = read_rstb(rstb_path, be=util.get_settings("wiiu")) else: table = get_stock_rstb() rstb_path.parent.mkdir(parents=True, exist_ok=True) table.set_size(entry, size) buf = io.BytesIO() table.write(buf, be=util.get_settings("wiiu")) rstb_path.write_bytes(util.compress(buf.getvalue()))
def get_ver(self, params=None): updated = util.get_settings("last_version") < VERSION res = { "version": USER_VERSION, "update": (util.get_latest_bcml() > VERSION and not util.get_settings("suppress_update")), "showChangelog": updated and util.get_settings("changelog"), } if updated: util.get_settings()["last_version"] = VERSION util.save_settings() return res
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 _clean_sarc(old_sarc: oead.Sarc, base_sarc: oead.Sarc) -> Optional[oead.SarcWriter]: old_files = {f.name for f in old_sarc.get_files()} new_sarc = oead.SarcWriter(endian=oead.Endianness.Big if util.get_settings( "wiiu") else oead.Endianness.Little) can_delete = True for nest_file, file_data in [(f.name, f.data) for f in base_sarc.get_files()]: ext = Path(nest_file).suffix if ext in {".yml", ".bak"}: continue if nest_file in old_files: old_data = util.unyaz_if_needed(old_sarc.get_file(nest_file).data) file_data = util.unyaz_if_needed(file_data) if nest_file not in old_files or (file_data != old_data and ext not in util.AAMP_EXTS): if (ext in util.SARC_EXTS and nest_file in old_files and nest_file not in SPECIAL): nest_old_sarc = oead.Sarc(old_data) nest_base_sarc = oead.Sarc(file_data) nest_new_sarc = _clean_sarc(nest_old_sarc, nest_base_sarc) if nest_new_sarc: new_bytes = nest_new_sarc.write()[1] if ext.startswith(".s") and ext != ".sarc": new_bytes = util.compress(new_bytes) new_sarc.files[nest_file] = oead.Bytes(new_bytes) can_delete = False else: continue else: if ext.startswith(".s") and ext != ".sarc": file_data = util.compress(file_data) new_sarc.files[nest_file] = oead.Bytes(file_data) can_delete = False return None if can_delete else new_sarc
def calculate_size( path: Union[Path, str], data: ByteString = None, guess: bool = True ) -> int: ext = path.suffix if isinstance(path, Path) else path[path.rindex(".") :] data = util.unyaz_if_needed(path.read_bytes() if isinstance(path, Path) else data) try: be = util.get_settings("wiiu") # pylint: disable=invalid-name size = getattr(calculate_size, "calculator").calculate_file_size_with_ext( data, wiiu=be, ext=ext, force=False ) if ext == ".bdmgparam": size = 0 if ext == ".hkrb": size += 40 if ext == ".baniminfo": size = int( (((len(data) + 31) & -32) * (1.5 if len(data) > 36864 else 4)) + 0xE4 + 0x24C ) if not be: size = int(size * 1.5) if size == 0 and guess: if ext in util.AAMP_EXTS: size = guess_aamp_size(data, be, ext) elif ext in {".bfres", ".sbfres"}: size = guess_bfres_size( data, be, path if isinstance(path, str) else path.name, ) return size except struct.error: return 0
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 __init__(self) -> None: self._gameid = "5866" if util.get_settings("wiiu") else "6386" if not GB_DATA.exists(): GB_DATA.write_bytes( util.decompress( (util.get_exec_dir() / "data" / "gb.sjson").read_bytes())) self._data = json.loads(GB_DATA.read_text("utf-8"))
def perform_merge(self): merged_quests = util.get_master_modpack_dir() / "logs" / "quests.byml" print("Loading quest mods...") diffs = self.consolidate_diffs(self.get_all_diffs()) if not diffs: print("No quest merging necessary") if merged_quests.exists(): merged_quests.unlink() try: util.inject_file_into_sarc( "Quest/QuestProduct.sbquestpack", util.get_nested_file_bytes( (f'{str(util.get_game_file("Pack/TitleBG.pack"))}' "//Quest/QuestProduct.sbquestpack"), unyaz=False, ), "Pack/TitleBG.pack", ) except FileNotFoundError: pass return print("Loading stock quests...") quests = get_stock_quests() stock_names = [q["Name"] for q in quests] print("Merging quest mods...") for name, mod in diffs["mod"].items(): try: quests[stock_names.index(name)] = mod except (IndexError, ValueError): diffs["add"].append(mod) for delete in reversed(diffs["del"]): try: quests.remove(quests[stock_names.index(delete)]) except ValueError: pass except IndexError: raise RuntimeError( f"An error occurred when attempting to remove a quest from your " "game. Most of the time this means the mod was accidentally made " "using the base game 1.0 TitleBG.pack instead of the latest updated " "version. Please contact the mod author for assistance.") added_names = set() for add in diffs["add"]: if add["Name"] not in added_names: quests.append(add) added_names.add(add["Name"]) print("Writing new quest pack...") data = oead.byml.to_binary(quests, big_endian=util.get_settings("wiiu")) merged_quests.parent.mkdir(parents=True, exist_ok=True) merged_quests.write_bytes(data) util.inject_file_into_sarc( "Quest/QuestProduct.sbquestpack", util.compress(data), "Pack/TitleBG.pack", create_sarc=True, )
def perform_merge(self): merged_events = util.get_master_modpack_dir() / "logs" / "eventinfo.byml" event_merge_log = util.get_master_modpack_dir() / "logs" / "eventinfo.log" print("Loading event info mods...") modded_events = self.consolidate_diffs(self.get_all_diffs()) event_mod_hash = hash(str(modded_events)) if not modded_events: print("No event info merging necessary") if merged_events.exists(): merged_events.unlink() event_merge_log.unlink() try: stock_eventinfo = util.get_nested_file_bytes( ( str(util.get_game_file("Pack/Bootup.pack")) + "//Event/EventInfo.product.sbyml" ), unyaz=False, ) util.inject_file_into_sarc( "Event/EventInfo.product.sbyml", stock_eventinfo, "Pack/Bootup.pack", ) except FileNotFoundError: pass return if event_merge_log.exists() and event_merge_log.read_text() == event_mod_hash: print("No event info merging necessary") return new_events = get_stock_eventinfo() for event, data in modded_events.items(): new_events[event] = data del modded_events print("Writing new event info...") event_bytes = oead.byml.to_binary( new_events, big_endian=util.get_settings("wiiu") ) del new_events util.inject_file_into_sarc( "Event/EventInfo.product.sbyml", util.compress(event_bytes), "Pack/Bootup.pack", create_sarc=True, ) print("Saving event info merge log...") event_merge_log.parent.mkdir(parents=True, exist_ok=True) event_merge_log.write_text(str(event_mod_hash)) merged_events.write_bytes(event_bytes) print("Updating RSTB...") rstb_size = rstb.SizeCalculator().calculate_file_size_with_ext( bytes(event_bytes), True, ".byml" ) del event_bytes rstable.set_size("Event/EventInfo.product.byml", rstb_size)
def _yml_to_byml(file: Path): data = oead.byml.to_binary( oead.byml.from_text(file.read_text("utf-8")), big_endian=util.get_settings("wiiu"), ) out = file.with_suffix("") out.write_bytes(data if not out.suffix.startswith(".s") else util.compress(data)) file.unlink()
def get_ver(self, params=None): return { "version": USER_VERSION, "update": util.get_latest_bcml() > VERSION and not util.get_settings("suppress_update"), }
def get_stock_rstb() -> rstb.ResourceSizeTable: if not hasattr(get_stock_rstb, "table"): get_stock_rstb.table = read_rstb( str( util.get_game_file( "System/Resource/ResourceSizeTable.product.srsizetable")), util.get_settings("wiiu"), ) return deepcopy(get_stock_rstb.table)
def mods(self): return [ m for m in self._data["mods"].values() if (("WiiU" in m["game"] if self._gameid == "5866" else "Switch" in m["game"]) and (util.get_settings("nsfw") or all( nsfw not in (m["name"] + m["description"] + m["text"]).lower() for nsfw in {"nsfw", "thicc", "boob", "cosplay"}))) ]
def save_settings(self, params): print("Saving settings, BCML will reload momentarily...") if util.get_settings("wiiu") != params["settings"]["wiiu"]: util.clear_all_caches() if hasattr(self, "gb_api"): self.gb_api.reset_update_time(params["settings"]["wiiu"]) del self.gb_api self.gb_api = GameBananaDb() util.get_settings.settings = params["settings"] util.save_settings()
def perform_merge(self): actor_path = (util.get_master_modpack_dir() / util.get_content_path() / "Actor" / "ActorInfo.product.sbyml") print("Loading modded actor info...") modded_actors = self.consolidate_diffs(self.get_all_diffs()) if not modded_actors: print("No actor info merging necessary.") if actor_path.exists(): actor_path.unlink() return print("Loading unmodded actor info...") actorinfo = get_stock_actorinfo() stock_actors = { crc32(actor["name"].encode("utf8")): actor for actor in actorinfo["Actors"] } print("Merging changes...") new_hashes = set() for actor_hash, actor_info in modded_actors.items(): if isinstance(actor_hash, str): actor_hash = int(actor_hash) if actor_hash in stock_actors: util.dict_merge(stock_actors[actor_hash], actor_info, overwrite_lists=True) else: actorinfo["Actors"].append(actor_info) new_hashes.add(actor_hash) print("Sorting new actor info...") actorinfo["Hashes"] = oead.byml.Array([ oead.S32(x) if x < 2147483648 else oead.U32(x) for x in sorted(new_hashes | set(stock_actors.keys())) ]) try: actorinfo["Actors"] = sorted( actorinfo["Actors"], key=lambda x: crc32(x["name"].encode("utf-8"))) except KeyError as err: if str(err) == "": raise RuntimeError( "Your actor info mods could not be merged. " "This usually indicates a corrupt game dump.") from err else: raise print("Saving new actor info...") actor_path.parent.mkdir(parents=True, exist_ok=True) actor_path.write_bytes( util.compress( oead.byml.to_binary(actorinfo, big_endian=util.get_settings("wiiu")))) print("Actor info merged successfully")
def swap_region(mod_pack: Path, user_lang: str) -> Path: mod_sarc = oead.Sarc(mod_pack.read_bytes()) mod_msg_data = mod_sarc.get_file(0).data new_pack = oead.SarcWriter( endian=oead.Endianness.Big if util.get_settings("wiiu") else oead.Endianness.Little ) new_pack.files[f"Message/Msg_{user_lang}.product.ssarc"] = oead.Bytes(mod_msg_data) new_pack_path = mod_pack.with_name(f"Bootup_{user_lang}.temppack") new_pack_path.write_bytes(new_pack.write()[1]) return new_pack_path
def perform_merge(self): merged_quests = util.get_master_modpack_dir() / "logs" / "quests.byml" print("Loading quest mods...") diffs = self.consolidate_diffs(self.get_all_diffs()) if not diffs: print("No quest merging necessary") if merged_quests.exists(): merged_quests.unlink() try: util.inject_file_into_sarc( "Quest/QuestProduct.sbquestpack", util.get_nested_file_bytes( (f'{str(util.get_game_file("Pack/TitleBG.pack"))}' "//Quest/QuestProduct.sbquestpack"), unyaz=False, ), "Pack/TitleBG.pack", ) except FileNotFoundError: pass return print("Loading stock quests...") quests = get_stock_quests() stock_names = [q["Name"] for q in quests] print("Merging quest mods...") for name, mod in diffs["mod"].items(): try: quests[stock_names.index(name)] = mod except (IndexError, ValueError): diffs["add"].append(mod) for delete in reversed(diffs["del"]): try: quests.remove(quests[stock_names.index(delete)]) except ValueError: pass added_names = set() for add in diffs["add"]: if add["Name"] not in added_names: quests.append(add) added_names.add(add["Name"]) print("Writing new quest pack...") data = oead.byml.to_binary(quests, big_endian=util.get_settings("wiiu")) merged_quests.parent.mkdir(parents=True, exist_ok=True) merged_quests.write_bytes(data) util.inject_file_into_sarc( "Quest/QuestProduct.sbquestpack", util.compress(data), "Pack/TitleBG.pack", create_sarc=True, )
def perform_merge(self): merged_effects = util.get_master_modpack_dir() / "logs" / "effects.byml" print("Loading status effect mods...") diffs = self.consolidate_diffs(self.get_all_diffs()) if not diffs: print("No status effect merging necessary...") if merged_effects.exists(): merged_effects.unlink() try: stock_effects = util.get_nested_file_bytes( ( str(util.get_game_file("Pack/Bootup.pack")) + "//Ecosystem/StatusEffectList.sbyml" ), unyaz=False, ) util.inject_file_into_sarc( "Ecosystem/StatusEffectList.sbyml", stock_effects, "Pack/Bootup.pack", ) del stock_effects except FileNotFoundError: pass return util.vprint("All status effect diffs:") util.vprint(diffs) effects = get_stock_effects() util.dict_merge(effects, diffs, overwrite_lists=True) del diffs print("Writing new effects list...") effect_bytes = oead.byml.to_binary( oead.byml.Array([effects]), big_endian=util.get_settings("wiiu") ) del effects util.inject_file_into_sarc( "Ecosystem/StatusEffectList.sbyml", util.compress(effect_bytes), "Pack/Bootup.pack", create_sarc=True, ) print("Saving status effect merge log...") merged_effects.parent.mkdir(parents=True, exist_ok=True) merged_effects.write_bytes(effect_bytes) print("Updating RSTB...") rstb_size = rstb.SizeCalculator().calculate_file_size_with_ext( effect_bytes, True, ".byml" ) del effect_bytes rstable.set_size("Ecosystem/StatusEffectList.byml", rstb_size)
def gen_rstb(self, params=None): try: mod = Path(self.get_folder()) assert mod.exists() except (FileNotFoundError, IndexError, AssertionError): return with util.TempModContext(): if not ((mod / "info.json").exists() or (mod / "rules.txt").exists()): (mod / "info.json").write_text( json.dumps( { "name": "Temp", "desc": "Temp pack", "url": "", "id": "VGVtcD0wLjA=", "image": "", "version": "1.0.0", "depends": [], "options": {}, "platform": "wiiu" if util.get_settings("wiiu") else "switch", } ) ) install.install_mod( mod, merge_now=True, options={ "options": {}, "disable": [ m.NAME for m in mergers.get_mergers() if m.NAME != "rstb" ], }, ) (mod / util.get_content_path() / "System" / "Resource").mkdir( parents=True, exist_ok=True ) copyfile( util.get_master_modpack_dir() / util.get_content_path() / "System" / "Resource" / "ResourceSizeTable.product.srsizetable", mod / util.get_content_path() / "System" / "Resource" / "ResourceSizeTable.product.srsizetable", )
def find_modded_files( tmp_dir: Path, pool: Optional[multiprocessing.pool.Pool] = None ) -> List[Union[Path, str]]: modded_files = [] if isinstance(tmp_dir, str): tmp_dir = Path(tmp_dir) if (tmp_dir / util.get_dlc_path()).exists(): try: util.get_aoc_dir() except FileNotFoundError: raise FileNotFoundError( "This mod uses DLC files, but BCML cannot locate the DLC folder in " "your game dump.") aoc_field = (tmp_dir / util.get_dlc_path() / ("0010" if util.get_settings("wiiu") else "") / "Pack" / "AocMainField.pack") if aoc_field.exists() and aoc_field.stat().st_size > 0: if not list((tmp_dir / util.get_dlc_path() / ("0010" if util.get_settings("wiiu") else "") ).rglob("Map/**/?-?_*.smubin")): aoc_pack = oead.Sarc(aoc_field.read_bytes()) for file in aoc_pack.get_files(): ex_out = (tmp_dir / util.get_dlc_path() / ("0010" if util.get_settings("wiiu") else "") / file.name) ex_out.parent.mkdir(parents=True, exist_ok=True) ex_out.write_bytes(file.data) aoc_field.write_bytes(b"") modded_files = [ f if "//" in f else Path(f) for f in rsext.find_modified_files( str(tmp_dir), util.get_settings("wiiu")) ] return modded_files
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 _pack_sarc(folder: Path, tmp_dir: Path, hashes: dict): packed = oead.SarcWriter( endian=oead.Endianness.Big if util.get_settings("wiiu") else oead.Endianness.Little ) try: canon = util.get_canon_name( folder.relative_to(tmp_dir).as_posix(), allow_no_source=True ) if canon not in hashes: raise FileNotFoundError("File not in game dump") stock_file = util.get_game_file(folder.relative_to(tmp_dir)) try: old_sarc = oead.Sarc(util.unyaz_if_needed(stock_file.read_bytes())) except (RuntimeError, ValueError, oead.InvalidDataError): raise ValueError("Cannot open file from game dump") old_files = {f.name for f in old_sarc.get_files()} except (FileNotFoundError, ValueError): for file in {f for f in folder.rglob("**/*") if f.is_file()}: packed.files[file.relative_to(folder).as_posix()] = file.read_bytes() else: for file in { f for f in folder.rglob("**/*") if f.is_file() and not f.suffix in EXCLUDE_EXTS }: file_data = file.read_bytes() xhash = xxhash.xxh64_intdigest(util.unyaz_if_needed(file_data)) file_name = file.relative_to(folder).as_posix() if file_name in old_files: old_hash = xxhash.xxh64_intdigest( util.unyaz_if_needed(old_sarc.get_file(file_name).data) ) if file_name not in old_files or (xhash != old_hash): packed.files[file_name] = file_data finally: shutil.rmtree(folder) if not packed.files: return # pylint: disable=lost-exception sarc_bytes = packed.write()[1] folder.write_bytes( util.compress(sarc_bytes) if (folder.suffix.startswith(".s") and not folder.suffix == ".sarc") else sarc_bytes )
def old_settings(self): old = util.get_data_dir() / "settings.ini" if old.exists(): try: upgrade.convert_old_settings() settings = util.get_settings() return { "exists": True, "message": "Your old settings were converted successfully.", "settings": settings, } except: # pylint: disable=bare-except return { "exists": True, "message": "Your old settings could not be converted.", } else: return {"exists": False, "message": "No old settings found."}