 aee0df5359
			
		
	
	aee0df5359
	
	
	
		
			
			## What is this fixing or adding? - Adds the majority of OoTR 7.0 features: - Pot shuffle, Freestanding item shuffle, Crate shuffle, Beehive shuffle - Key rings mode - Dungeon shortcuts to speed up dungeons - "Regional" shuffle for dungeon items - New options for shop pricing in shopsanity - Expanded Ganon's Boss Key shuffle options - Pre-planted beans - Improved Chest Appearance Matches Contents mode - Blue Fire Arrows - Bonk self-damage - Finer control over MQ dungeons and spawn position randomization - Several bugfixes as a result of the update: - Items recognized by the server and valid starting items are now in a 1-to-1 correspondence. In particular, starting with keys is now supported. - Entrance randomization success rate improved. Hopefully it is now at 100%. Co-authored-by: Zach Parks <zach@alliware.com>
		
			
				
	
	
		
			253 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			253 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import tkinter as tk
 | |
| import argparse
 | |
| import logging
 | |
| import random
 | |
| import os
 | |
| import zipfile
 | |
| from itertools import chain
 | |
| 
 | |
| from BaseClasses import MultiWorld
 | |
| from Options import Choice, Range, Toggle
 | |
| from worlds.oot import OOTWorld
 | |
| from worlds.oot.Cosmetics import patch_cosmetics
 | |
| from worlds.oot.Options import cosmetic_options, sfx_options
 | |
| from worlds.oot.Rom import Rom, compress_rom_file
 | |
| from worlds.oot.N64Patch import apply_patch_file
 | |
| from worlds.oot.Utils import data_path
 | |
| from Utils import local_path
 | |
| 
 | |
| logger = logging.getLogger('OoTAdjuster')
 | |
| 
 | |
| def main():
 | |
|     parser = argparse.ArgumentParser()
 | |
| 
 | |
|     parser.add_argument('--rom', default='', 
 | |
|         help='Path to an OoT randomized ROM to adjust.')
 | |
|     parser.add_argument('--vanilla_rom', default='',
 | |
|         help='Path to a vanilla OoT ROM for patching.')
 | |
|     for name, option in chain(cosmetic_options.items(), sfx_options.items()):
 | |
|         parser.add_argument('--'+name, default=None,
 | |
|             help=option.__doc__)
 | |
|     parser.add_argument('--is_glitched', default=False, action='store_true',
 | |
|         help='Setting this to true will enable protection on kokiri tunic colors for weirdshot.')
 | |
|     parser.add_argument('--deathlink',
 | |
|         help='Enable DeathLink system', action='store_true')
 | |
| 
 | |
|     args = parser.parse_args()
 | |
|     if not os.path.isfile(args.rom):
 | |
|         adjustGUI()
 | |
|     else:
 | |
|         adjust(args)
 | |
| 
 | |
| def adjustGUI():
 | |
|     from tkinter import Tk, LEFT, BOTTOM, TOP, E, W, \
 | |
|         StringVar, IntVar, Checkbutton, Frame, Label, X, Entry, Button, \
 | |
|         OptionMenu, filedialog, messagebox, ttk
 | |
|     from argparse import Namespace
 | |
|     from Main import __version__ as MWVersion
 | |
| 
 | |
|     window = tk.Tk()
 | |
|     window.wm_title(f"Archipelago {MWVersion} OoT Adjuster")
 | |
|     set_icon(window)
 | |
| 
 | |
|     opts = Namespace()
 | |
| 
 | |
|     # Select ROM
 | |
|     romDialogFrame = Frame(window)
 | |
|     romLabel = Label(romDialogFrame, text='Rom/patch to adjust')
 | |
|     vanillaLabel = Label(romDialogFrame, text='OoT Base Rom')
 | |
|     opts.rom = StringVar()
 | |
|     opts.vanilla_rom = StringVar(value="The Legend of Zelda - Ocarina of Time.z64")
 | |
|     romEntry = Entry(romDialogFrame, textvariable=opts.rom)
 | |
