Пример #1
0
def get_msbt_hashes(lang: str = 'USen') -> {}:
    """
    Gets the MSBT hash table for the given language, or US English by default

    :param lang: The game language to use, defaults to USen.
    :type lang: str, optional
    :returns: A dictionary of MSBT files and their vanilla hashes.
    :rtype: dict of str: str
    """
    if not hasattr(get_msbt_hashes, 'texthashes'):
        get_msbt_hashes.texthashes = {}
    if lang not in get_msbt_hashes.texthashes:
        hash_table = util.get_exec_dir() / 'data' / 'msyt' / \
            f'Msg_{lang}_hashes.csv'
        if hash_table.exists():
            get_msbt_hashes.texthashes[lang] = {}
            with hash_table.open('r') as h_file:
                csv_loop = csv.reader(h_file)
                for row in csv_loop:
                    get_msbt_hashes.texthashes[lang][row[0]] = row[1]
        elif util.get_game_file(f'Pack/Bootup_{lang}.pack').exists():
            get_msbt_hashes.texthashes[lang] = {}
            with util.get_game_file(f'Pack/Bootup_{lang}.pack').open(
                    'rb') as b_file:
                bootup_pack = sarc.read_file_and_make_sarc(b_file)
            msg_bytes = util.decompress(
                bootup_pack.get_file_data(
                    f'Message/Msg_{lang}.product.ssarc').tobytes())
            msg_pack = sarc.SARC(msg_bytes)
            for msbt in msg_pack.list_files():
                get_msbt_hashes.texthashes[lang][msbt] = xxhash.xxh32(
                    msg_pack.get_file_data(msbt)).hexdigest()
    return get_msbt_hashes.texthashes[lang]
Пример #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')
Пример #3
0
def extract_ref_msyts(lang: str = 'USen',
                      for_merge: bool = False,
                      tmp_dir: Path = util.get_work_dir() / 'tmp_text'):
    """
    Extracts the reference MSYT texts for the given language to a temp dir

    :param lang: The game language to use, defaults to USen.
    :type lang: str, optional
    :param for_merge: Whether the output is to be merged (or as reference), defaults to False
    :type for_merge: bool
    :param tmp_dir: The temp directory to extract to, defaults to "tmp_text" in BCML's working
    directory.
    :type tmp_dir: class:`pathlib.Path`, optional
    """
    if tmp_dir.exists():
        shutil.rmtree(tmp_dir, ignore_errors=True)

    with util.get_game_file(f'Pack/Bootup_{lang}.pack').open('rb') as b_file:
        bootup_pack = sarc.read_file_and_make_sarc(b_file)
    msg_bytes = util.decompress(
        bootup_pack.get_file_data(
            f'Message/Msg_{lang}.product.ssarc').tobytes())
    msg_pack = sarc.SARC(msg_bytes)
    if not for_merge:
        merge_dir = tmp_dir / 'ref'
    else:
        merge_dir = tmp_dir / 'merged'
    msg_pack.extract_to_dir(str(merge_dir))
    msbt_to_msyt(merge_dir)
Пример #4
0
def merge_dungeonstatic(diffs: dict = None):
    """Merges all changes to the CDungeon Static.smubin"""
    if not diffs:
        diffs = {}
        loader = yaml.CSafeLoader
        yaml_util.add_constructors(loader)
        for mod in [mod for mod in util.get_installed_mods() \
                    if (mod.path / 'logs' / 'dstatic.yml').exists()]:
            diffs.update(
                yaml.load((mod.path / 'logs' / 'dstatic.yml').read_bytes(),
                          Loader=loader))

    if not diffs:
        return

    new_static = byml.Byml(
        util.decompress_file(
            str(util.get_game_file(
                'aoc/0010/Map/CDungeon/Static.smubin')))).parse()

    base_dungeons = [dungeon['Map'] for dungeon in new_static['StartPos']]
    for dungeon, diff in diffs.items():
        if dungeon not in base_dungeons:
            new_static['StartPos'].append(diff)
        else:
            for key, value in diff.items():
                new_static['StartPos'][base_dungeons.index(
                    dungeon)][key] = value

    output_static = util.get_master_modpack_dir() / 'aoc' / '0010' / 'Map' / \
        'CDungeon' / 'Static.smubin'
    output_static.parent.mkdir(parents=True, exist_ok=True)
    output_static.write_bytes(
        util.compress(byml.Writer(new_static, True).get_bytes()))
