Ejemplo n.º 1
0
def repack_archive(content_dir: Path, archive_path: Path,
                   rel_archive_dir: Path, wiiu: bool) -> bool:
    temp_archive_dir = archive_path.with_name(archive_path.name +
                                              '.PATCHER_TEMP')
    os.rename(archive_path, temp_archive_dir)

    archive = _find_sarc(content_dir / rel_archive_dir)
    if archive:
        writer = sarc.make_writer_from_sarc(archive, lambda x: True)
    else:
        writer = sarc.SARCWriter(wiiu)
    if not writer:
        return False

    for root, dirs, files in os.walk(temp_archive_dir, topdown=False):
        for file_name in files:
            host_file_path = Path(os.path.join(root, file_name))
            path_in_archive = host_file_path.relative_to(
                temp_archive_dir).as_posix()
            # For some reason, Nintendo uses paths with leading slashes in these archives. Annoying.
            if file_name == 'gamedata.ssarc' or file_name == 'savedataformat.ssarc':
                path_in_archive = '/' + path_in_archive
            with open(host_file_path, 'rb') as f:
                writer.add_file(path_in_archive, f.read())

    with open(archive_path, 'wb') as archive_file:
        writer.write(archive_file)

    if archive_path.suffix.startswith('.s'):
        sys.stderr.write('compressing...\n')
        _compress_file(archive_path)
    shutil.rmtree(temp_archive_dir)
    return True
Ejemplo n.º 2
0
def inject_savedata_into_bootup(bgsvdata: sarc.SARCWriter, bootup_path: Path = None) -> int:
    """
    Packs a savedata SARC into Bootup.pack and returns the RSTB size of the new savedataformat.sarc

    :param bgsvdata: A SARCWriter for the new savedata
    :type bgsvdata: class:`sarc.SARCWriter`
    :param bootup_path: Path to the Bootup.pack to update, defaults to a master BCML copy
    :type bootup_path: class:`pathlib.Path`, optional
    :returns: Returns the RSTB size of the new savedataformat.sarc
    :rtype: int
    """
    if not bootup_path:
        master_boot = util.get_master_modpack_dir() / 'content' / 'Pack' / 'Bootup.pack'
        bootup_path = master_boot if master_boot.exists() \
            else util.get_game_file('Pack/Bootup.pack')
    with bootup_path.open('rb') as b_file:
        bootup_pack = sarc.read_file_and_make_sarc(b_file)
    new_pack = sarc.make_writer_from_sarc(bootup_pack)
    new_pack.delete_file('GameData/savedataformat.ssarc')
    savedata_bytes = bgsvdata.get_bytes()
    new_pack.add_file('GameData/savedataformat.ssarc',
                      util.compress(savedata_bytes))
    (util.get_master_modpack_dir() / 'content' / 'Pack').mkdir(parents=True, exist_ok=True)
    with (util.get_master_modpack_dir() / 'content' / 'Pack' / 'Bootup.pack').open('wb') as b_file:
        new_pack.write(b_file)
    return rstb.SizeCalculator().calculate_file_size_with_ext(savedata_bytes, True, '.sarc')
Ejemplo n.º 3
0
def inject_file_into_bootup(file: str,
                            data: bytes,
                            create_bootup: bool = False):
    """
    Injects a file into the master BCML `Bootup.pack`

    :param file: The path of the file to inject
    :type file: str
    :param data: The bytes of the file to inject
    :type data: bytes
    :param create_bootup: Whether to create `Bootup.pack` if it does not exist, defaults to False
    :type create_bootup: bool, optional
    """
    bootup_path = get_master_modpack_dir() / 'content' / 'Pack' / 'Bootup.pack'
    if bootup_path.exists() or create_bootup:
        if not bootup_path.exists():
            bootup_path.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy(get_game_file('Pack/Bootup.pack'), bootup_path)
        with bootup_path.open('rb') as b_file:
            old_bootup = sarc.read_file_and_make_sarc(b_file)
            new_bootup = sarc.make_writer_from_sarc(old_bootup)
        if file in old_bootup.list_files():
            new_bootup.delete_file(file)
        new_bootup.add_file(file, data)
        bootup_path.write_bytes(new_bootup.get_bytes())
    else:
        raise FileNotFoundError(
            'Bootup.pack is not present in the master BCML mod')
