def get_aamp_diff(file: Union[Path, str], tmp_dir: Path): """ Diffs a modded AAMP file from the stock game version :param file: The modded AAMP file to diff :type file: class:`typing.Union[class:pathlib.Path, str]` :param tmp_dir: The temp directory containing the mod :type tmp_dir: class:`pathlib.Path` :return: Returns a string representation of the AAMP file diff """ if isinstance(file, str): nests = file.split('//') mod_bytes = util.get_nested_file_bytes(file) ref_path = str(util.get_game_file( Path(nests[0]).relative_to(tmp_dir))) + '//' + '//'.join(nests[1:]) ref_bytes = util.get_nested_file_bytes(ref_path) else: with file.open('rb') as m_file: mod_bytes = m_file.read() mod_bytes = util.unyaz_if_needed(mod_bytes) with util.get_game_file(file.relative_to(tmp_dir)).open('rb') as r_file: ref_bytes = r_file.read() ref_bytes = util.unyaz_if_needed(ref_bytes) ref_aamp = aamp.Reader(ref_bytes).parse() mod_aamp = aamp.Reader(mod_bytes).parse() return _aamp_diff(ref_aamp, mod_aamp)
def aamp_to_yml(input_data: bytes) -> bytes: dumper = yaml.CDumper yu.register_representers(dumper) reader = aamp.Reader(input_data, track_strings=True) root = reader.parse() dumper.__aamp_reader = reader return yaml.dump(root, Dumper=dumper, allow_unicode=True, encoding='utf-8', default_flow_style=None)
def dump_to_csv(content_dir: Path, predicate: ActorPredicate, props: typing.Iterable[Prop], f: typing.TextIO) -> None: actorpack_dir = (content_dir / 'Actor/Pack') writer = csv.writer(f) header = ['Actor', 'Name'] header.extend(prop.column_name for prop in props) writer.writerow(header) for actorpack in actorpack_dir.glob('*'): actor_name = actorpack.stem if not predicate(actor_name): continue try: bgparamlist_p = next( (actorpack / 'Actor/GeneralParamList').glob('*.bgparamlist')) pio = aamp.Reader(bgparamlist_p.open('rb').read()).parse() proot = pio.list('param_root') cols = [actor_name, _names.get(actor_name, '?')] for prop in props: value = proot.object(prop.obj).param(prop.param) cols.append(prop.format_fn(value)) writer.writerow(cols) except StopIteration: continue
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 dump_aamp(data: bytes): class Dumper(yaml.CDumper): pass reader = aamp.Reader(data, track_strings=True) aamp_root = reader.parse() aamp.yaml_util.register_representers(Dumper) Dumper.__aamp_reader = reader return yaml.dump(aamp_root, Dumper=Dumper, allow_unicode=True, encoding="utf-8")
def nested_patch(pack: sarc.SARC, nest: dict) -> (sarc.SARCWriter, dict): """ Recursively patches deep merge files in a SARC :param pack: The SARC in which to recursively patch. :type pack: class:`sarc.SARC` :param nest: A dict of nested patches to apply. :type nest: dict :return: Returns a new SARC with patches applied and a dict of any failed patches. :rtype: (class:`sarc.SARCWriter`, dict, dict) """ new_sarc = sarc.make_writer_from_sarc(pack) failures = {} for file, stuff in nest.items(): file_bytes = pack.get_file_data(file).tobytes() yazd = file_bytes[0:4] == b'Yaz0' file_bytes = util.decompress(file_bytes) if yazd else file_bytes if isinstance(stuff, dict): sub_sarc = sarc.SARC(file_bytes) new_sarc.delete_file(file) 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 = new_sub_sarc.get_bytes() new_sarc.add_file( file, new_bytes if not yazd else util.compress(new_bytes)) elif isinstance(stuff, list): try: if file_bytes[0:4] == 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) cache_merged_aamp(file, new_bytes) else: raise ValueError( 'Wait, what the heck, this isn\'t an AAMP file?!') except ValueError: new_bytes = pack.get_file_data(file).tobytes() print(f'Deep merging {file} failed. No changed were made.') new_sarc.delete_file(file) new_sarc.add_file(file, new_bytes) return new_sarc, failures
def load_actor_aiprog(self, actor_name: str) -> bool: if not _rom_path: return False rel_roots = ( '', 'Pack/Bootup.pack/', 'Pack/TitleBG.pack/', 'Pack/RemainsWind.pack/', 'Pack/RemainsElectric.pack/', 'Pack/RemainsWater.pack/', 'Pack/RemainsFire.pack/', ) for rel_root in rel_roots: root = _rom_path / rel_root aiprog_dir = root / 'Actor' / 'Pack' / f'{actor_name}.sbactorpack' / 'Actor' / 'AIProgram' for path in _list_aiprog_files(aiprog_dir): with open(path, 'rb') as aiprog: pio = aamp.Reader(aiprog.read()).parse() if self._do_load_actor_aiprog(pio): return True return False
def test_aamp_from_bin_aamp(benchmark, file): benchmark.group = "from_bin: " + file benchmark(lambda d: aamp.Reader(d).parse(), data[file])