Пример #1
0
    def perform_merge(self):
        # pylint: disable=unsupported-assignment-operation
        user_langs = (
            {util.get_settings("lang")}
            if not self._options["all_langs"]
            else util.get_user_languages()
        )
        print("Loading text mods...")
        diffs = self.consolidate_diffs(self.get_all_diffs())
        if not diffs:
            print("No text merge necessary")
            for bootup in util.get_master_modpack_dir().rglob("**/Bootup_????.pack"):
                bootup.unlink()
            return

        # find a mod lang for each user lang
        lang_map = map_languages(user_langs, set(diffs.keys()))
        for user_lang, mod_lang in lang_map.items():
            print(f"Merging modded texts for {mod_lang} into {user_lang}...")
            rsext.mergers.texts.merge_language(
                json.dumps(diffs[mod_lang]),
                str(util.get_game_file(f"Pack/Bootup_{user_lang}.pack")),
                str(
                    util.get_master_modpack_dir()
                    / util.get_content_path()
                    / "Pack"
                    / f"Bootup_{user_lang}.pack"
                ),
                util.get_settings("wiiu"),
            )
            print(f"{user_lang} texts merged successfully")
Пример #2
0
def log_merged_files_rstb():
    """ Generates an RSTB log for the master BCML modpack containing merged files """
    print('Updating RSTB for merged files...')
    diffs = {}
    files = [item for item in util.get_master_modpack_dir().rglob('**/*') if item.is_file()]
    guess = util.get_settings_bool('guess_merge')
    for file in files:
        if file.parent == 'logs':
            continue
        if file.suffix not in RSTB_EXCLUDE_EXTS and file.name not in RSTB_EXCLUDE_NAMES:
            size = calculate_size(file)
            if size == 0 and guess:
                if file.suffix in util.AAMP_EXTS:
                    size = guess_aamp_size(file)
                elif file.suffix in ['.bfres', '.sbfres']:
                    size = guess_bfres_size(file)
            canon = util.get_canon_name(file.relative_to(util.get_master_modpack_dir()))
            if canon:
                diffs[canon] = size
    sarc_files = [file for file in files if util.is_file_sarc(str(file)) \
                  and file.suffix != '.ssarc']
    if sarc_files:
        num_threads = min(multiprocessing.cpu_count(), len(sarc_files))
        pool = multiprocessing.Pool(processes=num_threads)
        results = pool.map(_get_sizes_in_sarc, sarc_files)
        pool.close()
        pool.join()
        for result in results:
            diffs.update(result)
    with (util.get_master_modpack_dir() / 'logs' / 'rstb.log').open('w', encoding='utf-8') as log:
        log.write('name,size,path\n')
        for canon, size in diffs.items():
            log.write(f'{canon},{size},//\n')
Пример #3
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)
Пример #4
0
def threaded_merge(item, verbose: bool) -> (str, dict):
    """Deep merges an individual file, suitable for multiprocessing"""
    file, stuff = item
    failures = {}

    base_file = util.get_game_file(file, file.startswith('aoc'))
    if (util.get_master_modpack_dir() / file).exists():
        base_file = util.get_master_modpack_dir() / file
    file_ext = os.path.splitext(file)[1]
    if file_ext in util.SARC_EXTS and (util.get_master_modpack_dir() /
                                       file).exists():
        base_file = (util.get_master_modpack_dir() / file)
    file_bytes = base_file.read_bytes()
    yazd = file_bytes[0:4] == b'Yaz0'
    file_bytes = file_bytes if not yazd else util.decompress(file_bytes)
    magic = file_bytes[0:4]

    if magic == b'SARC':
        new_sarc, sub_failures = nested_patch(sarc.SARC(file_bytes), stuff)
        del file_bytes
        new_bytes = new_sarc.get_bytes()
        for failure, contents in sub_failures.items():
            print(f'Some patches to {failure} failed to apply.')
            failures[failure] = contents
    else:
        try:
            if magic == 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)
            else:
                raise ValueError(f'{file} is not a SARC or AAMP file.')
        except ValueError:
            new_bytes = file_bytes
            del file_bytes
            print(f'Deep merging file {file} failed. No changes were made.')

    new_bytes = new_bytes if not yazd else util.compress(new_bytes)
    output_file = (util.get_master_modpack_dir() / file)
    if base_file == output_file:
        output_file.unlink()
    output_file.parent.mkdir(parents=True, exist_ok=True)
    output_file.write_bytes(new_bytes)
    del new_bytes
    if magic == b'SARC' and verbose:
        print(f'Finished patching files inside {file}')
    elif verbose:
        print(f'Finished patching {file}')
    return util.get_canon_name(file), failures