Ejemplo n.º 4
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
Ejemplo n.º 5
0
def create_bnp_mod(mod: Path, output: Path, options: dict = None):
    """[summary]
    
    :param mod: [description]
    :type mod: Path
    :param output: [description]
    :type output: Path
    :param options: [description], defaults to {}
    :type options: dict, optional
    """
    if isinstance(mod, str):
        mod = Path(mod)
    if mod.is_file():
        print('Extracting mod...')
        tmp_dir: Path = open_mod(mod)
    elif mod.is_dir():
        print(f'Loading mod from {str(mod)}...')
        tmp_dir: Path = util.get_work_dir() / \
            f'tmp_{xxhash.xxh32(str(mod)).hexdigest()}'
        shutil.copytree(str(mod), str(tmp_dir))
    else:
        print(f'Error: {str(mod)} is neither a valid file nor a directory')
        return

    print('Packing loose files...')
    pack_folders = sorted(
        {
            d
            for d in tmp_dir.rglob('**/*')
            if d.is_dir() and d.suffix in util.SARC_EXTS
        },
        key=lambda d: len(d.parts),
        reverse=True)
    for folder in pack_folders:
        new_tmp: Path = folder.with_suffix(folder.suffix + '.tmp')
        shutil.move(folder, new_tmp)
        new_sarc = sarc.SARCWriter(be=True)
        for file in {f for f in new_tmp.rglob('**/*') if f.is_file()}:
            new_sarc.add_file(
                file.relative_to(new_tmp).as_posix(), file.read_bytes())
        sarc_bytes = new_sarc.get_bytes()
        if str(folder.suffix).startswith('.s') and folder.suffix != '.sarc':
            sarc_bytes = util.compress(sarc_bytes)
        folder.write_bytes(sarc_bytes)
        shutil.rmtree(new_tmp)

    if not options:
        options = {}
    options['texts'] = {'user_only': False}
    pool = Pool(cpu_count())
    logged_files = generate_logs(tmp_dir, options=options, original_pool=pool)

    print('Removing unnecessary files...')
    if (tmp_dir / 'logs' / 'map.yml').exists():
        print('Removing map units...')
        for file in [file for file in logged_files if isinstance(file, Path) and \
                           fnmatch(file.name, '[A-Z]-[0-9]_*.smubin')]:
            file.unlink()
    if [file for file in (tmp_dir / 'logs').glob('*texts*')]:
        print('Removing language bootup packs...')
        for bootup_lang in (tmp_dir / 'content' /
                            'Pack').glob('Bootup_*.pack'):
            bootup_lang.unlink()
    if (tmp_dir / 'logs' / 'actorinfo.yml').exists() and \
       (tmp_dir / 'content' / 'Actor' / 'ActorInfo.product.sbyml').exists():
        print('Removing ActorInfo.product.sbyml...')
        (tmp_dir / 'content' / 'Actor' / 'ActorInfo.product.sbyml').unlink()
    if (tmp_dir / 'logs' / 'gamedata.yml').exists() or (
            tmp_dir / 'logs' / 'savedata.yml').exists():
        print('Removing gamedata sarcs...')
        with (tmp_dir / 'content' / 'Pack' /
              'Bootup.pack').open('rb') as b_file:
            bsarc = sarc.read_file_and_make_sarc(b_file)
        csarc = sarc.make_writer_from_sarc(bsarc)
        bsarc_files = list(bsarc.list_files())
        if 'GameData/gamedata.ssarc' in bsarc_files:
            csarc.delete_file('GameData/gamedata.ssarc')
        if 'GameData/savedataformat.ssarc' in bsarc_files:
            csarc.delete_file('GameData/savedataformat.ssarc')
        with (tmp_dir / 'content' / 'Pack' /
              'Bootup.pack').open('wb') as b_file:
            csarc.write(b_file)

    hashes = util.get_hash_table()
    print('Creating partial packs...')
    sarc_files = {
        file
        for file in tmp_dir.rglob('**/*') if file.suffix in util.SARC_EXTS
    }
    if sarc_files:
        pool.map(partial(_clean_sarc, hashes=hashes, tmp_dir=tmp_dir),
                 sarc_files)
        pool.close()
        pool.join()

        sarc_files = {
            file
            for file in tmp_dir.rglob('**/*') if file.suffix in util.SARC_EXTS
        }
        if sarc_files:
            with (tmp_dir / 'logs' / 'packs.log').open(
                    'w', encoding='utf-8') as p_file:
                final_packs = [
                    file for file in list(tmp_dir.rglob('**/*'))
                    if file.suffix in util.SARC_EXTS
                ]
                if final_packs:
                    p_file.write('name,path\n')
                    for file in final_packs:
                        p_file.write(
                            f'{util.get_canon_name(file.relative_to(tmp_dir))},'
                            f'{file.relative_to(tmp_dir)}\n')
    else:
        if (tmp_dir / 'logs' / 'packs.log').exists():
            (tmp_dir / 'logs' / 'packs.log').unlink()

    print('Cleaning any junk files...')
    for file in tmp_dir.rglob('**/*'):
        if file.parent.stem == 'logs':
            continue
        if file.suffix in ['.yml', '.bak', '.tmp', '.old']:
            file.unlink()

    print('Removing blank folders...')
    for folder in reversed(list(tmp_dir.rglob('**/*'))):
        if folder.is_dir() and not list(folder.glob('*')):
            shutil.rmtree(folder)

    print(f'Saving output file to {str(output)}...')
    x_args = [
        str(util.get_exec_dir() / 'helpers' / '7z.exe'), 'a',
        str(output), f'{str(tmp_dir / "*")}'
    ]
    subprocess.run(x_args,
                   stdout=subprocess.PIPE,
                   stderr=subprocess.PIPE,
                   creationflags=util.CREATE_NO_WINDOW)
    print('Conversion complete.')