|     vanillaEntry = Entry(romDialogFrame, textvariable=opts.vanilla_rom)
 | |
| 
 | |
|     def RomSelect():
 | |
|         rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".z64", ".n64", ".apz5")), ("All Files", "*")])
 | |
|         opts.rom.set(rom)
 | |
|     def VanillaSelect():
 | |
|         rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".z64", ".n64")), ("All Files", "*")])
 | |
|         opts.vanilla_rom.set(rom)
 | |
| 
 | |
|     romSelectButton = Button(romDialogFrame, text='Select Rom', command=RomSelect)
 | |
|     vanillaSelectButton = Button(romDialogFrame, text='Select Rom', command=VanillaSelect)
 | |
|     romDialogFrame.pack(side=TOP, expand=True, fill=X)
 | |
|     romLabel.pack(side=LEFT)
 | |
|     romEntry.pack(side=LEFT, expand=True, fill=X)
 | |
|     romSelectButton.pack(side=LEFT)
 | |
|     vanillaLabel.pack(side=LEFT)
 | |
|     vanillaEntry.pack(side=LEFT, expand=True, fill=X)
 | |
|     vanillaSelectButton.pack(side=LEFT)
 | |
| 
 | |
|     # Cosmetic options
 | |
|     romSettingsFrame = Frame(window)
 | |
| 
 | |
|     def dropdown_option(type, option_name, row, column):
 | |
|         if type == 'cosmetic':
 | |
|             option = cosmetic_options[option_name]
 | |
|         elif type == 'sfx':
 | |
|             option = sfx_options[option_name]
 | |
|         optionFrame = Frame(romSettingsFrame)
 | |
|         optionFrame.grid(row=row, column=column, sticky=E)
 | |
|         optionLabel = Label(optionFrame, text=option.display_name)
 | |
|         optionLabel.pack(side=LEFT)
 | |
|         setattr(opts, option_name, StringVar())
 | |
|         getattr(opts, option_name).set(option.name_lookup[option.default])
 | |
|         optionMenu = OptionMenu(optionFrame, getattr(opts, option_name), *option.name_lookup.values())
 | |
|         optionMenu.pack(side=LEFT)
 | |
| 
 | |
|     dropdown_option('cosmetic', 'default_targeting', 0, 0)
 | |
|     dropdown_option('cosmetic', 'display_dpad', 0, 1)
 | |
|     dropdown_option('cosmetic', 'correct_model_colors', 0, 2)
 | |
|     dropdown_option('cosmetic', 'background_music', 1, 0)
 | |
|     dropdown_option('cosmetic', 'fanfares', 1, 1)
 | |
|     dropdown_option('cosmetic', 'ocarina_fanfares', 1, 2)
 | |
|     dropdown_option('cosmetic', 'kokiri_color', 2, 0)
 | |
|     dropdown_option('cosmetic', 'goron_color', 2, 1)
 | |
|     dropdown_option('cosmetic', 'zora_color', 2, 2)
 | |
|     dropdown_option('cosmetic', 'silver_gauntlets_color', 3, 0)
 | |
|     dropdown_option('cosmetic', 'golden_gauntlets_color', 3, 1)
 | |
|     dropdown_option('cosmetic', 'mirror_shield_frame_color', 3, 2)
 | |
|     dropdown_option('cosmetic', 'navi_color_default_inner', 4, 0)
 | |
|     dropdown_option('cosmetic', 'navi_color_default_outer', 4, 1)
 | |
|     dropdown_option('cosmetic', 'navi_color_enemy_inner', 5, 0)
 | |
|     dropdown_option('cosmetic', 'navi_color_enemy_outer', 5, 1)
 | |
|     dropdown_option('cosmetic', 'navi_color_npc_inner', 6, 0)
 | |
|     dropdown_option('cosmetic', 'navi_color_npc_outer', 6, 1)
 | |
|     dropdown_option('cosmetic', 'navi_color_prop_inner', 7, 0)
 | |
|     dropdown_option('cosmetic', 'navi_color_prop_outer', 7, 1)
 | |
