Example #1
0
    def generate_diff(self, mod_dir: Path,
                      modded_files: List[Union[Path, str]]) -> ParameterIO:
        print("Logging changes to shop files...")
        diffs = ParameterIO()
        file_names = ParameterObject()
        for file in [
                file for file in modded_files
                if Path(file).suffix in EXT_FOLDERS
        ]:
            try:
                mod_bytes = util.get_nested_file_bytes(
                    str(mod_dir) + "/" + str(file))
                nests = str(file).split("//", 1)
                try:
                    ref_path = str(util.get_game_file(Path(
                        nests[0]))) + "//" + nests[1]
                except FileNotFoundError:
                    continue
                try:
                    ref_bytes = util.get_nested_file_bytes(ref_path)
                except AttributeError:
                    continue
                shop_type = str(file).split(".")[-1]

                mod_pio = get_named_pio(ParameterIO.from_binary(mod_bytes),
                                        shop_type)
                ref_pio = get_named_pio(ParameterIO.from_binary(ref_bytes),
                                        shop_type)

                file_names.params[oead.aamp.Name(file).hash] = Parameter(file)
                diffs.lists[file] = gen_diffs(ref_pio, mod_pio)
            except (KeyError, AttributeError) as err:
                raise err
        diffs.objects["Filenames"] = file_names
        return diffs
Example #2
0
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)
Example #3
0
def log_drop_file(file: str, mod_dir: Path):
    if "Bootup.pack" in file:
        return {}
    drop = ParameterIO.from_binary(
        util.get_nested_file_bytes(str(mod_dir) + "/" + file))
    drop_table = _drop_to_dict(drop)
    del drop
    try:
        base_file = file[:file.index("//")]
        sub_file = file[file.index("//"):]
        ref_drop = ParameterIO.from_binary(
            util.get_nested_file_bytes(
                str(util.get_game_file(base_file)) + sub_file))
        ref_table = _drop_to_dict(ref_drop)
        del ref_drop
        for table, contents in drop_table.items():
            if table not in ref_table:
                continue
            for item, prob in {(i, p)
                               for i, p in contents["items"].items()
                               if i in ref_table[table]["items"]}:
                if prob == ref_table[table]["items"][item]:
                    drop_table[table]["items"][item] = util.UNDERRIDE
        del ref_table
    except (
            FileNotFoundError,
            oead.InvalidDataError,
            AttributeError,
            RuntimeError,
            ValueError,
    ):
        util.vprint(f"Could not load stock {file}")
    return {file: drop_table}
Example #4
0
def merge_drop_file(file: str, drop_table: dict):
    base_path = file[:file.index("//")]
    sub_path = file[file.index("//"):]
    try:
        ref_drop = _drop_to_dict(
            ParameterIO.from_binary(
                util.get_nested_file_bytes(
                    str(util.get_game_file(base_path)) + sub_path)))
        for table in set(ref_drop.keys()):
            if table not in drop_table:
                del ref_drop[table]
            else:
                for item in set(ref_drop[table]["items"].keys()):
                    if item not in drop_table[table]["items"]:
                        del ref_drop[table]["items"][item]
        util.dict_merge(ref_drop, drop_table)
        drop_table = ref_drop
    except (FileNotFoundError, AttributeError, RuntimeError):
        pass
    actor_name_matches = re.search(r"Pack\/(.+)\.sbactorpack", file)
    if actor_name_matches:
        actor_name = actor_name_matches.groups()[0]
    else:
        raise ValueError(f"No actor name found in {file}")
    pio = _dict_to_drop(drop_table)
    util.inject_files_into_actor(actor_name,
                                 {file.split("//")[-1]: pio.to_binary()})
