def start(): settings, gui, args_loglevel, no_log_file = get_settings_from_command_line_args( ) if is_bundled() and len(sys.argv) == 1: # for the bundled builds, if we have no arguments, the user # probably wants the gui. Users of the bundled build who want the command line # interface shouuld specify at least one option, possibly setting a value to a # default if they like all the defaults close_console() guiMain() sys.exit(0) # 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) logger = logging.getLogger('') if not no_log_file: ts = time.time() st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H-%M-%S') log_dir = local_path('Logs') if not os.path.exists(log_dir): os.makedirs(log_dir) log_path = os.path.join(log_dir, '%s.log' % st) log_file = logging.FileHandler(log_path) log_file.setFormatter( logging.Formatter('[%(asctime)s] %(message)s', datefmt='%H:%M:%S')) logger.addHandler(log_file) if not settings.check_version: try: version_error = check_version(settings.checked_version) except VersionError as e: logger.warning(str(e)) try: if gui: guiMain(settings) elif settings.cosmetics_only: cosmetic_patch(settings) elif settings.patch_file != '': from_patch_file(settings) elif settings.count != None and settings.count > 1: orig_seed = settings.seed for i in range(settings.count): settings.update_seed(orig_seed + '-' + str(i)) main(settings) else: main(settings) except Exception as ex: logger.exception(ex)
def try_run(self, code_to_run, *code_arg): try: code_to_run(*code_arg) except Exception as e: self.update_status('Error: ' + str(e)) if not is_bundled(): traceback.print_exc() self.queue_event(self.stop)
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 ] # 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']: # 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: # If Input ROM is compressed, then Decompress it subcall = [] if is_bundled(): sub_dir = "." else: sub_dir = "Decompress" if platform.system() == 'Windows': if 8 * struct.calcsize("P") == 64: subcall = [sub_dir + "\\Decompress.exe", file, decomp_file] else: subcall = [ sub_dir + "\\Decompress32.exe", file, decomp_file ] elif platform.system() == 'Linux': if platform.uname()[4] == 'aarch64' or platform.uname( )[4] == 'arm64': subcall = [ sub_dir + "/Decompress_ARM64", file, decomp_file ] else: subcall = [sub_dir + "/Decompress", file, decomp_file] elif platform.system() == 'Darwin': subcall = [sub_dir + "/Decompress.out", file, decomp_file] else: raise RuntimeError( 'Unsupported operating system for decompression. Please supply an already decompressed ROM.' ) subprocess.call(subcall, **subprocess_args()) self.read_rom(decomp_file) else: # ROM file is a valid and already uncompressed pass
def start(): args = parse_arguments(None) if is_bundled() and len(sys.argv) == 1: # for the bundled builds, if we have no arguments, the user # probably wants the gui. Users of the bundled build who want the command line # interface shouuld specify at least one option, possibly setting a value to a # default if they like all the defaults from Gui import guiMain close_console() guiMain() sys.exit(0) # ToDo: Validate files further than mere existance if not args.jsonout and 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) sys.exit(1) if any([ sprite is not None and not os.path.isfile(sprite) and not get_sprite_from_name(sprite) for sprite in args.sprite.values() ]): if not args.jsonout: input( 'Could not find link sprite sheet at given location. \nPress Enter to exit.' ) sys.exit(1) else: raise IOError('Cannot find sprite file at %s' % args.sprite) # 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) if args.gui: from Gui import guiMain guiMain(args) elif args.count is not None: seed = args.seed for _ in range(args.count): main(seed=seed, args=args) seed = random.randint(0, 999999999) else: main(seed=args.seed, args=args)
def start(): settings, gui, args_loglevel = get_settings_from_command_line_args() if is_bundled() and len(sys.argv) == 1: # for the bundled builds, if we have no arguments, the user # probably wants the gui. Users of the bundled build who want the command line # interface shouuld specify at least one option, possibly setting a value to a # default if they like all the defaults close_console() guiMain() sys.exit(0) # 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) sys.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) logger = logging.getLogger('') if not settings.check_version: try: version_error = check_version(settings.checked_version) except VersionError as e: logger.warning(str(e)) if gui: guiMain(settings) elif settings.cosmetics_only: cosmetic_patch(settings) elif settings.patch_file != '': from_patch_file(settings) elif settings.count is not None: orig_seed = settings.seed for i in range(settings.count): settings.update_seed(orig_seed + '-' + str(i)) main(settings) else: main(settings)
def start(): parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true') parser.add_argument('--logic', default='noglitches', const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'nologic'], help='''\ Select Enforcement of Item Requirements. (default: %(default)s) No Glitches: Minor Glitches: May require Fake Flippers, Bunny Revival and Dark Room Navigation. No Logic: Distribute items without regard for item requirements. ''') parser.add_argument('--mode', default='open', const='open', nargs='?', choices=['standard', 'open', 'swordless'], help='''\ Select game mode. (default: %(default)s) Open: World starts with Zelda rescued. Standard: Fixes Hyrule Castle Secret Entrance and Front Door but may lead to weird rain state issues if you exit through the Hyrule Castle side exits before rescuing Zelda in a full shuffle. Swordless: Like Open, but with no swords. Curtains in Skull Woods and Agahnims Tower are removed, Agahnim\'s Tower barrier can be destroyed with hammer. Misery Mire and Turtle Rock can be opened without a sword. Hammer damages Ganon. Ether and Bombos Tablet can be activated with Hammer (and Book). ''') parser.add_argument('--goal', default='ganon', const='ganon', nargs='?', choices=['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'crystals'], help='''\ Select completion goal. (default: %(default)s) Ganon: Collect all crystals, beat Agahnim 2 then defeat Ganon. Crystals: Collect all crystals then defeat Ganon. Pedestal: Places the Triforce at the Master Sword Pedestal. All Dungeons: Collect all crystals, pendants, beat both Agahnim fights and then defeat Ganon. Triforce Hunt: Places 30 Triforce Pieces in the world, collect 20 of them to beat the game. ''') parser.add_argument('--difficulty', default='normal', const='normal', nargs='?', choices=['easy', 'normal', 'hard', 'expert', 'insane'], help='''\ Select game difficulty. Affects available itempool. (default: %(default)s) Easy: An easy setting with extra equipment. Normal: Normal difficulty. Hard: A harder setting with less equipment and reduced health. Expert: A harder yet setting with minimum equipment and health. Insane: A setting with the absolute minimum in equipment and no extra health. ''') parser.add_argument('--timer', default='none', const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'], help='''\ Select game timer setting. Affects available itempool. (default: %(default)s) None: No timer. Display: Displays a timer but does not affect the itempool. Timed: Starts with clock at zero. Green Clocks subtract 4 minutes (Total: 20), Blue Clocks subtract 2 minutes (Total: 10), Red Clocks add 2 minutes (Total: 10). Winner is player with lowest time at the end. Timed OHKO: Starts clock at 10 minutes. Green Clocks add 5 minutes (Total: 25). As long as clock is at 0, Link will die in one hit. OHKO: Like Timed OHKO, but no clock items are present and the clock is permenantly at zero. Timed Countdown: Starts with clock at 40 minutes. Same clocks as Timed mode. If time runs out, you lose (but can still keep playing). ''') parser.add_argument('--progressive', default='on', const='normal', nargs='?', choices=['on', 'off', 'random'], help='''\ Select progressive equipment setting. Affects available itempool. (default: %(default)s) On: Swords, Shields, Armor, and Gloves will all be progressive equipment. Each subsequent item of the same type the player finds will upgrade that piece of equipment by one stage. Off: Swords, Shields, Armor, and Gloves will not be progressive equipment. Higher level items may be found at any time. Downgrades are not possible. Random: Swords, Shields, Armor, and Gloves will, per category, be randomly progressive or not. Link will die in one hit. ''') parser.add_argument('--algorithm', default='balanced', const='balanced', nargs='?', choices=['freshness', 'flood', 'vt21', 'vt22', 'vt25', 'vt26', 'balanced'], help='''\ Select item filling algorithm. (default: %(default)s balanced: vt26 derivitive that aims to strike a balance between the overworld heavy vt25 and the dungeon heavy vt26 algorithm. vt26: Shuffle items and place them in a random location that it is not impossible to be in. This includes dungeon keys and items. vt25: Shuffle items and place them in a random location that it is not impossible to be in. vt21: Unbiased in its selection, but has tendency to put Ice Rod in Turtle Rock. vt22: Drops off stale locations after 1/3 of progress items were placed to try to circumvent vt21\'s shortcomings. Freshness: Keep track of stale locations (ones that cannot be reached yet) and decrease likeliness of selecting them the more often they were found unreachable. Flood: Push out items starting from Link\'s House and slightly biased to placing progression items with less restrictions. ''') parser.add_argument('--shuffle', default='full', const='full', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple'], help='''\ Select Entrance Shuffling Algorithm. (default: %(default)s) Full: Mix cave and dungeon entrances freely while limiting multi-entrance caves to one world. Simple: Shuffle Dungeon Entrances/Exits between each other and keep all 4-entrance dungeons confined to one location. All caves outside of death mountain are shuffled in pairs and matched by original type. Restricted: Use Dungeons shuffling from Simple but freely connect remaining entrances. Crossed: Mix cave and dungeon entrances freely while allowing caves to cross between worlds. Insanity: Decouple entrances and exits from each other and shuffle them freely. Caves that used to be single entrance will still exit to the same location from which they are entered. Vanilla: All entrances are in the same locations they were in the base game. Legacy shuffles preserve behavior from older versions of the entrance randomizer including significant technical limitations. The dungeon variants only mix up dungeons and keep the rest of the overworld vanilla. ''') parser.add_argument('--rom', default='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', help='Path to an ALttP JAP(1.0) 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('--count', 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) parser.add_argument('--fastmenu', default='normal', const='normal', nargs='?', choices=['normal', 'instant', 'double', 'triple', 'quadruple', 'half'], help='''\ Select the rate at which the menu opens and closes. (default: %(default)s) ''') parser.add_argument('--quickswap', help='Enable quick item swapping with L and R.', action='store_true') parser.add_argument('--disablemusic', help='Disables game music.', action='store_true') parser.add_argument('--keysanity', help='''\ Keys (and other dungeon items) are no longer restricted to their dungeons, but can be anywhere ''', action='store_true') parser.add_argument('--retro', help='''\ Keys are universal, shooting arrows costs rupees, and a few other little things make this more like Zelda-1. ''', action='store_true') parser.add_argument('--custom', default=False, help='Not supported.') parser.add_argument('--customitemarray', default=False, help='Not supported.') parser.add_argument('--nodungeonitems', help='''\ Remove Maps and Compasses from Itempool, replacing them by empty slots. ''', action='store_true') parser.add_argument('--beatableonly', help='''\ Only check if the game is beatable with placement. Do not ensure all locations are reachable. This only has an effect on the restrictive algorithm currently. ''', action='store_true') # included for backwards compatibility parser.add_argument('--shuffleganon', help=argparse.SUPPRESS, action='store_true', default=True) parser.add_argument('--no-shuffleganon', help='''\ If set, the Pyramid Hole and Ganon's Tower are not included entrance shuffle pool. ''', action='store_false', dest='shuffleganon') 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. (default: %(default)s) ''') parser.add_argument('--heartcolor', default='red', const='red', nargs='?', choices=['red', 'blue', 'green', 'yellow'], help='Select the color of Link\'s heart meter. (default: %(default)s)') 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, or 0x7078 (28792) bytes including palette data. Alternatively, can be a ALttP Rom patched with a Link sprite that will be extracted. ''') parser.add_argument('--suppress_rom', help='Do not create an output rom file.', action='store_true') parser.add_argument('--gui', help='Launch the GUI', action='store_true') parser.add_argument('--jsonout', action='store_true', help='''\ Output .json patch to stdout instead of a patched rom. Used for VT site integration, do not use otherwise. ''') args = parser.parse_args() if is_bundled() and len(sys.argv) == 1: # for the bundled builds, if we have no arguments, the user # probably wants the gui. Users of the bundled build who want the command line # interface shouuld specify at least one option, possibly setting a value to a # default if they like all the defaults close_console() guiMain() sys.exit(0) # ToDo: Validate files further than mere existance if not args.jsonout and 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) sys.exit(1) if args.sprite is not None and not os.path.isfile(args.sprite): if not args.jsonout: input('Could not find link sprite sheet at given location. \nPress Enter to exit.' % args.sprite) sys.exit(1) else: raise IOError('Cannot find sprite file at %s' % args.sprite) # 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) if args.gui: guiMain(args) elif args.count is not None: seed = args.seed for _ in range(args.count): main(seed=seed, args=args) seed = random.randint(0, 999999999) else: main(seed=args.seed, args=args)
def start(): parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true') parser.add_argument('--bridge', default='medallions', const='medallions', nargs='?', choices=['medallions', 'vanilla', 'dungeons', 'open'], help='''\ Select requirement to spawn the Rainbow Bridge to reach Ganon's Castle. (default: %(default)s) Medallions: Collect all six medallions to create the bridge. Vanilla: Collect only the Shadow and Spirit Medallions and then view the Light Arrow cutscene. All Dungeons: Collect all spiritual stones and all medallions to create the bridge. Open: The bridge will spawn without an item requirement. ''') parser.add_argument('--rom', default='ZOOTDEC.z64', help='Path to an OoT 1.0 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('--count', 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) parser.add_argument('--open_forest', help='''\ Mido no longer blocks the path to the Deku Tree and the Kokiri boy no longer blocks the path out of the forest. ''', action='store_true') parser.add_argument('--open_door_of_time', help='''\ The Door of Time is open from the beginning of the game. ''', action='store_true') parser.add_argument('--fast_ganon', help='''\ The barrier within Ganon's Castle leading to Ganon's Tower is dispelled from the beginning of the game, the Boss Key is not required in Ganon's Tower, Ganondorf gives a hint for the location of Light Arrows, and the tower collapse sequence is removed. ''', action='store_true') parser.add_argument('--nodungeonitems', help='''\ Remove Maps and Compasses from Itempool, replacing them by empty slots. ''', action='store_true') parser.add_argument('--beatableonly', help='''\ Only check if the game is beatable with placement. Do not ensure all locations are reachable. This only has an effect on the restrictive algorithm currently. ''', action='store_true') parser.add_argument('--hints', help='''\ Gossip Stones provide helpful hints about which items are in inconvenient locations if the Stone of Agony is in the player's inventory. ''', action='store_true') parser.add_argument('--kokiricolor', default='Kokiri Green', const='medallions', nargs='?', choices=['Kokiri Green', 'Goron Red', 'Zora Blue', 'Black', 'White', 'Purple', 'Yellow', 'Orange', 'Pink', 'Gray', 'Brown', 'Gold', 'Silver', 'Beige', 'Teal', 'Royal Blue', 'Sonic Blue', 'Blood Red', 'Blood Orange', 'NES Green', 'Dark Green', 'Random', 'True Random'], help='''\ Choose the color for Link's Kokiri Tunic. (default: %(default)s) Color: Make the Kokiri Tunic this color. Random: Choose a random color from this list of colors. True Random: Choose a random color from any color the N64 can draw. ''') parser.add_argument('--goroncolor', default='Goron Red', const='medallions', nargs='?', choices=['Kokiri Green', 'Goron Red', 'Zora Blue', 'Black', 'White', 'Purple', 'Yellow', 'Orange', 'Pink', 'Gray', 'Brown', 'Gold', 'Silver', 'Beige', 'Teal', 'Royal Blue', 'Sonic Blue', 'Blood Red', 'Blood Orange', 'NES Green', 'Dark Green', 'Random', 'True Random'], help='''\ Choose the color for Link's Goron Tunic. (default: %(default)s) Color: Make the Goron Tunic this color. Random: Choose a random color from this list of colors. True Random: Choose a random color from any color the N64 can draw. ''') parser.add_argument('--zoracolor', default='Zora Blue', const='medallions', nargs='?', choices=['Kokiri Green', 'Goron Red', 'Zora Blue', 'Black', 'White', 'Purple', 'Yellow', 'Orange', 'Pink', 'Gray', 'Brown', 'Gold', 'Silver', 'Beige', 'Teal', 'Royal Blue', 'Sonic Blue', 'Blood Red', 'Blood Orange', 'NES Green', 'Dark Green', 'Random', 'True Random'], help='''\ Choose the color for Link's Zora Tunic. (default: %(default)s) Color: Make the Zora Tunic this color. Random: Choose a random color from this list of colors. True Random: Choose a random color from any color the N64 can draw. ''') parser.add_argument('--healthSFX', 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: Replace the sound effect with a random sound from this list. None: Eliminate heart beeps. ''') parser.add_argument('--suppress_rom', help='Do not create an output rom file.', action='store_true') parser.add_argument('--compress_rom', help='Create a compressed version of the output rom file.', action='store_true') parser.add_argument('--gui', help='Launch the GUI', action='store_true') args = parser.parse_args() if is_bundled() and len(sys.argv) == 1: # for the bundled builds, if we have no arguments, the user # probably wants the gui. Users of the bundled build who want the command line # interface shouuld specify at least one option, possibly setting a value to a # default if they like all the defaults close_console() guiMain() sys.exit(0) # 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) sys.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) if args.gui: guiMain(args) elif args.count is not None: seed = args.seed for _ in range(args.count): main(seed=seed, args=args) seed = random.randint(0, 999999999) else: main(seed=args.seed, args=args)
def unofficial_sprite_dir(self): if is_bundled(): return output_path("sprites/unofficial") return self.local_unofficial_sprite_dir
def __init__(self, parent, callback, adjuster=False): if is_bundled(): self.deploy_icons() self.parent = parent self.window = Toplevel(parent) self.window.geometry("800x650") self.sections = [] self.callback = callback self.adjuster = adjuster self.window.wm_title("TAKE ANY ONE YOU WANT") self.window['padx'] = 5 self.window['pady'] = 5 self.all_sprites = [] def open_official_sprite_listing(_evt): webbrowser.open("http://alttpr.com/sprite_preview") def open_unofficial_sprite_dir(_evt): open_file(self.unofficial_sprite_dir) def open_spritesomething_listing(_evt): webbrowser.open( "https://artheau.github.io/SpriteSomething/?mode=zelda3/link") official_frametitle = Frame(self.window) official_title_text = Label(official_frametitle, text="Official Sprites") official_title_link = Label(official_frametitle, text="(open)", fg="blue", cursor="hand2") official_title_text.pack(side=LEFT) official_title_link.pack(side=LEFT) official_title_link.bind("<Button-1>", open_official_sprite_listing) unofficial_frametitle = Frame(self.window) unofficial_title_text = Label(unofficial_frametitle, text="Unofficial Sprites") unofficial_title_link = Label(unofficial_frametitle, text="(open)", fg="blue", cursor="hand2") unofficial_title_text.pack(side=LEFT) unofficial_title_link.pack(side=LEFT) unofficial_title_link.bind("<Button-1>", open_unofficial_sprite_dir) spritesomething_title_link = Label(unofficial_frametitle, text="(SpriteSomething)", fg="blue", cursor="hand2") spritesomething_title_link.pack(side=LEFT) spritesomething_title_link.bind("<Button-1>", open_spritesomething_listing) self.icon_section( official_frametitle, self.official_sprite_dir + '/*', 'Official sprites not found. Click "Update official sprites" to download them.' ) self.icon_section( unofficial_frametitle, self.unofficial_sprite_dir + '/*', 'Put sprites in the unofficial sprites folder (see open link above) to have them appear here.' ) frame = Frame(self.window) frame.pack(side=BOTTOM, fill=X, pady=5) button = Button(frame, text="Browse for file...", command=self.browse_for_sprite) button.pack(side=RIGHT, padx=(5, 0)) button = Button(frame, text="Update official sprites", command=self.update_official_sprites) button.pack(side=RIGHT, padx=(5, 0)) button = Button(frame, text="Default Link sprite", command=self.use_default_link_sprite) button.pack(side=LEFT, padx=(0, 5)) button = Button(frame, text="Random sprite", command=self.use_random_sprite) button.pack(side=LEFT, padx=(0, 5)) if adjuster: button = Button(frame, text="Current sprite from rom", command=self.use_default_sprite) button.pack(side=LEFT, padx=(0, 5)) set_icon(self.window) self.window.focus()
def main(settings, window=dummy_window()): start = time.process_time() logger = logging.getLogger('') worlds = [] allowed_tricks = {} for trick in logic_tricks.values(): settings.__dict__[ trick['name']] = trick['name'] in settings.allowed_tricks settings.load_distribution() # we load the rom before creating the seed so that error get caught early if settings.compress_rom == 'None' and not settings.create_spoiler: raise Exception( '`No Output` must have spoiler enabled to produce anything.') if settings.compress_rom != 'None': window.update_status('Loading ROM') rom = Rom(settings.rom) if not settings.world_count: settings.world_count = 1 if settings.world_count < 1 or settings.world_count > 255: raise Exception('World Count must be between 1 and 255') if settings.player_num > settings.world_count or settings.player_num < 1: if settings.compress_rom not in ['None', 'Patch']: raise Exception('Player Num must be between 1 and %d' % settings.world_count) else: settings.player_num = 1 logger.info('OoT Randomizer Version %s - Seed: %s\n\n', __version__, settings.seed) settings.remove_disabled() random.seed(settings.numeric_seed) settings.resolve_random_settings() for i in range(0, settings.world_count): worlds.append(World(settings)) window.update_status('Creating the Worlds') for id, world in enumerate(worlds): world.id = id world.distribution = settings.distribution.world_dists[id] logger.info('Generating World %d.' % id) window.update_progress(0 + 1 * (id + 1) / settings.world_count) logger.info('Creating Overworld') # Determine MQ Dungeons dungeon_pool = list(world.dungeon_mq) dist_num_mq = world.distribution.configure_dungeons( world, dungeon_pool) if world.mq_dungeons_random: for dungeon in dungeon_pool: world.dungeon_mq[dungeon] = random.choice([True, False]) world.mq_dungeons = list(world.dungeon_mq.values()).count(True) else: mqd_picks = random.sample(dungeon_pool, world.mq_dungeons - dist_num_mq) for dung in mqd_picks: world.dungeon_mq[dung] = True if settings.logic_rules == 'glitched': overworld_data = os.path.join(data_path('Glitched World'), 'Overworld.json') else: overworld_data = os.path.join(data_path('World'), 'Overworld.json') world.load_regions_from_json(overworld_data) create_dungeons(world) if settings.shopsanity != 'off': world.random_shop_prices() world.set_scrub_prices() window.update_progress(0 + 4 * (id + 1) / settings.world_count) logger.info('Calculating Access Rules.') set_rules(world) window.update_progress(0 + 5 * (id + 1) / settings.world_count) logger.info('Generating Item Pool.') generate_itempool(world) set_shop_rules(world) set_drop_location_names(world) logger.info('Setting Entrances.') set_entrances(worlds) window.update_status('Placing the Items') logger.info('Fill the world.') distribute_items_restrictive(window, worlds) window.update_progress(35) spoiler = Spoiler(worlds) cosmetics_log = None if settings.create_spoiler: window.update_status('Calculating Spoiler Data') logger.info('Calculating playthrough.') create_playthrough(spoiler) window.update_progress(50) if settings.create_spoiler or settings.hints != 'none': window.update_status('Calculating Hint Data') State.update_required_items(spoiler) for world in worlds: world.update_useless_areas(spoiler) buildGossipHints(spoiler, world) window.update_progress(55) spoiler.build_file_hash() logger.info('Patching ROM.') settings_string_hash = hashlib.sha1( settings.settings_string.encode('utf-8')).hexdigest().upper()[:5] if settings.output_file: outfilebase = settings.output_file elif settings.world_count > 1: outfilebase = 'OoT_%s_%s_W%d' % (settings_string_hash, settings.seed, settings.world_count) else: outfilebase = 'OoT_%s_%s' % (settings_string_hash, settings.seed) output_dir = default_output_path(settings.output_dir) if settings.compress_rom == 'Patch': rng_state = random.getstate() file_list = [] window.update_progress(65) for world in worlds: if settings.world_count > 1: window.update_status('Patching ROM: Player %d' % (world.id + 1)) patchfilename = '%sP%d.zpf' % (outfilebase, world.id + 1) else: window.update_status('Patching ROM') patchfilename = '%s.zpf' % outfilebase random.setstate(rng_state) patch_rom(spoiler, world, rom, outfilebase) cosmetics_log = patch_cosmetics(settings, rom) window.update_progress(65 + 20 * (world.id + 1) / settings.world_count) window.update_status('Creating Patch File') output_path = os.path.join(output_dir, patchfilename) file_list.append(patchfilename) create_patch_file(rom, output_path) rom.restore() window.update_progress(65 + 30 * (world.id + 1) / settings.world_count) if settings.create_cosmetics_log and cosmetics_log: window.update_status('Creating Cosmetics Log') if settings.world_count > 1: cosmetics_log_filename = "%sP%d_Cosmetics.txt" % ( outfilebase, world.id + 1) else: cosmetics_log_filename = '%s_Cosmetics.txt' % outfilebase cosmetics_log.to_file( os.path.join(output_dir, cosmetics_log_filename)) file_list.append(cosmetics_log_filename) cosmetics_log = None if settings.world_count > 1: window.update_status('Creating Patch Archive') output_path = os.path.join(output_dir, '%s.zpfz' % outfilebase) with zipfile.ZipFile(output_path, mode="w") as patch_archive: for file in file_list: file_path = os.path.join(output_dir, file) patch_archive.write(file_path, file.replace(outfilebase, ''), compress_type=zipfile.ZIP_DEFLATED) for file in file_list: os.remove(os.path.join(output_dir, file)) logger.info("Created patchfile at: %s" % output_path) window.update_progress(95) elif settings.compress_rom != 'None': window.update_status('Patching ROM') patch_rom(spoiler, worlds[settings.player_num - 1], rom, outfilebase) cosmetics_log = patch_cosmetics(settings, rom) window.update_progress(65) window.update_status('Saving Uncompressed ROM') if settings.world_count > 1: filename = "%sP%d.z64" % (outfilebase, settings.player_num) else: filename = '%s.z64' % outfilebase output_path = os.path.join(output_dir, filename) rom.write_to_file(output_path) if settings.compress_rom == 'True': window.update_status('Compressing ROM') logger.info('Compressing ROM.') if is_bundled(): compressor_path = "." else: compressor_path = "Compress" if platform.system() == 'Windows': if 8 * struct.calcsize("P") == 64: compressor_path += "\\Compress.exe" else: compressor_path += "\\Compress32.exe" elif platform.system() == 'Linux': if platform.uname()[4] == 'aarch64' or platform.uname( )[4] == 'arm64': compressor_path += "/Compress_ARM64" else: compressor_path += "/Compress" elif platform.system() == 'Darwin': compressor_path += "/Compress.out" else: compressor_path = "" logger.info('OS not supported for compression') output_compress_path = output_path[:output_path. rfind('.')] + '-comp.z64' if compressor_path != "": run_process( window, logger, [compressor_path, output_path, output_compress_path]) os.remove(output_path) logger.info("Created compessed rom at: %s" % output_compress_path) else: logger.info("Created uncompessed rom at: %s" % output_path) window.update_progress(95) for world in worlds: for info in setting_infos: world.settings.__dict__[info.name] = world.__dict__[info.name] settings.distribution.update_spoiler(spoiler) if settings.create_spoiler: window.update_status('Creating Spoiler Log') spoiler_path = os.path.join(output_dir, '%s_Spoiler.json' % outfilebase) settings.distribution.to_file(spoiler_path) logger.info("Created spoiler log at: %s" % ('%s_Spoiler.json' % outfilebase)) else: window.update_status('Creating Settings Log') settings_path = os.path.join(output_dir, '%s_Settings.json' % outfilebase) settings.distribution.to_file(settings_path) logger.info("Created settings log at: %s" % ('%s_Settings.json' % outfilebase)) if settings.create_cosmetics_log and cosmetics_log: window.update_status('Creating Cosmetics Log') if settings.world_count > 1 and not settings.output_file: filename = "%sP%d_Cosmetics.txt" % (outfilebase, settings.player_num) else: filename = '%s_Cosmetics.txt' % outfilebase cosmetic_path = os.path.join(output_dir, filename) cosmetics_log.to_file(cosmetic_path) logger.info("Created cosmetic log at: %s" % cosmetic_path) window.update_progress(100) if cosmetics_log and cosmetics_log.error: window.update_status( 'Success: Rom patched successfully. Some cosmetics could not be applied.' ) else: window.update_status('Success: Rom patched successfully') logger.info('Done. Enjoy.') logger.debug('Total Time: %s', time.process_time() - start) return worlds[settings.player_num - 1]
def from_patch_file(settings, window=dummy_window()): start = time.process_time() logger = logging.getLogger('') # we load the rom before creating the seed so that error get caught early if settings.compress_rom == 'None' or settings.compress_rom == 'Patch': raise Exception( 'Output Type must be a ROM when patching from a patch file.') window.update_status('Loading ROM') rom = Rom(settings.rom) logger.info('Patching ROM.') filename_split = os.path.basename(settings.patch_file).split('.') if settings.output_file: outfilebase = settings.output_file else: outfilebase = filename_split[0] extension = filename_split[-1] output_dir = default_output_path(settings.output_dir) output_path = os.path.join(output_dir, outfilebase) window.update_status('Patching ROM') if extension == 'zpf': subfile = None else: subfile = 'P%d.zpf' % (settings.player_num) if not settings.output_file: output_path += 'P%d' % (settings.player_num) apply_patch_file(rom, settings.patch_file, subfile) cosmetics_log = None if settings.repatch_cosmetics: cosmetics_log = patch_cosmetics(settings, rom) window.update_progress(65) window.update_status('Saving Uncompressed ROM') uncompressed_output_path = output_path + '.z64' rom.write_to_file(uncompressed_output_path) if settings.compress_rom == 'True': window.update_status('Compressing ROM') logger.info('Compressing ROM.') if is_bundled(): compressor_path = "." else: compressor_path = "Compress" if platform.system() == 'Windows': if 8 * struct.calcsize("P") == 64: compressor_path += "\\Compress.exe" else: compressor_path += "\\Compress32.exe" elif platform.system() == 'Linux': compressor_path += "/Compress" elif platform.system() == 'Darwin': compressor_path += "/Compress.out" else: compressor_path = "" logger.info('OS not supported for compression') output_compress_path = output_path + '-comp.z64' if compressor_path != "": run_process(window, logger, [ compressor_path, uncompressed_output_path, output_compress_path ]) os.remove(uncompressed_output_path) logger.info("Created compessed rom at: %s" % output_compress_path) else: logger.info("Created uncompessed rom at: %s" % output_path) window.update_progress(95) if settings.create_cosmetics_log and cosmetics_log: window.update_status('Creating Cosmetics Log') if settings.world_count > 1 and not settings.output_file: filename = "%sP%d_Cosmetics.txt" % (outfilebase, settings.player_num) else: filename = '%s_Cosmetics.txt' % outfilebase cosmetic_path = os.path.join(output_dir, filename) cosmetics_log.to_file(cosmetic_path) logger.info("Created cosmetic log at: %s" % cosmetic_path) window.update_progress(100) if cosmetics_log and cosmetics_log.error: window.update_status( 'Success: Rom patched successfully. Some cosmetics could not be applied.' ) else: window.update_status('Success: Rom patched successfully') logger.info('Done. Enjoy.') logger.debug('Total Time: %s', time.process_time() - start) return True
def patch_and_output(settings, window, spoiler, rom, start): logger = logging.getLogger('') logger.info('Patching ROM.') worlds = spoiler.worlds cosmetics_log = None settings_string_hash = hashlib.sha1( settings.settings_string.encode('utf-8')).hexdigest().upper()[:5] if settings.output_file: outfilebase = settings.output_file elif settings.world_count > 1: outfilebase = 'OoT_%s_%s_W%d' % (settings_string_hash, settings.seed, settings.world_count) else: outfilebase = 'OoT_%s_%s' % (settings_string_hash, settings.seed) output_dir = default_output_path(settings.output_dir) if settings.compress_rom == 'Patch': rng_state = random.getstate() file_list = [] window.update_progress(65) for world in worlds: if settings.world_count > 1: window.update_status('Patching ROM: Player %d' % (world.id + 1)) patchfilename = '%sP%d.zpf' % (outfilebase, world.id + 1) else: window.update_status('Patching ROM') patchfilename = '%s.zpf' % outfilebase random.setstate(rng_state) patch_rom(spoiler, world, rom) cosmetics_log = patch_cosmetics(settings, rom) rom.update_header() window.update_progress(65 + 20 * (world.id + 1) / settings.world_count) window.update_status('Creating Patch File') output_path = os.path.join(output_dir, patchfilename) file_list.append(patchfilename) create_patch_file(rom, output_path) rom.restore() window.update_progress(65 + 30 * (world.id + 1) / settings.world_count) if settings.create_cosmetics_log and cosmetics_log: window.update_status('Creating Cosmetics Log') if settings.world_count > 1: cosmetics_log_filename = "%sP%d_Cosmetics.txt" % ( outfilebase, world.id + 1) else: cosmetics_log_filename = '%s_Cosmetics.txt' % outfilebase cosmetics_log.to_file( os.path.join(output_dir, cosmetics_log_filename)) file_list.append(cosmetics_log_filename) cosmetics_log = None if settings.world_count > 1: window.update_status('Creating Patch Archive') output_path = os.path.join(output_dir, '%s.zpfz' % outfilebase) with zipfile.ZipFile(output_path, mode="w") as patch_archive: for file in file_list: file_path = os.path.join(output_dir, file) patch_archive.write(file_path, file.replace(outfilebase, ''), compress_type=zipfile.ZIP_DEFLATED) for file in file_list: os.remove(os.path.join(output_dir, file)) logger.info("Created patchfile at: %s" % output_path) window.update_progress(95) elif settings.compress_rom != 'None': window.update_status('Patching ROM') patch_rom(spoiler, worlds[settings.player_num - 1], rom) cosmetics_log = patch_cosmetics(settings, rom) window.update_progress(65) window.update_status('Saving Uncompressed ROM') if settings.world_count > 1: filename = "%sP%d.z64" % (outfilebase, settings.player_num) else: filename = '%s.z64' % outfilebase output_path = os.path.join(output_dir, filename) rom.write_to_file(output_path) if settings.compress_rom == 'True': window.update_status('Compressing ROM') logger.info('Compressing ROM.') if is_bundled(): compressor_path = "." else: compressor_path = "Compress" if platform.system() == 'Windows': if 8 * struct.calcsize("P") == 64: compressor_path += "\\Compress.exe" else: compressor_path += "\\Compress32.exe" elif platform.system() == 'Linux': if platform.uname()[4] == 'aarch64' or platform.uname( )[4] == 'arm64': compressor_path += "/Compress_ARM64" else: compressor_path += "/Compress" elif platform.system() == 'Darwin': compressor_path += "/Compress.out" else: compressor_path = "" logger.info('OS not supported for compression') output_compress_path = output_path[:output_path. rfind('.')] + '-comp.z64' if compressor_path != "": run_process( window, logger, [compressor_path, output_path, output_compress_path]) os.remove(output_path) logger.info("Created compessed rom at: %s" % output_compress_path) else: logger.info("Created uncompessed rom at: %s" % output_path) window.update_progress(95) if not settings.create_spoiler or settings.output_settings: settings.distribution.update_spoiler(spoiler, False) window.update_status('Creating Settings Log') settings_path = os.path.join(output_dir, '%s_Settings.json' % outfilebase) settings.distribution.to_file(settings_path, False) logger.info("Created settings log at: %s" % ('%s_Settings.json' % outfilebase)) if settings.create_spoiler: settings.distribution.update_spoiler(spoiler, True) window.update_status('Creating Spoiler Log') spoiler_path = os.path.join(output_dir, '%s_Spoiler.json' % outfilebase) settings.distribution.to_file(spoiler_path, True) logger.info("Created spoiler log at: %s" % ('%s_Spoiler.json' % outfilebase)) if settings.create_cosmetics_log and cosmetics_log: window.update_status('Creating Cosmetics Log') if settings.world_count > 1 and not settings.output_file: filename = "%sP%d_Cosmetics.txt" % (outfilebase, settings.player_num) else: filename = '%s_Cosmetics.txt' % outfilebase cosmetic_path = os.path.join(output_dir, filename) cosmetics_log.to_file(cosmetic_path) logger.info("Created cosmetic log at: %s" % cosmetic_path) window.update_progress(100) if cosmetics_log and cosmetics_log.error: window.update_status( 'Success: Rom patched successfully. Some cosmetics could not be applied.' ) else: window.update_status('Success: Rom patched successfully') logger.info('Done. Enjoy.') logger.debug('Total Time: %s', time.process_time() - start) return worlds[settings.player_num - 1]
def start(): args = parse_arguments(None) if is_bundled() and len(sys.argv) == 1: # for the bundled builds, if we have no arguments, the user # probably wants the gui. Users of the bundled build who want the command line # interface shouuld specify at least one option, possibly setting a value to a # default if they like all the defaults from Gui import guiMain close_console() guiMain() sys.exit(0) # ToDo: Validate files further than mere existance if not args.jsonout and 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) sys.exit(1) if any([ sprite is not None and not os.path.isfile(sprite) and not get_sprite_from_name(sprite) for sprite in args.sprite.values() ]): if not args.jsonout: input( 'Could not find link sprite sheet at given location. \nPress Enter to exit.' ) sys.exit(1) else: raise IOError('Cannot find sprite file at %s' % args.sprite) # 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) if args.gui: from Gui import guiMain guiMain(args) elif args.count is not None: seed = args.seed or random.randint(0, 999999999) failures = [] logger = logging.getLogger('') for _ in range(args.count): try: main(seed=seed, args=args) logger.info('Finished run %s', _ + 1) except (FillError, Exception, RuntimeError) as err: failures.append((err, seed)) logger.warning('Generation failed: %s', err) seed = random.randint(0, 999999999) for fail in failures: logger.info('%s seed failed with: %s', fail[1], fail[0]) fail_rate = 100 * len(failures) / args.count success_rate = 100 * (args.count - len(failures)) / args.count fail_rate = str(fail_rate).split('.') success_rate = str(success_rate).split('.') logger.info('Generation fail rate: ' + str(fail_rate[0]).rjust(3, " ") + '.' + str(fail_rate[1]).ljust(6, '0') + '%') logger.info('Generation success rate: ' + str(success_rate[0]).rjust(3, " ") + '.' + str(success_rate[1]).ljust(6, '0') + '%') else: main(seed=args.seed, args=args)
def start(): parser = argparse.ArgumentParser( formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true') parser.add_argument('--bridge', default='medallions', const='medallions', nargs='?', choices=['medallions', 'vanilla', 'dungeons', 'open'], help='''\ Select requirement to spawn the Rainbow Bridge to reach Ganon's Castle. (default: %(default)s) Medallions: Collect all six medallions to create the bridge. Vanilla: Collect only the Shadow and Spirit Medallions and then view the Light Arrow cutscene. All Dungeons: Collect all spiritual stones and all medallions to create the bridge. Open: The bridge will spawn without an item requirement. ''') parser.add_argument('--shuffle', default='vanilla', const='vanilla', nargs='?', choices=['vanilla', 'simple'], help='''\ Select Entrance Shuffling Algorithm. (default: %(default)s) Vanilla: All entrances are in the same locations they were in the base game. Simple: Only single entrance houses, caves, and dungeons move. ''') parser.add_argument( '--rom', default='ZELOOTROMDEC.z64', help='Path to a decompressed OoT 1.0 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('--count', 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) parser.add_argument('--open_forest', help='''\ Mido no longer blocks the path to the Deku Tree and the Kokiri boy no longer blocks the path out of the forest. ''', action='store_true') parser.add_argument('--nodungeonitems', help='''\ Remove Maps and Compasses from Itempool, replacing them by empty slots. ''', action='store_true') parser.add_argument('--beatableonly', help='''\ Only check if the game is beatable with placement. Do not ensure all locations are reachable. This only has an effect on the restrictive algorithm currently. ''', action='store_true') parser.add_argument('--suppress_rom', help='Do not create an output rom file.', action='store_true') parser.add_argument('--gui', help='Launch the GUI', action='store_true') args = parser.parse_args() if is_bundled() and len(sys.argv) == 1: # for the bundled builds, if we have no arguments, the user # probably wants the gui. Users of the bundled build who want the command line # interface shouuld specify at least one option, possibly setting a value to a # default if they like all the defaults close_console() guiMain() sys.exit(0) # 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) sys.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) if args.gui: guiMain(args) elif args.count is not None: seed = args.seed for _ in range(args.count): main(seed=seed, args=args) seed = random.randint(0, 999999999) else: main(seed=args.seed, args=args)
def cosmetic_patch(settings, window=dummy_window()): start = time.clock() logger = logging.getLogger('') # we load the rom before creating the seed so that error get caught early if settings.compress_rom == 'None': raise Exception( 'An output type must be specified to produce anything.') window.update_status('Loading ROM') rom = LocalRom(settings) logger.info('OoT Randomizer Version %s', __version__) logger.info('Patching ROM.') outfilebase = 'OoT_cosmetics' output_dir = default_output_path(settings.output_dir) if settings.compress_rom == 'Patch': file_list = [] window.update_progress(65) window.update_status('Patching ROM') patchfilename = '%s.zpf' % outfilebase patch_cosmetics(settings, rom) window.update_progress(65 + 20) window.update_status('Creating Patch File') output_path = os.path.join(output_dir, patchfilename) file_list.append(patchfilename) create_patch_file(rom, output_path) window.update_progress(95) elif settings.compress_rom != 'None': window.update_status('Patching ROM') cosmetics_log = patch_cosmetics(settings, rom) window.update_progress(65) window.update_status('Saving Uncompressed ROM') filename = '%s.z64' % outfilebase output_path = os.path.join(output_dir, filename) rom.write_to_file(output_path) if settings.compress_rom == 'True': window.update_status('Compressing ROM') logger.info('Compressing ROM.') if is_bundled(): compressor_path = "." else: compressor_path = "Compress" if platform.system() == 'Windows': if 8 * struct.calcsize("P") == 64: compressor_path += "\\Compress.exe" else: compressor_path += "\\Compress32.exe" elif platform.system() == 'Linux': if platform.uname()[4] == 'aarch64' or platform.uname( )[4] == 'arm64': compressor_path += "/Compress_ARM64" else: compressor_path += "/Compress" elif platform.system() == 'Darwin': compressor_path += "/Compress.out" else: compressor_path = "" logger.info('OS not supported for compression') if compressor_path != "": run_process(window, logger, [ compressor_path, output_path, output_path[:output_path.rfind('.')] + '-comp.z64' ]) os.remove(output_path) window.update_progress(95) if settings.create_cosmetics_log and cosmetics_log: window.update_status('Creating Cosmetics Log') filename = '%s_Cosmetics.txt' % outfilebase cosmetics_log.to_file(os.path.join(output_dir, filename)) window.update_progress(100) window.update_status('Success: Rom patched successfully') logger.info('Done. Enjoy.') logger.debug('Total Time: %s', time.clock() - start)