def _get_sizes_in_sarc( file: Union[Path, oead.Sarc], guess: bool, is_aoc: bool = False ) -> {}: sizes = {} if isinstance(file, Path): is_aoc = util.get_dlc_path() in file.as_posix() try: file = oead.Sarc(util.unyaz_if_needed(file.read_bytes())) except (RuntimeError, oead.InvalidDataError): print(f"{file} could not be opened") return {} for nest_file, data in [(file.name, file.data) for file in file.get_files()]: canon = nest_file.replace(".s", ".") if data[0:4] == b"Yaz0": data = util.decompress(data) ext = Path(canon).suffix if ( util.is_file_modded(canon, data) and ext not in EXCLUDE_EXTS and canon not in EXCLUDE_NAMES ): sizes[canon] = calculate_size(canon, data, guess=guess) if ext in util.SARC_EXTS - SARC_EXCLUDES: try: nest_sarc = oead.Sarc(data) except (ValueError, RuntimeError, oead.InvalidDataError): continue sizes.update(_get_sizes_in_sarc(nest_sarc, guess, is_aoc=is_aoc)) del nest_sarc del data del file return sizes
def get_gamedata_sarc() -> oead.Sarc: bootup_path: Path = root_dir() / "content" / "Pack" / "Bootup.pack" bootup_sarc = oead.Sarc(bootup_path.read_bytes()) gamedata_sarc = oead.Sarc( oead.yaz0.decompress( bootup_sarc.get_file("GameData/gamedata.ssarc").data)) return gamedata_sarc
def _clean_sarc_file(file: Path, hashes: dict, tmp_dir: Path): canon = util.get_canon_name(file.relative_to(tmp_dir)) try: stock_file = util.get_game_file(file.relative_to(tmp_dir)) except FileNotFoundError: return try: old_sarc = oead.Sarc(util.unyaz_if_needed(stock_file.read_bytes())) except (RuntimeError, ValueError, oead.InvalidDataError): return if canon not in hashes: return try: base_sarc = oead.Sarc(util.unyaz_if_needed(file.read_bytes())) except (RuntimeError, ValueError, oead.InvalidDataError): return new_sarc = _clean_sarc(old_sarc, base_sarc) if not new_sarc: file.unlink() else: write_bytes = new_sarc.write()[1] file.write_bytes( write_bytes if not (file.suffix.startswith(".s") and file.suffix != ".ssarc") else util.compress(write_bytes) )
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 inject_files_into_actor(actor: str, files: Dict[str, ByteString]): actor_sarc: oead.Sarc if actor in TITLE_ACTORS: title_path = (get_master_modpack_dir() / get_content_path() / "Pack" / "TitleBG.pack") if not title_path.exists(): title_path = get_game_file("Pack/TitleBG.pack") title_sarc = oead.Sarc(title_path.read_bytes()) actor_sarc = oead.Sarc( decompress( title_sarc.get_file_data( f"Actor/Pack/{actor}.sbactorpack").data)) del title_sarc else: actor_path = (get_master_modpack_dir() / get_content_path() / "Actor" / "Pack" / f"{actor}.sbactorpack") if not actor_path.exists(): actor_path = get_game_file(f"Actor/Pack/{actor}.sbactorpack") actor_sarc = oead.Sarc(decompress(actor_path.read_bytes())) new_sarc = oead.SarcWriter.from_sarc(actor_sarc) del actor_sarc for file, data in files.items(): new_sarc.files[file] = oead.Bytes(data) out_bytes = compress(new_sarc.write()[1]) if actor in TITLE_ACTORS: inject_file_into_sarc(f"Actor/Pack/{actor}.sbactorpack", out_bytes, "Pack/TitleBG.pack", True) else: output = (get_master_modpack_dir() / get_content_path() / "Actor" / "Pack" / f"{actor}.sbactorpack") output.parent.mkdir(parents=True, exist_ok=True) output.write_bytes(out_bytes)
def from_actor(self, pack: Union[Path, str]) -> None: handled_filenames = set() if isinstance(pack, str): pack_nests = pack.split("//") pack = Path(pack_nests[-1]) titlebg = oead.Sarc(Path(pack_nests[0]).read_bytes()) data = util.unyaz_if_needed(titlebg.get_file(pack_nests[-1]).data) else: data = util.unyaz_if_needed(pack.read_bytes()) self._actorname = pack.stem sarcdata = oead.Sarc(data) actorlink_name = f"Actor/ActorLink/{self._actorname}.bxml" actorlink = oead.aamp.ParameterIO.from_binary( sarcdata.get_file(actorlink_name).data) name_table = oead.aamp.get_default_name_table() for key in actorlink.objects: if name_table.get_name(key.hash, 0, 0) == "LinkTarget": for link in actorlink.objects[key].params: linkstr = name_table.get_name(link.hash, 0, 0) self._links[linkstr] = actorlink.objects[ "LinkTarget"].params[link].v elif name_table.get_name(key.hash, 0, 0) == "Tags": for tag in actorlink.objects[key].params: self._tags.append(actorlink.objects["Tags"].params[tag].v) else: self._misc_tags.append({key: actorlink.objects[key]}) handled_filenames.add(actorlink_name) for link in util.AAMP_LINK_REFS: folder, ext = util.AAMP_LINK_REFS[link] linkref = self._links[link] if linkref == "Dummy": continue filename = f"Actor/{folder}/{linkref}{ext}" filedata = sarcdata.get_file(filename).data self._aampfiles[link] = oead.aamp.ParameterIO.from_binary(filedata) handled_filenames.add(filename) for link in util.BYML_LINK_REFS: folder, ext = util.BYML_LINK_REFS[link] linkref = self._links[link] if linkref == "Dummy": continue filename = f"Actor/{folder}/{linkref}{ext}" filedata = sarcdata.get_file(filename).data self._bymlfiles[link] = oead.byml.from_binary(filedata) handled_filenames.add(filename) for f in sarcdata.get_files(): if not f.name in handled_filenames: self._miscfiles[f"{f.name}"] = bytes(f.data)
def _convert_text_logs(logs_path: Path): diffs = {} with Pool(maxtasksperchild=500) as pool: for diff in pool.imap_unordered(_convert_text_log, logs_path.glob("texts_*.yml")): diffs.update(diff) fails = set() for text_pack in logs_path.glob("newtexts_*.sarc"): lang = text_pack.stem[9:] sarc = oead.Sarc(text_pack.read_bytes()) for file in sarc.get_files(): if lang not in diffs: diffs[lang] = {} try: diffs[lang].update( {file.name: read_msbt(bytes(file.data))["entries"]}) except RuntimeError: print( f"Warning: {file.name} could not be processed and will not be used" ) fails.add(file.name) continue util.vprint(f"{len(fails)} text files failed to process:\n{fails}") text_pack.unlink() (logs_path / "texts.json").write_text(json.dumps(diffs, ensure_ascii=False, indent=2), encoding="utf-8")
def _unbuild_file( f: Path, out: Path, content: str, mod: Path, verbose: bool, skip_texts: bool = False ) -> set: of = out / f.relative_to(mod) if not of.parent.exists(): of.parent.mkdir(parents=True, exist_ok=True) names = set() canon = get_canon_name(f.relative_to(mod)) if canon: names.add(canon) if f.name in HANDLED: pass elif f.suffix in AAMP_EXTS: of.with_suffix(f"{f.suffix}.yml").write_bytes(_aamp_to_yml(f.read_bytes())) elif f.suffix in BYML_EXTS: of.with_suffix(f"{f.suffix}.yml").write_bytes(_byml_to_yml(f.read_bytes())) elif f.suffix in SARC_EXTS: with f.open("rb") as file: data = file.read() if data[0:4] == b"Yaz0": data = decompress(data) if data[0:4] != b"SARC": return names s = oead.Sarc(data) if "bactorpack" in f.suffix: names.update(_unbuild_actorpack(s, out / content)) else: names.update(_unbuild_sarc(s, of, skip_texts=skip_texts)) del s else: of.write_bytes(f.read_bytes()) if verbose: print(f"Unbuilt {f.relative_to(mod).as_posix()}") return names
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_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 get_sizes_in_sarc( sarc: oead.Sarc, contents: Contents, guess: bool, dlc: bool ) -> Dict[str, int]: prefix = "" if not dlc else "Aoc/0010/" vals = {} if isinstance(contents, list): for file in contents: if file[file.rindex(".") :] in EXCLUDE_EXTS: continue canon = prefix + file.replace(".s", ".") vals[canon] = calculate_size(canon, sarc.get_file(file).data, guess) elif isinstance(contents, dict): for subpath, subcontents in contents.items(): ext = subpath[subpath.rindex(".") :] if ext in EXCLUDE_EXTS: continue data = util.unyaz_if_needed(sarc.get_file(subpath).data) canon = prefix + subpath.replace(".s", ".") vals[canon] = calculate_size(canon, data, guess) if ext not in SARC_EXCLUDES: try: subsarc = oead.Sarc(data) except (ValueError, RuntimeError, oead.InvalidDataError): continue vals.update(get_sizes_in_sarc(subsarc, subcontents, guess, dlc)) return vals
def get_nested_file_bytes(file: str, unyaz: bool = True) -> bytes: nests = file.split("//") sarcs = [] sarcs.append(oead.Sarc(unyaz_if_needed(Path(nests[0]).read_bytes()))) i = 1 while i < len(nests) - 1: sarc_bytes = unyaz_if_needed(sarcs[i - 1].get_file(nests[i]).data) sarcs.append(oead.Sarc(sarc_bytes)) i += 1 file_bytes = sarcs[-1].get_file(nests[-1]).data if file_bytes[0:4] == b"Yaz0" and unyaz: file_bytes = decompress(file_bytes) else: file_bytes = bytes(file_bytes) del sarcs return file_bytes
def inject_file_into_sarc(file: str, data: bytes, sarc: str, create_sarc: bool = False): path = get_master_modpack_dir() / get_content_path() / sarc if path.exists() or create_sarc: if not path.exists(): path.parent.mkdir(parents=True, exist_ok=True) shutil.copy(get_game_file(sarc), path) sarc_data = path.read_bytes() yaz = sarc_data[0:4] == b"Yaz0" if yaz: sarc_data = decompress(sarc_data) old_sarc = oead.Sarc(sarc_data) del sarc_data new_sarc = oead.SarcWriter.from_sarc(old_sarc) del old_sarc new_sarc.files[file] = data if isinstance(data, bytes) else bytes(data) new_bytes = new_sarc.write()[1] del new_sarc path.write_bytes(new_bytes if not yaz else compress(new_bytes)) del new_bytes else: raise FileNotFoundError( f"{sarc} is not present in the master BCML mod")
def unbuild_sarc() -> None: set_start_method("spawn", True) parser = argparse.ArgumentParser( description="Unbuild a single SARC file completely") parser.add_argument("sarc", help="SARC archive to unbuild") parser.add_argument( "--output", "-O", help="Output folder for unbuilt SARC,\ defaults to file name w/o extension", ) args = parser.parse_args() try: file: Path = Path(args.sarc) data = file.read_bytes() if data[0:4] == b"Yaz0": data = oead.yaz0.decompress(data) unsarc( oead.Sarc(data), Path(args.output) if args.output else file.with_suffix(""), True, ) except (FileNotFoundError, oead.InvalidDataError, OSError) as err: print(err) return
def get_last_two_savedata_files(bootup_path) -> list: bootup_sarc = oead.Sarc(bootup_path.read_bytes()) savedata_sarc = oead.Sarc( oead.yaz0.decompress( bootup_sarc.get_file("GameData/savedataformat.ssarc").data)) savedata_writer = oead.SarcWriter.from_sarc(savedata_sarc) idx = 0 files = [] while True: try: savedata_writer.files[f"/saveformat_{idx+2}.bgsvdata"] idx += 1 except KeyError: files.append(savedata_writer.files[f"/saveformat_{idx}.bgsvdata"]) files.append( savedata_writer.files[f"/saveformat_{idx+1}.bgsvdata"]) return files
def read_sarc(src: Path) -> oead.Sarc: data = read(src=src) data = oead.yaz0.decompress(data) if data[:4] == b"Yaz0" else data if data[:4] != b"SARC": raise SystemExit("Invalid file") return oead.Sarc(data)
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 _unbuild_sarc( s: oead.Sarc, output: Path, skip_actorpack: bool = False, skip_texts: bool = False ): SKIP_SARCS = { "tera_resource.Cafe_Cafe_GX2.release.ssarc", "tera_resource.Nin_NX_NVN.release.ssarc", } output.mkdir(parents=True, exist_ok=True) if any(f.name.startswith("/") for f in s.get_files()): (output / ".slash").write_bytes(b"") names = set() for sarc_file in s.get_files(): sf = sarc_file.name osf = output / sf names.add(sf.replace(".s", ".")) if sf.startswith("/"): osf = output / sf[1:] osf.parent.mkdir(parents=True, exist_ok=True) ext = osf.suffix if ext in SARC_EXTS: if osf.name in SKIP_SARCS or (osf.name.startswith("Msg_") and skip_texts): osf.write_bytes(sarc_file.data) continue try: ss = oead.Sarc(_if_unyaz(sarc_file.data)) if ( "bactorpack" in ext and output.stem == "TitleBG" and not skip_actorpack ): names.update(_unbuild_actorpack(ss, output.parent.parent)) else: names.update(_unbuild_sarc(ss, osf, skip_texts=skip_texts)) del ss except ValueError: osf.write_bytes(b"") elif ext in AAMP_EXTS: if osf.with_suffix(f"{osf.suffix}.yml").exists(): continue osf.with_suffix(f"{osf.suffix}.yml").write_bytes( _aamp_to_yml(sarc_file.data) ) elif ext in BYML_EXTS: osf.with_suffix(f"{osf.suffix}.yml").write_bytes( _byml_to_yml(sarc_file.data) ) else: osf.write_bytes(sarc_file.data) if "Msg_" in output.name: pymsyt.export(output, output) rmtree(output) output.with_suffix("").rename(output) if output.suffix in {".ssarc", ".sarc"}: (output / ".align").write_text(str(s.guess_min_alignment())) return names
def _convert_sarc_file(pack: Path, to_wiiu: bool) -> list: data = pack.read_bytes() if not data: return [] sarc = oead.Sarc(util.unyaz_if_needed(data)) new_bytes, error = _convert_sarc(sarc, to_wiiu) pack.write_bytes( util.compress(new_bytes) if pack.suffix.startswith(".s") and pack.suffix != ".sarc" else new_bytes) return error
def write(self, root_str: str, be: bool) -> None: if self._texts: for entry, text in self._texts.items(): entry_name = f"{self._actor_name}_{entry}" self._misc_texts[entry_name] = { "contents": [{ "text": text }] } # type:ignore[index] settings = util.BatSettings() msyt = { "group_count": self._group_count, "atr1_unknown": self._atr_unknown, "entries": {}, } for entry, data in self._misc_texts.items(): msyt["entries"][entry] = data # type:ignore[index] platform = "wiiu" if be else "switch" temp = settings.get_data_dir() / "temp.msbt" pymsyt.write_msbt(msyt, temp, platform=platform) msbt = temp.read_bytes() temp.unlink() lang = settings.get_setting("lang") text_pack = Path(f"{root_str}/Pack/Bootup_{lang}.pack") text_pack_load = text_pack if not text_pack.exists(): text_pack.parent.mkdir(parents=True, exist_ok=True) text_pack.touch() text_pack_load = Path( util.find_file(Path(f"Pack/Bootup_{lang}.pack"))) text_sarc = oead.Sarc(text_pack_load.read_bytes()) text_sarc_writer = oead.SarcWriter.from_sarc(text_sarc) message = f"Message/Msg_{lang}.product.ssarc" message_sarc = oead.Sarc( oead.yaz0.decompress(text_sarc.get_file(message).data)) message_sarc_writer = oead.SarcWriter.from_sarc(message_sarc) msbt_name = f"ActorType/{self._profile}.msbt" message_sarc_writer.files[msbt_name] = msbt message_bytes = message_sarc_writer.write()[1] text_sarc_writer.files[message] = oead.yaz0.compress(message_bytes) text_pack.write_bytes(text_sarc_writer.write()[1])
def __init__(self, pack: Path, profile: str): self._texts = {} self._misc_texts = {} self._actor_name = pack.stem self._profile = profile root_dir = pack.parent while True: if (root_dir / "Actor").exists() or (not root_dir.stem == "Actor" and (root_dir / "Pack").exists()): break root_dir = root_dir.parent settings = util.BatSettings() lang = settings.get_setting("lang") text_pack = root_dir / f"Pack/Bootup_{lang}.pack" if not text_pack.exists(): root_dir = Path(settings.get_setting("update_dir")) text_pack = root_dir / f"Pack/Bootup_{lang}.pack" text_sarc = oead.Sarc(text_pack.read_bytes()) message = f"Message/Msg_{lang}.product.ssarc" message_sarc = oead.Sarc( oead.yaz0.decompress(text_sarc.get_file(message).data)) msbt = message_sarc.get_file(f"ActorType/{self._profile}.msbt") if not msbt: return temp = settings.get_data_dir() / "temp.msbt" with temp.open("wb") as t_file: t_file.write(msbt.data) msyt = pymsyt.parse_msbt(temp) del text_sarc del message_sarc del msbt temp.unlink() self._group_count = msyt["group_count"] self._atr_unknown = msyt["atr1_unknown"] for entry in msyt["entries"]: if self._actor_name in entry: entry_name = entry.replace(f"{self._actor_name}_", "") for control_type in msyt["entries"][entry]["contents"]: if "text" in control_type: self._texts[entry_name] = control_type["text"] self._misc_texts[entry] = msyt["entries"][entry]
def _convert_actorpack(actor_pack: Path, to_wiiu: bool) -> Union[None, str]: error = None sarc = oead.Sarc(util.unyaz_if_needed(actor_pack.read_bytes())) new_sarc = oead.SarcWriter.from_sarc(sarc) new_sarc.set_endianness(oead.Endianness.Big if to_wiiu else oead.Endianness.Little) for file in sarc.get_files(): if "Physics/" in file.name and "Actor/" not in file.name: ext = file.name[file.name.rindex(".") :] if ext in NO_CONVERT_EXTS: if not util.is_file_modded( util.get_canon_name(file.name, allow_no_source=True), file.data, count_new=True, ): actor_name = file.name[ file.name.rindex("/") : file.name.rindex(".") ] try: pack_path = util.get_game_file( f"Actor/Pack/{actor_name}.sbactorpack" ) stock_data = util.get_nested_file_bytes( f"{str(pack_path)}//{file.name}" ) if stock_data: new_sarc.files[file.name] = stock_data else: raise FileNotFoundError(file.name) except (FileNotFoundError, AttributeError): error = ( "This mod contains a Havok file not supported by the " f"converter: {file.name}" ) else: error = ( "This mod contains a Havok file not supported by the" f" converter: {file.name}" ) else: if file.data[0:4] == b"AAMP": continue try: hk = Havok.from_bytes(bytes(file.data)) except: # pylint: disable=bare-except return f"Could not parse Havok file {file.name}" if to_wiiu: hk.to_wiiu() else: hk.to_switch() hk.serialize() new_sarc.files[file.name] = hk.to_bytes() actor_pack.write_bytes(util.compress(new_sarc.write()[1])) return error
def inject_bytes_into_sarc(sarc: Path, name: str, data: bytes) -> None: sarc_data = sarc.read_bytes() yaz = sarc_data[0:4] == b"Yaz0" if yaz: sarc_data = oead.yaz0.decompress(sarc_data) sarc_writer = oead.SarcWriter.from_sarc(oead.Sarc(sarc_data)) del sarc_data sarc_writer.files[name] = data new_bytes = sarc_writer.write()[1] del sarc_writer sarc.write_bytes(new_bytes if not yaz else oead.yaz0.compress(new_bytes)) del new_bytes
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 diff_language(bootup: Path, pool: multiprocessing.Pool = None) -> {}: diff = {} language = bootup.name[7:-5] bootup_sarc = oead.Sarc(bootup.read_bytes()) msg_sarc = oead.Sarc( util.decompress( bootup_sarc.get_file( f"Message/Msg_{language}.product.ssarc").data)) with TemporaryDirectory() as tmp: tmp_dir = Path(tmp) mod_out = tmp_dir / "mod" print("Extracting mod texts...") for file in msg_sarc.get_files(): out = mod_out / file.name out.parent.mkdir(parents=True, exist_ok=True) out.write_bytes(file.data) del msg_sarc print("Converting texts to MSYT...") msbt_to_msyt(mod_out, pool=pool) hashes = get_text_hashes(language) ref_lang = "XXen" if language.endswith("en") else language print("Extracting reference texts...") extract_refs(ref_lang, tmp_dir) ref_dir = tmp_dir / "refs" / ref_lang this_pool = pool or multiprocessing.Pool(maxtasksperchild=500) print("Identifying modified text files...") results = this_pool.map( partial(diff_msyt, ref_dir=ref_dir, hashes=hashes, mod_out=mod_out), mod_out.rglob("**/*.msyt"), ) if not pool: this_pool.close() this_pool.join() for result in results: diff.update(result) return diff
def find_modded_sarc_files(mod_sarc: Union[Path, oead.Sarc], tmp_dir: Path, name: str = "", aoc: bool = False) -> List[str]: if isinstance(mod_sarc, Path): if any(mod_sarc.name.startswith(exclude) for exclude in ["Bootup_"]): return [] name = str(mod_sarc.relative_to(tmp_dir)) aoc = util.get_dlc_path() in mod_sarc.parts or "Aoc" in mod_sarc.parts try: mod_sarc = oead.Sarc(util.unyaz_if_needed(mod_sarc.read_bytes())) except (RuntimeError, ValueError, oead.InvalidDataError): return [] modded_files = [] for file, contents in [(f.name, bytes(f.data)) for f in mod_sarc.get_files()]: canon = file.replace(".s", ".") if aoc: canon = "Aoc/0010/" + canon contents = util.unyaz_if_needed(contents) nest_path = str(name).replace("\\", "/") + "//" + file if util.is_file_modded(canon, contents, True): modded_files.append(nest_path) util.vprint( f'Found modded file {canon} in {str(name).replace("//", "/")}') if util.is_file_sarc(canon) and ".ssarc" not in file: try: nest_sarc = oead.Sarc(contents) except ValueError: continue sub_mod_files = find_modded_sarc_files(nest_sarc, name=nest_path, tmp_dir=tmp_dir, aoc=aoc) modded_files.extend(sub_mod_files) else: util.vprint( f'Ignored unmodded file {canon} in {str(name).replace("//", "/")}' ) return modded_files
def create_sarc(self, big_endian: bool, alignment: int) -> dict: tmp_sarc = oead.SarcWriter( oead.Endianness.Big if big_endian else oead.Endianness.Little, oead.SarcWriter.Mode.New if alignment == 4 else oead.SarcWriter.Mode.Legacy, ) self._open_sarc, tree, modded = _sarc.open_sarc( oead.Sarc(tmp_sarc.write()[1])) return { "sarc": tree, "be": big_endian, "path": "", "modded": modded, }
def nested_patch(pack: oead.Sarc, nest: dict) -> Tuple[oead.SarcWriter, dict]: new_sarc: oead.SarcWriter = oead.SarcWriter.from_sarc(pack) failures: dict = {} for file, stuff in nest.items(): file_bytes = pack.get_file(file).data yazd = file_bytes[0:4] == b"Yaz0" file_bytes = util.decompress(file_bytes) if yazd else file_bytes if isinstance(stuff, dict): sub_sarc = oead.Sarc(file_bytes) new_sub_sarc, sub_failures = nested_patch(sub_sarc, stuff) for failure in sub_failures: failure[file + "//" + failure] = sub_failures[failure] del sub_sarc new_bytes = bytes(new_sub_sarc.write()[1]) new_sarc.files[file] = new_bytes if not yazd else util.compress( new_bytes) elif isinstance(stuff, ParameterList): try: if file_bytes[0:4] == b"AAMP": aamp_contents = ParameterIO.from_binary(file_bytes) try: file_ext = os.path.splitext(file)[1] aamp_contents = shop_merge( aamp_contents, file_ext.replace(".", ""), stuff.lists["Additions"], stuff.lists["Removals"], ) aamp_bytes = ParameterIO.to_binary(aamp_contents) except: # pylint: disable=bare-except raise RuntimeError( f"AAMP file {file} could be merged.") del aamp_contents new_bytes = aamp_bytes if not yazd else util.compress( aamp_bytes) cache_merged_shop(file, new_bytes) else: raise ValueError( "Wait, what the heck, this isn't an AAMP file?!") except ValueError: new_bytes = pack.get_file(file).data print(f"Deep merging {file} failed. No changes were made.") new_sarc.files[file] = oead.Bytes(new_bytes) return new_sarc, failures
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 inject_files_into_bootup(bootup_path: Path, files: list, datas: list): sarc_data = bootup_path.read_bytes() yaz = sarc_data[0:4] == b"Yaz0" if yaz: sarc_data = oead.yaz0.decompress(sarc_data) old_sarc = oead.Sarc(sarc_data) del sarc_data new_sarc = oead.SarcWriter.from_sarc(old_sarc) del old_sarc for idx in range(len(files)): new_sarc.files[files[idx]] = (datas[idx] if isinstance( datas[idx], bytes) else bytes(datas[idx])) new_bytes = new_sarc.write()[1] del new_sarc bootup_path.write_bytes( new_bytes if not yaz else oead.yaz0.compress(new_bytes)) del new_bytes