Example #5
0
    def perform_merge(self):
        merged_quests = util.get_master_modpack_dir() / "logs" / "quests.byml"
        print("Loading quest mods...")
        diffs = self.consolidate_diffs(self.get_all_diffs())
        if not diffs:
            print("No quest merging necessary")
            if merged_quests.exists():
                merged_quests.unlink()
                try:
                    util.inject_file_into_sarc(
                        "Quest/QuestProduct.sbquestpack",
                        util.get_nested_file_bytes(
                            (f'{str(util.get_game_file("Pack/TitleBG.pack"))}'
                             "//Quest/QuestProduct.sbquestpack"),
                            unyaz=False,
                        ),
                        "Pack/TitleBG.pack",
                    )
                except FileNotFoundError:
                    pass
            return
        print("Loading stock quests...")
        quests = get_stock_quests()
        stock_names = [q["Name"] for q in quests]

        print("Merging quest mods...")
        for name, mod in diffs["mod"].items():
            try:
                quests[stock_names.index(name)] = mod
            except (IndexError, ValueError):
                diffs["add"].append(mod)
        for delete in reversed(diffs["del"]):
            try:
                quests.remove(quests[stock_names.index(delete)])
            except ValueError:
                pass
            except IndexError:
                raise RuntimeError(
                    f"An error occurred when attempting to remove a quest from your "
                    "game. Most of the time this means the mod was accidentally made "
                    "using the base game 1.0 TitleBG.pack instead of the latest updated "
                    "version. Please contact the mod author for assistance.")
        added_names = set()
        for add in diffs["add"]:
            if add["Name"] not in added_names:
                quests.append(add)
                added_names.add(add["Name"])

        print("Writing new quest pack...")
        data = oead.byml.to_binary(quests,
                                   big_endian=util.get_settings("wiiu"))
        merged_quests.parent.mkdir(parents=True, exist_ok=True)
        merged_quests.write_bytes(data)
        util.inject_file_into_sarc(
            "Quest/QuestProduct.sbquestpack",
            util.compress(data),
            "Pack/TitleBG.pack",
            create_sarc=True,
        )
Example #6
0
    def perform_merge(self):
        merged_events = util.get_master_modpack_dir() / "logs" / "eventinfo.byml"
        event_merge_log = util.get_master_modpack_dir() / "logs" / "eventinfo.log"

        print("Loading event info mods...")
        modded_events = self.consolidate_diffs(self.get_all_diffs())
        event_mod_hash = hash(str(modded_events))
        if not modded_events:
            print("No event info merging necessary")
            if merged_events.exists():
                merged_events.unlink()
                event_merge_log.unlink()
                try:
                    stock_eventinfo = util.get_nested_file_bytes(
                        (
                            str(util.get_game_file("Pack/Bootup.pack"))
                            + "//Event/EventInfo.product.sbyml"
                        ),
                        unyaz=False,
                    )
                    util.inject_file_into_sarc(
                        "Event/EventInfo.product.sbyml",
                        stock_eventinfo,
                        "Pack/Bootup.pack",
                    )
                except FileNotFoundError:
                    pass
            return
        if event_merge_log.exists() and event_merge_log.read_text() == event_mod_hash:
            print("No event info merging necessary")
            return

        new_events = get_stock_eventinfo()
        for event, data in modded_events.items():
            new_events[event] = data
        del modded_events

        print("Writing new event info...")
        event_bytes = oead.byml.to_binary(
            new_events, big_endian=util.get_settings("wiiu")
        )
        del new_events
        util.inject_file_into_sarc(
            "Event/EventInfo.product.sbyml",
            util.compress(event_bytes),
            "Pack/Bootup.pack",
            create_sarc=True,
        )
        print("Saving event info merge log...")
        event_merge_log.parent.mkdir(parents=True, exist_ok=True)
        event_merge_log.write_text(str(event_mod_hash))
        merged_events.write_bytes(event_bytes)

        print("Updating RSTB...")
        rstb_size = rstb.SizeCalculator().calculate_file_size_with_ext(
            bytes(event_bytes), True, ".byml"
        )
        del event_bytes
        rstable.set_size("Event/EventInfo.product.byml", rstb_size)
