Archipelago/OoTAdjuster.py

251 lines
10 KiB
Python

import tkinter as tk
import argparse
import logging
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 Utils 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 multiworld and OOTWorld to use as a base
multiworld = MultiWorld(1)
ootworld = OOTWorld(multiworld, 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()