From bc58c715fb9f518e459e8fec201e3e485083bfc9 Mon Sep 17 00:00:00 2001 From: kipcode66 Date: Thu, 26 Sep 2024 00:34:26 +0000 Subject: [PATCH] added wii BiTE saves --- RomHack.toml.in | 4 +- external/misc/any.py | 34 +- external/misc/anyb.py | 517 +++-- external/misc/dat2qlogs.py | 28 + external/misc/gci2qlogs.py | 28 + external/misc/nandpack.py | 1788 +++++++++++++++++ modules/boot/include/practice.h | 2 - .../include/any_bite_saves_menu.h | 61 + .../src/any_bite_saves_menu.cpp | 62 +- .../menu_any_saves/include/any_saves_menu.h | 5 - .../menu_practice/include/practice_menu.h | 2 +- .../menus/menu_practice/src/practice_menu.cpp | 4 - res/save_files/any.bin | Bin 4080 -> 4080 bytes res/save_files/any_bite.bin | Bin 3680 -> 3680 bytes res/save_files_wii/any.bin | Bin 4160 -> 4160 bytes .../any/{argarok.bin => argorok.bin} | Bin res/save_files_wii/any_bite.bin | Bin 0 -> 4000 bytes res/save_files_wii/any_bite/ag.bin | Bin 0 -> 2700 bytes .../any_bite/arbiters_grounds.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/arealfos.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/argorok.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/beast_ganon.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/before_kb1.bin | Bin 0 -> 2707 bytes .../any_bite/bombhouse_skip.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/boss_bug.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/bulblin_camp.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/cits.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/cits_2.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/cits_tower.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/dark_hammer.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/darknut.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/death_sword.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/deku_toad.bin | Bin 0 -> 2707 bytes .../any_bite/early_boss_key.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/early_city.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/earlypf.bin | Bin 0 -> 2707 bytes .../any_bite/eldin_twilight.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/ems.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/enter_lakebed.bin | Bin 0 -> 2707 bytes .../any_bite/epona_oob_to_flight_by_fowl.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/fan_tower.bin | Bin 0 -> 2700 bytes .../any_bite/faron_twilight.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/freezard_skip.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/goats.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/hc.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/horseback.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/hugo.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/iza.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/kb1.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/lakebed_1.bin | Bin 0 -> 2707 bytes .../any_bite/lanayru_twilight.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/mdh_bridge.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/mdh_tower.bin | Bin 0 -> 2707 bytes .../any_bite/messenger_skip.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/morpheel.bin | Bin 0 -> 2707 bytes .../any_bite/ordon_gate_clip.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/plumm_oob.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/poe_1_skip.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/pot1.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/pot2.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/purple_mist.bin | Bin 0 -> 2700 bytes res/save_files_wii/any_bite/seam_clip.bin | Bin 0 -> 2707 bytes .../any_bite/snowpeak_ruins_mbbb.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/stallord.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/stupidroom.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/towerclimb.bin | Bin 0 -> 2707 bytes .../any_bite/waterfall_sidehop.bin | Bin 0 -> 2707 bytes res/save_files_wii/any_bite/zant.bin | Bin 0 -> 2700 bytes 68 files changed, 2337 insertions(+), 198 deletions(-) create mode 100755 external/misc/dat2qlogs.py create mode 100755 external/misc/gci2qlogs.py create mode 100755 external/misc/nandpack.py rename res/save_files_wii/any/{argarok.bin => argorok.bin} (100%) create mode 100644 res/save_files_wii/any_bite.bin create mode 100644 res/save_files_wii/any_bite/ag.bin create mode 100644 res/save_files_wii/any_bite/arbiters_grounds.bin create mode 100644 res/save_files_wii/any_bite/arealfos.bin create mode 100644 res/save_files_wii/any_bite/argorok.bin create mode 100644 res/save_files_wii/any_bite/beast_ganon.bin create mode 100644 res/save_files_wii/any_bite/before_kb1.bin create mode 100644 res/save_files_wii/any_bite/bombhouse_skip.bin create mode 100644 res/save_files_wii/any_bite/boss_bug.bin create mode 100644 res/save_files_wii/any_bite/bulblin_camp.bin create mode 100644 res/save_files_wii/any_bite/cits.bin create mode 100644 res/save_files_wii/any_bite/cits_2.bin create mode 100644 res/save_files_wii/any_bite/cits_tower.bin create mode 100644 res/save_files_wii/any_bite/dark_hammer.bin create mode 100644 res/save_files_wii/any_bite/darknut.bin create mode 100644 res/save_files_wii/any_bite/death_sword.bin create mode 100644 res/save_files_wii/any_bite/deku_toad.bin create mode 100644 res/save_files_wii/any_bite/early_boss_key.bin create mode 100644 res/save_files_wii/any_bite/early_city.bin create mode 100644 res/save_files_wii/any_bite/earlypf.bin create mode 100644 res/save_files_wii/any_bite/eldin_twilight.bin create mode 100644 res/save_files_wii/any_bite/ems.bin create mode 100644 res/save_files_wii/any_bite/enter_lakebed.bin create mode 100644 res/save_files_wii/any_bite/epona_oob_to_flight_by_fowl.bin create mode 100644 res/save_files_wii/any_bite/fan_tower.bin create mode 100644 res/save_files_wii/any_bite/faron_twilight.bin create mode 100644 res/save_files_wii/any_bite/freezard_skip.bin create mode 100644 res/save_files_wii/any_bite/goats.bin create mode 100644 res/save_files_wii/any_bite/hc.bin create mode 100644 res/save_files_wii/any_bite/horseback.bin create mode 100644 res/save_files_wii/any_bite/hugo.bin create mode 100644 res/save_files_wii/any_bite/iza.bin create mode 100644 res/save_files_wii/any_bite/kb1.bin create mode 100644 res/save_files_wii/any_bite/lakebed_1.bin create mode 100644 res/save_files_wii/any_bite/lanayru_twilight.bin create mode 100644 res/save_files_wii/any_bite/mdh_bridge.bin create mode 100644 res/save_files_wii/any_bite/mdh_tower.bin create mode 100644 res/save_files_wii/any_bite/messenger_skip.bin create mode 100644 res/save_files_wii/any_bite/morpheel.bin create mode 100644 res/save_files_wii/any_bite/ordon_gate_clip.bin create mode 100644 res/save_files_wii/any_bite/plumm_oob.bin create mode 100644 res/save_files_wii/any_bite/poe_1_skip.bin create mode 100644 res/save_files_wii/any_bite/pot1.bin create mode 100644 res/save_files_wii/any_bite/pot2.bin create mode 100644 res/save_files_wii/any_bite/purple_mist.bin create mode 100644 res/save_files_wii/any_bite/seam_clip.bin create mode 100644 res/save_files_wii/any_bite/snowpeak_ruins_mbbb.bin create mode 100644 res/save_files_wii/any_bite/stallord.bin create mode 100644 res/save_files_wii/any_bite/stupidroom.bin create mode 100644 res/save_files_wii/any_bite/towerclimb.bin create mode 100644 res/save_files_wii/any_bite/waterfall_sidehop.bin create mode 100644 res/save_files_wii/any_bite/zant.bin diff --git a/RomHack.toml.in b/RomHack.toml.in index a6d3e6a0..675cb76f 100644 --- a/RomHack.toml.in +++ b/RomHack.toml.in @@ -25,8 +25,8 @@ iso = "@TPGZ_CFG_BLD_ISO@" "tpgz/save_files/any" = "../@TPGZ_CFG_SAVE_ANY_PATH@/any" # any% bite saves -"tpgz/save_files/any_bite.bin" = "../res/save_files/any_bite.bin" -"tpgz/save_files/any_bite" = "../res/save_files/any_bite" +"tpgz/save_files/any_bite.bin" = "../@TPGZ_CFG_SAVE_ANY_PATH@/any_bite.bin" +"tpgz/save_files/any_bite" = "../@TPGZ_CFG_SAVE_ANY_PATH@/any_bite" # 100% saves "tpgz/save_files/hundo.bin" = "../res/save_files/hundo.bin" diff --git a/external/misc/any.py b/external/misc/any.py index 294e39c1..adf2f7d4 100644 --- a/external/misc/any.py +++ b/external/misc/any.py @@ -138,7 +138,7 @@ def main(args=None): "arealfos", "cits_2", "cits_tower", - "argarok", + "argorok", "palace_1", "palace_2", "early_platform", @@ -153,14 +153,20 @@ def main(args=None): any_p = [{**copy.deepcopy(default_entry), "id": i, "filename": name} for i, name in enumerate(file_names)] - file_dict = {e: i for i, e in enumerate(file_names)} + file_dict = {} + for i, e in enumerate(file_names): + if not e in file_dict: + file_dict[e] = [i] + else: + file_dict[e].append(i) - def update_entry(filename, data): - if filename in file_names: - any_p[file_dict[filename]] = {**any_p[file_dict[filename]], **data} + def update_entry(filename, data, n = 1): + count = sum(1 for entry in any_p if entry["filename"] == filename) + if n <= count and n > 0: + any_p[file_dict[filename][n - 1]] = {**any_p[file_dict[filename][n - 1]], **data} # ordon gate clip - update_entry("ordon_gate_clip", { + update_entry("ordon_gate_clip", n = 1, data = { 'requirements': Requirements.POS | Requirements.CAM, 'pos': (827.450012, 216.490097, -4533.90625), 'angle': 498, @@ -172,12 +178,16 @@ def update_entry(filename, data): }) if args.platform is Platform.GCN: - any_p[1]["requirements"] = Requirements.POS | Requirements.CAM - any_p[1]["pos"] = (466.622467, 319.770752, -11651.3867) - any_p[1]["angle"] = 52540 - any_p[1]["cam"]["pos"] = (735.525391, 524.418701, -11576.4746) - any_p[1]["cam"]["target"] = (465.674622, 421.052704, -11651.0684) - any_p[1]["counter"] = 10 + update_entry("ordon_gate_clip", n = 2, data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (466.622467, 319.770752, -11651.3867), + 'angle': 52540, + 'cam': { + 'pos': (735.525391, 524.418701, -11576.4746), + 'target': (465.674622, 421.052704, -11651.0684) + }, + 'counter': 10 + }) # back in time update_entry("bit", { diff --git a/external/misc/anyb.py b/external/misc/anyb.py index 1fb2ff16..2f6801da 100644 --- a/external/misc/anyb.py +++ b/external/misc/anyb.py @@ -1,171 +1,350 @@ +import copy +import sys +import argparse import struct +from enum import IntEnum, unique -REQ_POS = 1 -REQ_CAM = 2 - -default_entry = { - "requirements": 0, - "pos": (0.0,0.0,0.0), - "angle": 0, - "cam": {"pos":(0,0,0), "target": (0,0,0)}, - "counter": 0, -} - -# order matters -file_names = [ - "ordon_gate_clip", - "ordon_gate_clip", - "goats", - "hugo", - "faron_twilight", - "ems", - "purple_mist", - "forest_bit", - "forest_escape", - "lanayru_gate_clip", - "pillar_clip", - "lakebed_1", - "deku_toad", - "karg", - "kb1", - "eldin_twilight", - "lanayru_twilight", - "waterfall_sidehop", - "iza", - "spr_warp", - "spr", - "darkhammer", - "lakebed_bk_skip", - "onebomb", - "mdh_tower", - "mdh_bridge", - "camp", - "ag", - "poe_1_skip", - "death_sword_skip", - "stallord", - "stallord", - "silver_rupee", - "cits_early", - "cits_1", - "aeralfos_skip", - "fan_tower", - "argorok", - "palace_1", - "palace_2", - "early_platform", - "zant", - "hc", - "hc_tower", - "beast_ganon", - "horseback_ganon", -] - -anyb_p = [{**default_entry, "id": i, "filename": file_names[i]} for i in range(46)] - -# ordon gate clip -anyb_p[0]["requirements"] = REQ_POS | REQ_CAM -anyb_p[0]["pos"] = (827.450012, 216.490097, -4533.90625) -anyb_p[0]["angle"] = 498 -anyb_p[0]["cam"]["pos"] = (833.467468, 477.604675, -4241.97266) -anyb_p[0]["cam"]["target"] = (827.497559, 329.622986, -4532.90723) -anyb_p[0]["counter"] = 10 - -# back in time -anyb_p[1]["requirements"] = REQ_POS | REQ_CAM -anyb_p[1]["pos"] = (466.622467, 319.770752, -11651.3867) -anyb_p[1]["angle"] = 52540 -anyb_p[1]["cam"]["pos"] = (735.525391, 524.418701, -11576.4746) -anyb_p[1]["cam"]["target"] = (465.674622, 421.052704, -11651.0684) -anyb_p[1]["counter"] = 10 - -# hugo -anyb_p[3]["requirements"] = REQ_POS | REQ_CAM -anyb_p[3]["pos"] = (701.797302, 85.5212784, -5299.6123) -anyb_p[3]["angle"] = 63622 -anyb_p[3]["cam"]["pos"] = (735.525391, 524.418701, -11576.4746) -anyb_p[3]["cam"]["target"] = (465.674622, 421.052704, -11651.0684) - -# purple mist -anyb_p[6]["requirements"] = REQ_POS -anyb_p[6]["pos"] = (-23524.6152, 250.0, -16220.166) -anyb_p[6]["angle"] = 40758 -anyb_p[6]["counter"] = 30 - -# forest escape -anyb_p[8]["requirements"] = REQ_POS | REQ_CAM -anyb_p[8]["pos"] = (-12433.6016, -235.969193, -17103.998) -anyb_p[8]["angle"] = 29553 -anyb_p[8]["cam"]["pos"] = (-12552.8252, -53.5801048, -16729.5313) -anyb_p[8]["cam"]["target"] = (-12433.2979, -106.667023, -17104.9512) -anyb_p[8]["counter"] = 30 - -# lanayru gate clip -anyb_p[9]["requirements"] = REQ_POS | REQ_CAM -anyb_p[9]["pos"] = (-63026.2852, -9065.92578, 71680.3438) -anyb_p[9]["angle"] = 44248 -anyb_p[9]["cam"]["pos"] = (-62655.8125, -8900.91309, 71903.6328) -anyb_p[9]["cam"]["target"] = (-63064.2148, -8969.97656, 71661.0781) -anyb_p[9]["counter"] = 15 - -# eldin twilight -anyb_p[15]["requirements"] = REQ_POS | REQ_CAM -anyb_p[15]["pos"] = (455.088379, -150.0, 11516.7227) -anyb_p[15]["angle"] = 6058 -anyb_p[15]["cam"]["pos"] = (219.367218, -20.1253014, 11157.582) -anyb_p[15]["cam"]["target"] = (482.515137, -39.9999771, 11558.5283) -anyb_p[15]["counter"] = 10 - -# iza -anyb_p[18]["requirements"] = REQ_POS -anyb_p[18]["pos"] = (5979.97217, 150.0, -2748.34155) -anyb_p[18]["angle"] = 10114 - -# snowpeak messenger skip -anyb_p[19]["requirements"] = REQ_POS | REQ_CAM -anyb_p[19]["pos"] = (-9294.87988, 980.0, -11712.3838) -anyb_p[19]["angle"] = 346 -anyb_p[19]["cam"]["pos"] = (-9309.65137, 1280.4469, -12130.7695) -anyb_p[19]["cam"]["target"] = (-9294.2207, 1180.0, -11692.3945) -anyb_p[19]["counter"] = 10 - -# spr -anyb_p[20]["requirements"] = REQ_POS -anyb_p[20]["pos"] = (0.0, -150.0, 6000.0) -anyb_p[20]["angle"] = 33768 - -# bk skip -anyb_p[22]["requirements"] = REQ_POS | REQ_CAM -anyb_p[22]["pos"] = (71.9835968, 1500.00, 2839.01587) -anyb_p[22]["angle"] = 32767 -anyb_p[22]["cam"]["pos"] = (71.9835968, 1719.93542, 2969.04565) -anyb_p[22]["cam"]["target"] = (71.9835968, 1660.0, 2839.01587) -anyb_p[22]["counter"] = 30 - -# morpheel -anyb_p[23]["requirements"] = REQ_POS | REQ_CAM -anyb_p[23]["pos"] = (-1193.0, -23999.00, -770.0) -anyb_p[23]["angle"] = 10754 -anyb_p[23]["counter"] = 20 - -# poe 1 skip -anyb_p[28]["requirements"] = REQ_POS | REQ_CAM -anyb_p[28]["pos"] = (-2046.97168, 0.0, -587.304871) -anyb_p[28]["angle"] = 49030 -anyb_p[28]["cam"]["pos"] = (-1779.00293, 213.707397, -584.686768) -anyb_p[28]["cam"]["target"] = (-2047.97168, 130.16568, -587.317139) -anyb_p[28]["counter"] = 10 - -file = open("any_bite.bin", "wb") - -for entry in anyb_p: - print(entry) - file.write(entry["requirements"].to_bytes(1, "big", signed=False)) - file.write(int(0).to_bytes(1, "big", signed=False)) # padding - file.write(entry["angle"].to_bytes(2, "big", signed=False)) - file.write(struct.pack('>fff', *entry["pos"])) - file.write(struct.pack('>fff', *entry["cam"]["pos"])) - file.write(struct.pack('>fff', *entry["cam"]["target"])) - file.write(entry["counter"].to_bytes(4, "big", signed=False)) - file.write(struct.pack(">32s", entry["filename"].encode("ascii"))) - file.write(int(0).to_bytes(4, "big", signed=False)) # padding +@unique +class Platform(IntEnum): + GCN = 0 + WII = 1 + + +class Requirements(IntEnum): + POS = 1 + CAM = 2 + +def main(args=None): + parser = argparse.ArgumentParser( + sys.argv[0], description="A tool to generate the metadata file for the any% BiTE save files.") + parser.add_argument( + "-p", "--platform", type=str.upper, choices=[e.name for e in Platform], default=Platform.GCN.name, help="The platform to generate for.") + args = parser.parse_args() + + args.platform = Platform[args.platform] + + default_entry = { + "requirements": 0, + "pos": (0.0,0.0,0.0), + "angle": 0, + "cam": {"pos":(0,0,0), "target": (0,0,0)}, + "counter": 0, + } + + # order matters + file_names = [ + "ordon_gate_clip", + "ordon_gate_clip", + "goats", + "hugo", + "faron_twilight", + "ems", + "purple_mist", + "forest_bit", + "forest_escape", + "lanayru_gate_clip", + "pillar_clip", + "lakebed_1", + "deku_toad", + "karg", + "kb1", + "eldin_twilight", + "lanayru_twilight", + "waterfall_sidehop", + "iza", + "spr_warp", + "spr", + "darkhammer", + "lakebed_bk_skip", + "onebomb", + "mdh_tower", + "mdh_bridge", + "camp", + "ag", + "poe_1_skip", + "death_sword_skip", + "stallord", + "stallord", + "silver_rupee", + "cits_early", + "cits_1", + "aeralfos_skip", + "fan_tower", + "argorok", + "palace_1", + "palace_2", + "early_platform", + "zant", + "hc", + "hc_tower", + "beast_ganon", + "horseback_ganon", + ] + + if args.platform is Platform.WII: + # order matters + file_names = [ + "ordon_gate_clip", + "ordon_gate_clip", + "seam_clip", + "goats", + "hugo", + "faron_twilight", + "ems", + "purple_mist", + "kb1", + "eldin_twilight", + "bombhouse_skip", + "epona_oob_to_flight_by_fowl", + "lanayru_twilight", + "waterfall_sidehop", + "boss_bug", + "iza", + "plumm_oob", + "enter_lakebed", + "lakebed_1", + "deku_toad", + "morpheel", + "mdh_tower", + "mdh_bridge", + "messenger_skip", + "snowpeak_ruins_mbbb", + "freezard_skip", + "dark_hammer", + "bulblin_camp", + "ag", + "poe_1_skip", + "early_boss_key", + "death_sword", + "stallord", + "stallord", + "early_city", + "cits", + "arealfos", + "cits_2", + "fan_tower", + "argorok", + "pot1", + "stupidroom", + "pot2", + "earlypf", + "zant", + "hc", + "darknut", + "towerclimb", + "beast_ganon", + "horseback", + ] + + anyb_p = [{**copy.deepcopy(default_entry), "id": i, "filename": file_names[i]} for i in range(len(file_names))] + + file_dict = {} + for i, e in enumerate(file_names): + if not e in file_dict: + file_dict[e] = [i] + else: + file_dict[e].append(i) + + def update_entry(filename, data, n = 1): + count = sum(1 for entry in anyb_p if entry["filename"] == filename) + if n <= count and n > 0: + anyb_p[file_dict[filename][n - 1]] = {**anyb_p[file_dict[filename][n - 1]], **data} + + # ordon gate clip + update_entry("ordon_gate_clip", n = 1, data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (827.450012, 216.490097, -4533.90625), + 'angle': 498, + 'cam': {'pos': (833.467468, 477.604675, -4241.97266), 'target': (827.497559, 329.622986, -4532.90723)}, + 'counter': 10, + }) + + # back in time + update_entry("ordon_gate_clip", n = 2, data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (466.622467, 319.770752, -11651.3867), + 'angle': 52540, + 'cam': {'pos': (735.525391, 524.418701, -11576.4746), 'target': (465.674622, 421.052704, -11651.0684)}, + 'counter': 10, + }) + + # hugo + update_entry("hugo", data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (701.797302, 85.5212784, -5299.6123), + 'angle': 63622, + 'cam': {'pos': (735.525391, 524.418701, -11576.4746), 'target': (465.674622, 421.052704, -11651.0684)}, + }) + + # purple mist + update_entry("purple_mist", data = { + 'requirements': Requirements.POS, + 'pos': (-23524.6152, 250.0, -16220.166), + 'angle': 40758, + 'counter': 30, + }) + + # king bulblin 1 + if args.platform is Platform.WII: + update_entry("kb1", data = { + 'requirements': Requirements.POS, + 'pos': (-9717.6035, 337.0316, 97.9661), + 'angle': 16384, + 'counter': 30, + }) + + # boss bug + if args.platform is Platform.WII: + update_entry("boss_bug", data = { + 'requirements': Requirements.POS, + 'pos': (-87517.1562, -18789.2812, 38927.0820), + 'angle': 41851, + 'counter': 30, + }) + + # plumm oob + if args.platform is Platform.WII: + update_entry("plumm_oob", data = { + 'requirements': Requirements.POS, + 'pos': (-104271.3750, -18470.0, 52661.7812), + 'angle': 45103, + 'counter': 30, + }) + + # mdh tower + if args.platform is Platform.WII: + update_entry("mdh_tower", data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (25362.3184, -3028.7673, 10060.8379), + 'angle': 29327, + 'counter': 30, + }) + + # mdh bridge + if args.platform is Platform.WII: + update_entry("mdh_bridge", data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (13050.0, 9825.0, 36202.0), + 'angle': 32768, + 'counter': 30, + }) + + # freezard skip + if args.platform is Platform.WII: + update_entry("freezard_skip", data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (-1125.0, 0.0, -1275.0), + 'angle': 32768, + 'counter': 30, + }) + + # dark hammer + if args.platform is Platform.WII: + update_entry("dark_hammer", data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (0.7448, 0.0, 1330.9711), + 'angle': 32768, + 'counter': 20, + }) + + # forest escape + update_entry("forest_escape", data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (-12433.6016, -235.969193, -17103.998), + 'angle': 29553, + 'cam': {'pos': (-12552.8252, -53.5801048, -16729.5313), 'target': (-12433.2979, -106.667023, -17104.9512)}, + 'counter': 30, + }) + + # lanayru gate clip + update_entry("lanayru_gate_clip", data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (-63026.2852, -9065.92578, 71680.3438), + 'angle': 44248, + 'cam': {'pos': (-62655.8125, -8900.91309, 71903.6328), 'target': (-63064.2148, -8969.97656, 71661.0781)}, + 'counter': 15, + }) + + # eldin twilight + update_entry("eldin_twilight", data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (455.088379, -150.0, 11516.7227), + 'angle': 6058, + 'cam': {'pos': (219.367218, -20.1253014, 11157.582), 'target': (482.515137, -39.9999771, 11558.5283)}, + 'counter': 10, + }) + + # waterfall sidehop + if args.platform is Platform.WII: + update_entry("waterfall_sidehop", data = { + 'requirements': Requirements.POS, + 'pos': (1169.5876, 12.6414, -1114.5820), + 'angle': 0, + 'counter': 10, + }) + + # iza + update_entry("iza", data = { + 'requirements': Requirements.POS, + 'pos': (5979.97217, 150.0, -2748.34155), + 'angle': 10114, + }) + + # snowpeak messenger skip + update_entry("spr_warp", data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (-9294.87988, 980.0, -11712.3838), + 'angle': 346, + 'cam': {'pos': (-9309.65137, 1280.4469, -12130.7695), 'target': (-9294.2207, 1180.0, -11692.3945)}, + 'counter': 10, + }) + + # spr + update_entry("spr", data = { + 'requirements': Requirements.POS, + 'pos': (0.0, 150.0, 6000.0), + 'angle': 33768, + }) + + # bk skip + update_entry("lakebed_bk_skip", data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (71.9835968, 1500.0, 2839.01587), + 'angle': 32767, + 'cam': {'pos': (71.9835968, 1719.93542, 2969.04565), 'target': (71.9835968, 1660.0, 2839.01587)}, + 'counter': 30, + }) + + # morpheel + update_entry('onebomb', data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (-1193.0, -23999.00, -770.0), + 'angle': 10754, + 'counter': 20, + }) + + # poe 1 skip + update_entry('poe_1_skip', data = { + 'requirements': Requirements.POS | Requirements.CAM, + 'pos': (-2046.97168, 0.0, -587.304871), + 'angle': 49030, + 'cam': {'pos': (-1779.00293, 213.707397, -584.686768), 'target': (-2047.97168, 130.16568, -587.317139)}, + 'counter': 10, + }) + + file = open("any_bite.bin", "wb") + + for entry in anyb_p: + print(entry) + file.write(entry["requirements"].to_bytes(1, "big", signed=False)) + file.write(int(0).to_bytes(1, "big", signed=False)) # padding + file.write(entry["angle"].to_bytes(2, "big", signed=False)) + file.write(struct.pack('>fff', *entry["pos"])) + file.write(struct.pack('>fff', *entry["cam"]["pos"])) + file.write(struct.pack('>fff', *entry["cam"]["target"])) + file.write(entry["counter"].to_bytes(4, "big", signed=False)) + file.write(struct.pack(">32s", entry["filename"].encode("ascii"))) + file.write(int(0).to_bytes(4, "big", signed=False)) # padding + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/external/misc/dat2qlogs.py b/external/misc/dat2qlogs.py new file mode 100755 index 00000000..e04f1e13 --- /dev/null +++ b/external/misc/dat2qlogs.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +import argparse, os, re, struct, sys + +def main(): + parser = argparse.ArgumentParser(description="Parses out quest logs from a Twilight Princess savefiles") + parser.add_argument("wsd", help="A valid twilight princess save data file extracted from a savefile (data.bin)") + wsdbytes = None + args = parser.parse_args() + file_in = args.wsd + with open(file_in,"rb") as wsdfile: + wsdbytes = bytearray(wsdfile.read()) + + qlog1 = wsdbytes[0x0008:0x0A9B] + qlog2 = wsdbytes[0x0A9C:0x152F] + qlog3 = wsdbytes[0x1530:0x1FC3] + + with open("qlog1.bin", "wb") as outfile: + outfile.write(qlog1) + + with open("qlog2.bin", "wb") as outfile: + outfile.write(qlog2) + + with open("qlog3.bin", "wb") as outfile: + outfile.write(qlog3) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/external/misc/gci2qlogs.py b/external/misc/gci2qlogs.py new file mode 100755 index 00000000..681c9b1e --- /dev/null +++ b/external/misc/gci2qlogs.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +import argparse, os, re, struct, sys + +def main(): + parser = argparse.ArgumentParser(description="Parses out quest logs from a Twilight Princess GCI") + parser.add_argument("gci", help="A valid twilight princess GCI file") + gcibytes = None + args = parser.parse_args() + file_in = args.gci + with open(file_in,"rb") as gcifile: + gcibytes = bytearray(gcifile.read()) + + qlog1 = gcibytes[0x4048:0x4AD4] + qlog2 = gcibytes[0x4ADC:0x5568] + qlog3 = gcibytes[0x5570:0x5FFC] + + with open("qlog1.bin", "w") as outfile: + outfile.write(qlog1) + + with open("qlog2.bin", "w") as outfile: + outfile.write(qlog2) + + with open("qlog3.bin", "w") as outfile: + outfile.write(qlog3) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/external/misc/nandpack.py b/external/misc/nandpack.py new file mode 100755 index 00000000..13dc0822 --- /dev/null +++ b/external/misc/nandpack.py @@ -0,0 +1,1788 @@ +#!/usr/bin/python3 + +"""Tool to pack/unpack Wii saves & inject REL modules.""" + +# Copyright 2021 kipcode66 & Seeky +# Based off of Dolphin Emulator Project https://github.com/dolphin-emu/dolphin.git +# Copyright 2010 Dolphin Emulator Project +# SPDX-License-Identifier: GPL-2.0-or-later + +# Based off of tachtig/twintig http://git.infradead.org/?p=users/segher/wii.git +# See also: https://wiibrew.org/wiki/Segher%27s_Wii.git +# Copyright 2007,2008 Segher Boessenkool +# Licensed under the terms of the GNU GPL, version 2 +# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt + +# The binary data loaded in the save files is taken from the REL loader code from PistonMiner. +# SPDX-License-Identifier: GPL-3.0-or-later +# Copyright 2020 Linus S. (aka PistonMiner) +# Modifications made by Zephiles + +from functools import reduce +import logging +import sys +import os +import argparse +import struct +import ctypes +from io import FileIO +from enum import IntEnum +from typing import Any, Dict, List +import hashlib +import secrets +import json +import base64 +from datetime import datetime, timezone +import re +from Crypto.Cipher import AES + +# +----------------------+ +# | Constants definition | +# +----------------------+ + +# Program Constants/global variables +VERSION = "0.2.2" + +# Constants +BLOCK_SZ = 0x40 +BNR_SZ = 0x60a0 +ICON_SZ = 0x1200 +FULL_BNR_MIN = 0x72a0 # BNR_SZ + 1*ICON_SZ +FULL_BNR_MAX = 0xF0A0 # BNR_SZ + 8*ICON_SZ +BK_LISTED_SZ = 0x70 +SIG_SZ = 0x40 +FULL_CERT_SZ = 0x3C0 + +DEFAULT_DEVICE_ID = 0x0403AC68 + +OS_BUS_CLOCK = 243000000 + +# Crypto values +SD_KEY = bytes([0xab, 0x01, 0xb9, 0xd8, + 0xe1, 0x62, 0x2b, 0x08, + 0xaf, 0xba, 0xd8, 0x4d, + 0xbf, 0xc2, 0xa5, 0x5d]) +SD_INITIAL_IV = bytes([0x21, 0x67, 0x12, 0xE6, + 0xAA, 0x1F, 0x68, 0x9F, + 0x95, 0xC5, 0xA2, 0x23, + 0x24, 0xDC, 0x6A, 0x98]) +MD5_BLANKER = bytes([0x0E, 0x65, 0x37, 0x81, + 0x99, 0xBE, 0x45, 0x17, + 0xAB, 0x06, 0xEC, 0x22, + 0x45, 0x1A, 0x57, 0x93]) +NG_ID = 0x0403AC68 +CA_ID = 1 +MS_ID = 2 + +DEFAULT_KEY_ID = 0x6AAB8C59 + +SYSTEM_MENU = 0x0000000100000002 + +DEFAULT_SIGNATURE = bytes([ + # R + 0x00, 0xD8, 0x81, 0x63, 0xB2, 0x00, 0x6B, 0x0B, 0x54, 0x82, + 0x88, 0x63, 0x81, 0x1C, 0x00, 0x71, 0x12, 0xED, 0xB7, 0xFD, + 0x21, 0xAB, 0x0E, 0x50, 0x0E, 0x1F, 0xBF, 0x78, 0xAD, 0x37, + # S + 0x00, 0x71, 0x8D, 0x82, 0x41, 0xEE, 0x45, 0x11, 0xC7, 0x3B, + 0xAC, 0x08, 0xB6, 0x83, 0xDC, 0x05, 0xB8, 0xA8, 0x90, 0x1F, + 0xA8, 0x2A, 0x0E, 0x4E, 0x76, 0xEF, 0x44, 0x72, 0x99, 0xF8 +]) + +DEFAULT_PRIVATE_KEY = bytes([0x00, 0xAB, 0xEE, 0xC1, 0xDD, 0xB4, + 0xA6, 0x16, 0x6B, 0x70, 0xFD, 0x7E, + 0x56, 0x67, 0x70, 0x57, 0x55, 0x27, + 0x38, 0xA3, 0x26, 0xC5, 0x46, 0x16, + 0xF7, 0x62, 0xC9, 0xED, 0x73, 0xF2]) + +# DEFAULT_PUBLIC_KEY = ec.privToPub(commondefs.DEFAULT_PRIVATE_KEY) +DEFAULT_PUBLIC_KEY = bytes([0x01, 0x04, 0x0b, 0xe0, 0x46, 0xea, + 0x95, 0x19, 0xf2, 0x85, 0x9b, 0x0d, + 0x94, 0x29, 0xa2, 0xc6, 0x91, 0x80, + 0x15, 0x89, 0x8f, 0x2e, 0xba, 0x20, + 0xcf, 0xfd, 0xb3, 0x16, 0x4f, 0x0c, + 0x01, 0x38, 0xc5, 0xd2, 0x2f, 0xc1, + 0xe9, 0xee, 0x17, 0x6c, 0x2d, 0x8f, + 0xa4, 0x74, 0xb0, 0xe9, 0x38, 0x66, + 0x6e, 0x60, 0xcf, 0x06, 0xd5, 0x08, + 0x7a, 0xc2, 0x4f, 0x01, 0x39, 0x79]) + +SQUARE = bytes([0x00, 0x01, 0x04, 0x05, + 0x10, 0x11, 0x14, 0x15, + 0x40, 0x41, 0x44, 0x45, + 0x50, 0x51, 0x54, 0x55]) + +SIGNATURE_END_MAGIC = 0x2f536969 + +# ACE data + +TIDS = {0x00010000525a4445: "us0", + 0x00010000525a4450: "eu", 0x00010000525a444a: "jp"} + +VERSIONS = {"us0": 0x00010000525a4445, "us2": 0x00010000525a4445, + "eu": 0x00010000525a4450, "jp": 0x00010000525a444a} + +GAME_INFO_PTR = { + # NA 1.0 + "us0": 0x80492928, + # NA 1.2 + "us2": 0x80479F30, + # PAL + "eu": 0x8047A828, + # JP + "jp": 0x80477DB0, +} + +BIN_DATA_INIT = { + # NA 1.0 + "us0": base64.b64decode("PICASYiEOEAchAqUOIQCCDyggEZgpYPgfYUiFH2JA6ZOgAQg"), + # NA 1.2 + "us2": base64.b64decode("PICASIiErkgchAqUOIQCCDyggERgpfngfYUiFH2JA6ZOgAQg"), + # PAL + "eu": base64.b64decode("PICASIiEt0AchAqUOIQCCDyggEVgpQLgfYUiFH2JA6ZOgAQg"), + # JP + "jp": base64.b64decode("PICASIiEjMgchAqUOIQCCDyggERgpdhgfYUiFH2JA6ZOgAQg"), +} + +BIN_DATA_MAIN = { + '1': { + # NA 1.0 + "us0": base64.b64decode("PYCANGGMovR9iAOmToAAITxggDRgY8V0SAAABXyIAqY4h" + "ABQSAABSYhtsmAsAwAAQYIAIIBtu0g4gAAFOKAAAT2AgC" + "phjPzcfYgDpk6AACE4YAAAPICAADigAAA9gIABYYyp+H2" + "JA6ZOgAQglCH/4HwIAqaQAQAkv6EACHx+G3h8nyN4P6CA" + "sH+j63hIAAAFfIgCpjiE/3A4oAMYPYCAPGGMCRR9iAOmT" + "oAAIX+j63g4gAMYSAAA0ZOtv5A4fQMYkG2/lDxggDRgY3" + "f0Y6QA/EgAAJ09gIA0YYzFeH2JA6Z/w/N4f+T7eLuhAAi" + "AAQAkfAgDpjghACBOgAQgfH8beDxggTNgYzqcPICAsGCE" + "ARxIAABdf+P7eEuEduB8fxt4P8CAAGPDW/Q8gICwOKADG" + "EuMB+Vjw1v0OIADGEgAAEk8YIAAYGOGRGPEXchIAAAhPG" + "CANGBjxXQ8gIAAYIRcYEgAAA1/4/t4SIM5OHyDIFBUhAG" + "6PKBIAHylI3iQowAAOIAABJQh/+B8CAKmkAEAJL+hAAh8" + "fxt4fJ4jeD+ggDRjpWGIfKgDpk6AACFjo2JsfGgDpn/j+" + "3h/xPN4ToAAIbuhAAiAAQAkfAgDpjghACBOgAAgkG2yZJ" + "Qh/1B8CAKmkAEAtL9BAAhINEUZfHobeD/ggAA7wAAAO6E" + "AIGPjXwR/pOt4OKAAAUg2wVUsAwAAQIIA5H+j63g4gAAA" + "OKAAAkg2ur0sAwAAQYAAzDuDAB9XnAA0f6PreDiAAAA4o" + "AAASDa6nSwDAABBgACsgG2zZH+E43g4oAAgSC3RbXx7G3" + "h/o+t4f2TbeH+F43hINrixLAMAAECBAFyAbbNsgJsAIIC" + "7AERILdFBfHwbeEg0RGF/Y9t4f4TjeEg0UXUsAwABQIIA" + "GEg0RF2Tn1S4k39UvIPbADRIAAAof2PbeEg0U51INERBg" + "G2zbH+E43hILdeFgG2zZH9k23hILdd5f6PreEg2wlEsHg" + "AAQYIADH/IA6ZOgAAhf0PTeEg0RB27QQAIgAEAtHwIA6Y" + "4IQCwSAAnSA=="), # 6D6F642E72656C00 + # NA 1.2 + "us2": base64.b64decode("PYCAM2GMTMR9iAOmToAAITxggDNgY29ESAAABXyIAqY4h" + "ABQSAABSYhtseAsAwAAQYIAIIBtuug4gAAFOKAAAT2AgC" + "phjPbwfYgDpk6AACE4YAAAPICAADigAAA9gIABYYyqsH2" + "JA6ZOgAQglCH/4HwIAqaQAQAkv6EACHx+G3h8nyN4P6CA" + "sH+j63hIAAAFfIgCpjiE/3A4oAMYPYCAOmGMs0x9iAOmT" + "oAAIX+j63g4gAMYSAAA0ZOtvwg4fQMYkG2/DDxggDNgYy" + "HEY6QA/EgAAJ09gIAzYYxvSH2JA6Z/w/N4f+T7eLuhAAi" + "AAQAkfAgDpjghACBOgAQgfH8beDxggTNgYzqcPICAsGCE" + "ARxIAABdf+P7eEuDILB8fxt4P8CAAGPDW/Q8gICwOKADG" + "EuKsh1jw1v0OIADGEgAAEk8YIAAYGOGRGPEXchIAAAhPG" + "CAM2Bjb0Q8gIAAYIRcYEgAAA1/4/t4SIM5OHyDIFBUhAG" + "6PKBIAHylI3iQowAAOIAABJQh/+B8CAKmkAEAJL+hAAh8" + "fxt4fJ4jeD+ggDNjpQtYfKgDpk6AACFjoww8fGgDpn/j+" + "3h/xPN4ToAAIbuhAAiAAQAkfAgDpjghACBOgAAgkG2x5J" + "Qh/1B8CAKmkAEAtL9BAAhIMu7pfHobeD/ggAA7wAAAO6E" + "AIGPjXwR/pOt4OKAAAUg1a40sAwAAQIIA5H+j63g4gAAA" + "OKAAAkg1ZPUsAwAAQYAAzDuDAB9XnAA0f6PreDiAAAA4o" + "AAASDVk1SwDAABBgACsgG2y5H+E43g4oAAgSCx7PXx7G3" + "h/o+t4f2TbeH+F43hINWLpLAMAAECBAFyAbbLsgJsAIIC" + "7AERILHsRfHwbeEgy7jF/Y9t4f4TjeEgy+0UsAwABQIIA" + "GEgy7i2Tn1S4k39UvIPbADRIAAAof2PbeEgy/W1IMu4Rg" + "G2y7H+E43hILIFVgG2y5H9k23hILIFJf6PreEg1bIksHg" + "AAQYIADH/IA6ZOgAAhf0PTeEgy7e27QQAIgAEAtHwIA6Y" + "4IQCwSAAnSA=="), # 6D6F642E72656C00 + # PAL + "eu": base64.b64decode("PYCAM2GMUPR9iAOmToAAITxggDNgY3N0SAAABXyIAqY4h" + "ABQSAABSYhtsKAsAwAAQYIAIIBtuag4gAAFOKAAAT2AgC" + "phjPsgfYgDpk6AACE4YAAAPICAADigAAA9gIABYYyqoH2" + "JA6ZOgAQglCH/4HwIAqaQAQAkv6EACHx+G3h8nyN4P6CA" + "sH+j63hIAAAFfIgCpjiE/3A4oAMYPYCAOmGMt9B9iAOmT" + "oAAIX+j63g4gAMYSAAA0ZOtvcg4fQMYkG29zDxggDNgYy" + "X0Y6QA/EgAAJ09gIAzYYxzeH2JA6Z/w/N4f+T7eLuhAAi" + "AAQAkfAgDpjghACBOgAQgfH8beDxggTNgYzqcPICAsGCE" + "ARxIAABdf+P7eEuDJOB8fxt4P8CAAGPDW/Q8gICwOKADG" + "EuKtqFjw1v0OIADGEgAAEk8YIAAYGOGRGPEXchIAAAhPG" + "CAM2Bjc3Q8gIAAYIRcYEgAAA1/4/t4SIM5OHyDIFBUhAG" + "6PKBIAHylI3iQowAAOIAABJQh/+B8CAKmkAEAJL+hAAh8" + "fxt4fJ4jeD+ggDNjpQ+IfKgDpk6AACFjoxBsfGgDpn/j+" + "3h/xPN4ToAAIbuhAAiAAQAkfAgDpjghACBOgAAgkG2wpJ" + "Qh/1B8CAKmkAEAtL9BAAhIMvMZfHobeD/ggAA7wAAAO6E" + "AIGPjXwR/pOt4OKAAAUg1b70sAwAAQIIA5H+j63g4gAAA" + "OKAAAkg1aSUsAwAAQYAAzDuDAB9XnAA0f6PreDiAAAA4o" + "AAASDVpBSwDAABBgACsgG2xpH+E43g4oAAgSCx/bXx7G3" + "h/o+t4f2TbeH+F43hINWcZLAMAAECBAFyAbbGsgJsAIIC" + "7AERILH9BfHwbeEgy8mF/Y9t4f4TjeEgy/3UsAwABQIIA" + "GEgy8l2Tn1S4k39UvIPbADRIAAAof2PbeEgzAZ1IMvJBg" + "G2xrH+E43hILIWFgG2xpH9k23hILIV5f6PreEg1cLksHg" + "AAQYIADH/IA6ZOgAAhf0PTeEgy8h27QQAIgAEAtHwIA6Y" + "4IQCwSAAnSA=="), # 6D6F642E72656C00 + # JP + "jp": base64.b64decode("PYCAM2GMZ+R9iAOmToAAITxggDNgY4pkSAAABXyIAqY4h" + "ABQSAABSYhtseAsAwAAQYIAIIBtutg4gAAFOKAAAT2AgC" + "thjBIQfYgDpk6AACE4YAAAPICAADigAAA9gIABYYyrLH2" + "JA6ZOgAQglCH/4HwIAqaQAQAkv6EACHx+G3h8nyN4P6CA" + "sH+j63hIAAAFfIgCpjiE/3A4oAMYPYCAOmGMzgR9iAOmT" + "oAAIX+j63g4gAMYSAAA0ZOtvvg4fQMYkG2+/DxggDNgYz" + "zkY6QA/EgAAJ09gIAzYYyKaH2JA6Z/w/N4f+T7eLuhAAi" + "AAQAkfAgDpjghACBOgAQgfH8beDxggTNgYzqcPICAsGCE" + "ARxIAABdf+P7eEuDO9B8fxt4P8CAAGPDW/Q8gICwOKADG" + "EuKzNVjw1v0OIADGEgAAEk8YIAAYGOGRGPEXchIAAAhPG" + "CAM2BjimQ8gIAAYIRcYEgAAA1/4/t4SIM5OHyDIFBUhAG" + "6PKBIAHylI3iQowAAOIAABJQh/+B8CAKmkAEAJL+hAAh8" + "fxt4fJ4jeD+ggDNjpSZ4fKgDpk6AACFjoydcfGgDpn/j+" + "3h/xPN4ToAAIbuhAAiAAQAkfAgDpjghACBOgAAgkG2x5J" + "Qh/1B8CAKmkAEAtL9BAAhIMwoJfHobeD/ggAA7wAAAO6E" + "AIGPjXwR/pOt4OKAAAUg1hkUsAwAAQIIA5H+j63g4gAAA" + "OKAAAkg1f60sAwAAQYAAzDuDAB9XnAA0f6PreDiAAAA4o" + "AAASDV/jSwDAABBgACsgG2y5H+E43g4oAAgSCyWXXx7G3" + "h/o+t4f2TbeH+F43hINX2hLAMAAECBAFyAbbLsgJsAIIC" + "7AERILJYxfHwbeEgzCVF/Y9t4f4TjeEgzFmUsAwABQIIA" + "GEgzCU2Tn1S4k39UvIPbADRIAAAof2PbeEgzGI1IMwkxg" + "G2y7H+E43hILJx1gG2y5H9k23hILJxpf6PreEg1h0EsHg" + "AAQYIADH/IA6ZOgAAhf0PTeEgzCQ27QQAIgAEAtHwIA6Y" + "4IQCwSAAnSA=="), # 6D6F642E72656C00 + }, + '2': { + # NA 1.0 + "us0": base64.b64decode("PYCANGGMovR9iAOmToAAITxggDRgY8V0SAAABXyIAqY4h" + "ABQSAABSYhtsmAsAwAAQYIAIIBtu0g4gAAFOKAAAT2AgC" + "phjPzcfYgDpk6AACE4YAAAPICAADigAAA9gIABYYyp+H2" + "JA6ZOgAQglCH/4HwIAqaQAQAkv6EACHx+G3h8nyN4P6CA" + "sH+j63hIAAAFfIgCpjiE/3A4oAMsPYCAPGGMCRR9iAOmT" + "oAAIX+j63g4gAMsSAAA0ZOtv5A4fQMskG2/lDxggDRgY3" + "f0Y6QA/EgAAJ09gIA0YYzFeH2JA6Z/w/N4f+T7eLuhAAi" + "AAQAkfAgDpjghACBOgAQgfH8beDxggTNgYzqcPICAsGCE" + "ARxIAABdf+P7eEuEduB8fxt4P8CAAGPDW/Q8gICwOKADL" + "EuMB+Vjw1v0OIADLEgAAEk8YIAAYGOGRGPEXchIAAAhPG" + "CANGBjxXQ8gIAAYIRcYEgAAA1/4/t4SIM5OHyDIFBUhAG" + "6PKBIAHylI3iQowAAOIAABJQh/+B8CAKmkAEAJL+hAAh8" + "fxt4fJ4jeD+ggDRjpWGIfKgDpk6AACFjo2JsfGgDpn/j+" + "3h/xPN4ToAAIbuhAAiAAQAkfAgDpjghACBOgAAgkG2yZJ" + "Qh/1B8CAKmkAEAtL9BAAhINEUZfHobeD/ggAA7wAAAO6E" + "AIGPjXxh/pOt4OKAAAUg2wVUsAwAAQIIA+H+j63g4gAAA" + "OKAAAkg2ur0sAwAAQYAA4DuDAB9XnAA0f6PreDiAAAA4o" + "AAASDa6nSwDAABBgADAgG2zZH+E43g4oAAgSC3RbXx7G3" + "h/o+t4f2TbeH+F43hINrixLAMAAECBAHCAbbNsgJsAIIC" + "7AERILdFBfHwbeEg0RGF/Y9t4f4TjeEg0UX0sAwABQIIA" + "LEg0RF2AuwBIfLsoUIBts2R/ZNt4SC3ZfZOfVLiTf1S8g" + "9sANEgAACh/Y9t4SDRTiUg0RC2AbbNsf4TjeEgt13GAbb" + "Nkf2TbeEgt12V/o+t4SDbCPSweAABBggAMf8gDpk6AACF" + "/Q9N4SDRECbtBAAiAAQC0fAgDpjghALBIACc0"), + # NA 1.2 + "us2": base64.b64decode("PYCAM2GMTMR9iAOmToAAITxggDNgY29ESAAABXyIAqY4h" + "ABQSAABSYhtseAsAwAAQYIAIIBtuug4gAAFOKAAAT2AgC" + "phjPbwfYgDpk6AACE4YAAAPICAADigAAA9gIABYYyqsH2" + "JA6ZOgAQglCH/4HwIAqaQAQAkv6EACHx+G3h8nyN4P6CA" + "sH+j63hIAAAFfIgCpjiE/3A4oAMsPYCAOmGMs0x9iAOmT" + "oAAIX+j63g4gAMsSAAA0ZOtvwg4fQMskG2/DDxggDNgYy" + "HEY6QA/EgAAJ09gIAzYYxvSH2JA6Z/w/N4f+T7eLuhAAi" + "AAQAkfAgDpjghACBOgAQgfH8beDxggTNgYzqcPICAsGCE" + "ARxIAABdf+P7eEuDILB8fxt4P8CAAGPDW/Q8gICwOKADL" + "EuKsh1jw1v0OIADLEgAAEk8YIAAYGOGRGPEXchIAAAhPG" + "CAM2Bjb0Q8gIAAYIRcYEgAAA1/4/t4SIM5OHyDIFBUhAG" + "6PKBIAHylI3iQowAAOIAABJQh/+B8CAKmkAEAJL+hAAh8" + "fxt4fJ4jeD+ggDNjpQtYfKgDpk6AACFjoww8fGgDpn/j+" + "3h/xPN4ToAAIbuhAAiAAQAkfAgDpjghACBOgAAgkG2x5J" + "Qh/1B8CAKmkAEAtL9BAAhIMu7pfHobeD/ggAA7wAAAO6E" + "AIGPjXxh/pOt4OKAAAUg1a40sAwAAQIIA+H+j63g4gAAA" + "OKAAAkg1ZPUsAwAAQYAA4DuDAB9XnAA0f6PreDiAAAA4o" + "AAASDVk1SwDAABBgADAgG2y5H+E43g4oAAgSCx7PXx7G3" + "h/o+t4f2TbeH+F43hINWLpLAMAAECBAHCAbbLsgJsAIIC" + "7AERILHsRfHwbeEgy7jF/Y9t4f4TjeEgy+00sAwABQIIA" + "LEgy7i2AuwBIfLsoUIBtsuR/ZNt4SCyDTZOfVLiTf1S8g" + "9sANEgAACh/Y9t4SDL9WUgy7f2AbbLsf4TjeEgsgUGAbb" + "Lkf2TbeEgsgTV/o+t4SDVsdSweAABBggAMf8gDpk6AACF" + "/Q9N4SDLt2btBAAiAAQC0fAgDpjghALBIACc0"), + # PAL + "eu": base64.b64decode("PYCAM2GMUPR9iAOmToAAITxggDNgY3N0SAAABXyIAqY4h" + "ABQSAABSYhtsKAsAwAAQYIAIIBtuag4gAAFOKAAAT2AgC" + "phjPsgfYgDpk6AACE4YAAAPICAADigAAA9gIABYYyqoH2" + "JA6ZOgAQglCH/4HwIAqaQAQAkv6EACHx+G3h8nyN4P6CA" + "sH+j63hIAAAFfIgCpjiE/3A4oAMsPYCAOmGMt9B9iAOmT" + "oAAIX+j63g4gAMsSAAA0ZOtvcg4fQMskG29zDxggDNgYy" + "X0Y6QA/EgAAJ09gIAzYYxzeH2JA6Z/w/N4f+T7eLuhAAi" + "AAQAkfAgDpjghACBOgAQgfH8beDxggTNgYzqcPICAsGCE" + "ARxIAABdf+P7eEuDJOB8fxt4P8CAAGPDW/Q8gICwOKADL" + "EuKtqFjw1v0OIADLEgAAEk8YIAAYGOGRGPEXchIAAAhPG" + "CAM2Bjc3Q8gIAAYIRcYEgAAA1/4/t4SIM5OHyDIFBUhAG" + "6PKBIAHylI3iQowAAOIAABJQh/+B8CAKmkAEAJL+hAAh8" + "fxt4fJ4jeD+ggDNjpQ+IfKgDpk6AACFjoxBsfGgDpn/j+" + "3h/xPN4ToAAIbuhAAiAAQAkfAgDpjghACBOgAAgkG2wpJ" + "Qh/1B8CAKmkAEAtL9BAAhIMvMZfHobeD/ggAA7wAAAO6E" + "AIGPjXxh/pOt4OKAAAUg1b70sAwAAQIIA+H+j63g4gAAA" + "OKAAAkg1aSUsAwAAQYAA4DuDAB9XnAA0f6PreDiAAAA4o" + "AAASDVpBSwDAABBgADAgG2xpH+E43g4oAAgSCx/bXx7G3" + "h/o+t4f2TbeH+F43hINWcZLAMAAECBAHCAbbGsgJsAIIC" + "7AERILH9BfHwbeEgy8mF/Y9t4f4TjeEgy/30sAwABQIIA" + "LEgy8l2AuwBIfLsoUIBtsaR/ZNt4SCyHfZOfVLiTf1S8g" + "9sANEgAACh/Y9t4SDMBiUgy8i2AbbGsf4TjeEgshXGAbb" + "Gkf2TbeEgshWV/o+t4SDVwpSweAABBggAMf8gDpk6AACF" + "/Q9N4SDLyCbtBAAiAAQC0fAgDpjghALBIACc0"), + # JP + "jp": base64.b64decode("PYCAM2GMZ+R9iAOmToAAITxggDNgY4pkSAAABXyIAqY4h" + "ABQSAABSYhtseAsAwAAQYIAIIBtutg4gAAFOKAAAT2AgC" + "thjBIQfYgDpk6AACE4YAAAPICAADigAAA9gIABYYyrLH2" + "JA6ZOgAQglCH/4HwIAqaQAQAkv6EACHx+G3h8nyN4P6CA" + "sH+j63hIAAAFfIgCpjiE/3A4oAMsPYCAOmGMzgR9iAOmT" + "oAAIX+j63g4gAMsSAAA0ZOtvvg4fQMskG2+/DxggDNgYz" + "zkY6QA/EgAAJ09gIAzYYyKaH2JA6Z/w/N4f+T7eLuhAAi" + "AAQAkfAgDpjghACBOgAQgfH8beDxggTNgYzqcPICAsGCE" + "ARxIAABdf+P7eEuDO9B8fxt4P8CAAGPDW/Q8gICwOKADL" + "EuKzNVjw1v0OIADLEgAAEk8YIAAYGOGRGPEXchIAAAhPG" + "CAM2BjimQ8gIAAYIRcYEgAAA1/4/t4SIM5OHyDIFBUhAG" + "6PKBIAHylI3iQowAAOIAABJQh/+B8CAKmkAEAJL+hAAh8" + "fxt4fJ4jeD+ggDNjpSZ4fKgDpk6AACFjoydcfGgDpn/j+" + "3h/xPN4ToAAIbuhAAiAAQAkfAgDpjghACBOgAAgkG2x5J" + "Qh/1B8CAKmkAEAtL9BAAhIMwoJfHobeD/ggAA7wAAAO6E" + "AIGPjXxh/pOt4OKAAAUg1hkUsAwAAQIIA+H+j63g4gAAA" + "OKAAAkg1f60sAwAAQYAA4DuDAB9XnAA0f6PreDiAAAA4o" + "AAASDV/jSwDAABBgADAgG2y5H+E43g4oAAgSCyWXXx7G3" + "h/o+t4f2TbeH+F43hINX2hLAMAAECBAHCAbbLsgJsAIIC" + "7AERILJYxfHwbeEgzCVF/Y9t4f4TjeEgzFm0sAwABQIIA" + "LEgzCU2AuwBIfLsoUIBtsuR/ZNt4SCyebZOfVLiTf1S8g" + "9sANEgAACh/Y9t4SDMYeUgzCR2AbbLsf4TjeEgsnGGAbb" + "Lkf2TbeEgsnFV/o+t4SDWHLSweAABBggAMf8gDpk6AACF" + "/Q9N4SDMI+btBAAiAAQC0fAgDpjghALBIACc0"), + } +} + +# +------------------+ +# | Common functions | +# +------------------+ + + +def alignUp(value: int, size: int): + """Aligns and address to the given size. + + :param value: The address to align up. + :type value: int + :param size: The size to which we align the address. + :type size: int + + :return The aligned address. + :rtype int + """ + return value + (size - value % size) % size + + +def sha1(data: bytes) -> bytes: + hasher = hashlib.sha1() + hasher.update(data) + return hasher.digest() + + +def md5(data: bytes) -> bytes: + hasher = hashlib.md5() + hasher.update(data) + return hasher.digest() + + +def aes_cbc_decrypt(key: bytes, iv: bytes, data: bytes) -> bytes: + return AES.new(key, AES.MODE_CBC, iv).decrypt(data) + + +def aes_cbc_encrypt(key: bytes, iv: bytes, data: bytes) -> bytes: + return AES.new(key, AES.MODE_CBC, iv).encrypt(data) + +# +-------------------+ +# | Class definitions | +# +-------------------+ + +# Classes used to generate a new signature +# (Algorithm from the Dolphin Emulator project) + + +class Elt: + pass + + +class Elt: + __slots__ = ('data',) + + def __init__(self, data: bytes = bytes(30)): + self.data = bytes(data[0:min(30, len(data))]) + \ + bytes(max(0, 30 - len(data))) + + def is_zero(self): + return all(d == 0 for d in self.data) + + def mulX(self): + new_data = bytearray(self.data) + carry = new_data[0] & 1 + + # x = (self.data[29] << 1) & 0xFF + # xs = itertools.chain([0], ((y << 1) & 0xFF for y in self.data[1:-1])) + # new_data[:-1] = list((x ^ (y >> 7)) & 0xFF for x, y in zip(xs, self.data[1:])) + x = 0 + for i in range(29): + y = new_data[i + 1] + new_data[i] = (x ^ (y >> 7)) & 0xFF + x = (y << 1) & 0xFF + + new_data[29] = (x ^ carry) & 0xFF + new_data[20] ^= (carry << 2) & 0xFF + self.data = bytes(new_data) + + def square(self): + # wide = bytearray(y for x in ( + # (SQUARE[x >> 4], SQUARE[x & 0xF]) for x in self.data) for y in x) + wide = bytearray(60) + for i in range(30): + wide[2 * i] = SQUARE[self.data[i] >> 4] + wide[2 * i + 1] = SQUARE[self.data[i] & 0xF] + + for i in range(30): + x = wide[i] + wide[i + 19] ^= x >> 7 + wide[i + 20] ^= (x << 1) & 0xFF + wide[i + 29] ^= x >> 1 + wide[i + 30] ^= (x << 7) & 0xFF + x = wide[30] & ~1 + wide[49] ^= x >> 7 + wide[50] ^= (x << 1) & 0xFF + wide[59] ^= x >> 1 + wide[30] &= 1 + return Elt(wide[30:]) + + def ItohTsujii(self, b, j): + t = Elt(self.data) + for _ in range(j): + t = t.square() + return t * b + + def inv(self): + t = self.ItohTsujii(self, 1) + s = t.ItohTsujii(self, 1) + t = s.ItohTsujii(s, 3) + s = t.ItohTsujii(self, 1) + t = s.ItohTsujii(s, 7) + s = t.ItohTsujii(t, 14) + t = s.ItohTsujii(self, 1) + s = t.ItohTsujii(t, 29) + t = s.ItohTsujii(s, 58) + s = t.ItohTsujii(t, 116) + return s.square() + + def __add__(self, other): + if not isinstance(other, Elt): + raise TypeError( + f"Cannot add {self.__class__.__name__} " + f"with {other.__class__.__name__}") + return Elt(bytes(a ^ b for a, b in zip(self.data, other.data))) + + def __truediv__(self, other): + if not isinstance(other, Elt): + raise TypeError( + f"Cannot divide {self.__class__.__name__} " + f"with {other.__class__.__name__}") + return self * other.inv() + + def __mul__(self, other: Elt): + if not isinstance(other, Elt): + raise TypeError( + f"Cannot multiply {self.__class__.__name__} " + f"with {other.__class__.__name__}") + d = Elt() + i = 0 + mask = 1 + for _ in range(233): + d.mulX() + if (self.data[i] & mask) != 0: + d = d + other + mask >>= 1 + if mask == 0: + mask = 0x80 + i += 1 + return d + + +class Point: + __slots__ = ('x', 'y',) + + def __init__(self, x: Elt = Elt(), y: Elt = Elt()): + self.x = Elt(x.data) + self.y = Elt(y.data) + + def is_zero(self): + return self.x.is_zero() and self.y.is_zero() + + def double(self): + r = Point() + if self.x.is_zero(): + return r + s = (self.y / self.x) + # print(s.data.hex()) + s = s + self.x + r.x = s.square() + s + r_x_data = bytearray(r.x.data) + r_x_data[29] ^= 1 + r.x.data = bytes(r_x_data) + r.y = s * r.x + r.x + self.x.square() + return r + + def __add__(self, other): + if not isinstance(other, Point): + raise TypeError( + f"Cannot add {self.__class__.__name__} " + f"with {other.__class__.__name__}") + if self.is_zero(): + return Point(other.x, other.y) + if other.is_zero(): + return Point(self.x, self.y) + u = self.x + other.x + if u.is_zero(): + u = self.y + other.y + if u.is_zero(): + return self.double() + return Point() + s = (self.y + other.y) / u + t = s.square() + s + other.x + t_data = bytearray(t.data) + t_data[29] ^= 1 + t.data = bytes(t_data) + rx = t + self.x + ry = s * t + self.y + rx + return Point(rx, ry) + + def __mul__(self, other): + if not isinstance(other, bytes | bytearray | memoryview): + raise TypeError( + f"Point only multiplies with bytes, " + f"got {other.__class__.__name__}") + d = Point() + for i in range(30): + mask = 0x80 + while mask != 0: + d = d.double() + if (other[i] & mask) != 0: + d = d + self + mask >>= 1 + return d + + +EC_G = Point(Elt(bytes([0x00, 0xfa, 0xc9, 0xdf, 0xcb, 0xac, + 0x83, 0x13, 0xbb, 0x21, 0x39, 0xf1, + 0xbb, 0x75, 0x5f, 0xef, 0x65, 0xbc, + 0x39, 0x1f, 0x8b, 0x36, 0xf8, 0xf8, + 0xeb, 0x73, 0x71, 0xfd, 0x55, 0x8b])), + Elt(bytes([0x01, 0x00, 0x6a, 0x08, 0xa4, 0x19, + 0x03, 0x35, 0x06, 0x78, 0xe5, 0x85, + 0x28, 0xbe, 0xbf, 0x8a, 0x0b, 0xef, + 0xf8, 0x67, 0xa7, 0xca, 0x36, 0x71, + 0x6f, 0x7e, 0x01, 0xf8, 0x10, 0x52]))) + +EC_N = bytes([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0xe9, 0x74, 0xe7, 0x2f, + 0x8a, 0x69, 0x22, 0x03, 0x1d, 0x26, 0x03, 0xcf, 0xe0, 0xd7]) + + +class SignatureType(IntEnum): + RSA4096 = 0x00010000 + RSA2048 = 0x00010001 + ECC = 0x00010002 + + +class PublicKeyType(IntEnum): + RSA4096 = 0x00000000 + RSA2048 = 0x00000001 + ECC = 0x00000002 + + +class SignatureECC: + __slots__ = ('type', 'sig', 'fill', 'issuer',) + PACK_FORMAT = struct.Struct('>I60s64s64s') + + def __init__(self, type: SignatureType, sig, fill, issuer): + self.type = type + self.sig = sig + self.fill = fill + self.issuer = issuer + + def pack(self): + return SignatureECC.PACK_FORMAT.pack(self.type.value, self.sig, self.fill, self.issuer) + + +class CertHeader: + __slots__ = ('public_key_type', 'name', 'id',) + PACK_FORMAT = struct.Struct('>I64sI') + + def __init__(self, public_key_type: PublicKeyType, name, id): + self.public_key_type = public_key_type + self.name = name + self.id = id + + def pack(self): + return CertHeader.PACK_FORMAT.pack(self.public_key_type.value, self.name, self.id) + + +class CertECC: + __slots__ = ("signature", "header", "public_key", "padding",) + + def __init__(self, signature: SignatureECC, header: CertHeader, public_key: bytes, padding: bytes): + self.signature = signature + self.header = header + self.public_key = public_key + self.padding = padding + + def pack(self): + return self.signature.pack() + self.header.pack() + self.public_key + self.padding + +# +---------------------+ +# | Signature functions | +# +---------------------+ + + +def priv_to_pub(key: bytes): + data = EC_G * bytes(key) + return data.x.data + data.y.data + + +def make_blank_ecc_cert(issuer, name, public_key: bytes, key_id): + return CertECC(SignatureECC(SignatureType.ECC, bytes(60), bytes(0x40), bytes(issuer, 'utf-8')[0:min(0x3F, len(issuer))] + b'\0'), CertHeader(PublicKeyType.ECC, bytes(name, 'utf-8')[0:min(0x3F, len(name))] + b'\0', key_id), public_key, bytes(60)) + + +def get_device_certificate(device_id=DEFAULT_DEVICE_ID): + name = f"NG{device_id:08x}" + cert = make_blank_ecc_cert( + f"Root-CA{CA_ID:08x}-MS{MS_ID:08x}", name, DEFAULT_PUBLIC_KEY, DEFAULT_KEY_ID) + cert.signature.sig = DEFAULT_SIGNATURE + return cert + + +def bn_sub_modulus(a, N, n): + a = bytearray(a) + c = 0 + for i in range(n - 1, -1, -1): + dig = N[i] + c + c = a[i] < dig + a[i] = (a[i] - dig) & 0xFF + return a + + +def bn_add(a: bytes, b: bytes, N, n): + d = bytearray(n) + c = 0 + for i in range(n - 1, -1, -1): + dig = a[i] + b[i] + c + c = (dig >= 0x100) + d[i] = (dig) & 0xFF + + if c: + d = bn_sub_modulus(d, N, n) + if d >= N: + d = bn_sub_modulus(d, N, n) + return d + + +def bn_mul(a, b, N, n): + d = bytearray(n) + for i in range(n): + mask = 0x80 + while mask != 0: + d = bn_add(d, d, N, n) + if (a[i] & mask) != 0: + d = bn_add(d, b, N, n) + mask >>= 1 + return d + + +def bn_exp(a, N, n, e, en): + t = bytearray(512) + d = bytearray(n) + d[n - 1] = 1 + for i in range(en): + mask = 0x80 + while mask != 0: + t = bn_mul(d, d, N, n) + if (e[i] & mask) != 0: + d = bn_mul(t, a, N, n) + else: + d[:] = t[:n] + mask >>= 1 + return d + + +def bn_inv(a, N, n): + d = bytearray(n) + t = bytearray(512) + s = bytearray(512) + t[0:n] = N + s[n - 1] = 2 + t = bn_sub_modulus(t, s, n) + d = bn_exp(a, N, n, t, n) + return d + + +def sign2(key: bytes, hash: bytes): + e = bytearray(30) + e[10:] = hash + m = bytearray(secrets.token_bytes(30)) + m[0] &= 1 + while m >= EC_N: + m = bytearray(secrets.token_bytes(30)) + m[0] &= 1 + r = (EC_G * m).x + if r.data >= EC_N: + r.data = bytes(bn_sub_modulus(r.data, EC_N, 30)) + + kk = bytearray(30) + kk[:] = key + if kk >= EC_N: + kk = bn_sub_modulus(kk, EC_N, 30) + s = Elt(bn_mul(r.data, kk, EC_N, 30)) + kk = bn_add(s.data, e, EC_N, 30) + minv = bn_inv(m, EC_N, 30) + s.data = bytes(bn_mul(minv, kk, EC_N, 30)) + + signature = bytearray(60) + signature[:30] = r.data + signature[30:] = s.data + return bytes(signature) + + +def sign(title_id, data, device_id=DEFAULT_DEVICE_ID): + hash = bytearray(20) + ap_priv = bytearray(30) + ap_priv[0x1d] = 1 + + logging.debug(f"Signing for device #{device_id:08x}") + # In practice, we can reduce the encryption time by using a + # pre-calculated "cert", but it's not a significant amount of time + signer = f"Root-CA{CA_ID:08x}-MS{MS_ID:08x}-NG{device_id:08x}" + name = f"AP{title_id:016x}" + logging.debug("Creating certificate...") + cert = make_blank_ecc_cert(signer, name, priv_to_pub(ap_priv), 0) + cert_packed = cert.pack() + hash = sha1(cert_packed[0x80:]) + cert.signature.sig = sign2(DEFAULT_PRIVATE_KEY, hash) + cert = cert.pack() + + logging.debug("Signing data...") + hash = sha1(data) + signature = sign2(ap_priv, hash) + logging.debug("Signed") + + return (signature, cert) + + +def verify_signature(public_key, signature, hash): + r = signature[:30] + s = signature[30:] + + s_inv = bn_inv(s, EC_N, 30) + e = bytearray(30) + e[10:] = hash[:20] + + w1 = bn_mul(e, s_inv, EC_N, 30) + w2 = bn_mul(r, s_inv, EC_N, 30) + + public_key_point = Point(Elt(public_key[:30]), Elt(public_key[30:])) + r1 = EC_G * w1 + public_key_point * w2 + rx = r1.x.data + if rx >= EC_N: + rx = bn_sub_modulus(rx, EC_N, 30) + + return rx == r + + +class Header: + """The encryption header. + + It contains the necessary data for the save file's encryption and + signature. It wraps the archive that contains the game's saved + files. + """ + + __slots__ = ("tid", "banner_size", "permissions", + "unk1", "md5", "unk2", "banner",) + PACK_FORMAT = struct.Struct('>QIBB16sH') + PACK_SIZE = PACK_FORMAT.size + FULL_BNR_MAX + + def __init__(self, tid: int, banner_size: int, permissions: int, unk1: int, md5: bytes, unk2: int, banner=bytes()): + self.tid = tid + self.banner_size = banner_size + self.permissions = permissions + self.unk1 = unk1 + self.md5 = md5 + self.unk2 = unk2 + self.banner = banner + + def __repr__(self) -> str: + return (f"Header(tid=0x{self.tid:016x}, " + f"banner_size=0x{self.banner_size:08x}, " + f"permissions=0x{self.permissions:02x}, " + f"unk1=0x{self.unk1:02x}, " + f"md5=0x{self.md5.hex()}, " + f"unk2={self.unk2:04x}, " + f"banner={self.banner})") + + def __str__(self) -> str: + return (f"Header(tid=0x{self.tid:016x}, " + f"banner_size=0x{self.banner_size:08x}, " + f"permissions=0x{self.permissions:02x}, " + f"unk1=0x{self.unk1:02x}, " + f"md5=0x{self.md5.hex()}, " + f"unk2={self.unk2:04x}, " + f"banner={self.banner.__class__.__name__}({len(self.banner)}))") + + def updateBanner(self, banner: bytes): + """Updates the headers and sets the new banner. + + :param banner: The banner file + :type banner: bytes + """ + new_size = len(banner) + if (new_size < FULL_BNR_MIN) or (new_size > FULL_BNR_MAX) or (((new_size - BNR_SZ) % ICON_SZ) != 0): + logging.error(f"Invalid banner size {new_size:04x}") + self.banner_size = new_size + self.banner = banner + self.md5 = md5(self.to_bytes()) + + @staticmethod + def generate(banner: bytes, game_version: str): + """Generates a new header from a given banner for the given version + of 'The Legend of Zelda: Twilight Princess'. + """ + hdr = Header(VERSIONS[game_version], len( + banner), 0x34, 0, MD5_BLANKER, 0, banner) + hdr.md5 = md5(hdr.to_bytes()) + return hdr + + @staticmethod + def unpack(buffer: bytes): + data = aes_cbc_decrypt(SD_KEY, SD_INITIAL_IV, + buffer[0:Header.PACK_SIZE]) + hdr = Header( + *Header.PACK_FORMAT.unpack(data[0:Header.PACK_FORMAT.size])) + hdr.banner = data[Header.PACK_FORMAT.size:][0:hdr.banner_size] + if (hdr.banner_size < FULL_BNR_MIN) or (hdr.banner_size > FULL_BNR_MAX) or (((hdr.banner_size - BNR_SZ) % ICON_SZ) != 0): + logging.error( + "Warning: Not a Wii save or read failure for " + f"banner size 0x{hdr.banner_size:04x} ({hdr.banner_size})") + return + hdr_md5 = hdr.md5 + hdr.md5 = MD5_BLANKER + md5_calc = md5(hdr.to_bytes()) + hdr.md5 = hdr_md5 + if md5_calc != hdr_md5: + logging.error( + f"[Header] MD5 mismatch: {hdr_md5.hex()} != {md5_calc.hex()}") + return + return hdr + + @staticmethod + def from_file(reader: FileIO): + reader.seek(0) + return Header.unpack(reader.read(Header.PACK_SIZE)) + + def to_bytes(self): + data1 = Header.PACK_FORMAT.pack( + self.tid, self.banner_size, self.permissions, self.unk1, self.md5, self.unk2) + data2 = self.banner + if len(data2) < FULL_BNR_MAX: + data2 += bytes(FULL_BNR_MAX - self.banner_size) + return data1 + data2 + + def pack(self): + self.md5 = MD5_BLANKER + self.md5 = md5(self.to_bytes()) + return aes_cbc_encrypt(SD_KEY, SD_INITIAL_IV, self.to_bytes()) + + +class BkHeader: + """The archive header. + + This header contains the data related to the archival of + the files saved by the game. + """ + + __slots__ = ("size", "magic", "ngid", "number_of_files", "size_of_files", + "unk1", "unk2", "total_size", "unk3", "tid", "mac_address", + "padding",) + PACK_FORMAT = struct.Struct('>8I64sQ6s18s') + MAGIC = 0x426B0001 + + def __init__(self, size, magic, ngid, number_of_files, size_of_files, unk1, unk2, total_size, unk3, tid, mac_address, padding): + self.size = size + self.magic = magic + self.ngid = ngid + self.number_of_files = number_of_files + self.size_of_files = size_of_files + self.unk1 = unk1 + self.unk2 = unk2 + self.total_size = total_size + self.unk3 = unk3 + self.tid = tid + self.mac_address = mac_address + self.padding = padding + + def __repr__(self) -> str: + return f"BkHeader(size=0x{self.size:08x}, magic=0x{self.magic:08x}, ngid=0x{self.ngid:08x}, number_of_files={self.number_of_files}, size_of_files={self.size_of_files}, unk1={self.unk1}, unk2={self.unk2}, total_size={self.total_size}, unk3={self.unk3}, tid=0x{self.tid:016x}, mac_address={self.mac_address}, padding={self.padding})" + + def __str__(self) -> str: + return f"BkHeader(size=0x{self.size:08x}, magic=0x{self.magic:08x}, ngid=0x{self.ngid:08x}, number_of_files={self.number_of_files}, size_of_files={self.size_of_files}, unk1={self.unk1}, unk2={self.unk2}, total_size={self.total_size}, unk3={self.unk3.__class__.__name__}({len(self.unk3)}), tid=0x{self.tid:016x}, mac_address={self.mac_address.hex()}, padding={self.padding.__class__.__name__}({len(self.padding)}))" + + @staticmethod + def generate(files: list, game_version: str): + size_of_files = sum(len(file) for file in files) + bkh = BkHeader(BK_LISTED_SZ, BkHeader.MAGIC, DEFAULT_DEVICE_ID, len(files), size_of_files, 0, 0, + size_of_files + FULL_CERT_SZ, bytes(0x40), VERSIONS[game_version], bytes(6), bytes(0x12)) + return bkh + + @staticmethod + def unpack(buffer): + hdr = BkHeader(*BkHeader.PACK_FORMAT.unpack(buffer)) + if hdr.size != BK_LISTED_SZ: + logging.error( + f"[BkHeader] Invalid header size: {BK_LISTED_SZ} != {hdr.size}") + return + if hdr.magic != BkHeader.MAGIC: + logging.error( + f"[BkHeader] Magic mismatch: {BkHeader.MAGIC:08x} != {hdr.magic:08x}") + return + if hdr.size_of_files + FULL_CERT_SZ != hdr.total_size: + logging.error( + f"[BkHeader] Invalid files size: {hdr.size_of_files + FULL_CERT_SZ} != {hdr.total_size}") + return + return hdr + + @staticmethod + def from_file(reader: FileIO): + reader.seek(Header.PACK_SIZE) + data = reader.read(BkHeader.PACK_FORMAT.size) + return BkHeader.unpack(data) + + def to_bytes(self): + return BkHeader.PACK_FORMAT.pack(self.size, self.magic, self.ngid, self.number_of_files, self.size_of_files, self.unk1, self.unk2, self.total_size, self.unk3, self.tid, self.mac_address, self.padding) + + def pack(self): + return self.to_bytes() + + +class NodeType(IntEnum): + FILE = 1 + DIR = 2 + + +class FileHDR: + __slots__ = ("magic", "size", "permissions", "attrib", + "node_type", "name", "padding", "iv", "unk",) + PACK_FORMAT = struct.Struct('>II3B64s5s16s32s') + MAGIC = 0x03adf17e + + def __init__(self, magic, size, permissions, attrib, node_type: NodeType, name, padding, iv, unk): + self.magic = magic + self.size = size + self.permissions = permissions + self.attrib = attrib + self.node_type = NodeType(node_type) + self.name = name + self.padding = padding + self.iv = iv + self.unk = unk + + def __str__(self) -> str: + return f"FileHDR(magic=0x{self.magic:08x}, size=0x{self.size:08x}, permissions=0x{self.permissions:02x}, attrib=0x{self.attrib:02x}, node_type={self.node_type.name[:1].upper() + self.node_type.name.lower()[1:]}, name={self.name}, padding=bytes({len(self.padding)}), iv={self.iv.hex()}, unk={self.unk})" + + @staticmethod + def unpack(buffer): + file_hdr = FileHDR(*FileHDR.PACK_FORMAT.unpack(buffer)) + if file_hdr.magic != FileHDR.MAGIC: + logging.error( + f"[FileHDR] magic field mismatch: {FileHDR.MAGIC:08x} != {file_hdr.magic:08x}") + return + return file_hdr + + @staticmethod + def from_save_file(save): + if len(save.path) >= 0x40: + logging.error( + f"Error: file name {save.path!a} is too long (64 characters max)") + return + file_hdr = FileHDR(FileHDR.MAGIC, len(save.data) if save.node_type == 1 else 0, save.mode, + save.attributes, save.node_type, bytes(save.path, 'utf-8')[:min(0x40, len(save.path))] + bytes(max(0, 0x40-len(save.path))), bytes(5), SD_INITIAL_IV, bytes(0x20)) + return file_hdr + + def pack(self): + return FileHDR.PACK_FORMAT.pack(self.magic, self.size, self.permissions, self.attrib, self.node_type, self.name, self.padding, self.iv, self.unk) + + +class SaveNode: + def __init__(self, mode=0, attributes=0, node_type: NodeType = NodeType.DIR, path="", data=None): + self.mode = mode + self.attributes = attributes + self.node_type = NodeType(node_type) + self.path = path + self.data = data + + def __repr__(self) -> str: + data = f", data={self.data}" if self.data is not None else "" + return f"SaveNode(path={repr(self.path)}, node_type={self.node_type}, mode={self.mode}, attributes={self.attributes}{data})" + + def __str__(self) -> str: + data = f", data={self.data.__class__.__name__}({len(self.data)})" if self.data is not None else "" + return f"SaveNode(path=\"{self.path}\", node_type={self.node_type.name[:1].upper() + self.node_type.name.lower()[1:]}, mode={self.mode}, attributes={self.attributes}{data})" + + @staticmethod + def unpack(reader: FileIO): + data = reader.read(FileHDR.PACK_FORMAT.size) + file_hdr = FileHDR.unpack(data) + if file_hdr is None: + return + file_data = None + if file_hdr.node_type == NodeType.FILE: + size = file_hdr.size + rounded_size = alignUp(size, BLOCK_SZ) + file_data = reader.read(rounded_size) + file_data = aes_cbc_decrypt(SD_KEY, file_hdr.iv, file_data)[0:size] + return SaveNode(file_hdr.permissions, file_hdr.attrib, file_hdr.node_type, + file_hdr.name.split(b'\x00')[0].decode('utf-8'), file_data) + + @staticmethod + def unpack_all(reader: FileIO, bkh: BkHeader): + files: List[SaveNode] = [] + reader.seek(Header.PACK_SIZE + BkHeader.PACK_FORMAT.size) + for i in range(0, bkh.number_of_files): + logging.debug(f"[SaveNode] Unpacking file #{i}") + file = SaveNode.unpack(reader) + if not file is None: + files.append(file) + else: + logging.error(f"[SaveNode] Error while unpacking file #{i}") + return + return files + + def pack(self): + file_hdr = FileHDR.from_save_file(self) + if file_hdr is None: + return + data_enc = bytes(0) + if not self.data is None and len(self.data) > 0: + data_aligned = self.data + \ + bytes(alignUp(len(self.data), BLOCK_SZ) - len(self.data)) + data_enc = aes_cbc_encrypt(SD_KEY, file_hdr.iv, data_aligned) + return file_hdr.pack() + data_enc + + def metadata(self): + return {'path': self.path, 'mode': self.mode, 'attributes': self.attributes, 'type': self.node_type} + + @staticmethod + def pack_all(files): + data = bytes() + for file in files: + data = data + file.pack() + return data + + def __len__(self): + return FileHDR.PACK_FORMAT.size + alignUp(len(self.data), BLOCK_SZ) if self.node_type == 1 else 0 + + +class SaveBin: + def __init__(self, header: Header, bkheader: BkHeader, files: List[SaveNode]): + self.header = header + self.bkheader = bkheader + self.files = files + + def __repr__(self) -> str: + return f"SaveBin(header={repr(self.header)}, bkheader={repr(self.bkheader)}, files=[{', '.join([repr(file) for file in self.files])}])" + + def __str__(self) -> str: + return f"SaveBin(header={str(self.header)}, bkheader={str(self.bkheader)}, files=[{', '.join([str(file) for file in self.files])}])" + + @staticmethod + def from_file(reader: FileIO): + header = Header.from_file(reader) + if header is None: + logging.error("[SaveBin] Could not parse Header") + return + bkheader = BkHeader.from_file(reader) + if bkheader is None: + logging.error("[SaveBin] Could not parse BkHeader") + return + files = SaveNode.unpack_all(reader, bkheader) + if files is None: + logging.error("[SaveBin] Could not parse files") + return + return SaveBin(header, bkheader, files) + + @staticmethod + def generate(banner: FileIO, game_version: str): + files = [] + bkheader = BkHeader.generate(files, game_version) + header = Header.generate(banner.read(), game_version) + return SaveBin(header, bkheader, files) + + def to_file(self, writer: FileIO): + writer.write(self.header.pack()) + data1 = self.bkheader.pack() + SaveNode.pack_all(self.files) + writer.write(data1) + data_sha1 = sha1(data1) + ap_sig, ap_cert = sign(SYSTEM_MENU, data_sha1, self.bkheader.ngid) + writer.write(ap_sig + struct.pack('>I', SIGNATURE_END_MAGIC) + + get_device_certificate(self.bkheader.ngid).pack() + ap_cert) + + def _update_bk_files(self): + self.bkheader.number_of_files = len(self.files) + self.bkheader.size_of_files = sum(len(file) for file in self.files) + self.bkheader.total_size = self.bkheader.size_of_files + FULL_CERT_SZ + + def add_file(self, path, data, mode=0x3f, attributes=0, node_type=NodeType.FILE): + indices = [i for i, f in enumerate( + self.files) if f.path == path] + indices.sort(reverse=True) + # Remove all duplicate occurences + n_idx = len(indices) + if n_idx > 0: + logging.debug( + f"{n_idx} file{'s' if n_idx > 1 else ''} already exist with the same name. Removing them...") + for idx in indices: + self.files.pop(idx) + # Split the path into component dirs, and fold it to get each directory's full path. + def fold(acc, x): + l = [] + l.extend(acc) + l.append(acc[-1] + '/' + x) + return l + dirs = path.split('/')[:-1] + if len(dirs) > 1: + dirs = reduce(fold, dirs[1:], [dirs[0]]) + # Checking for all the parent directories if they exist + for d in dirs: + # For each parent directory, find it in the list... + f = next((f for f in self.files if f.path == d), None) + if not f is None: + # ... if found, skip to the next directory... + if f.node_type != NodeType.DIR: + raise RuntimeError(f"'{d}' already exists and is not a directory") + logging.debug(f"'{d}' found") + continue + else: + # ... else create directory. + logging.debug(f"'{d}' not found") + self.add_file(d, None, mode, attributes, NodeType.DIR) + logging.debug(f"Adding '{path}'...") + self.files.append(SaveNode(mode, attributes, node_type, path, data)) + self._sort_files() + self._update_bk_files() + + def _sort_files(self): + self.files.sort(key=lambda x: x.path) + pass + + def rm_file(self, path): + indices = [i for i, f in enumerate( + self.files) if f.path == path] + indices.sort(reverse=True) + # Remove all occurences + n_idx = len(indices) + if n_idx > 0: + # If it is a directory, remove all the children recursively + for f in [self.files[idx] for idx in indices]: + if f.node_type == NodeType.DIR: + for sub_path in [x.path for x in self.files if x.path.startswith(f.path + '/')]: + self.rm_file(sub_path) + logging.debug(f"removing '{path}'...") + for idx in indices: + self.files.pop(idx) + self._sort_files() + self._update_bk_files() + + def config(self): + return {'tid': self.header.tid, 'permissions': self.header.permissions, 'ngid': self.bkheader.ngid, 'mac_address': struct.unpack('>Q', bytes(2) + self.bkheader.mac_address)[0], 'files': [file.metadata() for file in self.files]} + +# +------------------------------+ +# | Save file patching functions | +# +------------------------------+ + + +def find_zeldaTp_idx(save_bin: SaveBin): + idx = -1 + for i in range(len(save_bin.files)): + file = save_bin.files[i] + if file.path == "zeldaTp.dat": + idx = i + return idx if idx > -1 else None + + +def update_checksum(data: bytes, fileNumber: int): + data = bytearray(data) + + offsetFile0 = (fileNumber - 1) * 0xA94 + 0x8 + offsetFile1 = offsetFile0 + 0x2000 + + def patchFilesU32(offset: int, value: int): + data[offsetFile0 + offset:offsetFile0 + + offset + 4] = struct.pack('>I', value) + data[offsetFile1 + offset:offsetFile1 + + offset + 4] = struct.pack('>I', value) + + dataFieldSize = 0xA8C + + dataFieldSum = 0 + for i in range(dataFieldSize): + dataFieldSum = ctypes.c_uint32( + dataFieldSum + data[offsetFile0 + i]).value + + # Patch in checksums. + patchFilesU32(0xA8C, dataFieldSum) + patchFilesU32( + 0xA90, ctypes.c_uint32(-(dataFieldSum + dataFieldSize)).value) + + return bytes(data) + + +def patch_file(data: bytes, fileNumber: int, version: str, rel_name: str = "mod.rel", bin_data_version='1', file_name=None): + data = bytearray(data) + + offsetFile0 = (fileNumber - 1) * 0xA94 + 0x8 + offsetFile1 = offsetFile0 + 0x2000 + + def patchFilesU16(offset: int, value: int): + data[offsetFile0 + offset:offsetFile0 + + offset + 2] = struct.pack('>H', value) + data[offsetFile1 + offset:offsetFile1 + + offset + 2] = struct.pack('>H', value) + + def patchFilesU32(offset: int, value: int): + data[offsetFile0 + offset:offsetFile0 + + offset + 4] = struct.pack('>I', value) + data[offsetFile1 + offset:offsetFile1 + + offset + 4] = struct.pack('>I', value) + + def patchFilesU64(offset: int, value: int): + data[offsetFile0 + offset:offsetFile0 + + offset + 8] = struct.pack('>Q', value) + data[offsetFile1 + offset:offsetFile1 + + offset + 8] = struct.pack('>Q', value) + + def patchFilesS64(offset: int, value: int): + data[offsetFile0 + offset:offsetFile0 + + offset + 8] = struct.pack('>q', value) + data[offsetFile1 + offset:offsetFile1 + + offset + 8] = struct.pack('>q', value) + + def patchFilesBytes(offset: int, value: bytes): + data[offsetFile0 + offset:offsetFile0 + offset + len(value)] = value + data[offsetFile1 + offset:offsetFile1 + offset + len(value)] = value + + # Set the last save time to the current date (as a kind of build date) + ticks = (datetime.now(timezone.utc).replace(tzinfo=None) - datetime(2000, 1, 1) + ).total_seconds() * (OS_BUS_CLOCK / 4) + patchFilesS64(0x28, int(ticks)) + + # Write the new file name (Link's name). + if file_name is None: + file_name = b'REL Loader v' + bytes(bin_data_version, 'utf-8') + else: + file_name = bytes(file_name, 'utf-8') + patchFilesBytes(0x1B4, file_name + b'\0') + + # Overwrite the next stage string with a bunch of filler 3s. + patchFilesBytes(0x58, b"3" * 0x12) + + # Write the pointer to the pointer to the init ASM function. + patchFilesU32(0x6A, GAME_INFO_PTR[version] + 0x1CC - 0xBC) + + # Write the pointer to the init ASM function. + patchFilesU32(0x1CC, GAME_INFO_PTR[version] + 0x1E4) + + # Write the init ASM function. + patchFilesBytes(0x1E4, BIN_DATA_INIT[version]) + + # Write the main ASM function. + patchFilesBytes( + 0x1E4 + len(BIN_DATA_INIT[version]), BIN_DATA_MAIN[bin_data_version][version] + bytes(rel_name, 'utf-8') + b'\x00') + + return update_checksum(data, fileNumber) + + +def generate_zeldaTp(save_bin: SaveBin): + data = bytes(0x4000) + for i in range(1, 4): + data = update_checksum(data, i) + save_bin.add_file("zeldaTp.dat", data, 0x34, 0, NodeType.FILE) + +# +-------------+ +# | Main script | +# +-------------+ + +def main(): + + # Function definitions used in the argument parser + # (shouldn't be defined in the root scope, since there + # is no need to make them available when imported as library) + + def parseFileNumber(string): + val = int(string) + if val < 1 or val > 3: + raise argparse.ArgumentTypeError( + "File number can only have integer values between 1 and 3") + return val + + + def parseFileName(string): + if len(string) > 12: + raise argparse.ArgumentTypeError( + f"File name is too long (12 characters max; got {len(string)})") + return string + + + def parseSaveFileName(string): + if len(string) > 31: + raise argparse.ArgumentTypeError( + f"File path is too long (31 characters max; got {len(string)})") + return string + + + def directoryPathParser(string): + if os.path.exists(string): + if os.path.isfile(string): + raise argparse.ArgumentTypeError( + "The output has to be an existing directory or a new directory; Got a file") + return string + + + def filePathParser(string): + if os.path.exists(string): + if not os.path.isfile(string): + raise argparse.ArgumentTypeError( + "The output has to be a file; Got a directory") + return string + + file_map_parser_re = re.compile("^([^\\:]*?)\\:([^\\:]*?)$") + def fileMapParser(string): + m = file_map_parser_re.match(string) + if not m: + raise argparse.ArgumentTypeError(f'"{string}" is not a valid mapping') + if len(m.group(2)) > 31: + raise argparse.ArgumentTypeError( + f"File path is too long (31 characters max; got {len(m.group(2))})") + return string + + class NameMapping: + def __init__(self, old, new): + self.old = old + self.new = new + + @staticmethod + def parse(string): + m = file_map_parser_re.match(string) + return NameMapping(m.group(1), m.group(2)) + + # Functions only used in the main function + # (shouldn't be defined in the root scope, since there + # is no need to make them available when imported as library) + + def updateMetaData(save_bin: SaveBin, meta: Dict[str, Any]): + save_bin.header.tid = meta["tid"] if "tid" in meta else save_bin.header.tid + save_bin.header.permissions = meta["permissions"] if "permissions" in meta else save_bin.header.permissions + save_bin.bkheader.ngid = meta["ngid"] if "ngid" in meta else save_bin.bkheader.ngid + save_bin.bkheader.mac_address = struct.pack(">Q", meta["mac_address"])[ + 2:] if "mac_address" in meta else save_bin.bkheader.mac_address + if "files" in meta: + for i in range(len(save_bin.files)): + try: + idx = [file["path"] + for file in meta["files"]].index(save_bin.files[i].path) + save_bin.files[i].attributes = meta["files"][idx]["attributes"] if "attributes" in meta["files"][idx] else save_bin.files[i].attributes + save_bin.files[i].mode = meta["files"][idx]["mode"] if "mode" in meta["files"][idx] else save_bin.files[i].mode + save_bin.files[i].node_type = meta["files"][idx]["type"] if "type" in meta["files"][idx] else save_bin.files[i].node_type + except ValueError as err: + continue + + + parser = argparse.ArgumentParser( + sys.argv[0], description="Tool to pack/unpack Wii saves & inject REL modules.") + parser.add_argument("-v", "--verbose", action="count", + help="increase verbosity of the output", default=1) + parser.add_argument("-q", "--quiet", action="store_true", + help="prevents output to the console", default=False) + parser.add_argument("-V", "--version", action="version", version=VERSION) + subparsers = parser.add_subparsers( + dest="command", metavar="command", help="Available commands are: generate, inject, patch, unpack, pack, meta, banner, files") + # Generate + gen_parser = subparsers.add_parser( + "generate", description="Generate a new save file", help="Generate a new save file") + gen_parser.add_argument("-i", "--index", action="append", type=parseFileNumber, + help="file number to inject the custom rel into (1 to 3)") + gen_parser.add_argument("-n", "--name", type=parseFileName, + help="overwrite the name of the internal REL file that will be loaded", default="mod.rel") + gen_parser.add_argument("rel", type=argparse.FileType( + 'rb'), help="Path to the REL module to pack") + gen_parser.add_argument("banner", type=argparse.FileType( + 'rb'), help="Path to the banner of the game") + gen_parser.add_argument( + "out", type=str, help="Path where to write the generated save file") + gen_parser.add_argument("-g", "--game-version", choices=[ + "us0", "us2", "eu", "jp"], help="Version to generate the save for", required=True) + gen_parser.add_argument("-m", "--meta", type=argparse.FileType("r"), + help="Metadata to use for the save file") + gen_parser.add_argument("--get-meta", type=argparse.FileType('w'), + help="Extract the medatada of the generated save") + gen_parser.add_argument("-l", "--loader-version", choices=[ + '1', '2'], help="Choose which version of the loader to put in the save file", default='1') + gen_parser.add_argument("-f", "--file-name", type=parseSaveFileName, + help="The player name in the save file (31 character max)", default=None) + # Inject + inj_parser = subparsers.add_parser( + "inject", description="Injects into an existing save file", help="Injects into an existing save file") + inj_parser.add_argument("-i", "--index", action="append", type=parseFileNumber, + help="file number to inject the custom rel into (1 to 3)") + inj_parser.add_argument("-n", "--name", type=parseFileName, + help="overwrite the name of the internal REL file that will be loaded", default="mod.rel") + inj_parser.add_argument("rel", type=argparse.FileType( + 'rb'), help="Path to the REL module to pack") + inj_parser.add_argument("save", type=argparse.FileType( + 'rb'), help="Save file to inject into") + inj_parser.add_argument( + "out", type=str, help="Path where to write the generated save file") + inj_parser.add_argument( + "-g", "--game-version", choices=["us0", "us2", "eu", "jp"], help="Version of the save file") + inj_parser.add_argument("-m", "--meta", type=argparse.FileType("r"), + help="Overwrite the metadata using the provided file") + inj_parser.add_argument("--get-meta", type=argparse.FileType('w'), + help="Extract the medatada of the generated save") + inj_parser.add_argument( + "-b", "--banner", type=argparse.FileType("rb"), help="Overwrite the banner") + inj_parser.add_argument("-l", "--loader-version", choices=[ + '1', '2'], help="Choose which version of the loader to put in the save file", default='1') + inj_parser.add_argument("-f", "--file-name", type=parseSaveFileName, + help="The player name in the save file (31 character max)", default=None) + # Patch + patch_parser = subparsers.add_parser( + "patch", description="Patches a zeldaTp.dat file", help="Patches a zeldaTp.dat file") + patch_parser.add_argument("-i", "--index", action="append", type=parseFileNumber, + help="file number to inject the custom rel into (1 to 3)") + patch_parser.add_argument("-n", "--name", type=parseFileName, + help="overwrite the name of the internal REL file that will be loaded", default="mod.rel") + patch_parser.add_argument("file", type=argparse.FileType( + 'rb'), help="The zeldaTp.dat file to patch") + patch_parser.add_argument( + "out", type=str, help="Where to write the patched file") + patch_parser.add_argument("-g", "--game-version", choices=[ + "us0", "us2", "eu", "jp"], help="Version to generate the save for", required=True) + patch_parser.add_argument("-l", "--loader-version", choices=[ + '1', '2'], help="Choose which version of the loader to put in the save file", default='2') + patch_parser.add_argument("-f", "--file-name", type=parseSaveFileName, + help="The player name in the save file (31 character max)", default=None) + # Unpack + unpack_parser = subparsers.add_parser( + "unpack", description="Unpacks a save into a directory", help="Unpacks a save into a directory") + unpack_parser.add_argument("save", type=argparse.FileType( + 'rb'), help="Path to the save file to unpack") + unpack_parser.add_argument("out_dir", type=directoryPathParser, + help="Path to the directory to unpack the save into") + unpack_parser.add_argument("--get-meta", type=argparse.FileType('w'), + help="Extract the medatada of the generated save") + # Pack + pack_parser = subparsers.add_parser( + "pack", description="Packs a folder into a new save file", help="Packs data files into a new save file") + pack_parser.add_argument("out_path", type=argparse.FileType( + 'wb'), help="Where to write the packed save") + pack_parser.add_argument("header", type=argparse.FileType( + 'rb'), help='Path to "header.bin"') + pack_parser.add_argument("bkheader", type=argparse.FileType( + 'rb'), help='Path to "bkheader.bin"') + pack_parser.add_argument("files", metavar="file", type=argparse.FileType( + 'rb'), nargs="*", help="Any file you want to pack in the save") + pack_parser.add_argument( + "-m", "--meta", type=argparse.FileType("r"), help="Metadata to use for the save file") + pack_parser.add_argument("--get-meta", type=argparse.FileType('w'), + help="Extract the medatada of the generated save") + # Meta + meta_parser = subparsers.add_parser( + "meta", description="Extract metadata from a save file", help="Extract metadata from a save file") + meta_parser.add_argument("save", type=argparse.FileType( + 'rb'), help="Save file to extract the metadata from") + meta_parser.add_argument("out", type=argparse.FileType( + 'w'), help="Path the the file where to write the metadata (JSON format)") + # Banner + banner_parser = subparsers.add_parser( + "banner", description="Extract banner from save file", help="Extract banner from save file") + banner_parser.add_argument("save", type=argparse.FileType( + 'rb'), help="Path to the save file to unpack") + banner_parser.add_argument("out", type=filePathParser, + help="Path to the file to store the banner into") + # Files + files_parser = subparsers.add_parser( + "files", description="Operations on the file inside the save.", help="Operations on the file inside the save.") + files_subparser = files_parser.add_subparsers( + dest="files_cmd", metavar="CMD", help="Available commands are: add, list, rm", required=True) + # Files; Add + files_add_parser = files_subparser.add_parser( + "add", description="Adds file(s) to a save", help="Adds file(s) to a save") + files_add_parser.add_argument( + "save", type=argparse.FileType('rb'), metavar="", help="Path to the save file") + files_add_parser.add_argument("-o", "--output", nargs='?', type=argparse.FileType( + 'wb'), default=None, help="Path to where to store the result (default: overwrites 'save')") + files_add_parser.add_argument( + "-M", "--map", action="extend", nargs='*', type=fileMapParser, help="Map a file name to an other one") + files_add_parser.add_argument("file", type=argparse.FileType( + 'rb'), nargs="+", metavar="", help="Path to the file(s) to add to the save") + # Files; List + files_list_parser = files_subparser.add_parser( + "list", description="Lists the file(s) within a given save", help="Lists the files within a given save") + files_list_parser.add_argument("save", type=argparse.FileType( + 'rb'), metavar="", help="Path to the save file") + # Files; Remove + files_rm_parser = files_subparser.add_parser( + "rm", description="Removes the file(s) within the save", help="Removes the file(s) within the save") + files_rm_parser.add_argument("save", type=argparse.FileType( + 'rb'), metavar="", help="Path to the save file") + files_rm_parser.add_argument("-o", "--output", nargs='?', type=argparse.FileType( + 'wb'), default=None, help="Path to where to store the result (default: overwrites 'save')") + files_rm_parser.add_argument( + "file", type=str, nargs="+", metavar="", help="Name of the file(s) to remove from the save") + # Help + help_parser = subparsers.add_parser( + "help", description="Get a help guide for a command", help="Get a help guide for a command") + help_parser.add_argument("cmd", choices=[ + "generate", "inject", "pack", "unpack", "patch", "meta", "banner", "files", "help"], help="A command you need help with", nargs='?') + + args = parser.parse_args() + + # Phase 1: Extract the simple options and check if it's only looking for a help text. + + if not "command" in args or args.command is None: + parser.print_help() + sys.exit(0) + + parsers = {"generate": gen_parser, "inject": inj_parser, "patch": patch_parser, + "pack": pack_parser, "unpack": unpack_parser, "help": help_parser, "meta": meta_parser, "banner": banner_parser, "files": files_parser} + + if args.command == "help": + if not args.cmd is None: + parsers[args.cmd].print_help() + else: + parser.print_help() + sys.exit(0) + + verbosity = args.verbose if not args.quiet else 0 + + LEVELS = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] + loglevel = LEVELS[min(max(4 - verbosity, 0), 4)] + + numeric_level = getattr(logging, loglevel.upper(), logging.CRITICAL) + logging.basicConfig( + level=numeric_level, format="[%(levelname)s]\t[%(asctime)s]\t%(pathname)s:%(lineno)d %(funcName)s: %(message)s") + + mappings = {} + if args.command == "files" and args.files_cmd == "add": + if args.map is None: + args.map = [] + for m in args.map: + mapping = NameMapping.parse(m) + logging.info(f"parsed mapping '{mapping.old}' to '{mapping.new}'") + mappings[mapping.old] = mapping.new + + # Phase 2: Load files and data/options + + # Phase 2.1: Load and/or generate the Save File (if needed) + save_bin = None + if args.command in ["inject", "unpack", "meta", "banner", "files"]: + # We need to fetch the save_bin from a provided file + with args.save as save: + if args.command == "inject": + prefix_len = len(os.path.commonprefix( + [args.rel.name, save.name])) + logging.info( + f"injecting '{'.../' if prefix_len > 0 else ''}{args.rel.name[prefix_len:]}' into '{'.../' if prefix_len > 0 else ''}{args.save.name[prefix_len:]}'") + save_bin = SaveBin.from_file(save) + if save_bin is None: + logging.error("Could not parse save file") + sys.exit(1) + logging.info("Loaded SaveBin") + logging.debug(f"{save_bin}") + elif args.command == "generate": + # We need to generate a new save_bin + logging.info("Generating new save file") + with args.banner as banner: + save_bin = SaveBin.generate(banner, args.game_version) + generate_zeldaTp(save_bin) + logging.debug(f"{save_bin}") + elif args.command in ["pack", "patch"]: + # We don't need any save file + pass + else: + logging.error(f'Error: command not supported {args.command!a}') + sys.exit(1) + + # Phase 2.2: Make sure we have a valid file index to put the loader into (if needed) + + if args.command in ["inject", "generate", "patch"]: + # We need to make sure we have the index of the file to patch the loader into. Default is file 3 + if args.index is None: + logging.info("No file index provided, defaulting to 3") + args.index = [3] + + # Phase 2.3: Load and process the rel file (if needed) + + rel_bin = None + if args.command in ["inject", "generate"]: + # Load the rel file + with args.rel as rel: + rel_bin = rel.read() + + # Phase 3: Execute the command and save the results + + if args.command in ["inject", "generate"]: + # Inject the rel into the save_bin + args.out = open(args.out, 'wb') + if args.game_version is None: + if not save_bin.header.tid in TIDS.keys(): + logging.error( + "Error: Game version unrecognized, please provide the --game-version option.") + sys.exit(1) + args.game_version = TIDS[save_bin.header.tid] + logging.info( + f'Game ID: {save_bin.header.tid >> 32:08x}-{struct.pack(">I", save_bin.header.tid & 0xffffffff).decode()}') + save_bin.add_file(args.name, rel_bin) + zeldaTp_idx = find_zeldaTp_idx(save_bin) + if not zeldaTp_idx is None: + for file_idx in args.index: + save_bin.files[zeldaTp_idx].data = bytes( + patch_file(save_bin.files[zeldaTp_idx].data, file_idx, args.game_version, args.name, args.loader_version, args.file_name)) + else: + logging.error( + 'Error: no "zeldaTp.dat" file in the save archive') + sys.exit(1) + + if "meta" in args and not args.meta is None: + meta = json.load(args.meta) + args.meta.close() + updateMetaData(save_bin, meta) + + if not args.get_meta is None: + json.dump(save_bin.config(), args.get_meta, + sort_keys=True, indent=4) + args.get_meta.close() + + logging.debug(f"Produced SaveBin: {save_bin}") + + save_bin.to_file(args.out) + elif args.command == "unpack": + path = os.path.realpath(args.out_dir) + if not os.path.exists(path): + os.mkdir(path) + header_file = open(args.out_dir + os.path.sep + "header.bin", 'wb') + header_file.write(save_bin.header.pack()) + header_file.close() + bkheader_file = open(args.out_dir + os.path.sep + "bkheader.bin", 'wb') + bkheader_file.write(save_bin.bkheader.pack()) + bkheader_file.close() + for file in save_bin.files: + file_handle = open(args.out_dir + os.path.sep + file.path, 'wb') + file_handle.write(file.data) + file_handle.close() + + if not args.get_meta is None: + json.dump(save_bin.config(), args.get_meta, + sort_keys=True, indent=4) + args.get_meta.close() + elif args.command == "pack": + header = Header.unpack(args.header.read()) + bkheader = BkHeader.unpack(args.bkheader.read()) + args.header.close() + args.bkheader.close() + save_bin = SaveBin(header, bkheader, []) + save_bin._update_bk_files() + for file in args.files: + save_bin.add_file(os.path.basename(file.name), + file.read(), node_type=NodeType.FILE) + file.close() + + if "meta" in args and not args.meta is None: + meta = json.load(args.meta) + args.meta.close() + updateMetaData(save_bin, meta) + + if not args.get_meta is None: + json.dump(save_bin.config(), args.get_meta, + sort_keys=True, indent=4) + args.get_meta.close() + + logging.debug(f"Generated SaveBin: {save_bin}") + # Recalculate the checksums + zeldaTp_idx = find_zeldaTp_idx(save_bin) + if zeldaTp_idx is not None: + data = save_bin.files[zeldaTp_idx].data + for i in range(1, 4): + data = update_checksum(data, i) + save_bin.files[zeldaTp_idx].data = data + save_bin.to_file(args.out_path) + elif args.command == "patch": + if args.game_version is None: + logging.error( + "Error: Game version required, please provide it through the --game-version option.") + sys.exit(1) + zeldaTp_data = args.file.read() + args.file.close() + if len(zeldaTp_data) != 0x4000: + logging.error( + f"Error: wrong file size (0x{len(zeldaTp_data):08x}; expected 0x4000)") + sys.exit(1) + for file_idx in args.index: + zeldaTp_data = bytes(patch_file( + zeldaTp_data, file_idx, args.game_version, args.name, args.loader_version, args.file_name)) + out = open(args.out, 'wb') + out.write(zeldaTp_data) + out.close() + elif args.command == "banner": + out = open(args.out, 'wb') + out.write(save_bin.header.banner) + out.close() + elif args.command == "meta": + json.dump(save_bin.config(), args.out, sort_keys=True, indent=4) + elif args.command == "files": + if args.files_cmd == "add": + file_path = args.save.name + for file in args.file: + file_name = file.name + logging.info(f"Loading '{file_name}'...") + with file as f: + file_data = f.read() + logging.debug(f"Adding '{file_name}'...") + if file_name in mappings.keys(): + file_name = mappings.get(file_name) + logging.debug(f"(Mapped to '{file_name}')") + save_bin.add_file(file_name, file_data) + if args.output is None: + logging.info( + f"No output file provided, overwritting input save") + logging.debug(f"({file_path})") + with open(file_path, 'wb') as f: + save_bin.to_file(f) + else: + with args.output as out: + save_bin.to_file(out) + elif args.files_cmd == "list": + print("id\ttype\tmode\tattr\tpath") + for i, file in enumerate(save_bin.files): + print( + f"#{i:04d}\t{file.node_type.name[:1].upper() + file.node_type.name.lower()[1:]}\t{file.mode}\t{file.attributes}\t{file.path}") + elif args.files_cmd == "rm": + file_path = args.save.name + for file in args.file: + save_bin.rm_file(file) + if args.output is None: + logging.info( + f"No output file provided, overwritting input save") + logging.debug(f"({file_path})") + with open(file_path, 'wb') as f: + save_bin.to_file(f) + else: + with args.output as out: + save_bin.to_file(out) + # TODO Add a command to edit a file's properties. + else: + raise ValueError( + f"'{args.files_cmd}' is not a recognized 'files' command.") + + +if __name__ == "__main__": + main() diff --git a/modules/boot/include/practice.h b/modules/boot/include/practice.h index d9a2e613..c8583883 100644 --- a/modules/boot/include/practice.h +++ b/modules/boot/include/practice.h @@ -4,9 +4,7 @@ enum { ANY_INDEX, -#ifdef GCN_PLATFORM ANY_BITE_INDEX, -#endif HUNDO_INDEX, AD_INDEX, #ifdef GCN_PLATFORM diff --git a/modules/menus/menu_any_bite_saves/include/any_bite_saves_menu.h b/modules/menus/menu_any_bite_saves/include/any_bite_saves_menu.h index aecd0681..25b8a816 100644 --- a/modules/menus/menu_any_bite_saves/include/any_bite_saves_menu.h +++ b/modules/menus/menu_any_bite_saves/include/any_bite_saves_menu.h @@ -1,5 +1,6 @@ #include "menus/menu.h" +#ifdef GCN_PLATFORM #define ANY_BITE_SPECIALS_AMNT 18 enum AnyBiTEPracticeIndex { @@ -53,6 +54,66 @@ enum AnyBiTEPracticeIndex { // Entry used as a count of entries ANY_BITE_SAVES_COUNT }; +#endif +#ifdef WII_PLATFORM +#define ANY_BITE_SPECIALS_AMNT 13 + +enum AnyBiTEPracticeIndex { + BITE_ORDON_GATE_CLIP_INDEX, + BITE_BACK_IN_TIME_INDEX, + BITE_SEAM_CLIP_INDEX, + BITE_GOATS_INDEX, + BITE_HUGO_INDEX, + BITE_FARON_TWILIGHT_INDEX, + BITE_EMS_INDEX, + BITE_MIST_INDEX, + BITE_KB1_INDEX, + BITE_ELDIN_TWILIGHT_INDEX, + BITE_BOMBHOUSE_SKIP_INDEX, + BITE_EPONA_OOB_INDEX, + BITE_LANAYRU_TWILIGHT_INDEX, + BITE_WATERFALL_SIDEHOP_INDEX, + BITE_BOSS_BUG_INDEX, + BITE_IZA_INDEX, + BITE_PLUMM_OOB_INDEX, + BITE_ENTER_LAKEBED_INDEX, + BITE_LAKEBED_1_INDEX, + BITE_TOAD_INDEX, + BITE_ONEBOMB_INDEX, + BITE_MDH_TOWER_INDEX, + BITE_MDH_BRIDGE_INDEX, + BITE_MESSENGER_SKIP_INDEX, + BITE_SPR_MBBB_INDEX, + BITE_FREEZARD_SKIP_INDEX, + BITE_DARK_HAMMER_INDEX, + BITE_BULBLIN_CAMP_INDEX, + BITE_AG_INDEX, + BITE_POE_1_SKIP_INDEX, + BITE_EARLY_BOSS_KEY_INDEX, + BITE_DSS_INDEX, + BITE_STALLORD_INDEX, + BITE_STALLORD2_INDEX, + BITE_CITS_EARLY_INDEX, + BITE_CITS_1_INDEX, + BITE_AERALFOS_SKIP_INDEX, + BITE_CITS_2_INDEX, + BITE_FAN_TOWER_INDEX, + BITE_ARGOROK_INDEX, + BITE_PALACE_1_INDEX, + BITE_STUPID_ROOM_INDEX, + BITE_PALACE_2_INDEX, + BITE_EARLY_PLATFORM_INDEX, + BITE_ZANT_INDEX, + BITE_HC_INDEX, + BITE_DARKNUT_INDEX, + BITE_HC_TOWER_INDEX, + BITE_BEAST_GANON_INDEX, + BITE_HORSEBACK_GANON_INDEX, + + // Entry used as a count of entries + ANY_BITE_SAVES_COUNT +}; +#endif class AnyBiTESavesMenu : public Menu { public: diff --git a/modules/menus/menu_any_bite_saves/src/any_bite_saves_menu.cpp b/modules/menus/menu_any_bite_saves/src/any_bite_saves_menu.cpp index 50aee8c9..7c7dc5b6 100644 --- a/modules/menus/menu_any_bite_saves/src/any_bite_saves_menu.cpp +++ b/modules/menus/menu_any_bite_saves/src/any_bite_saves_menu.cpp @@ -7,6 +7,7 @@ KEEP_FUNC AnyBiTESavesMenu::AnyBiTESavesMenu(Cursor& cursor) : Menu(cursor), lines{ +#ifdef GCN_PLATFORM {"ordon gate clip", BITE_ORDON_GATE_CLIP_INDEX, "Gate Clip outside Ordon Spring"}, {"back in time", BITE_BACK_IN_TIME_INDEX, "Back in Time off the Ordon Spring bridge"}, {"goats", BITE_GOATS_INDEX, "Goat herding 2"}, @@ -54,6 +55,59 @@ KEEP_FUNC AnyBiTESavesMenu::AnyBiTESavesMenu(Cursor& cursor) {"final tower climb", BITE_HC_TOWER_INDEX, "The tower climb before Ganondorf"}, {"beast ganon", BITE_BEAST_GANON_INDEX, "The Beast Ganon fight"}, {"horseback ganon", BITE_HORSEBACK_GANON_INDEX, "The Horseback Ganondorf fight"}, +#endif +#ifdef WII_PLATFORM + {"ordon gate clip", BITE_ORDON_GATE_CLIP_INDEX, "Gate Clip outside Ordon Spring"}, + {"back in time", BITE_BACK_IN_TIME_INDEX, "Back in Time off the Ordon Spring bridge"}, + {"seam clip", BITE_SEAM_CLIP_INDEX, "Seam Clip in Hyrule Field South"}, + {"goats", BITE_GOATS_INDEX, "Goat herding 2"}, + {"sword and shield skip", BITE_HUGO_INDEX, "Hangin' with Hugo"}, + {"faron twilight", BITE_FARON_TWILIGHT_INDEX, "Faron Twilight tears"}, + {"early master sword", BITE_EMS_INDEX, "Super Jump to Sacred Grove"}, + {"purple mist", BITE_MIST_INDEX, "Purple mist in Faron Woods (post-EMS)"}, + {"king bulblin 1", BITE_KB1_INDEX, "King Bulblin 1 fight"}, + {"eldin twilight", BITE_ELDIN_TWILIGHT_INDEX, "Eldin Twilight tears"}, + {"bombhouse skip", BITE_BOMBHOUSE_SKIP_INDEX, "Bombhouse skip in Kakariko"}, + {"epona oob", BITE_EPONA_OOB_INDEX, "Epona OoB in Eldin Province"}, + {"lanayru twilight", BITE_LANAYRU_TWILIGHT_INDEX, "Lanayru Twilight tears"}, + {"waterfall sidehop", BITE_WATERFALL_SIDEHOP_INDEX, "Waterfall sidehop after Rutela skip"}, + {"boss bug", BITE_BOSS_BUG_INDEX, "Boss Bug in Lake Hylia"}, + {"iza", BITE_IZA_INDEX, "Steal Iza's bomb bag"}, + {"plumm oob", BITE_PLUMM_OOB_INDEX, "Plumm OoB in Zora's Domain"}, + {"enter lakebed", BITE_ENTER_LAKEBED_INDEX, "Enter Lakebed Temple"}, + {"lakebed 1", BITE_LAKEBED_1_INDEX, "The 1st Lakebed Temple segment"}, + {"deku toad", BITE_TOAD_INDEX, "Lakebed Temple miniboss"}, + {"onebomb morpheel", BITE_ONEBOMB_INDEX, "Morpheel fight (no Zora Armor)"}, + {"mdh tower", BITE_MDH_TOWER_INDEX, "MDH tower climb"}, + {"mdh bridge", BITE_MDH_BRIDGE_INDEX, "MDH castle rooftops"}, + {"messenger skip", BITE_MESSENGER_SKIP_INDEX, "LJA to skip the Snowpeak messengers"}, + {"snowpeak mbbb", BITE_SPR_MBBB_INDEX, "Snowpeak Ruins miniboss"}, + {"freezard skip", BITE_FREEZARD_SKIP_INDEX, "Freezard skip in Snowpeak Ruins"}, + {"darkhammer", BITE_DARK_HAMMER_INDEX, "Snowpeak Ruins miniboss"}, + {"bulblin camp", BITE_BULBLIN_CAMP_INDEX, "The camp before Arbiter's Grounds"}, + {"arbiter's grounds", BITE_AG_INDEX, "The Arbiter's Grounds segment"}, + {"poe 1 skip", BITE_POE_1_SKIP_INDEX, "The pillar jump in Arbiter's Grounds"}, + {"early boss key", BITE_EARLY_BOSS_KEY_INDEX, "Early Boss Key in Arbiter's Grounds"}, + {"death sword", BITE_DSS_INDEX, "Arbiter's Grounds miniboss"}, + {"stallord", BITE_STALLORD_INDEX, "Arbiter's Grounds boss"}, + {"stallord 2", BITE_STALLORD2_INDEX, "Stallord 2nd phase"}, + {"city early", BITE_CITS_EARLY_INDEX, "Clip to the cannon early"}, + {"city 1", BITE_CITS_1_INDEX, "The 1st City in the Sky segment"}, + {"aeralfos skip", BITE_AERALFOS_SKIP_INDEX, "City in the Sky miniboss"}, + {"city 2", BITE_CITS_2_INDEX, "The 2nd City in the Sky segment"}, + {"fan tower", BITE_FAN_TOWER_INDEX, "Final fan room in City"}, + {"argorok", BITE_ARGOROK_INDEX, "City in the Sky boss"}, + {"palace sol 1", BITE_PALACE_1_INDEX, "The 1st Sol of Palace of Twilight"}, + {"palace sol 2", BITE_STUPID_ROOM_INDEX, "The 2nd Sol of Palace of Twilight"}, + {"palace 2", BITE_PALACE_2_INDEX, "After light sword"}, + {"early platform", BITE_EARLY_PLATFORM_INDEX, "Early platform in Palace of Twilight"}, + {"zant", BITE_ZANT_INDEX, "Palace of Twilight boss"}, + {"hyrule castle", BITE_HC_INDEX, "The Hyrule Castle segment"}, + {"darknut", BITE_DARKNUT_INDEX, "The Darknut fight in Hyrule Castle"}, + {"final tower climb", BITE_HC_TOWER_INDEX, "The tower climb before Ganondorf"}, + {"beast ganon", BITE_BEAST_GANON_INDEX, "The Beast Ganon fight"}, + {"horseback ganon", BITE_HORSEBACK_GANON_INDEX, "The Horseback Ganondorf fight"}, +#endif } {} AnyBiTESavesMenu::~AnyBiTESavesMenu() {} @@ -68,14 +122,16 @@ void AnyBiTESavesMenu::draw() { special AnySpecials[] = { special(BITE_ORDON_GATE_CLIP_INDEX, nullptr, SaveMngSpecial_OrdonRock), special(BITE_HUGO_INDEX, SaveMngSpecial_Hugo, SaveMngSpecial_SpawnHugo), +#ifdef GCN_PLATFORM special(BITE_KARG_INDEX, SaveMngSpecial_KargOoB, nullptr), special(BITE_LAKEBED_BK_SKIP_INDEX, SaveMngSpecial_LakebedBKSkip, nullptr), - special(BITE_ONEBOMB_INDEX, nullptr, SaveMngSpecial_Morpheel), - special(BITE_STALLORD_INDEX, SaveMngSpecial_Stallord, nullptr), - special(BITE_STALLORD2_INDEX, SaveMngSpecial_Stallord2_init, SaveMngSpecial_Stallord2), special(BITE_FRST_ESCAPE_INDEX, SaveMngSpecial_BossFlags, nullptr), special(BITE_LANAYRU_GATE_CLIP_INDEX, SaveMngSpecial_BossFlags, nullptr), special(BITE_PILLAR_CLIP_INDEX, SaveMngSpecial_BossFlags, nullptr), +#endif + special(BITE_ONEBOMB_INDEX, nullptr, SaveMngSpecial_Morpheel), + special(BITE_STALLORD_INDEX, SaveMngSpecial_Stallord, nullptr), + special(BITE_STALLORD2_INDEX, SaveMngSpecial_Stallord2_init, SaveMngSpecial_Stallord2), special(BITE_LAKEBED_1_INDEX, SaveMngSpecial_BossFlags, nullptr), special(BITE_WATERFALL_SIDEHOP_INDEX, SaveMngSpecial_WaterfallSidehop, nullptr), special(BITE_DARK_HAMMER_INDEX, SaveMngSpecial_BossFlags, SaveMngSpecial_Darkhammer), diff --git a/modules/menus/menu_any_saves/include/any_saves_menu.h b/modules/menus/menu_any_saves/include/any_saves_menu.h index 38a85225..30641fce 100644 --- a/modules/menus/menu_any_saves/include/any_saves_menu.h +++ b/modules/menus/menu_any_saves/include/any_saves_menu.h @@ -127,10 +127,5 @@ class AnySavesMenu : public Menu { virtual void draw(); private: -#ifdef GCN_PLATFORM Line lines[ANY_SAVES_COUNT]; -#endif -#ifdef WII_PLATFORM - Line lines[ANY_SAVES_COUNT]; -#endif }; \ No newline at end of file diff --git a/modules/menus/menu_practice/include/practice_menu.h b/modules/menus/menu_practice/include/practice_menu.h index c66a8f65..0a9c14bb 100644 --- a/modules/menus/menu_practice/include/practice_menu.h +++ b/modules/menus/menu_practice/include/practice_menu.h @@ -6,7 +6,7 @@ #ifdef GCN_PLATFORM #define PRACTICE_MENU_NUM 6 #elif defined WII_PLATFORM -#define PRACTICE_MENU_NUM 4 +#define PRACTICE_MENU_NUM 5 #endif class PracticeMenu : public Menu { diff --git a/modules/menus/menu_practice/src/practice_menu.cpp b/modules/menus/menu_practice/src/practice_menu.cpp index 15f00994..f1cd0992 100644 --- a/modules/menus/menu_practice/src/practice_menu.cpp +++ b/modules/menus/menu_practice/src/practice_menu.cpp @@ -6,9 +6,7 @@ KEEP_FUNC PracticeMenu::PracticeMenu(Cursor& cursor) : Menu(cursor), lines{ {"any%", ANY_INDEX, "Any% practice saves", false}, -#ifdef GCN_PLATFORM {"any% BiTE", ANY_BITE_INDEX, "Any% (BiTE route) practice saves", false}, -#endif {"100%", HUNDO_INDEX, "100% practice saves", false}, {"all dungeons", AD_INDEX, "All Dungeons practice saves", false}, #ifdef GCN_PLATFORM @@ -30,11 +28,9 @@ void PracticeMenu::draw() { case ANY_INDEX: g_menuMgr->push(MN_ANY_SAVES_INDEX); return; -#ifdef GCN_PLATFORM case ANY_BITE_INDEX: g_menuMgr->push(MN_ANY_BITE_SAVES_INDEX); return; -#endif case HUNDO_INDEX: g_menuMgr->push(MN_HUNDO_SAVES_INDEX); return; diff --git a/res/save_files/any.bin b/res/save_files/any.bin index 23f969894154360f934c7676b60ee500dd40558f..bdb33e5566999db554c7f3a9533d8250e2ec2e70 100644 GIT binary patch delta 53 zcmV-50LuUHAMhV60{{W?L{8kzLs)%x#f`5(L{MGpL+;IH#e|Q@L{8t>L!{5X#f_&z L0000Au`I+7q`Mg2 delta 11 Scmew$|3QA@1f7jKNB99E3k4hi diff --git a/res/save_files/any_bite.bin b/res/save_files/any_bite.bin index 976069cf196e81a3a0fcfacf6257be0f7a8e834d..bb255d098fd36ccb425e210fba9ad81ef2a06520 100644 GIT binary patch delta 624 zcmaDL^FT&}nSt?>i{G8I&JnfcM|;;hxCF$$c7As@?PyE?2^YWnH=LK8-*>ckxzj`q z3ketVhYMVorOq5PdHBrv#kDBsOKtm)nen(ytgx7Dz$i2EjEMp+B@952QJS7VS&>m6 zs8)7z4x@*!#h`}|iCKD&hFlSaJf<5qoRY2mHVA*V!1$@58Y`aB{30gdIncTo| z%xvS7$r5Y^?9O5g46eHyCQg)PE-om7Izevo9ySvRr~dD=T(13n?`k<=H<*3Cfx*?V zz;QAMyT!yw(vzdvA)&H{9b)w#2+fk5m|Fn%OCrcyN|O(8B!Hc1!wFH_gGTFcL7fk! z&p>GzZm>9WT4LVh_3VaVGxk8lCiCz>^u$2vH4vJmDlxAFZhuBHlqt^yWI~LPo&1Iu JqSb~^1prWN+1LO8 delta 459 zcmaDL^FT&`nSt?>i{G8I&JnfcM|;;hOuoP&Gf~F`Ox1uW>4~RJK@=-PMrnHf#EAwV z&g5)HupkRVYHsnwiHegSFbaUhxtYM?lcS;ZYAF31N_#Uy`s9cnl; zLw3^Si(E>RH?V*Vm0?NENy(gCzzDHfn-yY0HH2ni$gD~Px!?mUSn*^THXV+`Vhjwf zyBiolPGVswE-0Ef(Gcvs-E0ue-0VD3ULm7yvzuLSOZjO2+E YjX;(&Gh`$K%>c6}zvhMLwdPX=0G0EhCjbBd diff --git a/res/save_files_wii/any.bin b/res/save_files_wii/any.bin index f5af98355480ee893132d281584b7fa2f202a867..495e1d1ddfd000670bc3db49eeee8311189f6aeb 100644 GIT binary patch delta 14 VcmX@0a6n;$KQCkc<^bLgOaLkd1wQ}) delta 14 VcmX@0a6n;$KQCkA<^bLgOaLje1u*~s diff --git a/res/save_files_wii/any/argarok.bin b/res/save_files_wii/any/argorok.bin similarity index 100% rename from res/save_files_wii/any/argarok.bin rename to res/save_files_wii/any/argorok.bin diff --git a/res/save_files_wii/any_bite.bin b/res/save_files_wii/any_bite.bin new file mode 100644 index 0000000000000000000000000000000000000000..daa9c8b98b979fe4949dd875c64e985034f2de6c GIT binary patch literal 4000 zcmb_fO=uKJ6s~a&!l*37zj49Yi?E=m_(MGSpPrq-8h;QKQM9a{uAc5pcUM!@&5$g@ zvKw+yQN)voiTF!I^df=>Mbpz!ccY*p9>m};E(^gG58@?TJtLS*($$%y=P-1=@4c_T ze(%-m>Lf^f%5HhlFYP{9Dj&JJA-iqgztUj8TJAV{FS}*nxpeN)t@4qJO#pxtp$f5J z0XZ1v4c+FZ(Z3|`jX!`<8XG>tL|*_$G`oRuii@^p6!tyci#3Jr{{R& zxcEljA>6_+GQ*XH$AuvUf*ckGezPaRm(J``Hnr(;>RtK#srQ2C1)5tR5p;FIDx$m| zPDwZPg67l}I;Jx=qn;Lk1n6q?Z`@xkwFBTEw0p)Rsv0x4OKk%~Q)l7g5?}-H=RRwd zE_F<6>K!~U;w#F_1aK8fg1J4?n?u`r=Kzpf@#(F!cmI?-XTa&)l3hEb|2BTS`_s_Z z+{%;lxpxX1iXLbFWMax1aT$iJ7_vPg^$&NzHn9*SM1~H5YNg0fZi6Z*8N=PieQh8M zwNY2tF$vC=J-+*6*4SPN+0eEpsv zAY&V@X$JKWIMoATi{}p-sEB2()Dsxu`;7@k?~)LL8nc3nyjci}Lt8*?5^8H03!66y z=+T_N;B0wqOLK1iiaj-whA%UwqH%d8Oov-*u9M(*klXYDqUVMPQCHIVEYvcV^G9+_i}mKyKqB@#}SYJ3vh?(~KR z(!t7Wc=D(B@<3YJc|GklEc)yX4W*?i3tPR0r!5>#*#yI7qU2R6$Ni5HHQHch3KntO zxWhsn6^tAWvJyYHh5FP3Fo)0jxXAd8(y3~!5r7t6-tOX8CBn7xxN^y0YIgn zZT0&NCVAD4tnh>Q1h}>Q1cC%1!TZB<P z_kaT=D6DTP`~W~~*^EnBaS3CvS_bY*2kR<@oGgIKTLG)?o=612*EKv>ofU)2XQ~_P zPI}z?xYO5oqSoWasIA}&jqI;%n0@==opVwFHOAuwk^zSSsv<7%J(S_G{oX5C&2oEq zQWh2<*RU!qi1}3XtH>c+;HB!uiVaijA8Je@YldmZ%=Dj<{?S|RU_2Qg~iS40W_I_T7jhpVW6}*t0O%dro=k$fb7* ztY!!I%X-Y!Xb6slqesbu;t~kS&_36C+N3+zIg;CXPaxnr!kgl$LVEFh4iTix)KsIv zl)fHKg6;s^1jsy~MJsEP?8Gzx;NmY*s;@Ocn~*S>uGRChs+(hd{rTC0Jo8KW zm|E)l-xo||sKBV*AlbpecRw)NvCYLtl$YA@A2;<5Bp((C$tv}~p3dvV+cc$yp98As ze#BZwmDLPZZnjS{FN zlo1gTJ(ctlbO9mgVS{cKA(Dh$H8v5Y%hF9Fbo$S^sJj~`L{QX+zxl3b_M0wQN9-ilss<79t|s61$BN zgUjtCmS=lonqtI=>ZTI0`NoX!Ai2g!I`d_imK=S)JfF{(Ma-#~L4|}_PNb?TVVQrH zv~=to*z)!&no=sZ?bx2w+|pe;cWg?Ej5=78{M&%hUY@yN9VNU@*2Rnxd za?BVXlQwHTut(~dE8f?XeT|PehtJjS7viIg8N#}aZ|72Iu)T3JzwXW5QB%qBG&@b=V@ggCSRB)_I6ej0Wrb&0J z(?b(R?BAKN=ogZ|d;a9sZow6+FLI@D?@Yys-}W5g9Evl2uiii!F-0%s_%D-uvC0xD znWW)rl~#em#@(LsBl0J3mSyT+6V@3^UGmZOifCNt5cO+GG7P?fuV4^-2A{wOkO1$% z8!!O+K_BP^aqtRsgO}hrcnZ3}W6%j6fez3P9)bs;4cu$a*Sw23kwmwMK}&d%sNzK< zIXn%+Yv2@$vwb@S?$p>AUAaM|jcDkkkaJo$~A;I1XySNh@|9b{JGx{#Mu} l(w5Sk?@u(XAzU~aOi_rTuLd3{4I{3AuvvwXDNn>m;yY|{2($nI literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/arealfos.bin b/res/save_files_wii/any_bite/arealfos.bin new file mode 100644 index 0000000000000000000000000000000000000000..cddceb9afcea5791ea0dc74310a5687150b28867 GIT binary patch literal 2700 zcmeHJO-NKx6#nje&zbQDeU4LE!t|^l1)-TN3|jccMw`H)iL|&M+!diwcnrGv|EgeCOUd_wO+9 zKn1IB^!|VZInyNBS6Vh=Vzms640jI|AIf$DM-C9l?1YJxD1x_#zSKAuaqCP?!|9WL zUpZ01hT~yhMHNuK1yfV!_D=R*K76!pZg6?p%JClr6m$U9a~gu9I-a=)r*(UjpN2bm z0eUDw##m@3B!S=niJ0@C(GAP(5m_8S})omyxPe4qU)uiTHcndVX=kIh(jXUPBU-Q7#Z z3s3IHhs8OHhj?A&bj-(aYqNfY@0Ec~#V5~`Vv#kTbZn{X&qifiW8<3>ldaACbX|b=r z<-SE)vcMV6jX=J&)p>MxS=P=?dcsVAi?X5l2 zp~Al0A|6Anr7e{WRnnlyF*?0`UDvE&B*~{iC7knxePPEWqsy#uS3F&g|4aHEh#wa3 zm6nL7C*opsyjzB-4_G4jtX00~s)C=1^J0EsMN1WaSv=3a8*wLTK7}nD^1en+GD>f~ z&-RxWY+F(ZQIVCFVbbGICynXIbvUuEb*MiQ`rGrT6lqM5YqpgbirRW!k?FVH-G)KN zVERq{4t(^M^!*0(Jyv4{Gq~N+@euu|2_N*6e+B KGcWI7t?ef;5wx2C literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/argorok.bin b/res/save_files_wii/any_bite/argorok.bin new file mode 100644 index 0000000000000000000000000000000000000000..8c73f70ffba6207e3258a61b320a7d11214b475c GIT binary patch literal 2700 zcmeHJPe>F|82{ePw7aabj_X#IFpVXoAhc_RhYr5ARktuHGlGINf*>R_Dm@4~yA&1( zdXPF53i?Zw7V`^1cgW+yWij6d;5ED-uq_3 z2P%-d2JJgYAiGVJJw?SsCajd+q36SQ-}PlU0S@lNA`yVGwncwJ_SiQ6H`K(a)TqzPrLq8C9TBb*)MsDOw2KnY|Ck7-TI(lpHt zL{>y{6PT6@T?U8cC7r3r&qFjtAJ(V<0JP7=;n4KlN%E+u7~&K<78h$JyZD=y+|PajFZFU<6b_YB$>SU@ zP#uY>24e|dcuJg~2G`2Gh*ju}!fgOYsfdj>x_%Rq)K!PmLEvMJShGLopT?&(FWR#M}C;~6JJ0Uhw@hB)57z&!Y` zR@njTVS#Mb?WxO~GAoMD|34VM(&=Lb^0dK4Tx)_cSY^mE(=`LX19C8(s=$?o&Ab$b>N+@Zq SSWK{yHF+P~$jkdzYx@C~5VwE; literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/beast_ganon.bin b/res/save_files_wii/any_bite/beast_ganon.bin new file mode 100644 index 0000000000000000000000000000000000000000..f37d9462b5d8b6e88f58a60e603f2b625aecd228 GIT binary patch literal 2700 zcmd^AOGuPa6#o84<1{|zPfaah8monZ;R|}KyyL5pOtgd+K^j63lGy_#B>fdbg-93T zBBDi$Hc?1e5=E(%wUFAh6dW^g5uqTYmG*b;z4JRhigY1F9%jztJLlg2oco`9e#F9n zR#p+;0@dwuSVU=ssShozq?_HhijLH`hlc^VZ6=_|fP#U2xj92$N{7NKZ&+~!<;P1h z(lg1GSAMiO{g4mH+>Yl5pI*uHXJx(aee>Ofh{@OwMMndW|H+HIM+|+@_lB8Vl|jqH zcmmAt%eSl?O6f4q>jGz-Tn<%(;+Dn|t}vce^Es$mpU95qs3S_~U<$TMjdPF3?eTbA zP{MTyMmnva2E#BwW&T?v&;J@Fr4*g5u3DB{e$Dyn)60-WgN}gzP7&v)!AX|MNHd$c z-7i9@4VKcvE~^nxcPz!Yt~{>4#ARaXRJHHh_*ZS=c2k{Y&2UVRGAerKe#Y$k_Db_f zY}DADqzFBhd`t!a4U+*Bj!&LYdQ(ynU}&6~Ii}jqb;{q=Cw6ecaD=w%=@02r(*tmm z=3I$O+dylx*$p@jNZVW$7Oli}0eh&Czy?=&|6M>;ua{AF1y$1>oi%qKSTQc&pyPA) zfXH~{?|IW5^YN(NFrpDlr{CPu6z?U;=`H;`os6qZVKL7JRHMGOb=aTK5tYa>6bUX^ zI1JSbT?^2Q-^Q3%fK4I)pEYj^_hlBRzjF^MV>-N`*3A>~wEvd=h-snE*1w$pnKidy zxh|q{K+ZGy{BZ7%_V4GV1lj$rzc${FPMp>=@R;?^NY2~Ncqse$tvEEa(@9v C?aZ|R literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/before_kb1.bin b/res/save_files_wii/any_bite/before_kb1.bin new file mode 100644 index 0000000000000000000000000000000000000000..5c0883f5810059d869c10e32f7e65a7ecaaa5793 GIT binary patch literal 2707 zcmZSJXW(Jr4N34g%<%s|Gvj|C(AU)Y|DORJEIY%y;^R>TMn(n(XA^|vk-rBRTw2$D za%2C%P#WqM9~@w4U<{Oouq=QqQHEnB3Or6C3=ButR7x+Gg~>26F|r}dL1%#+@ZaOV zE673U0OCw^QLvkt85o*?IvEv!1Pm}i7!EK#n3j{{my?s@Vqj!ufr-QYEC3V%dULP= zmYz@LU}yPc=4GQe&b1&vFA+rq&H-uwp*SE3)(ez?c-R5RY5;{5!vTnILB2+Szw-a< zfjmis2!zGBt-2V*k`8>IN>V8VVBtz7;|Lf7D*gWlDu7f;uz+fF zp$3KqAl1O&Py!@?5|H`fBiw_PkG%yB=Ls*s!TnwTNho*EbaZ#NSb#%?{+p1S#G7QX&av&411CRq8 zJpQ|a9E1*-flkE+fLb^i7@Axe7#J0iOl5chJ7#NvZAmWS+P(L#; z2mnoCMo~Q|IE~#}2}?jO@yX1~MsbmAL4IB$iU^zoj4lw01Cl@*3?LqM0J0mJKmrHA zN`T1#8T^(1Uk~I`#i^=kH0bho!40Y|Nm!@7z7BP2hj}-!sj`Y z8+M8?BrZ|pVF20b01}4*2L>i~MivDgs4x>uoLHK1p@0HIg920^Gpgf(hHx+lK;s=` z;)VktauhHyFfsLn0K>s8`$PYPf@SOe3v)RhVtZ|W5F9plMnh->gaE7@fE97DyayA9 z1pz;V;Q=ZKKmaDs1>wT^P%fN*4k`dMA4a3AgYj|D5B$Iq9&-B`fThF$0+_#PAYKHQZGz@il-BEi7O c0hWOij30m!5Z!RG5y4=*a$rRGlg6F|0D@eH#{d8T literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/boss_bug.bin b/res/save_files_wii/any_bite/boss_bug.bin new file mode 100644 index 0000000000000000000000000000000000000000..8e201da28105ace906f06280eb279c2cdf30f5f5 GIT binary patch literal 2707 zcmZSJXW(Jb3rTR6XZZi08HoS?*Vokf|DOR27$FphTy`xY!PSn9kr^QbVjVxL`Sj?) zYmeRTOK{&Ub&C%UFf=k`VBiI_3{8P5L>XMew!S+i#PH_W?XGJkb0IQD42&#{a;Rn@ z^FR)8@c8cvau7OTW?)1Y0`q}dfUau-QjCg7X7NGT4oE@_403Y(a&mH942;YyFmb4# z85jfv_!-z?qJxEAb}|1|+S^Sa=lEpiWurLDwIDw)5k&;f0mdK*#Q{km4F(VoI{?`Y zps->%09L{v0AjSe9dgx&bB*a}qy<;Q=ZKKmaDs1>wT^P<3$rKd3y+d>9QC29?oZ zzylM&PTO~a#T^&~891=3ppz)f-?Xxh*5;$Sy9LPoAG)G}L7qW^g&AnKPy<5)kZNFb zC~;_DP+(wy6c9iXmN38uz+I^TELuS#5-f~p@uK(vta9WLj8_hfJWtcY$GuDb|4#=1 DL8p;B literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/bulblin_camp.bin b/res/save_files_wii/any_bite/bulblin_camp.bin new file mode 100644 index 0000000000000000000000000000000000000000..1e63d35fdd967287bded53bba1a71cb52d7b7f34 GIT binary patch literal 2707 zcmeH}TSyd97{|Y}Gj5uu+p#bko>ElFG#{D&d~~XJ*bWyGjNT1og4M`L1*3J9GA&UGh;j zeOp&+c2S04ZUI@ipil_1gxN}7FTFp~&5X{BX++C2Pw!HR`#lTZ-MjrHQrR~<^VQzU zEo)bWibF&*EGtw>#B!;^y>i8!dC?1%Ma@q)4%sy&L}{!=jWSWo2Fxl^A;dBv%4J(( zk9EbxouLuSQC>n*n3fV9nt_KdvPQJq&$dT5nLjP@9KS!u@Apq9rs;N2C!uER@puT+ z{JZ4R!^Xk7kJr(ZUcIAXXHs(&yBiy}CPm^7<|IFxaaGf^Cv2jGACh%ptAsq#j>~wy zI7sB06Aw5ByScwfnt7{->X(j1V%qrm)_p>Jj@Pk1>$`I$G}7C#jo`SSNV`P zRZYu4KUGBrYS`f=7pdQ*gTqa0G?n~C&91Rda*>HixhA_MojmSD{AxI1+ZJ9z4y^Rl zxb1tW?G&9Qaxb=hKCz6&Z#5?-|K^Cj`~edS?xLuRaZb!@H_WH*J3w-e5zQlym*lE? zZ}3@$^VNDb_5G?MlRPeibgdt&$Er-jinprlfBgsFfU{Vnww&5ODy|+a_Xng3=YSf+~1%*m_ql~zZp@(WYy99OBb4$CrWX7Vo8j#HMZv2=cNJ}EbuQ#)DkCowhe zpOpMo>Q9fK+}vHbV*O>V=a=N^8lLSv%sCWiX0zE&1~J_%=J=IKK3HXmlyow1v`TBX zo;U*S+CbF)C6U>GO4#FY>U@uG(nZHcmuRaY$vF59#=r>p3ci405CxyW2QUcUg8}dk zM8F%+2VR4h;5m2(o`A>T5qJoCK@aE#UEsmVT*Ft!i6o{&7+S&$#5!IO%i_VpbFkm? z;CzDzbsrB>5ib}ixBZL~=yF~#Ix00mTBp4@08W5JKujj|Cu@S&p9=W+dE zZr1zj`I&Mq+BM6kbu0+GLSqNnox&mrYH(pZs~k6lp;B`3m_q?I zC7!B35a%Y7gJ4Id%+;H+3hixh1t7&dN1)GhbFgJS0BZr9patex7q7x*>)MaiRn?FC zyBlxziJbJZp_DJ)L8Icgf3BNpP)1&BKy^SaUj4Ry-*OkuG+yApThWO6Qga>++SHcj z_L1rp{;WLGL5+b1WPoaOfh+~_?qzG5%TW5__W$g!U4hum0x7nF_2^l8Yhijma8zR- zFotDMpCiZoX8X&#nBB^6%~ngbXAac(wB}WX84ltou!Q=Bqguc)_a3 zbhGMA3`*q35`_iuKjm9Mir>2Y?fglL>%%CD^98!BHXoKHT-$L`*KyLBc2?^EH@=s( z4LWG+jK&J!GQ-gYTk0wVmjcwyVTH`%-4nfZ$RAgM$@5yVnc!bW_9ifQdlBB@jtH&; z>*W{G$a}k4gm;q8N4iU-E7*29z<)|YaJ=k-b6sam%xaK ziyzWqh=(#=i4e}z1s}DKC&EV($C4rFN8NjZ4+J5CFu^FnM}jeefZ`W?Q2E@*>Gtv= z<-46fQ9c9DCUICPw)3Y-xREjLdcuM$5VO+ZXF#Wl{pbnq`GY3fAA#?!_iH?vJEBK zSxYgMU$(9|YX#txQ{mC0ZHxLEDpnPgzHa;4&ug-m11NAD!24Q*{M!`ny4w1w7?q8~ z6Bq$n=<%9n9#Se`ybdv7;;>0FkW}HsAfb$v{{TqjE@#I$Dv-i$pafEd#nA0eb-UdT zAhIHYlfaBvpsFh1GAEdjys6IKQY1x+Dy#O#k@Hp`tlAwH`QhLeu-{HZwi`^uGAUZR zpZP&vg2y0MM&_C)0Hm3*F=mrHwTGFWy?&_5^JVa(JaWl6W}0CZ<3e8YvvWQB)4=I+ zy)!N4Rm;3&B@J^_6aXB58-lfiZ?}+J8JQ5Ga3UNom8|4%YSKKp1A4SPToewKVG#Hzh;IMJ+QDGpDM?Okp+r~Q0<@_5HQtZF`}E=a@O_un+8Yp9t##wTE>%rp|FbahK= zZB1Q&;f%(V*(?S{962O`Drum|A$)t2Db;w;h~~$zGFUFBIIGy!M~DI%bFHh(?EfREAXbfjArVjtjQ{^jxqQ@v=!KUjDIU*I3D496LE)<9Lr_8^`+`tG9ltPPFTf7rGMd0mbjpSd@#N z_oB&g?xX1N^Wv-QTHIHj7=C!Remd$QUwPuNcV|rw(}r_z(;2`oEEL08S&vV{>hJ(S zcwn`NRkRw;iao}h?Vt4xElv1bM*DrFK7jQX@c+`8k2-*g&8GO_x}jZLBxM^#EHanpVYvVz%1W3R*l76x2ZwRIC-R2SGPQse+J# z$D4va_~MCpBbe5DJQRKLQ81d+f<9O*cr|tW|Cwx))bl|>#GmAw@4qJh%+CB-C;X;-=PfSQJ~hv2k-=akWib8+o2(K*0^6&l*G=rKn{($lYc# z#MMSw#0R8*oY)7TjUF&$2)njtJJGRA-ZWN!Oup0mPeykO(K`^wp2@RwzT*AkL+jm} zB}LCqk1sSz(C>ILd%F{`Albc=T`4Pvgai9}dzWgS^A|^Us`v)9+jDVIn5yR&k8_w{ z`La`8if6fEMnawjbFf|M7PK_OaR48kU=&wkE(F<#qW}}}Ab}Ilb^PUA(+)4k66-ei z#9EuqToA+j)$P95kv3WO)1McQcdVMCCV&-$65RdfOVN=wjwBE9+iE&QqXolmw`ta% zT`isUqhdv4$p#B0N*D^3W`m(8@aYvhjxCMRtpEGWE+b!ML%6|rpD_b$zz$X;UnlAEz>RQu@6Y`{Qg0@*Hu0Lvy9)H-y6M_>gnZ~ICVBMN_OaK zki0&02MV)Ryz4Qg_q|bLX#~8AWLTHL%l{);_ao;weBQdg;rcD6KRth5k!}a7Vt1)) zSi4pmI{tQWzw6@QGG~W#5W@Js>{PnoOp=Wiz*gxPf-Q9nf^7nIa~Nf?>^@_Z3*&q6 zRl@kH6{`vUWn@p{gO``)2g7N>U0}!fesuCq2nccERz* zC!*9!Qt>q^!po|*w?5A8fzXy-zz&=tHZ(31-R6Q{?^+szLFe%E{jyqMGub4q~UGgD1GNb0*}RR@2cta`*h Z5c^2%6R|#GX=44vy+l`n-Zm&qLPQd?tHvgxbXhD>3HS7$b6MSG6DfkCKK3{BUH&sWbLN~OE5*_J zvMRZkB8kN-unlSHibBEGJ<*u4Y5()caH70>3&ozkw6VtzhdhNNNzreP7gp{pab>!Q zV)HAvZFjlyh&CotPiM`|p3->QTDIqOlTX#SvWSII#lk$no>+ifq!fq+Dfw!9RBh2j zn)Q_$3B4ybF`|}OB8DaSEl%@fW()J@$YzVT*=$k7%#xOrNWoy$EEWr4n}3#d57bW{ z{CEe8l8S5U4hJl*;7EO4bwI`+U{B&#f!5fbwqY3(-Xe5vRYDfw`)xi&X(BSM_9t|Q z-9OPF+~ggfYjfv3KJ)yQ=0-*N;@7be_1(GQn(6O2z#mxSs!jGaC05jBLvw0;l!`p# z<*aqfH5!$vX{w>rY!sK-v)k+vnMlT_OreHEkY&+n-*N|Q@DSpWiwMqZv4z^s(?z1N z&05Su%l^u&cKi+*2W}#-iE&OWVcFYs5akN-TCyaNIMsx^e3e~)wT886meWR)1?MK2 z?PytqY*)wV(=2EFf6Y%n0plqfpPcdi7l-wko;2Vch4WVxJL+;UDvsk zl9eo(@!tup#L;mkACue3PE3-;8-Z)WcrnBTLZX=+SF7-nB(~`G6k3N@Q~Yx+8*~Tn z`OH2^>DX&h+R8~V559ppFaxH+S1<*<;1l=&Ccy+42V=kk-hmPD7Q6;8!7vyCgWwq$ z0R5m3JO#brNmruXn!||%vW^?QfTt>DJk=M&o#~h1zvs?%cJ7p{+({WcbtYenBeT%w z^VHc{(G#pW{MAWt4%C7RdhROxY*4Jnhu~)jUszY7!`r!=$V@AkBoV{9+PO0`8+i$2 MYbwzyFBISOcfbk+vj6}9 literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/darknut.bin b/res/save_files_wii/any_bite/darknut.bin new file mode 100644 index 0000000000000000000000000000000000000000..65f81ba7693bf5399d4b047c509557910a26dbe3 GIT binary patch literal 2707 zcmd^BYitZr6h3!n)a>eZ$0|zMj>nG@No~C%k+@y$T8T=%B{r(5kb2f5M4ISqNt8%z z2oZuMD)o$zc=pFq$`8>5Km4I9el!tDH(AjN?Kt<|nXYY#`XLcccFuQR_slu>H47fd z0cowL16%($#;DB)z>`6I~9lwyR0m|krhA#1x1G8!;)sR zx;8V$$dP7~fVhP69VbyB05!z`5_W@x8px2rWuU~vbdx+Dm&fCA02v?)OS)p15~!*Q z*ydkG#L*?a--$(~&8^tD4!k(a78O-)+E`{OsfaDJ-wNbb_o7XTae564j*5v=ncPjC zBaLcYykXr2Qy@31wlXn$e%tzj&+odV&QqZshLPxwBQ1RIT=BLy??4vh1h|b3VUnxqd3*mgs%4rbCXP*urbU|AN2D6%?7djWfWALxwwt!IuN%gSpRk1qwx5% zsL`OhAroiceM29rAu@V}ixE~R6GQz6HQcwURonMI4P+h6$YwSOa?aTRP;DB>QVds* zP^xibDCz&Xq>W>~Nxq5CTyMx*04*7c`TIrFW(knay0LFfjF$N(X_j*OxYdkdx|vWC*+K)}RiSA@{p`fM;CEdEc4Uci%_+UgV$7 zpIXgBRE@>;x}38uTbA(M?mfEBN+5I_t%ZF2V$%E=`)(?HV`%6?!Vo++He|UD2pfM{a-S;LEa;~MQ@crViXc*sAZcj8Gt{sZl<}bI$i&kvUXBcv>i5!9{ zd|b#`QH#%k83{a2iRKy17;>(z$7fY&#PSY(;n@g2=TQD2d=+7gKZftiWjbOQf#uUg w4I3J;0#QS0Nuntjdr|0)EJ1Ac2V>8Rq%$`erq-}F9cx#ARRfSAxkgvZH#%h*Qvd(} literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/death_sword.bin b/res/save_files_wii/any_bite/death_sword.bin new file mode 100644 index 0000000000000000000000000000000000000000..5e378b9885e3810d09a9062538dbcce113a4ad7e GIT binary patch literal 2707 zcmeH|c}Nsd9LIleb~KODXWBmSi+)PBIqE9qUb=YKa6^cU{cUQ zL`3vQRP;~K0YcCpD|AOmBoSR2CekfdsEEG4Z)V=wu2De*75&)H`# z4;6-pMub<+ofj%7C7O~&;b&Klh4VRGkJL4AvTH)cG%87h4`y!5XS6q?xne!^V={wgFH7iRv9fe2mqx7W>^f7wT@RU&Al^GFEHmxcNTh zEmgCtqm?3U9p$Wc`e|ylXm?k|LXS%Rq-Gb`QPRl5q_l7SC54RS!T9;;pzT;#giKf| ziE+m_sO}IQAxfU+_cx;9$Py_jWa4U-R<_Q*x;%L7D5XgIyqPDF?Fxo%@y&E}SH$>%=gwJv>-gjJOVp Nohq~}Eg%Nod;uAL4MhL| literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/deku_toad.bin b/res/save_files_wii/any_bite/deku_toad.bin new file mode 100644 index 0000000000000000000000000000000000000000..6dd6dc33bf6fe03be272548591d12b38d6408e5a GIT binary patch literal 2707 zcmeH|TSyd97{|XeJE^I;o2+CP)?H{91S!`F^vTKlgN2e6m1q(~k*HwdLy<=jN(xMf z=plM4>m}#{LJz_AP3??Sth-jQFM?4Y`Ql~iARoIc&WX;L(Q7ZFaD`g#dS7}tE z*2(4qi%iC-*)4XF6w>5S3vWvxTikr?rf=Sk#ll`FbODvwMm3M+roW`dIDVIg16L@Z zFwTjk#ylqWTNA|!*%T6|ntF$?a{lQQMCE^3VmlxYOFnXn*`JKdcCCg{Cf)-=WIoYw z!@gM;_Jd|#t}nG2Cg~^3!0&Z_XDF`GN=k^VuyjTHk-0^&<=l(1I2K*(^`ralfUEDk z&M8A(qgwFYsU5_jIMa@5oSejr7X+^IhY?yJ+@%w*RuM^8S;V=BT2~-c`R`rWkZ_2p$NcSUm80sMu*K)S{pj z5fSl8#TUT?M8Ss$-Xf(~O7Uu?O0ifC9#oNb{Lk)A+N7d_2r7Q$H{Y4bH#55v3Q!gc zELk&kE~V!)pMXrBRC-a6Bg|EDJoD;G)&30Kmq9eU{$7nr+34qCLNO%^R zrb$@lpCxBnxAd;>yNITYc^fxxN@}ih>z2)HlOhQRTOdEHFv{JK;x5yM#p|UXb4tjR zc0vY5x>-g0`-dfEX zzaW6Tt!no6bWyasXE|$_eww;$s(rV8k*<Kno_!tYPAf*FvcRzUA=mXa^vu0OgL)4_aEGIu~$R<;>$k4lRdMs*NPO5HL z!GH62?SOY!q+U7o{;0V6UHN`MqPPbfQF(Qi%J=FL_sgJHujW@;U8%}1_3E-couXzm zIRTqSCBLQmLCQgfy?^U{p(r_@GR;VL&B)MqBWj)BE&7Gz@18%owHt87(zDzs+EHle z_-%g!=TMy4JM4XA5i{&^?)*!r2SZLEOQd9yg{xIsV+}U!_LQ~7aU4s^|A?&o=Y_S! zQrEbDr6HPDXrggBNd~|d@EP=jkKhA%2jbujcnx~NE6@X8f^P5}bb@E#33voLKs#sy z4?!zv0S~}^a2MQZF0cYKIFZEmh(SwuiCDr*26K3*^aSh|JT%ebAtS&;RK`pCBd+h8 z0v+KcpFT-Vkh+Y=d%z)34UTxRQ?M(*JkQ?%yG+_rnhT2K2bK{ooHS+_#L(A19xAUu Q+yE7B6-JhsBL@4v0_J=R8vpGBx4bis!LrG+k(O#eBvv)gK74+Rqa?D_xiznnAQIdkS; zNClB(ZLP3>2QXMoD$1(NtUD@9mgYN`jurQ>jW+{0ubh1iL@Vf@2!QKk$48HO9FA=B zloxNx$zF}AEr@cn*8`a6fvY&w>Aclk=YD#yYtJIB1~W#e2XMcYz;%y8m#uw>f~$OE zQFabaH!}jXz~NR^r>eRbE!E34DwDx#1d^<~5et;D^6du$S;yHijshfc43xk^4KZ!E zC)@4zI3Pwr3(f&Gj6f7cz-`VlA**^MPHsm?$YYe)z}G9t_5xaNz{;DZYQChZ=9Jf+M=<~IYZz>5SlFeN*C0|vvH zt;o)^Z@kIxy>?rj8|QdzdY|9N8NZyFH=*EQRm(xNK`NfUuTxndp=C0N!*8JsD(1Pp zeFm|vwqd9sttn|Si$NjRT*ZTEkU%g7@$IEhD)FEa&HqP#?F7Ov3;VAo`lqMI^#`-> z1Em1_02e(UH^~pzMf%G~IIpj-TfL>~epa|nyBlFAYTgxbTws3$3DGEi>rI;fZA`Nn z`JglUxomn|F@5Tn8GYZsO#Xr3-(Ek4qTG+JdNe~8#M% z&6R literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/earlypf.bin b/res/save_files_wii/any_bite/earlypf.bin new file mode 100644 index 0000000000000000000000000000000000000000..fccfcc7e9490974b5e23454b99be4e005da1bb92 GIT binary patch literal 2707 zcmd^BYe*DP6h3!n(%IG3X)`pmb*!KVL9H1TL|v_24KwT=B$^n7J?()8LC#8$QQ%S( zK~QUCSP?{l_VCBr`$s5LP(PwJ`XND}u+iJ5bMM`8+^tB0DC)3t&UYU7&N=5kX2A_9 zK$flXv;){Ahk-O@!Q@K@#M7Cw;u+yrZT7@i$K9K6g13*iRQgVqm61?bTRdmpQ-DJ60yK!2Jb+=aCw8vE8cTo{<$DdI08=Vtd2M5ikt4w- z0ZA3gAWlL+02)dFB&-GrHIT7BPL#N1ff#qYQ{8U214vTTC@Y5PR#g?^%CrXlIrMmZ z@bdwDeZs=(EgQjuT{g*Evvo_Qsl-Zb0sECBXWd{V%A|<&2BwEZqf~|msDHFkk2_-2 zfFTM<+wL6g!77peb*zs@is199Xz-#ZsP@4EL_=~vTIUYydhO4HC!&6qts zVJLdpvy$wc)dFD(^^wRD$#f1;l4f!Y3N(sE*}gYK0X4z0Ukq7TAUO<9L`u9}=ql`M zfTP&z8A8{;33HJ&Y}i<4r#HUw{>=ul{FM|`>|CFX=sFOWNLc@Q-Z=pIENTp>E=a@f zyDsbL8cIe_a4`Z3Wze5EqNCHM?ylW;zbKN-n- z(4Djqa`F2>bHnT_q415Np$iE^@UFBW(;N^04Io1%*6pdW*qlC!n;`Z(!p8pdL>`6W zYvg}GlvZX|u5f;q^Q)Yn7LDue&@PY zeAfE2R=?Mmo(ke~8u4r4s}5lNF?`b(<8Joj~WXxUtSq?y)UB;K5uL-LWo&W#< literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/eldin_twilight.bin b/res/save_files_wii/any_bite/eldin_twilight.bin new file mode 100644 index 0000000000000000000000000000000000000000..642edb3fbec21746b4305d5b633d1b30ad6e6b40 GIT binary patch literal 2707 zcmZSJXW(Jr3rTR=&G7#}GZ6p(udk`||33p5FhVF0x$KO;py%m@j7UNt;Uh{7Ad10t zxdD)Liw_PkG_XWsu`-A<9G4b5>eAW3aO}>OYu_$F)L1aEF){LibYce}2RL~AcLg~J z9WXO6q6>lfKrKMmHGwEaBvaWSYzHJE1_n7femOZgE(S(s7MM8H&kPI#KoMq`=-{CZ zyv@J<06E7eGcOy(S*`{7d5I_@a1Jm)KqwAK0%2`i zzaGevgz;fCg9!57n7>YoOg+TxmqSOV98}XaR z!5{#QcaXXb2k@&NNwSdWPjmoAI1|GMCdP&a21X%B05puGa2TEDHuEHc^ISKU&W^_i{ZrR;a7^s23)~&BVwCHUcXFa=?F& z|E?ehp#z9B(M7>-W`-EXr~skCBol<~fF#7gAScH!Cnv|nz`)1?6NmblfdS+!bbrDW z4Ir&F_Za^rko`WHdD$rLa4pEsOGFWYbAbK;p*SE3^Ag0v4nQRhO(1m#Aifm;De?I=k{C8HP!vFuD(bbLeM?+vV1O{aYz{&w=S;+t` zAK~Q#%+dT1h6kt|00Edj2yc|6RtV5HTtTJ(e@6w7<0ZgxD%8Nx0CYkFgF^vG5EnqW zl7j(URR}OJa-b?^Y(V9Y7#`!51L%I;4iey40Va2V$(3MoCzxCXCU=3!)nIZrm@J6+ zUckxGa6(0hlVbu)lcNGi6bu@i?k#BK&2j*8z%k*Lxku|*GUw;RlRLzL^n15t&JV6} zWkw*nkvDz8BnDHkzDC~64j}Ha`Fx&%;q!SLkYFQk+6o{}X_Sfn(Ga|70T3?%vfn`D zGA97l&j8w|0pbJc2|x;Do&v-TxqE;vfVd$(;PbJV2bW!h7K8!m^KLN@F1a!<+ySRCf3EXOBO8FcK*p%}9Q8sO1}(HH)`E4|T2Erv1e&|a#&l|% zYTo6Noy*khF1v!4OP7mUIxLYqYZqd7!VC6tEbN6smywxkW%K0>S|vHw@%wcgxI$5d zaZW7l-&^J?IgLM1ro1xp$|-gYGA>JUTSmF~4hWI`NtWyQcAn$M?0z}F)OMJppCSXl z*M(hXT&E3`7?&aIhzqWuv(nF7>G|WgrW3BQ?-r*lb(8ACv(q|>Lvf~`)H(TxSsw_r z^2IPGaH{m-(<&kvDqD0eBG(@^GylJa4TqO5`uri)=sKtvXAh8I9()ILUO7h5eofxA=Ha_3|L)^1w{7<*(ZgUCaZsvqBT3J>k`9a1qplOLpu!?0isR q`^RA0|D8nL>Hg@2{e+9ZhN&ts)OC&r^YRf_LB1Gc$y})s`|%TY5dD7u literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/epona_oob_to_flight_by_fowl.bin b/res/save_files_wii/any_bite/epona_oob_to_flight_by_fowl.bin new file mode 100644 index 0000000000000000000000000000000000000000..7344b995198734ef9c297351873d14d186517899 GIT binary patch literal 2707 zcmZSJXW(H_4@q#AXZZi08HoS?*Vokf|DORJEW6mFy8B=&BQpbovrYLDn6z8Nyi8ZW zDJ$LCzaP~5?-m~%U}$6rl!UPu8AKV5InI-DX;RlX`u*I=gQvhU29^vA%#3_6Q!r_e z0~|d5yMi2q4wxAj(S^W#pcZ}xh9)4zsEA}D8-(qEB*efVC&w=*C&$IW$jky0hx=JT z0O&ZF=wP9jUCe)#_I4AuNa2XZBmguqP3ZPmpfhIHWj9LJxJ|1tla*I54l|7W;5#{1xK>^1y; zzB9RDrwCgjlY&fu2vFVuu7(N>#*O$5;b0Je#yd#eh6DIjk0e=0^eZ|5BbFkb#3zqbSl3^EX9?;L%KL^HHq^W&i)7D?nAH1Pde3ZlMN-1|ZeI=upDYz@Wgu w04X4VBrIWo4S>5+0a&zxL?jp((c*=%0jzT55sX(3j66@%!pBWN|Nk!n0F(rVJG$_VeD}o1G#Udy)T15|qb{hgJ zDAbFm;id4CHL zSOL)8yYm)6VA3LNZfc&ipc*H~9uDL`d|qA$uxpF!eAH+PrF}e(W2)ysXCl4>S+Zwu zCcX_xXf;et9e(!Y%84%>UB%t(s)$K!1<+tfOoikaht#oAFy*WSS*ZY?Zf&=$HY}-w z>N-P$5b!A~u#i+LBqm@0xjq1PS7b&FzY-K2IEyWljASepip639V5%mK8dx?1UDpBI zTx>{nKYmVqDd}u~_SDge^7dp0vi;chJYbC0gdM4H$u>D!%p92u=*K}PUdG4zxnca& zy0XD0?9Sj>qS5`c$6LP_zp8ULMlM)ZsT{|q#o4)?`1bO0(YzN4zRMXKIwQCd+=&^Q zc?;>{%psO<+5`m-!=+M};y8bEWE1*!Xfrrgj#E`{@tDI8y3b8DQE<6rxCkL+{%X6_ zb-0*=>i|AF!3a~xN)cue{N_e7@j|6z(Q~=|4IDRIflmrE@z#W8)#k5@H@G+~&%m}@Yo;Q5b%@`2cG* c=DwZ=#TutkXf;nU1ut!|*M+6s;J@kaC*K*OzyJUM literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/faron_twilight.bin b/res/save_files_wii/any_bite/faron_twilight.bin new file mode 100644 index 0000000000000000000000000000000000000000..8e1923d98215a9478e74dee0f802446093b16460 GIT binary patch literal 2700 zcmZSJXW(IA3`q!bU|{?Y0s0y`|Nk?90V9M0ky$fuyD+3UA&G$Hj{H5q;L^JGlNq@f8Ud;lm9qy#{0ICv}nw;srmgbP3!u=wW? zIKaU8^N|zN-^NA;h7HW<>PGpaAut*OgE9nQSs0dQp=Bnx5I}Y?KZN5^jVwq3N1&~iAzy-e$upWRop#UU^3m{y{0jVkk7#KNlDIQ_sj8_hz`*=G@ zfMW%i+yN$6g2|m=aut}|1t!5&I>&A>yCC9w0VhYp2^AqujtMMHjtU@AFley3Q_*Pi zB>~6*$Anwv9<5`^oSzR*?hpsk@7$9YFhJKztyb0i+fH%~OE5;rkt+ q3m|TY5BPj6=D}qbp#@<;`n+4rgG;W=3%7x2R5yIl0lM9QkQ)GT(aQAz literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/freezard_skip.bin b/res/save_files_wii/any_bite/freezard_skip.bin new file mode 100644 index 0000000000000000000000000000000000000000..759aaa82652559024f729816cad512ae5d76451b GIT binary patch literal 2707 zcmeH{TS!zv7{|ZaJ*n$$w`C>6Sa%~ABq>*f?8#%S=7WWqWf6EGh$PX%qK6_!F;o)D zi0C1DO6euK67;rFVG<&eh;-A~M3gRKiAuPq|D22NE`~lN6!o#c`7ZN6b7pqt3^~X~ zTS_bC9wfPi;@AEgFki zpQ%yMJ^6_dH6;)+@n*vkwLoUhD5FH=bXuKGXDl(Zq!lGn@UUt&n~ku{Kg+uM>n4tT zzK=;sMb)*(!X}r0ysow?tg?_`P2yLM)>xjlU>OqLBy@gNf@2VB^QlS$k!gKsO~mZc znR?+<_I$0$n+pWZ^EVn#E6O)fQ;iL)XXmzermyWVf9y!CGPyPsJJ2U<0-|$cBUBg| zD`U;uJ849whN=1zv(dRi&+f5nWFi@hGA;E;4B0|<`>sDygFnHFT1;eJ%Wc$hnXVE= zZPRkTwCt~}YTNIYvEe3~G%?PJ*;l+wH&LDtZzP+Y#ID*O@O3WzXbow@tfY-28}>~y zyC7yUvT)ilRD=J`D*gldW|8s98Q+glH(o8i4_G1Y0o9ED*oa)$E!;0bz26w$@Wz#D zud;r7XJ$MUVgCQ^o2EZ%O5Bfka zcnNyIi;g6hBbO5iWF0@efTt;?JS`Z{JsH;#f8?G`F7A;W+(Vf>ZN{hNmTY(*Pn(?; zo?wm9Z_a{CpaxvgYc~<+fFeCVia1lm!a9=NlkNKn7tR_cNyIR(R_@8lL0tklnhGtW I%}Q|kCt+6uSpWb4 literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/goats.bin b/res/save_files_wii/any_bite/goats.bin new file mode 100644 index 0000000000000000000000000000000000000000..4c501d099f9da782df985660aa946b2454fe529d GIT binary patch literal 2700 zcmZSJXW(IA2uTPEW%&R9KO+$T*VoYb|DORJOqumBeOtCZNZ45wA$d%+&eAO_f#KNl zMtRXtxA@=y0|SsOf+fmuGbC}MJQ{Bm+~Tnr41EHH5-{V<_HMGM?!HJ%P~kxyn`Hj2w!3-a?4QAFSz zU`T*a9FW8kC`@p5#4_N92mnp}`N)asZv#;424-UQkE$CDfzc2c&=7!UVd7ljQAeD* zQB`Dy0EzKIrY$Ji;8sEza6XDEoE%`;&%hvnQK67%3QpsPoCM>Q1L$u4lh|SK=YL`Y z1LM@h7NBI})Cr7#5<5X+jUcgBkeG(e97Y8U>ykVBQAzRiH~^{kclTe4gV9L@_#@ko`JZ}QHclrK(U7);t_~=3?iO@ zh^HXp8HjieA`Wo$|4%%~@v0yZ6!(dTIDYUa9_Dz(kSGY^9^ugAPdv);q##iU#QpZz o;z;86#}?-je}Gs&L9AaO)^8B&4~X>_!~*+652ORstW5j|05D1O#sB~S literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/hc.bin b/res/save_files_wii/any_bite/hc.bin new file mode 100644 index 0000000000000000000000000000000000000000..17a6e3f64f657feb391466405205c8ba479ff861 GIT binary patch literal 2707 zcmd^BU1$_n6h3!n8fQ1z>~xJLlDHYG4_XAXt1$!wZ??&H3rUH8RcJ7Vl$J!R@rU#w zU}j?=Q3@^tgPSMz(*WzIza70155FWo;6 z{vEq_{z60O+nNohpN8tU)%pW|0C#15&4(rB7?r&Q22UjKR)ztFg6EE1eZ*t@8-b{B zRL(M2vIJMPzm`JuVuhGdx36%dK_*$FdoN4M>06VFnAgjPI*>_GDU_`X=&)Wu<1~$SB`C zSNs#h#~aOCdAW~!jiTB-^ivsgbSwekCu2KFXZZz4P-su3wkp#3nv(R8vk){W7WLAq zrUz@FXQj1k@iZ1lO@JGbHrpGR1;PdJA zlT()K5VYSB_gQs1R3bxy|BobgsfsP-`H^$YGv;#)X99O#eMg*gZEFPqMfpaYVGJV)#4+Cs)FzKcA6$jfzTtg8v^)!pn(MYN@#pz80bR65NxCa*=iR= zO#`4nC3b7^A~aa+iX_ENkog^9WB>C+{)o@6k^ceF`j}b!f%8k8-{Smx&Tn&mne!hx zZ`n1`;?&GvR(YLTRPCrRn3a)UqLA!7jZXrp; z=_sQp*nEb_;qw8^K;%$gn;NQ1+^_V;K0-Mhs!RM{rS$YOnVCa#4(6`EDhD7-1J?BD E3(-CqX8-^I literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/horseback.bin b/res/save_files_wii/any_bite/horseback.bin new file mode 100644 index 0000000000000000000000000000000000000000..3ca6b794ba3ca45e1e7045c051f47a0aa6358509 GIT binary patch literal 2707 zcmds3OK22H82+ofO?oDoOgl!+5_fDL2VbBQ(M1SYGbV8sRAPLB!6*@zxITiuyS1Ha<1(6Ik7P=0m?v_OXx+A~;cIlsrniZ9y(0m{o?03h>X1o9z4VPP11WQ`X z>#F3y#hat3K>X5p4@m+DK+{qHwL^@D7HHU?5K4lQ2n+`O!C=q}Bu|sxeBH7c7={7Z z<~Js!=*;o6Q?RIg;2dNBYFxxfmMgIZ(@P78X1^ZBD2x z#5i?w_Pj&a_&+r{MNUA(q}Y`5v0HR7a$NtFgv%ww&SP^Ua~}J9$uj`cO~vs?CGu0kG0@QUc6jdKkmcP8jd$dEwnRkoIF2*$ zeb~d``IEO$!(c8IOzaj9Pz?%Zk3%n)p zg23AX>(>s{dvx}2YQUq%wWhGiy+T-|w=L$|LeUWWrJ}BLMt7|z-F38eJnH_6T2HEc z{oWF;W4>S7j=(%|U(8q6ir1miG`>#ho>K0N`8K!XwJA1X-5^_Wx(%;0$Y0A?T^#dI z;Pa+&8+9BNAJ3FEZ0p1dWDPZy>F(;}UuA)|)u?+as*`_~tEaAWoms<{e5~F0Tn#|R J{c8>Pz5_m+8p;3w literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/hugo.bin b/res/save_files_wii/any_bite/hugo.bin new file mode 100644 index 0000000000000000000000000000000000000000..a24065814bf87ff31db9f0458fc4b2da64f699ca GIT binary patch literal 2700 zcmZSJXW(IA3`qzIW%&P}nejgm=xgZw|IYvhj1US$PMOXAQelM_NW{6eIUOc(T=$nTN5HgI6jBGGdFlmqj z{(Jm)1vv;EK%9v#3U)Iy#3V)q2#riSAafYxZ6p9AJEbP#lm1MI8u0JnR5sGJ%K#U?mI!AT}KQmH%H4QVk^2#kinAPWIl7KY_na3KJ6BTO9TTz&|{qYfr8 zO4B3+(8HZ3mcR`KmHxjN6+nDY5e_CBfK&qmLjgz-7eKg@15#B8FfekUDrRf|@*Pmc QMg))X%7GE#Pa1m?0NY+?Z2$lO literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/iza.bin b/res/save_files_wii/any_bite/iza.bin new file mode 100644 index 0000000000000000000000000000000000000000..2aad0d01bb131e5a6c16301bec52758eaec4c1f5 GIT binary patch literal 2707 zcmd^>TS!zv7{|XkdrC9YO;)lCdx3UAka8`;o;>CSAB2poh@ccHibMm89*P_zsHGkf zBYKE>Df1<$9-`j1Kq^E;6p3ycCX#entWXhm`p-FMj=K5aiHN|e^VnwE~j2umU=+I~AjbwBIO9%_HmH(1)Awfyag(gQmy z!g*n$6_&LHX_@r2t*5g6QI)6kW=qMcgJw^76EU56)hxeiS%6U{N@PKa;v$w9GJOnT zL}#T6%$PCFvW*!bBCj{y>-DA(bLlGXtl8~$6PEdRNo#lg$f3^W+CWu({kTikd0pmbN2PKG6tgwG*%n_V84LG}rF{k>ps%A24v>8pSlmIWhY>|4YkU zB&YEQ%2e+Gb>%d>1{GH&rCUasd=E%b{VA5~_;#M-$L)SKztVP?l%J{sbL)bL88>Jp zCC{Dlo1$vn3-!2#o_}`T=irK6ZJd0xMt9-a8Jsv2XU1`Zlb`T^Knhy*#V~!RN-ezJpN^17E-=Fakb;VK4*+ z!F$jT-hnsZ6?h5yKriS4-JlC}f)4NkJa5kQdkQ#_1V?KUg_iK3sN}(TDi7schy8(v z*86!#_wW$q^59spw z!&g~6oV;j=hYbSIzp!_p!u)sH>R^}gS~8JXjqP0nAxzL(FZ z;3*+q3%OA7sE86gcvYMfv!^q r7?2kP#Zey&nLxf0LDRykRUi={Is1ap3O;hSh!>HNTf2y=Em)}^9IkkT literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/lakebed_1.bin b/res/save_files_wii/any_bite/lakebed_1.bin new file mode 100644 index 0000000000000000000000000000000000000000..db632e183ff006378c1a667410031886d44e63d4 GIT binary patch literal 2707 zcmd^=T}TvB6vxkRF?tSoRw`DG+(*~TZ%v3Q&|F0VWt169Pa7O8hQ9&Fj6vveg(-XvnGCWFqN-EKEwoBx+|^*4+i`f?XVY2`=i zkH!^O+}KcG8y9gLG(`NWafLR;+OQ6ri(XOKOd+?h9hu2ATHvg80=B`rPc{j2edV{h zZBvn$I&-zP*)YC4d2Gn+8{Fs{p-)1$*w7P(Q%+Vii|uud)G zP1{vDxIoSBu*;;7E;luQS|Yhu&c<$sXYGYp=!HU;keOp;^My=WB01La`*j?+LQ#cr zPAu)8w)wX+SRxHsZe_~jlCPX%H$cW^KU|hkCcXngUFa;*SPw)eb zgDCh4K7%pv5sZQn5CQMO5O@dPfLGup7z6|01?UHTpcgy?Pr&2Obf2ey6A5s%CShm+ z4;WQE5KG~~>?^Q8@Zd%t4{9DBq#PcYD6;%jTcC@0U~*Dug0v>QJ_Rm-I&jgBU4xwm s%5DEJZ2P~Hs59LkJ->(W;;&$uMhtbG<-y!M#5Is7=2$Xy!iY`(0)4^#;s5{u literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/lanayru_twilight.bin b/res/save_files_wii/any_bite/lanayru_twilight.bin new file mode 100644 index 0000000000000000000000000000000000000000..ab8aa060a1018853a913ac3fedf4269ecd354862 GIT binary patch literal 2707 zcmd^1vykt{%O6D_hJdvg-AOGb=GQ1Dr4 z!X=m{U`FVa-Gy50P7`e~TR&@1O4Zd`Fhkez@;QSy?Sd`}?}f z6f3}n$X^$FSfiN>*5PxxSClYP2#YRhxu7_MaCwPjxa{t!W71i%ZKiiqHkEhgt`D3P z;+vJnlEyyXie<*)2lzc-b+;q5vcrdQ-O4cnuba7I2Tj@GC>@)N&u$a{U0wxw78S>b z6GeZ{9-njOf=V=xKdj@x9h7!3&WU-=75t+sUQov~|HZ0%K;7i(+f%x#pvH9CB}M*;#1`ZNUBV zG`IwM!Igq`1AY@|FT{u7H%MQ$L4P=XaVO!!cQH*PhPux4Xk!y}4K$fqvY)vsgzFbP C=kq53 literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/mdh_bridge.bin b/res/save_files_wii/any_bite/mdh_bridge.bin new file mode 100644 index 0000000000000000000000000000000000000000..e33dd428df6199d1d63b13e046c258dd8136d016 GIT binary patch literal 2707 zcmeH{T}V@57{{M?JEk*pYfLhXxk(!gq?|-qHy+8E7hafI5kVFqA03|{hqJ)Ip;jj-g91Z zP&Tcottabf!nuVLU@k2w|OekWWz1VtK(>}%O*ogk_T=7r!ckbf19oh9J*RpB{)>T<^YG_K|Fsg823~0Y}9dt4lAoOX zeq>&@Z#A4soCCVZ^~^bOv@UkOM562R{?bO%vF8@b#Qe6jQ_rikm=exyBJ*j=n*WLv zvvn!f$uc>&jDNQM)%Q>Cw;iFjxtA;XN}giIv+eD~VK`F{s+?TJ6jfY-V?w?dA`~Tt zxNx)zFIQm+`yy)HVWZ-o(`+y}cjlAZ6|HldN$c23f?4nl%z!EI1$+jRAPnAvw_pOi z0pnl{guqKM0$zY;;0YK8Ltqd*1_PiU^npjEdq&5BNQE&z{fOFB@CHN(vDjM&BUnG1X jT{-UXsV(?&DUB%#G4yqUdy7kuS3rqTCG__+Ei&^1c>@3p literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/mdh_tower.bin b/res/save_files_wii/any_bite/mdh_tower.bin new file mode 100644 index 0000000000000000000000000000000000000000..829ba4ebd1ddad2f389fd1e76b0be94b89f4bfb0 GIT binary patch literal 2707 zcmeH{Ur19?9LIlWyQVX9YfLhXIi(E*DJN0(7k;B5($F-K+xMFq9Q~j5$T_XnIu`uifOUkzUSVZ{ShDbP!!dVeSZJW@80|S?VNkb zK{i@dTUYjp$ZBC4WOYHIrjahpFhchZU%PmDpwp5XO9}5`f5F6qd&A=uquGmJ9_dK&J3Bl7jNy*-hy*Lc^GSs`cQz2YO$fLf{fL_=D+t`F)O z%3>yFl|h^(KcV5a6g(6yL?zSkROU>J=E&)^I-SmRVrWAbg)}*>&1R!{W5zE0U0&~C z)6|~nTX?)uwXbo1LL0sVO^x*lnP`BS#BV3G9M2e7MG0>cb$-1B#~@xpxmpY2)zl%*KU5-ky&qYt*CZjO*IOLj!%wJ z<@n@A*1rBKjj7Z!(_CYgi%D8`lbt6MsTj(%&?RYPTNKOR@W=dQJR5bKr5>V1IKV=< zuKx~YY=54L4L4E1#5gBrH%3ch%siqpQJhaUJBeMj-{#9)k)t&tR{qN${trYpi{vXO z-yfNm?OP3}QhW!5$n|t1jK-0=XdI5L%lk_kNk^YsC=(0Y(k?x((o%~1ZWEbLQ^vwq zB$=&CvQC!Cxn=yb?XSLnV!!POwJm*I$yIU`GoEd4Cl14zdPwEuBBrR~3VbHy!4RP+ zF~o&WtMIZF7Pl{=HZNdQ{BxQO`RC7ka)+XIZ8vEh+ei=s-@zORg0J8Um;nLs0lWuO z;2oF*6JQ*?2BY8=cmbY)5iksfz*8^?20%Y}0v>}$-B~V2DJK$OYjymv0`AsoxjUS} zJ%tzGzu}&hF78nr+(Sj&9V|Cu%Np2n?w*|$mLRRE&yRsKpaGnV)GwxUq1^iI|Gyc+PD5{R}x2q&KnWKHxv9`TiorLTH-s^rj~a!;;@ zC`GkAd6aBOqrUDPhoAHuxb<{nP2K1wJwx%VD*T*SODsSxRf@%elp=LJp^g|#Mq*~& ziO}RFM${Td#IS^rq-vJT?1`Zm+3jY#-JV3uC}}~76iimdVzCg~Tv^gP)Hq%D`5qP} zl^tz37PYwIYYEV{oi1w;?BM1w`j2$EnmeS;^YA z-Jo%qnrE7J8};NeHM`5Ml7VD2W%$*UM6$$(<9EDaTeI*IQeZ`#h{y^HwRh7MqWG;^ zlt(82a$DPer;H6Zkl(;KCuUuCn@*xaQM{fkRua2vz0cP;f=6pqd(8iFMZbaIW|98M z>EDkY*B5Wrj8fbKs_6No7(ZASx?cjpy#9P$>sPAoX9w$+Pz9Z8LZ;QxU#amTB`+~$ zdEx5im4&MGzC(fEZ2alxM=!SxuCn(gmolXc$%y~9wh@QoOg<@hkb{^ciy62k^n)Q> zQKE?hSF5nnB^Ggeidv^%tN7D+Hn+AB%02!4PCFb8JAcQ6C|;0yQ! zroj}L1QWmqK7cXs9=rvw!6+C3!{8+t0)t=xya4^+c~_dlR=|k_au6@HfV-3m?g}Jx zcXki#kKDb%!QGOLyD5je=881mlm}hJUGwuo6Qm{K%~@~>)Pu{x*mco3nz|A5;3f+gS&I{5tl%|R)vw#P9-q=6Xs3>p#T5? literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/morpheel.bin b/res/save_files_wii/any_bite/morpheel.bin new file mode 100644 index 0000000000000000000000000000000000000000..35e62f024c3e8b2797b6b740fab7e15dd0a499e5 GIT binary patch literal 2707 zcmeH|TS!zv7{|ZaJ*B3(+p>~fSa+daFr-{5&?k?%ULGuzEQvsqAc{l{3m=LcMJOpS zA)<$ zXAapYg*H|0E6gCWS(7x_=FBWjqlMNzbmi*Cst8LUDy;B4mBYP#nWGOM426n^)05vG zE3VmD?$7ZPtqjy`-;wPvAj(Oj-k$2VM^lYdlHB~fHBZm+ZzWR5CGYa97X_%LT9GJ7 z3+U|;y+xB`C1#VIIi7;Vh}r-{38tkQkxH%=rbaH8&EJQH=uBfr0zIL8scEn=fSB(~4H)FyoV%{c%pq@f@5t}xXp|zm1#uQ=! z>p9*e!gUp2>k1;FsCDLYYqO?(GxON6zK*y2ll>k0`F&?g>I5%)AbL6e;}P=XdD3N~QR-Go0xGxpp}0N{RU>0ZXB&bNmtmMeG$2yu%7wv zHEb}raMEY?Dq6=Li*~At1T$b7M8G8Y0ltF?5C&hsCom2^f-x`(Lf}0Z2JgTd@Cv*H zLtqfR00W>O^nqvK33%L<=62?DA_2BmCkQX#UagXQqbb~%bqVnY?%UwzKE=s>l+C@9 z0i&O^1wO#NQ&YkdtTo~F32+Y7f%7r#8sc0~7V95G9Q$?>b)|X2XLl1`{AEm0h@r02 T+?SIJT>-h`8pEbLwP@rg*+&2$ literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/ordon_gate_clip.bin b/res/save_files_wii/any_bite/ordon_gate_clip.bin new file mode 100644 index 0000000000000000000000000000000000000000..3ab14454cf3c1f485bebe2a9872860e19f455085 GIT binary patch literal 2700 zcmZSJXW(Igfd5dS2cn^32x`i#w~fmm`+@|V+Y!P?#g_haE@faiCZ?k+8tN7w9AIc* z!oa|YV2LsuTsY;v^IE0wqf+`JKl))ZjEsy-2y@U`AP2x4L@bSN20p%=9KW2L92c?@ zOg|&b4m_LLzU|*TkTZNT^RiK#<64lPmxv+)=P&}}0*K;(0zeuC%s}Cdaz;a7Gz3Oc z2*65T7(J5wKI&I!2ndXZ1mz*Xc;x`PA=^O$94o-&4lubAOzs4etH9(gFu58`?gouU}yV5FhaQSj>aVEOGs2v7{|YJ=cZ=nOfr%^xXwT`AV@hcdj+2#O$6zVmdR_EWc`5fLS5RWI>KJD#Jz0 zt?XotAt?B)RQ{wHGi}=>Bk6gD;Wi8-otR5ksm_{-JRT2WnSYmbK5G~|`0>6hs`5zv z(HX^+H8#}O&WI!&Y>52qMGI|-y~*c-o$ig}u<|GBOM7Y(AesizLTBe!qzW*C?tn z&WUCHd&^uTr|}2MR98k_InAj-#Z^ge+o+K50V%3K-F6eclb7)0PQRL8X(vp|&rpH6 zb#c2DH)#c>&7JX^qH5g>^|*zee`ejSaK(;WoO1Qmx(m4z~rRV1ZhcqbsAg*b>NZ{y8*ihR671) o*v@Y!QAf5vdSNHw;xA#kP7HOO70SyDcjj#d?k05*Bi$X#3Qyw%QLBR#rsNgCL5e2NiuVbd*4) zpa&5Z(Wi>O1U*0q`mjOo2$4*pmxhKZm5UW>q0@ivLw8ptf`XzB`#a~%Ip@yK+<9E` zk%zvPYz$8$r<=J1WJW29>Tj+0Rn3X_sX<#sQ_tsHTtqSw@e{&{<~hI`gNQp45K7+wb>h5K~k$sFEnPz>vMy$nU>hV# zf3Bz?a!gE&NuSk~?U8oU@~+Z(eXRpZ|GCQjLVS`jLs+x1cP@u|>T5Ui8@{Yf4sCL= z4>{c|AS!Qfp=fLS8g_8b1!~c$qI=H@EZk2CyUI?HgLF*FG17zSzsyxPx&{%xeyx|9BR8MDt|T z6!LgUuB!JspP*qCq&x#$I#QIeI7y@`d^)rlO3<0)acIc4VD(s3*NjwULcxFccfA2? zvE*H8h<$$Sxc$HKd_bbO2Mn?E87Xb6Zt#8?u<~~QwzaQRyB)OZGA)~8=GGbkoyI4h zQsoF?XJMl)wjRh$&RfyZ&BoDFBWCP*kC^-g!Qb6~@(^#MDOO$LYVO`is)GOa9^@Q` zGkvdKLmDwvFXH&hBwwtuL`phoxLT!`qq2nCQ*NTXIL@+6{cFM+W2sXcM012s{Ay zk7aAVxtvI%Tf|@`JYSUX{DHAN5IhUNjR&S`JfQk`fC_njPsD7yX2M2zes8a|1gRYJ z;s7`bO2G*$b{>8hEV0^K;TK9@%CYRc_~F&~B2XGrRbm)d4G$EB5m!Ojtb%6Ftzw|- EJB;87(*OVf literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/pot1.bin b/res/save_files_wii/any_bite/pot1.bin new file mode 100644 index 0000000000000000000000000000000000000000..890cfe1535952628b3a8e6b07b21ff57061287e8 GIT binary patch literal 2707 zcmd^BT}TvB6h3!n(phVD+KdWo8!IA1$hD$^pi$h-hYb4!A{=hn{k!pqD^lBa6`KoO@@Sc2n#niaP9^^PTUUd*|FUckV0% zKn1qEEO-mRBc)ggP^f$vQaJ{l1!RFp2Lfq+P)Xp>7_~LYo z>rT#|4^b$5_hA*=H}4!ob=Vt=EtQ<$FIsk*9fy2P$3=Mt8lr%juI*TRlhk#!qQ}mW~aZOuYW^r|92Ih{s>9<9qLcZVxjIV17}sF?8LT0C|)( z2~ zpn zMXv91eTnOPTvxB|srD+y(p|v}6$(p_mkwY+>k28Ih4R{@xAH&xv);^y|)3?3}uXX15HD8QnXCio=Mf_33 zDjPBXINom>`Dh!_(0rPxVM{YsAZjQrj|s`-K~S?IWDv9z zMG({~6;=dMU|Ie!?e(LQ9zP^{Z1kf)feRa1luhT}JL9&iJs_f}!_GP1`OcX;=bpK9 z?}8grfvhO?Tn4a74g+QC!iwx1vnYH1Q-D10Li9z>41gi9J9ezEJyrnC&+!^YE|!$X>e|K> z6Gwtg0+KA8uQX`~0#H`~AmMCK&;uEgI0j1GvOr9`-N|mZ+W};lNy>_0g;iCBxM@aj z{B!8R!)Mvm`1pjy6_uO7gI%`7TeY>a%yMEawt)SLQL}C^5!0lI^g8CZi$ixWCpgBK>*f1z$VoJ-uD~%JtTiq?b+Fn8Fl{ zQ!8ip>k#C>dcB&|&prhq3VR}vWs(*ANlO~ZQOMOOPRcgeEefa!QT{@E6bmGWz==qS z=Y_Ar-a0sftsp+C=D%>ZhRR5g)-2mP2E+q_ildbfuuCXK@rzn4FlDtfh>ja?GZ{f zPWq$qZzF9oi%rj+c0c(!c?qCJQ~CIbVNTTU?D!Pcx}64FA4G7`%>Nk?bMlgDt?$R; zt==RHkdONS7c1_F^Ub{e{pE!jx5l@uwWV5qhM6bUhUrAjye^I@43GUvr2~-2A|HE% zg>kX*n}*3};mJ0blnzFIhqLDWos?e+{qFH&i@SlYv9ej0$5p4v5sGGMa($KSb6j8Jx@t{tm0i*AOmx|mfLxcOF_w#-`#k7(Zlvh&_2R2& zn{un%9_c*PFbZvtuiPH?7T07l>vwJl9)y|vKEJb~0k8dYBltN*y5=&{?_A%E*E;{0 zweR)iO+ma)B7P%c6#>jYiqD(Ge6#^HY&=cWu%!(v5H*w(Ms5{_9xZSMSE23l6@?yp YrN$O!V{6!$h_$OfssV^{&-nc23!)ngoB#j- literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/purple_mist.bin b/res/save_files_wii/any_bite/purple_mist.bin new file mode 100644 index 0000000000000000000000000000000000000000..5b6e86a9fcf9a05f09eaf0c158136ffea504d30c GIT binary patch literal 2700 zcmeHJKWGzS6n|eY)*}S2777*R47ga3Ce zE9b{ZYeQ+V)WA`#$u|{TfOF4M&ks+>BZ?m@)m+NYy!c+&yH2;p3YRaO9ZDaUNWd}_ z+tWvjb8lYf59aHOr%pe9S_vaYm+JPn_$#0QJ*r1gP`d?XwyS>422q!+R7ru*Ac*CB z48gW7+qUCGMl6&XJ(C=W>=3HE=URCA<@P6(bLQIk^_H?miZ{l&MQJ+H2Y#0X!HW0`$PS4_Ajby$z%GWGbokkM_Ks_D$-Eyj`!{ zovx>I|IP{Qk`oB$fXqtY8uPI^AHp)NfQ(`ez`}JhG&{D9ClH?NHg5TktuX!da1t5) zF(vozb;yy6L%Ev*5(vHzNQl~2l`>ip=qF`H6XXSPTK)gVQ=i@TUaNdRUDfQ6n&-O` zxR~xmWRSaMA^VS23P(@WqBYoaWBwLVnr6z;%7A6S3SbrBnzNeUnrlVIv*!h~V(~k3jd&m2 qk~wJcd$U~RUbq`xro=zJ%tx+(ZG!y*`wdnFtAY7_3}QtJQ2zsz@&>*D literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/seam_clip.bin b/res/save_files_wii/any_bite/seam_clip.bin new file mode 100644 index 0000000000000000000000000000000000000000..ef1f19fa4c4cae6e880a9f18bb218a37c98a7426 GIT binary patch literal 2707 zcmZSJXW(IAW&nZzP@u1&^Z!3s1esWN`ev3^w;4#t*@Wd6OzN1OoYP^~X;R19w!iT( za*GcRFf_1WU|<2Wj0_nV*hCqQSv7bcx^v#**p#0_jW#efEKH0%FjFvTkON>2B9;bf zVZt;CyNH||znq*L7Xt$$3rqo~pJ6hCgJxV-et#3lDL$Eb*(gqOEy&MHL=k~=7=bKQd^5^4xgToovwnm59Ej8_hf@DQMq_v7~c H|33`?4}xNE literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/snowpeak_ruins_mbbb.bin b/res/save_files_wii/any_bite/snowpeak_ruins_mbbb.bin new file mode 100644 index 0000000000000000000000000000000000000000..068541fab2994c17183f5bbd0888c063731a62c7 GIT binary patch literal 2707 zcmeH{TS!zv7{|ZabMmgc$x4Q??nW*MQmzQvlgCi=!NSb42)qzPl4xPkLy@BxDG6mo z^bkFj^b%bOdfT8d2@^>~D$_6#rORT5O1Y>1oXcLM(1(PgKKA$B<~ws{cD^%YCmU@n zuU2{}+A5I>*_584sW3^7U8lQOAzSQDGgM%d<`C0+duQwKiY z!=mJp+WJFbiz__bP+t?42?W@a_*Fui+tW9!poF)GIJxDKnR^cVfEadhSwJx7t@y}^C;9b1uv-@PV zeXm75Q$d0S@D0p^S@0FifN9_ZpTGw&1t!4+7zbnE9T)*`!5i=j41qx~0A7NA&fZp!3ovjut_oef*S)8^)c zB}jAR>yzL-r~{Xb+;#Z5pu~s|!p{`Gkj^BRuVXhZds@Y0nHbj9#@$)D$jcyCuYzWD IQT5OK0M7peC;$Ke literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/stallord.bin b/res/save_files_wii/any_bite/stallord.bin new file mode 100644 index 0000000000000000000000000000000000000000..1f07d30f0e949a4fd620c42ec39ce5e0776ea603 GIT binary patch literal 2707 zcmeH|Sx8h-7{|YJ@6EOJHjQK$<276VcoM++}RWs33%jKIV76bZyLBEinK+=wD?RD-0tbhGiTyTQo6J zyi6?Fal@KI#E6a+5wT@vfAC15B&%@pux5$#zg7!%3^EN|@%KB~5Mn z-|hNv3xiVD?AccyF}PI+_U|i;h=d(1f%xn|D^^2@HBB8BKPLQ;SwgC?!!nU>)DTGv z!U5Z0%~ch`Oj|#+*FV-fDNkIgJz^Li!*#6NJUiEYqpb})`5kvsnWQaT=SE(4YO4Et zDA?Oy%#JU=LOnXwj8trpo#ZcSc9WeaiF8a#`rcRKNR63_-v~|FmW4$~h7}hbw|tfA z&d@oc80#DnpH}8#V!<>K*G2R9h_(G0I>s;2u*5hg=CX#*e?E&0BEP6wM5>F#uDWjX z`5JaXNGE8F(}h_ZJFn%gq*fii`ZTB}C8dd&eez5*kyJ?|Mef1wv7oM*!CJHZ-^}a^ z*qcSpm2>Wo6L)@H+z&_)?|>;zJ|W7t>%#BLq@8#AceL}QI)1iYmuTxKRnTAtbebD^ zOXX8E8yPk}WNM=H$b3{c)7{ePp&v$^dH=BJHVE+lLIx{vgT3DD20q#2`UuMbIEn zib@D-l?*F_DEgP!{!Hjcs2+OCjUIXl6gIL5ozA^?X4la|dx!!LJLi1oJ7?~kd*pM! z*8|`zC@ox+&m zy&hM@oETvMa;x?*h3V@D_T_*7I3f(63m(z+Xe^KPv$b>8Gt_siT)Uf+^r1tUTan~G5#?B4eh^7H?@q)F9>&^tor&;qs zc0meu-*rPvRnSs-hcBa5QYj23&gy<>lWS|5UIfxwlF}&$B}_9l9b}sVq7cEiM^sf| zWH288JyI6Y*ivuKtK|3O1Ar1w<>TitVs@H050rqYvGrkH-0?f6A|l451;d*A$K=hP zBy$kJbAX8%cZ~6kyx95DZ^X^`p;8=pawq_4B8i`#3eC+Nq+gOLxYfwpFizhh><@_4MLFpX z)0ddO%k*WY?=gLa>HADqZ5^tzOImN1%PzHw4fzUXnP}ekp`de*iVj;Z{)+C!J>~Z3 z S*fRrbmnW(Li1O!VlDLjP4{8P7*eZhH&63@2!Ib!4XfTF|CAQj7B|Zeq z-54;6;G!tfQeCA=+M@Ua{^nsdn)abIQu|OJG%G%&4}~mjG0~cg=iWP$%_fRL5JV3< z-}%nZ-aY63%|Z~0fvnm1VGsBfm&Za`@ag;u7L1nD)#v|t>gsh*1SsGA!(Au*Yv+H- z+uxt6jUKQ3=K1W+%F5zzt-3gzgO1SJs|4Elq? zpa)2SCcR$WvKbhL0odkSCd6nweG7X7*NXaWUqJ|G*^x-YH`{6*BVXeb@XrQBJ{w$Q zn-m#q;dV--fWi5&3{JC}arLGSJ8VKOHErj#WM$viNM z@7`O-t`-$OJZY9z6=9q@IlFq(5PQ_Sp0qAK1!)S++3YIS364{f6XYm_O^Qt^FZzlO zhAZbkm&$R;v^4mT>7)J9HegQ+JAzZaMA~djT7VScgw5i9dUH(lZFb1zucf%)3S<<* z7?6N0n7z908F>78)=V%0P=vD|xX7lMsJZ-&i;>VNr}2rCJ8mdOQ{$fBW5ovwOSliJ z1qvttLoq>9)3|zsQd2nNx%kVOIhV&4g%|$ee?T4rn7LFOe?rQe5sv<*&Syi51_mw9 zx6ph;-e)UBq<|wHaexSenRlMSDxb=@4_XDj>p<)VRa-mf$MHM;`z49tJs`x1dq&xI z-thg>W5=EG9qnAHj=jLnlY7H9a%y?E;1nk1pHlSzczNXiKB-}Pe*Cs2{r=072YB9< z%^XL_#u0ox)aiRXxv!J+rpM2(=@y#S+IFT*-%+Be_}hV2#&`*Y?x1Z@hVKVjmga8> zU2F^!LnxSnccp?b#{&sw0u*b+zCH0MnLKw!hTgnf$Knr&-p<|n6@fnq zyejZ#f!757BJjGvhR^#O+&cShcEGJCw3c#{dxfy@gJj&dg`y$$OSHaYURRwv+j*#M z3hKUSojViR&{)QG-1lYjAbc#o7x&e-;j@2P7LQZ5dl`4eeVg0y*%UARVt}nanZ)Nu z=$Qo*@vR&2byA^@tI@I0K>hzxx^~5Ev^K01R Q#oCQm)c~Y)mNj(mIX|HvNdN!< literal 0 HcmV?d00001 diff --git a/res/save_files_wii/any_bite/waterfall_sidehop.bin b/res/save_files_wii/any_bite/waterfall_sidehop.bin new file mode 100644 index 0000000000000000000000000000000000000000..a8671b5bbf5f0afcf3b4064e2f8c446e4fc0162c GIT binary patch literal 2707 zcmd^>Ur19?9LIlWyZ*^cmuz4Z+XOcfjNB?%FLfg|AAB&fB8p0)D3Tde^ia^zmmKzx z5IsdtA-x3UL(q$?hguOLl8ne|#F9v?hFMd>oxbPZovYhJ>}8PkW1sW;d(Zuydv?!8 z)l^9vJCE=7kPvnt#O8)ZAt+yl;@f8jren;Wn~04zOg()PPqrp%SHHX1dTd`uAQ&K8 zV_1PLD0We7q`vQ2|Id*<;h9^*o6O8MB8|Co=UVQ`2J|-3A{)vVJF)zb;o}l?*9XPw zmd#aV+WN8(k=tGEcDpNx*)lJ|LNa4tywiJ0 zi0@V%OBm1bejq&-Il&)0tGaC74INJ8bycG?4yUw?l>|$r(?ryl(I3-iPe}Z_(xZ~qPC~_&8l-io#g7>Q*qTnwQ01; zb3ls9SD3ElXX`9Klg+F7mClCc8C{6%h!|Bmt)=op>WfyR)?KU{6np;MzUAvL_TS^= zp<0dM-Zh*!6leMcos$>8%fBMoeOto3N=JqtI*I?B1#$><5&74 z(Ci*cBNTFIQGZEVinf+YDcZCe=p~lWAGLwDze=%JYN?=*!vj+>aeZgz_AZwojXy-u zm)qz4oVjOaXWl#1!GgAJS9lgd=mC%9(anvgJS>!hr)F1e@3>S|kxwwC&(Eb1d*{T@ ziHa=VdM?qqwY|CNF-pZcpKfbER9B3GlDX?TSoz+0nn&ubw{PYGVH2@nz}*L1L;a^@l9AXdpe(DYHsPH;3(D{XK_ zD=bJj91MrU0kA4V#mY=CZ^5!G_{%H|_{R;_^YFDJKM)PJrMtJMgigHg)!r9NA_a%G zKzw!*iro;RmdVL-lEP2R5;_L+Wp0%>2xt$?j|rQd9(Y6OmCsD~Mt{Bai$3?UyVvt_ zA|{31kZ0#a^UTz{UC!CM+M9=0R~vPdrKH=t<;wh_BTPj)!f_sQqp@<4Q={Krk>lJWI>^QJo%6;*3sg-}PkpFHWVut^KK zKu^l#!gMQ^kBw{AK>z-Wt@XpTD@6=jBuc1)rP(m_486T&oZB+!7W02@+3Q6v($;h- zILU4rut7&RChKYbvUGJ;+A{+av%jysJLd&n$Kbf?XZ{731^-*fzbxqa9q_N7ANMyV z-z{6I4@mLzfd#(bRsH;peBPhm*M3X&{l62+V9JAGtKn2aE%iYHI{@l@K)u0=-IJ;utj@NB8H-x6n&7{T>}q=9 z^5h>7^QZ`$XCxkzcvj+P62FpoT;kUf`(Bvot2CVp>q3=g%1B0S5tZV!d^h6O?B?Xi z^OEQudt|b!GWX53PY0XvbY=>){c4j*$J6%5|rx{v-On2H}%VkrMOSio>2UVxTy5w&x~0GFM`uk?o|X X5*?Y#asAL~p-VaJt|so*y*T^<6pbNP literal 0 HcmV?d00001