Пример #5
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)
Пример #6
0
def merge_aamp_files(file: str, tree: dict):
    try:
        base_file = util.get_game_file(file)
    except FileNotFoundError:
        util.vprint(f"Skipping {file}, not found in dump")
        return
    if (util.get_master_modpack_dir() / file).exists():
        base_file = util.get_master_modpack_dir() / file
    sarc = Sarc(util.unyaz_if_needed(base_file.read_bytes()))
    new_data = _merge_in_sarc(sarc, tree)
    if base_file.suffix.startswith(".s") and base_file.suffix != ".ssarc":
        new_data = util.compress(new_data)
    (util.get_master_modpack_dir() / file).parent.mkdir(parents=True,
                                                        exist_ok=True)
    (util.get_master_modpack_dir() / file).write_bytes(new_data)
Пример #7
0
 def get_bootup_injection(self):
     tmp_sarc = util.get_master_modpack_dir() / "logs" / "gamedata.sarc"
     if tmp_sarc.exists():
         return ("GameData/gamedata.ssarc",
                 util.compress(tmp_sarc.read_bytes()))
     else:
         return
Пример #8
0
def uninstall_mod(mod: BcmlMod, wait_merge: bool = False):
    print(f"Uninstalling {mod.name}...")
    try:
        shutil.rmtree(str(mod.path))
    except (OSError, PermissionError) as err:
        raise RuntimeError(
            f"The folder for {mod.name} could not be removed. "
            "You may need to delete it manually and remerge, or "
            "close all open programs (including BCML and Windows Explorer) "
            "and try again. The location of the folder is "
            f"<code>{str(mod.path)}</code>.") from err

    for fall_mod in [
            m for m in util.get_installed_mods(True)
            if m.priority > mod.priority
    ]:
        fall_mod.change_priority(fall_mod.priority - 1)

    if not util.get_installed_mods():
        shutil.rmtree(util.get_master_modpack_dir())
        util.create_bcml_graphicpack_if_needed()
    else:
        if not wait_merge:
            refresh_merges()

    print(f"{mod.name} has been uninstalled.")
Пример #9
0
    def perform_merge(self):
        print("Loading modded SARC list...")
        sarcs = {
            s: ss
            for s, ss in self.consolidate_diffs(self.get_all_diffs()).items()
            if ss
        }
        for file in [
            file
            for file in util.get_master_modpack_dir().rglob("**/*")
            if file.suffix in util.SARC_EXTS - EXCLUDE_EXTS
            and not any(ex in file.name for ex in SPECIAL)
        ]:
            file.unlink()
        for sarc_file in sarcs:
            try:
                sarcs[sarc_file].insert(0, util.get_game_file(sarc_file))
            except FileNotFoundError:
                continue
        if not sarcs:
            print("No SARC merging necessary")
            return
        print(f"Merging {len(sarcs)} SARC files...")
        from bcml import bcml as rsext

        rsext.mergers.packs.merge_sarcs(sarcs)
        # pool = self._pool or Pool(maxtasksperchild=500)
        # results = pool.starmap(merge_sarcs, sarcs.items())
        # pool.starmap(write_sarc, results)
        # if not self._pool:
        #     pool.close()
        #     pool.join()
        print("Finished merging SARCs")