Пример #5
0
def get_dungeonstatic_diff(file: Path) -> dict:
    """Returns the changes made to the Static.smubin containing shrine entrance coordinates

    :param file: The Static.mubin file to diff
    :type file: class:`pathlib.Path`
    :return: Returns a dict of shrines and their updated entrance coordinates
    :rtype: dict of str: dict
    """
    base_file = util.get_game_file('aoc/0010/Map/CDungeon/Static.smubin',
                                   aoc=True)
    base_pos = byml.Byml(util.decompress_file(
        str(base_file))).parse()['StartPos']

    mod_pos = byml.Byml(util.decompress_file(str(file))).parse()['StartPos']

    base_dungeons = [dungeon['Map'] for dungeon in base_pos]
    diffs = {}
    for dungeon in mod_pos:
        if dungeon['Map'] not in base_dungeons:
            diffs[dungeon['Map']] = dungeon
        else:
            base_dungeon = base_pos[base_dungeons.index(dungeon['Map'])]
            if dungeon['Rotate'] != base_dungeon['Rotate']:
                diffs[dungeon['Map']] = {'Rotate': dungeon['Rotate']}
            if dungeon['Translate'] != base_dungeon['Translate']:
                if dungeon['Map'] not in diffs:
                    diffs[dungeon['Map']] = {}
                diffs[dungeon['Map']]['Translate'] = dungeon['Translate']

    return diffs
Пример #6
0
 def perform_merge(self):
     diffs = self.consolidate_diffs(self.get_all_diffs())
     output: Path
     static_data: Path
     try:
         util.get_aoc_dir()
         output = (util.get_master_modpack_dir() / util.get_dlc_path() /
                   ("0010" if util.get_settings("wiiu") else "") /
                   STATIC_PATH)
         static_data = util.get_game_file("Map/MainField/Static.smubin",
                                          aoc=True).read_bytes()
     except FileNotFoundError:
         output = util.get_master_modpack_dir(
         ) / "logs" / "mainstatic.smubin"
         static_data = util.get_nested_file_bytes(
             (str(util.get_game_file("Pack/Bootup.pack")) +
              "//Map/MainField/Static.smubin"),
             unyaz=False,
         )
     if not diffs:
         try:
             output.unlink()
         except:
             pass
         return
     stock_static = oead.byml.from_binary(util.decompress(static_data))
     merged = Hash()
     for cat in stock_static:
         if cat in diffs:
             items = {get_id(item): item for item in stock_static[cat]}
             util.dict_merge(items, diffs[cat])
             merged[cat] = Array([
                 item for _, item in items.items() if "remove" not in item
             ])
         else:
             merged[cat] = stock_static[cat]
     data = util.compress(
         oead.byml.to_binary(merged, big_endian=util.get_settings("wiiu")))
     output.parent.mkdir(parents=True, exist_ok=True)
     output.write_bytes(data)
     if "mainstatic" in str(output):
         util.inject_file_into_sarc(
             "Map/MainField/Static.smubin",
             data,
             "Pack/Bootup.pack",
             create_sarc=True,
         )
Пример #7
0
def get_stock_gamedata() -> sarc.SARC:
    """ Gets the contents of the unmodded gamedata.sarc """
    if not hasattr(get_stock_gamedata, 'gamedata'):
        with util.get_game_file('Pack/Bootup.pack').open('rb') as b_file:
            bootup = sarc.read_file_and_make_sarc(b_file)
        get_stock_gamedata.gamedata = sarc.SARC(util.decompress(
            bootup.get_file_data('GameData/gamedata.ssarc')))
    return get_stock_gamedata.gamedata
Пример #8
0
 def generate_diff(self, mod_dir: Path, modded_files: List[Union[str,
                                                                 Path]]):
     mod_data: bytes
     stock_data: bytes
     if (mod_dir / util.get_dlc_path() /
         ("0010" if util.get_settings("wiiu") else "") /
             STATIC_PATH) in modded_files:
         mod_data = (mod_dir / util.get_dlc_path() /
                     ("0010" if util.get_settings("wiiu") else "") /
                     STATIC_PATH).read_bytes()
         stock_data = util.get_game_file("Map/MainField/Static.smubin",
                                         aoc=True).read_bytes()
     elif (f"{util.get_content_path()}/Pack/Bootup.pack//Map/MainField/Static.smubin"
           ) in modded_files:
         mod_data = util.get_nested_file_bytes(
             (str(mod_dir / util.get_content_path() / "Pack" /
                  "Bootup.pack") + "//Map/MainField/Static.smubin"),
             unyaz=False,
         )
         stock_data = util.get_nested_file_bytes(
             (str(util.get_game_file("Pack/Bootup.pack")) +
              "//Map/MainField/Static.smubin"),
             unyaz=False,
         )
     else:
         return None
     stock_static: Hash = oead.byml.from_binary(util.decompress(stock_data))
     mod_static: Hash = oead.byml.from_binary(util.decompress(mod_data))
     diffs = Hash()
     for cat in stock_static:
         if cat not in stock_static:
             continue
         stock_items = {get_id(item): item for item in stock_static[cat]}
         mod_items = {get_id(item): item for item in mod_static[cat]}
         diffs[cat] = Hash({
             item_id: item
             for item_id, item in mod_items.items()
             if item_id not in stock_items or item != stock_items[item_id]
         })
         for item_id, item in [(i1, i2) for i1, i2 in stock_items.items()
                               if i1 not in mod_items]:
             item["remove"] = True
             diffs[cat][item_id] = item
         if not diffs[cat]:
             del diffs[cat]
     return diffs
