def _build_sarc(d: Path, params: BuildParams): rvs = {} for f in { f for f in (params.mod / d.relative_to(params.out)).rglob("**/*") if f.is_file() }: canon = get_canon_name(f.relative_to(params.mod), True) if params.table.is_file_modded(canon, f.read_bytes()): modified = True break else: modified = False try: s = oead.SarcWriter( endian=oead.Endianness.Big if params.be else oead.Endianness.Little ) lead = "" if (d / ".align").exists(): alignment = int((d / ".align").read_text()) s.set_mode(oead.SarcWriter.Mode.Legacy) s.set_min_alignment(alignment) (d / ".align").unlink() if (d / ".slash").exists(): lead = "/" (d / ".slash").unlink() f: Path for f in {f for f in d.rglob("**/*") if f.is_file()}: path = f.relative_to(d).as_posix() canon = path.replace(".s", ".") data = f.read_bytes() if ( f.suffix not in SARC_EXTS | AAMP_EXTS | BYML_EXTS | RSTB_EXCLUDE_EXTS and d.suffix not in {".sarc", ".ssarc"} ) and params.table.is_file_modded(canon, data, flag_new=True): rvs.update({canon: _get_rstb_val(path, data, params.guess, params.be)}) s.files[lead + path] = data f.unlink() shutil.rmtree(d) _, sb = s.write() if modified and _should_rstb(d): rvs.update( { get_canon_name(d.relative_to(params.out)): _get_rstb_val( d.name, sb, params.guess, params.be ) } ) d.write_bytes( sb if not (d.suffix.startswith(".s") and d.suffix != ".sarc") else compress(sb) ) except Exception as err: # pylint: disable=broad-except params.warning(f"Failed to build {d.relative_to(params.out).as_posix()}. {err}") return {} else: if params.verbose: print(f"Built {d.relative_to(params.out).as_posix()}") return rvs
def make_new_savedata(store: FlagStore, big_endian: bool, orig_files: list) -> bytes: svwriter = oead.SarcWriter( endian=oead.Endianness.Big if big_endian else oead.Endianness.Little) svdata_array = store.flags_to_svdata_Array() num_files = ceil(len(svdata_array) / 8192) for idx in range(num_files): start = idx * 8192 end = (idx + 1) * 8192 if end > len(svdata_array): end = len(svdata_array) svwriter.files[f"/saveformat_{idx}.bgsvdata"] = oead.byml.to_binary( oead.byml.Hash({ "file_list": oead.byml.Array([ { "IsCommon": False, "IsCommonAtSameAccount": False, "IsSaveSecureCode": True, "file_name": "game_data.sav", }, oead.byml.Array(svdata_array[start:end]), ]), "save_info": oead.byml.Array([{ "directory_num": oead.S32(num_files + 2), "is_build_machine": True, "revision": oead.S32(18203), }]), }), big_endian, ) svwriter.files[f"/saveformat_{num_files}.bgsvdata"] = orig_files[0] svwriter.files[f"/saveformat_{num_files+1}.bgsvdata"] = orig_files[1] return svwriter.write()[1]
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 _build_actor(link: Path, params: BuildParams): pack = oead.SarcWriter( endian=oead.Endianness.Big if params.be else oead.Endianness.Little ) actor_name = link.stem modified = False rvs = {} files = _parse_actor_link(link, params) for name, path in files.items(): try: data = path.read_bytes() except FileNotFoundError as e: if name.startswith("Physics"): params.warning( f"Havok physics file {name} not found for actor {actor_name}. " "Ignore if intentionally using a file not in the actor pack." ) continue params.warning( f'Failed to build actor "{actor_name}". Could not find linked file "' f'{Path(e.filename).relative_to(params.out)}".' ) return {} pack.files[name] = data canon = name.replace(".s", ".") if params.table.is_file_modded(canon, memoryview(data), True): if not modified: modified = True rvs.update({canon: _get_rstb_val(canon, data, params.guess, params.be)}) _, sb = pack.write() dest: Path if actor_name in TITLE_ACTORS | params.titles: dest = ( params.out / params.content / "Pack" / "TitleBG.pack" / "Actor" / "Pack" / f"{actor_name}.sbactorpack" ) else: dest = ( params.out / params.content / "Actor" / "Pack" / f"{actor_name}.sbactorpack" ) if not dest.parent.exists(): dest.parent.mkdir(parents=True, exist_ok=True) dest.write_bytes(compress(sb)) if modified: rvs.update( { f"Actor/Pack/{actor_name}.bactorpack": _get_rstb_val( f"{actor_name}.sbactorpack", sb, params.guess, params.be ) } ) return rvs
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 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 make_new_gamedata(store: FlagStore, big_endian: bool) -> bytes: bgwriter = oead.SarcWriter( endian=oead.Endianness.Big if big_endian else oead.Endianness.Little) for prefix, data_type in BGDATA_MAPPING.items(): bgdata_array = store.flags_to_bgdata_Array(prefix) num_files = ceil(len(bgdata_array) / 4096) for idx in range(num_files): start = idx * 4096 end = (idx + 1) * 4096 if end > len(bgdata_array): end = len(bgdata_array) bgwriter.files[f"/{prefix}_{idx}.bgdata"] = oead.byml.to_binary( oead.byml.Hash({data_type: bgdata_array[start:end]}), big_endian) return bgwriter.write()[1]
def _build_sarc(d: Path, params: BuildParams): rvs = {} for f in { f for f in (params.mod / d.relative_to(params.out)).rglob('**/*') if f.is_file() }: canon = get_canon_name(f.relative_to(params.mod), True) if params.table.is_file_modded(canon, f.read_bytes()): modified = True break else: modified = False try: s = oead.SarcWriter(endian=oead.Endianness.Big if params.be else oead. Endianness.Little) lead = '' if (d / '.align').exists(): alignment = int((d / '.align').read_text()) s.set_mode(oead.SarcWriter.Mode.Legacy) s.set_min_alignment(alignment) (d / '.align').unlink() if (d / '.slash').exists(): lead = '/' (d / '.slash').unlink() f: Path for f in {f for f in d.rglob('**/*') if f.is_file()}: path = f.relative_to(d).as_posix() s.files[lead + path] = f.read_bytes() f.unlink() shutil.rmtree(d) _, sb = s.write() if modified and _should_rstb(d): rvs.update({ get_canon_name(d.relative_to(params.out)): _get_rstb_val(d.suffix, sb, params.guess, params.be) }) d.write_bytes(sb if not (d.suffix.startswith('.s') and d.suffix != '.sarc') else compress(sb)) except: print(f'Failed to build {d.relative_to(params.out).as_posix()}') return {} else: if params.verbose: print(f'Built {d.relative_to(params.out).as_posix()}') return rvs
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 sarc_create(args: argparse.Namespace) -> None: sarc = oead.SarcWriter( oead.Endianness.Big if args.big_endian else oead.Endianness.Little) if args.folder.name == "-": raise SystemExit("You cannot pipe in a folder") for f in args.folder.glob("**/*.*"): if f.is_file(): sarc.files[f.as_posix()[len(args.folder.as_posix()) + 1:]] = f.read_bytes() if args.sarc and args.sarc.name == "!!": args.sarc = args.folder.with_suffix(".pack") if args.sarc and args.sarc.name != "-": [write_stdout(f"{f}\n".encode("utf-8")) for f in sarc.files] write_sarc(sarc, args.sarc) return
def get_bytes(self, be: bool) -> bytes: writer = oead.SarcWriter() endianness = oead.Endianness.Big if be else oead.Endianness.Little writer.set_endianness(endianness) filename = f"Actor/ActorLink/{self._actorname}.bxml" writer.files[filename] = oead.aamp.ParameterIO.to_binary( self.get_actorlink()) for link, data in self._aampfiles.items(): folder, ext = util.AAMP_LINK_REFS[link] filename = f"Actor/{folder}/{self.get_link(link)}{ext}" writer.files[filename] = oead.aamp.ParameterIO.to_binary(data) for link, data in self._bymlfiles.items(): folder, ext = util.BYML_LINK_REFS[link] filename = f"Actor/{folder}/{self.get_link(link)}{ext}" writer.files[filename] = oead.byml.to_binary(data, be) for filename, data in self._miscfiles.items(): writer.files[filename] = data return writer.write()[1]
def merge_sarcs(file_name: str, sarcs: List[Union[Path, bytes]]) -> (str, bytes): opened_sarcs: List[oead.Sarc] = [] if "ThunderRodLv2" in file_name: print() if isinstance(sarcs[0], Path): for i, sarc_path in enumerate(sarcs): sarcs[i] = sarc_path.read_bytes() for sarc_bytes in sarcs: sarc_bytes = util.unyaz_if_needed(sarc_bytes) try: opened_sarcs.append(oead.Sarc(sarc_bytes)) except (ValueError, RuntimeError, oead.InvalidDataError): continue all_files = { file.name for open_sarc in opened_sarcs for file in open_sarc.get_files() } nested_sarcs = {} new_sarc = oead.SarcWriter(endian=oead.Endianness.Big if util.get_settings( "wiiu") else oead.Endianness.Little) files_added = set() for opened_sarc in reversed(opened_sarcs): for file in [ f for f in opened_sarc.get_files() if f.name not in files_added ]: file_data = oead.Bytes(file.data) if (file.name[file.name.rindex("."):] in util.SARC_EXTS - EXCLUDE_EXTS) and file.name not in SPECIAL: if file.name not in nested_sarcs: nested_sarcs[file.name] = [] nested_sarcs[file.name].append(util.unyaz_if_needed(file_data)) elif util.is_file_modded(file.name.replace(".s", "."), file_data, count_new=True): new_sarc.files[file.name] = file_data files_added.add(file.name) util.vprint(set(nested_sarcs.keys())) for file, sarcs in nested_sarcs.items(): if not sarcs: continue merged_bytes = merge_sarcs(file, sarcs[::-1])[1] if Path(file).suffix.startswith(".s") and not file.endswith(".sarc"): merged_bytes = util.compress(merged_bytes) new_sarc.files[file] = merged_bytes files_added.add(file) for file in [file for file in all_files if file not in files_added]: for opened_sarc in [ open_sarc for open_sarc in opened_sarcs if (file in [f.name for f in open_sarc.get_files()]) ]: new_sarc.files[file] = oead.Bytes(opened_sarc.get_file(file).data) break if "Bootup.pack" in file_name: for merger in [ merger() for merger in mergers.get_mergers() if merger.is_bootup_injector() ]: inject = merger.get_bootup_injection() if not inject: continue file, data = inject new_sarc.files[file] = data return (file_name, bytes(new_sarc.write()[1]))
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 perform_merge(self): force = self._options.get("force", False) slog_path = util.get_master_modpack_dir() / "logs" / "savedata.log" new_entries = self.consolidate_diffs(self.get_all_diffs()) if not new_entries: print("No savedata merging necessary.") if slog_path.exists(): slog_path.unlink() if (util.get_master_modpack_dir() / "logs" / "savedata.sarc").exists(): (util.get_master_modpack_dir() / "logs" / "savedata.sarc").unlink() return if slog_path.exists() and not force: with slog_path.open("r") as l_file: if xxhash.xxh64_hexdigest(str(new_entries)) == l_file.read(): print("No savedata merging necessary.") return savedata = get_stock_savedata() save_files = sorted(savedata.get_files(), key=lambda f: f.name)[0:-2] print("Merging changes...") merged_entries = oead.byml.Array( sorted( { entry["HashValue"].v: entry for entry in [ *[ e for file in save_files for e in oead.byml.from_binary(file.data)["file_list"][1] ], *new_entries["add"], ] if entry not in new_entries["del"] }.values(), key=itemgetter("HashValue"), )) print("Creating and injecting new savedataformat.sarc...") new_savedata = oead.SarcWriter( endian=oead.Endianness.Big if util.get_settings("wiiu") else oead. Endianness.Little) num_files = ceil(len(merged_entries) / 8192) for i in range(num_files): end_pos = (i + 1) * 8192 if end_pos > len(merged_entries): end_pos = len(merged_entries) data = oead.byml.to_binary( oead.byml.Hash({ "file_list": oead.byml.Array([ { "IsCommon": False, "IsCommonAtSameAccount": False, "IsSaveSecureCode": True, "file_name": "game_data.sav", }, oead.byml.Array(merged_entries[i * 8192:end_pos]), ]), "save_info": oead.byml.Array([{ "directory_num": oead.S32(8), "is_build_machine": True, "revision": oead.S32(18203), }]), }), big_endian=util.get_settings("wiiu"), ) new_savedata.files[f"/saveformat_{i}.bgsvdata"] = data new_savedata.files[f"/saveformat_{num_files}.bgsvdata"] = oead.Bytes( savedata.get_file("/saveformat_6.bgsvdata").data) new_savedata.files[ f"/saveformat_{num_files + 1}.bgsvdata"] = oead.Bytes( savedata.get_file("/saveformat_7.bgsvdata").data) del savedata new_save_bytes = new_savedata.write()[1] del new_savedata util.inject_file_into_sarc( "GameData/savedataformat.ssarc", util.compress(new_save_bytes), "Pack/Bootup.pack", create_sarc=True, ) (util.get_master_modpack_dir() / "logs").mkdir(parents=True, exist_ok=True) ((util.get_master_modpack_dir() / "logs" / "savedata.sarc").write_bytes(new_save_bytes)) print("Updating RSTB...") rstable.set_size( "GameData/savedataformat.sarc", rstable.calculate_size("GameData/savedataformat.sarc", new_save_bytes), ) del new_save_bytes slog_path.parent.mkdir(parents=True, exist_ok=True) with slog_path.open("w", encoding="utf-8") as l_file: l_file.write(xxhash.xxh64_hexdigest(str(new_entries)))
def perform_merge(self): force = self._options.get("force", False) glog_path = util.get_master_modpack_dir() / "logs" / "gamedata.log" modded_entries = self.consolidate_diffs(self.get_all_diffs()) util.vprint("All gamedata diffs:") util.vprint(modded_entries) if not modded_entries: print("No gamedata merging necessary.") if glog_path.exists(): glog_path.unlink() if (util.get_master_modpack_dir() / "logs" / "gamedata.sarc").exists(): (util.get_master_modpack_dir() / "logs" / "gamedata.sarc").unlink() return if glog_path.exists() and not force: with glog_path.open("r") as l_file: if xxhash.xxh64_hexdigest( str(modded_entries)) == l_file.read(): print("No gamedata merging necessary.") return print("Loading stock gamedata...") gamedata = consolidate_gamedata(get_stock_gamedata()) merged_entries = { data_type: oead.byml.Hash({entry["DataName"]: entry for entry in entries}) for data_type, entries in gamedata.items() } del gamedata print("Merging changes...") for data_type in {d for d in merged_entries if d in modded_entries}: util.dict_merge( merged_entries[data_type], modded_entries[data_type]["add"], shallow=True, ) for entry in modded_entries[data_type]["del"]: try: del merged_entries[data_type][entry] except KeyError: continue merged_entries = oead.byml.Hash({ data_type: oead.byml.Array({value for _, value in entries.items()}) for data_type, entries in merged_entries.items() }) print("Creating and injecting new gamedata.sarc...") new_gamedata = oead.SarcWriter( endian=oead.Endianness.Big if util.get_settings("wiiu") else oead. Endianness.Little) for data_type in merged_entries: num_files = ceil(len(merged_entries[data_type]) / 4096) for i in range(num_files): end_pos = (i + 1) * 4096 if end_pos > len(merged_entries[data_type]): end_pos = len(merged_entries[data_type]) new_gamedata.files[ f"/{data_type}_{i}.bgdata"] = oead.byml.to_binary( oead.byml.Hash({ data_type: merged_entries[data_type][i * 4096:end_pos] }), big_endian=util.get_settings("wiiu"), ) new_gamedata_bytes = new_gamedata.write()[1] del new_gamedata util.inject_file_into_sarc( "GameData/gamedata.ssarc", util.compress(new_gamedata_bytes), "Pack/Bootup.pack", create_sarc=True, ) (util.get_master_modpack_dir() / "logs").mkdir(parents=True, exist_ok=True) (util.get_master_modpack_dir() / "logs" / "gamedata.sarc").write_bytes(new_gamedata_bytes) print("Updating RSTB...") rstable.set_size( "GameData/gamedata.sarc", rstable.calculate_size("GameData/gamedata.sarc", new_gamedata_bytes), ) del new_gamedata_bytes glog_path.parent.mkdir(parents=True, exist_ok=True) with glog_path.open("w", encoding="utf-8") as l_file: l_file.write(xxhash.xxh64_hexdigest(str(modded_entries)))
def _build_actor(link: Path, params: BuildParams): pack = oead.SarcWriter( endian=oead.Endianness.Big if params.be else oead.Endianness.Little) actor_name = link.stem actor = oead.aamp.ParameterIO.from_binary(link.read_bytes()) actor_path = params.out / params.content / 'Actor' targets = actor.objects['LinkTarget'] modified = False try: files = {f'Actor/ActorLink/{actor_name}.bxml': link} for p, name in targets.params.items(): name = name.v if name == 'Dummy': continue if p.hash in LINK_MAP: path = LINK_MAP[p.hash].replace('*', name) files['Actor/' + path] = actor_path / path elif p == 110127898: # ASUser list_path = actor_path / 'ASList' / f'{name}.baslist' aslist_bytes = list_path.read_bytes() files[f'Actor/ASList/{name}.baslist'] = list_path aslist = oead.aamp.ParameterIO.from_binary(aslist_bytes) for _, anim in aslist.lists['ASDefines'].objects.items(): filename = anim.params["Filename"].v if filename != 'Dummy': files[ f'Actor/AS/{filename}.bas'] = actor_path / 'AS' / f'{filename}.bas' elif p == 1086735552: # AttentionUser list_path = actor_path / 'AttClientList' / f'{name}.batcllist' atcllist_bytes = list_path.read_bytes() files[f'Actor/AttClientList/{name}.batcllist'] = list_path atcllist = oead.aamp.ParameterIO.from_binary(atcllist_bytes) for _, atcl in atcllist.lists['AttClients'].objects.items(): filename = atcl.params['FileName'].v if filename != 'Dummy': files[ f'Actor/AttClient/{filename}.batcl'] = actor_path / 'AttClient' / f'{filename}.batcl' elif p == 4022948047: # RgConfigListUser list_path = actor_path / 'RagdollConfigList' / f'{name}.brgconfiglist' rgconfiglist_bytes = list_path.read_bytes() files[ f'Actor/RagdollConfigList/{name}.brgconfiglist'] = list_path rgconfiglist = oead.aamp.ParameterIO.from_binary( rgconfiglist_bytes) for _, impl in rgconfiglist.lists[ 'ImpulseParamList'].objects.items(): filename = impl.params['FileName'].v if filename != 'Dummy': files[f'Actor/RagdollConfig/{filename}.brgconfig'] = actor_path / \ 'RagdollConfig' / f'{filename}.brgconfig' elif p == 2366604039: # PhysicsUser phys_source = params.out / params.content / 'Physics' phys_path = actor_path / 'Physics' / f'{name}.bphysics' phys_bytes = phys_path.read_bytes() files[f'Actor/Physics/{name}.bphysics'] = phys_path phys = oead.aamp.ParameterIO.from_binary(phys_bytes) types = phys.lists['ParamSet'].objects[1258832850] if types.params['use_ragdoll'].v: rg_path = str(phys.lists['ParamSet'].objects['Ragdoll']. params['ragdoll_setup_file_path'].v) files[ f'Physics/Ragdoll/{rg_path}'] = phys_source / 'Ragdoll' / rg_path if types.params['use_support_bone'].v: sb_path = str( phys.lists['ParamSet'].objects['SupportBone']. params['support_bone_setup_file_path'].v) files[ f'Physics/SupportBone/{sb_path}'] = phys_source / 'SupportBone' / sb_path if types.params['use_cloth'].v: cloth_path = str( phys.lists['ParamSet'].lists['Cloth'].objects[ 'ClothHeader'].params['cloth_setup_file_path'].v) files[ f'Physics/Cloth/{cloth_path}'] = phys_source / 'Cloth' / cloth_path if types.params['use_rigid_body_set_num'].v > 0: for _, rigid in phys.lists['ParamSet'].lists[ 'RigidBodySet'].lists.items(): try: rigid_path = str(rigid.objects[4288596824]. params['setup_file_path'].v) if (phys_path / 'RigidBody' / rigid_path).exists(): files[ f'Physics/RigidBody/{rigid_path}'] = phys_path / 'RigidBody' / rigid_path except KeyError: continue for name, path in files.items(): data = path.read_bytes() pack.files[name] = data if not modified and params.table.is_file_modded( name.replace('.s', ''), memoryview(data), True): modified = True except FileNotFoundError as e: print( f'Failed to build actor "{actor_name}": Could not find linked file "{e.filename}".' ) return {} _, sb = pack.write() dest: Path if actor_name in TITLE_ACTORS: dest = params.out / params.content / 'Pack' / 'TitleBG.pack' / 'Actor' / 'Pack' / f'{actor_name}.sbactorpack' else: dest = params.out / params.content / 'Actor' / 'Pack' / f'{actor_name}.sbactorpack' if not dest.parent.exists(): dest.parent.mkdir(parents=True, exist_ok=True) dest.write_bytes(compress(sb)) if modified: return { f'Actor/Pack/{actor_name}.bactorpack': _get_rstb_val('.sbactorpack', sb, params.guess, params.be) } return {}
def build_sarc() -> None: set_start_method("spawn", True) parser = argparse.ArgumentParser( description="Build a SARC file from a single source folder") parser.add_argument("source", help="Source folder for SARC") parser.add_argument( "output", help='Path to output SARC file, will auto compress if\ extension starts with ".s"', ) parser.add_argument("--be", "-B", help="Use big endian where applicable", action="store_true") parser.add_argument("--verbose", "-V", help="Provide more detailed output", action="store_true") args = parser.parse_args() source = Path(args.source) output = Path(args.output) def build_yaml(file: Path) -> bytes: real_file = file.with_suffix("") data: bytes if real_file.suffix in BYML_EXTS: data = oead.byml.to_binary( oead.byml.from_text(file.read_text("utf-8")), args.be) elif real_file.suffix in AAMP_EXTS: data = oead.aamp.ParameterIO.from_text( file.read_text("utf-8")).to_binary() else: raise TypeError("Can only build AAMP or BYML files from YAML") if real_file.suffix.startswith(".s"): data = oead.yaz0.compress(data) return data yml_table = {file: build_yaml(file) for file in source.rglob("**/*.yml")} all_files = { f for f in source.rglob("**/*") if f.is_file() or (f.is_dir() and f.suffix in SARC_EXTS) } nest_sarcs = {} for nest in sorted( {d for d in all_files if d.suffix in SARC_EXTS and d.is_dir()} | {source}, key=lambda x: len(x.parts), reverse=True, ): if args.verbose: print(f"Building {nest.name}...") sarc = oead.SarcWriter( oead.Endianness.Big if args.be else oead.Endianness.Little) for file in all_files.copy(): try: rel_path = file.relative_to(nest) assert file != nest except (ValueError, AssertionError): continue store_path = (rel_path.as_posix() if rel_path.suffix != ".yml" else rel_path.with_suffix("").as_posix()) if args.verbose: print(f" Adding {store_path} to {nest.name}") sarc.files[store_path] = yml_table.pop( file, nest_sarcs.pop(file, None) or file.read_bytes()) all_files.remove(file) if nest.suffix.startswith(".s") and nest.suffix != ".sarc": sarc_data = oead.yaz0.compress(sarc.write()[1]) else: sarc_data = sarc.write()[1] nest_sarcs[nest] = sarc_data if args.verbose: print(f"Finished building {nest.name}") output.write_bytes(nest_sarcs[source])