From c605046337d844a39331bdc8499b21ba8657b67c Mon Sep 17 00:00:00 2001 From: LLCoolDave Date: Fri, 26 May 2017 18:40:48 +0200 Subject: [PATCH] Add some support for creating a plandomizer. --- Plando.py | 255 +++++++++++++++++++++++++++++++++++++++ Plandomizer_Template.txt | 244 +++++++++++++++++++++++++++++++++++++ 2 files changed, 499 insertions(+) create mode 100644 Plando.py create mode 100644 Plandomizer_Template.txt diff --git a/Plando.py b/Plando.py new file mode 100644 index 00000000..167f8a82 --- /dev/null +++ b/Plando.py @@ -0,0 +1,255 @@ +from BaseClasses import World, CollectionState, Item +from Regions import create_regions +from EntranceShuffle import link_entrances +from Rom import patch_rom +from Rules import set_rules +from Items import ItemFactory +import random +import time +import logging +import argparse +import os +import hashlib + +__version__ = '0.1-dev' + +logic_hash = [182, 244, 144, 92, 149, 200, 93, 183, 124, 169, 226, 46, 111, 163, 5, 193, 13, 112, 125, 101, 128, 84, 31, 67, 107, 94, 184, 100, 189, 18, 8, 171, + 142, 57, 173, 38, 37, 211, 253, 131, 98, 239, 167, 116, 32, 186, 70, 148, 66, 151, 143, 86, 59, 83, 16, 51, 240, 152, 60, 242, 190, 117, 76, 122, + 15, 221, 62, 39, 174, 177, 223, 34, 150, 50, 178, 238, 95, 219, 10, 162, 222, 0, 165, 202, 74, 36, 206, 209, 251, 105, 175, 135, 121, 88, 214, 247, + 154, 161, 71, 19, 85, 157, 40, 96, 225, 27, 230, 49, 231, 207, 64, 35, 249, 134, 132, 108, 63, 24, 4, 127, 255, 14, 145, 23, 81, 216, 113, 90, 194, + 110, 65, 229, 43, 1, 11, 168, 147, 103, 156, 77, 80, 220, 28, 227, 213, 198, 172, 79, 75, 140, 44, 146, 188, 17, 6, 102, 56, 235, 166, 89, 218, 246, + 99, 78, 187, 126, 119, 196, 69, 137, 181, 55, 20, 215, 199, 130, 9, 45, 58, 185, 91, 33, 197, 72, 115, 195, 114, 29, 30, 233, 141, 129, 155, 159, 47, + 224, 236, 21, 234, 191, 136, 104, 87, 106, 26, 73, 250, 248, 228, 48, 53, 243, 237, 241, 61, 180, 12, 208, 245, 232, 192, 2, 7, 170, 123, 176, 160, 201, + 153, 217, 252, 158, 25, 205, 22, 133, 254, 138, 203, 118, 210, 204, 82, 97, 52, 164, 68, 139, 120, 109, 54, 3, 41, 179, 212, 42] + + +def main(args, seed=None): + start = time.clock() + + # initialize the world + world = World('default', 'noglitches', 'standard', 'normal', 'ganon', False) + logger = logging.getLogger('') + + hasher = hashlib.md5() + with open(args.plando, 'rb') as plandofile: + buf = plandofile.read() + hasher.update(buf) + world.seed = int(hasher.hexdigest(), 16) % 1000000000 + + random.seed(world.seed) + + world.spoiler += 'ALttP Plandomizer Version %s - Seed: %s\n\n' % (__version__, args.plando) + + logger.info(world.spoiler) + + create_regions(world) + + world.spoiler += link_entrances(world) + + logger.info('Calculating Access Rules.') + + world.spoiler += set_rules(world) + + logger.info('Fill the world.') + + world.spoiler += fill_world(world, args.plando) + + world.spoiler += print_location_spoiler(world) + + logger.info('Calculating playthrough.') + + world.spoiler += create_playthrough(world) + + logger.info('Patching ROM.') + + if args.sprite is not None: + sprite = bytearray(open(args.sprite, 'rb').read()) + else: + sprite = None + + rom = bytearray(open(args.rom, 'rb').read()) + patched_rom = patch_rom(world, rom, logic_hash, args.quickswap, args.heartbeep, sprite) + + outfilebase = 'Plando_%s_%s' % (os.path.splitext(os.path.basename(args.plando))[0], world.seed) + + with open('%s.sfc' % outfilebase, 'wb') as outfile: + outfile.write(patched_rom) + if args.create_spoiler: + with open('%s_Spoiler.txt' % outfilebase, 'w') as outfile: + outfile.write(world.spoiler) + + logger.info('Done. Enjoy.') + logger.debug('Total Time: %s' % (time.clock() - start)) + + return world + + +def fill_world(world, plando): + mm_medallion = 'Ether' + tr_medallion = 'Quake' + logger = logging.getLogger('') + with open(plando, 'r') as plandofile: + for line in plandofile.readlines(): + if ':' in line: + line = line.lstrip() + + if line.startswith('#'): + continue + if line.startswith('!'): + if line.startswith('!mm_medallion'): + _, medallionstr = line.split(':', 1) + mm_medallion = medallionstr.strip() + elif line.startswith('!tr_medallion'): + _, medallionstr = line.split(':', 1) + tr_medallion = medallionstr.strip() + elif line.startswith('!mode'): + _, modestr = line.split(':', 1) + world.mode = modestr.strip() + elif line.startswith('!logic'): + _, logicstr = line.split(':', 1) + world.logic = logicstr.strip() + elif line.startswith('!goal'): + _, goalstr = line.split(':', 1) + world.goal = goalstr.strip() + continue + + locationstr, itemstr = line.split(':', 1) + location = world.get_location(locationstr.strip()) + if location is None: + logger.warn('Unknown location: %s' % locationstr) + continue + else: + item = ItemFactory(itemstr.strip()) + if item is not None: + world.push_item(location, item) + + world.required_medallions = (mm_medallion, tr_medallion) + return 'Misery Mire Medallion: %s\nTurtle Rock Medallion: %s\n\n' % (mm_medallion, tr_medallion) + + +def copy_world(world): + # ToDo: Not good yet + ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.goal, world.place_dungeon_items) + ret.required_medallions = list(world.required_medallions) + ret.agahnim_fix_required = world.agahnim_fix_required + ret.swamp_patch_required = world.swamp_patch_required + create_regions(ret) + + # connect copied world + for region in world.regions: + for entrance in region.entrances: + ret.get_entrance(entrance.name).connect(ret.get_region(region.name)) + + set_rules(ret) + + # fill locations + for location in world.get_locations(): + if location.item is not None: + item = Item(location.item.name, location.item.advancement, location.item.key) + ret.get_location(location.name).item = item + item.location = ret.get_location(location.name) + + # copy remaining itempool. No item in itempool should have an assigned location + for item in world.itempool: + ret.itempool.append(Item(item.name, item.advancement, item.key)) + + # copy progress items in state + ret.state.prog_items = list(world.state.prog_items) + + return ret + + +def create_playthrough(world): + # create a copy as we will modify it + world = copy_world(world) + + # if we do pedestal%, ganon should not be a viable option as far as the playthrough is concerned + if world.goal == 'pedestal': + world.get_location('Ganon').item = None + + # get locations containing progress items + prog_locations = [location for location in world.get_locations() if location.item is not None and location.item.advancement] + + collection_spheres = [] + state = CollectionState(world) + sphere_candidates = list(prog_locations) + logging.getLogger('').debug('Building up collection spheres.') + while sphere_candidates: + sphere = [] + # build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres + for location in sphere_candidates: + if state.can_reach(location): + sphere.append(location) + + for location in sphere: + sphere_candidates.remove(location) + state.collect(location.item) + + collection_spheres.append(sphere) + + logging.getLogger('').debug('Calculated sphere %i, containing %i of %i progress items.' % (len(collection_spheres), len(sphere), len(prog_locations))) + + if not sphere: + logging.getLogger('').debug('The following items could not be placed: %s' % ['%s at %s' % (location.item.name, location.name) for location in sphere_candidates]) + raise RuntimeError('Not all progression items reachable. Something went terribly wrong here.') + + # in the second phase, we cull each sphere such that the game is still beatable, reducing each range of influence to the bare minimum required inside it + for sphere in reversed(collection_spheres): + to_delete = [] + for location in sphere: + # we remove the item at location and check if game is still beatable + logging.getLogger('').debug('Checking if %s is required to beat the game.' % location.item.name) + old_item = location.item + location.item = None + state.remove(old_item) + world._item_cache = {} # need to invalidate + if world.can_beat_game(): + to_delete.append(location) + else: + # still required, got to keep it around + location.item = old_item + + # cull entries in spheres for spoiler walkthrough at end + for location in to_delete: + sphere.remove(location) + + # we are now down to just the required progress items in collection_spheres in a minimum number of spheres. As a cleanup, we right trim empty spheres (can happen if we have multiple triforces) + collection_spheres = [sphere for sphere in collection_spheres if sphere] + + # we can finally output our playthrough + return 'Playthrough:\n' + ''.join(['%s: {\n%s}\n' % (i + 1, ''.join([' %s: %s\n' % (location, location.item) for location in sphere])) for i, sphere in enumerate(collection_spheres)]) + '\n' + + +def print_location_spoiler(world): + return 'Locations:\n\n' + '\n'.join(['%s: %s' % (location, location.item if location.item is not None else 'Nothing') for location in world.get_locations()]) + '\n\n' + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true') + parser.add_argument('--rom', default='Base_Rom.sfc', help='Path to a VT21 standard normal difficulty rom to use as a base.') + parser.add_argument('--loglevel', default='info', const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.') + parser.add_argument('--seed', help='Define seed number to generate.', type=int) + parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true') + parser.add_argument('--heartbeep', default='normal', const='normal', nargs='?', choices=['normal', 'half', 'quarter', 'off'], + help='Select the rate at which the heart beep sound is played at low health.') + parser.add_argument('--sprite', help='Path to a sprite sheet to use for Link. Needs to be in binary format and have a length of 0x7000 (28672) bytes.') + parser.add_argument('--plando', help='Filled out template to use for setting up the rom.') + args = parser.parse_args() + + # ToDo: Validate files further than mere existance + if not os.path.isfile(args.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.' % args.rom) + exit(1) + if not os.path.isfile(args.plando): + input('Could not find Plandomizer distribution at expected path %s. Please run with -h to see help for further information. \nPress Enter to exit.' % args.plando) + exit(1) + if args.sprite is not None and not os.path.isfile(args.rom): + input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite) + exit(1) + + # set up logger + loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[args.loglevel] + logging.basicConfig(format='%(message)s', level=loglevel) + + main(args=args) diff --git a/Plandomizer_Template.txt b/Plandomizer_Template.txt new file mode 100644 index 00000000..667c743c --- /dev/null +++ b/Plandomizer_Template.txt @@ -0,0 +1,244 @@ +# Lines starting with a # are comments and ignored by the parsers +# Lines without a : are also ignored + +# These are special instructions for setting the medallion requirements to enter the dungeons +!mm_medallion: Bombos +!tr_medallion: Quake + +# This sets the game mode +!mode: open + +# This sets the logic (for verification purposes) +!logic: noglitches + +# This sets the goal (only used for generating the spoiler log) +!goal: ganon + +# Now we fill in all locations + +Mushroom: Mushroom +Bottle Vendor: Bottle +Haunted Grove: Ocarina +Piece of Heart (Dam): Nothing +Purple Chest: Nothing +[cave-022-B1] Thiefs hut [top chest]: Nothing +[cave-022-B1] Thiefs hut [top left chest]: Nothing +[cave-022-B1] Thiefs hut [top right chest]: Nothing +[cave-022-B1] Thiefs hut [bottom left chest]: Nothing +[cave-022-B1] Thiefs hut [bottom right chest]: Nothing +Uncle: Fighter Sword +[cave-034] Hyrule Castle Secret Entrance: Nothing +King Zora: Flippers +Piece of Heart (Zoras River): Nothing +[cave-018] Graveyard - top right grave: Cape +[cave-047] Dam: Nothing +[cave-040] Links House: Lamp +[cave-031] Tavern: Nothing +[cave-026] Chicken House: Nothing +[cave-044] Aginahs Cave: Nothing +[cave-035] Sahasrahlas Hut [left chest]: Nothing +[cave-035] Sahasrahlas Hut [center chest]: Nothing +[cave-035] Sahasrahlas Hut [right chest]: Nothing +Sahasrahla: Pegasus Boots +[cave-021] Kakariko Well [top chest]: Nothing +[cave-021] Kakariko Well [left chest row of 3]: Nothing +[cave-021] Kakariko Well [center chest row of 3]: Nothing +[cave-021] Kakariko Well [right chest row of 3]: Nothing +[cave-021] Kakariko Well [bottom chest]: Nothing +Blacksmiths: Tempered Sword +Magic Bat: Magic Upgrade (1/2) +Sick Kid: Bug Catching Net +Hobo: Bottle +Piece of Heart (Thieves Forest Hideout): Nothing +Piece of Heart (Lumberjack Tree): Nothing +Piece of Heart (Cave South of Haunted Grove): Nothing +Piece of Heart (Graveyard Cave): Nothing +Piece of Heart (Desert Cave): Nothing +[cave-050] Lake Hylia Cave [bottom left chest]: Nothing +[cave-050] Lake Hylia Cave [top left chest]: Nothing +[cave-050] Lake Hylia Cave [top right chest]: Nothing +[cave-050] Lake Hylia Cave [bottom right chest]: Nothing +[cave-050] Lake Hylia Cave [generous guy]: Nothing +[cave-051] Ice Cave: Ice Rod +[cave-016] Bonk Rock Cave: Nothing +Library: Book of Mudora +Witch: Magic Powder +Piece of Heart (Lake Hylia): Nothing +Piece of Heart (Maze Race): Nothing +Piece of Heart (Desert - west side): Nothing +[dungeon-L2-B1] Desert Palace - Big Chest: Power Glove +[dungeon-L2-B1] Desert Palace - Torch: Small Key (Desert Palace) +[dungeon-L2-B1] Desert Palace - Map Room: Nothing +[dungeon-L2-B1] Desert Palace - Compass Room: Nothing +[dungeon-L2-B1] Desert Palace - Big Key Room: Big Key (Desert Palace) +Lanmolas - Heart Container: Nothing +Lanmolas - Pendant: Blue Pendant +[dungeon-L1-1F] Eastern Palace - Compass Room: Nothing +[dungeon-L1-1F] Eastern Palace - Big Chest: Bow +[dungeon-L1-1F] Eastern Palace - Big Ball Room: Nothing +[dungeon-L1-1F] Eastern Palace - Big Key Room: Big Key (Eastern Palace) +[dungeon-L1-1F] Eastern Palace - Map Room: Nothing +Armos - Heart Container: Nothing +Armos - Pendant: Green Pendant +Altar: Master Sword +[dungeon-C-B1] Hyrule Castle - Boomerang Room: Nothing +[dungeon-C-B1] Hyrule Castle - Map Room: Nothing +[dungeon-C-B1] Hyrule Castle - Next To Zelda: Nothing +[dungeon-C-B1] Escape - First B1 Room: Small Key (Escape) +[dungeon-C-B1] Escape - Final Basement Room [left chest]: Nothing +[dungeon-C-B1] Escape - Final Basement Room [middle chest]: Nothing +[dungeon-C-B1] Escape - Final Basement Room [right chest]: Nothing +[dungeon-C-1F] Sanctuary: Sanctuary Heart Container +[dungeon-A1-2F] Hyrule Castle Tower - 2 Knife Guys Room: Small Key (Agahnims Tower) +[dungeon-A1-3F] Hyrule Castle Tower - Maze Room: Small Key (Agahnims Tower) +Old Mountain Man: Magic Mirror +Piece of Heart (Spectacle Rock Cave): Nothing +[cave-009-1F] Death Mountain - right cave [top left chest]: Nothing +[cave-009-1F] Death Mountain - right cave [top left middle chest]: Nothing +[cave-009-1F] Death Mountain - right cave [top right middle chest]: Nothing +[cave-009-1F] Death Mountain - right cave [top right chest]: Nothing +[cave-009-1F] Death Mountain - right cave [bottom chest]: Nothing +[cave-009-B1] Death Mountain - right cave [left chest]: Nothing +[cave-009-B1] Death Mountain - right cave [right chest]: Nothing +[cave-012-1F] Death Mountain - left cave]: Nothing +Ether Tablet: Ether +Piece of Heart (Spectacle Rock): Nothing +[dungeon-L3-1F] Tower of Hera - Freestanding Key: Small Key (Tower of Hera) +[dungeon-L3-1F] Tower of Hera - Entrance: Nothing +[dungeon-L3-1F] Tower of Hera - Basement: Big Key (Tower of Hera) +[dungeon-L3-1F] Tower of Hera - 4F [small chest]: Nothing +[dungeon-L3-1F] Tower of Hera - Big Chest: Moon Pearl +Moldorm - Heart Container: Nothing +Moldorm - Pendant: Red Pendant +Piece of Heart (Pyramid): Nothing +Catfish: Quake +Flute Boy: Shovel +Piece of Heart (Digging Game): Nothing +Bombos Tablet: Bombos +[cave-073] Cave Northeast of Swamp Palace [top chest]: Nothing +[cave-073] Cave Northeast of Swamp Palace [top middle chest]: Nothing +[cave-073] Cave Northeast of Swamp Palace [bottom middle chest]: Nothing +[cave-073] Cave Northeast of Swamp Palace [bottom chest]: Nothing +[cave-073] Cave Northeast of Swamp Palace [generous guy]: Nothing +Piece of Heart (Dark World Blacksmith Pegs): Nothing +Pyramid Fairy [left chest]: Golden Sword +Pyramid Fairy [right chest]: Silver Arrows +[cave-063] Doorless Hut: Nothing +[cave-062] C-Shaped House: Nothing +Piece of Heart (Treasure Chest Game): Nothing +Piece of Heart (Bumper Cave): Nothing +[cave-071] Misery Mire West Area [left chest]: Nothing +[cave-071] Misery Mire West Area [right chest]: Nothing +[cave-057-1F] Dark World Death Mountain Climb [top chest]: Nothing +[cave-057-1F] Dark World Death Mountain Climb [bottom chest]: Nothing +[cave-055] Spike Cave: Cane of Byrna +[cave-056] Hookshot Cave [top right chest]: Nothing +[cave-056] Hookshot Cave [top left chest]: Nothing +[cave-056] Hookshot Cave [bottom right chest]: Nothing +[cave-056] Hookshot Cave [bottom left chest]: Nothing +Piece of Heart (Death Mountain - Floating Island): Nothing +[cave-013] Mimic Cave: Nothing +[dungeon-D2-1F] Swamp Palace - First Room: Small Key (Swamp Palace) +[dungeon-D2-1F] Swamp Palace - Map Room: Nothing +[dungeon-D2-B1] Swamp Palace - Big Chest: Hookshot +[dungeon-D2-B1] Swamp Palace - South of Hookshot Room: Nothing +[dungeon-D2-B1] Swamp Palace - Big Key Chest: Big Key (Swamp Palace) +[dungeon-D2-B1] Swamp Palace - Compass Chest: Nothing +[dungeon-D2-B2] Swamp Palace - Flooded Room [left chest]: Nothing +[dungeon-D2-B2] Swamp Palace - Flooded Room [right chest]: Nothing +[dungeon-D2-B2] Swamp Palace - Waterfall Room: Nothing +Arrghus - Heart Container: Nothing +Arrghus - Crystal: Crystal 2 +[dungeon-D4-B1] Thieves Town - Bottom Left of Huge Room [bottom right chest]: Big Key (Thieves Town) +[dungeon-D4-B1] Thieves Town - Bottom Left of Huge Room [top left chest]: Nothing +[dungeon-D4-B1] Thieves Town - Bottom Right of Huge Room: Nothing +[dungeon-D4-B1] Thieves Town - Top Left of Huge Room: Nothing +[dungeon-D4-1F] Thieves Town - Room above Boss: Nothing +[dungeon-D4-B2] Thieves Town - Big Chest: Titans Mitts +[dungeon-D4-B2] Thieves Town - Chest next to Blind: Small Key (Thieves Town) +Blind - Heart Container: Nothing +Blind - Crystal: Crystal 4 +[dungeon-D3-B1] Skull Woods - Compass Room: Nothing +[dungeon-D3-B1] Skull Woods - East of Big Chest: Nothing +[dungeon-D3-B1] Skull Woods - Big Chest: Fire Rod +[dungeon-D3-B1] Skull Woods - Push Statue Room: Small Key (Skull Woods) +[dungeon-D3-B1] Skull Woods - South of Big Chest: Small Key (Skull Woods) +[dungeon-D3-B1] Skull Woods - Big Key Room: Big Key (Skull Woods) +[dungeon-D3-B1] Skull Woods - Final Section Entrance: Small Key (Skull Woods) +Mothula - Heart Container: Nothing +Mothula - Crystal: Crystal 3 +[dungeon-D5-B1] Ice Palace - Compass Room: Nothing +[dungeon-D5-B4] Ice Palace - Above Big Chest: Nothing +[dungeon-D5-B5] Ice Palace - Big Chest: Blue Mail +[dungeon-D5-B5] Ice Palace - Jellyfish Room: Small Key (Ice Palace) +[dungeon-D5-B3] Ice Palace - Spike Room: Small Key (Ice Palace) +[dungeon-D5-B1] Ice Palace - Big Key Room: Big Key (Ice Palace) +[dungeon-D5-B2] Ice Palace - Map Room: Nothing +Kholdstare - Heart Container: Nothing +Kholdstare - Crystal: Crystal 5 +[dungeon-D6-B1] Misery Mire - Big Chest: Cane of Somaria +[dungeon-D6-B1] Misery Mire - Map Room: Nothing +[dungeon-D6-B1] Misery Mire - Hub Room: Small Key (Misery Mire) +[dungeon-D6-B1] Misery Mire - End of Bridge: Small Key (Misery Mire) +[dungeon-D6-B1] Misery Mire - Spike Room: Small Key (Misery Mire) +[dungeon-D6-B1] Misery Mire - Compass Room: Nothing +[dungeon-D6-B1] Misery Mire - Big Key Room: Big Key (Misery Mire) +Vitreous - Heart Container: Nothing +Vitreous - Crystal: Crystal 6 +[dungeon-D7-1F] Turtle Rock - Compass Room: Nothing +[dungeon-D7-1F] Turtle Rock - Map Room [left chest]: Nothing +[dungeon-D7-1F] Turtle Rock - Map Room [right chest]: Small Key (Turtle Rock) +[dungeon-D7-1F] Turtle Rock - Chain Chomp Room: Small Key (Turtle Rock) +[dungeon-D7-B1] Turtle Rock - Big Key Room: Big Key (Turtle Rock) +[dungeon-D7-B1] Turtle Rock - Big Chest: Mirror Shield +[dungeon-D7-B1] Turtle Rock - Roller Switch Room: Small Key (Turtle Rock) +[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [bottom left chest]: Small Key (Turtle Rock) +[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [bottom right chest]: Nothing +[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top left chest]: Nothing +[dungeon-D7-B2] Turtle Rock - Eye Bridge Room [top right chest]: Nothing +Trinexx - Heart Container: Nothing +Trinexx - Crystal: Crystal 7 +[dungeon-D1-B1] Dark Palace - Shooter Room: Small Key (Palace of Darkness) +[dungeon-D1-1F] Dark Palace - Jump Room [left chest]: Small Key (Palace of Darkness) +[dungeon-D1-B1] Dark Palace - Turtle Stalfos Room: Small Key (Palace of Darkness) +[dungeon-D1-1F] Dark Palace - Big Key Room: Big Key (Palace of Darkness) +[dungeon-D1-1F] Dark Palace - Jump Room [right chest]: Small Key (Palace of Darkness) +[dungeon-D1-1F] Dark Palace - Statue Push Room: Nothing +[dungeon-D1-1F] Dark Palace - Compass Room: Nothing +# logic cannot account for hammer and small key in maze +[dungeon-D1-B1] Dark Palace - Dark Room [left chest]: Small Key (Palace of Darkness) +[dungeon-D1-B1] Dark Palace - Dark Room [right chest]: Small Key (Palace of Darkness) +[dungeon-D1-1F] Dark Palace - Maze Room [top chest]: Nothing +[dungeon-D1-1F] Dark Palace - Maze Room [bottom chest]: Nothing +[dungeon-D1-1F] Dark Palace - Big Chest: Hammer +[dungeon-D1-1F] Dark Palace - Spike Statue Room: Nothing +Helmasaur - Heart Container: Nothing +Helmasaur - Crystal: Crystal 1 +[dungeon-A2-1F] Ganons Tower - Torch: Small Key (Ganons Tower) +[dungeon-A2-1F] Ganons Tower - Right Staircase [left chest]: Nothing +[dungeon-A2-1F] Ganons Tower - Right Staircase [right chest]: Nothing +[dungeon-A2-1F] Ganons Tower - Tile Room: Small Key (Ganons Tower) +[dungeon-A2-1F] Ganons Tower - Compass Room [top left chest]: Nothing +[dungeon-A2-1F] Ganons Tower - Compass Room [top right chest]: Nothing +[dungeon-A2-1F] Ganons Tower - Compass Room [bottom left chest]: Nothing +[dungeon-A2-1F] Ganons Tower - Compass Room [bottom right chest]: Nothing +[dungeon-A2-1F] Ganons Tower - North of Hookshot Room [top left chest]: Nothing +[dungeon-A2-1F] Ganons Tower - North of Hookshot Room [top right chest]: Nothing +[dungeon-A2-1F] Ganons Tower - North of Hookshot Room [bottom left chest]: Nothing +[dungeon-A2-1F] Ganons Tower - North of Hookshot Room [bottom right chest]: Nothing +[dungeon-A2-1F] Ganons Tower - Map Room: Nothing +[dungeon-A2-1F] Ganons Tower - Firesnake Room: Small Key (Ganons Tower) +[dungeon-A2-1F] Ganons Tower - Teleport Room [top left chest]: Nothing +[dungeon-A2-1F] Ganons Tower - Teleport Room [top right chest]: Nothing +[dungeon-A2-1F] Ganons Tower - Teleport Room [bottom left chest]: Nothing +[dungeon-A2-1F] Ganons Tower - Teleport Room [bottom right chest]: Nothing +[dungeon-A2-1F] Ganons Tower - above Armos: Nothing +[dungeon-A2-1F] Ganons Tower - Big Chest: Red Mail +[dungeon-A2-B1] Ganons Tower - Armos Room [left chest]: Nothing +[dungeon-A2-B1] Ganons Tower - Armos Room [right chest]: Nothing +[dungeon-A2-B1] Ganons Tower - Armos Room [bottom chest]: Big Key (Ganons Tower) +[dungeon-A2-6F] Ganons Tower - Mini Helmasaur Room [left chest]: Nothing +[dungeon-A2-6F] Ganons Tower - Mini Helmasaur Room [right chest]: Nothing +[dungeon-A2-6F] Ganons Tower - Room before Moldorm: Small Key (Ganons Tower) +[dungeon-A2-6F] Ganons Tower - Moldorm Room: Nothing +Ganon: Triforce