Пример #10
0
def uninstall_mod(mod: BcmlMod, wait_merge: bool = False):
    has_patches = (mod.path / "patches").exists()
    try:
        shutil.rmtree(str(mod.path), onerror=force_del)
    except (OSError, PermissionError, WindowsError) as err:
        raise RuntimeError(
            f"The folder for {mod.name} could not be removed. "
            "You may need to delete it manually and remerge, or "
            "close all open programs (including BCML and Windows Explorer) "
            "and try again. The location of the folder is "
            f"<code>{str(mod.path)}</code>.") from err

    for fall_mod in [
            m for m in util.get_installed_mods(True)
            if m.priority > mod.priority
    ]:
        fall_mod.change_priority(fall_mod.priority - 1)

    if not util.get_installed_mods():
        shutil.rmtree(util.get_master_modpack_dir())
        util.create_bcml_graphicpack_if_needed()
    else:
        if not wait_merge:
            refresh_merges()

    if has_patches and not util.get_settings("no_cemu"):
        shutil.rmtree(
            util.get_cemu_dir() / "graphicPacks" / "bcmlPatches" /
            util.get_safe_pathname(mod.name),
            ignore_errors=True,
        )

    print(f"{mod.name} has been uninstalled.")
Пример #11
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,
        )
Пример #12
0
 def get_bootup_injection(self):
     tmp_sarc = util.get_master_modpack_dir() / "logs" / "effects.byml"
     if tmp_sarc.exists():
         return (
             "Ecosystem/StatusEffectList.sbyml",
             util.compress(tmp_sarc.read_bytes()),
         )
     return
Пример #13
0
 def get_bootup_injection(self):
     tmp_sarc = util.get_master_modpack_dir() / "logs" / "savedata.sarc"
     if tmp_sarc.exists():
         return (
             "GameData/savedataformat.ssarc",
             util.compress(tmp_sarc.read_bytes()),
         )
     return None
Пример #14
0
 def perform_merge(self):
     print("Loading modded SARC list...")
     sarcs = {
         s: ss
         for s, ss in self.consolidate_diffs(self.get_all_diffs()).items()
         if ss
     }
     if "only_these" in self._options:
         for sarc_file in self._options["only_these"]:
             master_path = util.get_master_modpack_dir() / sarc_file
             if master_path.exists():
                 master_path.unlink()
         for sarc_file in [
                 file for file in sarcs
                 if file not in self._options["only_these"]
         ]:
             del sarcs[sarc_file]
     else:
         for file in [
                 file
                 for file in util.get_master_modpack_dir().rglob("**/*")
                 if file.suffix in util.SARC_EXTS
         ]:
             file.unlink()
     for sarc_file in sarcs:
         try:
             sarcs[sarc_file].insert(0, util.get_game_file(sarc_file))
         except FileNotFoundError:
             continue
     if not sarcs:
         print("No SARC merging necessary")
         return
     print(f"Merging {len(sarcs)} SARC files...")
     pool = self._pool or Pool(maxtasksperchild=500)
     results = pool.starmap(merge_sarcs, sarcs.items())
     for result in results:
         file, file_data = result
         output_path = util.get_master_modpack_dir() / file
         output_path.parent.mkdir(parents=True, exist_ok=True)
         if output_path.suffix.startswith(".s"):
             file_data = util.compress(file_data)
         output_path.write_bytes(file_data)
     if not self._pool:
         pool.close()
         pool.join()
     print("Finished merging SARCs")
Пример #15
0
 def explore_master(self, params=None):
     path = util.get_master_modpack_dir()
     if SYSTEM == "Windows":
         startfile(path)
     elif SYSTEM == "Darwin":
         run(["open", path], check=False)
     else:
         run(["xdg-open", path], check=False)
Пример #16
0
def get_merged_files() -> List[str]:
    """Gets a list of all currently deep merged files"""
    log = util.get_master_modpack_dir() / 'logs' / 'deepmerge.log'
    if not log.exists():
        return []
    else:
        with log.open('r') as l_file:
            return l_file.readlines()
Пример #17
0
def refresh_merges():
    print("Cleansing old merges...")
    shutil.rmtree(util.get_master_modpack_dir(), True)
    print("Refreshing merged mods...")
    with Pool(maxtasksperchild=500) as pool:
        for merger in mergers.sort_mergers(
            [merger_class() for merger_class in mergers.get_mergers()]):
            merger.set_pool(pool)
            merger.perform_merge()