Example #7
0
def get_stock_eventinfo() -> {}:
    """ Gets the contents of the stock `EventInfo.product.sbyml` """
    if not hasattr(get_stock_eventinfo, 'event_info'):
        get_stock_eventinfo.event_info = byml.Byml(
            util.get_nested_file_bytes(
                str(util.get_game_file('Pack/Bootup.pack')) + '//Event/EventInfo.product.sbyml',
                unyaz=True
            )
        ).parse()
    return deepcopy(get_stock_eventinfo.event_info)
Example #8
0
    def perform_merge(self):
        merged_effects = util.get_master_modpack_dir() / "logs" / "effects.byml"
        print("Loading status effect mods...")
        diffs = self.consolidate_diffs(self.get_all_diffs())
        if not diffs:
            print("No status effect merging necessary...")
            if merged_effects.exists():
                merged_effects.unlink()
                try:
                    stock_effects = util.get_nested_file_bytes(
                        (
                            str(util.get_game_file("Pack/Bootup.pack"))
                            + "//Ecosystem/StatusEffectList.sbyml"
                        ),
                        unyaz=False,
                    )
                    util.inject_file_into_sarc(
                        "Ecosystem/StatusEffectList.sbyml",
                        stock_effects,
                        "Pack/Bootup.pack",
                    )
                    del stock_effects
                except FileNotFoundError:
                    pass
            return
        util.vprint("All status effect diffs:")
        util.vprint(diffs)

        effects = get_stock_effects()
        util.dict_merge(effects, diffs, overwrite_lists=True)
        del diffs

        print("Writing new effects list...")
        effect_bytes = oead.byml.to_binary(
            oead.byml.Array([effects]), big_endian=util.get_settings("wiiu")
        )
        del effects
        util.inject_file_into_sarc(
            "Ecosystem/StatusEffectList.sbyml",
            util.compress(effect_bytes),
            "Pack/Bootup.pack",
            create_sarc=True,
        )
        print("Saving status effect merge log...")
        merged_effects.parent.mkdir(parents=True, exist_ok=True)
        merged_effects.write_bytes(effect_bytes)

        print("Updating RSTB...")
        rstb_size = rstb.SizeCalculator().calculate_file_size_with_ext(
            effect_bytes, True, ".byml"
        )
        del effect_bytes
        rstable.set_size("Ecosystem/StatusEffectList.byml", rstb_size)
Example #9
0
    def perform_merge(self):
        merged_quests = util.get_master_modpack_dir() / "logs" / "quests.byml"
        print("Loading quest mods...")
        diffs = self.consolidate_diffs(self.get_all_diffs())
        if not diffs:
            print("No quest merging necessary")
            if merged_quests.exists():
                merged_quests.unlink()
                try:
                    util.inject_file_into_sarc(
                        "Quest/QuestProduct.sbquestpack",
                        util.get_nested_file_bytes(
                            (f'{str(util.get_game_file("Pack/TitleBG.pack"))}'
                             "//Quest/QuestProduct.sbquestpack"),
                            unyaz=False,
                        ),
                        "Pack/TitleBG.pack",
                    )
                except FileNotFoundError:
                    pass
            return
        print("Loading stock quests...")
        quests = get_stock_quests()
        stock_names = [q["Name"] for q in quests]

        print("Merging quest mods...")
        for name, mod in diffs["mod"].items():
            try:
                quests[stock_names.index(name)] = mod
            except (IndexError, ValueError):
                diffs["add"].append(mod)
        for delete in reversed(diffs["del"]):
            try:
                quests.remove(quests[stock_names.index(delete)])
            except ValueError:
                pass
        added_names = set()
        for add in diffs["add"]:
            if add["Name"] not in added_names:
                quests.append(add)
                added_names.add(add["Name"])

        print("Writing new quest pack...")
        data = oead.byml.to_binary(quests,
                                   big_endian=util.get_settings("wiiu"))
        merged_quests.parent.mkdir(parents=True, exist_ok=True)
        merged_quests.write_bytes(data)
        util.inject_file_into_sarc(
            "Quest/QuestProduct.sbquestpack",
            util.compress(data),
            "Pack/TitleBG.pack",
            create_sarc=True,
        )