|     # sword_trail_duration, 8, 2
 | |
|     dropdown_option('cosmetic', 'sword_trail_color_inner', 8, 0)
 | |
|     dropdown_option('cosmetic', 'sword_trail_color_outer', 8, 1)
 | |
|     dropdown_option('cosmetic', 'bombchu_trail_color_inner', 9, 0)
 | |
|     dropdown_option('cosmetic', 'bombchu_trail_color_outer', 9, 1)
 | |
|     dropdown_option('cosmetic', 'boomerang_trail_color_inner', 10, 0)
 | |
|     dropdown_option('cosmetic', 'boomerang_trail_color_outer', 10, 1)
 | |
|     dropdown_option('cosmetic', 'heart_color', 11, 0)
 | |
|     dropdown_option('cosmetic', 'magic_color', 12, 0)
 | |
|     dropdown_option('cosmetic', 'a_button_color', 11, 1)
 | |
|     dropdown_option('cosmetic', 'b_button_color', 11, 2)
 | |
|     dropdown_option('cosmetic', 'c_button_color', 12, 1)
 | |
|     dropdown_option('cosmetic', 'start_button_color', 12, 2)
 | |
| 
 | |
|     dropdown_option('sfx', 'sfx_navi_overworld', 14, 0)
 | |
|     dropdown_option('sfx', 'sfx_navi_enemy', 14, 1)
 | |
|     dropdown_option('sfx', 'sfx_low_hp', 14, 2)
 | |
|     dropdown_option('sfx', 'sfx_menu_cursor', 15, 0)
 | |
|     dropdown_option('sfx', 'sfx_menu_select', 15, 1)
 | |
|     dropdown_option('sfx', 'sfx_nightfall', 15, 2)
 | |
|     dropdown_option('sfx', 'sfx_horse_neigh', 16, 0)
 | |
|     dropdown_option('sfx', 'sfx_hover_boots', 16, 1)
 | |
|     dropdown_option('sfx', 'sfx_ocarina', 16, 2)
 | |
| 
 | |
|     # Special cases
 | |
|     # Sword trail duration is a range
 | |
|     option = cosmetic_options['sword_trail_duration']
 | |
|     optionFrame = Frame(romSettingsFrame)
 | |
|     optionFrame.grid(row=8, column=2, sticky=E)
 | |
|     optionLabel = Label(optionFrame, text=option.display_name)
 | |
|     optionLabel.pack(side=LEFT)
 | |
|     setattr(opts, 'sword_trail_duration', StringVar())
 | |
|     getattr(opts, 'sword_trail_duration').set(option.default)
 | |
|     optionMenu = OptionMenu(optionFrame, getattr(opts, 'sword_trail_duration'), *range(4, 21))
 | |
|     optionMenu.pack(side=LEFT)
 | |
| 
 | |
|     # Glitched is a checkbox
 | |
|     opts.is_glitched = IntVar(value=0)
 | |
|     glitched_checkbox = Checkbutton(romSettingsFrame, text="Glitched Logic?", variable=opts.is_glitched)
 | |
|     glitched_checkbox.grid(row=17, column=0, sticky=W)
 | |
| 
 | |
|     # Deathlink is a checkbox
 | |
|     opts.deathlink = IntVar(value=0)
 | |
|     deathlink_checkbox = Checkbutton(romSettingsFrame, text="DeathLink (Team Deaths)", variable=opts.deathlink)
 | |
|     deathlink_checkbox.grid(row=17, column=1, sticky=W)
 | |
| 
 | |
|     romSettingsFrame.pack(side=TOP)
 | |
| 
 | |
|     def adjustRom():
 | |
|         try:
 | |
|             guiargs = Namespace()
 | |
|             options = vars(opts)
 | |
|             for o in options:
 | |
|                 result = options[o].get()
 | |
|                 if result == 'true':
 | |
|                     result = True
 | |
|                 if result == 'false':
 | |
|                     result = False
 | |
|                 setattr(guiargs, o, result)
 | |