Пример #18
0
 def get_bootup_injection(self):
     tmp_sarc = util.get_master_modpack_dir() / "logs" / "eventinfo.byml"
     if tmp_sarc.exists():
         return (
             "Event/EventInfo.product.sbyml",
             util.compress(tmp_sarc.read_bytes()),
         )
     else:
         return
Пример #19
0
 def get_bootup_injection(self):
     tmp_sarc = util.get_master_modpack_dir() / 'logs' / 'eventinfo.byml'
     if tmp_sarc.exists():
         return (
             'Event/EventInfo.product.sbyml',
             util.compress(tmp_sarc.read_bytes())
         )
     else:
         return
Пример #20
0
def link_master_mod(output: Path = None):
    util.create_bcml_graphicpack_if_needed()
    if not output:
        if not util.get_settings("export_dir"):
            return
        output = Path(util.get_settings("export_dir"))
    if output.exists():
        shutil.rmtree(output, ignore_errors=True)
    try:
        output.mkdir(parents=True, exist_ok=True)
        if not util.get_settings("no_cemu"):
            output.mkdir(parents=True, exist_ok=True)
            shutil.copy(util.get_master_modpack_dir() / "rules.txt",
                        output / "rules.txt")
    except (OSError, PermissionError, FileExistsError,
            FileNotFoundError) as err:
        raise RuntimeError(
            "There was a problem creating the master BCML graphic pack. "
            "It may be a one time fluke, so try remerging and/or restarting BCML. "
            "This can also happen if BCML and/or Cemu are installed into Program "
            "Files or any folder which requires administrator (or root) permissions. "
            "You can try running BCML as administrator or root, but bear in mind this "
            "is not officially supported. If the problem persists, good luck, because "
            "it's something wonky about your PC, I guess.") from err

    mod_folders: List[Path] = sorted(
        [
            item for item in util.get_modpack_dir().glob("*")
            if item.is_dir() and not (item / ".disabled").exists()
        ],
        reverse=True,
    )
    util.vprint(mod_folders)
    link_or_copy: Any = os.link if not util.get_settings(
        "no_hardlinks") else copyfile
    for mod_folder in mod_folders:
        for item in mod_folder.rglob("**/*"):
            rel_path = item.relative_to(mod_folder)
            exists = (output / rel_path).exists()
            is_log = str(rel_path).startswith("logs")
            is_opt = str(rel_path).startswith("options")
            is_meta = str(rel_path).startswith("meta")
            is_extra = (len(rel_path.parts) == 1 and rel_path.suffix != ".txt"
                        and not item.is_dir())
            if exists or is_log or is_extra or is_meta or is_opt:
                continue
            if item.is_dir():
                (output / rel_path).mkdir(parents=True, exist_ok=True)
            elif item.is_file():
                try:
                    link_or_copy(str(item), str(output / rel_path))
                except OSError:
                    if link_or_copy is os.link:
                        link_or_copy = copyfile
                        link_or_copy(str(item), str(output / rel_path))
                    else:
                        raise
Пример #21
0
 def remerge(self, params):
     try:
         if not util.get_installed_mods():
             if util.get_master_modpack_dir().exists():
                 rmtree(util.get_master_modpack_dir())
                 install.link_master_mod()
             return
         if params["name"] == "all":
             install.refresh_merges()
         else:
             [
                 m() for m in mergers.get_mergers()
                 if m().friendly_name == params["name"]
             ][0].perform_merge()
     except Exception as err:  # pylint: disable=broad-except
         raise Exception(
             f"There was an error merging your mods. {str(err)}\n"
             "Note that this could leave your game in an unplayable state.")
Пример #22
0
 def get_bootup_injection(self):
     tmp_sarc = util.get_master_modpack_dir() / "logs" / "areadata.byml"
     if tmp_sarc.exists():
         return (
             "Ecosystem/AreaData.sbyml",
             util.compress(tmp_sarc.read_bytes()),
         )
     else:
         return