Example #10
0
def _convert_actorpack(actor_pack: Path, to_wiiu: bool) -> Union[None, str]:
    error = None
    sarc = oead.Sarc(util.unyaz_if_needed(actor_pack.read_bytes()))
    new_sarc = oead.SarcWriter.from_sarc(sarc)
    new_sarc.set_endianness(oead.Endianness.Big if to_wiiu else oead.Endianness.Little)
    for file in sarc.get_files():
        if "Physics/" in file.name and "Actor/" not in file.name:
            ext = file.name[file.name.rindex(".") :]
            if ext in NO_CONVERT_EXTS:
                if not util.is_file_modded(
                    util.get_canon_name(file.name, allow_no_source=True),
                    file.data,
                    count_new=True,
                ):
                    actor_name = file.name[
                        file.name.rindex("/") : file.name.rindex(".")
                    ]
                    try:
                        pack_path = util.get_game_file(
                            f"Actor/Pack/{actor_name}.sbactorpack"
                        )
                        stock_data = util.get_nested_file_bytes(
                            f"{str(pack_path)}//{file.name}"
                        )
                        if stock_data:
                            new_sarc.files[file.name] = stock_data
                        else:
                            raise FileNotFoundError(file.name)
                    except (FileNotFoundError, AttributeError):
                        error = (
                            "This mod contains a Havok file not supported by the "
                            f"converter: {file.name}"
                        )
                else:
                    error = (
                        "This mod contains a Havok file not supported by the"
                        f" converter: {file.name}"
                    )
            else:
                if file.data[0:4] == b"AAMP":
                    continue
                try:
                    hk = Havok.from_bytes(bytes(file.data))
                except:  # pylint: disable=bare-except
                    return f"Could not parse Havok file {file.name}"
                if to_wiiu:
                    hk.to_wiiu()
                else:
                    hk.to_switch()
                hk.serialize()
                new_sarc.files[file.name] = hk.to_bytes()
    actor_pack.write_bytes(util.compress(new_sarc.write()[1]))
    return error
Example #11
0
def get_stock_eventinfo() -> oead.byml.Hash:
    if not hasattr(get_stock_eventinfo, "event_info"):
        get_stock_eventinfo.event_info = oead.byml.to_text(
            oead.byml.from_binary(
                util.get_nested_file_bytes(
                    str(util.get_game_file("Pack/Bootup.pack"))
                    + "//Event/EventInfo.product.sbyml",
                    unyaz=True,
                )
            )
        )
    return oead.byml.from_text(get_stock_eventinfo.event_info)
Example #12
0
def merge_events():
    """ Merges all installed event info mods """
    event_mods = [mod for mod in util.get_installed_mods() \
                  if (mod.path / 'logs' / 'eventinfo.yml').exists()]
    merged_events = util.get_master_modpack_dir() / 'logs' / 'eventinfo.byml'
    event_merge_log = util.get_master_modpack_dir() / 'logs' / 'eventinfo.log'
    event_mod_hash = str(hash(tuple(event_mods)))

    if not event_mods:
        print('No event info merging necessary')
        if merged_events.exists():
            merged_events.unlink()
            event_merge_log.unlink()
            try:
                stock_eventinfo = util.get_nested_file_bytes(
                    str(util.get_game_file('Pack/Bootup.pack')) + '//Event/EventInfo.product.sbyml',
                    unyaz=False
                )
                util.inject_file_into_bootup(
                    'Event/EventInfo.product.sbyml',
                    stock_eventinfo
                )
            except FileNotFoundError:
                pass
        return
    if event_merge_log.exists() and event_merge_log.read_text() == event_mod_hash:
        print('No event info merging necessary')
        return

    print('Loading event info mods...')
    modded_events = {}
    for mod in event_mods:
        modded_events.update(get_events_for_mod(mod))
    new_events = get_stock_eventinfo()
    for event, data in modded_events.items():
        new_events[event] = data

    print('Writing new event info...')
    event_bytes = byml.Writer(new_events, be=True).get_bytes()
    util.inject_file_into_bootup(
        'Event/EventInfo.product.sbyml',
        util.compress(event_bytes),
        create_bootup=True
    )
    print('Saving event info merge log...')
    event_merge_log.write_text(event_mod_hash)
    merged_events.write_bytes(event_bytes)

    print('Updating RSTB...')
    rstb_size = rstb.SizeCalculator().calculate_file_size_with_ext(event_bytes, True, '.byml')
    rstable.set_size('Event/EventInfo.product.byml', rstb_size)
Example #13
0
 def rem_underride(data: dict):
     for file, tables in data.items():
         stock: Optional[dict] = None
         for name, table in tables.items():
             for actor, prob in table["items"].items():
                 if prob == util.UNDERRIDE:
                     if stock == None:
                         base_file = file[:file.index("//")]
                         sub_file = file[file.index("//"):]
                         ref_drop = ParameterIO.from_binary(
                             util.get_nested_file_bytes(
                                 str(util.get_game_file(base_file)) +
                                 sub_file))
                         stock = _drop_to_dict(ref_drop)
                     data[file][name]["items"][actor] = stock[name][
                         "items"][actor]
Example #14
0
def get_stock_areadata() -> oead.byml.Hash:
    if not hasattr(get_stock_areadata, "areadata"):
        get_stock_areadata.areadata = oead.byml.to_text(
            oead.byml.Hash(
                {
                    str(area["AreaNumber"].v): area
                    for area in oead.byml.from_binary(
                        util.get_nested_file_bytes(
                            str(util.get_game_file("Pack/Bootup.pack"))
                            + "//Ecosystem/AreaData.sbyml",
                            unyaz=True,
                        )
                    )
                }
            )
        )
    return oead.byml.from_text(get_stock_areadata.areadata)
Example #15
0
    def perform_merge(self):
        merged_areadata = util.get_master_modpack_dir() / "logs" / "areadata.byml"
        areadata_merge_log = util.get_master_modpack_dir() / "logs" / "areadata.log"

        print("Loading area data mods...")
        modded_areadata = self.consolidate_diffs(self.get_all_diffs())
        areadata_mod_hash = hash(str(modded_areadata))
        if not modded_areadata:
            print("No area data merging necessary")
            if merged_areadata.exists():
                merged_areadata.unlink()
                areadata_merge_log.unlink()
                try:
                    stock_areadata = util.get_nested_file_bytes(
                        (
                            str(util.get_game_file("Pack/Bootup.pack"))
                            + "//Ecosystem/AreaData.sbyml"
                        ),
                        unyaz=False,
                    )
                    util.inject_file_into_sarc(
                        "Ecosystem/AreaData.sbyml",
                        stock_areadata,
                        "Pack/Bootup.pack",
                    )
                except FileNotFoundError:
                    pass
            return
        if (
            areadata_merge_log.exists()
            and areadata_merge_log.read_text() == areadata_mod_hash
        ):
            print("No area data merging necessary")
            return

        new_areadata = get_stock_areadata()
        util.dict_merge(new_areadata, modded_areadata, overwrite_lists=True)

        print("Writing new area data...")
        areadata_bytes = oead.byml.to_binary(
            oead.byml.Array(
                [v for _, v in sorted(new_areadata.items(), key=lambda x: int(x[0]))]
            ),
            big_endian=util.get_settings("wiiu"),
        )
        del new_areadata
        util.inject_file_into_sarc(
            "Ecosystem/AreaData.sbyml",
            util.compress(areadata_bytes),
            "Pack/Bootup.pack",
            create_sarc=True,
        )
        print("Saving area data merge log...")
        areadata_merge_log.parent.mkdir(parents=True, exist_ok=True)
        areadata_merge_log.write_text(str(areadata_mod_hash))
        merged_areadata.write_bytes(areadata_bytes)

        print("Updating RSTB...")
        rstb_size = rstb.SizeCalculator().calculate_file_size_with_ext(
            bytes(areadata_bytes), True, ".byml"
        )
        del areadata_bytes
        rstable.set_size("Ecosystem/AreaData.byml", rstb_size)