|             guiargs.sword_trail_duration = int(guiargs.sword_trail_duration)
 | |
|             path = adjust(guiargs)
 | |
|         except Exception as e:
 | |
|             logging.exception(e)
 | |
|             messagebox.showerror(title="Error while adjusting Rom", message=str(e))
 | |
|         else:
 | |
|             messagebox.showinfo(title="Success", message=f"Rom patched successfully to {path}")
 | |
| 
 | |
|     # Adjust button
 | |
|     bottomFrame = Frame(window)
 | |
|     adjustButton = Button(bottomFrame, text='Adjust Rom', command=adjustRom)
 | |
|     adjustButton.pack(side=BOTTOM, padx=(5, 5))
 | |
|     bottomFrame.pack(side=BOTTOM, pady=(5, 5))
 | |
| 
 | |
|     window.mainloop()
 | |
| 
 | |
| def set_icon(window):
 | |
|     logo = tk.PhotoImage(file=local_path('data', 'icon.png'))
 | |
|     window.tk.call('wm', 'iconphoto', window._w, logo)
 | |
| 
 | |
| def adjust(args):
 | |
|     # Create a fake world and OOTWorld to use as a base
 | |
|     world = MultiWorld(1)
 | |
|     world.slot_seeds = {1: random}
 | |
|     ootworld = OOTWorld(world, 1)
 | |
|     # Set options in the fake OOTWorld
 | |
|     for name, option in chain(cosmetic_options.items(), sfx_options.items()):
 | |
|         result = getattr(args, name, None)
 | |
|         if result is None:
 | |
|             if issubclass(option, Choice):
 | |
|                 result = option.name_lookup[option.default]
 | |
|             elif issubclass(option, Range) or issubclass(option, Toggle):
 | |
|                 result = option.default
 | |
|             else:
 | |
|                 raise Exception("Unsupported option type")
 | |
|         setattr(ootworld, name, result)
 | |
|     ootworld.logic_rules = 'glitched' if args.is_glitched else 'glitchless'
 | |
|     ootworld.death_link = args.deathlink
 | |
| 
 | |
|     delete_zootdec = False
 | |
|     if os.path.splitext(args.rom)[-1] in ['.z64', '.n64']:
 | |
|         # Load up the ROM
 | |
|         rom = Rom(file=args.rom, force_use=True)
 | |
|         delete_zootdec = True
 | |
|     elif os.path.splitext(args.rom)[-1] in ['.apz5', '.zpf']:
 | |
|         # Load vanilla ROM
 | |
|         rom = Rom(file=args.vanilla_rom, force_use=True)
 | |
|         apz5_file = args.rom
 | |
|         base_name = os.path.splitext(apz5_file)[0]
 | |
|         # Patch file
 | |
|         apply_patch_file(rom, apz5_file,
 | |
|             sub_file=(os.path.basename(base_name) + '.zpf'
 | |
|                 if zipfile.is_zipfile(apz5_file)
 | |
|                 else None))
 | |
|     else:
 | |
|         raise Exception("Invalid file extension; requires .n64, .z64, .apz5, .zpf")
 | |
|     # Call patch_cosmetics
 | |
|     try:
 | |
|         patch_cosmetics(ootworld, rom)
 | |
|         rom.write_byte(rom.sym('DEATH_LINK'), args.deathlink)
 | |
|         # Output new file
 | |
|         path_pieces = os.path.splitext(args.rom)
 | |
|         decomp_path = path_pieces[0] + '-adjusted-decomp.n64'
 | |
|         comp_path = path_pieces[0] + '-adjusted.n64'
 | |
|         rom.write_to_file(decomp_path)
 | |
|         os.chdir(data_path("Compress"))
 | |
|         compress_rom_file(decomp_path, comp_path)
 | |
|         os.remove(decomp_path)
 | |
|     finally:
 | |
|         if delete_zootdec:
 | |
|             os.chdir(os.path.split(__file__)[0])
 | |
|             os.remove("ZOOTDEC.z64")
 | |
|     return comp_path
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |