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 generate_diff(self, mod_dir: Path, modded_files: List[Union[Path, str]]): if (f"{util.get_content_path()}/Pack/TitleBG.pack//Quest/QuestProduct.sbquestpack" not in modded_files): return {} print("Logging modified quests...") stock_quests = get_stock_quests() stock_names = [q["Name"] for q in stock_quests] title_sarc = oead.Sarc((mod_dir / util.get_content_path() / "Pack" / "TitleBG.pack").read_bytes()) mod_quests = oead.byml.from_binary( util.decompress( title_sarc.get_file("Quest/QuestProduct.sbquestpack").data)) mod_names = [q["Name"] for q in mod_quests] diffs = oead.byml.Hash({ "add": oead.byml.Array(), "mod": oead.byml.Hash(), "del": oead.byml.Array({q for q in stock_names if q not in mod_names}), }) for i, quest in enumerate(mod_quests): quest_name = quest["Name"] quest["prev_quest"] = mod_quests[ i - 1]["Name"] if i > 0 else "--index_zero" if quest_name not in stock_names: diffs["add"].append(quest) elif quest != stock_quests[stock_names.index(quest_name)]: diffs["mod"][quest_name] = quest return diffs
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 _make_bnp_logs(tmp_dir: Path, options: dict): util.vprint(install.generate_logs(tmp_dir, options=options)) print("Removing unnecessary files...") if (tmp_dir / "logs" / "map.yml").exists(): print("Removing map units...") for file in [ file for file in tmp_dir.rglob("**/*.smubin") if fnmatch(file.name, "[A-Z]-[0-9]_*.smubin") and "MainField" in file.parts ]: file.unlink() if set((tmp_dir / "logs").glob("*texts*")): print("Removing language bootup packs...") for bootup_lang in (tmp_dir / util.get_content_path() / "Pack").glob( "Bootup_*.pack" ): bootup_lang.unlink() if (tmp_dir / "logs" / "actorinfo.yml").exists() and ( tmp_dir / util.get_content_path() / "Actor" / "ActorInfo.product.sbyml" ).exists(): print("Removing ActorInfo.product.sbyml...") ( tmp_dir / util.get_content_path() / "Actor" / "ActorInfo.product.sbyml" ).unlink() if (tmp_dir / "logs" / "gamedata.yml").exists() or ( tmp_dir / "logs" / "savedata.yml" ).exists(): print("Removing gamedata sarcs...") bsarc = oead.Sarc( (tmp_dir / util.get_content_path() / "Pack" / "Bootup.pack").read_bytes() ) csarc = oead.SarcWriter.from_sarc(bsarc) bsarc_files = {f.name for f in bsarc.get_files()} if "GameData/gamedata.ssarc" in bsarc_files: del csarc.files["GameData/gamedata.ssarc"] if "GameData/savedataformat.ssarc" in bsarc_files: del csarc.files["GameData/savedataformat.ssarc"] (tmp_dir / util.get_content_path() / "Pack" / "Bootup.pack").write_bytes( csarc.write()[1] )
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 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 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 bin_data = oead.byml.to_binary(modded_actors, False) rsext.mergers.actorinfo.merge_actorinfo(bin_data) print("Actor info merged successfully")
def generate_diff(self, mod_dir: Path, modded_files: List[Union[Path, str]]): if (f"{util.get_content_path()}/Pack/Bootup.pack//GameData/gamedata.ssarc" in modded_files): print("Logging changes to game data flags...") bootup_sarc = oead.Sarc( util.unyaz_if_needed((mod_dir / util.get_content_path() / "Pack" / "Bootup.pack").read_bytes())) data_sarc = oead.Sarc( util.decompress( bootup_sarc.get_file("GameData/gamedata.ssarc").data)) diff = get_modded_gamedata_entries(data_sarc, pool=self._pool) del bootup_sarc del data_sarc return diff else: return {}
def generate_diff(self, mod_dir: Path, modded_files: List[Union[str, Path]]): print("Checking for modified languages...") languages = { util.get_file_language(file) for file in modded_files if (isinstance(file, Path) and "Bootup_" in file.name and "Graphic" not in file.name) } if not languages: return None util.vprint(f'Languages: {",".join(languages)}') language_map = {} save_langs = (LANGUAGES if self._options.get("all_langs", False) else [util.get_settings("lang")]) for lang in save_langs: if lang in languages: language_map[lang] = lang elif lang[2:4] in [l[2:4] for l in languages]: language_map[lang] = [ l for l in languages if l[2:4] == lang[2:4] ][0] else: language_map[lang] = [l for l in LANGUAGES if l in languages][0] util.vprint(f"Language map:") util.vprint(language_map) language_diffs = {} for language in set(language_map.values()): print(f"Logging text changes for {language}...") language_diffs[language] = diff_language( mod_dir / util.get_content_path() / "Pack" / f"Bootup_{language}.pack", pool=self._pool, ) return { save_lang: language_diffs[map_lang] for save_lang, map_lang in language_map.items() }
def generate_diff(self, mod_dir: Path, modded_files: List[Union[Path, str]]): if ( f"{util.get_content_path()}/Pack/Bootup.pack//Ecosystem/AreaData.sbyml" in modded_files ): print("Logging modded areadata...") bootup_sarc = oead.Sarc( ( mod_dir / util.get_content_path() / "Pack" / "Bootup.pack" ).read_bytes() ) areadata = oead.byml.from_binary( util.decompress(bootup_sarc.get_file("Ecosystem/AreaData.sbyml").data) ) diff = get_modded_areadata(areadata) del bootup_sarc del areadata return diff else: return {}
def generate_diff(self, mod_dir: Path, modded_files: List[Union[str, Path]]): needle = f"{util.get_content_path()}/Pack/Bootup.pack//Ecosystem/StatusEffectList.sbyml" if needle not in modded_files: return {} print("Logging changes to effect status levels...") stock_effects = get_stock_effects() bootup_sarc = oead.Sarc((mod_dir / util.get_content_path() / "Pack" / "Bootup.pack").read_bytes()) mod_effects = oead.byml.from_binary( util.decompress( bootup_sarc.get_file( "Ecosystem/StatusEffectList.sbyml").data))[0] diff = oead.byml.Hash({ effect: params for effect, params in mod_effects.items() if stock_effects[effect] != params }) del stock_effects del mod_effects
def generate_diff(self, mod_dir: Path, modded_files: List[Union[Path, str]]): if ( f"{util.get_content_path()}/Pack/Bootup.pack//Event/EventInfo.product.sbyml" in modded_files ): print("Logging modded events...") bootup_sarc = oead.Sarc( (mod_dir / util.get_content_path() / "Pack" / "Bootup.pack").read_bytes() ) event_info = oead.byml.from_binary( util.decompress( bootup_sarc.get_file("Event/EventInfo.product.sbyml").data ) ) diff = get_modded_events(event_info) del bootup_sarc del event_info return diff else: return {}
def generate_diff(self, mod_dir: Path, modded_files: List[Union[str, Path]]): print("Checking for modified languages...") mod_langs = { util.get_file_language(file) for file in modded_files if ( isinstance(file, Path) and "Bootup_" in file.name and "Graphic" not in file.name ) } if not mod_langs: return None util.vprint(f'Languages: {",".join(mod_langs)}') # find a user lang for each mod lang language_map = map_languages(mod_langs, util.get_user_languages()) util.vprint("Language map:") util.vprint(language_map) language_diffs = {} for mod_lang, user_lang in language_map.items(): print(f"Logging text changes for {user_lang}...") mod_pack = ( mod_dir / util.get_content_path() / "Pack" / f"Bootup_{mod_lang}.pack" ) if not user_lang == mod_lang: mod_pack = swap_region(mod_pack, user_lang) ref_pack = util.get_game_file(f"Pack/Bootup_{user_lang}.pack") language_diffs[user_lang] = rsext.mergers.texts.diff_language( str(mod_pack), str(ref_pack), user_lang[2:4] != mod_lang[2:4] ) if not user_lang == mod_lang: mod_pack.unlink() return language_diffs
def perform_merge(self): # pylint: disable=unsupported-assignment-operation langs = ({util.get_settings("lang")} if not self._options["all_langs"] else get_user_languages()) for lang in langs: print("Loading text mods...") diffs = self.consolidate_diffs(self.get_all_diffs()) if not diffs or lang not in diffs: print("No text merge necessary") for bootup in util.get_master_modpack_dir().rglob( "**/Bootup_????.pack"): bootup.unlink() return util.vprint({ lang: { file: list(entries.keys()) for file, entries in diffs[lang].items() } }) print(f"Merging modded texts for {lang}...") saved_files = set() with TemporaryDirectory() as tmp: tmp_dir = Path(tmp) ref_lang = "XXen" if lang.endswith("en") else lang extract_refs(ref_lang, tmp_dir) tmp_dir = tmp_dir / "refs" / ref_lang this_pool = self._pool or multiprocessing.Pool( maxtasksperchild=500) this_pool.map(partial(merge_msyt, tmp_dir=tmp_dir), diffs[lang].items()) if not self._pool: this_pool.close() this_pool.join() m_args = [ MSYT_PATH, "create", "-d", str(tmp_dir), "-p", "wiiu" if util.get_settings("wiiu") else "switch", "-o", str(tmp_dir), ] result: subprocess.CompletedProcess if system() == "Windows": result = subprocess.run( m_args, capture_output=True, creationflags=util.CREATE_NO_WINDOW, check=False, text=True, ) else: result = subprocess.run( m_args, capture_output=True, check=False, text=True, ) if result.stderr: raise RuntimeError( f"There was an error merging game texts. {result.stderr}" ) msg_sarc = oead.SarcWriter( endian=oead.Endianness.Big if util. get_settings("wiiu") else oead.Endianness.Little) for file in tmp_dir.rglob("**/*.msbt"): msg_sarc.files[file.relative_to( tmp_dir).as_posix()] = file.read_bytes() saved_files.add(file.relative_to(tmp_dir).as_posix()) bootup_sarc = oead.SarcWriter( endian=oead.Endianness.Big if util. get_settings("wiiu") else oead.Endianness.Little) bootup_sarc.files[ f"Message/Msg_{lang}.product.ssarc"] = util.compress( msg_sarc.write()[1]) bootup_path = (util.get_master_modpack_dir() / util.get_content_path() / "Pack" / f"Bootup_{lang}.pack") bootup_path.parent.mkdir(parents=True, exist_ok=True) bootup_path.write_bytes(bootup_sarc.write()[1]) del bootup_sarc del msg_sarc print(f"{lang} texts merged successfully")
def create_bnp_mod(mod: Path, output: Path, meta: dict, options: Optional[dict] = None): if isinstance(mod, str): mod = Path(mod) if not options: options = {"options": {}, "disable": []} if mod.is_file(): print("Extracting mod...") tmp_dir: Path = install.open_mod(mod) elif mod.is_dir(): print(f"Loading mod from {str(mod)}...") tmp_dir = Path(TemporaryDirectory().name) shutil.copytree(mod, tmp_dir) else: print(f"Error: {str(mod)} is neither a valid file nor a directory") return if not ((tmp_dir / util.get_content_path()).exists() or (tmp_dir / util.get_dlc_path()).exists()): if (tmp_dir.parent / util.get_content_path()).exists(): tmp_dir = tmp_dir.parent elif util.get_settings("wiiu") and (tmp_dir / "Content").exists(): (tmp_dir / "Content").rename(tmp_dir / "content") else: raise FileNotFoundError( "This mod does not appear to have a valid folder structure") if (tmp_dir / "rules.txt").exists(): (tmp_dir / "rules.txt").unlink() if "showDepends" in meta: del meta["showDepends"] depend_string = f"{meta['name']}=={meta['version']}" meta["id"] = urlsafe_b64encode(depend_string.encode("utf8")).decode("utf8") any_platform = (options.get("options", dict()).get("general", dict()).get("agnostic", False)) meta["platform"] = ("any" if any_platform else "wiiu" if util.get_settings("wiiu") else "switch") (tmp_dir / "info.json").write_text(dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8") with Pool(maxtasksperchild=500) as pool: yml_files = set(tmp_dir.glob("**/*.yml")) if yml_files: print("Compiling YAML documents...") pool.map(_do_yml, yml_files) hashes = util.get_hash_table(util.get_settings("wiiu")) print("Packing SARCs...") _pack_sarcs(tmp_dir, hashes, pool) for folder in {d for d in tmp_dir.glob("options/*") if d.is_dir()}: _pack_sarcs(folder, hashes, pool) for option_dir in tmp_dir.glob("options/*"): for file in { f for f in option_dir.rglob("**/*") if (f.is_file() and (tmp_dir / f.relative_to(option_dir)).exists()) }: data1 = (tmp_dir / file.relative_to(option_dir)).read_bytes() data2 = file.read_bytes() if data1 == data2: util.vprint( f"Removing {file} from option {option_dir.name}, " "identical to base mod") file.unlink() del data1 del data2 if not options: options = {"disable": [], "options": {}} options["options"]["texts"] = {"all_langs": True} try: _make_bnp_logs(tmp_dir, options) for option_dir in { d for d in tmp_dir.glob("options/*") if d.is_dir() }: _make_bnp_logs(option_dir, options) except Exception as err: # pylint: disable=broad-except pool.terminate() raise Exception( f"There was an error generating change logs for your mod. {str(err)}" ) _clean_sarcs(tmp_dir, hashes, pool) for folder in {d for d in tmp_dir.glob("options/*") if d.is_dir()}: _clean_sarcs(folder, hashes, pool) print("Cleaning any junk files...") for file in {f for f in tmp_dir.rglob("**/*") if f.is_file()}: if "logs" in file.parts: continue if (file.suffix in {".yml", ".json", ".bak", ".tmp", ".old"} and file.stem != "info"): file.unlink() print("Removing blank folders...") for folder in reversed(list(tmp_dir.rglob("**/*"))): if folder.is_dir() and not list(folder.glob("*")): shutil.rmtree(folder) print(f"Saving output file to {str(output)}...") x_args = [util.get_7z_path(), "a", str(output), f'{str(tmp_dir / "*")}'] if system() == "Windows": subprocess.run( x_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=util.CREATE_NO_WINDOW, check=True, ) else: subprocess.run(x_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) shutil.rmtree(tmp_dir, ignore_errors=True) print("Conversion complete.")
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 perform_merge(self): pool = self._pool or Pool(maxtasksperchild=500) if not self._table: self._table = get_stock_rstb() diffs = self.consolidate_diffs(self.get_all_diffs()) master = util.get_master_modpack_dir() master_files = { f for f in master.rglob("**/*") if f.is_file() and "logs" not in f.parts } diffs.update( { k: v for r in pool.map( partial( _get_modded_file_size, mod_dir=master, guess=not self._options.get("no_guess", False), ), {f for f in master_files if f.suffix not in EXCLUDE_EXTS}, ) for k, v in r.items() if r is not None and not self.should_exclude(k, v) } ) diffs.update( { k: v for r in pool.map( partial(_get_sizes_in_sarc, guess=not util.get_settings("no_guess"),), { f for f in master_files if f.suffix in util.SARC_EXTS - SARC_EXCLUDES }, ) for k, v in r.items() if r is not None and not self.should_exclude(k, v) } ) table = self._table for canon, size in diffs.copy().items(): if size == 0: if table.is_in_table(canon): table.delete_entry(canon) else: continue elif table.is_in_table(canon) and size < table.get_size(canon): del diffs[canon] continue else: table.set_size(canon, size) if table.is_in_table(f"Message/Msg_{util.get_settings('lang')}.product.sarc"): table.delete_entry(f"Message/Msg_{util.get_settings('lang')}.product.sarc") out = ( master / util.get_content_path() / "System" / "Resource" / "ResourceSizeTable.product.srsizetable" ) out.parent.mkdir(parents=True, exist_ok=True) with io.BytesIO() as buf: table.write(buf, util.get_settings("wiiu")) out.write_bytes(util.compress(buf.getvalue())) log = master / "logs" / "rstb.json" log.parent.mkdir(parents=True, exist_ok=True) log.write_text(json.dumps(diffs, ensure_ascii=False, indent=2, sort_keys=True))