Пример #23
0
def deep_merge(verbose: bool = False, wait_rstb: bool = False, only_these: List[str] = None,
               original_pool: multiprocessing.Pool = None):
    """Performs deep merge on all installed AAMP files"""
    mods = get_deepmerge_mods()
    if not mods:
        print('No deep merge necessary.')
        return
    if (util.get_master_modpack_dir() / 'logs' / 'rstb.log').exists():
        (util.get_master_modpack_dir() / 'logs' / 'rstb.log').unlink()
    merge_log = (util.get_master_modpack_dir() / 'logs' / 'deepmerge.log')
    old_merges = []
    if merge_log.exists():
        if only_these:
            old_merges = merge_log.read_text().splitlines()
        merge_log.unlink()


    print('Loading deep merge data...')
    diffs = consolidate_diff_files(get_deepmerge_diffs(only_these=only_these))

    print('Performing deep merge...')
    if not diffs:
        return
    num_threads = min(multiprocessing.cpu_count(), len(diffs))
    pool = original_pool or multiprocessing.Pool(processes=num_threads)
    pool.map(partial(threaded_merge, verbose=verbose), diffs.items())
    if not original_pool:
        pool.close()
        pool.join()

    if not wait_rstb:
        bcml.rstable.generate_master_rstb()

    (util.get_master_modpack_dir() / 'logs').mkdir(parents=True, exist_ok=True)
    with merge_log.open('w', encoding='utf-8') as l_file:
        for file_type in diffs:
            for file in diffs[file_type]:
                l_file.write(f'{file}\n')
                if only_these and file in old_merges:
                    old_merges.remove(file)
        if only_these:
            for file in old_merges:
                l_file.write(f'{file}\n')
Пример #24
0
    def perform_merge(self):
        actor_path = (util.get_master_modpack_dir() / util.get_content_path() /
                      "Actor" / "ActorInfo.product.sbyml")
        print("Loading modded actor info...")
        modded_actors = self.consolidate_diffs(self.get_all_diffs())
        if not modded_actors:
            print("No actor info merging necessary.")
            if actor_path.exists():
                actor_path.unlink()
            return

        print("Loading unmodded actor info...")
        actorinfo = get_stock_actorinfo()
        stock_actors = {
            crc32(actor["name"].encode("utf8")): actor
            for actor in actorinfo["Actors"]
        }

        print("Merging changes...")
        new_hashes = set()
        for actor_hash, actor_info in modded_actors.items():
            if isinstance(actor_hash, str):
                actor_hash = int(actor_hash)
            if actor_hash in stock_actors:
                util.dict_merge(stock_actors[actor_hash],
                                actor_info,
                                overwrite_lists=True)
            else:
                actorinfo["Actors"].append(actor_info)
                new_hashes.add(actor_hash)

        print("Sorting new actor info...")
        actorinfo["Hashes"] = oead.byml.Array([
            oead.S32(x) if x < 2147483648 else oead.U32(x)
            for x in sorted(new_hashes | set(stock_actors.keys()))
        ])
        try:
            actorinfo["Actors"] = sorted(
                actorinfo["Actors"],
                key=lambda x: crc32(x["name"].encode("utf-8")))
        except KeyError as err:
            if str(err) == "":
                raise RuntimeError(
                    "Your actor info mods could not be merged. "
                    "This usually indicates a corrupt game dump.") from err
            else:
                raise

        print("Saving new actor info...")
        actor_path.parent.mkdir(parents=True, exist_ok=True)
        actor_path.write_bytes(
            util.compress(
                oead.byml.to_binary(actorinfo,
                                    big_endian=util.get_settings("wiiu"))))
        print("Actor info merged successfully")
