def get_modded_msyts(msg_sarc: sarc.SARC, lang: str = 'USen', tmp_dir: Path = util.get_work_dir() / 'tmp_text') -> (list, dict): """ Gets a list of modified game text files in a given message SARC :param msg_sarc: The message SARC to scan for changes. :type msg_sarc: class:`sarc.SARC` :param lang: The game language to use, defaults to USen. :type lang: str, optional :param tmp_dir: The temp directory to use, defaults to "tmp_text" in BCML's working directory. :type tmp_dir: class:`pathlib.Path`, optional :returns: Returns a tuple containing a list of modded text files and a dict of new text files with their contents. :rtype: (list of str, dict of str: bytes) """ hashes = get_msbt_hashes(lang) modded_msyts = [] added_msbts = {} write_msbts = [] for msbt in msg_sarc.list_files(): if any(exclusion in msbt for exclusion in EXCLUDE_TEXTS): continue m_data = msg_sarc.get_file_data(msbt) m_hash = xxhash.xxh32(m_data).hexdigest() if msbt not in hashes: added_msbts[msbt] = m_data elif m_hash != hashes[msbt]: write_msbts.append((tmp_dir / msbt, m_data.tobytes())) modded_msyts.append(msbt.replace('.msbt', '.msyt')) if write_msbts: pool = multiprocessing.Pool() pool.map(write_msbt, write_msbts) pool.close() pool.join() return modded_msyts, added_msbts
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 get_modded_savedata_entries(savedata: sarc.SARC) -> []: """ Gets all of the modified savedata entries in a dict of modded savedata contents. :param savedata: The saveformatdata.sarc to search for modded entries. :type savedata: class:`sarc.SARC` :return: Returns a list of modified savedata entries. :rtype: list """ ref_savedata = get_stock_savedata() ref_hashes = [] new_entries = [] for file in sorted(ref_savedata.list_files())[0:-2]: for item in byml.Byml(ref_savedata.get_file_data(file).tobytes()).parse()['file_list'][1]: ref_hashes.append(item['HashValue']) for file in sorted(savedata.list_files())[0:-2]: for item in byml.Byml(savedata.get_file_data(file).tobytes()).parse()['file_list'][1]: if item['HashValue'] not in ref_hashes: new_entries.append(item) return new_entries
def is_savedata_modded(savedata: sarc.SARC) -> {}: """ Detects if any .bgsvdata file has been modified. :param savedata: The saveformatdata.sarc to check for modification. :type savedata: class:`sarc.SARC` :returns: Returns True if any .bgsvdata in the given savedataformat.sarc has been modified. :rtype: bool """ hashes = get_savedata_hashes() sv_files = sorted(savedata.list_files()) fix_slash = '/' if not sv_files[0].startswith('/') else '' modded = False for svdata in sv_files[0:-2]: svdata_bytes = savedata.get_file_data(svdata).tobytes() svdata_hash = xxhash.xxh32(svdata_bytes).hexdigest() del svdata_bytes if not modded: modded = fix_slash + \ svdata not in hashes or svdata_hash != hashes[fix_slash + svdata] return modded
def consolidate_gamedata(gamedata: sarc.SARC) -> {}: """ Consolidates all game data in a game data SARC :return: Returns a dict of all game data entries in a SARC :rtype: dict of str: list """ data = {} pool = Pool(processes=cpu_count()) game_dict = {} for file in gamedata.list_files(): game_dict[file] = gamedata.get_file_data(file).tobytes() results = pool.map( partial(_bgdata_from_bytes, game_dict=game_dict), gamedata.list_files() ) pool.close() pool.join() del game_dict del gamedata for result in results: util.dict_merge(data, result) return data