diff --git a/ASM/scripts/build.py b/ASM/scripts/build.py index 02ccb2a..792787b 100644 --- a/ASM/scripts/build.py +++ b/ASM/scripts/build.py @@ -97,4 +97,4 @@ # Diff ROMs call(['python', 'scripts/rom_diff.py', - 'roms/base.z64', 'roms/patched.z64', '../data/rom_patch.txt']) + 'roms/base.z64', 'roms/patched.z64', '../data/rom_patch.txt']) diff --git a/ASM/scripts/rom_diff.py b/ASM/scripts/rom_diff.py index d3ede3c..c8495fb 100644 --- a/ASM/scripts/rom_diff.py +++ b/ASM/scripts/rom_diff.py @@ -1,4 +1,3 @@ -import json import sys if len(sys.argv) < 4: @@ -8,6 +7,7 @@ compare_path = sys.argv[2] output_path = sys.argv[3] + def unequal_chunks(file1, file2): chunk_size = 2048 i = 0 @@ -20,6 +20,7 @@ def unequal_chunks(file1, file2): yield (i * chunk_size, chunk1, chunk2) i += 1 + diffs = [] with open(base_path, 'rb') as base_f, open(compare_path, 'rb') as comp_f: for (i, base_c, comp_c) in unequal_chunks(base_f, comp_f): diff --git a/BaseClasses.py b/BaseClasses.py index 5033539..ecea5fc 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -1,8 +1,8 @@ import copy -from enum import Enum, unique import logging -from collections import OrderedDict, Counter, defaultdict import random +from collections import OrderedDict, Counter, defaultdict +from enum import Enum, unique class World(object): @@ -57,7 +57,6 @@ def __init__(self, settings): self.keys_placed = False self.spoiler = Spoiler(self) - def copy(self): ret = World(self.settings) ret.skipped_trials = copy.copy(self.skipped_trials) @@ -66,12 +65,6 @@ def copy(self): ret.can_take_damage = self.can_take_damage ret.shop_prices = copy.copy(self.shop_prices) ret.id = self.id - from Regions import create_regions - from Dungeons import create_dungeons - from Rules import set_rules, set_shop_rules - create_regions(ret) - create_dungeons(ret) - set_rules(ret) # connect copied world for region in self.regions: @@ -97,8 +90,6 @@ def copy(self): # copy progress items in state ret.state.prog_items = copy.copy(self.state.prog_items) - set_shop_rules(ret) - return ret def initialize_regions(self): @@ -112,7 +103,7 @@ def initialize_items(self): item.world = self for region in self.regions: for location in region.locations: - if location.item != None: + if location.item is not None: location.item.world = self for item in [item for dungeon in self.dungeons for item in dungeon.all_items]: item.world = self @@ -122,7 +113,7 @@ def random_shop_prices(self): self.shop_prices = {} for region in self.regions: if self.shopsanity == 'random': - shop_item_count = random.randint(0,4) + shop_item_count = random.randint(0, 4) else: shop_item_count = int(self.shopsanity) @@ -201,7 +192,8 @@ def get_unrestricted_dungeon_items(self): return itempool def find_items(self, item): - return [location for location in self.get_locations() if location.item is not None and location.item.name == item] + return [location for location in self.get_locations() if + location.item is not None and location.item.name == item] def push_item(self, location, item): if not isinstance(location, Location): @@ -212,10 +204,13 @@ def push_item(self, location, item): location.item = item item.location = location - if item.type != 'Token' and item.type != 'Event' and item.type != 'Shop' and not (item.key or item.map or item.compass) and item.advancement and location.parent_region.dungeon: + if item.type != 'Token' and item.type != 'Event' and item.type != 'Shop' and not ( + item.key or item.map or item.compass) and item.advancement and location.parent_region.dungeon: location.parent_region.dungeon.major_items += 1 - logging.getLogger('').debug('Placed %s [World %d] at %s [World %d]', item, item.world.id if hasattr(item, 'world') else -1, location, location.world.id if hasattr(location, 'world') else -1) + logging.getLogger('').debug('Placed %s [World %d] at %s [World %d]', item, + item.world.id if hasattr(item, 'world') else -1, location, + location.world.id if hasattr(location, 'world') else -1) else: raise RuntimeError('Cannot assign item %s to location %s.' % (item, location)) @@ -331,24 +326,24 @@ def has(self, item, count=1): return self.prog_items[item] >= count def item_count(self, item): - return self.prog_items[item] + return self.prog_items[item] def is_adult(self): return self.has('Master Sword') def can_child_attack(self): - return self.has_slingshot() or \ - self.has('Boomerang') or \ - self.has_sticks() or \ - self.has_explosives() or \ - self.has('Kokiri Sword') or \ - (self.has('Dins Fire') and self.has('Magic Meter')) + return self.has_slingshot() or \ + self.has('Boomerang') or \ + self.has_sticks() or \ + self.has_explosives() or \ + self.has('Kokiri Sword') or \ + (self.has('Dins Fire') and self.has('Magic Meter')) def can_stun_deku(self): - return self.is_adult() or \ - self.can_child_attack() or \ - self.has_nuts() or \ - self.has('Buy Deku Shield') + return self.is_adult() or \ + self.can_child_attack() or \ + self.has_nuts() or \ + self.has('Buy Deku Shield') def has_nuts(self): return self.has('Buy Deku Nut (5)') or self.has('Buy Deku Nut (10)') or self.has('Deku Nut Drop') @@ -367,13 +362,13 @@ def has_bombs(self): def has_blue_fire(self): return self.has_bottle() and \ - (self.can_reach('Ice Cavern') - or self.can_reach('Ganons Castle Water Trial') + (self.can_reach('Ice Cavern') + or self.can_reach('Ganons Castle Water Trial') or self.has('Buy Blue Fire') or (self.world.dungeon_mq['GTG'] and self.can_reach('Gerudo Training Grounds Stalfos Room'))) def has_ocarina(self): - return (self.has('Ocarina') or self.has("Fairy Ocarina") or self.has("Ocarina of Time")) + return self.has('Ocarina') or self.has("Fairy Ocarina") or self.has("Ocarina of Time") def can_play(self, song): return self.has_ocarina() and self.has(song) @@ -411,17 +406,17 @@ def can_buy_bombchus(self): def has_bombchus(self): return (self.world.bombchus_in_logic and \ - ((any(pritem.startswith('Bombchus') for pritem in self.prog_items) and \ - self.can_buy_bombchus()) \ - or (self.has('Progressive Wallet') and self.can_reach('Haunted Wasteland')))) \ - or (not self.world.bombchus_in_logic and self.has('Bomb Bag') and \ - self.can_buy_bombchus()) + ((any(pritem.startswith('Bombchus') for pritem in self.prog_items) and \ + self.can_buy_bombchus()) \ + or (self.has('Progressive Wallet') and self.can_reach('Haunted Wasteland')))) \ + or (not self.world.bombchus_in_logic and self.has('Bomb Bag') and \ + self.can_buy_bombchus()) def has_bombchus_item(self): return (self.world.bombchus_in_logic and \ (any(pritem.startswith('Bombchus') for pritem in self.prog_items) \ - or (self.has('Progressive Wallet') and self.can_reach('Haunted Wasteland')))) \ - or (not self.world.bombchus_in_logic and self.has('Bomb Bag')) + or (self.has('Progressive Wallet') and self.can_reach('Haunted Wasteland')))) \ + or (not self.world.bombchus_in_logic and self.has('Bomb Bag')) def has_explosives(self): return self.has_bombs() or self.has_bombchus() @@ -433,7 +428,7 @@ def can_dive(self): return self.has('Progressive Scale') def can_see_with_lens(self): - return ((self.has('Magic Meter') and self.has('Lens of Truth')) or self.world.logic_lens != 'all') + return (self.has('Magic Meter') and self.has('Lens of Truth')) or self.world.logic_lens != 'all' def has_projectile(self, age='either'): if age == 'child': @@ -441,30 +436,49 @@ def has_projectile(self, age='either'): elif age == 'adult': return self.has_explosives() or self.has_bow() or self.has('Progressive Hookshot') elif age == 'both': - return self.has_explosives() or ((self.has_bow() or self.has('Progressive Hookshot')) and (self.has_slingshot() or self.has('Boomerang'))) + return self.has_explosives() or ((self.has_bow() or self.has('Progressive Hookshot')) and ( + self.has_slingshot() or self.has('Boomerang'))) else: - return self.has_explosives() or ((self.has_bow() or self.has('Progressive Hookshot')) or (self.has_slingshot() or self.has('Boomerang'))) + return self.has_explosives() or ((self.has_bow() or self.has('Progressive Hookshot')) or ( + self.has_slingshot() or self.has('Boomerang'))) def has_GoronTunic(self): - return (self.has('Goron Tunic') or self.has('Buy Goron Tunic')) + return self.has('Goron Tunic') or self.has('Buy Goron Tunic') def has_ZoraTunic(self): - return (self.has('Zora Tunic') or self.has('Buy Zora Tunic')) + return self.has('Zora Tunic') or self.has('Buy Zora Tunic') def can_leave_forest(self): return self.world.open_forest or self.can_reach(self.world.get_location('Queen Gohma')) def can_finish_adult_trades(self): - zora_thawed = (self.can_play('Zeldas Lullaby') or (self.has('Hover Boots') and self.world.logic_zora_with_hovers)) and self.has_blue_fire() + zora_thawed = (self.can_play('Zeldas Lullaby') or ( + self.has('Hover Boots') and self.world.logic_zora_with_hovers)) and self.has_blue_fire() carpenter_access = self.has('Epona') or self.has('Progressive Hookshot', 2) - return (self.has('Claim Check') or ((self.has('Progressive Strength Upgrade') or self.can_blast_or_smash() or self.has_bow()) and (((self.has('Eyedrops') or self.has('Eyeball Frog') or self.has('Prescription') or self.has('Broken Sword')) and zora_thawed) or ((self.has('Poachers Saw') or self.has('Odd Mushroom') or self.has('Cojiro') or self.has('Pocket Cucco') or self.has('Pocket Egg')) and zora_thawed and carpenter_access)))) + return (self.has('Claim Check') or ( + (self.has('Progressive Strength Upgrade') or self.can_blast_or_smash() or self.has_bow()) and ((( + self.has( + 'Eyedrops') or self.has( + 'Eyeball Frog') or self.has( + 'Prescription') or self.has( + 'Broken Sword')) and zora_thawed) or ( + ( + self.has( + 'Poachers Saw') or self.has( + 'Odd Mushroom') or self.has( + 'Cojiro') or self.has( + 'Pocket Cucco') or self.has( + 'Pocket Egg')) and zora_thawed and carpenter_access)))) def has_bottle(self): - is_normal_bottle = lambda item: (item.startswith('Bottle') and item != 'Bottle with Letter' and (item != 'Bottle with Big Poe' or self.is_adult())) + is_normal_bottle = lambda item: (item.startswith('Bottle') and item != 'Bottle with Letter' and ( + item != 'Bottle with Big Poe' or self.is_adult())) return any(is_normal_bottle(pritem) for pritem in self.prog_items) def bottle_count(self): - return sum([pritem for pritem in self.prog_items if pritem.startswith('Bottle') and pritem != 'Bottle with Letter' and (pritem != 'Bottle with Big Poe' or self.is_adult())]) + return sum([pritem for pritem in self.prog_items if + pritem.startswith('Bottle') and pritem != 'Bottle with Letter' and ( + pritem != 'Bottle with Big Poe' or self.is_adult())]) def has_hearts(self, count): # Warning: This only considers items that are marked as advancement items @@ -473,19 +487,20 @@ def has_hearts(self, count): def heart_count(self): # Warning: This only considers items that are marked as advancement items return ( - self.item_count('Heart Container') - + self.item_count('Piece of Heart') // 4 - + 3 # starting hearts + self.item_count('Heart Container') + + self.item_count('Piece of Heart') // 4 + + 3 # starting hearts ) def has_fire_source(self): return self.can_use('Dins Fire') or self.can_use('Fire Arrows') def guarantee_hint(self): - if(self.world.hints == 'mask'): + if self.world.hints == 'mask': # has the mask of truth - return self.has('Zeldas Letter') and self.can_play('Sarias Song') and self.has('Kokiri Emerald') and self.has('Goron Ruby') and self.has('Zora Sapphire') - elif(self.world.hints == 'agony'): + return self.has('Zeldas Letter') and self.can_play('Sarias Song') and self.has( + 'Kokiri Emerald') and self.has('Goron Ruby') and self.has('Zora Sapphire') + elif self.world.hints == 'agony': # has the Stone of Agony return self.has('Stone of Agony') return True @@ -497,7 +512,9 @@ def nighttime(self): def can_finish_GerudoFortress(self): if self.world.gerudo_fortress == 'normal': - return self.has('Small Key (Gerudo Fortress)', 4) and (self.can_use('Bow') or self.can_use('Hookshot') or self.can_use('Hover Boots') or self.world.logic_tricks) + return self.has('Small Key (Gerudo Fortress)', 4) and ( + self.can_use('Bow') or self.can_use('Hookshot') or self.can_use( + 'Hover Boots') or self.world.logic_tricks) elif self.world.gerudo_fortress == 'fast': return self.has('Small Key (Gerudo Fortress)', 1) and self.is_adult() else: @@ -509,17 +526,17 @@ def collect(self, item): if item.advancement: self.prog_items[item.name] += 1 self.clear_cached_unreachable() - + # Be careful using this function. It will not uncollect any # items that may be locked behind the item, only the item itself. def remove(self, item): if self.prog_items[item.name] > 0: self.prog_items[item.name] -= 1 if self.prog_items[item.name] <= 0: - del self.prog_items[item.name] + del self.prog_items[item.name] # invalidate collected cache. unreachable locations are still unreachable - self.region_cache = {k: v for k, v in self.region_cache.items() if not v} + self.region_cache = {k: v for k, v in self.region_cache.items() if not v} self.location_cache = {k: v for k, v in self.location_cache.items() if not v} self.entrance_cache = {k: v for k, v in self.entrance_cache.items() if not v} self.recursion_count = 0 @@ -541,7 +558,7 @@ def get_states_with_items(base_state_list, itempool): for base_state in base_state_list: new_state = base_state.copy() for item in itempool: - if item.world.id == base_state.world.id: # Check world + if item.world.id == base_state.world.id: # Check world new_state.collect(item) new_state_list.append(new_state) CollectionState.collect_locations(new_state_list) @@ -553,13 +570,16 @@ def get_states_with_items(base_state_list, itempool): @staticmethod def collect_locations(state_list): # Get all item locations in the worlds - item_locations = [location for state in state_list for location in state.world.get_filled_locations() if location.item.advancement] + item_locations = [location for state in state_list for location in state.world.get_filled_locations() if + location.item.advancement] # will loop if there is more items opened up in the previous iteration. Always run once reachable_items_locations = True while reachable_items_locations: # get reachable new items locations - reachable_items_locations = [location for location in item_locations if location.name not in state_list[location.world.id].collected_locations and state_list[location.world.id].can_reach(location)] + reachable_items_locations = [location for location in item_locations if + location.name not in state_list[location.world.id].collected_locations and + state_list[location.world.id].can_reach(location)] for location in reachable_items_locations: # Mark the location collected in the state world it exists in state_list[location.world.id].collected_locations[location.name] = True @@ -598,13 +618,13 @@ def update_required_items(worlds): # get list of all of the progressive items that can appear in hints all_locations = [location for world in worlds for location in world.get_filled_locations()] - item_locations = [location for location in all_locations - if location.item.advancement - and location.item.type != 'Event' - and location.item.type != 'Shop' - and not location.event - and (worlds[0].shuffle_smallkeys != 'dungeon' or not location.item.smallkey) - and (worlds[0].shuffle_bosskeys != 'dungeon' or not location.item.bosskey)] + item_locations = [location for location in all_locations + if location.item.advancement + and location.item.type != 'Event' + and location.item.type != 'Shop' + and not location.event + and (worlds[0].shuffle_smallkeys != 'dungeon' or not location.item.smallkey) + and (worlds[0].shuffle_bosskeys != 'dungeon' or not location.item.bosskey)] # if the playthrough was generated, filter the list of locations to the # locations in the playthrough. The required locations is a subset of these @@ -612,14 +632,17 @@ def update_required_items(worlds): # copied spoiler world, so must try to find the matching locations by name if worlds[0].spoiler.playthrough: spoiler_locations = defaultdict(lambda: []) - for location in [location for _,sphere in worlds[0].spoiler.playthrough.items() for location in sphere]: + for location in [location for _, sphere in worlds[0].spoiler.playthrough.items() for location in sphere]: spoiler_locations[location.name].append(location.world.id) - item_locations = list(filter(lambda location: location.world.id in spoiler_locations[location.name], item_locations)) + item_locations = list( + filter(lambda location: location.world.id in spoiler_locations[location.name], item_locations)) required_locations = [] reachable_items_locations = True - while (item_locations and reachable_items_locations): - reachable_items_locations = [location for location in all_locations if location.name not in state_list[location.world.id].collected_locations and state_list[location.world.id].can_reach(location)] + while item_locations and reachable_items_locations: + reachable_items_locations = [location for location in all_locations if + location.name not in state_list[location.world.id].collected_locations and + state_list[location.world.id].can_reach(location)] for location in reachable_items_locations: # Try to remove items one at a time and see if the game is still beatable if location in item_locations: @@ -634,7 +657,8 @@ def update_required_items(worlds): # Filter the required location to only include location in the world for world in worlds: - world.spoiler.required_locations = list(filter(lambda location: location.world.id == world.id, required_locations)) + world.spoiler.required_locations = list( + filter(lambda location: location.world.id == world.id, required_locations)) @unique @@ -729,7 +753,7 @@ class Dungeon(object): def __init__(self, name, regions, boss_key, small_keys, dungeon_items): def to_array(obj): - if obj == None: + if obj is None: return [] if isinstance(obj, list): return obj @@ -763,7 +787,8 @@ def __unicode__(self): class Location(object): - def __init__(self, name='', address=None, address2=None, default=None, type='Chest', scene=None, hint='Termina', parent=None): + def __init__(self, name='', address=None, address2=None, default=None, type='Chest', scene=None, hint='Termina', + parent=None): self.name = name self.parent_region = parent self.item = None @@ -783,7 +808,8 @@ def __init__(self, name='', address=None, address2=None, default=None, type='Che self.price = None def can_fill(self, state, item, check_access=True): - return self.always_allow(item, self) or (self.parent_region.can_fill(item) and self.item_rule(item) and (not check_access or self.can_reach(state))) + return self.always_allow(item, self) or (self.parent_region.can_fill(item) and self.item_rule(item) and ( + not check_access or self.can_reach(state))) def can_fill_fast(self, item): return self.item_rule(item) @@ -802,7 +828,8 @@ def __unicode__(self): class Item(object): - def __init__(self, name='', advancement=False, priority=False, type=None, code=None, index=None, object=None, model=None): + def __init__(self, name='', advancement=False, priority=False, type=None, code=None, index=None, object=None, + model=None): self.name = name self.advancement = advancement self.priority = priority @@ -844,7 +871,6 @@ def compass(self): @property def dungeonitem(self): return self.type == 'SmallKey' or self.type == 'BossKey' or self.type == 'Map' or self.type == 'Compass' - def __str__(self): return str(self.__unicode__()) @@ -868,35 +894,51 @@ def parse_data(self): sort_order = {"Song": 0, "Boss": -1} spoiler_locations.sort(key=lambda item: sort_order.get(item.type, 1)) if self.world.settings.world_count > 1: - self.locations = {'other locations': OrderedDict([(str(location), "%s [Player %d]" % (str(location.item), location.item.world.id + 1) if location.item is not None else 'Nothing') for location in spoiler_locations])} + self.locations = {'other locations': OrderedDict([(str(location), "%s [Player %d]" % ( + str(location.item), location.item.world.id + 1) if location.item is not None else 'Nothing') for + location in + spoiler_locations])} else: - self.locations = {'other locations': OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in spoiler_locations])} + self.locations = {'other locations': OrderedDict( + [(str(location), str(location.item) if location.item is not None else 'Nothing') for location in + spoiler_locations])} self.settings = self.world.settings def to_file(self, filename): self.parse_data() with open(filename, 'w') as outfile: outfile.write('OoT Randomizer Version %s - Seed: %s\n\n' % (self.version, self.settings.seed)) - outfile.write('Settings (%s):\n%s' % (self.settings.get_settings_string(), self.settings.get_settings_display())) + outfile.write( + 'Settings (%s):\n%s' % (self.settings.get_settings_string(), self.settings.get_settings_display())) if self.settings.world_count > 1: - outfile.write('\n\nLocations [World %d]:\n\n' % (self.settings.player_num)) + outfile.write('\n\nLocations [World %d]:\n\n' % self.settings.player_num) else: outfile.write('\n\nLocations:\n\n') - outfile.write('\n'.join(['%s: %s' % (location, item) for (location, item) in self.locations['other locations'].items()])) + outfile.write('\n'.join( + ['%s: %s' % (location, item) for (location, item) in self.locations['other locations'].items()])) outfile.write('\n\nPlaythrough:\n\n') if self.settings.world_count > 1: - outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s [World %d]: %s [Player %d]' % (location.name, location.world.id + 1, item.name, item.world.id + 1) for (location, item) in sphere.items()])) for (sphere_nr, sphere) in self.playthrough.items()])) + outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s [World %d]: %s [Player %d]' % ( + location.name, location.world.id + 1, item.name, item.world.id + 1) for (location, item) in + sphere.items()])) for + (sphere_nr, sphere) in self.playthrough.items()])) else: - outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location.name, item.name) for (location, item) in sphere.items()])) for (sphere_nr, sphere) in self.playthrough.items()])) + outfile.write('\n'.join(['%s: {\n%s\n}' % ( + sphere_nr, + '\n'.join([' %s: %s' % (location.name, item.name) for (location, item) in sphere.items()])) + for (sphere_nr, sphere) in self.playthrough.items()])) if len(self.hints) > 0: outfile.write('\n\nAlways Required Locations:\n\n') if self.settings.world_count > 1: - outfile.write('\n'.join(['%s: %s [Player %d]' % (location.name, location.item.name, location.item.world.id + 1) for location in self.required_locations])) + outfile.write('\n'.join( + ['%s: %s [Player %d]' % (location.name, location.item.name, location.item.world.id + 1) for + location in self.required_locations])) else: - outfile.write('\n'.join(['%s: %s' % (location.name, location.item.name) for location in self.required_locations])) + outfile.write('\n'.join( + ['%s: %s' % (location.name, location.item.name) for location in self.required_locations])) outfile.write('\n\nGossip Stone Hints:\n\n') outfile.write('\n'.join(self.hints.values())) diff --git a/BetterOoT.py b/BetterOoT.py index 635f710..7d5af6b 100644 --- a/BetterOoT.py +++ b/BetterOoT.py @@ -1,16 +1,14 @@ #!/usr/bin/env python3 import argparse -import os import logging -import random -import textwrap +import os import sys +import textwrap from Gui import guiMain from Main import main -from Utils import is_bundled, close_console -from Patches import get_tunic_color_options, get_navi_color_options from Settings import get_settings_from_command_line_args +from Utils import is_bundled, close_console class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): @@ -20,7 +18,6 @@ def _get_help_string(self, action): def start(): - settings, gui, args_loglevel = get_settings_from_command_line_args() if is_bundled() and len(sys.argv) == 1: @@ -34,11 +31,13 @@ def start(): # ToDo: Validate files further than mere existance if not os.path.isfile(settings.rom): - input('Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % settings.rom) + input( + 'Could not find valid base rom for patching at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % settings.rom) sys.exit(1) # set up logger - loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args_loglevel] + loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[ + args_loglevel] logging.basicConfig(format='%(message)s', level=loglevel) logger = logging.getLogger('') @@ -53,5 +52,6 @@ def start(): else: main(settings) + if __name__ == '__main__': - start() \ No newline at end of file + start() diff --git a/Gui.py b/Gui.py index b526aac..66ebeb2 100644 --- a/Gui.py +++ b/Gui.py @@ -1,22 +1,18 @@ #!/usr/bin/env python3 -from argparse import Namespace -from glob import glob import json -import random -import re import os -import shutil -from tkinter import Scale, Checkbutton, OptionMenu, Toplevel, LabelFrame, Radiobutton, PhotoImage, Tk, BOTH, LEFT, RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, E, X, N, S, NW, Entry, Spinbox, Button, filedialog, messagebox, ttk, HORIZONTAL, Toplevel +import re +import webbrowser +from tkinter import Scale, Checkbutton, LabelFrame, Radiobutton, Tk, BOTH, LEFT, \ + RIGHT, BOTTOM, TOP, StringVar, IntVar, Frame, Label, W, N, NW, Entry, Spinbox, Button, filedialog, \ + messagebox, ttk, HORIZONTAL from tkinter.colorchooser import * -from urllib.parse import urlparse -from urllib.request import urlopen -from GuiUtils import ToolTips, set_icon, BackgroundTask, BackgroundTaskProgress, Dialog +from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress from Main import main -from Utils import is_bundled, local_path, default_output_path, open_file -from Patches import get_tunic_color_options, get_navi_color_options from Settings import Settings, setting_infos -import webbrowser +from Utils import local_path, default_output_path, open_file + def settings_to_guivars(settings, guivars): for info in setting_infos: @@ -24,30 +20,31 @@ def settings_to_guivars(settings, guivars): if name not in guivars: continue guivar = guivars[name] - value = settings.__dict__[name] + value = settings.__dict__[name] # checkbox if info.type == bool: - guivar.set( int(value) ) + guivar.set(int(value)) # dropdown/radiobox if info.type == str: if value is None: - guivar.set( "" ) + guivar.set("") else: if info.gui_params and 'options' in info.gui_params: if 'Custom Color' in info.gui_params['options'] and re.match(r'^[A-Fa-f0-9]{6}$', value): guivar.set('Custom (#' + value + ')') else: - for gui_text,gui_value in info.gui_params['options'].items(): + for gui_text, gui_value in info.gui_params['options'].items(): if gui_value == value: - guivar.set( gui_text ) + guivar.set(gui_text) else: - guivar.set( value ) + guivar.set(value) # text field for a number... if info.type == int: if value is None: - guivar.set( str(1) ) + guivar.set(str(1)) else: - guivar.set( str(value) ) + guivar.set(str(value)) + def guivars_to_settings(guivars): result = {} @@ -71,7 +68,7 @@ def guivars_to_settings(guivars): result[name] = guivar.get() # text field for a number... if info.type == int: - result[name] = int( guivar.get() ) + result[name] = int(guivar.get()) if result['seed'] == "": result['seed'] = None if result['count'] == 1: @@ -79,6 +76,7 @@ def guivars_to_settings(guivars): return Settings(result) + def guiMain(settings=None): frames = {} @@ -99,8 +97,8 @@ def guiMain(settings=None): adjustWindow = ttk.Frame(notebook) customWindow = ttk.Frame(notebook) notebook.add(frames['rom_tab'], text='ROM') - #notebook.add(frames['rules_tab'], text='Main Rules') - #notebook.add(frames['logic_tab'], text='Detailed Logic') + # notebook.add(frames['rules_tab'], text='Main Rules') + # notebook.add(frames['logic_tab'], text='Detailed Logic') notebook.add(frames['other_tab'], text='Options') notebook.add(frames['aesthetic_tab'], text='Cosmetics') @@ -114,27 +112,26 @@ def guiMain(settings=None): # hierarchy ############ - #Rules Tab - frames['open'] = LabelFrame(frames['rules_tab'], text='Open', labelanchor=NW) - frames['world'] = LabelFrame(frames['rules_tab'], text='World', labelanchor=NW) - frames['logic'] = LabelFrame(frames['rules_tab'], text='Shuffle', labelanchor=NW) + # Rules Tab + frames['open'] = LabelFrame(frames['rules_tab'], text='Open', labelanchor=NW) + frames['world'] = LabelFrame(frames['rules_tab'], text='World', labelanchor=NW) + frames['logic'] = LabelFrame(frames['rules_tab'], text='Shuffle', labelanchor=NW) # Logic tab frames['rewards'] = LabelFrame(frames['logic_tab'], text='Remove Specific Locations', labelanchor=NW) - frames['tricks'] = LabelFrame(frames['logic_tab'], text='Specific expected tricks', labelanchor=NW) + frames['tricks'] = LabelFrame(frames['logic_tab'], text='Specific expected tricks', labelanchor=NW) - #Other Tab + # Other Tab frames['convenience'] = LabelFrame(frames['other_tab'], text='Remove Area Intro Cutscenes', labelanchor=NW) - frames['other'] = LabelFrame(frames['other_tab'], text='Settings', labelanchor=NW) + frames['other'] = LabelFrame(frames['other_tab'], text='Settings', labelanchor=NW) - #Aesthetics tab + # Aesthetics tab frames['cosmetics'] = LabelFrame(frames['aesthetic_tab'], text='General', labelanchor=NW) frames['tuniccolor'] = LabelFrame(frames['aesthetic_tab_left'], text='Tunic Color', labelanchor=NW) frames['swordcolor'] = LabelFrame(frames['aesthetic_tab_left'], text='Sword Color', labelanchor=NW) - frames['navicolor'] = LabelFrame(frames['aesthetic_tab_right'], text='Navi Color', labelanchor=NW) - frames['lowhp'] = LabelFrame(frames['aesthetic_tab_left'], text='Low HP SFX', labelanchor=NW) - frames['navihint'] = LabelFrame(frames['aesthetic_tab_right'], text='Navi SFX', labelanchor=NW) - + frames['navicolor'] = LabelFrame(frames['aesthetic_tab_right'], text='Navi Color', labelanchor=NW) + frames['lowhp'] = LabelFrame(frames['aesthetic_tab_left'], text='Low HP SFX', labelanchor=NW) + frames['navihint'] = LabelFrame(frames['aesthetic_tab_right'], text='Navi SFX', labelanchor=NW) # shared settingsFrame = Frame(mainWindow) @@ -143,8 +140,8 @@ def guiMain(settings=None): def show_settings(event=None): settings = guivars_to_settings(guivars) - settings_string_var.set( settings.get_settings_string() ) - + settings_string_var.set(settings.get_settings_string()) + # Update any dependencies for info in setting_infos: if info.gui_params and 'dependency' in info.gui_params: @@ -153,26 +150,25 @@ def show_settings(event=None): if widgets[info.name].winfo_class() == 'Frame': for child in widgets[info.name].winfo_children(): if child.winfo_class() == 'TCombobox': - child.configure(state= 'readonly' if dep_met else 'disabled') + child.configure(state='readonly' if dep_met else 'disabled') else: - child.configure(state= 'normal' if dep_met else 'disabled') + child.configure(state='normal' if dep_met else 'disabled') if child.winfo_class() == 'Scale': - child.configure(fg='Black'if dep_met else 'Grey') + child.configure(fg='Black' if dep_met else 'Grey') else: if widgets[info.name].winfo_class() == 'TCombobox': - widgets[info.name].configure(state= 'readonly' if dep_met else 'disabled') + widgets[info.name].configure(state='readonly' if dep_met else 'disabled') else: - widgets[info.name].configure(state= 'normal' if dep_met else 'disabled') + widgets[info.name].configure(state='normal' if dep_met else 'disabled') if widgets[info.name].winfo_class() == 'Scale': - widgets[info.name].configure(fg='Black'if dep_met else 'Grey') + widgets[info.name].configure(fg='Black' if dep_met else 'Grey') - if info.name in guivars and guivars[info.name].get() == 'Custom Color': color = askcolor() if color == (None, None): - color = ((0,0,0),'#000000') + color = ((0, 0, 0), '#000000') guivars[info.name].set('Custom (' + color[1] + ')') def show_settings_special(event=None): @@ -193,9 +189,7 @@ def show_settings_special(event=None): widgets['logic_zora_with_cucco'].deselect() widgets['logic_fewer_tunic_requirements'].deselect() settings = guivars_to_settings(guivars) - settings_string_var.set( settings.get_settings_string() ) - - + settings_string_var.set(settings.get_settings_string()) def import_settings(event=None): try: @@ -212,7 +206,7 @@ def import_settings(event=None): settingsEntry.pack(side=LEFT, anchor=W) importSettingsButton.pack(side=LEFT, anchor=W, padx=5) - #Import ROM + # Import ROM fileDialogFrame = Frame(frames['rom_tab']) @@ -225,17 +219,18 @@ def RomSelect(): rom = filedialog.askopenfilename(filetypes=[("ROM Files", (".z64", ".n64")), ("All Files", "*")]) if rom != '': guivars['rom'].set(rom) + romSelectButton = Button(romDialogFrame, text='Select ROM', command=RomSelect, width=10) - baseRomLabel.pack(side=LEFT, padx=(38,0)) + baseRomLabel.pack(side=LEFT, padx=(38, 0)) romEntry.pack(side=LEFT, padx=3) romSelectButton.pack(side=LEFT) romDialogFrame.pack() - fileDialogFrame.pack(side=TOP, anchor=W, padx=5, pady=(5,1)) + fileDialogFrame.pack(side=TOP, anchor=W, padx=5, pady=(5, 1)) - #Import WAD + # Import WAD fileDialogFrame = Frame(frames['rom_tab']) wadDialogFrame = Frame(fileDialogFrame) @@ -244,25 +239,22 @@ def RomSelect(): wadEntry = Entry(wadDialogFrame, textvariable=guivars['wad'], width=40) def wadSelect(): - wad = filedialog.askopenfilename(filetypes=[("WAD Files", (".wad")), ("All Files", "*")]) + wad = filedialog.askopenfilename(filetypes=[("WAD Files", ".wad"), ("All Files", "*")]) if wad != '': guivars['wad'].set(wad) + wadSelectButton = Button(wadDialogFrame, text='Select WAD', command=wadSelect, width=10) - baseWadLabel.pack(side=LEFT, padx=(38,0)) + baseWadLabel.pack(side=LEFT, padx=(38, 0)) wadEntry.pack(side=LEFT, padx=3) wadSelectButton.pack(side=LEFT) wadDialogFrame.pack() - fileDialogFrame.pack(side=TOP, anchor=W, padx=5, pady=(5,1)) + fileDialogFrame.pack(side=TOP, anchor=W, padx=5, pady=(5, 1)) - #Output dir - def open_output(): - open_file(output_path('')) - def output_dir_select(): - rom = filedialog.askdirectory(initialdir = default_output_path(guivars['output_dir'].get())) + rom = filedialog.askdirectory(initialdir=default_output_path(guivars['output_dir'].get())) if rom != '': guivars['output_dir'].set(rom) @@ -271,16 +263,15 @@ def output_dir_select(): guivars['output_dir'] = StringVar(value='') outputDirEntry = Entry(outputDialogFrame, textvariable=guivars['output_dir'], width=40) outputDirButton = Button(outputDialogFrame, text='Select Dir', command=output_dir_select, width=10) - outputDirLabel.pack(side=LEFT, padx=(3,0)) + outputDirLabel.pack(side=LEFT, padx=(3, 0)) outputDirEntry.pack(side=LEFT, padx=3) outputDirButton.pack(side=LEFT) - outputDialogFrame.pack(side=TOP, anchor=W, padx=5, pady=(5,1)) - - + outputDialogFrame.pack(side=TOP, anchor=W, padx=5, pady=(5, 1)) if os.path.exists(local_path('README.html')): def open_readme(): open_file(local_path('README.html')) + openReadmeButton = Button(outputDialogFrame, text='Open Documentation', command=open_readme) openReadmeButton.pack(side=LEFT, padx=5) @@ -293,8 +284,7 @@ def open_readme(): countLabel.pack(side=LEFT) countSpinbox.pack(side=LEFT, padx=2) - #countDialogFrame.pack(side=TOP, anchor=W, padx=5, pady=(1,1)) - + # countDialogFrame.pack(side=TOP, anchor=W, padx=5, pady=(1,1)) # build gui ############ @@ -309,13 +299,14 @@ def open_readme(): # create a variable to access the box's state guivars[info.name] = IntVar(value=default_value) # create the checkbox - widgets[info.name] = Checkbutton(frames[info.gui_params['group']], text=info.gui_params['text'], variable=guivars[info.name], justify=LEFT, wraplength=190, command=show_settings) - - - #Hardcode in checkboxes I want shown (messy af but who cares) - #to put on left flags tab, change type in settings info to "convenience" - #to put on right settings tab, change type in settings info to "other" - #the order they will appear is how they are ordered in settings_info + widgets[info.name] = Checkbutton(frames[info.gui_params['group']], text=info.gui_params['text'], + variable=guivars[info.name], justify=LEFT, wraplength=190, + command=show_settings) + + # Hardcode in checkboxes I want shown (messy af but who cares) + # to put on left flags tab, change type in settings info to "convenience" + # to put on right settings tab, change type in settings info to "other" + # the order they will appear is how they are ordered in settings_info if info.name == 'fast_chests': widgets[info.name].pack(expand=False, anchor=W) if info.name == 'no_owls': @@ -331,7 +322,7 @@ def open_readme(): if info.name == 'knuckle_cs': widgets[info.name].pack(expand=False, anchor=W) - #area intro cutscenes + # area intro cutscenes if info.name == 'skip_deku': widgets[info.name].pack(expand=False, anchor=W) if info.name == 'skip_gc': @@ -371,14 +362,15 @@ def open_readme(): if info.name == 'fast_elevator': widgets[info.name].pack(expand=False, anchor=W) - if info.gui_params['widget'] == 'SpecialCheckbutton': # determine the initial value of the checkbox default_value = 1 if info.gui_params['default'] == "checked" else 0 # create a variable to access the box's state guivars[info.name] = IntVar(value=default_value) # create the checkbox - widgets[info.name] = Checkbutton(frames[info.gui_params['group']], text=info.gui_params['text'], variable=guivars[info.name], justify=LEFT, wraplength=190, command=show_settings_special) + widgets[info.name] = Checkbutton(frames[info.gui_params['group']], text=info.gui_params['text'], + variable=guivars[info.name], justify=LEFT, wraplength=190, + command=show_settings_special) widgets[info.name].pack(expand=False, anchor=W) elif info.gui_params['widget'] == 'Combobox': @@ -389,7 +381,8 @@ def open_readme(): # dropdown = OptionMenu(widgets[info.name], guivars[info.name], *(info['options'])) if isinstance(info.gui_params['options'], list): info.gui_params['options'] = dict(zip(info.gui_params['options'], info.gui_params['options'])) - dropdown = ttk.Combobox(widgets[info.name], textvariable=guivars[info.name], values=list(info.gui_params['options'].keys()), state='readonly', width=30) + dropdown = ttk.Combobox(widgets[info.name], textvariable=guivars[info.name], + values=list(info.gui_params['options'].keys()), state='readonly', width=30) dropdown.bind("<>", show_settings) dropdown.pack(side=BOTTOM, anchor=W) # label the option @@ -404,12 +397,14 @@ def open_readme(): else: widgets[info.name].pack(expand=False, side=TOP, anchor=W, padx=3, pady=3) - + elif info.gui_params['widget'] == 'Radiobutton': # create the variable to store the user's decision guivars[info.name] = StringVar(value=info.gui_params['default']) # create the option menu - widgets[info.name] = LabelFrame(frames[info.gui_params['group']], text=info.gui_params['text'] if 'text' in info.gui_params else info["name"], labelanchor=NW) + widgets[info.name] = LabelFrame(frames[info.gui_params['group']], + text=info.gui_params['text'] if 'text' in info.gui_params else info[ + "name"], labelanchor=NW) if isinstance(info.gui_params['options'], list): info.gui_params['options'] = dict(zip(info.gui_params['options'], info.gui_params['options'])) # setup orientation @@ -420,12 +415,14 @@ def open_readme(): anchor = N # add the radio buttons for option in info.gui_params["options"]: - radio_button = Radiobutton(widgets[info.name], text=option, value=option, variable=guivars[info.name], justify=LEFT, wraplength=190, indicatoron=False, command=show_settings) + radio_button = Radiobutton(widgets[info.name], text=option, value=option, + variable=guivars[info.name], justify=LEFT, wraplength=190, + indicatoron=False, command=show_settings) radio_button.pack(expand=True, side=side, anchor=anchor) # pack the frame if info.name == 'create_wad': widgets[info.name].pack(expand=False, side=TOP, anchor=W, padx=10, pady=5) - + elif info.gui_params['widget'] == 'Scale': # create the variable to store the user's decision guivars[info.name] = IntVar(value=info.gui_params['default']) @@ -435,9 +432,11 @@ def open_readme(): minval = 'min' in info.gui_params and info.gui_params['min'] or 0 maxval = 'max' in info.gui_params and info.gui_params['max'] or 100 stepval = 'step' in info.gui_params and info.gui_params['step'] or 1 - scale = Scale(widgets[info.name], variable=guivars[info.name], from_=minval, to=maxval, tickinterval=stepval, resolution=stepval, showvalue=0, orient=HORIZONTAL, sliderlength=15, length=200, command=show_settings) - - #scale.pack(side=BOTTOM, anchor=W) + scale = Scale(widgets[info.name], variable=guivars[info.name], from_=minval, to=maxval, + tickinterval=stepval, resolution=stepval, showvalue=0, orient=HORIZONTAL, sliderlength=15, + length=200, command=show_settings) + + # scale.pack(side=BOTTOM, anchor=W) # label the option if 'text' in info.gui_params: label = Label(widgets[info.name], text=info.gui_params['text']) @@ -458,51 +457,51 @@ def open_readme(): label = Label(widgets[info.name], text=info.gui_params['text']) label.pack(side=LEFT, anchor=W, padx=5) # pack the frame - - #widgets[info.name].pack(expand=False, side=TOP, anchor=W, padx=3, pady=3) + + # widgets[info.name].pack(expand=False, side=TOP, anchor=W, padx=3, pady=3) if 'tooltip' in info.gui_params: ToolTips.register(widgets[info.name], info.gui_params['tooltip']) # pack the hierarchy - frames['logic'].pack( fill=BOTH, expand=True, anchor=N, side=RIGHT, pady=(5,1) ) - frames['open'].pack( fill=BOTH, expand=True, anchor=W, side=TOP, pady=(5,1) ) - frames['world'].pack( fill=BOTH, expand=True, anchor=W, side=BOTTOM, pady=(5,1) ) + frames['logic'].pack(fill=BOTH, expand=True, anchor=N, side=RIGHT, pady=(5, 1)) + frames['open'].pack(fill=BOTH, expand=True, anchor=W, side=TOP, pady=(5, 1)) + frames['world'].pack(fill=BOTH, expand=True, anchor=W, side=BOTTOM, pady=(5, 1)) # Logic tab - frames['rewards'].pack(fill=BOTH, expand=True, anchor=N, side=LEFT, pady=(5,1) ) - frames['tricks'].pack( fill=BOTH, expand=True, anchor=N, side=LEFT, pady=(5,1) ) + frames['rewards'].pack(fill=BOTH, expand=True, anchor=N, side=LEFT, pady=(5, 1)) + frames['tricks'].pack(fill=BOTH, expand=True, anchor=N, side=LEFT, pady=(5, 1)) - #Other Tab - frames['convenience'].pack(fill=BOTH, expand=True, anchor=N, side=LEFT, pady=(5,1) ) - frames['other'].pack( fill=BOTH, expand=True, anchor=N, side=LEFT, pady=(5,1) ) + # Other Tab + frames['convenience'].pack(fill=BOTH, expand=True, anchor=N, side=LEFT, pady=(5, 1)) + frames['other'].pack(fill=BOTH, expand=True, anchor=N, side=LEFT, pady=(5, 1)) - #Aesthetics tab + # Aesthetics tab frames['cosmetics'].pack(fill=BOTH, expand=True, anchor=W, side=TOP) - frames['aesthetic_tab_left'].pack( fill=BOTH, expand=True, anchor=W, side=LEFT) + frames['aesthetic_tab_left'].pack(fill=BOTH, expand=True, anchor=W, side=LEFT) frames['aesthetic_tab_right'].pack(fill=BOTH, expand=True, anchor=W, side=RIGHT) - #Aesthetics tab - Left Side - frames['tuniccolor'].pack(fill=BOTH, expand=True, anchor=W, side=TOP, pady=(5,1) ) - frames['swordcolor'].pack(fill=BOTH, expand=True, anchor=W, side=TOP, pady=(5,1) ) - frames['lowhp'].pack( fill=BOTH, expand=True, anchor=W, side=TOP, pady=(5,1) ) + # Aesthetics tab - Left Side + frames['tuniccolor'].pack(fill=BOTH, expand=True, anchor=W, side=TOP, pady=(5, 1)) + frames['swordcolor'].pack(fill=BOTH, expand=True, anchor=W, side=TOP, pady=(5, 1)) + frames['lowhp'].pack(fill=BOTH, expand=True, anchor=W, side=TOP, pady=(5, 1)) - #Aesthetics tab - Right Side - frames['navicolor'].pack( fill=BOTH, expand=True, anchor=W, side=TOP, pady=(5,1) ) - frames['navihint'].pack( fill=BOTH, expand=True, anchor=W, side=TOP, pady=(5,1) ) + # Aesthetics tab - Right Side + frames['navicolor'].pack(fill=BOTH, expand=True, anchor=W, side=TOP, pady=(5, 1)) + frames['navihint'].pack(fill=BOTH, expand=True, anchor=W, side=TOP, pady=(5, 1)) - notebook.pack(fill=BOTH, expand=True, padx=5, pady=5) multiworldFrame = LabelFrame(frames['rom_tab'], text='Multi-World Generation') - countLabel = Label(multiworldFrame, wraplength=350, justify=LEFT, text='This is used for co-op generations. Increasing Player Count will drastically increase the generation time. For more information see:') - hyperLabel = Label(multiworldFrame, wraplength=350, justify=LEFT, text='https://github.com/TestRunnerSRL/bizhawk-co-op', fg='blue', cursor='hand2') + countLabel = Label(multiworldFrame, wraplength=350, justify=LEFT, + text='This is used for co-op generations. Increasing Player Count will drastically increase the generation time. For more information see:') + hyperLabel = Label(multiworldFrame, wraplength=350, justify=LEFT, + text='https://github.com/TestRunnerSRL/bizhawk-co-op', fg='blue', cursor='hand2') hyperLabel.bind("", lambda event: webbrowser.open_new(r"https://github.com/TestRunnerSRL/bizhawk-co-op")) countLabel.pack(side=TOP, anchor=W, padx=5, pady=0) hyperLabel.pack(side=TOP, anchor=W, padx=5, pady=0) - worldCountFrame = Frame(multiworldFrame) countLabel = Label(worldCountFrame, text='Player Count') guivars['world_count'] = StringVar() @@ -510,7 +509,7 @@ def open_readme(): countLabel.pack(side=LEFT) countSpinbox.pack(side=LEFT, padx=2) - #worldCountFrame.pack(side=LEFT, anchor=N, padx=10, pady=(1,5)) + # worldCountFrame.pack(side=LEFT, anchor=N, padx=10, pady=(1,5)) playerNumFrame = Frame(multiworldFrame) countLabel = Label(playerNumFrame, text='Player ID') @@ -519,7 +518,7 @@ def open_readme(): countLabel.pack(side=LEFT) countSpinbox.pack(side=LEFT, padx=2) - playerNumFrame.pack(side=LEFT, anchor=N, padx=10, pady=(1,5)) + playerNumFrame.pack(side=LEFT, anchor=N, padx=10, pady=(1, 5)) # create the option menu def generateRom(): @@ -529,11 +528,10 @@ def generateRom(): else: BackgroundTaskProgress(mainWindow, "Patching File", main, settings) - generateButton = Button(settingsFrame, text='Generate Patched File', command=generateRom) generateButton.pack(side=LEFT, padx=(5, 0)) - - settingsFrame.pack(fill=BOTH, anchor=W, padx=5, pady=(10,10)) + + settingsFrame.pack(fill=BOTH, anchor=W, padx=5, pady=(10, 10)) def multiple_run(settings, window): orig_seed = settings.seed @@ -542,9 +540,6 @@ def multiple_run(settings, window): window.update_title("Patching ROM") main(settings, window) - - - guivars['checked_version'] = StringVar() if settings is not None: @@ -555,7 +550,7 @@ def multiple_run(settings, window): try: settingsFile = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'settings.sav') with open(settingsFile) as f: - settings = Settings( json.load(f) ) + settings = Settings(json.load(f)) settings.update_seed("") settings_to_guivars(settings, guivars) except: @@ -570,5 +565,6 @@ def multiple_run(settings, window): settings = guivars_to_settings(guivars) json.dump(settings.__dict__, outfile) + if __name__ == '__main__': guiMain() diff --git a/GuiUtils.py b/GuiUtils.py index 3ce0a81..12b52b4 100644 --- a/GuiUtils.py +++ b/GuiUtils.py @@ -5,11 +5,12 @@ from Utils import local_path + def set_icon(window): er16 = tk.PhotoImage(file=local_path('data/oot16.gif')) er32 = tk.PhotoImage(file=local_path('data/oot32.gif')) er48 = tk.PhotoImage(file=local_path('data/oot32.gif')) - window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48) # pylint: disable=protected-access + window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48) # pylint: disable=protected-access # Although tkinter is intended to be thread safe, there are many reports of issues @@ -39,7 +40,7 @@ def update_status(self, text): def stop(self): self.running = False - #safe to call from worker + # safe to call from worker def queue_event(self, event): self.queue.put(event) @@ -51,7 +52,7 @@ def process_queue(self): event = self.queue.get_nowait() event() if self.running: - #if self is no longer running self.window may no longer be valid + # if self is no longer running self.window may no longer be valid self.window.update_idletasks() except queue.Empty: pass @@ -83,7 +84,8 @@ def __init__(self, parent, title, code_to_run, *code_arg): self.label.pack() self.button_var = tk.StringVar(value="Please wait...") - self.button = tk.Button(self.window, textvariable=self.button_var, width=10, height=2, state='disabled', command=self.close) + self.button = tk.Button(self.window, textvariable=self.button_var, width=10, height=2, state='disabled', + command=self.close) self.button.pack() self.window.resizable(width=False, height=False) @@ -92,7 +94,7 @@ def __init__(self, parent, title, code_to_run, *code_arg): self.window.transient(parent) self.window.protocol("WM_DELETE_WINDOW", self.close_pass) self.window.grab_set() - self.window.geometry("+%d+%d" % (parent.winfo_rootx()+50, parent.winfo_rooty()+150)) + self.window.geometry("+%d+%d" % (parent.winfo_rootx() + 50, parent.winfo_rooty() + 150)) self.window.focus_set() super().__init__(self.window, code_to_run, *tuple(list(code_arg) + [self])) @@ -102,7 +104,7 @@ def __init__(self, parent, title, code_to_run, *code_arg): def close_pass(self): pass - #safe to call from worker thread + # safe to call from worker thread def update_status(self, text): self.queue_event(lambda: self.label_var.set(text)) @@ -155,8 +157,8 @@ def __init__(self, parent, title=None, question=None, oktext=None, canceltext=No self.grab_set() self.protocol("WM_DELETE_WINDOW", self.cancel) - self.geometry("+%d+%d" % (parent.winfo_rootx()+50, - parent.winfo_rooty()+150)) + self.geometry("+%d+%d" % (parent.winfo_rootx() + 50, + parent.winfo_rooty() + 150)) self.wait_window(self) diff --git a/MQ.py b/MQ.py index cdfc283..ea4533f 100644 --- a/MQ.py +++ b/MQ.py @@ -43,11 +43,12 @@ # the floor map data is missing a vertex pointer that would point within kaleido_scope. # As such, if the file moves, the patch will break. -from Utils import local_path -from Rom import LocalRom import json from struct import pack, unpack +from Rom import LocalRom +from Utils import local_path + SCENE_TABLE = 0xB71440 @@ -70,12 +71,12 @@ def __repr__(self): remap = "{0:x}".format(self.remap) return "{0}: {1:x} {2:x}, remap {3}".format(self.name, self.start, self.end, remap) - def relocate(self, rom:LocalRom): + def relocate(self, rom: LocalRom): if self.remap is None: return new_start = self.remap - + offset = new_start - self.start new_end = self.end + offset @@ -85,14 +86,14 @@ def relocate(self, rom:LocalRom): class CollisionMesh(object): - def __init__(self, rom:LocalRom, start, offset): + def __init__(self, rom: LocalRom, start, offset): self.offset = offset self.poly_addr = rom.read_int32(start + offset + 0x18) self.polytypes_addr = rom.read_int32(start + offset + 0x1C) self.camera_data_addr = rom.read_int32(start + offset + 0x20) self.polytypes = (self.poly_addr - self.polytypes_addr) // 8 - def write_to_scene(self, rom:LocalRom, start): + def write_to_scene(self, rom: LocalRom, start): addr = start + self.offset + 0x18 rom.write_int32s(addr, [self.poly_addr, self.polytypes_addr, self.camera_data_addr]) @@ -107,19 +108,19 @@ def __init__(self, delta): class Icon(object): def __init__(self, data): - self.icon = data["Icon"]; - self.count = data["Count"]; + self.icon = data["Icon"] + self.count = data["Count"] self.points = [IconPoint(x) for x in data["IconPoints"]] - def write_to_minimap(self, rom:LocalRom, addr): + def write_to_minimap(self, rom: LocalRom, addr): rom.write_sbyte(addr, self.icon) - rom.write_byte(addr + 1, self.count) + rom.write_byte(addr + 1, self.count) cur = 2 for p in self.points: p.write_to_minimap(rom, addr + cur) cur += 0x03 - def write_to_floormap(self, rom:LocalRom, addr): + def write_to_floormap(self, rom: LocalRom, addr): rom.write_int16(addr, self.icon) rom.write_int32(addr + 0x10, self.count) @@ -135,12 +136,12 @@ def __init__(self, point): self.x = point["x"] self.y = point["y"] - def write_to_minimap(self, rom:LocalRom, addr): + def write_to_minimap(self, rom: LocalRom, addr): rom.write_sbyte(addr, self.flag) - rom.write_byte(addr+1, self.x) - rom.write_byte(addr+2, self.y) + rom.write_byte(addr + 1, self.x) + rom.write_byte(addr + 2, self.y) - def write_to_floormap(self, rom:LocalRom, addr): + def write_to_floormap(self, rom: LocalRom, addr): rom.write_int16(addr, self.flag) rom.write_f32(addr + 4, float(self.x)) rom.write_f32(addr + 8, float(self.y)) @@ -160,9 +161,8 @@ def __init__(self, scene): for item in temp_paths: self.paths.append(item['Points']) + def write_data(self, rom: LocalRom): - def write_data(self, rom:LocalRom): - # write floormap and minimap data self.write_map_data(rom) @@ -176,22 +176,22 @@ def write_data(self, rom:LocalRom): code = rom.read_byte(headcur) loop = 0x20 - while loop > 0 and code != 0x14: #terminator + while loop > 0 and code != 0x14: # terminator loop -= 1 - if code == 0x03: #collision + if code == 0x03: # collision col_mesh_offset = rom.read_int24(headcur + 5) col_mesh = CollisionMesh(rom, start, col_mesh_offset) - self.patch_mesh(rom, col_mesh); + self.patch_mesh(rom, col_mesh) - elif code == 0x04: #rooms + elif code == 0x04: # rooms room_list_offset = rom.read_int24(headcur + 5) - elif code == 0x0D: #paths + elif code == 0x0D: # paths path_offset = self.append_path_data(rom) rom.write_int32(headcur + 4, path_offset) - elif code == 0x0E: #transition actors + elif code == 0x0E: # transition actors t_offset = rom.read_int24(headcur + 5) addr = self.file.start + t_offset write_actor_data(rom, addr, self.transition_actors) @@ -203,7 +203,7 @@ def write_data(self, rom:LocalRom): self.file.end = align16(self.file.end) update_dmadata(rom, self.file) update_scene_table(rom, self.id, self.file.start, self.file.end) - + # write room file data for room in self.rooms: room.write_data(rom) @@ -215,8 +215,7 @@ def write_data(self, rom:LocalRom): rom.write_int32s(cur, [room.file.start, room.file.end]) cur += 0x08 - - def write_map_data(self, rom:LocalRom): + def write_map_data(self, rom: LocalRom): if self.id >= 10: return @@ -224,7 +223,7 @@ def write_map_data(self, rom:LocalRom): floormap_indices = 0xB6C934 floormap_vrom = 0xBC7E00 floormap_index = rom.read_int16(floormap_indices + (self.id * 2)) - floormap_index //= 2 # game uses texture index, where two textures are used per floor + floormap_index //= 2 # game uses texture index, where two textures are used per floor cur = floormap_vrom + (floormap_index * 0x1EC) for floormap in self.floormaps: @@ -232,32 +231,30 @@ def write_map_data(self, rom:LocalRom): Icon.write_to_floormap(icon, rom, cur) cur += 0xA4 - # fixes jabu jabu floor B1 having no chest data if self.id == 2: cur = floormap_vrom + (0x08 * 0x1EC + 4) - kaleido_scope_chest_verts = 0x803A3DA0 # hax, should be vram 0x8082EA00 - rom.write_int32s(cur, [0x17, kaleido_scope_chest_verts, 0x04]) + kaleido_scope_chest_verts = 0x803A3DA0 # hax, should be vram 0x8082EA00 + rom.write_int32s(cur, [0x17, kaleido_scope_chest_verts, 0x04]) - # write minimaps + # write minimaps map_mark_vrom = 0xBF40D0 map_mark_vram = 0x808567F0 - map_mark_array_vram = 0x8085D2DC # ptr array in map_mark_data to minimap "marks" + map_mark_array_vram = 0x8085D2DC # ptr array in map_mark_data to minimap "marks" array_vrom = map_mark_array_vram - map_mark_vram + map_mark_vrom map_mark_scene_vram = rom.read_int32(self.id * 4 + array_vrom) mark_vrom = map_mark_scene_vram - map_mark_vram + map_mark_vrom - + cur = mark_vrom for minimap in self.minimaps: for icon in minimap: Icon.write_to_minimap(icon, rom, cur) cur += 0x26 - - def patch_mesh(self, rom:LocalRom, mesh:CollisionMesh): + def patch_mesh(self, rom: LocalRom, mesh: CollisionMesh): start = self.file.start - + final_cams = [] # build final camera data @@ -281,7 +278,7 @@ def patch_mesh(self, rom:LocalRom, mesh:CollisionMesh): self.write_cam_data(rom, self.file.end, final_cams) mesh.camera_data_addr = get_segment_address(2, self.file.end - self.file.start) self.file.end += len(final_cams) * 8 - + else: types_move_addr = mesh.camera_data_addr + (len(final_cams) * 8) @@ -290,7 +287,7 @@ def patch_mesh(self, rom:LocalRom, mesh:CollisionMesh): self.write_cam_data(rom, addr, final_cams) # if polytypes needs to be moved, do so - if (types_move_addr != mesh.polytypes_addr): + if types_move_addr != mesh.polytypes_addr: a_start = self.file.start + (mesh.polytypes_addr & 0xFFFFFF) b_start = self.file.start + (types_move_addr & 0xFFFFFF) size = mesh.polytypes * 8 @@ -313,25 +310,23 @@ def patch_mesh(self, rom:LocalRom, mesh:CollisionMesh): flags = item['Flags'] addr = self.file.start + (mesh.poly_addr & 0xFFFFFF) + (id * 0x10) - vert_bit = rom.read_byte(addr + 0x02) & 0x1F # VertexA id data + vert_bit = rom.read_byte(addr + 0x02) & 0x1F # VertexA id data rom.write_int16(addr, t) rom.write_byte(addr + 0x02, (flags << 5) + vert_bit) # Write Mesh to Scene mesh.write_to_scene(rom, self.file.start) - - def write_cam_data(self, rom:LocalRom, addr, cam_data): + def write_cam_data(self, rom: LocalRom, addr, cam_data): for item in cam_data: data, pos = item rom.write_int32s(addr, [data, pos]) addr += 8 - # appends path data to the end of the rom # returns segment address to path data - def append_path_data(self, rom:LocalRom): + def append_path_data(self, rom: LocalRom): start = self.file.start cur = self.file.end records = [] @@ -341,13 +336,13 @@ def append_path_data(self, rom:LocalRom): offset = get_segment_address(2, cur - start) records.append((nodes, offset)) - #flatten + # flatten points = [x for points in path for x in points] rom.write_int16s(cur, points) path_size = align4(len(path) * 6) cur += path_size - records_offset = get_segment_address(2, cur - start) + records_offset = get_segment_address(2, cur - start) for node, offset in records: rom.write_byte(cur, node) rom.write_int32(cur + 4, offset) @@ -364,7 +359,7 @@ def __init__(self, room): self.objects = [int(x, 16) for x in room['Objects']] self.actors = [convert_actor_data(x) for x in room['Actors']] - def write_data(self, rom:LocalRom): + def write_data(self, rom: LocalRom): # move file to remap address self.file.relocate(rom) @@ -373,10 +368,10 @@ def write_data(self, rom:LocalRom): code = rom.read_byte(headcur) loop = 0x20 - while loop != 0 and code != 0x14: #terminator + while loop != 0 and code != 0x14: # terminator loop -= 1 - if code == 0x01: # actors + if code == 0x01: # actors offset = self.file.end - self.file.start write_actor_data(rom, self.file.end, self.actors) self.file.end += len(self.actors) * 0x10 @@ -384,7 +379,7 @@ def write_data(self, rom:LocalRom): rom.write_byte(headcur + 1, len(self.actors)) rom.write_int32(headcur + 4, get_segment_address(3, offset)) - elif code == 0x0B: # objects + elif code == 0x0B: # objects offset = self.append_object_data(rom, self.objects) rom.write_byte(headcur + 1, len(self.objects)) @@ -396,9 +391,8 @@ def write_data(self, rom:LocalRom): # update file reference self.file.end = align16(self.file.end) update_dmadata(rom, self.file) - - def append_object_data(self, rom:LocalRom, objects): + def append_object_data(self, rom: LocalRom, objects): offset = self.file.end - self.file.start cur = self.file.end rom.write_int16s(cur, objects) @@ -408,8 +402,7 @@ def append_object_data(self, rom:LocalRom, objects): return offset -def patch_files(rom:LocalRom, mq_scenes:list): - +def patch_files(rom: LocalRom, mq_scenes: list): data = get_json() scenes = [Scene(x) for x in data] for scene in scenes: @@ -419,7 +412,6 @@ def patch_files(rom:LocalRom, mq_scenes:list): scene.write_data(rom) - def get_json(): with open(local_path('data/mqu.json'), 'r') as stream: data = json.load(stream) @@ -428,7 +420,7 @@ def get_json(): def convert_actor_data(str): spawn_args = str.split(" ") - return [ int(x,16) for x in spawn_args ] + return [int(x, 16) for x in spawn_args] def get_segment_address(base, offset): @@ -442,7 +434,7 @@ def patch_ice_cavern_scene_header(rom): rom.write_int32s(0x2BEB038, [0x0D000000, 0x02000000]) -def patch_spirit_temple_mq_room_6(rom:LocalRom, room_addr): +def patch_spirit_temple_mq_room_6(rom: LocalRom, room_addr): cur = room_addr actor_list_addr = 0 @@ -450,8 +442,8 @@ def patch_spirit_temple_mq_room_6(rom:LocalRom, room_addr): # scan for actor list and header end code = rom.read_byte(cur) - while code != 0x14: #terminator - if code == 0x01: # actors + while code != 0x14: # terminator + if code == 0x01: # actors actor_list_addr = rom.read_int32(cur + 4) cmd_actors_offset = cur - room_addr @@ -467,13 +459,13 @@ def patch_spirit_temple_mq_room_6(rom:LocalRom, room_addr): alt_data_off = header_size + 8 # set new alternate header offset - alt_header_off = align16(alt_data_off + (4 * 3)) # alt header record size * num records + alt_header_off = align16(alt_data_off + (4 * 3)) # alt header record size * num records # write alternate header data # the first 3 words are mandatory. the last 3 are just to make the binary # cleaner to read rom.write_int32s(room_addr + alt_data_off, - [0, get_segment_address(3, alt_header_off), 0, 0, 0, 0]) + [0, get_segment_address(3, alt_header_off), 0, 0, 0, 0]) # clone header a_start = room_addr @@ -482,13 +474,13 @@ def patch_spirit_temple_mq_room_6(rom:LocalRom, room_addr): b_end = b_start + header_size rom.buffer[b_start:b_end] = rom.buffer[a_start:a_end] - + # make the child header skip the first actor, # which avoids the spawning of the block while in the hole cmd_addr = room_addr + cmd_actors_offset actor_list_addr += 0x10 actors = rom.read_byte(cmd_addr + 1) - rom.write_byte(cmd_addr+1, actors - 1) + rom.write_byte(cmd_addr + 1, actors - 1) rom.write_int32(cmd_addr + 4, actor_list_addr) # move header @@ -500,11 +492,12 @@ def patch_spirit_temple_mq_room_6(rom:LocalRom, room_addr): def verify_remap(scenes): - def test_remap(file:File): + def test_remap(file: File): if file.remap is not None: if file.start < file.remap: return False return True + print("test code: verify remap won't corrupt data") for scene in scenes: @@ -518,26 +511,30 @@ def test_remap(file:File): print("{0} - {1}".format(result, file)) -def update_dmadata(rom:LocalRom, file:File): +def update_dmadata(rom: LocalRom, file: File): key, start, end = file.dma_key, file.start, file.end rom.update_dmadata_record(key, start, end) -def update_scene_table(rom:LocalRom, sceneId, start, end): + +def update_scene_table(rom: LocalRom, sceneId, start, end): cur = sceneId * 0x14 + SCENE_TABLE rom.write_int32s(cur, [start, end]) -def write_actor_data(rom:LocalRom, cur, actors): +def write_actor_data(rom: LocalRom, cur, actors): for actor in actors: rom.write_int16s(cur, actor) cur += 0x10 + def align4(value): return ((value + 3) // 4) * 4 + def align16(value): return ((value + 0xF) // 0x10) * 0x10 + # This function inserts space in a ovl section at the section's offset # The section size is expanded # Every relocation entry in the section after the offet is moved accordingly @@ -583,10 +580,10 @@ def insert_space(rom, file, vram_start, insert_section, insert_offset, insert_si # move relocation if section is increased and it's after the insert if insert_section == section and offset >= insert_offset: # rebuild new relocation entry - rom.write_int32(cur, - ((section + 1) << 30) | - (type << 24) | - (offset + insert_size)) + rom.write_int32(cur, + ((section + 1) << 30) | + (type << 24) | + (offset + insert_size)) # value contains the vram address value = rom.read_int32(address) @@ -616,7 +613,7 @@ def insert_space(rom, file, vram_start, insert_section, insert_offset, insert_si value = None # update the vram values if it's been moved - if value != None and value >= insert_vram: + if value is not None and value >= insert_vram: # value = new vram address new_value = value + insert_size @@ -682,12 +679,12 @@ def add_relocations(rom, file, addresses): # Otherwise, try to infer type from value value = rom.read_int32(address) op = value >> 26 - type = 2 # default: data - if op == 0x02 or op == 0x03: # j or jal + type = 2 # default: data + if op == 0x02 or op == 0x03: # j or jal type = 4 - elif op == 0x0F: # lui + elif op == 0x0F: # lui type = 5 - elif op == 0x08: # addi + elif op == 0x08: # addi type = 6 # Calculate section and offset @@ -701,13 +698,13 @@ def add_relocations(rom, file, addresses): offset = address - sections[section - 1] # generate relocation entry - relocations.append((section << 30) - | (type << 24) - | (offset & 0x00FFFFFF)) + relocations.append((section << 30) + | (type << 24) + | (offset & 0x00FFFFFF)) # Rebuild Relocation Table cur = header + 0x10 - relocations.sort(key = lambda val: val & 0xC0FFFFFF) + relocations.sort(key=lambda val: val & 0xC0FFFFFF) rom.write_int32(cur, len(relocations)) cur += 4 for relocation in relocations: diff --git a/Main.py b/Main.py index 18e34c0..26e8242 100644 --- a/Main.py +++ b/Main.py @@ -1,29 +1,28 @@ -from collections import OrderedDict -from itertools import zip_longest -import json import logging +import os import platform -import random +import struct import subprocess import time -import os -import struct -from BaseClasses import World, CollectionState, Item -from Rom import LocalRom +from BaseClasses import World from Patches import patch_rom +from Rom import LocalRom from Utils import default_output_path -class dummy_window(): + +class DummyWindow: def __init__(self): pass + def update_status(self, text): pass + def update_progress(self, val): pass -def main(settings, window=dummy_window()): +def main(settings, window=DummyWindow()): start = time.clock() logger = logging.getLogger('') @@ -41,7 +40,7 @@ def main(settings, window=dummy_window()): logger.info('Patching ROM.') - outfilebase = 'BOoT_%s' % (worlds[0].settings_string) + outfilebase = 'BOoT_%s' % worlds[0].settings_string output_dir = default_output_path(settings.output_dir) window.update_status('Patching ROM') @@ -71,19 +70,23 @@ def main(settings, window=dummy_window()): else: logger.info('OS not supported for compression') - #uncomment below for decompressed output (for debugging) - #rom.write_to_file(default_output_path('%s.z64' % outfilebase)) - run_process(window, logger, [compressor_path, rom_path, os.path.join(output_dir, '%s-comp.z64' % outfilebase)], None) + # uncomment below for decompressed output (for debugging) + # rom.write_to_file(default_output_path('%s.z64' % outfilebase)) + run_process(window, logger, [compressor_path, rom_path, os.path.join(output_dir, '%s-comp.z64' % outfilebase)], + None) os.remove(rom_path) window.update_progress(85) - #wad generation + # wad generation window.update_status('Generating WAD') logger.info('Generating WAD.') if settings.create_wad == 'True': - run_process(window, logger,["bin\\gzinject.exe", "-a","genkey"], b'45e') #generate common key - run_process(window, logger,["bin\\gzinject.exe", "-a","inject", "--rom", os.path.join(output_dir, '%s-comp.z64' % outfilebase), "--wad", settings.wad, "-o",os.path.join(output_dir, '%s.wad' % outfilebase), "-i", "NBOE", "-t", "Better OOT", "--disable-cstick-d-remapping", "--disable-dpad-u-remapping", "--cleanup"], None) + run_process(window, logger, ["bin\\gzinject.exe", "-a", "genkey"], b'45e') # generate common key + run_process(window, logger, ["bin\\gzinject.exe", "-a", "inject", "--rom", + os.path.join(output_dir, '%s-comp.z64' % outfilebase), "--wad", settings.wad, "-o", + os.path.join(output_dir, '%s.wad' % outfilebase), "-i", "NBOE", "-t", "Better OOT", + "--disable-cstick-d-remapping", "--disable-dpad-u-remapping", "--cleanup"], None) os.remove(os.path.join(output_dir, '%s-comp.z64' % outfilebase)) window.update_progress(95) @@ -98,6 +101,7 @@ def main(settings, window=dummy_window()): return worlds[settings.player_num - 1] + def run_process(window, logger, args, stdin): process = subprocess.Popen(args, bufsize=1, stdout=subprocess.PIPE, stdin=subprocess.PIPE) filecount = None @@ -110,11 +114,9 @@ def run_process(window, logger, args, stdin): find_index = line.find(b'files remaining') if find_index > -1: files = int(line[:find_index].strip()) - if filecount == None: + if filecount is None: filecount = files window.update_progress(50 + ((1 - (files / filecount)) * 30)) logger.info(line.decode('utf-8').strip('\n')) else: break - - diff --git a/Messages.py b/Messages.py index f8fcde9..b6ca099 100644 --- a/Messages.py +++ b/Messages.py @@ -10,35 +10,35 @@ # name of type, followed by number of additional bytes to read, follwed by a function that prints the code CONTROL_CODES = { - 0x00: ('pad', 0, lambda _: '' ), - 0x01: ('line-break', 0, lambda _: '\n' ), - 0x02: ('end', 0, lambda _: '' ), - 0x04: ('box-break', 0, lambda _: '\nâ–¼\n' ), - 0x05: ('color', 1, lambda d: '' ), - 0x06: ('gap', 1, lambda d: '<' + str(d) + 'px gap>' ), - 0x07: ('goto', 2, lambda d: '' ), - 0x08: ('instant', 0, lambda _: '' ), - 0x09: ('un-instant', 0, lambda _: '' ), - 0x0A: ('keep-open', 0, lambda _: '' ), - 0x0B: ('event', 0, lambda _: '' ), - 0x0C: ('box-break-delay', 1, lambda d: '\nâ–¼\n' ), - 0x0E: ('fade-out', 1, lambda d: '' ), - 0x0F: ('name', 0, lambda _: '' ), - 0x10: ('ocarina', 0, lambda _: '' ), - 0x12: ('sound', 2, lambda d: '' ), - 0x13: ('icon', 1, lambda d: '' ), - 0x14: ('speed', 1, lambda d: '' ), - 0x15: ('background', 3, lambda d: '' ), - 0x16: ('marathon', 0, lambda _: '' ), - 0x17: ('race', 0, lambda _: '' ), - 0x18: ('points', 0, lambda _: '' ), - 0x19: ('skulltula', 0, lambda _: '' ), - 0x1A: ('unskippable', 0, lambda _: '' ), - 0x1B: ('two-choice', 0, lambda _: '' ), - 0x1C: ('three-choice', 0, lambda _: '' ), - 0x1D: ('fish', 0, lambda _: '' ), - 0x1E: ('high-score', 1, lambda d: '' ), - 0x1F: ('time', 0, lambda _: '' ), + 0x00: ('pad', 0, lambda _: ''), + 0x01: ('line-break', 0, lambda _: '\n'), + 0x02: ('end', 0, lambda _: ''), + 0x04: ('box-break', 0, lambda _: '\nâ–¼\n'), + 0x05: ('color', 1, lambda d: ''), + 0x06: ('gap', 1, lambda d: '<' + str(d) + 'px gap>'), + 0x07: ('goto', 2, lambda d: ''), + 0x08: ('instant', 0, lambda _: ''), + 0x09: ('un-instant', 0, lambda _: ''), + 0x0A: ('keep-open', 0, lambda _: ''), + 0x0B: ('event', 0, lambda _: ''), + 0x0C: ('box-break-delay', 1, lambda d: '\nâ–¼\n'), + 0x0E: ('fade-out', 1, lambda d: ''), + 0x0F: ('name', 0, lambda _: ''), + 0x10: ('ocarina', 0, lambda _: ''), + 0x12: ('sound', 2, lambda d: ''), + 0x13: ('icon', 1, lambda d: ''), + 0x14: ('speed', 1, lambda d: ''), + 0x15: ('background', 3, lambda d: ''), + 0x16: ('marathon', 0, lambda _: ''), + 0x17: ('race', 0, lambda _: ''), + 0x18: ('points', 0, lambda _: ''), + 0x19: ('skulltula', 0, lambda _: ''), + 0x1A: ('unskippable', 0, lambda _: ''), + 0x1B: ('two-choice', 0, lambda _: ''), + 0x1C: ('three-choice', 0, lambda _: ''), + 0x1D: ('fish', 0, lambda _: ''), + 0x1E: ('high-score', 1, lambda d: ''), + 0x1F: ('time', 0, lambda _: ''), } SPECIAL_CHARACTERS = { @@ -57,10 +57,10 @@ 0xAA: '[Control Stick]', } -GOSSIP_STONE_MESSAGES = list( range(0x0401, 0x0421) ) # ids of the actual hints -GOSSIP_STONE_MESSAGES += [0x2053, 0x2054] # shared initial stone messages -TEMPLE_HINTS_MESSAGES = [0x7057, 0x707A] # dungeon reward hints from the temple of time pedestal -LIGHT_ARROW_HINT = [0x70CC] # ganondorf's light arrow hint line +GOSSIP_STONE_MESSAGES = list(range(0x0401, 0x0421)) # ids of the actual hints +GOSSIP_STONE_MESSAGES += [0x2053, 0x2054] # shared initial stone messages +TEMPLE_HINTS_MESSAGES = [0x7057, 0x707A] # dungeon reward hints from the temple of time pedestal +LIGHT_ARROW_HINT = [0x70CC] # ganondorf's light arrow hint line # messages for shorter item messages ITEM_MESSAGES = { @@ -195,7 +195,6 @@ 0x00F9: "\x08\x13\x1EYou put a \x05\x41Big Poe \x05\x40in a bottle!\x01Let's sell it at the \x05\x41Ghost Shop\x05\x40!\x01Something good might happen!", } - # messages for keysanity item pickup # ids are in the space freed up by move_shop_item_messages() KEYSANITY_MESSAGES = { @@ -236,7 +235,6 @@ 0xa1: '\x13\x77\x08You found a \x05\x41Small Key\x05\x40\x01for \x05\x47Ganon\'s Castle\x05\x40!\x09', } - # messages for song items SONG_MESSAGES = { 0x00B0: "\x08\x06\x28You have learned the\x01\x06\x2F\x05\x42Minuet of Forest\x05\x40!", @@ -250,7 +248,7 @@ 0x00BA: "\x08\x06\x14You've learned \x05\x42Saria's Song\x05\x40!", 0x00BB: "\x08\x06\x0BYou've learned the \x05\x46Sun's Song\x05\x40!", 0x00BC: "\x08\x06\x05You've learned the \x05\x44Song of Time\x05\x40!", - 0x00BD: "\x08You've learned the \x05\x45Song of Storms\x05\x40!", + 0x00BD: "\x08You've learned the \x05\x45Song of Storms\x05\x40!", } MISC_MESSAGES = { @@ -262,18 +260,21 @@ def bytes_to_int(bytes, signed=False): return int.from_bytes(bytes, byteorder='big', signed=signed) + # convert int to an array of bytes of the given width def int_to_bytes(num, width, signed=False): return int.to_bytes(num, width, byteorder='big', signed=signed) + def display_code_list(codes): message = "" for code in codes: message += str(code) return message + # holds a single character or control code of a string -class Text_Code(): +class Text_Code: def display(self): if self.code in CONTROL_CODES: @@ -323,16 +324,17 @@ def __init__(self, code, data): __str__ = __repr__ = display + # holds a single message, and all its data -class Message(): +class Message: def display(self): meta_data = ["#" + str(self.index), - "ID: 0x" + "{:04x}".format(self.id), - "Offset: 0x" + "{:06x}".format(self.offset), - "Length: 0x" + "{:04x}".format(self.unpadded_length) + "/0x" + "{:04x}".format(self.length), - "Box Type: " + str(self.box_type), - "Postion: " + str(self.position)] + "ID: 0x" + "{:04x}".format(self.id), + "Offset: 0x" + "{:06x}".format(self.offset), + "Length: 0x" + "{:04x}".format(self.unpadded_length) + "/0x" + "{:04x}".format(self.length), + "Box Type: " + str(self.box_type), + "Postion: " + str(self.position)] return ', '.join(meta_data) + '\n' + self.text def get_python_string(self): @@ -346,7 +348,8 @@ def is_id_message(self): if self.unpadded_length == 5: for i in range(4): code = self.text_codes[i].code - if not (code in range(ord('0'),ord('9')+1) or code in range(ord('A'),ord('F')+1) or code in range(ord('a'),ord('f')+1) ): + if not (code in range(ord('0'), ord('9') + 1) or code in range(ord('A'), ord('F') + 1) or code in range( + ord('a'), ord('f') + 1)): return False return True return False @@ -362,42 +365,42 @@ def parse_text(self): if next_char in CONTROL_CODES: extra_bytes = CONTROL_CODES[next_char][1] if extra_bytes > 0: - data = bytes_to_int(self.raw_text[index : index + extra_bytes]) + data = bytes_to_int(self.raw_text[index: index + extra_bytes]) index += extra_bytes text_code = Text_Code(next_char, data) self.text_codes.append(text_code) - if next_char == 0x02: # message end code + if next_char == 0x02: # message end code break - if next_char == 0x07: # goto + if next_char == 0x07: # goto self.has_goto = True self.ending = text_code - if next_char == 0x0A: # keep-open + if next_char == 0x0A: # keep-open self.has_keep_open = True self.ending = text_code - if next_char == 0x0B: # event + if next_char == 0x0B: # event self.has_event = True self.ending = text_code - if next_char == 0x0E: # fade out + if next_char == 0x0E: # fade out self.has_fade = True self.ending = text_code - if next_char == 0x10: # ocarina + if next_char == 0x10: # ocarina self.has_ocarina = True self.ending = text_code - if next_char == 0x1B: # two choice + if next_char == 0x1B: # two choice self.has_two_choice = True - if next_char == 0x1C: # three choice + if next_char == 0x1C: # three choice self.has_three_choice = True self.text = display_code_list(self.text_codes) self.unpadded_length = index def is_basic(self): - return not (self.has_goto or self.has_keep_open or self.has_event or self.has_fade or self.has_ocarina or self.has_two_choice or self.has_three_choice) + return not ( + self.has_goto or self.has_keep_open or self.has_event or self.has_fade or self.has_ocarina or self.has_two_choice or self.has_three_choice) # writes a Message back into the rom, using the given index and offset to update the table # returns the offset of the next message def write(self, rom, index, offset, replace_ending=False, ending=None, always_allow_skip=True, speed_up_text=True): - # construct the table entry id_bytes = int_to_bytes(self.id, 2) offset_bytes = int_to_bytes(offset, 3) @@ -412,49 +415,47 @@ def write(self, rom, index, offset, replace_ending=False, ending=None, always_al not_quick_text_ids = [ - 0x71B0, #trade timer - 0x4022, #ruto warp text - 0x2064, 0x2065, 0x2066, #kokiri/field owl - 0x206C, #kak/field owl - 0x2068, 0x2069, #hyrule castle owl - 0x10C0, 0x10C1, 0x10C2, 0x10C3, #lost woods owl before saria - 0x10C4, 0x10C5, 0x10C6, 0x10C7, #lost woods owl after saria - 0x00E2, 0x0165, 0x00E3, #sarias song - 0x003B, #fw textbox - 0x0141, #first navi text - 0x407B, 0x407C, 0x407F, 0x4080, 0x4083, 0x4084, 0x4086, 0x4092, #fishing text - 0x007A, #bugs text - 0x0047, #fish text - 0x0046, #fairy text - 0x5041, #dampe race text - 0x002E, 0x71AF, #child/adult archery - 0x001A, #play again text - 0x00FA, 0x00FB, 0x00FC, 0x00FD, #bowling piece of heart - 0x10DC, 0x10DD, #deku stick/nut upgrades - 0x40AB, 0x40A9, #frogs - 0x109D, 0x109E, 0x109F #intro + 0x71B0, # trade timer + 0x4022, # ruto warp text + 0x2064, 0x2065, 0x2066, # kokiri/field owl + 0x206C, # kak/field owl + 0x2068, 0x2069, # hyrule castle owl + 0x10C0, 0x10C1, 0x10C2, 0x10C3, # lost woods owl before saria + 0x10C4, 0x10C5, 0x10C6, 0x10C7, # lost woods owl after saria + 0x00E2, 0x0165, 0x00E3, # sarias song + 0x003B, # fw textbox + 0x0141, # first navi text + 0x407B, 0x407C, 0x407F, 0x4080, 0x4083, 0x4084, 0x4086, 0x4092, # fishing text + 0x007A, # bugs text + 0x0047, # fish text + 0x0046, # fairy text + 0x5041, # dampe race text + 0x002E, 0x71AF, # child/adult archery + 0x001A, # play again text + 0x00FA, 0x00FB, 0x00FC, 0x00FD, # bowling piece of heart + 0x10DC, 0x10DD, # deku stick/nut upgrades + 0x40AB, 0x40A9, # frogs + 0x109D, 0x109E, 0x109F # intro ] auto_close_ids = [ - 0x605A, #twinrova text - 0x109D, 0x109E, #intro - 0x4022 #ruto part 2 + 0x605A, # twinrova text + 0x109D, 0x109E, # intro + 0x4022 # ruto part 2 ] - # # speed the text only if ids dont = specificed text boxes - #! OPTING OUT OF SPECIFIC QUICK TEXT HAPPENS HERE + # ! OPTING OUT OF SPECIFIC QUICK TEXT HAPPENS HERE if speed_up_text: if self.id in not_quick_text_ids: pass else: - offset = Text_Code(0x08, 0).write(rom, offset) # allow instant - - + offset = Text_Code(0x08, 0).write(rom, offset) # allow instant + # write the message for code in self.text_codes: # ignore ending codes if it's going to be replaced @@ -468,24 +469,22 @@ def write(self, rom, index, offset, replace_ending=False, ending=None, always_al pass elif speed_up_text and code.code in box_breaks: if self.id in auto_close_ids: - offset = code.write(rom, offset) #vanilla text code + offset = code.write(rom, offset) # vanilla text code else: - offset = Text_Code(0x04, 0).write(rom, offset) # un-delayed break - offset = Text_Code(0x08, 0).write(rom, offset) # allow instant + offset = Text_Code(0x04, 0).write(rom, offset) # un-delayed break + offset = Text_Code(0x08, 0).write(rom, offset) # allow instant else: offset = code.write(rom, offset) if replace_ending: if ending: - if speed_up_text and ending.code == 0x10: # ocarina - offset = Text_Code(0x09, 0).write(rom, offset) # disallow instant text - offset = ending.write(rom, offset) # write special ending - offset = Text_Code(0x02, 0).write(rom, offset) # write end code - - + if speed_up_text and ending.code == 0x10: # ocarina + offset = Text_Code(0x09, 0).write(rom, offset) # disallow instant text + offset = ending.write(rom, offset) # write special ending + offset = Text_Code(0x02, 0).write(rom, offset) # write end code while offset % 4 > 0: - offset = Text_Code(0x00, 0).write(rom, offset) # pad to 4 byte align + offset = Text_Code(0x00, 0).write(rom, offset) # pad to 4 byte align return offset @@ -537,26 +536,29 @@ def from_string(cls, text, id=0, opts=0x00): __str__ = __repr__ = display + # wrapper for updating the text of a message, given its message id # if the id does not exist in the list, then it will add it def update_message_by_id(messages, id, text, opts=None): # get the message index - index = next( (m.index for m in messages if m.id == id), -1) + index = next((m.index for m in messages if m.id == id), -1) # update if it was found if index >= 0: update_message_by_index(messages, index, text, opts) else: add_message(messages, text, id, opts) + # Gets the message by its ID. Returns None if the index does not exist def get_message_by_id(messages, id): # get the message index - index = next( (m.index for m in messages if m.id == id), -1) + index = next((m.index for m in messages if m.id == id), -1) if index >= 0: return messages[index] else: return None + # wrapper for updating the text of a message, given its index in the list def update_message_by_index(messages, index, text, opts=None): if opts is None: @@ -564,33 +566,34 @@ def update_message_by_index(messages, index, text, opts=None): messages[index] = Message.from_string(text, messages[index].id, opts) messages[index].index = index + # wrapper for adding a string message to a list of messages def add_message(messages, text, id=0, opts=0x00): - messages.append( Message.from_string(text, id, opts) ) + messages.append(Message.from_string(text, id, opts)) messages[-1].index = len(messages) - 1 + # holds a row in the shop item table (which contains pointers to the description and purchase messages) -class Shop_Item(): +class Shop_Item: def display(self): meta_data = ["#" + str(self.index), - "Item: 0x" + "{:04x}".format(self.get_item_id), - "Price: " + str(self.price), - "Amount: " + str(self.pieces), - "Object: 0x" + "{:04x}".format(self.object), - "Model: 0x" + "{:04x}".format(self.model), - "Description: 0x" + "{:04x}".format(self.description_message), - "Purchase: 0x" + "{:04x}".format(self.purchase_message),] + "Item: 0x" + "{:04x}".format(self.get_item_id), + "Price: " + str(self.price), + "Amount: " + str(self.pieces), + "Object: 0x" + "{:04x}".format(self.object), + "Model: 0x" + "{:04x}".format(self.model), + "Description: 0x" + "{:04x}".format(self.description_message), + "Purchase: 0x" + "{:04x}".format(self.purchase_message), ] func_data = [ - "func1: 0x" + "{:08x}".format(self.func1), - "func2: 0x" + "{:08x}".format(self.func2), - "func3: 0x" + "{:08x}".format(self.func3), - "func4: 0x" + "{:08x}".format(self.func4),] + "func1: 0x" + "{:08x}".format(self.func1), + "func2: 0x" + "{:08x}".format(self.func2), + "func3: 0x" + "{:08x}".format(self.func3), + "func4: 0x" + "{:08x}".format(self.func4), ] return ', '.join(meta_data) + '\n' + ', '.join(func_data) # write the shop item back def write(self, rom, shop_table_address, index): - entry_offset = shop_table_address + 0x20 * index bytes = [] @@ -611,7 +614,6 @@ def write(self, rom, shop_table_address, index): # read a single message def __init__(self, rom, shop_table_address, index): - entry_offset = shop_table_address + 0x20 * index entry = rom.read_bytes(entry_offset, 0x20) @@ -631,23 +633,27 @@ def __init__(self, rom, shop_table_address, index): __str__ = __repr__ = display + # reads each of the shop items def read_shop_items(rom, shop_table_address): shop_items = [] for index in range(0, 100): - shop_items.append( Shop_Item(rom, shop_table_address, index) ) + shop_items.append(Shop_Item(rom, shop_table_address, index)) return shop_items + # writes each of the shop item back into rom def write_shop_items(rom, shop_table_address, shop_items): for s in shop_items: s.write(rom, shop_table_address, s.index) + # these are unused shop items, and contain text ids that are used elsewhere, and should not be moved SHOP_ITEM_EXCEPTIONS = [0x0A, 0x0B, 0x11, 0x12, 0x13, 0x14, 0x29] + # returns a set of all message ids used for shop items def get_shop_message_id_set(shop_items): ids = set() @@ -657,20 +663,23 @@ def get_shop_message_id_set(shop_items): ids.add(shop.purchase_message) return ids + # remove all messages that easy to tell are unused to create space in the message index table def remove_unused_messages(messages): messages[:] = [m for m in messages if not m.is_id_message()] for index, m in enumerate(messages): m.index = index + # takes all messages used for shop items, and moves messages from the 00xx range into the unused 80xx range def move_shop_item_messages(messages, shop_items): # checks if a message id is in the item message range def is_in_item_range(id): bytes = int_to_bytes(id, 2) return bytes[0] == 0x00 + # get the ids we want to move - ids = set( id for id in get_shop_message_id_set(shop_items) if is_in_item_range(id) ) + ids = set(id for id in get_shop_message_id_set(shop_items) if is_in_item_range(id)) # update them in the message list for id in ids: # should be a singleton list, but in case something funky is going on, handle it as a list regardless @@ -684,19 +693,20 @@ def is_in_item_range(id): if is_in_item_range(shop.purchase_message): shop.purchase_message |= 0x8000 + def make_player_message(text): player_text_U = '\x05\x42Player \x18\x05\x40' player_text_L = '\x05\x42player \x18\x05\x40' pronoun_mapping = { 'You have ': player_text_U + ' ', - 'You\'ve ': player_text_U + ' ', - 'Your ': player_text_U + '\'s ', - 'You ': player_text_U + ' ', + 'You\'ve ': player_text_U + ' ', + 'Your ': player_text_U + '\'s ', + 'You ': player_text_U + ' ', 'you have ': player_text_L + ' ', - 'you\'ve ': player_text_L + ' ', - 'your ': player_text_L + '\'s ', - 'you ': player_text_L + ' ', + 'you\'ve ': player_text_L + ' ', + 'your ': player_text_L + '\'s ', + 'you ': player_text_L + ' ', } verb_mapping = { @@ -717,8 +727,6 @@ def make_player_message(text): return new_text - - # add the keysanity messages # make sure to call this AFTER move_shop_item_messages() def add_keysanity_messages(messages, world): @@ -728,6 +736,7 @@ def add_keysanity_messages(messages, world): else: update_message_by_id(messages, id, text, 0x23) + # add the song messages # make sure to call this AFTER move_shop_item_messages() def add_song_messages(messages, world): @@ -737,6 +746,7 @@ def add_song_messages(messages, world): else: update_message_by_id(messages, id, text, 0x23) + # reduce item message sizes def update_item_messages(messages, world): for id, text in ITEM_MESSAGES.items(): @@ -746,18 +756,20 @@ def update_item_messages(messages, world): else: update_message_by_id(messages, id, text) + def update_misc_messages(messages): for id, text in MISC_MESSAGES.items(): update_message_by_id(messages, id, text, 0x02) + # run all keysanity related patching to add messages for dungeon specific items def message_patch_for_dungeon_items(messages, shop_items, world): move_shop_item_messages(messages, shop_items) add_keysanity_messages(messages, world) + # reads each of the game's messages into a list of Message objects def read_messages(rom): - table_offset = TABLE_START index = 0 messages = [] @@ -767,20 +779,20 @@ def read_messages(rom): if id == 0xFFFD: table_offset += 8 - continue # this is only here to give an ending offset + continue # this is only here to give an ending offset if id == 0xFFFF: - break # this marks the end of the table + break # this marks the end of the table - messages.append( Message.from_rom(rom, index) ) + messages.append(Message.from_rom(rom, index)) index += 1 table_offset += 8 return messages + # wrtie the messages back def repack_messages(rom, messages, permutation=None, always_allow_skip=True, speed_up_text=True): - if permutation is None: permutation = range(len(messages)) @@ -789,14 +801,16 @@ def repack_messages(rom, messages, permutation=None, always_allow_skip=True, spe for old_index, new_index in enumerate(permutation): old_message = messages[old_index] new_message = messages[new_index] - remember_id = new_message.id + remember_id = new_message.id new_message.id = old_message.id - + offset = new_message.write(rom, old_index, offset, True, old_message.ending, always_allow_skip, speed_up_text) new_message.id = remember_id if offset > TEXT_SIZE_LIMIT: - raise(TypeError("Message Text table is too large: 0x" + "{:x}".format(offset) + " written / 0x" + "{:x}".format(TEXT_SIZE_LIMIT) + " allowed.")) + raise (TypeError( + "Message Text table is too large: 0x" + "{:x}".format(offset) + " written / 0x" + "{:x}".format( + TEXT_SIZE_LIMIT) + " allowed.")) # end the table table_index = len(messages) @@ -806,30 +820,31 @@ def repack_messages(rom, messages, permutation=None, always_allow_skip=True, spe table_index += 1 entry_offset = TABLE_START + 8 * table_index if 8 * (table_index + 1) > TABLE_SIZE_LIMIT: - raise(TypeError("Message ID table is too large: 0x" + "{:x}".format(8 * (table_index + 1)) + " written / 0x" + "{:x}".format(TABLE_SIZE_LIMIT) + " allowed.")) + raise (TypeError("Message ID table is too large: 0x" + "{:x}".format( + 8 * (table_index + 1)) + " written / 0x" + "{:x}".format(TABLE_SIZE_LIMIT) + " allowed.")) rom.write_bytes(entry_offset, [0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) + # shuffles the messages in the game, making sure to keep various message types in their own group def shuffle_messages(rom, except_hints=True, always_allow_skip=True): - messages = read_messages(rom) permutation = [i for i, _ in enumerate(messages)] def is_not_exempt(m): exempt_as_id = m.is_id_message() - exempt_as_hint = ( except_hints and m.id in (GOSSIP_STONE_MESSAGES + TEMPLE_HINTS_MESSAGES + LIGHT_ARROW_HINT + list(KEYSANITY_MESSAGES.keys()) ) ) - return not ( exempt_as_id or exempt_as_hint ) - - have_goto = list( filter( lambda m: is_not_exempt(m) and m.has_goto, messages) ) - have_keep_open = list( filter( lambda m: is_not_exempt(m) and m.has_keep_open, messages) ) - have_event = list( filter( lambda m: is_not_exempt(m) and m.has_event, messages) ) - have_fade = list( filter( lambda m: is_not_exempt(m) and m.has_fade, messages) ) - have_ocarina = list( filter( lambda m: is_not_exempt(m) and m.has_ocarina, messages) ) - have_two_choice = list( filter( lambda m: is_not_exempt(m) and m.has_two_choice, messages) ) - have_three_choice = list( filter( lambda m: is_not_exempt(m) and m.has_three_choice, messages) ) - basic_messages = list( filter( lambda m: is_not_exempt(m) and m.is_basic(), messages) ) - + exempt_as_hint = (except_hints and m.id in ( + GOSSIP_STONE_MESSAGES + TEMPLE_HINTS_MESSAGES + LIGHT_ARROW_HINT + list(KEYSANITY_MESSAGES.keys()))) + return not (exempt_as_id or exempt_as_hint) + + have_goto = list(filter(lambda m: is_not_exempt(m) and m.has_goto, messages)) + have_keep_open = list(filter(lambda m: is_not_exempt(m) and m.has_keep_open, messages)) + have_event = list(filter(lambda m: is_not_exempt(m) and m.has_event, messages)) + have_fade = list(filter(lambda m: is_not_exempt(m) and m.has_fade, messages)) + have_ocarina = list(filter(lambda m: is_not_exempt(m) and m.has_ocarina, messages)) + have_two_choice = list(filter(lambda m: is_not_exempt(m) and m.has_two_choice, messages)) + have_three_choice = list(filter(lambda m: is_not_exempt(m) and m.has_three_choice, messages)) + basic_messages = list(filter(lambda m: is_not_exempt(m) and m.is_basic(), messages)) def shuffle_group(group): group_permutation = [i for i, _ in enumerate(group)] @@ -839,7 +854,7 @@ def shuffle_group(group): permutation[group[index_to].index] = group[index_from].index # need to use 'list' to force 'map' to actually run through - list( map( shuffle_group, [ + list(map(shuffle_group, [ have_goto + have_keep_open + have_event + have_fade + basic_messages, have_ocarina, have_two_choice, diff --git a/Patches.py b/Patches.py index 8236649..b86a464 100644 --- a/Patches.py +++ b/Patches.py @@ -1,22 +1,13 @@ -import io -import json -import logging -import os -import platform -import struct -import subprocess -import random -import copy - from collections import namedtuple -Color = namedtuple('Color', ' R G B') -from Utils import local_path, default_output_path -from Messages import * from MQ import patch_files, File, update_dmadata, insert_space, add_relocations +from Messages import * +from Utils import local_path + +Color = namedtuple('Color', ' R G B') TunicColors = { - "Custom Color": [0, 0, 0], + "Custom Color": [0, 0, 0], "Kokiri Green": [0x1E, 0x69, 0x1B], "Goron Red": [0x64, 0x14, 0x00], "Zora Blue": [0x00, 0x3C, 0x64], @@ -25,7 +16,7 @@ "Azure Blue": [0x13, 0x9E, 0xD8], "Vivid Cyan": [0x13, 0xE9, 0xD8], "Light Red": [0xF8, 0x7C, 0x6D], - "Fuchsia":[0xFF, 0x00, 0xFF], + "Fuchsia": [0xFF, 0x00, 0xFF], "Purple": [0x95, 0x30, 0x80], "MM Purple": [0x50, 0x52, 0x9A], "Twitch Purple": [0x64, 0x41, 0xA5], @@ -51,7 +42,7 @@ } NaviColors = { - "Custom Color": [0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00], + "Custom Color": [0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00], "Gold": [0xFE, 0xCC, 0x3C, 0xFF, 0xFE, 0xC0, 0x07, 0x00], "White": [0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0x00], "Green": [0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00], @@ -73,36 +64,42 @@ "Phantom Zelda": [0x97, 0x7A, 0x6C, 0xFF, 0x6F, 0x46, 0x67, 0x00], } -sword_colors = { # Initial Color Fade Color - "Custom Color": (Color(0x00, 0x00, 0x00), Color(0x00, 0x00, 0x00)), - "Rainbow": (Color(0x00, 0x00, 0x00), Color(0x00, 0x00, 0x00)), - "White": (Color(0xFF, 0xFF, 0xFF), Color(0xFF, 0xFF, 0xFF)), - "Red": (Color(0xFF, 0x00, 0x00), Color(0xFF, 0x00, 0x00)), - "Green": (Color(0x00, 0xFF, 0x00), Color(0x00, 0xFF, 0x00)), - "Blue": (Color(0x00, 0x00, 0xFF), Color(0x00, 0x00, 0xFF)), - "Cyan": (Color(0x00, 0xFF, 0xFF), Color(0x00, 0xFF, 0xFF)), - "Magenta": (Color(0xFF, 0x00, 0xFF), Color(0xFF, 0x00, 0xFF)), - "Orange": (Color(0xFF, 0xA5, 0x00), Color(0xFF, 0xA5, 0x00)), - "Gold": (Color(0xFF, 0xD7, 0x00), Color(0xFF, 0xD7, 0x00)), - "Purple": (Color(0x80, 0x00, 0x80), Color(0x80, 0x00, 0x80)), - "Pink": (Color(0xFF, 0x69, 0xB4), Color(0xFF, 0x69, 0xB4)), +sword_colors = { # Initial Color Fade Color + "Custom Color": (Color(0x00, 0x00, 0x00), Color(0x00, 0x00, 0x00)), + "Rainbow": (Color(0x00, 0x00, 0x00), Color(0x00, 0x00, 0x00)), + "White": (Color(0xFF, 0xFF, 0xFF), Color(0xFF, 0xFF, 0xFF)), + "Red": (Color(0xFF, 0x00, 0x00), Color(0xFF, 0x00, 0x00)), + "Green": (Color(0x00, 0xFF, 0x00), Color(0x00, 0xFF, 0x00)), + "Blue": (Color(0x00, 0x00, 0xFF), Color(0x00, 0x00, 0xFF)), + "Cyan": (Color(0x00, 0xFF, 0xFF), Color(0x00, 0xFF, 0xFF)), + "Magenta": (Color(0xFF, 0x00, 0xFF), Color(0xFF, 0x00, 0xFF)), + "Orange": (Color(0xFF, 0xA5, 0x00), Color(0xFF, 0xA5, 0x00)), + "Gold": (Color(0xFF, 0xD7, 0x00), Color(0xFF, 0xD7, 0x00)), + "Purple": (Color(0x80, 0x00, 0x80), Color(0x80, 0x00, 0x80)), + "Pink": (Color(0xFF, 0x69, 0xB4), Color(0xFF, 0x69, 0xB4)), } + def get_tunic_colors(): return list(TunicColors.keys()) + def get_tunic_color_options(): return ["Random Choice", "Completely Random"] + get_tunic_colors() + def get_navi_colors(): return list(NaviColors.keys()) + def get_sword_colors(): return list(sword_colors.keys()) + def get_sword_color_options(): return ["Random Choice", "Completely Random"] + get_sword_colors() + def get_navi_color_options(): return ["Random Choice", "Completely Random"] + get_navi_colors() @@ -113,18 +110,17 @@ def patch_rom(world, rom): address, value = [int(x, 16) for x in line.split(',')] rom.write_byte(address, value) - - #Sword Colors + # Sword Colors sword_trails = [ - ('Inner Initial Sword Trail', world.sword_trail_color_inner, - [(0x00BEFF80, 0xB0, 0x40), (0x00BEFF88, 0x20, 0x00)], rom.sym('CFG_RAINBOW_SWORD_INNER_ENABLED')), - ('Outer Initial Sword Trail', world.sword_trail_color_outer, - [(0x00BEFF7C, 0xB0, 0xFF), (0x00BEFF84, 0x10, 0x00)], rom.sym('CFG_RAINBOW_SWORD_OUTER_ENABLED')), + ('Inner Initial Sword Trail', world.sword_trail_color_inner, + [(0x00BEFF80, 0xB0, 0x40), (0x00BEFF88, 0x20, 0x00)], rom.sym('CFG_RAINBOW_SWORD_INNER_ENABLED')), + ('Outer Initial Sword Trail', world.sword_trail_color_outer, + [(0x00BEFF7C, 0xB0, 0xFF), (0x00BEFF84, 0x10, 0x00)], rom.sym('CFG_RAINBOW_SWORD_OUTER_ENABLED')), ] sword_color_list = get_sword_colors() - for index, item in enumerate(sword_trails): + for item in sword_trails: sword_trail_name, sword_trail_option, sword_trail_addresses, sword_trail_rainbow_symbol = item # handle random @@ -149,7 +145,7 @@ def patch_rom(world, rom): color = list(sword_colors[sword_trail_option][index]) # build color from hex code else: - color = list(int(sword_trail_option[i:i+2], 16) for i in (0, 2 ,4)) + color = list(int(sword_trail_option[i:i + 2], 16) for i in (0, 2, 4)) custom_color = True if sword_trail_option == 'White': @@ -161,12 +157,12 @@ def patch_rom(world, rom): rom.write_byte(0x00BEFF8C, world.sword_trail_duration) - #Boots on D-Pad + # Boots on D-Pad if world.quickboots: symbol = rom.sym('QUICKBOOTS_ENABLE') rom.write_byte(symbol, 0x01) - #Jabu elevator + # Jabu elevator if world.fast_elevator: symbol = rom.sym('JABU_ENABLE') rom.write_byte(symbol, 0x01) @@ -176,7 +172,7 @@ def patch_rom(world, rom): # Can always return to youth rom.write_byte(0xCB6844, 0x35) - rom.write_byte(0x253C0E2, 0x03) # Moves sheik from pedestal + rom.write_byte(0x253C0E2, 0x03) # Moves sheik from pedestal if world.skip_intro: # Remove intro cutscene @@ -207,130 +203,131 @@ def patch_rom(world, rom): rom.write_bytes(0x1FC0CF8, Block_code) # Speed learning Saria's Song - rom.write_int32(0x020B1734, 0x0000003C) # Header: frame_count - rom.write_int32s(0x20B1DA8, [0x00000013, 0x0000000C]) # Textbox, Count - rom.write_int16s(None, [0x0015, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int16s(None, [0x00D1, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int32s(0x020B19C0, [0x0000000A, 0x00000006]) # Link, Count - rom.write_int16s(0x020B19C8, [0x0011, 0x0000, 0x0010, 0x0000]) # Action, start, end, ???? - rom.write_int16s(0x020B19F8, [0x003E, 0x0011, 0x0020, 0x0000]) # Action, start, end, ???? - rom.write_int32s(None, [0x80000000, # ??? - 0x00000000, 0x000001D4, 0xFFFFF731, # start_XYZ - 0x00000000, 0x000001D4, 0xFFFFF712]) # end_XYZ + rom.write_int32(0x020B1734, 0x0000003C) # Header: frame_count + rom.write_int32s(0x20B1DA8, [0x00000013, 0x0000000C]) # Textbox, Count + rom.write_int16s(None, [0x0015, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int16s(None, [0x00D1, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int32s(0x020B19C0, [0x0000000A, 0x00000006]) # Link, Count + rom.write_int16s(0x020B19C8, [0x0011, 0x0000, 0x0010, 0x0000]) # Action, start, end, ???? + rom.write_int16s(0x020B19F8, [0x003E, 0x0011, 0x0020, 0x0000]) # Action, start, end, ???? + rom.write_int32s(None, [0x80000000, # ??? + 0x00000000, 0x000001D4, 0xFFFFF731, # start_XYZ + 0x00000000, 0x000001D4, 0xFFFFF712]) # end_XYZ # Speed learning Epona's Song - rom.write_int32s(0x029BEF60, [0x000003E8, 0x00000001]) # Terminator Execution - rom.write_int16s(None, [0x005E, 0x000A, 0x000B, 0x000B]) # ID, start, end, end - rom.write_int32s(0x029BECB0, [0x00000013, 0x00000002]) # Textbox, Count - rom.write_int16s(None, [0x00D2, 0x0000, 0x0009, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int16s(None, [0xFFFF, 0x000A, 0x003C, 0xFFFF, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int32s(0x029BEF60, [0x000003E8, 0x00000001]) # Terminator Execution + rom.write_int16s(None, [0x005E, 0x000A, 0x000B, 0x000B]) # ID, start, end, end + rom.write_int32s(0x029BECB0, [0x00000013, 0x00000002]) # Textbox, Count + rom.write_int16s(None, [0x00D2, 0x0000, 0x0009, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int16s(None, [0xFFFF, 0x000A, 0x003C, 0xFFFF, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 # Speed learning Song of Time - rom.write_int32s(0x0252FB98, [0x000003E8, 0x00000001]) # Terminator Execution - rom.write_int16s(None, [0x0035, 0x003B, 0x003C, 0x003C]) # ID, start, end, end - rom.write_int32s(0x0252FC80, [0x00000013, 0x0000000C]) # Textbox, Count - rom.write_int16s(None, [0x0019, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int16s(None, [0x00D5, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int32(0x01FC3B84, 0xFFFFFFFF) # Other Header?: frame_count + rom.write_int32s(0x0252FB98, [0x000003E8, 0x00000001]) # Terminator Execution + rom.write_int16s(None, [0x0035, 0x003B, 0x003C, 0x003C]) # ID, start, end, end + rom.write_int32s(0x0252FC80, [0x00000013, 0x0000000C]) # Textbox, Count + rom.write_int16s(None, [0x0019, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int16s(None, [0x00D5, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int32(0x01FC3B84, 0xFFFFFFFF) # Other Header?: frame_count # Speed learning Song of Storms - rom.write_int32(0x03041084, 0x0000000A) # Header: frame_count - rom.write_int32s(0x03041088, [0x00000013, 0x00000002]) # Textbox, Count - rom.write_int16s(None, [0x00D6, 0x0000, 0x0009, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int16s(None, [0xFFFF, 0x00BE, 0x00C8, 0xFFFF, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int32(0x03041084, 0x0000000A) # Header: frame_count + rom.write_int32s(0x03041088, [0x00000013, 0x00000002]) # Textbox, Count + rom.write_int16s(None, [0x00D6, 0x0000, 0x0009, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int16s(None, [0xFFFF, 0x00BE, 0x00C8, 0xFFFF, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 # Speed learning Nocturne of Shadow - rom.write_int32s(0x01FFE458, [0x000003E8, 0x00000001]) # Other Scene? Terminator Execution - rom.write_int16s(None, [0x002F, 0x0001, 0x0002, 0x0002]) # ID, start, end, end - rom.write_int32(0x01FFFDF4, 0x0000003C) # Header: frame_count - rom.write_int32s(0x02000FD8, [0x00000013, 0x0000000E]) # Textbox, Count - rom.write_int16s(None, [0x0013, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int16s(None, [0x0077, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int32s(0x02000128, [0x000003E8, 0x00000001]) # Terminator Execution - rom.write_int16s(None, [0x0032, 0x003A, 0x003B, 0x003B]) # ID, start, end, end + rom.write_int32s(0x01FFE458, [0x000003E8, 0x00000001]) # Other Scene? Terminator Execution + rom.write_int16s(None, [0x002F, 0x0001, 0x0002, 0x0002]) # ID, start, end, end + rom.write_int32(0x01FFFDF4, 0x0000003C) # Header: frame_count + rom.write_int32s(0x02000FD8, [0x00000013, 0x0000000E]) # Textbox, Count + rom.write_int16s(None, [0x0013, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int16s(None, [0x0077, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int32s(0x02000128, [0x000003E8, 0x00000001]) # Terminator Execution + rom.write_int16s(None, [0x0032, 0x003A, 0x003B, 0x003B]) # ID, start, end, end # Speed learning Requiem of Spirit - rom.write_bytes(0x021A072C, [0x0F, 0x22]) # Change time of day to A60C instead of 8000 - rom.write_int32(0x0218AF14, 0x0000003C) # Header: frame_count - rom.write_int32s(0x0218C574, [0x00000013, 0x00000008]) # Textbox, Count - rom.write_int16s(None, [0x0012, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int16s(None, [0x0076, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int32s(0x0218B478, [0x000003E8, 0x00000001]) # Terminator Execution - rom.write_int16s(None, [0x0030, 0x003A, 0x003B, 0x003B]) # ID, start, end, end - rom.write_int32s(0x0218AF18, [0x0000000A, 0x0000000B]) # Link, Count - rom.write_int16s(0x0218AF20, [0x0011, 0x0000, 0x0010, 0x0000]) # Action, start, end, ???? - rom.write_int32s(None, [0x40000000, # ??? - 0xFFFFFAF9, 0x00000008, 0x00000001, # start_XYZ - 0xFFFFFAF9, 0x00000008, 0x00000001, # end_XYZ - 0x0F671408, 0x00000000, 0x00000001]) # normal_XYZ - rom.write_int16s(0x0218AF50, [0x003E, 0x0011, 0x0020, 0x0000]) # Action, start, end, ???? + rom.write_bytes(0x021A072C, [0x0F, 0x22]) # Change time of day to A60C instead of 8000 + rom.write_int32(0x0218AF14, 0x0000003C) # Header: frame_count + rom.write_int32s(0x0218C574, [0x00000013, 0x00000008]) # Textbox, Count + rom.write_int16s(None, [0x0012, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int16s(None, [0x0076, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int32s(0x0218B478, [0x000003E8, 0x00000001]) # Terminator Execution + rom.write_int16s(None, [0x0030, 0x003A, 0x003B, 0x003B]) # ID, start, end, end + rom.write_int32s(0x0218AF18, [0x0000000A, 0x0000000B]) # Link, Count + rom.write_int16s(0x0218AF20, [0x0011, 0x0000, 0x0010, 0x0000]) # Action, start, end, ???? + rom.write_int32s(None, [0x40000000, # ??? + 0xFFFFFAF9, 0x00000008, 0x00000001, # start_XYZ + 0xFFFFFAF9, 0x00000008, 0x00000001, # end_XYZ + 0x0F671408, 0x00000000, 0x00000001]) # normal_XYZ + rom.write_int16s(0x0218AF50, [0x003E, 0x0011, 0x0020, 0x0000]) # Action, start, end, ???? if world.song_speedup: # Speed learning Zelda's Lullaby - rom.write_int32s(0x02E8E90C, [0x000003E8, 0x00000001]) # Terminator Execution - rom.write_int16s(None, [0x0073, 0x003B, 0x003C, 0x003C]) # ID, start, end, end - rom.write_int32s(0x02E8E91C, [0x00000013, 0x0000000C]) # Textbox, Count - rom.write_int16s(None, [0x0017, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int16s(None, [0x00D4, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 - + rom.write_int32s(0x02E8E90C, [0x000003E8, 0x00000001]) # Terminator Execution + rom.write_int16s(None, [0x0073, 0x003B, 0x003C, 0x003C]) # ID, start, end, end + rom.write_int32s(0x02E8E91C, [0x00000013, 0x0000000C]) # Textbox, Count + rom.write_int16s(None, + [0x0017, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int16s(None, [0x00D4, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 + # Speed learning Minuet of Forest - rom.write_int32(0x020AFF84, 0x0000003C) # Header: frame_count - rom.write_int32s(0x020B0800, [0x00000013, 0x0000000A]) # Textbox, Count - rom.write_int16s(None, [0x000F, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int16s(None, [0x0073, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int32s(0x020AFF88, [0x0000000A, 0x00000005]) # Link, Count - rom.write_int16s(0x020AFF90, [0x0011, 0x0000, 0x0010, 0x0000]) # Action, start, end, ???? - rom.write_int16s(0x020AFFC1, [0x003E, 0x0011, 0x0020, 0x0000]) # Action, start, end, ???? - rom.write_int32s(0x020B0488, [0x00000056, 0x00000001]) # Music Change, Count - rom.write_int16s(None, [0x003F, 0x0021, 0x0022, 0x0000]) # Action, start, end, ???? - rom.write_int32s(0x020B04C0, [0x0000007C, 0x00000001]) # Music Fade Out, Count - rom.write_int16s(None, [0x0004, 0x0000, 0x0000, 0x0000]) # Action, start, end, ???? - + rom.write_int32(0x020AFF84, 0x0000003C) # Header: frame_count + rom.write_int32s(0x020B0800, [0x00000013, 0x0000000A]) # Textbox, Count + rom.write_int16s(None, [0x000F, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int16s(None, [0x0073, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int32s(0x020AFF88, [0x0000000A, 0x00000005]) # Link, Count + rom.write_int16s(0x020AFF90, [0x0011, 0x0000, 0x0010, 0x0000]) # Action, start, end, ???? + rom.write_int16s(0x020AFFC1, [0x003E, 0x0011, 0x0020, 0x0000]) # Action, start, end, ???? + rom.write_int32s(0x020B0488, [0x00000056, 0x00000001]) # Music Change, Count + rom.write_int16s(None, [0x003F, 0x0021, 0x0022, 0x0000]) # Action, start, end, ???? + rom.write_int32s(0x020B04C0, [0x0000007C, 0x00000001]) # Music Fade Out, Count + rom.write_int16s(None, [0x0004, 0x0000, 0x0000, 0x0000]) # Action, start, end, ???? + # Speed learning Bolero of Fire - rom.write_int32(0x0224B5D4, 0x0000003C) # Header: frame_count - rom.write_int32s(0x0224D7E8, [0x00000013, 0x0000000A]) # Textbox, Count - rom.write_int16s(None, [0x0010, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int16s(None, [0x0074, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int32s(0x0224B5D8, [0x0000000A, 0x0000000B]) # Link, Count - rom.write_int16s(0x0224B5E0, [0x0011, 0x0000, 0x0010, 0x0000]) # Action, start, end, ???? - rom.write_int16s(0x0224B610, [0x003E, 0x0011, 0x0020, 0x0000]) # Action, start, end, ???? - rom.write_int32s(0x0224B7F0, [0x0000002F, 0x0000000E]) # Sheik, Count - rom.write_int16s(0x0224B7F8, [0x0000]) # Action - rom.write_int16s(0x0224B828, [0x0000]) # Action - rom.write_int16s(0x0224B858, [0x0000]) # Action - rom.write_int16s(0x0224B888, [0x0000]) # Action - + rom.write_int32(0x0224B5D4, 0x0000003C) # Header: frame_count + rom.write_int32s(0x0224D7E8, [0x00000013, 0x0000000A]) # Textbox, Count + rom.write_int16s(None, [0x0010, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int16s(None, [0x0074, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int32s(0x0224B5D8, [0x0000000A, 0x0000000B]) # Link, Count + rom.write_int16s(0x0224B5E0, [0x0011, 0x0000, 0x0010, 0x0000]) # Action, start, end, ???? + rom.write_int16s(0x0224B610, [0x003E, 0x0011, 0x0020, 0x0000]) # Action, start, end, ???? + rom.write_int32s(0x0224B7F0, [0x0000002F, 0x0000000E]) # Sheik, Count + rom.write_int16s(0x0224B7F8, [0x0000]) # Action + rom.write_int16s(0x0224B828, [0x0000]) # Action + rom.write_int16s(0x0224B858, [0x0000]) # Action + rom.write_int16s(0x0224B888, [0x0000]) # Action + # Speed learning Serenade of Water - rom.write_int32(0x02BEB254, 0x0000003C) # Header: frame_count - rom.write_int32s(0x02BEC880, [0x00000013, 0x00000010]) # Textbox, Count - rom.write_int16s(None, [0x0011, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int16s(None, [0x0075, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int32s(0x02BEB258, [0x0000000A, 0x0000000F]) # Link, Count - rom.write_int16s(0x02BEB260, [0x0011, 0x0000, 0x0010, 0x0000]) # Action, start, end, ???? - rom.write_int16s(0x02BEB290, [0x003E, 0x0011, 0x0020, 0x0000]) # Action, start, end, ???? - rom.write_int32s(0x02BEB530, [0x0000002F, 0x00000006]) # Sheik, Count - rom.write_int16s(0x02BEB538, [0x0000, 0x0000, 0x018A, 0x0000]) # Action, start, end, ???? - rom.write_int32s(None, [0x1BBB0000, # ??? - 0xFFFFFB10, 0x8000011A, 0x00000330, # start_XYZ - 0xFFFFFB10, 0x8000011A, 0x00000330]) # end_XYZ - rom.write_int32s(0x02BEC848, [0x00000056, 0x00000001]) # Music Change, Count - rom.write_int16s(None, [0x0059, 0x0021, 0x0022, 0x0000]) # Action, start, end, ???? - + rom.write_int32(0x02BEB254, 0x0000003C) # Header: frame_count + rom.write_int32s(0x02BEC880, [0x00000013, 0x00000010]) # Textbox, Count + rom.write_int16s(None, [0x0011, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int16s(None, [0x0075, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int32s(0x02BEB258, [0x0000000A, 0x0000000F]) # Link, Count + rom.write_int16s(0x02BEB260, [0x0011, 0x0000, 0x0010, 0x0000]) # Action, start, end, ???? + rom.write_int16s(0x02BEB290, [0x003E, 0x0011, 0x0020, 0x0000]) # Action, start, end, ???? + rom.write_int32s(0x02BEB530, [0x0000002F, 0x00000006]) # Sheik, Count + rom.write_int16s(0x02BEB538, [0x0000, 0x0000, 0x018A, 0x0000]) # Action, start, end, ???? + rom.write_int32s(None, [0x1BBB0000, # ??? + 0xFFFFFB10, 0x8000011A, 0x00000330, # start_XYZ + 0xFFFFFB10, 0x8000011A, 0x00000330]) # end_XYZ + rom.write_int32s(0x02BEC848, [0x00000056, 0x00000001]) # Music Change, Count + rom.write_int16s(None, [0x0059, 0x0021, 0x0022, 0x0000]) # Action, start, end, ???? + # Speed learning Prelude of Light - rom.write_int32(0x0252FD24, 0x0000003C) # Header: frame_count - rom.write_int32s(0x02531320, [0x00000013, 0x0000000E]) # Textbox, Count - rom.write_int16s(None, [0x0014, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int16s(None, [0x0078, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 - rom.write_int32s(0x0252FF10, [0x0000002F, 0x00000009]) # Sheik, Count - rom.write_int16s(0x0252FF18, [0x0006, 0x0000, 0x0000, 0x0000]) # Action, start, end, ???? - rom.write_int32s(0x025313D0, [0x00000056, 0x00000001]) # Music Change, Count - rom.write_int16s(None, [0x003B, 0x0021, 0x0022, 0x0000]) # Action, start, end, ???? - - #Fix backwalk issue for Fairy Fountains - rom.write_bytes(0xC8A5C4, [0x10, 0x00, 0x00, 0x2F]) # Branch to end of funtion - rom.write_bytes(0xC8A5C8, [0xAE, 0x00, 0x00, 0x28]) # Keep fairy still - rom.write_bytes(0xC8A684, [0xA2, 0x0C, 0x00, 0xB4]) # Rotate fairy under the floor - rom.write_bytes(0xC8BE84, [0x00, 0x00, 0x00, 0x00]) # 0 out relocation table for jump + rom.write_int32(0x0252FD24, 0x0000003C) # Header: frame_count + rom.write_int32s(0x02531320, [0x00000013, 0x0000000E]) # Textbox, Count + rom.write_int16s(None, [0x0014, 0x0000, 0x0010, 0x0002, 0x088B, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int16s(None, [0x0078, 0x0011, 0x0020, 0x0000, 0xFFFF, 0xFFFF]) # ID, start, end, type, alt1, alt2 + rom.write_int32s(0x0252FF10, [0x0000002F, 0x00000009]) # Sheik, Count + rom.write_int16s(0x0252FF18, [0x0006, 0x0000, 0x0000, 0x0000]) # Action, start, end, ???? + rom.write_int32s(0x025313D0, [0x00000056, 0x00000001]) # Music Change, Count + rom.write_int16s(None, [0x003B, 0x0021, 0x0022, 0x0000]) # Action, start, end, ???? + + # Fix backwalk issue for Fairy Fountains + rom.write_bytes(0xC8A5C4, [0x10, 0x00, 0x00, 0x2F]) # Branch to end of funtion + rom.write_bytes(0xC8A5C8, [0xAE, 0x00, 0x00, 0x28]) # Keep fairy still + rom.write_bytes(0xC8A684, [0xA2, 0x0C, 0x00, 0xB4]) # Rotate fairy under the floor + rom.write_bytes(0xC8BE84, [0x00, 0x00, 0x00, 0x00]) # 0 out relocation table for jump # Speed Magic Meter Great Fairy rom.write_bytes(0x2CF7136, [0x00, 0x70]) @@ -346,7 +343,7 @@ def patch_rom(world, rom): rom.write_bytes(0x2CF8344, [0x00, 0x56]) rom.write_bytes(0x2CF834C, [0x00, 0xDD, 0x00, 0x57, 0x00, 0x59]) rom.write_bytes(0x2CF83AA, [0x00, 0x56, 0x00, 0x57]) - + # Speed Double Magic Meter Great Fairy rom.write_bytes(0x2CF83E6, [0x00, 0x70]) rom.write_bytes(0x2CF83F4, [0x00, 0x56]) @@ -424,38 +421,38 @@ def patch_rom(world, rom): # Speed scene after Deku Tree rom.write_bytes(0x2077E20, [0x00, 0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) rom.write_bytes(0x2078A10, [0x00, 0x0E, 0x00, 0x1F, 0x00, 0x20, 0x00, 0x20]) - Block_code = [0x00, 0x80, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + Block_code = [0x00, 0x80, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x1E, 0x00, 0x28, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] rom.write_bytes(0x2079570, Block_code) - + # Speed scene after Dodongo's Cavern rom.write_bytes(0x2221E88, [0x00, 0x0C, 0x00, 0x3B, 0x00, 0x3C, 0x00, 0x3C]) rom.write_bytes(0x2223308, [0x00, 0x81, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00]) - + # Speed scene after Jabu Jabu's Belly rom.write_bytes(0x2113340, [0x00, 0x0D, 0x00, 0x3B, 0x00, 0x3C, 0x00, 0x3C]) rom.write_bytes(0x2113C18, [0x00, 0x82, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00]) rom.write_bytes(0x21131D0, [0x00, 0x01, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x3C]) - + # Speed scene after Forest Temple rom.write_bytes(0xD4ED68, [0x00, 0x45, 0x00, 0x3B, 0x00, 0x3C, 0x00, 0x3C]) rom.write_bytes(0xD4ED78, [0x00, 0x3E, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00]) rom.write_bytes(0x207B9D4, [0xFF, 0xFF, 0xFF, 0xFF]) - + # Speed scene after Fire Temple rom.write_bytes(0x2001848, [0x00, 0x1E, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) rom.write_bytes(0xD100B4, [0x00, 0x62, 0x00, 0x3B, 0x00, 0x3C, 0x00, 0x3C]) rom.write_bytes(0xD10134, [0x00, 0x3C, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00]) - + # Speed scene after Water Temple rom.write_bytes(0xD5A458, [0x00, 0x15, 0x00, 0x3B, 0x00, 0x3C, 0x00, 0x3C]) rom.write_bytes(0xD5A3A8, [0x00, 0x3D, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00]) rom.write_bytes(0x20D0D20, [0x00, 0x29, 0x00, 0xC7, 0x00, 0xC8, 0x00, 0xC8]) - + # Speed scene after Shadow Temple rom.write_bytes(0xD13EC8, [0x00, 0x61, 0x00, 0x3B, 0x00, 0x3C, 0x00, 0x3C]) rom.write_bytes(0xD13E18, [0x00, 0x41, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00]) - + # Speed scene after Spirit Temple rom.write_bytes(0xD3A0A8, [0x00, 0x60, 0x00, 0x3B, 0x00, 0x3C, 0x00, 0x3C]) rom.write_bytes(0xD39FF0, [0x00, 0x3F, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00]) @@ -470,7 +467,7 @@ def patch_rom(world, rom): rom.write_byte(0x2F5B559, 0x04) rom.write_byte(0x2F5B621, 0x04) rom.write_byte(0x2F5B761, 0x07) - #fix position for cs skip + # fix position for cs skip rom.write_bytes(0x2F61072, [0xFB, 0x0D]) rom.write_bytes(0x2F61076, [0x04, 0xF3]) @@ -490,7 +487,7 @@ def patch_rom(world, rom): # Speed Twinrova defeat scene rom.write_bytes(0xD678CC, [0x24, 0x01, 0x03, 0xA2, 0xA6, 0x01, 0x01, 0x42]) rom.write_bytes(0xD67BA4, [0x10, 0x00]) - + # Ganondorf battle end rom.write_byte(0xD82047, 0x09) @@ -510,17 +507,18 @@ def patch_rom(world, rom): rom.write_bytes(0xE83D28, [0x00, 0x00, 0x00, 0x00]) rom.write_bytes(0xE83B5C, [0x00, 0x00, 0x00, 0x00]) rom.write_bytes(0xE84C80, [0x10, 0x00]) - + # Speed completion of the trials in Ganon's Castle - rom.write_bytes(0x31A8090, [0x00, 0x6B, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) #Forest - rom.write_bytes(0x31A9E00, [0x00, 0x6E, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) #Fire - rom.write_bytes(0x31A8B18, [0x00, 0x6C, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) #Water - rom.write_bytes(0x31A9430, [0x00, 0x6D, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) #Shadow - rom.write_bytes(0x31AB200, [0x00, 0x70, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) #Spirit - rom.write_bytes(0x31AA830, [0x00, 0x6F, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) #Light + rom.write_bytes(0x31A8090, [0x00, 0x6B, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) # Forest + rom.write_bytes(0x31A9E00, [0x00, 0x6E, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) # Fire + rom.write_bytes(0x31A8B18, [0x00, 0x6C, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) # Water + rom.write_bytes(0x31A9430, [0x00, 0x6D, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) # Shadow + rom.write_bytes(0x31AB200, [0x00, 0x70, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) # Spirit + rom.write_bytes(0x31AA830, [0x00, 0x6F, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) # Light - #Speed Silver Gaunts cs to avoid crash - rom.write_bytes(0x0218D51A, [0x00, 0x00, 0x00, 0x00]) #Set start and end frame of terminate command to beginning of cs + # Speed Silver Gaunts cs to avoid crash + rom.write_bytes(0x0218D51A, + [0x00, 0x00, 0x00, 0x00]) # Set start and end frame of terminate command to beginning of cs # Speed obtaining Fairy Ocarina rom.write_bytes(0x2151230, [0x00, 0x72, 0x00, 0x3C, 0x00, 0x3D, 0x00, 0x3D]) @@ -530,10 +528,12 @@ def patch_rom(world, rom): rom.write_bytes(0x2150E20, [0xFF, 0xFF, 0xFA, 0x4C]) # Speed Zelda Light Arrow cutscene - rom.write_bytes(0x2531B40, [0x00, 0x20, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) # Load into flashback ASAP - rom.write_bytes(0x01FC28C8, [0x00, 0x00, 0x03, 0xE8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]) # Load into tot ASAP + rom.write_bytes(0x2531B40, [0x00, 0x20, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) # Load into flashback ASAP + rom.write_bytes(0x01FC28C8, + [0x00, 0x00, 0x03, 0xE8, 0x00, 0x00, 0x00, 0x01, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00]) # Load into tot ASAP rom.write_bytes(0x2532FBC, [0x00, 0x75]) - rom.write_bytes(0x2532FEA, [0x00, 0x75, 0x00, 0x80]) + rom.write_bytes(0x2532FEA, [0x00, 0x75, 0x00, 0x80]) rom.write_byte(0x2533115, 0x05) rom.write_bytes(0x2533141, [0x06, 0x00, 0x06, 0x00, 0x10]) rom.write_bytes(0x2533171, [0x0F, 0x00, 0x11, 0x00, 0x40]) @@ -545,7 +545,7 @@ def patch_rom(world, rom): rom.write_bytes(0x25338C2, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]) rom.write_bytes(0x25339C2, [0x00, 0x75, 0x00, 0x76]) rom.write_bytes(0x2533830, [0x00, 0x31, 0x00, 0x81, 0x00, 0x82, 0x00, 0x82]) - + # Speed Bridge of Light cutscene rom.write_bytes(0x292D644, [0x00, 0x00, 0x00, 0xA0]) rom.write_bytes(0x292D680, [0x00, 0x02, 0x00, 0x0A, 0x00, 0x6C, 0x00, 0x00]) @@ -554,27 +554,27 @@ def patch_rom(world, rom): rom.write_bytes(0x292D810, [0x00, 0x02, 0x00, 0x3C]) rom.write_bytes(0x292D924, [0xFF, 0xFF, 0x00, 0x14, 0x00, 0x96, 0xFF, 0xFF]) - #Speed Pushing of All Pushable Objects - rom.write_bytes(0xDD2B86, [0x40, 0x80]) #block speed - rom.write_bytes(0xDD2D26, [0x00, 0x03]) #block delay - rom.write_bytes(0xDD9682, [0x40, 0x80]) #milk crate speed - rom.write_bytes(0xDD981E, [0x00, 0x03]) #milk crate delay - rom.write_bytes(0xCE1BD0, [0x40, 0x80, 0x00, 0x00]) #amy puzzle speed - rom.write_bytes(0xCE0F0E, [0x00, 0x03]) #amy puzzle delay - rom.write_bytes(0xC77CA8, [0x40, 0x80, 0x00, 0x00]) #fire block speed - rom.write_bytes(0xC770C2, [0x00, 0x01]) #fire block delay - rom.write_bytes(0xCC5DBC, [0x29, 0xE1, 0x00, 0x01]) #forest basement puzzle delay - rom.write_bytes(0xDBB5E8, [0x2B, 0x01, 0x00, 0x00]) #spirit cobra mirror startup #might remove - rom.write_bytes(0xDBCF70, [0x2B, 0x01, 0x00, 0x01]) #spirit cobra mirror delay - rom.write_bytes(0xDBA230, [0x28, 0x41, 0x00, 0x19]) #truth spinner speed - rom.write_bytes(0xDBA3A4, [0x24, 0x18, 0x00, 0x00]) #truth spinner delay - - #Speed Deku Seed Upgrade Scrub Cutscene - rom.write_bytes(0xECA900, [0x24, 0x03, 0xC0, 0x00]) #scrub angle - rom.write_bytes(0xECAE90, [0x27, 0x18, 0xFD, 0x04]) #skip straight to giving item - rom.write_bytes(0xECB618, [0x25, 0x6B, 0x00, 0xD4]) #skip straight to digging back in - rom.write_bytes(0xECAE70, [0x00, 0x00, 0x00, 0x00]) #never initialize cs camera - rom.write_bytes(0xE5972C, [0x24, 0x08, 0x00, 0x01]) #timer set to 1 frame for giving item + # Speed Pushing of All Pushable Objects + rom.write_bytes(0xDD2B86, [0x40, 0x80]) # block speed + rom.write_bytes(0xDD2D26, [0x00, 0x03]) # block delay + rom.write_bytes(0xDD9682, [0x40, 0x80]) # milk crate speed + rom.write_bytes(0xDD981E, [0x00, 0x03]) # milk crate delay + rom.write_bytes(0xCE1BD0, [0x40, 0x80, 0x00, 0x00]) # amy puzzle speed + rom.write_bytes(0xCE0F0E, [0x00, 0x03]) # amy puzzle delay + rom.write_bytes(0xC77CA8, [0x40, 0x80, 0x00, 0x00]) # fire block speed + rom.write_bytes(0xC770C2, [0x00, 0x01]) # fire block delay + rom.write_bytes(0xCC5DBC, [0x29, 0xE1, 0x00, 0x01]) # forest basement puzzle delay + rom.write_bytes(0xDBB5E8, [0x2B, 0x01, 0x00, 0x00]) # spirit cobra mirror startup #might remove + rom.write_bytes(0xDBCF70, [0x2B, 0x01, 0x00, 0x01]) # spirit cobra mirror delay + rom.write_bytes(0xDBA230, [0x28, 0x41, 0x00, 0x19]) # truth spinner speed + rom.write_bytes(0xDBA3A4, [0x24, 0x18, 0x00, 0x00]) # truth spinner delay + + # Speed Deku Seed Upgrade Scrub Cutscene + rom.write_bytes(0xECA900, [0x24, 0x03, 0xC0, 0x00]) # scrub angle + rom.write_bytes(0xECAE90, [0x27, 0x18, 0xFD, 0x04]) # skip straight to giving item + rom.write_bytes(0xECB618, [0x25, 0x6B, 0x00, 0xD4]) # skip straight to digging back in + rom.write_bytes(0xECAE70, [0x00, 0x00, 0x00, 0x00]) # never initialize cs camera + rom.write_bytes(0xE5972C, [0x24, 0x08, 0x00, 0x01]) # timer set to 1 frame for giving item if world.no_owls: # Remove remaining owls @@ -591,8 +591,8 @@ def patch_rom(world, rom): rom.write_bytes(0xE56924, [0x00, 0x00, 0x00, 0x00]) # Fix Ice Cavern Alcove Camera - rom.write_byte(0x2BECA25,0x01); - rom.write_byte(0x2BECA2D,0x01); + rom.write_byte(0x2BECA25, 0x01) + rom.write_byte(0x2BECA2D, 0x01) # Speed Jabu Jabu swallowing Link rom.write_bytes(0xCA0784, [0x00, 0x18, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02]) @@ -603,11 +603,11 @@ def patch_rom(world, rom): # Ruto never disappears from Jabu Jabu's Belly rom.write_byte(0xD01EA3, 0x00) - #Move fire/forest temple switches down 1 unit to make it easier to press - rom.write_bytes(0x24860A8, [0xFC, 0xF4]) #forest basement 1 - rom.write_bytes(0x24860C8, [0xFC, 0xF4]) #forest basement 2 - rom.write_bytes(0x24860E8, [0xFC, 0xF4]) #forest basement 3 - rom.write_bytes(0x236C148, [0x11, 0x93]) #fire hammer room + # Move fire/forest temple switches down 1 unit to make it easier to press + rom.write_bytes(0x24860A8, [0xFC, 0xF4]) # forest basement 1 + rom.write_bytes(0x24860C8, [0xFC, 0xF4]) # forest basement 2 + rom.write_bytes(0x24860E8, [0xFC, 0xF4]) # forest basement 3 + rom.write_bytes(0x236C148, [0x11, 0x93]) # fire hammer room # Speed up Epona race start rom.write_bytes(0x29BE984, [0x00, 0x00, 0x00, 0x02]) @@ -626,7 +626,7 @@ def patch_rom(world, rom): rom.write_byte(0x2025159, 0x02) rom.write_byte(0x2023E19, 0x02) - #Speed opening of Door of Time + # Speed opening of Door of Time rom.write_bytes(0xE0A176, [0x00, 0x02]) rom.write_bytes(0xE0A35A, [0x00, 0x01, 0x00, 0x02]) @@ -642,35 +642,35 @@ def patch_rom(world, rom): rom.write_bytes(0xCA3EA2, [0x00, 0x00, 0x25, 0x4A, 0x00, 0x08]) # Make the check for BGS 1 full day - rom.write_bytes(0xED446C, [0x28, 0x41, 0x00, 0x01]) #day check for claim check - rom.write_bytes(0xED4494, [0x28, 0x41, 0x00, 0x01]) #day check for dialogue - + rom.write_bytes(0xED446C, [0x28, 0x41, 0x00, 0x01]) # day check for claim check + rom.write_bytes(0xED4494, [0x28, 0x41, 0x00, 0x01]) # day check for dialogue + # Fixed reward order for Bombchu Bowling - rom.write_bytes(0xE2E694, [0x80, 0xAA, 0xE2, 0x64]) #item 1 = hp - rom.write_bytes(0xE2E698, [0x80, 0xAA, 0xE2, 0x88]) #item 2 = bombs - rom.write_bytes(0xE2E69C, [0x80, 0xAA, 0xE2, 0x28]) #item 3 = bombbag - rom.write_bytes(0xE2E6A0, [0x80, 0xAA, 0xE2, 0x4C]) #item 4 = rupee - rom.write_bytes(0xE2E6A4, [0x80, 0xAA, 0xE2, 0x58]) #item 5 = chus - rom.write_bytes(0xE2D440, [0x24, 0x19, 0x00, 0x00]) #start at item 1 - + rom.write_bytes(0xE2E694, [0x80, 0xAA, 0xE2, 0x64]) # item 1 = hp + rom.write_bytes(0xE2E698, [0x80, 0xAA, 0xE2, 0x88]) # item 2 = bombs + rom.write_bytes(0xE2E69C, [0x80, 0xAA, 0xE2, 0x28]) # item 3 = bombbag + rom.write_bytes(0xE2E6A0, [0x80, 0xAA, 0xE2, 0x4C]) # item 4 = rupee + rom.write_bytes(0xE2E6A4, [0x80, 0xAA, 0xE2, 0x58]) # item 5 = chus + rom.write_bytes(0xE2D440, [0x24, 0x19, 0x00, 0x00]) # start at item 1 + # Speed Dampe digging - rom.write_bytes(0x9532F8, [0x08, 0x08, 0x08, 0x59]) #text - rom.write_bytes(0xCC4024, [0x00, 0x00, 0x00, 0x00]) #first try + rom.write_bytes(0x9532F8, [0x08, 0x08, 0x08, 0x59]) # text + rom.write_bytes(0xCC4024, [0x00, 0x00, 0x00, 0x00]) # first try - #Give hp after first ocarina minigame round - rom.write_bytes(0xDF2204, [0x24, 0x03, 0x00, 0x02]) + # Give hp after first ocarina minigame round + rom.write_bytes(0xDF2204, [0x24, 0x03, 0x00, 0x02]) rom.write_byte(0xDF2647, 0x3E) - #Move keese in fire block room + # Move keese in fire block room rom.write_bytes(0x23A4058, [0x9E, 0x3A]) rom.write_bytes(0x23A4068, [0x9E, 0x3A]) rom.write_bytes(0x23A4088, [0x9E, 0x3A]) rom.write_bytes(0x23A4098, [0x9E, 0x3A]) - + # Make item descriptions into a single box Short_item_descriptions = [0x92EC84, 0x92F9E3, 0x92F2B4, 0x92F37A, 0x92F513, 0x92F5C6, 0x92E93B, 0x92EA12] for address in Short_item_descriptions: - rom.write_byte(address,0x02) + rom.write_byte(address, 0x02) # will be populated with data to be written to initial save # see initial_save.asm and config.asm for more details on specifics @@ -685,7 +685,7 @@ def write_bits_to_save(offset, value, filter=None): return initial_save_table += [(offset & 0xFF00) >> 8, offset & 0xFF, 0x00, value] - + # will overwrite the byte at offset with the given value def write_byte_to_save(offset, value, filter=None): nonlocal initial_save_table @@ -711,7 +711,7 @@ def write_save_table(rom): # Initial Save Data - #set initial time of day dynamically according to intro cs + # set initial time of day dynamically according to intro cs tod = 0x6AAB if world.skip_intro: @@ -725,125 +725,127 @@ def write_save_table(rom): byte2 = (tod & 0xFF) timeofday = [byte1, byte2] - write_bytes_to_save(0x0C, timeofday) #write sum to time of day + write_bytes_to_save(0x0C, timeofday) # write sum to time of day if world.forest_elevator: - write_bits_to_save(0x00D4 + 0x03 * 0x1C + 0x04 + 0x0, 0x08) # Forest Temple switch flag (Poe Sisters cutscene) + write_bits_to_save(0x00D4 + 0x03 * 0x1C + 0x04 + 0x0, 0x08) # Forest Temple switch flag (Poe Sisters cutscene) if world.no_owls: - write_bits_to_save(0x00D4 + 0x51 * 0x1C + 0x04 + 0x2, 0x08) # Hyrule Field switch flag (Owl) - write_bits_to_save(0x00D4 + 0x56 * 0x1C + 0x04 + 0x2, 0x40) # Sacred Forest Meadow switch flag (Owl) - write_bits_to_save(0x00D4 + 0x5B * 0x1C + 0x04 + 0x2, 0x01) # Lost Woods switch flag (Owl) - write_bits_to_save(0x00D4 + 0x5B * 0x1C + 0x04 + 0x3, 0x80) # Lost Woods switch flag (Owl) - write_bits_to_save(0x00D4 + 0x5C * 0x1C + 0x04 + 0x0, 0x80) # Desert Colossus switch flag (Owl) - write_bits_to_save(0x00D4 + 0x5F * 0x1C + 0x04 + 0x3, 0x20) # Hyrule Castle switch flag (Owl) - write_bits_to_save(0x0EE0, 0x80) # "Spoke to Kaepora Gaebora by Lost Woods" - - write_bits_to_save(0x0ED4, 0x10) # "Met Deku Tree" - write_bits_to_save(0x0ED5, 0x20) # "Deku Tree Opened Mouth" - write_bits_to_save(0x0ED6, 0x08) # "Rented Horse From Ingo" - write_bits_to_save(0x0EDA, 0x08) # "Began Nabooru Battle" - write_bits_to_save(0x0EDC, 0x80) # "Entered the Master Sword Chamber" - write_bits_to_save(0x0EDD, 0x20) # "Pulled Master Sword from Pedestal" - write_bits_to_save(0x00D4 + 0x05 * 0x1C + 0x04 + 0x1, 0x01) # Water temple switch flag (Ruto) - - write_bits_to_save(0x0EE7, 0x20) # "Nabooru Captured by Twinrova" - write_bits_to_save(0x0EE7, 0x10) # "Spoke to Nabooru in Spirit Temple" - write_bits_to_save(0x0EED, 0x20) # "Sheik, Spawned at Master Sword Pedestal as Adult" - write_bits_to_save(0x0EED, 0x80) # "Watched Ganon's Tower Collapse / Caught by Gerudo" - write_bits_to_save(0x0EED, 0x01) # "Nabooru Ordered to Fight by Twinrova" - write_bits_to_save(0x0EF9, 0x01) # "Greeted by Saria" - write_bits_to_save(0x0F0A, 0x04) # "Spoke to Ingo Once as Adult" - - write_bits_to_save(0x0ED7, 0x01) # "Spoke to Child Malon at Castle or Market" - write_bits_to_save(0x0ED7, 0x20) # "Spoke to Child Malon at Ranch" - write_bits_to_save(0x0ED7, 0x40) # "Invited to Sing With Child Malon" - write_bits_to_save(0x0F09, 0x10) # "Met Child Malon at Castle or Market" - write_bits_to_save(0x0F09, 0x20) # "Child Malon Said Epona Was Scared of You" - - write_bits_to_save(0x0F21, 0x04) # "Ruto in JJ (M3) Talk First Time" - write_bits_to_save(0x0F21, 0x02) # "Ruto in JJ (M2) Meet Ruto" - - write_bits_to_save(0x0EE2, 0x01) # "Began Ganondorf Battle" - #write_bits_to_save(0x0EE3, 0x80) # "Began Bongo Bongo Battle" - write_bits_to_save(0x0EE3, 0x40) # "Began Barinade Battle" - write_bits_to_save(0x0EE3, 0x20) # "Began Twinrova Battle" - write_bits_to_save(0x0EE3, 0x10) # "Began Morpha Battle" - write_bits_to_save(0x0EE3, 0x08) # "Began Volvagia Battle" - write_bits_to_save(0x0EE3, 0x04) # "Began Phantom Ganon Battle" - write_bits_to_save(0x0EE3, 0x02) # "Began King Dodongo Battle" - write_bits_to_save(0x0EE3, 0x01) # "Began Gohma Battle" - - #Static Intro CS - write_bits_to_save(0x0EE9, 0x80) # "Entered Temple of Time" - write_bits_to_save(0x0EEA, 0x04) # "Entered Ganon's Castle (Exterior)" - write_bits_to_save(0x0EEB, 0x10) # "Entered Lon Lon Ranch" - write_bits_to_save(0x0F08, 0x08) # "Entered Hyrule Castle" + write_bits_to_save(0x00D4 + 0x51 * 0x1C + 0x04 + 0x2, 0x08) # Hyrule Field switch flag (Owl) + write_bits_to_save(0x00D4 + 0x56 * 0x1C + 0x04 + 0x2, 0x40) # Sacred Forest Meadow switch flag (Owl) + write_bits_to_save(0x00D4 + 0x5B * 0x1C + 0x04 + 0x2, 0x01) # Lost Woods switch flag (Owl) + write_bits_to_save(0x00D4 + 0x5B * 0x1C + 0x04 + 0x3, 0x80) # Lost Woods switch flag (Owl) + write_bits_to_save(0x00D4 + 0x5C * 0x1C + 0x04 + 0x0, 0x80) # Desert Colossus switch flag (Owl) + write_bits_to_save(0x00D4 + 0x5F * 0x1C + 0x04 + 0x3, 0x20) # Hyrule Castle switch flag (Owl) + write_bits_to_save(0x0EE0, 0x80) # "Spoke to Kaepora Gaebora by Lost Woods" + + write_bits_to_save(0x0ED4, 0x10) # "Met Deku Tree" + write_bits_to_save(0x0ED5, 0x20) # "Deku Tree Opened Mouth" + write_bits_to_save(0x0ED6, 0x08) # "Rented Horse From Ingo" + write_bits_to_save(0x0EDA, 0x08) # "Began Nabooru Battle" + write_bits_to_save(0x0EDC, 0x80) # "Entered the Master Sword Chamber" + write_bits_to_save(0x0EDD, 0x20) # "Pulled Master Sword from Pedestal" + write_bits_to_save(0x00D4 + 0x05 * 0x1C + 0x04 + 0x1, 0x01) # Water temple switch flag (Ruto) + + write_bits_to_save(0x0EE7, 0x20) # "Nabooru Captured by Twinrova" + write_bits_to_save(0x0EE7, 0x10) # "Spoke to Nabooru in Spirit Temple" + write_bits_to_save(0x0EED, 0x20) # "Sheik, Spawned at Master Sword Pedestal as Adult" + write_bits_to_save(0x0EED, 0x80) # "Watched Ganon's Tower Collapse / Caught by Gerudo" + write_bits_to_save(0x0EED, 0x01) # "Nabooru Ordered to Fight by Twinrova" + write_bits_to_save(0x0EF9, 0x01) # "Greeted by Saria" + write_bits_to_save(0x0F0A, 0x04) # "Spoke to Ingo Once as Adult" + + write_bits_to_save(0x0ED7, 0x01) # "Spoke to Child Malon at Castle or Market" + write_bits_to_save(0x0ED7, 0x20) # "Spoke to Child Malon at Ranch" + write_bits_to_save(0x0ED7, 0x40) # "Invited to Sing With Child Malon" + write_bits_to_save(0x0F09, 0x10) # "Met Child Malon at Castle or Market" + write_bits_to_save(0x0F09, 0x20) # "Child Malon Said Epona Was Scared of You" + + write_bits_to_save(0x0F21, 0x04) # "Ruto in JJ (M3) Talk First Time" + write_bits_to_save(0x0F21, 0x02) # "Ruto in JJ (M2) Meet Ruto" + + write_bits_to_save(0x0EE2, 0x01) # "Began Ganondorf Battle" + # write_bits_to_save(0x0EE3, 0x80) # "Began Bongo Bongo Battle" + write_bits_to_save(0x0EE3, 0x40) # "Began Barinade Battle" + write_bits_to_save(0x0EE3, 0x20) # "Began Twinrova Battle" + write_bits_to_save(0x0EE3, 0x10) # "Began Morpha Battle" + write_bits_to_save(0x0EE3, 0x08) # "Began Volvagia Battle" + write_bits_to_save(0x0EE3, 0x04) # "Began Phantom Ganon Battle" + write_bits_to_save(0x0EE3, 0x02) # "Began King Dodongo Battle" + write_bits_to_save(0x0EE3, 0x01) # "Began Gohma Battle" + + # Static Intro CS + write_bits_to_save(0x0EE9, 0x80) # "Entered Temple of Time" + write_bits_to_save(0x0EEA, 0x04) # "Entered Ganon's Castle (Exterior)" + write_bits_to_save(0x0EEB, 0x10) # "Entered Lon Lon Ranch" + write_bits_to_save(0x0F08, 0x08) # "Entered Hyrule Castle" if world.skip_field: - write_bits_to_save(0x0EE9, 0x01) # "Entered Hyrule Field" + write_bits_to_save(0x0EE9, 0x01) # "Entered Hyrule Field" if world.skip_castle: - write_bits_to_save(0x0EE9, 0x20) # "Entered Hyrule Castle" + write_bits_to_save(0x0EE9, 0x20) # "Entered Hyrule Castle" if world.skip_kak: - write_bits_to_save(0x0EE9, 0x08) # "Entered Kakariko Village" + write_bits_to_save(0x0EE9, 0x08) # "Entered Kakariko Village" if world.skip_gy: - write_bits_to_save(0x0EEB, 0x40) # "Entered Graveyard" + write_bits_to_save(0x0EEB, 0x40) # "Entered Graveyard" if world.skip_dmt: - write_bits_to_save(0x0EE9, 0x02) # "Entered Death Mountain Trail" + write_bits_to_save(0x0EE9, 0x02) # "Entered Death Mountain Trail" if world.skip_gc: - write_bits_to_save(0x0EE9, 0x40) # "Entered Goron City" + write_bits_to_save(0x0EE9, 0x40) # "Entered Goron City" if world.skip_dmc: - write_bits_to_save(0x0EEA, 0x02) # "Entered Death Mountain Crater" + write_bits_to_save(0x0EEA, 0x02) # "Entered Death Mountain Crater" if world.skip_domain: - write_bits_to_save(0x0EE9, 0x10) # "Entered Zora's Domain" + write_bits_to_save(0x0EE9, 0x10) # "Entered Zora's Domain" if world.skip_fountain: - write_bits_to_save(0x0EEB, 0x80) # "Entered Zora's Fountain" + write_bits_to_save(0x0EEB, 0x80) # "Entered Zora's Fountain" if world.skip_lh: - write_bits_to_save(0x0EEB, 0x02) # "Entered Lake Hylia" + write_bits_to_save(0x0EEB, 0x02) # "Entered Lake Hylia" if world.skip_gv: - write_bits_to_save(0x0EEB, 0x04) # "Entered Gerudo Valley" + write_bits_to_save(0x0EEB, 0x04) # "Entered Gerudo Valley" if world.skip_gf: - write_bits_to_save(0x0EEB, 0x08) # "Entered Gerudo's Fortress" + write_bits_to_save(0x0EEB, 0x08) # "Entered Gerudo's Fortress" if world.skip_colossus: - write_bits_to_save(0x0EEA, 0x01) # "Entered Desert Colossus" + write_bits_to_save(0x0EEA, 0x01) # "Entered Desert Colossus" if world.skip_deku: - write_bits_to_save(0x0EE8, 0x01) # "Entered Deku Tree" + write_bits_to_save(0x0EE8, 0x01) # "Entered Deku Tree" if world.skip_dc: - write_bits_to_save(0x0EEB, 0x01) # "Entered Dodongo's Cavern" + write_bits_to_save(0x0EEB, 0x01) # "Entered Dodongo's Cavern" if world.skip_jabu: - write_bits_to_save(0x0EEB, 0x20) # "Entered Jabu-Jabu's Belly" - + write_bits_to_save(0x0EEB, 0x20) # "Entered Jabu-Jabu's Belly" # Make the Kakariko Gate not open with the MS - not sure if i wanna keep it like this rom.write_int32(0xDD3538, 0x34190000) if not world.open_kakariko: - rom.write_int32(0xDD3538, 0x34190000) # li t9, 0 + rom.write_int32(0xDD3538, 0x34190000) # li t9, 0 # Move carpenter starting position if world.skip_kak: - rom.write_bytes(0x1FF93A4, [0x01, 0x8D, 0x00, 0x11, 0x01, 0x6C, 0xFF, 0x92, 0x00, 0x00, 0x01, 0x78, 0xFF, 0x2E, 0x00, 0x00, 0x00, 0x03, 0xFD, 0x2B, 0x00, 0xC8, 0xFF, 0xF9, 0xFD, 0x03, 0x00, 0xC8, 0xFF, 0xA9, 0xFD, 0x5D, 0x00, 0xC8, 0xFE, 0x5F]) # re order the carpenter's path - rom.write_byte(0x1FF93D0, 0x06) # set the path points to 6 - rom.write_bytes(0x20160B6, [0x01, 0x8D, 0x00, 0x11, 0x01, 0x6C]) # set the carpenter's start position - + rom.write_bytes(0x1FF93A4, + [0x01, 0x8D, 0x00, 0x11, 0x01, 0x6C, 0xFF, 0x92, 0x00, 0x00, 0x01, 0x78, 0xFF, 0x2E, 0x00, 0x00, + 0x00, 0x03, 0xFD, 0x2B, 0x00, 0xC8, 0xFF, 0xF9, 0xFD, 0x03, 0x00, 0xC8, 0xFF, 0xA9, 0xFD, 0x5D, + 0x00, 0xC8, 0xFE, 0x5F]) # re order the carpenter's path + rom.write_byte(0x1FF93D0, 0x06) # set the path points to 6 + rom.write_bytes(0x20160B6, [0x01, 0x8D, 0x00, 0x11, 0x01, 0x6C]) # set the carpenter's start position + # Make all chest opening animations fast if world.fast_chests: - rom.write_int32(0xBDA2E8, 0x240AFFFF) # addiu t2, r0, -1 - # replaces # lb t2, 0x0002 (t1) + rom.write_int32(0xBDA2E8, 0x240AFFFF) # addiu t2, r0, -1 + # replaces # lb t2, 0x0002 (t1) if world.quest == 'master': for i in world.dungeon_mq: @@ -879,14 +881,14 @@ def write_save_table(rom): patch_files(rom, mq_scenes) - ### Load Shop File + # Load Shop File # Move shop actor file to free space shop_item_file = File({ - 'Name':'En_GirlA', - 'Start':'00C004E0', - 'End':'00C02E00', - 'RemapStart':'03485000', - }) + 'Name': 'En_GirlA', + 'Start': '00C004E0', + 'End': '00C02E00', + 'RemapStart': '03485000', + }) shop_item_file.relocate(rom) # Increase the shop item table size @@ -902,36 +904,36 @@ def write_save_table(rom): add_relocations(rom, shop_item_file, new_relocations) # update actor table - rom.write_int32s(0x00B5E490 + (0x20 * 4), - [shop_item_file.start, - shop_item_file.end, - shop_item_vram_start, - shop_item_vram_start + (shop_item_file.end - shop_item_file.start)]) + rom.write_int32s(0x00B5E490 + (0x20 * 4), + [shop_item_file.start, + shop_item_file.end, + shop_item_vram_start, + shop_item_vram_start + (shop_item_file.end - shop_item_file.start)]) # Update DMA Table update_dmadata(rom, shop_item_file) # Create 2nd Bazaar Room bazaar_room_file = File({ - 'Name':'shop1_room_1', - 'Start':'028E4000', - 'End':'0290D7B0', - 'RemapStart':'03489000', - }) + 'Name': 'shop1_room_1', + 'Start': '028E4000', + 'End': '0290D7B0', + 'RemapStart': '03489000', + }) bazaar_room_file.dma_key = 0x03472000 bazaar_room_file.relocate(rom) # Update DMA Table update_dmadata(rom, bazaar_room_file) # Add new Bazaar Room to Bazaar Scene - rom.write_int32s(0x28E3030, [0x00010000, 0x02000058]) #reduce position list size - rom.write_int32s(0x28E3008, [0x04020000, 0x02000070]) #expand room list size + rom.write_int32s(0x28E3030, [0x00010000, 0x02000058]) # reduce position list size + rom.write_int32s(0x28E3008, [0x04020000, 0x02000070]) # expand room list size - rom.write_int32s(0x28E3070, [0x028E4000, 0x0290D7B0, - bazaar_room_file.start, bazaar_room_file.end]) #room list - rom.write_int16s(0x28E3080, [0x0000, 0x0001]) # entrance list - rom.write_int16(0x28E4076, 0x0005) # Change shop to Kakariko Bazaar - #rom.write_int16(0x3489076, 0x0005) # Change shop to Kakariko Bazaar + rom.write_int32s(0x28E3070, [0x028E4000, 0x0290D7B0, + bazaar_room_file.start, bazaar_room_file.end]) # room list + rom.write_int16s(0x28E3080, [0x0000, 0x0001]) # entrance list + rom.write_int16(0x28E4076, 0x0005) # Change shop to Kakariko Bazaar + # rom.write_int16(0x3489076, 0x0005) # Change shop to Kakariko Bazaar # Load Message and Shop Data messages = read_messages(rom) @@ -965,8 +967,8 @@ def write_save_table(rom): # Set OHKO mode if world.difficulty == 'ohko': - rom.write_int32(0xAE80A8, 0xA4A00030) # sh zero,48(a1) - rom.write_int32(0xAE80B4, 0x06000003) # bltz s0, +0003 + rom.write_int32(0xAE80A8, 0xA4A00030) # sh zero,48(a1) + rom.write_int32(0xAE80B4, 0x06000003) # bltz s0, +0003 # give dungeon items the correct messages message_patch_for_dungeon_items(messages, shop_items, world) @@ -974,7 +976,7 @@ def write_save_table(rom): # reduce item message lengths update_item_messages(messages, world) - #update misc messages + # update misc messages update_misc_messages(messages) repack_messages(rom, messages) @@ -986,17 +988,17 @@ def write_save_table(rom): # patch music if world.background_music == 'random': randomize_music(rom) - elif world.background_music == 'off': + elif world.background_music == 'off': disable_music(rom) # re-seed for aesthetic effects. They shouldn't be affected by the generation seed random.seed() - + # Custom color tunic Tunics = [] - Tunics.append(0x00B6DA38) # Kokiri Tunic - Tunics.append(0x00B6DA3B) # Goron Tunic - Tunics.append(0x00B6DA3E) # Zora Tunic + Tunics.append(0x00B6DA38) # Kokiri Tunic + Tunics.append(0x00B6DA3B) # Goron Tunic + Tunics.append(0x00B6DA3E) # Zora Tunic colorList = get_tunic_colors() randomColors = random.choices(colorList, k=3) @@ -1012,19 +1014,20 @@ def write_save_table(rom): if world.tunic_colors[i] == 'Random Choice': color = TunicColors[randomColors[i]] # grab the color from the list - elif thisColor in TunicColors: - color = TunicColors[thisColor] - # build color from hex code - else: - color = list(int(thisColor[i:i+2], 16) for i in (0, 2 ,4)) + elif thisColor in TunicColors: + color = TunicColors[thisColor] + # build color from hex code + else: + color = list(int(thisColor[i:i + 2], 16) for i in (0, 2, 4)) rom.write_bytes(Tunics[i], color) # patch navi colors Navi = [] - Navi.append([0x00B5E184]) # Default - Navi.append([0x00B5E19C, 0x00B5E1BC]) # Enemy, Boss - Navi.append([0x00B5E194]) # NPC - Navi.append([0x00B5E174, 0x00B5E17C, 0x00B5E18C, 0x00B5E1A4, 0x00B5E1AC, 0x00B5E1B4, 0x00B5E1C4, 0x00B5E1CC, 0x00B5E1D4]) # Everything else + Navi.append([0x00B5E184]) # Default + Navi.append([0x00B5E19C, 0x00B5E1BC]) # Enemy, Boss + Navi.append([0x00B5E194]) # NPC + Navi.append([0x00B5E174, 0x00B5E17C, 0x00B5E18C, 0x00B5E1A4, 0x00B5E1AC, 0x00B5E1B4, 0x00B5E1C4, 0x00B5E1CC, + 0x00B5E1D4]) # Everything else naviList = get_navi_colors() randomColors = random.choices(naviList, k=4) @@ -1043,21 +1046,22 @@ def write_save_table(rom): if world.navi_colors[i] == 'Random Choice': color = NaviColors[randomColors[i]] # grab the color from the list - elif thisColor in NaviColors: - color = NaviColors[thisColor] - # build color from hex code - else: - color = list(int(thisColor[i:i+2], 16) for i in (0, 2 ,4)) - color = color + [0xFF] + color + [0x00] + elif thisColor in NaviColors: + color = NaviColors[thisColor] + # build color from hex code + else: + color = list(int(thisColor[i:i + 2], 16) for i in (0, 2, 4)) + color = color + [0xFF] + color + [0x00] rom.write_bytes(Navi[i][j], color) - #Navi hints + # Navi hints NaviHint = [] - NaviHint.append([0xAE7EF2, 0xC26C7E]) #Overworld Hint - NaviHint.append([0xAE7EC6]) #Enemy Target Hint - naviHintSFXList = ['Default', 'Notification', 'Rupee', 'Timer', 'Tamborine', 'Recovery Heart', 'Carrot Refill', 'Navi - Hey!', 'Navi - Random', 'Zelda - Gasp', 'Cluck', 'Mweep!', 'None'] + NaviHint.append([0xAE7EF2, 0xC26C7E]) # Overworld Hint + NaviHint.append([0xAE7EC6]) # Enemy Target Hint + naviHintSFXList = ['Default', 'Notification', 'Rupee', 'Timer', 'Tamborine', 'Recovery Heart', 'Carrot Refill', + 'Navi - Hey!', 'Navi - Random', 'Zelda - Gasp', 'Cluck', 'Mweep!', 'None'] randomNaviHintSFX = random.choices(naviHintSFXList, k=2) - + for i in range(len(NaviHint)): for j in range(len(NaviHint[i])): thisNaviHintSFX = world.navi_hint_sounds[i] @@ -1090,11 +1094,12 @@ def write_save_table(rom): if thisNaviHintSFX != 'Default': rom.write_bytes(NaviHint[i][j], naviHintSFX) - #Low health beep - healthSFXList = ['Default', 'Softer Beep', 'Rupee', 'Timer', 'Tamborine', 'Recovery Heart', 'Carrot Refill', 'Navi - Hey!', 'Zelda - Gasp', 'Cluck', 'Mweep!', 'None'] + # Low health beep + healthSFXList = ['Default', 'Softer Beep', 'Rupee', 'Timer', 'Tamborine', 'Recovery Heart', 'Carrot Refill', + 'Navi - Hey!', 'Zelda - Gasp', 'Cluck', 'Mweep!', 'None'] randomSFX = random.choice(healthSFXList) address = 0xADBA1A - + if world.healthSFX == 'Random Choice': thisHealthSFX = randomSFX else: @@ -1125,9 +1130,10 @@ def write_save_table(rom): healthSFX = [0x00, 0x00, 0x00, 0x00] address = 0xADBA14 rom.write_bytes(address, healthSFX) - + return rom + # Format: (Title, Sequence ID) bgm_sequence_ids = [ ('Hyrule Field', 0x02), @@ -1179,6 +1185,7 @@ def write_save_table(rom): ('Mini-game', 0x6C) ] + def randomize_music(rom): # Read in all the Music data bgm_data = [] @@ -1196,12 +1203,12 @@ def randomize_music(rom): rom.write_bytes(0xB89AE0 + (bgm[1] * 0x10), bgm_sequence) rom.write_int16(0xB89910 + 0xDD + (bgm[1] * 2), bgm_instrument) - # Write Fairy Fountain instrument to File Select (uses same track but different instrument set pointer for some reason) - rom.write_int16(0xB89910 + 0xDD + (0x57 * 2), rom.read_int16(0xB89910 + 0xDD + (0x28 * 2))) - + # Write Fairy Fountain instrument to File Select (uses same track but different instrument set pointer for some reason) + rom.write_int16(0xB89910 + 0xDD + (0x57 * 2), rom.read_int16(0xB89910 + 0xDD + (0x28 * 2))) + + def disable_music(rom): # First track is no music blank_track = rom.read_bytes(0xB89AE0 + (0 * 0x10), 0x10) for bgm in bgm_sequence_ids: rom.write_bytes(0xB89AE0 + (bgm[1] * 0x10), blank_track) - diff --git a/Rom.py b/Rom.py index 145b498..09a5c18 100644 --- a/Rom.py +++ b/Rom.py @@ -1,17 +1,14 @@ -import io import json -import logging import os import platform import struct import subprocess -import random -import copy -from Utils import local_path, default_output_path +from Utils import local_path DMADATA_START = 0x7430 + class LocalRom(object): def __init__(self, settings, patch=True): self.last_address = None @@ -20,11 +17,11 @@ def __init__(self, settings, patch=True): decomp_file = 'ZOOTDEC.z64' os.chdir(os.path.dirname(os.path.realpath(__file__))) - #os.chdir(output_path(os.path.dirname(os.path.realpath(__file__)))) + # os.chdir(output_path(os.path.dirname(os.path.realpath(__file__)))) with open(local_path('data/symbols.json'), 'r') as stream: symbols = json.load(stream) - self.symbols = { name: int(addr, 16) for name, addr in symbols.items() } + self.symbols = {name: int(addr, 16) for name, addr in symbols.items()} self.read_rom(file) self.decompress_rom_file(file, decomp_file) @@ -33,19 +30,19 @@ def __init__(self, settings, patch=True): def decompress_rom_file(self, file, decomp_file): validCRC = [ - [0xEC, 0x70, 0x11, 0xB7, 0x76, 0x16, 0xD7, 0x2B], # Compressed - [0x70, 0xEC, 0xB7, 0x11, 0x16, 0x76, 0x2B, 0xD7], # Byteswap compressed - [0x93, 0x52, 0x2E, 0x7B, 0xE5, 0x06, 0xD4, 0x27], # Decompressed + [0xEC, 0x70, 0x11, 0xB7, 0x76, 0x16, 0xD7, 0x2B], # Compressed + [0x70, 0xEC, 0xB7, 0x11, 0x16, 0x76, 0x2B, 0xD7], # Byteswap compressed + [0x93, 0x52, 0x2E, 0x7B, 0xE5, 0x06, 0xD4, 0x27], # Decompressed ] # Validate ROM file file_name = os.path.splitext(file) romCRC = list(self.buffer[0x10:0x18]) - + if romCRC not in validCRC: # Bad CRC validation raise RuntimeError('ROM file %s is not a valid OoT 1.0 US ROM.' % file) - elif len(self.buffer) < 0x2000000 or len(self.buffer) > (0x4000000) or file_name[1] not in ['.z64', '.n64']: + elif len(self.buffer) < 0x2000000 or len(self.buffer) > 0x4000000 or file_name[1] not in ['.z64', '.n64']: # ROM is too big, or too small, or not a bad type raise RuntimeError('ROM file %s is not a valid OoT 1.0 US ROM.' % file) elif len(self.buffer) == 0x2000000: @@ -62,7 +59,8 @@ def decompress_rom_file(self, file, decomp_file): elif platform.system() == 'Darwin': subcall = ["bin/Decompress/Decompress.out", file, decomp_file] else: - raise RuntimeError('Unsupported operating system for decompression. Please supply an already decompressed ROM.') + raise RuntimeError( + 'Unsupported operating system for decompression. Please supply an already decompressed ROM.') subprocess.call(subcall) self.read_rom(decomp_file) @@ -82,7 +80,7 @@ def read_byte(self, address): def read_bytes(self, address, len): self.last_address = address + len - return self.buffer[address : address + len] + return self.buffer[address: address + len] def read_int16(self, address): return bytes_as_int16(self.read_bytes(address, 2)) @@ -94,56 +92,56 @@ def read_int32(self, address): return bytes_as_int32(self.read_bytes(address, 4)) def write_byte(self, address, value): - if address == None: + if address is None: address = self.last_address self.buffer[address] = value self.last_address = address + 1 def write_sbyte(self, address, value): - if address == None: + if address is None: address = self.last_address self.write_bytes(address, struct.pack('b', value)) def write_int16(self, address, value): - if address == None: + if address is None: address = self.last_address self.write_bytes(address, int16_as_bytes(value)) def write_int24(self, address, value): - if address == None: + if address is None: address = self.last_address self.write_bytes(address, int24_as_bytes(value)) def write_int32(self, address, value): - if address == None: + if address is None: address = self.last_address self.write_bytes(address, int32_as_bytes(value)) - def write_f32(self, address, value:float): - if address == None: + def write_f32(self, address, value: float): + if address is None: address = self.last_address self.write_bytes(address, struct.pack('>f', value)) def write_bytes(self, startaddress, values): - if startaddress == None: + if startaddress is None: startaddress = self.last_address for i, value in enumerate(values): self.write_byte(startaddress + i, value) def write_int16s(self, startaddress, values): - if startaddress == None: + if startaddress is None: startaddress = self.last_address for i, value in enumerate(values): self.write_int16(startaddress + (i * 2), value) def write_int24s(self, startaddress, values): - if startaddress == None: + if startaddress is None: startaddress = self.last_address for i, value in enumerate(values): self.write_int24(startaddress + (i * 3), value) def write_int32s(self, startaddress, values): - if startaddress == None: + if startaddress is None: startaddress = self.last_address for i, value in enumerate(values): self.write_int32(startaddress + (i * 4), value) @@ -163,9 +161,9 @@ def update_crc(self): d = self.read_int32(cur) if ((t6 + d) & u32) < t6: - t4 += 1 + t4 += 1 - t6 = (t6+d) & u32 + t6 = (t6 + d) & u32 t3 ^= d shift = d & 0x1F r = ((d << shift) | (d >> (32 - shift))) & u32 @@ -188,7 +186,6 @@ def update_crc(self): # Finally write the crc back to the rom self.write_int32s(0x10, [crc0, crc1]) - def read_rom(self, file): # "Reads rom into bytearray" with open(file, 'rb') as stream: @@ -198,16 +195,15 @@ def read_rom(self, file): def _get_dmadata_record(rom, cur): start = rom.read_int32(cur) - end = rom.read_int32(cur+0x04) - size = end-start + end = rom.read_int32(cur + 0x04) + size = end - start return start, end, size - def verify_dmadata(rom): cur = DMADATA_START overlapping_records = [] dma_data = [] - + while True: this_start, this_end, this_size = rom._get_dmadata_record(cur) @@ -220,19 +216,18 @@ def verify_dmadata(rom): dma_data.sort(key=lambda v: v[0]) for i in range(0, len(dma_data) - 1): - + this_start, this_end, this_size = dma_data[i] next_start, next_end, next_size = dma_data[i + 1] if this_end > next_start: overlapping_records.append( - '0x%08X - 0x%08X (Size: 0x%04X)\n0x%08X - 0x%08X (Size: 0x%04X)' % \ - (this_start, this_end, this_size, next_start, next_end, next_size) - ) + '0x%08X - 0x%08X (Size: 0x%04X)\n0x%08X - 0x%08X (Size: 0x%04X)' % \ + (this_start, this_end, this_size, next_start, next_end, next_size) + ) if len(overlapping_records) > 0: raise Exception("Overlapping DMA Data Records!\n%s" % \ - '\n-------------------------------------\n'.join(overlapping_records)) - + '\n-------------------------------------\n'.join(overlapping_records)) def update_dmadata_record(rom, key, start, end): cur = DMADATA_START @@ -255,19 +250,24 @@ def int16_as_bytes(value): value = value & 0xFFFF return [(value >> 8) & 0xFF, value & 0xFF] + def int24_as_bytes(value): value = value & 0xFFFFFF return [(value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF] + def int32_as_bytes(value): value = value & 0xFFFFFFFF return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF] + def bytes_as_int16(values): return (values[0] << 8) | values[1] + def bytes_as_int24(values): return (values[0] << 16) | (values[1] << 8) | values[2] + def bytes_as_int32(values): return (values[0] << 24) | (values[1] << 16) | (values[2] << 8) | values[3] diff --git a/Settings.py b/Settings.py index c81d97e..15569ee 100644 --- a/Settings.py +++ b/Settings.py @@ -1,22 +1,25 @@ import argparse -import textwrap -import string -import re -import random import hashlib import math +import random +import re +import string +import textwrap from Patches import get_tunic_color_options, get_navi_color_options, get_sword_color_options + class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter): def _get_help_string(self, action): return textwrap.dedent(action.help) + # 32 characters letters = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" -index_to_letter = { i: letters[i] for i in range(32) } -letter_to_index = { v: k for k, v in index_to_letter.items() } +index_to_letter = {i: letters[i] for i in range(32)} +letter_to_index = {v: k for k, v in index_to_letter.items()} + def bit_string_to_text(bits): # pad the bits array to be multiple of 5 @@ -32,87 +35,95 @@ def bit_string_to_text(bits): result += index_to_letter[value] return result + def text_to_bit_string(text): bits = [] for c in text: index = letter_to_index[c] for b in range(5): - bits += [ (index >> b) & 1 ] + bits += [(index >> b) & 1] return bits + # holds the info for a single setting -class Setting_Info(): +class Setting_Info: + + def __init__(self, name, type, bitwidth=0, shared=False, args_params=None, gui_params=None): + if args_params is None: + args_params = {} + self.name = name # name of the setting, used as a key to retrieve the setting's value everywhere + self.type = type # type of the setting's value, used to properly convert types in GUI code + self.bitwidth = bitwidth # number of bits needed to store the setting, used in converting settings to a string + self.shared = shared # whether or not the setting is one that should be shared, used in converting settings to a string + self.args_params = args_params # parameters that should be pased to the command line argument parser's add_argument() function + self.gui_params = gui_params # parameters that the gui uses to build the widget components - def __init__(self, name, type, bitwidth=0, shared=False, args_params={}, gui_params=None): - self.name = name # name of the setting, used as a key to retrieve the setting's value everywhere - self.type = type # type of the setting's value, used to properly convert types in GUI code - self.bitwidth = bitwidth # number of bits needed to store the setting, used in converting settings to a string - self.shared = shared # whether or not the setting is one that should be shared, used in converting settings to a string - self.args_params = args_params # parameters that should be pased to the command line argument parser's add_argument() function - self.gui_params = gui_params # parameters that the gui uses to build the widget components class Setting_Widget(Setting_Info): - def __init__(self, name, type, choices, default, args_params={}, - gui_params=None, shared=False): + def __init__(self, name, type, choices, default, args_params=None, + gui_params=None, shared=False): + if args_params is None: + args_params = {} assert 'default' not in args_params and 'default' not in gui_params, \ - 'Setting {}: default shouldn\'t be defined in '\ - 'args_params or in gui_params'.format(name) + 'Setting {}: default shouldn\'t be defined in ' \ + 'args_params or in gui_params'.format(name) assert 'choices' not in args_params, \ - 'Setting {}: choices shouldn\'t be defined in '\ - 'args_params'.format(name) + 'Setting {}: choices shouldn\'t be defined in ' \ + 'args_params'.format(name) assert 'options' not in gui_params, \ - 'Setting {}: options shouldn\'t be defined in '\ - 'gui_params'.format(name) + 'Setting {}: options shouldn\'t be defined in ' \ + 'gui_params'.format(name) if 'type' not in args_params: args_params['type'] = type - if 'type' not in gui_params: gui_params['type'] = type + if 'type' not in gui_params: gui_params['type'] = type self.choices = choices self.default = default args_params['choices'] = list(choices.keys()) args_params['default'] = default - gui_params['options'] = {v: k for k, v in choices.items()} - gui_params['default'] = choices[default] + gui_params['options'] = {v: k for k, v in choices.items()} + gui_params['default'] = choices[default] super().__init__(name, type, self.calc_bitwidth(choices), shared, args_params, gui_params) - def calc_bitwidth(self, choices): count = len(choices) if count > 0: return math.ceil(math.log(count, 2)) return 0 + class Combobox(Setting_Widget): def __init__(self, name, choices, default, args_help, gui_text=None, - gui_group=None, gui_tooltip=None, gui_dependency=None, - shared=False): + gui_group=None, gui_tooltip=None, gui_dependency=None, + shared=False): type = str gui_params = { - 'widget': 'Combobox', - } - if gui_text is not None: gui_params['text'] = gui_text - if gui_group is not None: gui_params['group'] = gui_group - if gui_tooltip is not None: gui_params['tooltip'] = gui_tooltip + 'widget': 'Combobox', + } + if gui_text is not None: gui_params['text'] = gui_text + if gui_group is not None: gui_params['group'] = gui_group + if gui_tooltip is not None: gui_params['tooltip'] = gui_tooltip if gui_dependency is not None: gui_params['dependency'] = gui_dependency args_params = { - 'help': args_help, - } + 'help': args_help, + } super().__init__(name, type, choices, default, args_params, gui_params, - shared) + shared) + # holds the particular choices for a run's settings -class Settings(): +class Settings: def get_settings_display(self): padding = 0 for setting in filter(lambda s: s.shared, setting_infos): - padding = max( len(setting.name), padding ) + padding = max(len(setting.name), padding) padding += 2 output = '' for setting in filter(lambda s: s.shared, setting_infos): @@ -127,21 +138,21 @@ def get_settings_string(self): value = self.__dict__[setting.name] i_bits = [] if setting.type == bool: - i_bits = [ 1 if value else 0 ] + i_bits = [1 if value else 0] if setting.type == str: index = setting.args_params['choices'].index(value) # https://stackoverflow.com/questions/10321978/integer-to-bitfield-as-a-list - i_bits = [1 if digit=='1' else 0 for digit in bin(index)[2:]] + i_bits = [1 if digit == '1' else 0 for digit in bin(index)[2:]] i_bits.reverse() if setting.type == int: value = value - ('min' in setting.gui_params and setting.gui_params['min'] or 0) value = int(value / ('step' in setting.gui_params and setting.gui_params['step'] or 1)) value = min(value, ('max' in setting.gui_params and setting.gui_params['max'] or value)) # https://stackoverflow.com/questions/10321978/integer-to-bitfield-as-a-list - i_bits = [1 if digit=='1' else 0 for digit in bin(value)[2:]] + i_bits = [1 if digit == '1' else 0 for digit in bin(value)[2:]] i_bits.reverse() # pad it - i_bits += [0] * ( setting.bitwidth - len(i_bits) ) + i_bits += [0] * (setting.bitwidth - len(i_bits)) bits += i_bits return bit_string_to_text(bits) @@ -203,21 +214,25 @@ def __init__(self, settings_dict): if info.type == int: self.__dict__[info.name] = info.gui_params['default'] or 1 self.settings_string = self.get_settings_string() - if(self.seed is None): + if self.seed is None: # https://stackoverflow.com/questions/2257441/random-string-generation-with-upper-case-letters-and-digits-in-python self.seed = ''.join(random.choices(string.ascii_uppercase + string.digits, k=10)) self.sanatize_seed() self.numeric_seed = self.get_numeric_seed() + def parse_custom_tunic_color(s): return parse_color(s, get_tunic_color_options()) + def parse_custom_sword_color(s): return parse_color(s, get_sword_color_options()) + def parse_custom_navi_color(s): return parse_color(s, get_navi_color_options()) + def parse_color(s, color_choices): if s == 'Custom Color': raise argparse.ArgumentTypeError('Specify custom color by using \'Custom (#xxxxxx)\'') @@ -228,297 +243,298 @@ def parse_color(s, color_choices): else: raise argparse.ArgumentTypeError('Invalid color specified') + # a list of the possible settings setting_infos = [ - - Setting_Info('check_version', bool, 0, False, - { - 'help': '''\ + + Setting_Info('check_version', bool, 0, False, + { + 'help': '''\ Checks if you are on the latest version ''', - 'action': 'store_true' - }), - Setting_Info('create_wad', str, 2, False, - { - 'default': 'False', - 'const': 'False', - 'nargs': '?', - 'choices': ['True', 'False'], - 'help': '''\ + 'action': 'store_true' + }), + Setting_Info('create_wad', str, 2, False, + { + 'default': 'False', + 'const': 'False', + 'nargs': '?', + 'choices': ['True', 'False'], + 'help': '''\ Choose the ouput file type. True: Creates a WAD for use on VC False: Creates a ROM for use on N64 or Emu ''', - }, - { - 'text': 'Output File Type', - 'group': 'rom_tab', - 'widget': 'Radiobutton', - 'default': ' ROM ', - 'horizontal': True, - 'options': { - ' ROM ': 'False', - ' WAD ': 'True', - }, - 'tooltip':'''\ + }, + { + 'text': 'Output File Type', + 'group': 'rom_tab', + 'widget': 'Radiobutton', + 'default': ' ROM ', + 'horizontal': True, + 'options': { + ' ROM ': 'False', + ' WAD ': 'True', + }, + 'tooltip': '''\ Choose the output file type. ROM: For use on N64 or Emulator WAD: For use on Wii Virtual Console ''' - }), + }), Setting_Info('rom', str, 0, False, { - 'default': 'ZOOTDEC.z64', - 'help': 'Path to an OoT 1.0 rom to use as a base.'}), + 'default': 'ZOOTDEC.z64', + 'help': 'Path to an OoT 1.0 rom to use as a base.'}), Setting_Info('wad', str, 0, False, { - 'default': '', - 'help': 'Path to a 1.2 WAD to use as a base for VC inject.'}), + 'default': '', + 'help': 'Path to a 1.2 WAD to use as a base for VC inject.'}), Setting_Info('output_dir', str, 0, False, { - 'default': '', - 'help': 'Path to output directory for rom generation.'}), + 'default': '', + 'help': 'Path to output directory for rom generation.'}), Setting_Info('seed', str, 0, False, { - 'help': 'Define seed number to generate.'}), + 'help': 'Define seed number to generate.'}), Setting_Info('count', int, 0, False, { - 'help': '''\ + 'help': '''\ Use to batch generate multiple seeds with same settings. If --seed is provided, it will be used for the first seed, then used to derive the next seed (i.e. generating 10 seeds with --seed given will produce the same 10 (different) roms each time). ''', - 'type': int}), + 'type': int}), Setting_Info('world_count', int, 0, False, { - 'default': 1, - 'help': '''\ + 'default': 1, + 'help': '''\ Use to create a multi-world generation for co-op seeds. World count is the number of players. Warning: Increasing the world count will drastically increase generation time. ''', - 'type': int}), + 'type': int}), Setting_Info('player_num', int, 0, False, { - 'default': 1, - 'help': '''\ + 'default': 1, + 'help': '''\ Use to select world to generate when there are multiple worlds. ''', - 'type': int}), - Setting_Info('create_spoiler', bool, 1, True, - { - 'help': 'Output a Spoiler File', - 'action': 'store_true' - }, - { - 'text': 'Create Spoiler Log', - 'group': 'rom_tab', - 'widget': 'Checkbutton', - 'default': 'unchecked', - 'dependency': lambda guivar: guivar['compress_rom'].get() != 'No ROM Output', - 'tooltip':'''\ + 'type': int}), + Setting_Info('create_spoiler', bool, 1, True, + { + 'help': 'Output a Spoiler File', + 'action': 'store_true' + }, + { + 'text': 'Create Spoiler Log', + 'group': 'rom_tab', + 'widget': 'Checkbutton', + 'default': 'unchecked', + 'dependency': lambda guivar: guivar['compress_rom'].get() != 'No ROM Output', + 'tooltip': '''\ Enabling this will change the seed. ''' - }), - Setting_Info('compress_rom', str, 2, False, - { - 'default': 'True', - 'const': 'True', - 'nargs': '?', - 'choices': ['True', 'False', 'None'], - 'help': '''\ + }), + Setting_Info('compress_rom', str, 2, False, + { + 'default': 'True', + 'const': 'True', + 'nargs': '?', + 'choices': ['True', 'False', 'None'], + 'help': '''\ Create a compressed version of the output rom file. True: Compresses. Improves stability. Will take longer to generate False: Uncompressed. Unstable. Faster generation None: No ROM Output. Creates spoiler log only ''', - }, - { - 'text': 'Compress Rom', - 'group': 'rom_tab', - 'widget': 'Radiobutton', - 'default': 'Compressed [Stable]', - 'horizontal': True, - 'options': { - 'Compressed [Stable]': 'True', - 'Uncompressed [Crashes]': 'False', - 'No ROM Output': 'None', - }, - 'tooltip':'''\ + }, + { + 'text': 'Compress Rom', + 'group': 'rom_tab', + 'widget': 'Radiobutton', + 'default': 'Compressed [Stable]', + 'horizontal': True, + 'options': { + 'Compressed [Stable]': 'True', + 'Uncompressed [Crashes]': 'False', + 'No ROM Output': 'None', + }, + 'tooltip': '''\ The first time compressed generation will take a while but subsequent generations will be quick. It is highly recommended to compress or the game will crash frequently except on real N64 hardware. ''' - }), - Setting_Info('open_kakariko', bool, 1, True, - { - 'help': '''\ + }), + Setting_Info('open_kakariko', bool, 1, True, + { + 'help': '''\ The gate in Kakariko Village to Death Mountain Trail is always open, instead of needing Zelda's Letter. ''', - 'action': 'store_true' - }, - { - 'text': 'Open Kakariko Gate', - 'group': 'open', - 'widget': 'Checkbutton', - 'default': 'unchecked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Open Kakariko Gate', + 'group': 'open', + 'widget': 'Checkbutton', + 'default': 'unchecked', + 'tooltip': '''\ The gate in Kakariko Village to Death Mountain Trail is always open, instead of needing Zelda's Letter. Either way, the gate is always open as adult. ''' - }), - Setting_Info('shuffle_song_items', bool, 1, True, - { - 'help': '''\ + }), + Setting_Info('shuffle_song_items', bool, 1, True, + { + 'help': '''\ Shuffles the songs with with rest of the item pool so that song can appear at other locations, and items can appear at the song locations. ''', - 'action': 'store_true' - }, - { - 'text': 'Shuffle Songs with Items', - 'group': 'logic', - 'widget': 'Checkbutton', - 'default': 'unchecked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Shuffle Songs with Items', + 'group': 'logic', + 'widget': 'Checkbutton', + 'default': 'unchecked', + 'tooltip': '''\ Songs can appear anywhere as normal items, not just at vanilla song locations. ''' - }), + }), Setting_Info('background_music', str, 2, False, - { - 'default': 'normal', - 'const': 'normal', - 'nargs': '?', - 'choices': ['normal', 'off', 'random'], - 'help': '''\ + { + 'default': 'normal', + 'const': 'normal', + 'nargs': '?', + 'choices': ['normal', 'off', 'random'], + 'help': '''\ Sets the background music behavior normal: Areas play their normal background music off: No background music random: Areas play random background music ''' - }, - { - 'text': 'Background Music', - 'group': 'cosmetics', - 'widget': 'Combobox', - 'default': 'Normal', - 'options': { - 'Normal': 'normal', - 'No Music': 'off', - 'Random': 'random', - }, - 'tooltip': '''\ + }, + { + 'text': 'Background Music', + 'group': 'cosmetics', + 'widget': 'Combobox', + 'default': 'Normal', + 'options': { + 'Normal': 'normal', + 'No Music': 'off', + 'Random': 'random', + }, + 'tooltip': '''\ 'No Music': No background music. is played. 'Random': Area background music is randomized. ''' - }), - - Setting_Info('kokiricolor', str, 0, False, - { - 'default': 'Kokiri Green', - 'const': 'Kokiri Green', - 'nargs': '?', - 'type': parse_custom_tunic_color, - 'help': '''\ + }), + + Setting_Info('kokiricolor', str, 0, False, + { + 'default': 'Kokiri Green', + 'const': 'Kokiri Green', + 'nargs': '?', + 'type': parse_custom_tunic_color, + 'help': '''\ Choose the color for Link's Kokiri Tunic. (default: %(default)s) Color: Make the Kokiri Tunic this color. Random Choice: Choose a random color from this list of colors. Completely Random: Choose a random color from any color the N64 can draw. ''' - }, - { - 'text': 'Kokiri Tunic Color', - 'group': 'tuniccolor', - 'widget': 'Combobox', - 'default': 'Kokiri Green', - 'options': get_tunic_color_options(), - 'tooltip':'''\ + }, + { + 'text': 'Kokiri Tunic Color', + 'group': 'tuniccolor', + 'widget': 'Combobox', + 'default': 'Kokiri Green', + 'options': get_tunic_color_options(), + 'tooltip': '''\ 'Random Choice': Choose a random color from this list of colors. 'Completely Random': Choose a random color from any color the N64 can draw. ''' - }), - Setting_Info('goroncolor', str, 0, False, - { - 'default': 'Goron Red', - 'const': 'Goron Red', - 'nargs': '?', - 'type': parse_custom_tunic_color, - 'help': '''\ + }), + Setting_Info('goroncolor', str, 0, False, + { + 'default': 'Goron Red', + 'const': 'Goron Red', + 'nargs': '?', + 'type': parse_custom_tunic_color, + 'help': '''\ Choose the color for Link's Goron Tunic. (default: %(default)s) Color: Make the Goron Tunic this color. Random Choice: Choose a random color from this list of colors. Completely Random: Choose a random color from any color the N64 can draw. ''' - }, - { - 'text': 'Goron Tunic Color', - 'group': 'tuniccolor', - 'widget': 'Combobox', - 'default': 'Goron Red', - 'options': get_tunic_color_options(), - 'tooltip':'''\ + }, + { + 'text': 'Goron Tunic Color', + 'group': 'tuniccolor', + 'widget': 'Combobox', + 'default': 'Goron Red', + 'options': get_tunic_color_options(), + 'tooltip': '''\ 'Random Choice': Choose a random color from this list of colors. 'Completely Random': Choose a random color from any color the N64 can draw. ''' - }), - Setting_Info('zoracolor', str, 0, False, - { - 'default': 'Zora Blue', - 'const': 'Zora Blue', - 'nargs': '?', - 'type': parse_custom_tunic_color, - 'help': '''\ + }), + Setting_Info('zoracolor', str, 0, False, + { + 'default': 'Zora Blue', + 'const': 'Zora Blue', + 'nargs': '?', + 'type': parse_custom_tunic_color, + 'help': '''\ Choose the color for Link's Zora Tunic. (default: %(default)s) Color: Make the Zora Tunic this color. Random Choice: Choose a random color from this list of colors. Completely Random: Choose a random color from any color the N64 can draw. ''' - }, - { - 'text': 'Zora Tunic Color', - 'group': 'tuniccolor', - 'widget': 'Combobox', - 'default': 'Zora Blue', - 'options': get_tunic_color_options(), - 'tooltip':'''\ + }, + { + 'text': 'Zora Tunic Color', + 'group': 'tuniccolor', + 'widget': 'Combobox', + 'default': 'Zora Blue', + 'options': get_tunic_color_options(), + 'tooltip': '''\ 'Random Choice': Choose a random color from this list of colors. 'Completely Random': Choose a random color from any color the N64 can draw. ''' - }), + }), Combobox( - name = 'sword_trail_duration', - default = 4, - choices = { - 4: 'Default', - 10: 'Long', - 15: 'Very Long', - 20: 'Lightsaber', - }, - args_help = '''\ + name='sword_trail_duration', + default=4, + choices={ + 4: 'Default', + 10: 'Long', + 15: 'Very Long', + 20: 'Lightsaber', + }, + args_help='''\ Select the duration of the sword trail ''', - gui_text = 'Sword Trail Duration', - gui_group = 'swordcolor', - gui_tooltip = '''\ + gui_text='Sword Trail Duration', + gui_group='swordcolor', + gui_tooltip='''\ Select the duration for sword trails. ''', - ), + ), Setting_Info('sword_trail_color_inner', str, 0, False, - { - 'default': 'White', - 'type': parse_custom_sword_color, - 'help': '''\ + { + 'default': 'White', + 'type': parse_custom_sword_color, + 'help': '''\ Choose the color for your sword trail when you swing. This controls the inner color. (default: %(default)s) Color: Make your sword trail this color. Random Choice: Choose a random color from this list of colors. @@ -526,779 +542,784 @@ def parse_color(s, color_choices): Rainbow: Rainbow sword trails. ''' - }, - { - 'text': 'Inner Color', - 'group': 'swordcolor', - 'widget': 'Combobox', - 'default': 'White', - 'options': get_sword_color_options(), - 'tooltip':'''\ + }, + { + 'text': 'Inner Color', + 'group': 'swordcolor', + 'widget': 'Combobox', + 'default': 'White', + 'options': get_sword_color_options(), + 'tooltip': '''\ 'Random Choice': Choose a random color from this list of colors. 'Completely Random': Choose a random color from any color the N64 can draw. 'Rainbow': Rainbow sword trails. ''' - }), + }), Setting_Info('sword_trail_color_outer', str, 0, False, - { - 'default': 'White', - 'type': parse_custom_sword_color, - 'help': '''\ + { + 'default': 'White', + 'type': parse_custom_sword_color, + 'help': '''\ Choose the color for your sword trail when you swing. This controls the outer color. (default: %(default)s) Color: Make your sword trail this color. Random Choice: Choose a random color from this list of colors. Completely Random: Choose a random color from any color the N64 can draw. Rainbow: Rainbow sword trails. ''' - }, - { - 'text': 'Outer Color', - 'group': 'swordcolor', - 'widget': 'Combobox', - 'default': 'White', - 'options': get_sword_color_options(), - 'tooltip':'''\ + }, + { + 'text': 'Outer Color', + 'group': 'swordcolor', + 'widget': 'Combobox', + 'default': 'White', + 'options': get_sword_color_options(), + 'tooltip': '''\ 'Random Choice': Choose a random color from this list of colors. 'Completely Random': Choose a random color from any color the N64 can draw. 'Rainbow': Rainbow sword trails. ''' - }), - Setting_Info('navicolordefault', str, 0, False, - { - 'default': 'White', - 'const': 'White', - 'nargs': '?', - 'type': parse_custom_navi_color, - 'help': '''\ + }), + Setting_Info('navicolordefault', str, 0, False, + { + 'default': 'White', + 'const': 'White', + 'nargs': '?', + 'type': parse_custom_navi_color, + 'help': '''\ Choose the color for Navi when she is idle. (default: %(default)s) Color: Make the Navi this color. Random Choice: Choose a random color from this list of colors. Completely Random: Choose a random color from any color the N64 can draw. ''' - }, - { - 'text': 'Navi Idle', - 'group': 'navicolor', - 'widget': 'Combobox', - 'default': 'White', - 'options': get_navi_color_options(), - 'tooltip':'''\ + }, + { + 'text': 'Navi Idle', + 'group': 'navicolor', + 'widget': 'Combobox', + 'default': 'White', + 'options': get_navi_color_options(), + 'tooltip': '''\ 'Random Choice': Choose a random color from this list of colors. 'Comepletely Random': Choose a random color from any color the N64 can draw. ''' - }), - Setting_Info('navicolorenemy', str, 0, False, - { - 'default': 'Yellow', - 'const': 'Yellow', - 'nargs': '?', - 'type': parse_custom_navi_color, - 'help': '''\ + }), + Setting_Info('navicolorenemy', str, 0, False, + { + 'default': 'Yellow', + 'const': 'Yellow', + 'nargs': '?', + 'type': parse_custom_navi_color, + 'help': '''\ Choose the color for Navi when she is targeting an enemy. (default: %(default)s) Color: Make the Navi this color. Random Choice: Choose a random color from this list of colors. Completely Random: Choose a random color from any color the N64 can draw. ''' - }, - { - 'text': 'Navi Targeting Enemy', - 'group': 'navicolor', - 'widget': 'Combobox', - 'default': 'Yellow', - 'options': get_navi_color_options(), - 'tooltip':'''\ + }, + { + 'text': 'Navi Targeting Enemy', + 'group': 'navicolor', + 'widget': 'Combobox', + 'default': 'Yellow', + 'options': get_navi_color_options(), + 'tooltip': '''\ 'Random Choice': Choose a random color from this list of colors. 'Comepletely Random': Choose a random color from any color the N64 can draw. ''' - }), - Setting_Info('navicolornpc', str, 0, False, - { - 'default': 'Light Blue', - 'const': 'Light Blue', - 'nargs': '?', - 'type': parse_custom_navi_color, - 'help': '''\ + }), + Setting_Info('navicolornpc', str, 0, False, + { + 'default': 'Light Blue', + 'const': 'Light Blue', + 'nargs': '?', + 'type': parse_custom_navi_color, + 'help': '''\ Choose the color for Navi when she is targeting an NPC. (default: %(default)s) Color: Make the Navi this color. Random Choice: Choose a random color from this list of colors. Completely Random: Choose a random color from any color the N64 can draw. ''' - }, - { - 'text': 'Navi Targeting NPC', - 'group': 'navicolor', - 'widget': 'Combobox', - 'default': 'Light Blue', - 'options': get_navi_color_options(), - 'tooltip':'''\ + }, + { + 'text': 'Navi Targeting NPC', + 'group': 'navicolor', + 'widget': 'Combobox', + 'default': 'Light Blue', + 'options': get_navi_color_options(), + 'tooltip': '''\ 'Random Choice': Choose a random color from this list of colors. 'Comepletely Random': Choose a random color from any color the N64 can draw. ''' - }), - Setting_Info('navicolorprop', str, 0, False, - { - 'default': 'Green', - 'const': 'Green', - 'nargs': '?', - 'type': parse_custom_navi_color, - 'help': '''\ + }), + Setting_Info('navicolorprop', str, 0, False, + { + 'default': 'Green', + 'const': 'Green', + 'nargs': '?', + 'type': parse_custom_navi_color, + 'help': '''\ Choose the color for Navi when she is targeting a prop. (default: %(default)s) Color: Make the Navi this color. Random Choice: Choose a random color from this list of colors. Completely Random: Choose a random color from any color the N64 can draw. ''' - }, - { - 'text': 'Navi Targeting Prop', - 'group': 'navicolor', - 'widget': 'Combobox', - 'default': 'Green', - 'options': get_navi_color_options(), - 'tooltip':'''\ + }, + { + 'text': 'Navi Targeting Prop', + 'group': 'navicolor', + 'widget': 'Combobox', + 'default': 'Green', + 'options': get_navi_color_options(), + 'tooltip': '''\ 'Random Choice': Choose a random color from this list of colors. 'Comepletely Random': Choose a random color from any color the N64 can draw. ''' - }), - Setting_Info('navisfxoverworld', str, 0, False, - { - 'default': 'Default', - 'const': 'Default', - 'nargs': '?', - 'choices': ['Default', 'Notification', 'Rupee', 'Timer', 'Tamborine', 'Recovery Heart', 'Carrot Refill', 'Navi - Hey!', 'Navi - Random', 'Zelda - Gasp', 'Cluck', 'Mweep!', 'Random', 'None'], - 'help': '''\ + }), + Setting_Info('navisfxoverworld', str, 0, False, + { + 'default': 'Default', + 'const': 'Default', + 'nargs': '?', + 'choices': ['Default', 'Notification', 'Rupee', 'Timer', 'Tamborine', 'Recovery Heart', + 'Carrot Refill', 'Navi - Hey!', 'Navi - Random', 'Zelda - Gasp', 'Cluck', 'Mweep!', + 'Random', 'None'], + 'help': '''\ Select the sound effect that plays when Navi has a hint. (default: %(default)s) Sound: Replace the sound effect with the chosen sound. Random Choice: Replace the sound effect with a random sound from this list. None: Eliminate Navi hint sounds. ''' - }, - { - 'text': 'Navi Hint', - 'group': 'navihint', - 'widget': 'Combobox', - 'default': 'Default', - 'options': [ - 'Random Choice', - 'Default', - 'Notification', - 'Rupee', - 'Timer', - 'Tamborine', - 'Recovery Heart', - 'Carrot Refill', - 'Navi - Hey!', - 'Navi - Random', - 'Zelda - Gasp', - 'Cluck', - 'Mweep!', - 'None', - ] - }), - Setting_Info('navisfxenemytarget', str, 0, False, - { - 'default': 'Default', - 'const': 'Default', - 'nargs': '?', - 'choices': ['Default', 'Notification', 'Rupee', 'Timer', 'Tamborine', 'Recovery Heart', 'Carrot Refill', 'Navi - Hey!', 'Navi - Random', 'Zelda - Gasp', 'Cluck', 'Mweep!', 'Random', 'None'], - 'help': '''\ + }, + { + 'text': 'Navi Hint', + 'group': 'navihint', + 'widget': 'Combobox', + 'default': 'Default', + 'options': [ + 'Random Choice', + 'Default', + 'Notification', + 'Rupee', + 'Timer', + 'Tamborine', + 'Recovery Heart', + 'Carrot Refill', + 'Navi - Hey!', + 'Navi - Random', + 'Zelda - Gasp', + 'Cluck', + 'Mweep!', + 'None', + ] + }), + Setting_Info('navisfxenemytarget', str, 0, False, + { + 'default': 'Default', + 'const': 'Default', + 'nargs': '?', + 'choices': ['Default', 'Notification', 'Rupee', 'Timer', 'Tamborine', 'Recovery Heart', + 'Carrot Refill', 'Navi - Hey!', 'Navi - Random', 'Zelda - Gasp', 'Cluck', 'Mweep!', + 'Random', 'None'], + 'help': '''\ Select the sound effect that plays when targeting an enemy. (default: %(default)s) Sound: Replace the sound effect with the chosen sound. Random Choice: Replace the sound effect with a random sound from this list. None: Eliminate Navi hint sounds. ''' - }, - { - 'text': 'Navi Enemy Target', - 'group': 'navihint', - 'widget': 'Combobox', - 'default': 'Default', - 'options': [ - 'Random Choice', - 'Default', - 'Notification', - 'Rupee', - 'Timer', - 'Tamborine', - 'Recovery Heart', - 'Carrot Refill', - 'Navi - Hey!', - 'Navi - Random', - 'Zelda - Gasp', - 'Cluck', - 'Mweep!', - 'None', - ] - }), - Setting_Info('healthSFX', str, 0, False, - { - 'default': 'Default', - 'const': 'Default', - 'nargs': '?', - 'choices': ['Default', 'Softer Beep', 'Rupee', 'Timer', 'Tamborine', 'Recovery Heart', 'Carrot Refill', 'Navi - Hey!', 'Zelda - Gasp', 'Cluck', 'Mweep!', 'Random', 'None'], - 'help': '''\ + }, + { + 'text': 'Navi Enemy Target', + 'group': 'navihint', + 'widget': 'Combobox', + 'default': 'Default', + 'options': [ + 'Random Choice', + 'Default', + 'Notification', + 'Rupee', + 'Timer', + 'Tamborine', + 'Recovery Heart', + 'Carrot Refill', + 'Navi - Hey!', + 'Navi - Random', + 'Zelda - Gasp', + 'Cluck', + 'Mweep!', + 'None', + ] + }), + Setting_Info('healthSFX', str, 0, False, + { + 'default': 'Default', + 'const': 'Default', + 'nargs': '?', + 'choices': ['Default', 'Softer Beep', 'Rupee', 'Timer', 'Tamborine', 'Recovery Heart', + 'Carrot Refill', 'Navi - Hey!', 'Zelda - Gasp', 'Cluck', 'Mweep!', 'Random', 'None'], + 'help': '''\ Select the sound effect that loops at low health. (default: %(default)s) Sound: Replace the sound effect with the chosen sound. Random Choice: Replace the sound effect with a random sound from this list. None: Eliminate heart beeps. ''' - }, - { - 'text': 'Low Health SFX', - 'group': 'lowhp', - 'widget': 'Combobox', - 'default': 'Default', - 'options': [ - 'Random Choice', - 'Default', - 'Softer Beep', - 'Rupee', - 'Timer', - 'Tamborine', - 'Recovery Heart', - 'Carrot Refill', - 'Navi - Hey!', - 'Zelda - Gasp', - 'Cluck', - 'Mweep!', - 'None', - ], - 'tooltip':'''\ + }, + { + 'text': 'Low Health SFX', + 'group': 'lowhp', + 'widget': 'Combobox', + 'default': 'Default', + 'options': [ + 'Random Choice', + 'Default', + 'Softer Beep', + 'Rupee', + 'Timer', + 'Tamborine', + 'Recovery Heart', + 'Carrot Refill', + 'Navi - Hey!', + 'Zelda - Gasp', + 'Cluck', + 'Mweep!', + 'None', + ], + 'tooltip': '''\ 'Random Choice': Choose a random sound from this list. 'Default': Beep. Beep. Beep. ''' - }), + }), ######################################################################## - #Better OoT Settings + # Better OoT Settings ######################################################################## - Setting_Info('skip_intro', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_intro', bool, 1, True, + { + 'help': '''\ Toggle the intro cutscene ''', - 'action': 'store_true' - }, - { - 'text': 'Skip Intro Cutscene', - 'group': 'other', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Skip Intro Cutscene', + 'group': 'other', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Skip the intro cutscene ''' - }), - + }), - Setting_Info('forest_elevator', bool, 1, True, - { - 'help': '''\ + Setting_Info('forest_elevator', bool, 1, True, + { + 'help': '''\ Toggle if the Poe Sisters cutscene is present in Forest Temple. ''', - 'action': 'store_true' - }, - { - 'text': 'Skip Forest Elevator Cutscene', - 'group': 'other', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Skip Forest Elevator Cutscene', + 'group': 'other', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Toggle if the Poe Sisters cutscene is present in Forest Temple. ''' - }), + }), - Setting_Info('knuckle_cs', bool, 1, True, - { - 'help': '''\ + Setting_Info('knuckle_cs', bool, 1, True, + { + 'help': '''\ Toggle if the cutscene plays after defeating Nabooru Knuckle ''', - 'action': 'store_true' - }, - { - 'text': 'Skip Nabooru Defeat Cutscene', - 'group': 'other', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Skip Nabooru Defeat Cutscene', + 'group': 'other', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Toggle if the cutscene plays after defeating Nabooru Knuckle ''' - }), + }), - Setting_Info('dungeon_speedup', bool, 1, True, - { - 'help': '''\ + Setting_Info('dungeon_speedup', bool, 1, True, + { + 'help': '''\ Shorten blue warp cutscenes. ''', - 'action': 'store_true' - }, - { - 'text': 'Fast Blue Warp Cutscenes', - 'group': 'other', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Fast Blue Warp Cutscenes', + 'group': 'other', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Shorten blue warp cutscenes ''' - }), + }), - Setting_Info('song_speedup', bool, 1, True, - { - 'help': '''\ + Setting_Info('song_speedup', bool, 1, True, + { + 'help': '''\ Shorten the cutscenes for songs that can be skipped with glitches ''', - 'action': 'store_true' - }, - { - 'text': 'Fast Song Cutscenes', - 'group': 'other', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Fast Song Cutscenes', + 'group': 'other', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Shorten the cutscenes for songs that can be skipped with glitches ''' - }), + }), - Setting_Info('fast_chests', bool, 1, True, - { - 'help': '''\ + Setting_Info('fast_chests', bool, 1, True, + { + 'help': '''\ Makes all chests open without the large chest opening cutscene ''', - 'action': 'store_true' - }, - { - 'text': 'Fast Chest Cutscenes', - 'group': 'other', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Fast Chest Cutscenes', + 'group': 'other', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ All chest animations are fast ''' - }), + }), - Setting_Info('fast_elevator', bool, 1, True, - { - 'help': '''\ + Setting_Info('fast_elevator', bool, 1, True, + { + 'help': '''\ The elvator in Jabu will start at the bottom ''', - 'action': 'store_true' - }, - { - 'text': 'Fast Jabu Elevator', - 'group': 'other', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Fast Jabu Elevator', + 'group': 'other', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ The elvator in Jabu will start at the bottom ''' - }), + }), - Setting_Info('no_owls', bool, 1, True, - { - 'help': '''\ + Setting_Info('no_owls', bool, 1, True, + { + 'help': '''\ Toggle Owls in the overworld. ''', - 'action': 'store_true' - }, - { - 'text': 'Remove Owls', - 'group': 'other', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Remove Owls', + 'group': 'other', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove owl triggers in the overworld. ''' - }), + }), - Setting_Info('quickboots', bool, 1, True, - { - 'help': '''\ + Setting_Info('quickboots', bool, 1, True, + { + 'help': '''\ Toggle Owls in the overworld. ''', - 'action': 'store_true' - }, - { - 'text': 'Quick Boots', - 'group': 'other', - 'widget': 'Checkbutton', - 'default': 'unchecked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Quick Boots', + 'group': 'other', + 'widget': 'Checkbutton', + 'default': 'unchecked', + 'tooltip': '''\ Toggle boots with the d-pad ''' - }), - - Setting_Info('difficulty', str, 2, True, - { - 'default': 'normal', - 'const': 'normal', - 'nargs': '?', - 'choices': ['normal', 'ohko'], - 'help': '''\ + }), + + Setting_Info('difficulty', str, 2, True, + { + 'default': 'normal', + 'const': 'normal', + 'nargs': '?', + 'choices': ['normal', 'ohko'], + 'help': '''\ Change the item pool for an added challenge. normal: Default items hard: Double defense, double magic, and all 8 heart containers are removed very_hard: Double defense, double magic, Nayru's Love, and all health upgrades are removed ohko: Same as very hard, and Link will die in one hit. ''' - }, - { - 'text': 'Damage', - 'group': 'other', - 'widget': 'Combobox', - 'default': 'Normal', - 'options': { - 'Normal': 'normal', - 'One Hit KO': 'ohko' - }, - 'tooltip':'''\ + }, + { + 'text': 'Damage', + 'group': 'other', + 'widget': 'Combobox', + 'default': 'Normal', + 'options': { + 'Normal': 'normal', + 'One Hit KO': 'ohko' + }, + 'tooltip': '''\ 'Normal': Vanilla behavior 'One Hit KO': Link dies in one hit. ''' - }), - - Setting_Info('quest', str, 2, True, - { - 'default': 'vanilla', - 'const': 'vanilla', - 'nargs': '?', - 'choices': ['vanilla', 'master'], - 'help': '''\ + }), + + Setting_Info('quest', str, 2, True, + { + 'default': 'vanilla', + 'const': 'vanilla', + 'nargs': '?', + 'choices': ['vanilla', 'master'], + 'help': '''\ Vanilla: Dungeons will be the original Ocarina of Time dungeons. Master: Dungeons will be in the form of the Master Quest. ''' - }, - { - 'text': 'Dungeon Quest', - 'group': 'other', - 'widget': 'Combobox', - 'default': 'Vanilla', - 'options': { - 'Vanilla': 'vanilla', - 'Master Quest': 'master', - }, - 'tooltip':'''\ + }, + { + 'text': 'Dungeon Quest', + 'group': 'other', + 'widget': 'Combobox', + 'default': 'Vanilla', + 'options': { + 'Vanilla': 'vanilla', + 'Master Quest': 'master', + }, + 'tooltip': '''\ 'Vanilla': Dungeons will be vanilla. 'Master Quest': Dungeons will be Master Quest. ''', - }), - - Setting_Info('default_targeting', str, 1, False, - { - 'default': 'hold', - 'const': 'always', - 'nargs': '?', - 'choices': ['hold', 'switch'], - 'help': '''\ + }), + + Setting_Info('default_targeting', str, 1, False, + { + 'default': 'hold', + 'const': 'always', + 'nargs': '?', + 'choices': ['hold', 'switch'], + 'help': '''\ Choose what the default Z-targeting is ''' - }, - { - 'text': 'Default Targeting Option', - 'group': 'other', - 'widget': 'Combobox', - 'default': 'Hold', - 'options': { - 'Hold': 'hold', - 'Switch': 'switch', - } - }), + }, + { + 'text': 'Default Targeting Option', + 'group': 'other', + 'widget': 'Combobox', + 'default': 'Hold', + 'options': { + 'Hold': 'hold', + 'Switch': 'switch', + } + }), ####################### - #area intro cutscenes + # area intro cutscenes ####################### - Setting_Info('skip_field', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_field', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': 'Hyrule Field (affects time of day)', - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'unchecked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Hyrule Field (affects time of day)', + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'unchecked', + 'tooltip': '''\ Remove area intro cutscene for this location. Note that enabling this option will add to initial time of day, making it start later in the day. This can cause problems making it to market for ESS Owl Skip or Aqua Escape. ''' - }), + }), - Setting_Info('skip_castle', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_castle', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': 'Hyrule Castle (affects time of day)', - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'unchecked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Hyrule Castle (affects time of day)', + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'unchecked', + 'tooltip': '''\ Remove area intro cutscene for this location. Note that enabling this option will add to initial time of day, making it start later in the day. This can cause problems making it to market for ESS Owl Skip or Aqua Escape. ''' - }), + }), - Setting_Info('skip_kak', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_kak', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': 'Kakariko Village', - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Kakariko Village', + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), - Setting_Info('skip_gy', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_gy', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': 'Graveyard', - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Graveyard', + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), - Setting_Info('skip_dmt', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_dmt', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': 'Death Mountain Trail', - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Death Mountain Trail', + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), - Setting_Info('skip_gc', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_gc', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': 'Goron City', - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Goron City', + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), - Setting_Info('skip_dmc', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_dmc', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': "Death Mountain Crater", - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': "Death Mountain Crater", + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), - Setting_Info('skip_domain', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_domain', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': "Zora's Domain", - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': "Zora's Domain", + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), - Setting_Info('skip_fountain', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_fountain', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': "Zora's Fountain", - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': "Zora's Fountain", + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), - Setting_Info('skip_lh', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_lh', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': 'Lake Hylia', - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Lake Hylia', + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), - Setting_Info('skip_gv', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_gv', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': 'Gerudo Valley', - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Gerudo Valley', + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), - Setting_Info('skip_gf', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_gf', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': 'Gerudo Fortress', - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Gerudo Fortress', + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), - Setting_Info('skip_colossus', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_colossus', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': 'Desert Colossus', - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Desert Colossus', + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), - Setting_Info('skip_deku', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_deku', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': 'Deku Tree', - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': 'Deku Tree', + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), - Setting_Info('skip_dc', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_dc', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': "Dodongo's Cavern", - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': "Dodongo's Cavern", + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), - Setting_Info('skip_jabu', bool, 1, True, - { - 'help': '''\ + Setting_Info('skip_jabu', bool, 1, True, + { + 'help': '''\ Remove area intro cutscene for this location ''', - 'action': 'store_true' - }, - { - 'text': "Jabu Jabu's Belly", - 'group': 'convenience', - 'widget': 'Checkbutton', - 'default': 'checked', - 'tooltip':'''\ + 'action': 'store_true' + }, + { + 'text': "Jabu Jabu's Belly", + 'group': 'convenience', + 'widget': 'Checkbutton', + 'default': 'checked', + 'tooltip': '''\ Remove area intro cutscene for this location ''' - }), + }), ] + # gets the randomizer settings, whether to open the gui, and the logger level from command line arguments def get_settings_from_command_line_args(): parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) @@ -1306,8 +1327,10 @@ def get_settings_from_command_line_args(): parser.add_argument("--" + info.name, **info.args_params) parser.add_argument('--gui', help='Launch the GUI', action='store_true') - parser.add_argument('--loglevel', default='info', const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.') - parser.add_argument('--settings_string', help='Provide sharable settings using a settings string. This will override all flags that it specifies.') + parser.add_argument('--loglevel', default='info', const='info', nargs='?', + choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.') + parser.add_argument('--settings_string', + help='Provide sharable settings using a settings string. This will override all flags that it specifies.') args = parser.parse_args() diff --git a/TextBox.py b/TextBox.py index 41a9562..c30db5b 100644 --- a/TextBox.py +++ b/TextBox.py @@ -46,7 +46,7 @@ def _wrapLines(text): currentLinePlusWord.append(word) currentLinePlusWordWidth = _calculateWidth(currentLinePlusWord) - if (currentLinePlusWordWidth <= LINE_WIDTH): + if currentLinePlusWordWidth <= LINE_WIDTH: currentLine = currentLinePlusWord currentWidth = currentLinePlusWordWidth else: @@ -86,7 +86,7 @@ def _getCharacterWidth(character): elif character == '@': return characterTable['M'] * 8 # A sane default with the most common character width - else : + else: return characterTable[' '] @@ -97,83 +97,84 @@ def _getCharacterWidth(character): # Larger numbers in the denominator mean more of that character fits on a line; conversely, larger values in this table # mean the character is wider and can't fit as many on one line. characterTable = { - 'a': 51480, # LINE_WIDTH / 35 - 'b': 51480, # LINE_WIDTH / 35 - 'c': 51480, # LINE_WIDTH / 35 - 'd': 51480, # LINE_WIDTH / 35 - 'e': 51480, # LINE_WIDTH / 35 - 'f': 34650, # LINE_WIDTH / 52 - 'g': 51480, # LINE_WIDTH / 35 - 'h': 51480, # LINE_WIDTH / 35 - 'i': 25740, # LINE_WIDTH / 70 - 'j': 34650, # LINE_WIDTH / 52 - 'k': 51480, # LINE_WIDTH / 35 - 'l': 25740, # LINE_WIDTH / 70 - 'm': 81900, # LINE_WIDTH / 22 - 'n': 51480, # LINE_WIDTH / 35 - 'o': 51480, # LINE_WIDTH / 35 - 'p': 51480, # LINE_WIDTH / 35 - 'q': 51480, # LINE_WIDTH / 35 - 'r': 42900, # LINE_WIDTH / 42 - 's': 51480, # LINE_WIDTH / 35 - 't': 42900, # LINE_WIDTH / 42 - 'u': 51480, # LINE_WIDTH / 35 - 'v': 51480, # LINE_WIDTH / 35 - 'w': 81900, # LINE_WIDTH / 22 - 'x': 51480, # LINE_WIDTH / 35 - 'y': 51480, # LINE_WIDTH / 35 - 'z': 51480, # LINE_WIDTH / 35 - 'A': 81900, # LINE_WIDTH / 22 - 'B': 51480, # LINE_WIDTH / 35 - 'C': 72072, # LINE_WIDTH / 25 - 'D': 72072, # LINE_WIDTH / 25 - 'E': 51480, # LINE_WIDTH / 35 - 'F': 51480, # LINE_WIDTH / 35 - 'G': 81900, # LINE_WIDTH / 22 - 'H': 60060, # LINE_WIDTH / 30 - 'I': 25740, # LINE_WIDTH / 70 - 'J': 51480, # LINE_WIDTH / 35 - 'K': 60060, # LINE_WIDTH / 30 - 'L': 51480, # LINE_WIDTH / 35 - 'M': 81900, # LINE_WIDTH / 22 - 'N': 72072, # LINE_WIDTH / 25 - 'O': 81900, # LINE_WIDTH / 22 - 'P': 51480, # LINE_WIDTH / 35 - 'Q': 81900, # LINE_WIDTH / 22 - 'R': 60060, # LINE_WIDTH / 30 - 'S': 60060, # LINE_WIDTH / 30 - 'T': 51480, # LINE_WIDTH / 35 - 'U': 60060, # LINE_WIDTH / 30 - 'V': 72072, # LINE_WIDTH / 25 - 'W': 100100, # LINE_WIDTH / 18 - 'X': 72072, # LINE_WIDTH / 25 - 'Y': 60060, # LINE_WIDTH / 30 - 'Z': 60060, # LINE_WIDTH / 30 - ' ': 51480, # LINE_WIDTH / 35 - '1': 25740, # LINE_WIDTH / 70 - '2': 51480, # LINE_WIDTH / 35 - '3': 51480, # LINE_WIDTH / 35 - '4': 60060, # LINE_WIDTH / 30 - '5': 51480, # LINE_WIDTH / 35 - '6': 51480, # LINE_WIDTH / 35 - '7': 51480, # LINE_WIDTH / 35 - '8': 51480, # LINE_WIDTH / 35 - '9': 51480, # LINE_WIDTH / 35 - '0': 60060, # LINE_WIDTH / 30 - '!': 51480, # LINE_WIDTH / 35 - '?': 72072, # LINE_WIDTH / 25 - '\'': 17325, # LINE_WIDTH / 104 - '"': 34650, # LINE_WIDTH / 52 - '.': 25740, # LINE_WIDTH / 70 - ',': 25740, # LINE_WIDTH / 70 - '/': 51480, # LINE_WIDTH / 35 - '-': 34650, # LINE_WIDTH / 52 - '_': 51480, # LINE_WIDTH / 35 - '(': 42900, # LINE_WIDTH / 42 - ')': 42900, # LINE_WIDTH / 42 - '$': 51480 # LINE_WIDTH / 35 + 'a': 51480, # LINE_WIDTH / 35 + 'b': 51480, # LINE_WIDTH / 35 + 'c': 51480, # LINE_WIDTH / 35 + 'd': 51480, # LINE_WIDTH / 35 + 'e': 51480, # LINE_WIDTH / 35 + 'f': 34650, # LINE_WIDTH / 52 + 'g': 51480, # LINE_WIDTH / 35 + 'h': 51480, # LINE_WIDTH / 35 + 'i': 25740, # LINE_WIDTH / 70 + 'j': 34650, # LINE_WIDTH / 52 + 'k': 51480, # LINE_WIDTH / 35 + 'l': 25740, # LINE_WIDTH / 70 + 'm': 81900, # LINE_WIDTH / 22 + 'n': 51480, # LINE_WIDTH / 35 + 'o': 51480, # LINE_WIDTH / 35 + 'p': 51480, # LINE_WIDTH / 35 + 'q': 51480, # LINE_WIDTH / 35 + 'r': 42900, # LINE_WIDTH / 42 + 's': 51480, # LINE_WIDTH / 35 + 't': 42900, # LINE_WIDTH / 42 + 'u': 51480, # LINE_WIDTH / 35 + 'v': 51480, # LINE_WIDTH / 35 + 'w': 81900, # LINE_WIDTH / 22 + 'x': 51480, # LINE_WIDTH / 35 + 'y': 51480, # LINE_WIDTH / 35 + 'z': 51480, # LINE_WIDTH / 35 + 'A': 81900, # LINE_WIDTH / 22 + 'B': 51480, # LINE_WIDTH / 35 + 'C': 72072, # LINE_WIDTH / 25 + 'D': 72072, # LINE_WIDTH / 25 + 'E': 51480, # LINE_WIDTH / 35 + 'F': 51480, # LINE_WIDTH / 35 + 'G': 81900, # LINE_WIDTH / 22 + 'H': 60060, # LINE_WIDTH / 30 + 'I': 25740, # LINE_WIDTH / 70 + 'J': 51480, # LINE_WIDTH / 35 + 'K': 60060, # LINE_WIDTH / 30 + 'L': 51480, # LINE_WIDTH / 35 + 'M': 81900, # LINE_WIDTH / 22 + 'N': 72072, # LINE_WIDTH / 25 + 'O': 81900, # LINE_WIDTH / 22 + 'P': 51480, # LINE_WIDTH / 35 + 'Q': 81900, # LINE_WIDTH / 22 + 'R': 60060, # LINE_WIDTH / 30 + 'S': 60060, # LINE_WIDTH / 30 + 'T': 51480, # LINE_WIDTH / 35 + 'U': 60060, # LINE_WIDTH / 30 + 'V': 72072, # LINE_WIDTH / 25 + 'W': 100100, # LINE_WIDTH / 18 + 'X': 72072, # LINE_WIDTH / 25 + 'Y': 60060, # LINE_WIDTH / 30 + 'Z': 60060, # LINE_WIDTH / 30 + ' ': 51480, # LINE_WIDTH / 35 + '1': 25740, # LINE_WIDTH / 70 + '2': 51480, # LINE_WIDTH / 35 + '3': 51480, # LINE_WIDTH / 35 + '4': 60060, # LINE_WIDTH / 30 + '5': 51480, # LINE_WIDTH / 35 + '6': 51480, # LINE_WIDTH / 35 + '7': 51480, # LINE_WIDTH / 35 + '8': 51480, # LINE_WIDTH / 35 + '9': 51480, # LINE_WIDTH / 35 + '0': 60060, # LINE_WIDTH / 30 + '!': 51480, # LINE_WIDTH / 35 + '?': 72072, # LINE_WIDTH / 25 + '\'': 17325, # LINE_WIDTH / 104 + '"': 34650, # LINE_WIDTH / 52 + '.': 25740, # LINE_WIDTH / 70 + ',': 25740, # LINE_WIDTH / 70 + '/': 51480, # LINE_WIDTH / 35 + '-': 34650, # LINE_WIDTH / 52 + '_': 51480, # LINE_WIDTH / 35 + '(': 42900, # LINE_WIDTH / 42 + ')': 42900, # LINE_WIDTH / 42 + '$': 51480 # LINE_WIDTH / 35 } + # To run tests, enter the following into a python3 REPL: # >>> from TextBox import test_lineWrapTests # >>> test_lineWrapTests() diff --git a/Utils.py b/Utils.py index 1d6b8fd..79e1703 100644 --- a/Utils.py +++ b/Utils.py @@ -1,29 +1,30 @@ import os import subprocess import sys -import urllib.request -from urllib.error import URLError, HTTPError -import re from random import choice as random_choice + def is_bundled(): return getattr(sys, 'frozen', False) + def local_path(path): if local_path.cached_path is not None: return os.path.join(local_path.cached_path, path) if is_bundled(): # we are running in a bundle - local_path.cached_path = sys._MEIPASS # pylint: disable=protected-access,no-member + local_path.cached_path = sys._MEIPASS # pylint: disable=protected-access,no-member else: # we are running in a normal Python environment local_path.cached_path = os.path.dirname(os.path.abspath(__file__)) return os.path.join(local_path.cached_path, path) + local_path.cached_path = None + def default_output_path(path): if path == '': path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Output') @@ -40,9 +41,10 @@ def open_file(filename): open_command = 'open' if sys.platform == 'darwin' else 'xdg-open' subprocess.call([open_command, filename]) + def close_console(): if sys.platform == 'win32': - #windows + # windows import ctypes.wintypes try: ctypes.windll.kernel32.FreeConsole() @@ -55,7 +57,7 @@ def close_console(): # as neither were used elsewhere at the time of writing. def random_choices(population, weights=None, k=1): pop_size = len(population) - if (weights is None): + if weights is None: weights = [1] * pop_size else: assert (pop_size == len(weights)), "population and weights mismatch" diff --git a/data/symbols.json b/data/symbols.json index e34d889..400301b 100644 --- a/data/symbols.json +++ b/data/symbols.json @@ -1,39 +1,39 @@ { - "CFG_RAINBOW_SWORD_INNER_ENABLED": "03481C03", - "CFG_RAINBOW_SWORD_OUTER_ENABLED": "03481C04", - "CS_POINTER": "03481C05", - "DPAD_TEXTURE": "034B61CE", - "EXTENDED_ITEM_DATA": "03482008", - "FAIRY_ITEMS": "03481C0F", - "FAIRY_OCARINA_ITEM": "03481C0E", - "FONT_TEXTURE": "034B4D06", - "INITIAL_SAVE_DATA": "03481800", - "ITEM_OVERRIDES": "03481000", - "ITEM_TABLE": "03482050", - "JABU_ENABLE": "03481C02", - "LIGHT_ARROW_ITEM": "03481C0D", - "PENDING_SPECIAL_ITEM": "03482018", - "PENDING_SPECIAL_ITEM_END": "0348201B", - "PLAYER_ID": "03481C00", - "PLAYER_OVERRIDE_DATA": "03482000", - "QUICKBOOTS_ENABLE": "03481C01", - "TIME_TRAVEL_SAVED_EQUIPS": "0348201C", - "cfg_dungeon_info_enable": "034B4CB8", - "cfg_dungeon_info_mq_enable": "034B4CD0", - "cfg_dungeon_info_mq_need_map": "034B4CCC", - "cfg_dungeon_info_reward_need_altar": "034B4CC4", - "cfg_dungeon_info_reward_need_compass": "034B4CC8", - "cfg_dungeon_is_mq": "034B4CF8", - "cfg_dungeon_rewards": "034B4BE0", - "dpad_sprite": "034B4B80", - "dungeon_count": "034B4CBC", - "dungeons": "034B4C04", - "font_sprite": "034B4B90", - "heap_next": "034B4CC0", - "items_sprite": "034B4BB0", - "medal_colors": "034B4BF0", - "medals_sprite": "034B4BC0", - "quest_items_sprite": "034B4BA0", - "setup_db": "034B4CE8", - "stones_sprite": "034B4BD0" + "CFG_RAINBOW_SWORD_INNER_ENABLED": "03481C03", + "CFG_RAINBOW_SWORD_OUTER_ENABLED": "03481C04", + "CS_POINTER": "03481C05", + "DPAD_TEXTURE": "034B61CE", + "EXTENDED_ITEM_DATA": "03482008", + "FAIRY_ITEMS": "03481C0F", + "FAIRY_OCARINA_ITEM": "03481C0E", + "FONT_TEXTURE": "034B4D06", + "INITIAL_SAVE_DATA": "03481800", + "ITEM_OVERRIDES": "03481000", + "ITEM_TABLE": "03482050", + "JABU_ENABLE": "03481C02", + "LIGHT_ARROW_ITEM": "03481C0D", + "PENDING_SPECIAL_ITEM": "03482018", + "PENDING_SPECIAL_ITEM_END": "0348201B", + "PLAYER_ID": "03481C00", + "PLAYER_OVERRIDE_DATA": "03482000", + "QUICKBOOTS_ENABLE": "03481C01", + "TIME_TRAVEL_SAVED_EQUIPS": "0348201C", + "cfg_dungeon_info_enable": "034B4CB8", + "cfg_dungeon_info_mq_enable": "034B4CD0", + "cfg_dungeon_info_mq_need_map": "034B4CCC", + "cfg_dungeon_info_reward_need_altar": "034B4CC4", + "cfg_dungeon_info_reward_need_compass": "034B4CC8", + "cfg_dungeon_is_mq": "034B4CF8", + "cfg_dungeon_rewards": "034B4BE0", + "dpad_sprite": "034B4B80", + "dungeon_count": "034B4CBC", + "dungeons": "034B4C04", + "font_sprite": "034B4B90", + "heap_next": "034B4CC0", + "items_sprite": "034B4BB0", + "medal_colors": "034B4BF0", + "medals_sprite": "034B4BC0", + "quest_items_sprite": "034B4BA0", + "setup_db": "034B4CE8", + "stones_sprite": "034B4BD0" } \ No newline at end of file