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 log_merged_files_rstb(): """ Generates an RSTB log for the master BCML modpack containing merged files """ print('Updating RSTB for merged files...') diffs = {} files = [item for item in util.get_master_modpack_dir().rglob('**/*') if item.is_file()] guess = util.get_settings_bool('guess_merge') for file in files: if file.parent == 'logs': continue if file.suffix not in RSTB_EXCLUDE_EXTS and file.name not in RSTB_EXCLUDE_NAMES: size = calculate_size(file) if size == 0 and guess: if file.suffix in util.AAMP_EXTS: size = guess_aamp_size(file) elif file.suffix in ['.bfres', '.sbfres']: size = guess_bfres_size(file) canon = util.get_canon_name(file.relative_to(util.get_master_modpack_dir())) if canon: diffs[canon] = size sarc_files = [file for file in files if util.is_file_sarc(str(file)) \ and file.suffix != '.ssarc'] if sarc_files: num_threads = min(multiprocessing.cpu_count(), len(sarc_files)) pool = multiprocessing.Pool(processes=num_threads) results = pool.map(_get_sizes_in_sarc, sarc_files) pool.close() pool.join() for result in results: diffs.update(result) with (util.get_master_modpack_dir() / 'logs' / 'rstb.log').open('w', encoding='utf-8') as log: log.write('name,size,path\n') for canon, size in diffs.items(): log.write(f'{canon},{size},//\n')
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 threaded_merge(item, verbose: bool) -> (str, dict): """Deep merges an individual file, suitable for multiprocessing""" file, stuff = item failures = {} base_file = util.get_game_file(file, file.startswith('aoc')) if (util.get_master_modpack_dir() / file).exists(): base_file = util.get_master_modpack_dir() / file file_ext = os.path.splitext(file)[1] if file_ext in util.SARC_EXTS and (util.get_master_modpack_dir() / file).exists(): base_file = (util.get_master_modpack_dir() / file) file_bytes = base_file.read_bytes() yazd = file_bytes[0:4] == b'Yaz0' file_bytes = file_bytes if not yazd else util.decompress(file_bytes) magic = file_bytes[0:4] if magic == b'SARC': new_sarc, sub_failures = nested_patch(sarc.SARC(file_bytes), stuff) del file_bytes new_bytes = new_sarc.get_bytes() for failure, contents in sub_failures.items(): print(f'Some patches to {failure} failed to apply.') failures[failure] = contents else: try: if magic == b'AAMP': aamp_contents = aamp.Reader(file_bytes).parse() for change in stuff: aamp_contents = _aamp_merge(aamp_contents, change) aamp_bytes = aamp.Writer(aamp_contents).get_bytes() del aamp_contents new_bytes = aamp_bytes if not yazd else util.compress( aamp_bytes) else: raise ValueError(f'{file} is not a SARC or AAMP file.') except ValueError: new_bytes = file_bytes del file_bytes print(f'Deep merging file {file} failed. No changes were made.') new_bytes = new_bytes if not yazd else util.compress(new_bytes) output_file = (util.get_master_modpack_dir() / file) if base_file == output_file: output_file.unlink() output_file.parent.mkdir(parents=True, exist_ok=True) output_file.write_bytes(new_bytes) del new_bytes if magic == b'SARC' and verbose: print(f'Finished patching files inside {file}') elif verbose: print(f'Finished patching {file}') return util.get_canon_name(file), failures
def merge_events(): """ Merges all installed event info mods """ event_mods = [mod for mod in util.get_installed_mods() \ if (mod.path / 'logs' / 'eventinfo.yml').exists()] merged_events = util.get_master_modpack_dir() / 'logs' / 'eventinfo.byml' event_merge_log = util.get_master_modpack_dir() / 'logs' / 'eventinfo.log' event_mod_hash = str(hash(tuple(event_mods))) if not event_mods: 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_bootup( 'Event/EventInfo.product.sbyml', stock_eventinfo ) 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 print('Loading event info mods...') modded_events = {} for mod in event_mods: modded_events.update(get_events_for_mod(mod)) new_events = get_stock_eventinfo() for event, data in modded_events.items(): new_events[event] = data print('Writing new event info...') event_bytes = byml.Writer(new_events, be=True).get_bytes() util.inject_file_into_bootup( 'Event/EventInfo.product.sbyml', util.compress(event_bytes), create_bootup=True ) print('Saving event info merge log...') event_merge_log.write_text(event_mod_hash) merged_events.write_bytes(event_bytes) print('Updating RSTB...') rstb_size = rstb.SizeCalculator().calculate_file_size_with_ext(event_bytes, True, '.byml') rstable.set_size('Event/EventInfo.product.byml', rstb_size)
def merge_aamp_files(file: str, tree: dict): try: base_file = util.get_game_file(file) except FileNotFoundError: util.vprint(f"Skipping {file}, not found in dump") return if (util.get_master_modpack_dir() / file).exists(): base_file = util.get_master_modpack_dir() / file sarc = Sarc(util.unyaz_if_needed(base_file.read_bytes())) new_data = _merge_in_sarc(sarc, tree) if base_file.suffix.startswith(".s") and base_file.suffix != ".ssarc": new_data = util.compress(new_data) (util.get_master_modpack_dir() / file).parent.mkdir(parents=True, exist_ok=True) (util.get_master_modpack_dir() / file).write_bytes(new_data)
def get_bootup_injection(self): tmp_sarc = util.get_master_modpack_dir() / "logs" / "gamedata.sarc" if tmp_sarc.exists(): return ("GameData/gamedata.ssarc", util.compress(tmp_sarc.read_bytes())) else: return
def uninstall_mod(mod: BcmlMod, wait_merge: bool = False): print(f"Uninstalling {mod.name}...") try: shutil.rmtree(str(mod.path)) except (OSError, PermissionError) 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() print(f"{mod.name} has been uninstalled.")
def perform_merge(self): print("Loading modded SARC list...") sarcs = { s: ss for s, ss in self.consolidate_diffs(self.get_all_diffs()).items() if ss } for file in [ file for file in util.get_master_modpack_dir().rglob("**/*") if file.suffix in util.SARC_EXTS - EXCLUDE_EXTS and not any(ex in file.name for ex in SPECIAL) ]: file.unlink() for sarc_file in sarcs: try: sarcs[sarc_file].insert(0, util.get_game_file(sarc_file)) except FileNotFoundError: continue if not sarcs: print("No SARC merging necessary") return print(f"Merging {len(sarcs)} SARC files...") from bcml import bcml as rsext rsext.mergers.packs.merge_sarcs(sarcs) # pool = self._pool or Pool(maxtasksperchild=500) # results = pool.starmap(merge_sarcs, sarcs.items()) # pool.starmap(write_sarc, results) # if not self._pool: # pool.close() # pool.join() print("Finished merging SARCs")
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 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 get_bootup_injection(self): tmp_sarc = util.get_master_modpack_dir() / "logs" / "effects.byml" if tmp_sarc.exists(): return ( "Ecosystem/StatusEffectList.sbyml", util.compress(tmp_sarc.read_bytes()), ) return
def get_bootup_injection(self): tmp_sarc = util.get_master_modpack_dir() / "logs" / "savedata.sarc" if tmp_sarc.exists(): return ( "GameData/savedataformat.ssarc", util.compress(tmp_sarc.read_bytes()), ) return None
def perform_merge(self): print("Loading modded SARC list...") sarcs = { s: ss for s, ss in self.consolidate_diffs(self.get_all_diffs()).items() if ss } if "only_these" in self._options: for sarc_file in self._options["only_these"]: master_path = util.get_master_modpack_dir() / sarc_file if master_path.exists(): master_path.unlink() for sarc_file in [ file for file in sarcs if file not in self._options["only_these"] ]: del sarcs[sarc_file] else: for file in [ file for file in util.get_master_modpack_dir().rglob("**/*") if file.suffix in util.SARC_EXTS ]: file.unlink() for sarc_file in sarcs: try: sarcs[sarc_file].insert(0, util.get_game_file(sarc_file)) except FileNotFoundError: continue if not sarcs: print("No SARC merging necessary") return print(f"Merging {len(sarcs)} SARC files...") pool = self._pool or Pool(maxtasksperchild=500) results = pool.starmap(merge_sarcs, sarcs.items()) for result in results: file, file_data = result output_path = util.get_master_modpack_dir() / file output_path.parent.mkdir(parents=True, exist_ok=True) if output_path.suffix.startswith(".s"): file_data = util.compress(file_data) output_path.write_bytes(file_data) if not self._pool: pool.close() pool.join() print("Finished merging SARCs")
def explore_master(self, params=None): path = util.get_master_modpack_dir() if SYSTEM == "Windows": startfile(path) elif SYSTEM == "Darwin": run(["open", path], check=False) else: run(["xdg-open", path], check=False)
def get_merged_files() -> List[str]: """Gets a list of all currently deep merged files""" log = util.get_master_modpack_dir() / 'logs' / 'deepmerge.log' if not log.exists(): return [] else: with log.open('r') as l_file: return l_file.readlines()
def refresh_merges(): print("Cleansing old merges...") shutil.rmtree(util.get_master_modpack_dir(), True) print("Refreshing merged mods...") with Pool(maxtasksperchild=500) as pool: for merger in mergers.sort_mergers( [merger_class() for merger_class in mergers.get_mergers()]): merger.set_pool(pool) merger.perform_merge()
def get_bootup_injection(self): tmp_sarc = util.get_master_modpack_dir() / "logs" / "eventinfo.byml" if tmp_sarc.exists(): return ( "Event/EventInfo.product.sbyml", util.compress(tmp_sarc.read_bytes()), ) else: return
def get_bootup_injection(self): tmp_sarc = util.get_master_modpack_dir() / 'logs' / 'eventinfo.byml' if tmp_sarc.exists(): return ( 'Event/EventInfo.product.sbyml', util.compress(tmp_sarc.read_bytes()) ) else: return
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 remerge(self, params): try: if not util.get_installed_mods(): if util.get_master_modpack_dir().exists(): rmtree(util.get_master_modpack_dir()) install.link_master_mod() return if params["name"] == "all": install.refresh_merges() else: [ m() for m in mergers.get_mergers() if m().friendly_name == params["name"] ][0].perform_merge() except Exception as err: # pylint: disable=broad-except raise Exception( f"There was an error merging your mods. {str(err)}\n" "Note that this could leave your game in an unplayable state.")
def get_bootup_injection(self): tmp_sarc = util.get_master_modpack_dir() / "logs" / "areadata.byml" if tmp_sarc.exists(): return ( "Ecosystem/AreaData.sbyml", util.compress(tmp_sarc.read_bytes()), ) else: return
def deep_merge(verbose: bool = False, wait_rstb: bool = False, only_these: List[str] = None, original_pool: multiprocessing.Pool = None): """Performs deep merge on all installed AAMP files""" mods = get_deepmerge_mods() if not mods: print('No deep merge necessary.') return if (util.get_master_modpack_dir() / 'logs' / 'rstb.log').exists(): (util.get_master_modpack_dir() / 'logs' / 'rstb.log').unlink() merge_log = (util.get_master_modpack_dir() / 'logs' / 'deepmerge.log') old_merges = [] if merge_log.exists(): if only_these: old_merges = merge_log.read_text().splitlines() merge_log.unlink() print('Loading deep merge data...') diffs = consolidate_diff_files(get_deepmerge_diffs(only_these=only_these)) print('Performing deep merge...') if not diffs: return num_threads = min(multiprocessing.cpu_count(), len(diffs)) pool = original_pool or multiprocessing.Pool(processes=num_threads) pool.map(partial(threaded_merge, verbose=verbose), diffs.items()) if not original_pool: pool.close() pool.join() if not wait_rstb: bcml.rstable.generate_master_rstb() (util.get_master_modpack_dir() / 'logs').mkdir(parents=True, exist_ok=True) with merge_log.open('w', encoding='utf-8') as l_file: for file_type in diffs: for file in diffs[file_type]: l_file.write(f'{file}\n') if only_these and file in old_merges: old_merges.remove(file) if only_these: for file in old_merges: l_file.write(f'{file}\n')
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 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 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 perform_merge(self): print('Loading modded SARC list...') sarcs = self.consolidate_diffs(self.get_all_diffs()) if 'only_these' in self._options: for sarc_file in self._options['only_these']: master_path = (util.get_master_modpack_dir() / sarc_file) if master_path.exists(): master_path.unlink() for sarc_file in [ file for file in sarcs if file not in self._options['only_these'] ]: del sarcs[sarc_file] else: for file in [file for file in util.get_master_modpack_dir().rglob('**/*') \ if file.suffix in util.SARC_EXTS]: file.unlink() for sarc_file in sarcs: try: sarcs[sarc_file].insert(0, util.get_game_file(sarc_file)) except FileNotFoundError: continue if not sarcs: print('No SARC merging necessary') return num_threads = min(cpu_count(), len(sarcs)) pool = self._pool or Pool(processes=num_threads) print(f'Merging {len(sarcs)} SARC files...') results = pool.starmap(merge_sarcs, sarcs.items()) for result in results: file, data = result output_path = util.get_master_modpack_dir() / file output_path.parent.mkdir(parents=True, exist_ok=True) if output_path.suffix.startswith('.s'): data = util.compress(data) output_path.write_bytes(data) if not self._pool: pool.close() pool.join() print('Finished merging SARCs')
def refresh_merges(verbose: bool = False): """ Runs RSTB, pack, and text merges together :param verbose: Whether to display more detailed output, defaults to False. :type verbose: bool, optional """ print('Cleansing old merges...') shutil.rmtree(util.get_master_modpack_dir()) print('Refreshing merged mods...') for merger in mergers.sort_mergers( [merger_class() for merger_class in mergers.get_mergers()]): merger.perform_merge()
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()))