Пример #9
0
def get_stock_actorinfo() -> dict:
    """ Gets the unmodded contents of ActorInfo.product.sbyml """
    actorinfo = util.get_game_file('Actor/ActorInfo.product.sbyml')
    with actorinfo.open('rb') as a_file:
        return byml.Byml(util.decompress(a_file.read())).parse()
Пример #10
0
    def perform_merge(self):
        shutil.rmtree(
            str(util.get_master_modpack_dir() / util.get_dlc_path() /
                ("0010" if util.get_settings("wiiu") else "") / "Map" /
                "MainField"),
            ignore_errors=True,
        )
        shutil.rmtree(
            str(util.get_master_modpack_dir() / util.get_content_path() /
                "Map" / "MainField"),
            ignore_errors=True,
        )
        log_path = util.get_master_modpack_dir() / "logs" / "map.log"
        if log_path.exists():
            log_path.unlink()
        print("Loading map mods...")
        map_diffs = self.consolidate_diffs(self.get_all_diffs())
        util.vprint("All map diffs:")
        util.vprint(map_diffs)
        if not map_diffs:
            print("No map merge necessary")
            return
        aoc_pack = (util.get_master_modpack_dir() / util.get_dlc_path() /
                    ("0010" if util.get_settings("wiiu") else "") / "Pack" /
                    "AocMainField.pack")
        if not aoc_pack.exists() or aoc_pack.stat().st_size > 0:
            print("Emptying AocMainField.pack...")
            aoc_pack.parent.mkdir(parents=True, exist_ok=True)
            aoc_pack.write_bytes(b"")

        rstb_vals = {}
        rstb_calc = rstb.SizeCalculator()
        print("Merging modded map units...")

        pool = self._pool or Pool(maxtasksperchild=500)
        rstb_results = pool.map(
            partial(merge_map, rstb_calc=rstb_calc),
            map_diffs.items(),
        )
        for result in rstb_results:
            rstb_vals[result[util.get_dlc_path()][0]] = result[
                util.get_dlc_path()][1]
            rstb_vals[result["main"][0]] = result["main"][1]
        if not self._pool:
            pool.close()
            pool.join()

        stock_static = [m for m in map_diffs if m[1] == "Static"]
        if stock_static:
            title_path = (util.get_master_modpack_dir() /
                          util.get_content_path() / "Pack" / "TitleBG.pack")
            if not title_path.exists():
                title_path.parent.mkdir(parents=True, exist_ok=True)
                shutil.copyfile(util.get_game_file("Pack/TitleBG.pack"),
                                title_path)
            title_bg: oead.SarcWriter = oead.SarcWriter.from_sarc(
                oead.Sarc(title_path.read_bytes()))
            for static in stock_static:
                del title_bg.files[
                    f"Map/MainField/{static[0]}/{static[0]}_Static.smubin"]
            title_path.write_bytes(title_bg.write()[1])
        print("Adjusting RSTB...")
        log_path.parent.mkdir(parents=True, exist_ok=True)
        with log_path.open("w", encoding="utf-8") as l_file:
            for canon, val in rstb_vals.items():
                l_file.write(f"{canon},{val}\n")
        print("Map merge complete")