Пример #25
0
def link_master_mod(output: Path = None):
    util.create_bcml_graphicpack_if_needed()
    if not output:
        if util.get_settings("no_cemu"):
            return
        output = util.get_cemu_dir() / "graphicPacks" / "BreathOfTheWild_BCML"
    if output.exists():
        shutil.rmtree(output, ignore_errors=True)
    try:
        output.mkdir(parents=True, exist_ok=True)
        if not util.get_settings("no_cemu"):
            shutil.copy(util.get_master_modpack_dir() / "rules.txt",
                        output / "rules.txt")
    except (OSError, PermissionError, FileExistsError,
            FileNotFoundError) as err:
        raise RuntimeError(
            "There was a problem creating the master BCML graphic pack. "
            "It may be a one time fluke, so try remerging and/or restarting BCML. "
            "If the problem persists, good luck, because it's something wonky about your "
            "PC, I guess.") from err

    mod_folders: List[Path] = sorted(
        [
            item for item in util.get_modpack_dir().glob("*")
            if item.is_dir() and not (item / ".disabled").exists()
        ],
        reverse=True,
    )
    util.vprint(mod_folders)
    link_or_copy = os.link if not util.get_settings(
        "no_hardlinks") else copyfile
    for mod_folder in mod_folders:
        for item in mod_folder.rglob("**/*"):
            rel_path = item.relative_to(mod_folder)
            exists = (output / rel_path).exists()
            is_log = str(rel_path).startswith("logs")
            is_opt = str(rel_path).startswith("options")
            is_meta = str(rel_path).startswith("meta")
            is_extra = (len(rel_path.parts) == 1 and rel_path.suffix != ".txt"
                        and not item.is_dir())
            if exists or is_log or is_extra or is_meta or is_opt:
                continue
            if item.is_dir():
                (output / rel_path).mkdir(parents=True, exist_ok=True)
            elif item.is_file():
                try:
                    link_or_copy(str(item), str(output / rel_path))
                except OSError:
                    if link_or_copy is os.link:
                        link_or_copy = copyfile
                        link_or_copy(str(item), str(output / rel_path))
                    else:
                        raise
Пример #26
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,
        )
Пример #27
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)
Пример #28
0
 def perform_merge(self):
     print('Loading modded SARC list...')
     sarcs = self.consolidate_diffs(self.get_all_diffs())
     if 'only_these' in self._options:
         for sarc_file in self._options['only_these']:
             master_path = (util.get_master_modpack_dir() / sarc_file)
             if master_path.exists():
                 master_path.unlink()
         for sarc_file in [
                 file for file in sarcs
                 if file not in self._options['only_these']
         ]:
             del sarcs[sarc_file]
     else:
         for file in [file for file in util.get_master_modpack_dir().rglob('**/*') \
                     if file.suffix in util.SARC_EXTS]:
             file.unlink()
     for sarc_file in sarcs:
         try:
             sarcs[sarc_file].insert(0, util.get_game_file(sarc_file))
         except FileNotFoundError:
             continue
     if not sarcs:
         print('No SARC merging necessary')
         return
     num_threads = min(cpu_count(), len(sarcs))
     pool = self._pool or Pool(processes=num_threads)
     print(f'Merging {len(sarcs)} SARC files...')
     results = pool.starmap(merge_sarcs, sarcs.items())
     for result in results:
         file, data = result
         output_path = util.get_master_modpack_dir() / file
         output_path.parent.mkdir(parents=True, exist_ok=True)
         if output_path.suffix.startswith('.s'):
             data = util.compress(data)
         output_path.write_bytes(data)
     if not self._pool:
         pool.close()
         pool.join()
     print('Finished merging SARCs')
Пример #29
0
def refresh_merges(verbose: bool = False):
    """
    Runs RSTB, pack, and text merges together

    :param verbose: Whether to display more detailed output, defaults to False.
    :type verbose: bool, optional
    """
    print('Cleansing old merges...')
    shutil.rmtree(util.get_master_modpack_dir())
    print('Refreshing merged mods...')
    for merger in mergers.sort_mergers(
        [merger_class() for merger_class in mergers.get_mergers()]):
        merger.perform_merge()
Пример #30
0
def set_size(entry: str, size: int):
    rstb_path = (util.get_master_modpack_dir() / util.get_content_path() /
                 "System" / "Resource" /
                 "ResourceSizeTable.product.srsizetable")
    if rstb_path.exists():
        table = read_rstb(rstb_path, be=util.get_settings("wiiu"))
    else:
        table = get_stock_rstb()
        rstb_path.parent.mkdir(parents=True, exist_ok=True)
    table.set_size(entry, size)
    buf = io.BytesIO()
    table.write(buf, be=util.get_settings("wiiu"))
    rstb_path.write_bytes(util.compress(buf.getvalue()))