示例#1
0
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
示例#2
0
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
示例#3
0
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
示例#4
0
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
示例#5
0
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