Пример #11
0
def get_stock_map(map_unit: Union[Map, tuple],
                  force_vanilla: bool = False) -> Hash:
    if isinstance(map_unit, tuple):
        map_unit = Map(*map_unit)
    try:
        aoc_dir = util.get_aoc_dir()
    except FileNotFoundError:
        force_vanilla = True
    map_bytes = None
    map_path: Union[str, Path]
    if force_vanilla:
        try:
            if util.get_settings("wiiu"):
                update = util.get_update_dir()
            else:
                update = util.get_game_dir()
            map_path = (
                update / "Map/MainField/"
                f"{map_unit.section}/{map_unit.section}_{map_unit.type}.smubin"
            )
            if not map_path.exists():
                map_path = (
                    util.get_game_dir() / "Map/MainField/"
                    f"{map_unit.section}/{map_unit.section}_{map_unit.type}.smubin"
                )
            map_bytes = map_path.read_bytes()
        except FileNotFoundError:
            try:
                title_pack = oead.Sarc(
                    util.get_game_file("Pack/TitleBG.pack").read_bytes())
                map_bytes = title_pack.get_file(
                    f"Map/MainField/{map_unit.section}/{map_unit.section}_{map_unit.type}"
                    ".smubin").data
            except (KeyError, RuntimeError, AttributeError):
                map_bytes = None
    else:
        if (aoc_dir / "Pack" / "AocMainField.pack").exists():
            try:
                map_pack = oead.Sarc(
                    (aoc_dir / "Pack" / "AocMainField.pack").read_bytes())
                map_bytes = map_pack.get_file(
                    f"Map/MainField/{map_unit.section}/{map_unit.section}_{map_unit.type}"
                    ".smubin").data
            except (KeyError, RuntimeError, AttributeError):
                map_bytes = None
        if not map_bytes:
            map_path = f"Map/MainField/{map_unit.section}/{map_unit.section}_{map_unit.type}.smubin"
            try:
                map_bytes = util.get_game_file(map_path, aoc=True).read_bytes()
            except FileNotFoundError:
                try:
                    map_bytes = util.get_game_file(map_path).read_bytes()
                except FileNotFoundError:
                    try:
                        title_pack = oead.Sarc(
                            util.get_game_file(
                                "Pack/TitleBG.pack").read_bytes())
                        map_bytes = bytes(
                            title_pack.get_file(
                                f"Map/MainField/{map_unit.section}/"
                                f"{map_unit.section}_{map_unit.type}.smubin").
                            data)
                    except (KeyError, RuntimeError, AttributeError):
                        map_bytes = None
    if not map_bytes:
        raise FileNotFoundError(
            f"The stock map file {map_unit.section}_{map_unit.type}.smubin could not be found."
        )
    map_bytes = util.decompress(map_bytes)
    return oead.byml.from_binary(map_bytes)
Пример #12
0
def get_stock_map(map_unit: Union[Map, tuple],
                  force_vanilla: bool = False) -> dict:
    """
    Finds the most significant available map unit from the unmodded game and returns its
    contents as a dict.

    :param map: The map section and type.
    :type map: class:`bcml.mubin.Map`
    :return: Returns a dict representation of the requested map unit.
    :rtype: dict
    """
    if isinstance(map_unit, tuple):
        map_unit = Map(*map_unit)
    try:
        aoc_dir = util.get_aoc_dir()
    except FileNotFoundError:
        force_vanilla = True
    map_bytes = None
    if force_vanilla:
        try:
            map_path = (
                util.get_update_dir() / 'Map/MainField/'
                f'{map_unit.section}/{map_unit.section}_{map_unit.type}.smubin'
            )
            if not map_path.exists():
                map_path = (
                    util.get_game_dir() / 'Map/MainField/'
                    f'{map_unit.section}/{map_unit.section}_{map_unit.type}.smubin'
                )
            map_bytes = map_path.read_bytes()
        except FileNotFoundError:
            with util.get_game_file('Pack/TitleBG.pack').open('rb') \
                  as s_file:
                title_pack = sarc.read_file_and_make_sarc(s_file)
            if title_pack:
                try:
                    map_bytes = title_pack.get_file_data(
                        f'Map/MainField/{map_unit.section}/{map_unit.section}_{map_unit.type}'
                        '.smubin').tobytes()
                except KeyError:
                    map_bytes = None
    else:
        if (aoc_dir / 'Pack' / 'AocMainField.pack').exists():
            with (aoc_dir / 'Pack' / 'AocMainField.pack').open('rb') as s_file:
                map_pack = sarc.read_file_and_make_sarc(s_file)
            if map_pack:
                try:
                    map_bytes = map_pack.get_file_data(
                        f'Map/MainField/{map_unit.section}/{map_unit.section}_{map_unit.type}'
                        '.smubin').tobytes()
                except KeyError:
                    map_bytes = None
        if not map_bytes:
            map_path = f'Map/MainField/{map_unit.section}/{map_unit.section}_{map_unit.type}.smubin'
            try:
                map_bytes = util.get_game_file(map_path, aoc=True).read_bytes()
            except FileNotFoundError:
                try:
                    map_bytes = util.get_game_file(map_path).read_bytes()
                except FileNotFoundError:
                    with util.get_game_file('Pack/TitleBG.pack').open('rb') \
                        as s_file:
                        title_pack = sarc.read_file_and_make_sarc(s_file)
                    if title_pack:
                        try:
                            map_bytes = title_pack.get_file_data(
                                f'Map/MainField/{map_unit.section}/'
                                f'{map_unit.section}_{map_unit.type}.smubin'
                            ).tobytes()
                        except KeyError:
                            map_bytes = None
    if not map_bytes:
        raise FileNotFoundError(
            f'The stock map file {map_unit.section}_{map_unit.type}.smubin could not be found.'
        )
    map_bytes = util.decompress(map_bytes)
    return byml.Byml(map_bytes).parse()