#! C:\Python34\python.exe

####################
# Isaac Racing Mods
####################

# Imports
import sys                    # For quitting the application
import traceback              # For error handling
import logging                # For logging all exceptions to a file
import tkinter                # We use TKinter to draw the GUI
import tkinter.messagebox     # This is not automatically imported above
import tkinter.filedialog     # This is not automatically imported above
import os                     # For various file operations (1/2)
import shutil                 # For various file operations (2/2)
import configparser           # For parsing the "options.ini" file
import psutil                 # For finding and killing the running Isaac process
import webbrowser             # For automatically launching Isaac
import subprocess             # For restarting the program
import time                   # For finding the epoch timestamp and sleeping before launching
import xml.etree.ElementTree  # For parsing the game's XML files (and builds.xml)
import random                 # For getting a random start
import re                     # For the search text box
import string                 # For generating a random Diversity seed (1/2)
import binascii               # For generating a random Diversity seed (2/2)
from PIL import Image, ImageFont, ImageDraw, ImageTk  # For drawing things on the title and character screen
import languages              # Translations for languages other than English
import platform               # For autodetecting the user's language (1/3)
import locale                 # For autodetecting the user's language (2/3)
import ctypes                 # For autodetecting the user's language (3/3)

# Configuration
mod_pretty_name = 'Isaac Racing Mods'
mod_name = 'isaac-racing-mods'
#mod_name = 'isaac-test'


############################
# General purpose functions
############################

def error(message, exception):
    # Build the error message
    if exception is not None:
        message += '\n\n'
        message += traceback.format_exc()

    # Print the message to standard out
    print(message.encode('utf-8'))

    # Log the error to a file
    logging.error(message)
    with open(log_file, 'a') as file:
        file.write('\n')

    # Show the error to the user
    title = get_text('Error')
    tkinter.messagebox.showerror(title, message)

    # Exit the program immediately
    sys.exit()


def warning(message, exception):
    # Build the warning message
    if exception is not None:
        message += '\n\n'
        message += traceback.format_exc()

    # Print the message to standard out
    print(message.encode('utf-8'))

    # Log the warning to a file
    logging.warning(message)
    with open(log_file, 'a') as file:
        file.write('\n')

    # Show the warning to the user
    title = get_text('Warning')
    tkinter.messagebox.showwarning(title, message)


def callback_error(self, *args):
    # Build the error message
    message = get_text('Generic error') + ':\n\n'
    message += traceback.format_exc()

    # Print the message to standard out
    print(message.encode('utf-8'))

    # Log the error to a file
    logging.error(message)
    with open(log_file, 'a') as file:
        file.write('\n')

    # Show the error to the user
    title = get_text('Error')
    tkinter.messagebox.showerror(title, message)

    # Exit the program immediately
    sys.exit()


##############################
# File manipulation functions
##############################

def delete_file_if_exists(path):
    if os.path.exists(path):
        if os.path.isfile(path):
            try:
                os.remove(path)
            except Exception as e:
                error('Failed to delete the "' + path + '" file:', e)
        elif os.path.isdir(path):
            try:
                shutil.rmtree(path)
            except Exception as e:
                error('Failed to delete the "' + path + '" directory:', e)
        else:
            error('Failed to delete "' + path + '", as it is not a file or a directory.', None)


def copy_file(path1, path2):
    if not os.path.exists(path1):
        error('Copying "' + path1 + '" failed because it does not exist.', None)

    if os.path.isfile(path1):
        try:
            shutil.copyfile(path1, path2)
        except Exception as e:
            error('Failed to copy the "' + path1 + '" file:', e)
    elif os.path.isdir(path1):
        try:
            shutil.copytree(path1, path2)
        except Exception as e:
            error('Failed to copy the "' + path1 + '" directory:', e)
    else:
        error('Failed to copy "' + path1 + '", as it is not a file or a directory.', None)


def create_directory(path):
    if os.path.exists(path):
        error('Failed to create the "' + path + '" directory, as a file or directory already exists by that name.', None)

    try:
        os.makedirs(path)
    except Exception as e:
        error('Failed to create the "' + path + '" directory:', e)


###########################
# Initialization functions
###########################

def set_custom_path():
    # Global variables
    global mod_options

    # Open file dialog
    user_entered_isaac_location = tkinter.filedialog.askopenfilename()

    # Check that the file is isaac-ng.exe
    if user_entered_isaac_location[-12:] != 'isaac-ng.exe':
        text = get_text('The file you selected is not called "isaac-ng.exe".')
        error(text, None)

    # Check that the file exists
    if not os.path.isfile(user_entered_isaac_location):
        error('The file you selected does not exist or is not a valid file.', None)

    # Derive the resources location from the location of isaac-ng.exe ("isaac-ng.exe" is 12 characters long)
    user_entered_resources_directory = os.path.join(user_entered_isaac_location[0:-12], 'resources')

    # Check that the resources directory exists
    if not os.path.isdir(user_entered_resources_directory):
        error(mod_pretty_name + ' was not able to find the "resources" directory that is supposed to live next to "isaac-ng.exe".', None)

    # Write the new path to the INI file
    mod_options.set('options', 'isaac_resources_directory', user_entered_resources_directory)
    try:
        with open(os.path.join('..', 'options.ini'), 'w') as config_file:
            mod_options.write(config_file)
    except Exception as e:
        error('Failed to write the new directory path to the "options.ini" file:', e)

    # Alert the user
    text1 = get_text('Success')
    text2 = get_text('You have successfully set') + mod_pretty_name + '.'
    tkinter.messagebox.showinfo(text1, text2)

    # Restart the program
    try:
        subprocess.Popen(['program.exe'])
    except Exception as e:
        error(mod_pretty_name + ' was not able to automatically restart itself. Please re-run it manually.', e)
    sys.exit()


##########################
# Isaac related functions
##########################

def launch_isaac():
    # If Isaac is running, kill it
    if automatically_close_isaac == True:
        try:
            for process in psutil.process_iter():
                if process.name() == 'isaac-ng.exe':
                    process.kill()
        except Exception as e:
            warning(mod_pretty_name + ' encountered a problem while scanning to see if Isaac is open (or automatically closing Isaac if it was open). If this problem persists, from the main menu, select "Miscellaneous Stuff" and uncheck the option for "Automatically attempt to close Isaac".', None)
            return

    # Wait 0.5 seconds before launching to try and prevent the bug where only certain parts of the mod get loaded
    time.sleep(0.5)

    # Launch Isaac
    try:
        webbrowser.open('steam://rungameid/250900')
    except Exception as e:
        error(mod_pretty_name + ' was not able to automatically start Isaac:', e)


# Draw and return the background image for the starting room that lists starting and removed items
def draw_start_room_background(items, removed_items=None, trinket=None):
    #######################################
    # draw_start_room_background functions
    #######################################

    # Create an image containing the specified text
    def create_text_image(text, font):
        img = Image.new('RGBA', (1000, 100))  # Dummy image
        w, h = ImageDraw.Draw(img).textsize(text, font=font)
        result = Image.new('RGBA', (w, h))  # Actual image, just large enough to fit the text
        ImageDraw.Draw(result).text((0, 0), text, (0, 0, 0), font=font)
        return result.resize((int(result.width / 2), int(result.height / 2)), Image.ANTIALIAS)

    # Given two images, return one image with the two connected vertically, centered if necessary
    def join_images_vertical(top, bottom):
        # Create a new image big enough to fit both of the old ones
        result = Image.new(top.mode, (max(top.width, bottom.width), top.height + bottom.height))
        middle_paste = int(result.width / 2 - top.width / 2)
        result.paste(top, (middle_paste, 0))
        middle_paste = int(result.width / 2 - bottom.width / 2)
        result.paste(bottom, (middle_paste, top.height))
        return result

    # Given two images, return one image with the two connected horizontally, centered if necessary
    def join_images_horizontal(left, right):
        result = Image.new(left.mode, (left.width + right.width, max(left.height, right.height)))
        middle_paste = int(result.height / 2 - left.height / 2)
        result.paste(left, (0, middle_paste))
        middle_paste = int(result.height / 2 - right.height / 2)
        result.paste(right, (left.width, middle_paste))
        return result

    #######################################
    # draw_start_room_background
    #######################################

    result = None
    font = ImageFont.truetype('fonts/olden.ttf', 32)
    if removed_items:
        if len(removed_items) > 1:
            text = 'Removed Items'
        else:
            text = 'Removed Item'
        result = create_text_image(text, font)
        removed_image = None
        for item in removed_items[:10]:
            item_image = get_item_icon(item)
            removed_image = join_images_horizontal(removed_image, item_image) if removed_image else item_image
        result = join_images_vertical(result, removed_image)
        removed_image = None
        for item in removed_items[10:19]:
            item_image = get_item_icon(item)
            removed_image = join_images_horizontal(removed_image, item_image) if removed_image else item_image
        if removed_image:
            if len(removed_items) > 19:
                removed_image = join_images_horizontal(removed_image, create_text_image('+' + str(len(removed_items) - 19), font))
            result = join_images_vertical(result, removed_image)
    if items or trinket:
        items_image = None
        if items:
            luck_up = 0
            for item in items:
                if item == '46':  # Lucky Foot
                    luck_up += 1
                    continue
                item_image = get_item_icon(item)
                items_image = join_images_horizontal(items_image, item_image) if items_image else item_image
            if luck_up > 0:
                item_image = get_item_icon(46)
                items_image = join_images_horizontal(items_image, item_image) if items_image else item_image
                items_image = join_images_horizontal(items_image, create_text_image('x' + str(luck_up), font))
        if trinket:
            items_image = join_images_horizontal(items_image, get_trinket_icon(trinket)) if items_image else item_image
        result = join_images_vertical(items_image, result) if result else items_image  # Add the starting items
        if len(items) > 1:
            text = 'Starting Items'
        else:
            text = 'Starting Item'
        result = join_images_vertical(create_text_image(text, font), result)  # Add start label
        result = join_images_vertical(Image.new('RGBA', (5, 15)), result)  # Add some space

    return result


#######################
# The main menu window
#######################

class ModSelectionWindow():
    def __init__(self, parent):
        # Initialize a new GUI window
        self.parent = parent
        self.window = tkinter.Toplevel(self.parent)
        self.window.withdraw()  # Hide the GUI
        self.window.title(mod_pretty_name + ' v' + mod_version)  # Set the GUI title
        self.window.iconbitmap('images/icons/the_d6.ico')  # Set the GUI icon
        self.window.resizable(False, False)
        self.window.protocol('WM_DELETE_WINDOW', lambda: uninstall_mod(self))  # Uninstall mod files when the window is closed

        # Start counting rows
        row = 0

        # Select a mod
        text = get_text('Select a mod') + ':'
        select_message = tkinter.Message(self.window, justify=tkinter.CENTER, text=text, font='font 20', width=400)
        select_message.grid(row=row, pady=5)
        row += 1

        # "Jud6s" button
        jud6s_button = tkinter.Button(self.window, text=' Jud6s (1) ', compound='left', font='font 20', width=275)
        jud6s_button.configure(command=self.show_jud6s_window)
        jud6s_button.icon = ImageTk.PhotoImage(get_item_icon('The D6'))
        jud6s_button.configure(image=jud6s_button.icon)
        jud6s_button.grid(row=row, pady=5, padx=50)
        self.window.bind('1', lambda event: jud6s_button.invoke())
        row += 1

        # "Instant Start" button
        instant_start_button = tkinter.Button(self.window, text=' Instant Start (2) ', compound='left', font='font 20', width=275)
        instant_start_button.configure(command=self.show_instant_start_window)
        instant_start_button.icon = ImageTk.PhotoImage(get_item_icon('More Options'))
        instant_start_button.configure(image=instant_start_button.icon)
        instant_start_button.grid(row=row, pady=5)
        self.window.bind('2', lambda event: instant_start_button.invoke())
        row += 1

        # "Diversity" button
        diversity_button = tkinter.Button(self.window, text=' Diversity (3) ', compound='left', font='font 20', width=275)
        diversity_button.configure(command=self.show_diversity_window)
        diversity_button.image = Image.open('images/icons/rainbow_poop_small.ico')
        diversity_button.icon = ImageTk.PhotoImage(diversity_button.image)
        diversity_button.configure(image=diversity_button.icon)
        diversity_button.grid(row=row, pady=5)
        self.window.bind('3', lambda event: diversity_button.invoke())
        row += 1

        # Spacing
        spacing = tkinter.Message(self.window, text='', font='font 6')
        spacing.grid(row=row)
        row += 1

        # "Miscellaneous Stuff" button
        text = ' ' + get_text('Miscellaneous Stuff') + ' (4) '
        miscellaneous_button = tkinter.Button(self.window, text=text, compound='left', font='font 15')
        miscellaneous_button.configure(command=self.show_miscellaneous_window)
        miscellaneous_button.grid(row=row, pady=5)
        self.window.bind('4', lambda event: miscellaneous_button.invoke())
        row += 1

        # "Exit" button
        text = ' ' + get_text('Exit') + ' (Esc) '
        exit_button = tkinter.Button(self.window, text=text, compound='left', font='font 15')
        exit_button.configure(command=self.exit)
        exit_button.grid(row=row, pady=5)
        self.window.bind('<Escape>', lambda event: uninstall_mod(self))  # Won't work with "exit_button.invoke()"
        row += 1

        # Spacing
        spacing = tkinter.Message(self.window, text='', font='font 12')
        spacing.grid(row=row)
        row += 1

        # "English" language checkbox
        self.language_en = tkinter.IntVar()
        if language == 'en':
            self.language_en.set(1)
        self.language_en_checkbox = tkinter.Checkbutton(self.window, text='English', font='font 14', variable=self.language_en, command=self.set_language_en)
        self.language_en_checkbox.grid(row=row)
        row += 1

        # "Français" language checkbox
        self.language_fr = tkinter.IntVar()
        if language == 'fr':
            self.language_fr.set(1)
        self.language_fr_checkbox = tkinter.Checkbutton(self.window, text='Français', font='font 14', variable=self.language_fr, command=self.set_language_fr)
        self.language_fr_checkbox.grid(row=row)
        row += 1

        # "Español" language checkbox
        self.language_es = tkinter.IntVar()
        if language == 'es':
            self.language_es.set(1)
        self.language_es_checkbox = tkinter.Checkbutton(self.window, text='Español', font='font 14', variable=self.language_es, command=self.set_language_es)
        self.language_es_checkbox.grid(row=row)
        row += 1

        # "Pусский" language checkbox
        self.language_ru = tkinter.IntVar()
        if language == 'ru':
            self.language_ru.set(1)
        self.language_ru_checkbox = tkinter.Checkbutton(self.window, text='Pусский', font='font 14', variable=self.language_ru, command=self.set_language_ru)
        self.language_ru_checkbox.grid(row=row)
        row += 1

        # Spacing
        spacing = tkinter.Message(self.window, text='', font='font 6')
        spacing.grid(row=row)
        row += 1

        # Place the window at the X and Y coordinates from either the INI or the previous window
        self.window.deiconify()  # Show the GUI
        self.window.geometry('+%d+%d' % (window_x, window_y))

    def show_jud6s_window(self):
        get_window_x_y(self)
        self.window.destroy()
        Jud6sWindow(self.parent)

    def show_instant_start_window(self):
        get_window_x_y(self)
        self.window.destroy()
        InstantStartWindow(self.parent)

    def show_diversity_window(self):
        get_window_x_y(self)
        self.window.destroy()
        DiversityWindow(self.parent)

    def show_miscellaneous_window(self):
        get_window_x_y(self)
        self.window.destroy()
        MiscellaneousWindow(self.parent)

    def set_language_en(self):
        self.set_language('en')

    def set_language_fr(self):
        self.set_language('fr')

    def set_language_es(self):
        self.set_language('es')

    def set_language_ru(self):
        self.set_language('ru')

    def set_language(self, new_language):
        # Global variables
        global mod_options
        global language

        # Validate function arguments
        if (new_language != 'en' and
            new_language != 'fr' and
            new_language != 'es' and
            new_language != 'ru'):

            error('The "set_language" function was passed an invalid language value.')

        # Write the new language to the INI file
        mod_options.set('options', 'language', new_language)
        try:
            with open(os.path.join('..', 'options.ini'), 'w') as config_file:
                mod_options.write(config_file)
        except Exception as e:
            error('Failed to write the new "language" value to the "options.ini" file:', e)

        # Change the global variable for the language
        language = new_language

        # Respawn the main menu
        get_window_x_y(self)
        self.window.destroy()
        ModSelectionWindow(self.parent)

    def exit(self):
        get_window_x_y(self)
        uninstall_mod(self)


#######################
# The Jud6s Mod window
#######################

class Jud6sWindow():
    def __init__(self, parent):
        # Initialize a new GUI window
        self.parent = parent
        self.window = tkinter.Toplevel(self.parent)
        self.window.withdraw()  # Hide the GUI
        self.window.title('Jud6s Mod v' + jud6s_version)  # Set the GUI title
        self.window.iconbitmap('images/icons/the_d6.ico')  # Set the GUI icon
        self.window.resizable(False, False)
        self.window.protocol('WM_DELETE_WINDOW', lambda: uninstall_mod(self))  # Uninstall mod files when the window is closed

        # Start counting rows
        row = 0

        # Select a mod
        text = get_text('Select a ruleset') + ':'
        select_message = tkinter.Message(self.window, justify=tkinter.CENTER, text=text, font='font 20', width=400)
        select_message.grid(row=row, pady=5)
        row += 1

        # 1 - "Normal (Unseeded)" button
        ruleset1_button = tkinter.Button(self.window, text=' Normal / Unseeded (1) ', compound='left', width=375)
        ruleset1_button.configure(font=('Helvetica', 13, 'bold'))
        ruleset1_button.configure(command=lambda: self.install_jud6s_mod(1))
        ruleset1_button.icon = ImageTk.PhotoImage(get_item_icon('The D6'))
        ruleset1_button.configure(image=ruleset1_button.icon)
        ruleset1_button.grid(row=row, pady=5, padx=50)
        self.window.bind('1', lambda event: ruleset1_button.invoke())
        row += 1

        # 2 - "Seeded" button
        ruleset2_button = tkinter.Button(self.window, text=' Seeded (2) ', compound='left', width=375)
        ruleset2_button.configure(font=('Helvetica', 13, 'bold'))
        ruleset2_button.configure(command=lambda: self.install_jud6s_mod(2))
        ruleset2_button.icon = ImageTk.PhotoImage(get_item_icon('The Compass'))
        ruleset2_button.configure(image=ruleset2_button.icon)
        ruleset2_button.grid(row=row, pady=5)
        self.window.bind('2', lambda event: ruleset2_button.invoke())
        row += 1

        # 3 - "Dark Room" button
        ruleset3_button = tkinter.Button(self.window, text=' Dark Room (3) ', compound='left', width=375)
        ruleset3_button.configure(font=('Helvetica', 13, 'bold'))
        ruleset3_button.configure(command=lambda: self.install_jud6s_mod(3))
        ruleset3_button.icon = ImageTk.PhotoImage(get_image('jud6s-extra/Ruleset 3 - Dark Room/gfx/items/collectibles/collectibles_327_thepolaroid.png'))
        ruleset3_button.configure(image=ruleset3_button.icon)
        ruleset3_button.grid(row=row, pady=5)
        self.window.bind('3', lambda event: ruleset3_button.invoke())
        row += 1

        # 4 - "The Lost Child Open Loser's Bracket" button
        ruleset4_button = tkinter.Button(self.window, text=' The Lost Child Open Loser\'s Bracket (4) ', compound='left', width=375)
        ruleset4_button.configure(font=('Helvetica', 13, 'bold'))
        ruleset4_button.configure(command=lambda: self.install_jud6s_mod(4))
        ruleset4_button.icon = ImageTk.PhotoImage(get_item_icon('Judas\' Shadow'))
        ruleset4_button.configure(image=ruleset4_button.icon)
        ruleset4_button.grid(row=row, pady=5)
        self.window.bind('4', lambda event: ruleset4_button.invoke())
        row += 1

        # 5 - "Mega Satan" button
        ruleset5_button = tkinter.Button(self.window, text=' Mega Satan (5) ', compound='left', width=375)
        ruleset5_button.configure(font=('Helvetica', 13, 'bold'))
        ruleset5_button.configure(command=lambda: self.install_jud6s_mod(5))
        ruleset5_button.icon = ImageTk.PhotoImage(get_item_icon('Key Piece 1'))
        ruleset5_button.configure(image=ruleset5_button.icon)
        ruleset5_button.grid(row=row, pady=5)
        self.window.bind('5', lambda event: ruleset5_button.invoke())
        row += 1

        # 6 - "Beginner" button
        ruleset6_button = tkinter.Button(self.window, text=' Beginner (6) ', compound='left', width=375)
        ruleset6_button.configure(font=('Helvetica', 13, 'bold'))
        ruleset6_button.configure(command=lambda: self.install_jud6s_mod(6))
        ruleset6_button.icon = ImageTk.PhotoImage(get_item_icon('The Soul'))
        ruleset6_button.configure(image=ruleset6_button.icon)
        ruleset6_button.grid(row=row, pady=5)
        self.window.bind('6', lambda event: ruleset6_button.invoke())
        row += 1

        # 7 - "Don't Stop" button
        ruleset7_button = tkinter.Button(self.window, text=' Don\'t Stop (7) ', compound='left', width=375)
        ruleset7_button.configure(font=('Helvetica', 13, 'bold'))
        ruleset7_button.configure(command=lambda: self.install_jud6s_mod(7))
        ruleset7_button.icon = ImageTk.PhotoImage(get_item_icon('The Belt'))
        ruleset7_button.configure(image=ruleset7_button.icon)
        ruleset7_button.grid(row=row, pady=5)
        self.window.bind('7', lambda event: ruleset7_button.invoke())
        row += 1

        # 8 - "Pageant Boy" button
        ruleset8_button = tkinter.Button(self.window, text=' Pageant Boy (8) ', compound='left', width=375)
        ruleset8_button.configure(font=('Helvetica', 13, 'bold'))
        ruleset8_button.configure(command=lambda: self.install_jud6s_mod(8))
        ruleset8_button.icon = ImageTk.PhotoImage(get_item_icon('Pageant Boy'))
        ruleset8_button.configure(image=ruleset8_button.icon)
        ruleset8_button.grid(row=row, pady=5)
        self.window.bind('8', lambda event: ruleset8_button.invoke())
        row += 1

        # Spacing
        spacing = tkinter.Message(self.window, text='', font='font 6')
        spacing.grid(row=row)
        row += 1

        # "About these options" button
        text = ' ' + get_text('About these options') + ' (9) '
        about_options_button = tkinter.Button(self.window, text=text, compound='left')
        about_options_button.configure(font=('Helvetica', 13))
        about_options_button.configure(command=self.about_options)
        about_options_button.icon = ImageTk.PhotoImage(get_item_icon('???'))
        about_options_button.configure(image=about_options_button.icon)
        about_options_button.grid(row=row, pady=5)
        self.window.bind('9', lambda event: about_options_button.invoke())
        row += 1

        # "Go Back" button
        text = ' ' + get_text('Go Back') + ' (Esc) '
        go_back_button = tkinter.Button(self.window, text=text, compound='left')
        go_back_button.configure(font=('Helvetica', 13))
        go_back_button.configure(command=self.go_back)
        go_back_button.icon = ImageTk.PhotoImage(Image.open('images/icons/go-back.png'))
        go_back_button.configure(image=go_back_button.icon)
        go_back_button.grid(row=row, pady=5)
        self.window.bind('<Escape>', lambda event: go_back_button.invoke())
        row += 1

        # Spacing
        spacing = tkinter.Message(self.window, text='', font='font 6')
        spacing.grid(row=row)
        row += 1

        # Instructions
        text = get_text('Isaac will open')
        m = tkinter.Message(self.window, justify=tkinter.CENTER, text=text, font='font 13', width=400)
        m.grid(row=row)
        row += 1
        text = get_text('Keep this program open')
        m = tkinter.Message(self.window, justify=tkinter.CENTER, text=text, font='font 13', width=400)
        m.grid(row=row)
        row += 1
        text = get_text('Isaac will return to normal') + '\n'
        m = tkinter.Message(self.window, justify=tkinter.CENTER, text=text, font='font 13', width=400)
        m.grid(row=row)
        row += 1

        # Spacing
        spacing = tkinter.Message(self.window, text='', font='font 6')
        spacing.grid(row=row)
        row += 1

        # Place the window at the X and Y coordinates from either the INI or the previous window
        self.window.deiconify()  # Show the GUI
        self.window.geometry('+%d+%d' % (window_x, window_y))

    def install_jud6s_mod(self, ruleset):
        # Remove everything from the resources directory EXCEPT the "packed" directory and config.ini
        for file_name in os.listdir(isaac_resources_directory):
            if file_name != 'packed' and file_name != 'config.ini':
                delete_file_if_exists(os.path.join(isaac_resources_directory, file_name))

        # If there isn't a config.ini in the resources directory already, copy over the default Jud6s one
        if not os.path.isfile(os.path.join(isaac_resources_directory, 'config.ini')):
            copy_file('other-files/config.ini', os.path.join(isaac_resources_directory, 'config.ini'))

        # Copy over the "jud6s" directory, which represents the base files for the mod
        for file_name in os.listdir('jud6s'):
            copy_file(os.path.join('jud6s', file_name), os.path.join(isaac_resources_directory, file_name))

        # Delete the "versusscreen.anm2" file if the user wants to keep the boss cutscenes
        if remove_boss_cutscenes == False:
            delete_file_if_exists(os.path.join(isaac_resources_directory, 'gfx/ui/boss/versusscreen.anm2'))

        # 1 - Normal / Unseeded
        if ruleset == 1:
            pass  # This requires no additional files

        # 2 - Seeded
        elif ruleset == 2:
            # Define the ruleset name
            ruleset_name = 'Ruleset 2 - Seeded'

            # The custom angel
            copy_file('jud6s-extra/' + ruleset_name + '/entities2.xml', os.path.join(isaac_resources_directory, 'entities2.xml'))

            # The removed unseeded items
            copy_file('jud6s-extra/' + ruleset_name + '/itempools.xml', os.path.join(isaac_resources_directory, 'itempools.xml'))

            # The removed trinkets
            copy_file('jud6s-extra/' + ruleset_name + '/items.xml', os.path.join(isaac_resources_directory, 'items.xml'))

            # The added starting items
            copy_file('jud6s-extra/' + ruleset_name + '/players.xml', os.path.join(isaac_resources_directory, 'players.xml'))

            # The custom angel rooms
            copy_file('jud6s-extra/' + ruleset_name + '/rooms/00.special rooms.stb', os.path.join(isaac_resources_directory, 'rooms/00.special rooms.stb'))

        # 3 - Dark Room
        elif ruleset == 3:
            # Define the ruleset name
            ruleset_name = 'Ruleset 3 - Dark Room'

            # The removed shovel
            copy_file('jud6s-extra/' + ruleset_name + '/itempools.xml', os.path.join(isaac_resources_directory, 'itempools.xml'))

            # The custom beam of light
            copy_file('jud6s-extra/' + ruleset_name + '/gfx/effects', os.path.join(isaac_resources_directory, 'gfx/effects'))

            # The custom Polaroid and Negative
            copy_file('jud6s-extra/' + ruleset_name + '/gfx/items', os.path.join(isaac_resources_directory, 'gfx/items'))

        # 4 - The Lost Child Open Loser's Bracket
        elif ruleset == 4:
            # Define the ruleset name
            ruleset_name = 'Ruleset 4 - LCO Loser\'s Bracket'

            # The removed shovel
            copy_file('jud6s-extra/' + ruleset_name + '/itempools.xml', os.path.join(isaac_resources_directory, 'itempools.xml'))

            # The custom beam of light
            copy_file('jud6s-extra/' + ruleset_name + '/gfx/effects', os.path.join(isaac_resources_directory, 'gfx/effects'))

            # The custom Polaroid and Negative
            copy_file('jud6s-extra/' + ruleset_name + '/gfx/items', os.path.join(isaac_resources_directory, 'gfx/items'))

            # The changed starting items and health
            copy_file('jud6s-extra/' + ruleset_name + '/players.xml', os.path.join(isaac_resources_directory, 'players.xml'))

            # The ruleset name is too long for the title screen, so make it shorter
            ruleset_name = 'LCO Loser\'s Bracket'

        # 5 - Mega Satan
        elif ruleset == 5:
            # Define the ruleset name
            ruleset_name = 'Ruleset 5 - Mega Satan'

            # The changed Dark Room
            copy_file('jud6s-extra/' + ruleset_name + '/rooms/16.dark room.stb', os.path.join(isaac_resources_directory, 'rooms/16.dark room.stb'))

            # The changed Chest
            copy_file('jud6s-extra/' + ruleset_name + '/rooms/17.chest.stb', os.path.join(isaac_resources_directory, 'rooms/17.chest.stb'))

            # The ruleset name is too long for the title screen, so make it shorter
            ruleset_name = 'Mega Satan'

        # 6 - Beginner
        elif ruleset == 6:
            # Define the ruleset name
            ruleset_name = 'Ruleset 6 - Beginner'

            # The extra half soul heart
            copy_file('jud6s-extra/' + ruleset_name + '/players.xml', os.path.join(isaac_resources_directory, 'players.xml'))

        # 7 - Don't Stop
        elif ruleset == 7:
            # Define the ruleset name
            ruleset_name = 'Ruleset 7 - Don\'t Stop'

            # The custom start rooms
            copy_file('jud6s-extra/' + ruleset_name + '/rooms/00.special rooms.stb', os.path.join(isaac_resources_directory, 'rooms/00.special rooms.stb'))
            copy_file('jud6s-extra/' + ruleset_name + '/rooms/16.dark room.stb', os.path.join(isaac_resources_directory, 'rooms/16.dark room.stb'))
            copy_file('jud6s-extra/' + ruleset_name + '/rooms/17.chest.stb', os.path.join(isaac_resources_directory, 'rooms/17.chest.stb'))

        # 8 - Pageant Boy
        elif ruleset == 8:
            # Define the ruleset name
            ruleset_name = 'Ruleset 8 - Pageant Boy'

            # The custom starting items
            copy_file('jud6s-extra/' + ruleset_name + '/players.xml', os.path.join(isaac_resources_directory, 'players.xml'))

            # The ruleset name is too long for the title screen, so make it shorter
            ruleset_name = 'Pageant Boy Tourney'

        # Draw a title screen and save it overtop the old title screen
        if ruleset != 1:
            title_img = Image.open('images/main-menu/titlemenu-base.png')
            title_draw = ImageDraw.Draw(title_img)

            title_screen_text = 'Jud6s Mod ' + jud6s_version
            large_font = ImageFont.truetype('fonts/IsaacSans.ttf', 19)
            w, h = title_draw.textsize(title_screen_text, font=large_font)
            title_draw.text((405 - w / 2, 239), title_screen_text, (134, 86, 86), font=large_font)

            small_font = ImageFont.truetype('fonts/IsaacSans.ttf', 15)
            w, h = title_draw.textsize(ruleset_name, font=small_font)
            title_draw.text((405 - w / 2, 256), ruleset_name, (134, 86, 86), font=small_font)

            title_img.save(os.path.join(isaac_resources_directory, 'gfx/ui/main menu/titlemenu.png'))

        # We are finished, so launch Isaac
        launch_isaac()

    def about_options(self):
        webbrowser.open('https://github.com/Zamiell/isaac-racing-mods/blob/master/docs/jud6s-mod-custom-rulesets.md')

    def go_back(self):
        get_window_x_y(self)
        self.window.destroy()
        ModSelectionWindow(self.parent)


###############################
# The Instant Start Mod window
###############################

class InstantStartWindow():
    def __init__(self, parent):
        self.builds = xml.etree.ElementTree.parse('xml/builds.xml').getroot()

        # By default, the user is not choosing a random build
        self.random_build = False

        # Initialize a new GUI window
        self.parent = parent
        self.window = tkinter.Toplevel(self.parent)
        self.window.withdraw()  # Hide the GUI
        self.window.title('Instant Start Mod v' + mod_version)  # Set the GUI title
        self.window.iconbitmap('images/icons/more_options.ico')  # Set the GUI icon
        self.window.resizable(False, False)
        self.window.protocol('WM_DELETE_WINDOW', lambda: uninstall_mod(self))  # Uninstall mod files when the window is closed

        # Start counting rows
        row = 0

        # Spacing
        m = tkinter.Message(self.window, text='', font='font 7')
        m.grid(row=row)
        row += 1

        # Button width
        if language == 'fr' or language == 'ru':
            button_width = 300
        else:
            button_width = 250

        # "Choose a Start" button
        text = ' ' + get_text('Choose a Start') + ' (1) '
        choose_start_button = tkinter.Button(self.window, text=text, compound='left', font='font 16', width=button_width)
        choose_start_button.configure(command=self.show_start_selector_window)
        choose_start_button.icon = ImageTk.PhotoImage(get_item_icon('More Options'))
        choose_start_button.configure(image=choose_start_button.icon)
        choose_start_button.grid(row=row, pady=5)
        self.window.bind('1', lambda event: choose_start_button.invoke())
        row += 1

        # "Random Start" button
        text = ' ' + get_text('Random Start') + ' (2) '
        random_start_button = tkinter.Button(self.window, text=text, compound='left', font='font 16', width=button_width)
        random_start_button.configure(command=self.get_random_start)
        random_start_button.icon = ImageTk.PhotoImage(get_item_icon('???'))
        random_start_button.configure(image=random_start_button.icon)
        random_start_button.grid(row=row, pady=5)
        self.window.bind('2', lambda event: random_start_button.invoke())
        row += 1

        # Spacing
        m = tkinter.Message(self.window, text='', font='font 7')
        m.grid(row=row)
        row += 1

        # Seeded checkbox
        self.seeded_mode = tkinter.IntVar()
        self.seeded_checkbox = tkinter.Checkbutton(self.window, text='Seeded (3)', font='font 14', variable=self.seeded_mode, command=self.seeded_checkbox_checked)
        self.seeded_checkbox.grid(row=row)
        self.window.bind('3', lambda event: self.seeded_checkbox.invoke())
        row += 1

        # Seeded+ checkbox
        self.seeded2_mode = tkinter.IntVar()
        self.seeded2_checkbox = tkinter.Checkbutton(self.window, text='Seeded+ (4)', font='font 14', variable=self.seeded2_mode, command=self.seeded2_checkbox_checked)
        self.seeded2_checkbox.grid(row=row)
        self.window.bind('4', lambda event: self.seeded2_checkbox.invoke())
        row += 1

        # LCO checkbox
        self.LCO_mode = tkinter.IntVar()
        LCO_checkbox = tkinter.Checkbutton(self.window, anchor=tkinter.E, text='Lost Child Open (5)', font='font 14', variable=self.LCO_mode)
        LCO_checkbox.grid(row=row)
        self.window.bind('5', lambda event: LCO_checkbox.invoke())
        row += 1

        # Mega Satan checkbox
        self.mega_satan_mode = tkinter.IntVar()
        mega_satan_checkbox = tkinter.Checkbutton(self.window, text='Mega Satan (6)', font='font 14', variable=self.mega_satan_mode)
        mega_satan_checkbox.grid(row=row)
        self.window.bind('6', lambda event: mega_satan_checkbox.invoke())
        row += 1

        # Spacing
        m = tkinter.Message(self.window, text='', font='font 7')
        m.grid(row=row)
        row += 1

        # "About these options" button
        text = ' ' + get_text('About these options') + ' (7) '
        about_options_button = tkinter.Button(self.window, text=text, compound='left')
        about_options_button.configure(font=('Helvetica', 13))
        about_options_button.configure(command=self.about_options)
        about_options_button.icon = ImageTk.PhotoImage(get_item_icon('???'))
        about_options_button.configure(image=about_options_button.icon)
        about_options_button.grid(row=row, pady=5)
        self.window.bind('7', lambda event: about_options_button.invoke())
        row += 1

        # "Go Back" button
        text = ' ' + get_text('Go Back') + ' (Esc) '
        go_back_button = tkinter.Button(self.window, text=text, compound='left')
        go_back_button.configure(font=('Helvetica', 13))
        go_back_button.icon = ImageTk.PhotoImage(Image.open('images/icons/go-back.png'))
        go_back_button.configure(image=go_back_button.icon)
        go_back_button.configure(command=self.go_back)
        go_back_button.grid(row=row, pady=5)
        self.window.bind('<Escape>', lambda event: go_back_button.invoke())
        row += 1

        # Spacing
        m = tkinter.Message(self.window, text='', font='font 7')
        m.grid(row=row)
        row += 1

        # Instructions
        text = get_text('Isaac will open')
        m = tkinter.Message(self.window, justify=tkinter.CENTER, text=text, font='font 13', width=400)
        m.grid(row=row)
        row += 1
        text = get_text('Keep this program open')
        m = tkinter.Message(self.window, justify=tkinter.CENTER, text=text, font='font 13', width=400)
        m.grid(row=row)
        row += 1
        text = get_text('Isaac will return to normal') + '\n'
        m = tkinter.Message(self.window, justify=tkinter.CENTER, text=text, font='font 13', width=400)
        m.grid(row=row)
        row += 1

        # Place the window at the X and Y coordinates from either the INI or the previous window
        self.window.deiconify()  # Show the GUI
        self.window.geometry('+%d+%d' % (window_x, window_y))

    def seeded_checkbox_checked(self):
        if self.seeded_mode.get() == 1 and self.seeded2_mode.get() == 1:
            self.seeded2_checkbox.invoke()

    def seeded2_checkbox_checked(self):
        if self.seeded_mode.get() == 1 and self.seeded2_mode.get() == 1:
            self.seeded_checkbox.invoke()

    def show_start_selector_window(self):
        #######################################
        # show_start_selector_window variables
        #######################################

        icon_zoom = 1.3
        icon_filter = Image.BILINEAR

        #######################################
        # show_start_selector_window functions
        #######################################

        # Callback for clicking on a build
        def select_build(event=None, build_widget=None):
            widget = event.widget if event else build_widget
            while not hasattr(widget, 'build'):
                if widget == self.parent:
                    return
                widget = widget._nametowidget(widget.winfo_parent())
            self.current_build = widget.build.attrib['id']
            self.install_instant_start_mod()
            go_back()

        # Callback for the search entry changing; will hide any builds that don't match the search
        def search_builds(sv):
            search_term = sv.get().lower()
            for widget in build_frames:
                item_name = widget.build.attrib['id']

                # This is enclosed in a try/except block because the user can enter regex special characters that will break the parse
                try:
                    if re.search('^' + search_term, item_name):
                        widget.grid()
                    else:
                        widget.grid_remove()
                except Exception as e:  # No builds will match a regex special character, so default to not showing anything
                    widget.grid_remove()
            canvas.yview_moveto(0)

        # Callback for pressing enter in the search box: launch the topmost visible build
        def select_search_builds():
            for child in build_frames:
                if child.winfo_manager() != '':
                    select_build(build_widget=child)
                    break

        def make_hearts_and_consumables_canvas(parent, build):
            current_canvas = tkinter.Canvas(parent, bg=current_bgcolor)
            current = 0
            redhearts = build.attrib.get('redhearts')
            soulhearts = build.attrib.get('soulhearts')
            blackhearts = build.attrib.get('blackhearts')
            heartcontainers = build.attrib.get('heartcontainers')

            def add_hearts(amount, type):
                curr = current
                for i in range(0, amount):
                    widget = tkinter.Label(current_canvas, bg=current_bgcolor)
                    widget.image = hearts_list[type]
                    widget.configure(image=widget.image)
                    widget.bind('<Button-1>', select_build)
                    widget.grid(column=curr % 6, row=0 if curr < 6 else 1)
                    curr += 1
                return curr

            if redhearts:
                fullreds = int(int(redhearts) / 2)
                current = add_hearts(fullreds, 0)
                if int(redhearts) % 2 == 1:
                    current = add_hearts(1, 1)
            if heartcontainers:
                current = add_hearts(int(heartcontainers) / 2, 2)
            if soulhearts:
                fullsouls = int(int(soulhearts) / 2)
                current = add_hearts(fullsouls, 5)
                if int(soulhearts) % 2 == 1:
                    current = add_hearts(1, 6)
            if blackhearts:
                fullblacks = int(int(blackhearts) / 2)
                current = add_hearts(fullblacks, 7)
                if int(blackhearts) % 2 == 1:
                    add_hearts(1, 8)
            if build.attrib.get('coins') or build.attrib.get('bombs') or build.attrib.get('keys'):
                widget = tkinter.Label(current_canvas, text='     Consumables:', bg=current_bgcolor)
                widget.bind('<Button-1>', select_build)
                widget.grid(column=current, row=0, rowspan=2, sticky=tkinter.NSEW)
                current += 1
            if build.attrib.get('coins'):
                widget = tkinter.Label(current_canvas, bg=current_bgcolor)
                widget.image = hud_list[0]
                widget.configure(image=widget.image)
                widget.bind('<Button-1>', select_build)
                widget.grid(column=current, row=0, rowspan=2)
                current += 1
                widget = tkinter.Label(current_canvas, text=build.attrib['coins'], bg=current_bgcolor)
                widget.bind('<Button-1>', select_build)
                widget.grid(column=current, row=0, rowspan=2, sticky=tkinter.NSEW)
                current += 1
            if build.attrib.get('bombs'):
                widget = tkinter.Label(current_canvas, bg=current_bgcolor)
                widget.image = hud_list[3]
                widget.configure(image=widget.image)
                widget.bind('<Button-1>', select_build)
                widget.grid(column=current, row=0, rowspan=2)
                current += 1
                widget = tkinter.Label(current_canvas, text=build.attrib['bombs'], bg=current_bgcolor)
                widget.bind('<Button-1>', select_build)
                widget.grid(column=current, row=0, rowspan=2, sticky=tkinter.NSEW)
                current += 1
            if build.attrib.get('keys'):
                widget = tkinter.Label(current_canvas, bg=current_bgcolor)
                widget.image = hud_list[1]
                widget.configure(image=widget.image)
                widget.bind('<Button-1>', select_build)
                widget.grid(column=current, row=0, rowspan=2)
                current += 1
                widget = tkinter.Label(current_canvas, text=build.attrib['keys'], bg=current_bgcolor)
                widget.bind('<Button-1>', select_build)
                widget.grid(column=current, row=0, rowspan=2, sticky=tkinter.NSEW)
                current += 1
            if build.attrib.get('card'):
                card_info = xml.etree.ElementTree.parse('game-files/pocketitems.xml').getroot()
                card_name = None
                for card in card_info:
                    if card.tag in ['card', 'rune'] and card.attrib['id'] == build.attrib['card']:
                        card_name = card.attrib['name']
                        break
                widget = tkinter.Label(current_canvas, text='     Card: ' + card_name, bg=current_bgcolor)
                widget.bind('<Button-1>', select_build)
                widget.grid(column=current, row=0, rowspan=2, sticky=tkinter.NSEW)
                current += 1

            return current_canvas

        # get_heart_icons - Process the hearts image into the individual heart icons and return them
        def get_heart_icons():
            '''
            hearts_list[0] = # Full red
            hearts_list[1] = # Half red
            hearts_list[2] = # Empty heart
            hearts_list[3] = # Left half eternal
            hearts_list[4] = # Right half eternal overlap
            hearts_list[5] = # Full soul
            hearts_list[6] = # Half soul
            hearts_list[7] = # Full black
            hearts_list[8] = # Half black
            '''
            hearts_list = [None] * 9
            hearts = Image.open('images/ui_hearts.png')

            # 16x16 left upper right lower
            for index in range(0, 9):
                left = (16 * index) % 80
                top = 0 if index < 5 else 16
                bottom = top + 16
                right = left + 16
                hearts_list[index] = hearts.crop((left, top, right, bottom))
                hearts_list[index] = hearts_list[index].resize((int(hearts_list[index].width * icon_zoom), int(hearts_list[index].height * icon_zoom)), icon_filter)
                hearts_list[index] = ImageTk.PhotoImage(hearts_list[index])
            return hearts_list

        # get_hud_icons - Parse the hudpickups.png file that contains all of the pickup grahpics
        def get_hud_icons():
            '''
            hud_icons[0] = # coins
            hud_icons[1] = # keys
            hud_icons[2] = # hardmode
            hud_icons[3] = # bombs
            hud_icons[4] = # golden key
            hud_icons[5] = # no achievements
            '''
            hud_icons = [None] * 6
            hud_image = Image.open('images/hudpickups.png')

            for index in range(0, 6):
                left = (16 * index) % 48
                top = 0 if index < 3 else 16
                bottom = top + 16
                right = left + 16
                hud_icons[index] = hud_image.crop((left, top, right, bottom))
                hud_icons[index] = hud_icons[index].resize((int(hud_icons[index].width * icon_zoom), int(hud_icons[index].height * icon_zoom)), icon_filter)
                hud_icons[index] = ImageTk.PhotoImage(hud_icons[index])
            return hud_icons

        # _configure_interior - Track changes to the canvas and frame width and sync them (while also updating the scrollbar)
        def _configure_interior(event):
            # Update the scrollbars to match the size of the inner frame
            size = (image_box.winfo_reqwidth(), image_box.winfo_reqheight())
            canvas.config(scrollregion='0 0 %s %s' % size)
            if image_box.winfo_reqwidth() != canvas.winfo_width():
                # Update the canvas's width to fit the inner frame
                canvas.config(width=image_box.winfo_reqwidth())

        def _configure_canvas(event):
            if image_box.winfo_reqwidth() != image_box.winfo_width():
                # Update the inner frame's width to fill the canvas
                canvas.itemconfigure(interior_id, width=canvas.winfo_width())

        # _on_mousewheel - Code taken from: https://stackoverflow.com/questions/17355902/python-tkinter-binding-mousewheel-to-scrollbar
        def _on_mousewheel(event):
            canvas.yview_scroll(int(-1 * (event.delta / 120)), 'units')

        def go_back():
            get_window2_x_y(self)
            self.window2.destroy()  # Destroy the "Start Selector" window
            self.window.deiconify()  # Show the main Instant Start window
            self.window.geometry('+%d+%d' % (window_x, window_y))

        #############################
        # show_start_selector_window
        #############################

        # Initialize a new GUI window
        get_window_x_y(self)
        self.window.withdraw()  # Hide the main Instant Start window
        self.window2 = tkinter.Toplevel(self.parent)

        self.window2.withdraw()  # Hide the GUI
        self.window2.title('Start Selector')  # Set the GUI title
        self.window2.iconbitmap('images/icons/more_options.ico')  # Set the GUI icon
        self.window2.resizable(False, True)
        self.window2.protocol('WM_DELETE_WINDOW', lambda: uninstall_mod(self))

        # Initialize the scrolling canvas
        canvas = tkinter.Canvas(self.window2, borderwidth=0)
        scrollbar = tkinter.Scrollbar(canvas, orient='vertical', command=canvas.yview)
        scrollbar.pack(side='right', fill='y')
        canvas.configure(yscrollcommand=scrollbar.set)
        canvas.configure(scrollregion=canvas.bbox('all'), width=200, height=200)
        canvas.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=tkinter.TRUE)

        # Scrolling code taken from: https://stackoverflow.com/questions/16188420/python-tkinter-scrollbar-for-frame
        image_box = tkinter.LabelFrame(canvas, borderwidth=0)
        interior_id = canvas.create_window(0, 0, window=image_box, anchor=tkinter.NW)
        image_box.bind('<Configure>', _configure_interior)
        canvas.bind('<Configure>', _configure_canvas)

        # Define keyboard bindings
        self.window2.bind('<MouseWheel>', _on_mousewheel)
        self.window2.bind('<Home>', lambda event: canvas.yview_moveto(0))
        self.window2.bind('<End>', lambda event: canvas.yview_moveto(1))
        self.window2.bind('<Prior>', lambda event: canvas.yview_scroll(-1, 'pages'))  # PgUp
        self.window2.bind('<Next>', lambda event: canvas.yview_scroll(1, 'pages'))  # PgDn
        self.window2.bind('<Escape>', lambda event: go_back())

        # Start counting rows
        row = 0

        # Start to build the GUI window
        hearts_list = get_heart_icons()
        hud_list = get_hud_icons()
        text = get_text('Click a build to play it')
        tkinter.Label(image_box, text=text, font='font 32 bold').grid(row=row, pady=5)
        row += 1

        # Search label
        search_widget_space = tkinter.Label(image_box)
        text = get_text('Search') + ': '
        search_label = tkinter.Label(search_widget_space, text=text, font=('Helvetica', 15))
        search_label.grid(row=0, column=0)

        # Search text box
        build_search_string = tkinter.StringVar()
        build_search_string.trace('w', lambda name, index, mode, sv=build_search_string: search_builds(sv))
        self.window2.entry = tkinter.Entry(search_widget_space, font=('Helvetica', 15), width=12, textvariable=build_search_string)
        self.window2.entry.bind('<Return>', lambda event: select_search_builds())
        self.window2.entry.grid(row=0, column=1)
        self.window2.entry.focus()  # Put the focus on the entry box whenever this window is opened
        search_widget_space.grid(row=row)
        row += 1

        # "Go Back" button
        text = ' ' + get_text('Go Back') + ' (Esc) '
        go_back_button = tkinter.Button(image_box, text=text, compound='left')
        go_back_button.configure(font=('Helvetica', 11))
        go_back_button.icon = ImageTk.PhotoImage(Image.open('images/icons/go-back.png'))
        go_back_button.configure(image=go_back_button.icon)
        go_back_button.configure(command=go_back)  # Goes to nested function, not class function
        go_back_button.grid(row=row, pady=15)
        row += 1

        # Parse builds.xml
        builds = xml.etree.ElementTree.parse('xml/builds.xml').getroot()  # This file contains all of the predetermined starts for the mod

        # Build the frames
        current_bgcolor = '#949494'
        build_frames = [None] * len(builds)
        for index, child in enumerate(builds):
            # Draw dividers in between the different kinds of starts
            if index == 0:
                tkinter.Message(image_box, text='', font='font 12').grid(row=row)  # Spacing
                row += 1
                text = get_text('Treasure Room Starts')
                tkinter.Label(image_box, text=text, font='font 20 bold').grid(row=row, pady=5)
                row += 1
            elif index == 19:
                tkinter.Message(image_box, text='', font='font 12').grid(row=row)  # Spacing
                row += 1
                text = get_text('Devil Room Starts')
                tkinter.Label(image_box, text=text, font='font 20 bold').grid(row=row, pady=5)
                row += 1
            elif index == 22:
                tkinter.Message(image_box, text='', font='font 12').grid(row=row)  # Spacing
                row += 1
                text = get_text('Angel Room Starts')
                tkinter.Label(image_box, text=text, font='font 20 bold').grid(row=row, pady=5)
                row += 1
            elif index == 25:
                tkinter.Message(image_box, text='', font='font 12').grid(row=row)  # Spacing
                row += 1
                text = get_text('Custom Starts (with the D6)')
                tkinter.Label(image_box, text=text, font='font 20 bold').grid(row=row, pady=5)
                row += 1
            elif index == 29:
                tkinter.Message(image_box, text='', font='font 12').grid(row=row)  # Spacing
                row += 1
                text = get_text('Custom Starts (without the D6)')
                tkinter.Label(image_box, text=text, font='font 20 bold').grid(row=row, pady=5)
                row += 1

            # Background color
            current_bgcolor = '#E0E0E0' if current_bgcolor == '#949494' else '#949494'

            # Draw the build frame here
            build_frame = tkinter.LabelFrame(image_box, bg=current_bgcolor)
            build_frame.bind('<Button-1>', select_build)
            build_frame.build = child
            build_frames[index] = build_frame

            # ID
            widget = tkinter.Label(build_frame, text=child.attrib['id'], font='font 32 bold', bg=current_bgcolor, width=2)
            widget.bind('<Button-1>', select_build)
            widget.grid(row=0, rowspan=3)

            # Items
            widget = tkinter.Label(build_frame, text='Item: ', bg=current_bgcolor)
            widget.bind('<Button-1>', select_build)
            widget.grid(row=0, column=1, sticky=tkinter.E)
            items_frame = tkinter.Canvas(build_frame)
            items = child.attrib.get('items')
            if items:
                items = items.split(' + ')
                luck_up = 0
                for i, item in enumerate(items):
                    if item == 'Lucky Foot':
                        luck_up += 1
                        continue
                    widget = tkinter.Label(items_frame, bg=current_bgcolor)
                    widget.image = get_item_icon(item)
                    widget.image = ImageTk.PhotoImage(widget.image.resize((int(widget.image.width * icon_zoom), int(widget.image.height * icon_zoom)), icon_filter))
                    widget.configure(image=widget.image)
                    widget.bind('<Button-1>', select_build)
                    widget.grid(row=0, column=i)
                if luck_up > 0:
                    widget = tkinter.Label(items_frame, bg=current_bgcolor)
                    widget.image = get_item_icon('Lucky Foot')
                    widget.image = ImageTk.PhotoImage(widget.image.resize((int(widget.image.width * icon_zoom), int(widget.image.height * icon_zoom)), icon_filter))
                    widget.configure(image=widget.image)
                    widget.bind('<Button-1>', select_build)
                    widget.grid(row=0, column=i + 1)
                    widget = tkinter.Label(build_frame, text='x' + str(luck_up), bg=current_bgcolor)
                    widget.grid(row=0, column=i + 2)

            # Trinket (appended to the end of the items)
            trinket = child.attrib.get('trinket')
            if trinket:
                widget = tkinter.Label(items_frame, bg=current_bgcolor)
                widget.image = get_trinket_icon(trinket)
                widget.image = ImageTk.PhotoImage(widget.image.resize((int(widget.image.width * icon_zoom), int(widget.image.height * icon_zoom)), icon_filter))
                widget.configure(image=widget.image)
                widget.bind('<Button-1>', select_build)
                widget.grid(row=0, column=len(items) + 1)
            items_frame.grid(row=0, column=2, sticky=tkinter.W)

            # Health (currently commented out since all builds will start with default health)
            '''
            widget = tkinter.Label(build_frame, text='Health: ', bg=current_bgcolor)
            widget.bind('<Button-1>', select_build)
            widget.grid(row=1, column=1, sticky=tkinter.E)

            hearts_and_consumables_frame = make_hearts_and_consumables_canvas(build_frame, child)
            hearts_and_consumables_frame.bind('<Button-1>', select_build)
            hearts_and_consumables_frame.grid(row=1, column=2, sticky=tkinter.W)
            '''

            # Removed Items (currently commented out since all builds will not have any removed items)
            '''
            widget = tkinter.Label(build_frame, text='Removed Items: ', bg=current_bgcolor)
            widget.bind('<Button-1>', select_build)
            widget.grid(row=2, column=1, sticky=tkinter.E)
            '''
            removed_items = child.attrib.get('removed')
            if removed_items:
                removed_items = removed_items.split(' + ')
                removed_items_frame = tkinter.Canvas(build_frame, bg=current_bgcolor)
                for i, item in enumerate(removed_items):
                    widget = tkinter.Label(removed_items_frame, bg=current_bgcolor)
                    widget.image = get_item_icon(item)
                    widget.image = ImageTk.PhotoImage(widget.image.resize((int(widget.image.width * icon_zoom), int(widget.image.height * icon_zoom)), icon_filter))
                    widget.configure(image=widget.image)
                    widget.bind('<Button-1>', select_build)
                    widget.grid(row=2, column=i)
                removed_items_frame.grid(row=2, column=2, sticky=tkinter.W)
            else:
                # Keep the spacing consistent by adding an empty invisible frame of the same height as an item
                tkinter.Frame(build_frame, width=0, height=32, bg=current_bgcolor, borderwidth=0).grid(row=2, column=2, sticky=tkinter.W)
            build_frame.grid(row=row, pady=5, padx=3, sticky=tkinter.W + tkinter.E)
            row += 1

        # Place the window at the X and Y coordinates from either the INI or the previous window
        self.window2.deiconify()  # Show the GUI
        self.window2.update_idletasks()  # Update the GUI
        window_width = image_box.winfo_width() + scrollbar.winfo_width() + 2
        window_height = max(min(int(self.window2.winfo_vrootheight() * 2 / 3), image_box.winfo_height() + 4), self.window2.winfo_height())
        self.window2.geometry('%dx%d+%d+%d' % (window_width, window_height, window_x, window_y))

    def get_random_start(self):
        self.current_build = str(random.randint(1, len(self.builds)))
        self.random_build = True
        self.install_instant_start_mod()

    def install_instant_start_mod(self):
        ####################
        # Utility functions
        ####################

        def remove_item_from_all_item_pools(item_name):
            item_id = get_item_id(item_name)
            for pool in itempools_info:
                for item in pool.findall('Item'):
                    if item.attrib['Id'] == item_id:  # Weirdly, it is capitalized this way in the vanilla XML
                        pool.remove(item)

        def remove_item_from_shop_pool(item_name):
            item_id = get_item_id(item_name)
            for pool in itempools_info:
                if pool.attrib['Name'] != 'shop':
                    continue
                for item in pool.findall('Item'):
                    if item.attrib['Id'] == item_id:  # Weirdly, it is capitalized this way in the vanilla XML
                        pool.remove(item)

        def add_item_to_shop_pool(item_name):
            item_id = get_item_id(item_name)
            for pool in itempools_info:
                if pool.attrib['Name'] == 'shop':
                    item_xml = '<Item DecreaseBy="1" Id="' + item_id + '" RemoveOn="0.1" Weight="1" />'
                    pool.append(xml.etree.ElementTree.fromstring(item_xml))

        def change_shop_removal():
            for pool in itempools_info:
                if pool.attrib['Name'] == 'shop':
                    for item in pool.findall('Item'):
                        item.attrib['DecreaseBy'] = '1'

        #############################
        # The main installation code
        #############################

        # Remove everything from the resources directory EXCEPT the "packed" directory and config.ini
        for file_name in os.listdir(isaac_resources_directory):
            if file_name != 'packed' and file_name != 'config.ini':
                delete_file_if_exists(os.path.join(isaac_resources_directory, file_name))

        # If there isn't a config.ini in the resources directory already, copy over the default Jud6s one
        if not os.path.isfile(os.path.join(isaac_resources_directory, 'config.ini')):
            copy_file('other-files/config.ini', os.path.join(isaac_resources_directory, 'config.ini'))

        # Copy over the "jud6s" directory, which represents the base files for the mod
        for file_name in os.listdir('jud6s'):
            copy_file(os.path.join('jud6s', file_name), os.path.join(isaac_resources_directory, file_name))

        # Delete the "versusscreen.anm2" file if the user wants to keep the boss cutscenes
        if remove_boss_cutscenes == False:
            delete_file_if_exists(os.path.join(isaac_resources_directory, 'gfx/ui/boss/versusscreen.anm2'))

        # Make a custom title menu graphic that represents which options were selected
        self.draw_title_graphic()

        # If this is seeded mode, copy over the special angel rooms
        if self.seeded_mode.get() == 1 or self.seeded2_mode.get() == 1:
            # Copy the custom angel rooms
            copy_file('jud6s-extra/Ruleset 2 - Seeded/rooms/00.special rooms.stb', os.path.join(isaac_resources_directory, 'rooms/00.special rooms.stb'))

            # Copy the custom angel entity
            copy_file('jud6s-extra/Ruleset 2 - Seeded/entities2.xml', os.path.join(isaac_resources_directory, 'entities2.xml'))

        # If this is LCO mode, copy over the special graphics
        if self.LCO_mode.get() == 1:
            # Make the directories if they don't already exist
            if not os.path.isdir(os.path.join(isaac_resources_directory, 'gfx/items')):
                create_directory(os.path.join(isaac_resources_directory, 'gfx/items'))
            if not os.path.isdir(os.path.join(isaac_resources_directory, 'gfx/items/collectibles')):
                create_directory(os.path.join(isaac_resources_directory, 'gfx/items/collectibles'))
            if not os.path.isdir(os.path.join(isaac_resources_directory, 'gfx/effects')):
                create_directory(os.path.join(isaac_resources_directory, 'gfx/effects'))

            # Copy the 3 special graphic files
            copy_file('jud6s-extra/Ruleset 4 - LCO Loser\'s Bracket/gfx/effects/effect_007_light.png', os.path.join(isaac_resources_directory, 'gfx/effects/effect_007_light.png'))
            copy_file('jud6s-extra/Ruleset 4 - LCO Loser\'s Bracket/gfx/items/collectibles/collectibles_327_thepolaroid.png', os.path.join(isaac_resources_directory, 'gfx/items/collectibles/collectibles_327_thepolaroid.png'))
            copy_file('jud6s-extra/Ruleset 4 - LCO Loser\'s Bracket/gfx/items/collectibles/collectibles_328_thenegative.png', os.path.join(isaac_resources_directory, 'gfx/items/collectibles/collectibles_328_thenegative.png'))

        # If this is Mega Satan mode, copy over the two modified floors
        if self.mega_satan_mode.get() == 1:
            # Copy the Dark Room and The Chest
            copy_file('jud6s-extra/Ruleset 5 - Mega Satan/rooms/16.dark room.stb', os.path.join(isaac_resources_directory, 'rooms/16.dark room.stb'))
            copy_file('jud6s-extra/Ruleset 5 - Mega Satan/rooms/17.chest.stb', os.path.join(isaac_resources_directory, 'rooms/17.chest.stb'))

        # Currently, "current_build" is an integer that corresponds to the build ID
        for build in self.builds:
            if build.attrib['id'] == self.current_build:
                self.current_build = build  # Set it to the XML element corresponding to the build from builds.xml
                break
        if isinstance(self.current_build, str):
            error('Something went wrong with selecting a build. (The selected build number of ' + self.current_build + ' did not match any of the builds.)', None)

        # Parse players.xml, items.xml, and itempools.xml
        players_xml = xml.etree.ElementTree.parse('jud6s/players.xml')
        players_info = players_xml.getroot()
        items_xml = xml.etree.ElementTree.parse('xml/items.vanilla.xml')
        items_info = items_xml.getroot()
        itempools_xml = xml.etree.ElementTree.parse('xml/itempools.vanilla.xml')
        itempools_info = itempools_xml.getroot()

        # Parse the build info
        items = self.current_build.get('items')
        trinket = self.current_build.get('trinket')
        removed_items = self.current_build.get('removed')
        redhearts = self.current_build.get('redhearts')
        soulhearts = self.current_build.get('soulhearts')
        blackhearts = self.current_build.get('blackhearts')
        heartcontainers = self.current_build.get('heartcontainers')
        keys = self.current_build.get('keys')
        coins = self.current_build.get('coins')
        bombs = self.current_build.get('bombs')
        card = self.current_build.get('card')
        blindfolded = self.current_build.get('blindfolded')

        # Remove Karma from the game (done in the Jud6s mod)
        for item in items_info:
            if item.tag == 'trinket' and item.attrib['name'] == 'Karma':
                item.attrib['achievement'] = '0'

        # Remove Cain's Eye and Broken Remote from the game (done in the seeded Jud6s mod)
        if self.seeded_mode.get() == 1 or self.seeded2_mode.get() == 1:
            for item in items_info:
                if item.tag == 'trinket' and item.attrib['name'] == 'Cain\'s Eye':
                    item.attrib['achievement'] = '0'
                if item.tag == 'trinket' and item.attrib['name'] == 'Broken Remote':
                    item.attrib['achievement'] = '0'

        # Add starting items
        if self.seeded_mode.get() == 1 or self.seeded2_mode.get() == 1:
            if items:
                items += ' + The Compass'
            else:
                items = 'The Compass'
        if self.LCO_mode.get() == 1:
            if items:
                items += ' + Judas\' Shadow'
            else:
                items = 'Judas\' Shadow'
        if items or trinket:
            if items:
                items = items.split(' + ')
            for character in players_info:
                # These are all of the 13 characters
                characterList = ['Isaac', 'Magdalene', 'Cain', 'Judas', '???', 'Eve', 'Samson', 'Azazel', 'Lazarus', 'Eden', 'The Lost', 'Lilith', 'Keeper']

                # Go through the 13 characters
                if character.attrib['name'] in characterList:
                    # Set starting health (currently only done in LCO mode)
                    if self.LCO_mode.get() == 1:
                        character.set('hp', '0')
                        character.set('armor', '0')

                    # Set a trinket
                    if trinket:
                        character.set('trinket', get_trinket_id(trinket))

                    # Set the item(s)
                    if items:
                        for i in range(0, len(items)):
                            items[i] = get_item_id(items[i])

                        # Give them an "items" attribute if they don't already have it
                        if 'items' not in character.attrib.keys():  # Necessary for Eden
                            character.attrib['items'] = ''

                        # Add the items to the player.xml
                        for item in items:
                            id = get_item_id(item)

                            if character.attrib['items'] != '':
                                character.attrib['items'] += ','
                            character.attrib['items'] += id

                        # Remove soul hearts and black hearts from items in items.xml (normal health ups are okay)
                        for item in items_info:
                            if item.attrib['id'] in items:
                                for key in ['soulhearts', 'blackhearts']:
                                    if key in item.attrib:
                                        del item.attrib[key]

                    # Set pickups
                    if coins:
                        character.set('coins', coins)
                    if bombs:
                        character.set('bombs', bombs)
                    if keys:
                        character.set('keys', keys)
                    if card:
                        character.set('card', card)

                    # Set blindfolded
                    if blindfolded:
                        character.set('canShoot', 'false')

        # Set the player's health (commented out since we always want the default health)
        '''
        for item in items_info:
            if item.attrib['name'] == 'The D6':
                item.set('hearts', redhearts)
                item.set('maxhearts', str(int(redhearts) + (int(redhearts) % 2) + (int(heartcontainers) if heartcontainers else 0)))
                if soulhearts:
                    item.set('soulhearts', soulhearts)
                if blackhearts:
                    item.set('blackhearts', blackhearts)
        '''

        # Remove items from the pools
        if self.seeded_mode.get() == 1 or self.seeded2_mode.get() == 1:
            item_list = 'Pandora\'s Box + Teleport! + Undefined + The Book of Sin'
            if removed_items:
                removed_items += ' + ' + item_list
            else:
                removed_items = item_list
        if self.LCO_mode.get() == 1:
            item_list = 'We Need To Go Deeper!'
            if removed_items:
                removed_items += ' + ' + item_list
            else:
                removed_items = item_list
        if removed_items:
            removed_items = removed_items.split(' + ')
            for item in removed_items:
                remove_item_from_all_item_pools(item)

        # Do the changes for seeded+
        if self.seeded2_mode.get() == 1:
            change_shop_removal()  # Change "DecreaseBy" on everything from 0.5 to 1
            removed_shop_items = [
                # Active items
                'Box Of Friends',
                'Converter',
                'Deck Of Cards',
                'Glowing Hour Glass',
                'Mom\'s Bottle Of Pills',
                'Mom\'s Box',
                'Pandora\'s Box',
                'Placebo',
                'Portable Slot',
                'Red Candle',
                'Remote Detonator',
                'Tear Detonator',
                'The Bible',
                'The Boomerang',
                'The Candle',
                'The Jar',
                'Ventricle Razor',
                'Wooden Nickel',

                # Passive Items
                '9 Volt',
                'Bogo Bombs',
                'Broken Watch',
                'Car Battery',
                'Deep Pockets',
                'Fanny Pack',
                'Humbleing Bundle',
                'Little Baggy',
                'Mom\'s Coin Purse',
                'Mom\'s Key',
                'Mom\'s Purse',
                'Night Light',
                'PHD',
                'Pay To Play',
                'Piggy Bank',
                'Spider Mod',
                'Steam Sale',
                'The Compass',
                'The Ladder'
            ]
            for item in removed_shop_items:
                remove_item_from_shop_pool(item)

            added_shop_items = [
                'Steven',
                'Blood of the Martyr',
                'Bob\'s Brain',
                'Speed Ball',
                'The Sad Onion',
                'Cupid\'s Arrow'
            ]
            for item in added_shop_items:
                add_item_to_shop_pool(item)

        # Write the changes to the copied over XML files
        players_xml.write(os.path.join(isaac_resources_directory, 'players.xml'))
        itempools_xml.write(os.path.join(isaac_resources_directory, 'itempools.xml'))
        items_xml.write(os.path.join(isaac_resources_directory, 'items.xml'))

        # Draw the start room background and save it in place
        if not os.path.isdir(os.path.join(isaac_resources_directory, 'gfx/backdrop')):
            create_directory(os.path.join(isaac_resources_directory, 'gfx/backdrop'))
        start_room_background = draw_start_room_background(items, removed_items, trinket)
        start_room_background.save(os.path.join(isaac_resources_directory, 'gfx/backdrop/controls.png'))

        # Open the character menu graphic and write the item/build on it
        color = (54, 47, 45)
        character_menu = Image.open('images/main-menu/charactermenu.png')
        character_menu_draw = ImageDraw.Draw(character_menu)
        small_font = ImageFont.truetype('fonts/comicbd.ttf', 10)
        large_font = ImageFont.truetype('fonts/comicbd.ttf', 14)
        if self.random_build is True:
            seed_text = 'Random Item'
            self.random_build = False  # Set this back to the default value
        elif int(self.current_build.attrib['id']) >= 26:  # Build 26 should be the first custom build
            seed_text = 'Build #' + self.current_build.attrib['id']
        else:
            seed_text = self.current_build.get('items')
        w, h = character_menu_draw.textsize('Selected start:', font=small_font)
        w2, h2 = character_menu_draw.textsize(seed_text, font=large_font)
        character_menu_draw.text((240 - w / 2, 31), 'Selected start:', color, font=small_font)
        character_menu_draw.text((240 - w2 / 2, 41), seed_text, color, font=large_font)
        character_menu.save(os.path.join(isaac_resources_directory, 'gfx/ui/main menu/charactermenu.png'))

        # We are finished, so launch Isaac
        launch_isaac()

    # Make a custom title menu graphic that represents which options were selected
    def draw_title_graphic(self):
        # Start by drawing the Instant Start logo on a clean Jud6s title menu graphic
        instant_start_logo = Image.open('images/main-menu/reset-logo.png')
        r, g, b, a = instant_start_logo.split()
        instant_start_logo = Image.merge('RGB', (r, g, b))
        mask = Image.merge('L', (a,))
        title_img = Image.open('images/main-menu/titlemenu-base.png')
        title_img.paste(instant_start_logo, (45, 178), mask)

        # Draw the Instant Start Mod text on it
        large_font = ImageFont.truetype('fonts/IsaacSans.ttf', 19)
        title_draw = ImageDraw.Draw(title_img)
        title_draw.text((10, 235), 'Instant Start', (134, 86, 86), font=large_font)
        w, h = title_draw.textsize('Mod v' + mod_version, font=large_font)
        title_draw.text((70 - w / 2, 250), 'Mod v' + mod_version, (134, 86, 86), font=large_font)

        # Draw the Jud6s text and version
        jud6s_text = 'Jud6s Mod ' + jud6s_version
        title_draw.text((345, 239), jud6s_text, (134, 86, 86), font=large_font)

        # Draw the text that shows the options that were selected
        if self.seeded_mode.get() == 1 or self.seeded2_mode.get() == 1 or self.LCO_mode.get() == 1 or self.mega_satan_mode.get() == 1:
            # Create the text string
            mode_text = '('
            if self.seeded_mode.get() == 1:
                mode_text += 'Seeded, '
            if self.seeded2_mode.get() == 1:
                mode_text += 'Seeded+, '
            if self.LCO_mode.get() == 1:
                mode_text += 'LCO, '
            if self.mega_satan_mode.get() == 1:
                mode_text += 'Mega Satan, '
            mode_text = mode_text[:-2]  # Remove the trailing ", "
            mode_text += ')'

            # Draw the text string
            small_font = ImageFont.truetype('fonts/IsaacSans.ttf', 15)
            w, h = title_draw.textsize(mode_text, font=small_font)
            title_draw.text((405 - w / 2, 255), mode_text, (134, 86, 86), font=small_font)

        # Save the result directly to the appropriate place in the Isaac resources folder
        title_img.save(os.path.join(isaac_resources_directory, 'gfx/ui/main menu/titlemenu.png'))

    def about_options(self):
        webbrowser.open('https://github.com/Zamiell/isaac-racing-mods/blob/master/docs/instant-start-mod.md')

    def go_back(self):
        get_window_x_y(self)
        self.window.destroy()
        ModSelectionWindow(self.parent)


###########################
# The Diversity Mod window
###########################

class DiversityWindow():
    def __init__(self, parent):
        # Class variables
        self.installed_diversity_seed = ''
        self.seed_was_randomly_selected = False
        self.random_button_was_clicked = False

        # Initialize a new GUI window
        self.parent = parent
        self.window = tkinter.Toplevel(self.parent)
        self.window.withdraw()  # Hide the GUI
        self.window.title('Diversity Mod v' + mod_version)  # Set the GUI title
        self.window.iconbitmap('images/icons/rainbow_poop.ico')  # Set the GUI icon
        self.window.resizable(False, False)
        self.window.protocol('WM_DELETE_WINDOW', lambda: uninstall_mod(self))  # Uninstall mod files when the window is closed

        # Start counting rows
        row = 0
        row2 = 0

        # Central GUI box
        dmbox = tkinter.LabelFrame(self.window, text='', padx=5, pady=5)
        dmbox.grid(row=row, column=0, columnspan=2, pady=20)
        row += 1

        # "Enter seed" label
        text = get_text('Enter a seed') + ':'
        enter_seed_label = tkinter.Label(dmbox, text=text, font='font 14')
        enter_seed_label.grid(row=row2, column=0, pady=10, padx=7, sticky=tkinter.W)

        # "New Random Seed" button
        new_random_seed_button = tkinter.Button(dmbox, font='font 12')
        new_random_seed_button.configure(command=self.new_random_seed)
        new_random_seed_button.icon = ImageTk.PhotoImage(get_item_icon('D100'))
        new_random_seed_button.configure(image=new_random_seed_button.icon)
        new_random_seed_button.grid(row=row2, column=1, padx=7, sticky=tkinter.E)
        row2 += 1

        # Seed entry box functions
        def select_all(event=None):
            self.seed_entry_box.select_range(0, 'end')

        def check_seed_installed(*args):
            # Set the background to indicate that current entry is installed
            if self.entry_box_contents.get() == self.installed_diversity_seed and self.installed_diversity_seed != '':
                self.seed_entry_box.configure(bg='#d8fbf8')
            else:
                self.seed_entry_box.configure(bg='#f4e6e6')

            # They typed something, so it is not a randomly selected seed
            if self.random_button_was_clicked is True:
                self.random_button_was_clicked = False
            else:
                self.seed_was_randomly_selected = False

            # Set everything to upper case
            self.entry_box_contents.set(self.entry_box_contents.get().upper())

        # Seed entry box
        self.entry_box_contents = tkinter.StringVar()
        self.entry_box_contents.trace('w', check_seed_installed)
        self.seed_entry_box = tkinter.Entry(dmbox, justify=tkinter.CENTER, font='font 32 bold', width=15, textvariable=self.entry_box_contents)
        self.seed_entry_box.configure(bg='#f4e6e6')
        self.seed_entry_box.bind('<Return>', lambda event: self.install_diversity_mod())
        self.seed_entry_box.bind('<Control-a>', select_all)
        self.seed_entry_box.grid(row=row2, padx=7, columnspan=2, pady=5)
        self.seed_entry_box.focus()  # Make the seed box have focus automatically
        row2 += 1

        # "Start Diversity Mod" button
        text = '   ' + get_text('Start Diversity Mod') + '   '
        start_diversity_mod_button = tkinter.Button(self.window, font='font 16', text=text, compound='left')
        start_diversity_mod_button.configure(command=self.install_diversity_mod)
        start_diversity_mod_button.icon = ImageTk.PhotoImage(Image.open('images/diversity/rainbow.png'))
        start_diversity_mod_button.configure(image=start_diversity_mod_button.icon)
        start_diversity_mod_button.grid(row=row, pady=7, columnspan=2)
        row += 1

        # Spacing
        spacing = tkinter.Message(self.window, text='', font='font 6')
        spacing.grid(row=row)
        row += 1

        # "About this mod" button
        text = ' ' + get_text('About this mod') + ' '
        about_options_button = tkinter.Button(self.window, text=text, compound='left')
        about_options_button.configure(font=('Helvetica', 13))
        about_options_button.configure(command=self.about_this_mod)
        about_options_button.icon = ImageTk.PhotoImage(get_item_icon('???'))
        about_options_button.configure(image=about_options_button.icon)
        about_options_button.grid(row=row, pady=5)
        row += 1

        # "Go Back" button
        text = ' ' + get_text('Go Back') + ' (Esc) '
        go_back_button = tkinter.Button(self.window, text=text, compound='left')
        go_back_button.configure(font=('Helvetica', 13))
        go_back_button.icon = ImageTk.PhotoImage(Image.open('images/icons/go-back.png'))
        go_back_button.configure(image=go_back_button.icon)
        go_back_button.configure(command=self.go_back)
        go_back_button.grid(row=row, column=0, pady=5)
        self.window.bind('<Escape>', lambda event: go_back_button.invoke())
        row += 1

        # Spacing
        spacing = tkinter.Message(self.window, text='', font='font 6')
        spacing.grid(row=row)
        row += 1

        # Instructions
        text = get_text('Isaac will open')
        m = tkinter.Message(self.window, justify=tkinter.CENTER, text=text, font='font 13', width=400)
        m.grid(row=row, column=0)
        row += 1
        text = get_text('Keep this program open')
        m = tkinter.Message(self.window, justify=tkinter.CENTER, text=text, font='font 13', width=400)
        m.grid(row=row, column=0)
        row += 1
        text = get_text('Isaac will return to normal') + '\n'
        m = tkinter.Message(self.window, justify=tkinter.CENTER, text=text, font='font 13', width=400)
        m.grid(row=row, column=0)
        row += 1

        # Spacing
        spacing = tkinter.Message(self.window, text='', font='font 6')
        spacing.grid(row=row)
        row += 1

        # Place the window at the X and Y coordinates from either the INI or the previous window
        self.window.deiconify()  # Show the GUI
        self.window.geometry('+%d+%d' % (window_x, window_y))

    def new_random_seed(self):
        self.random_button_was_clicked = True
        self.seed_was_randomly_selected = True
        random_seed = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
        self.entry_box_contents.set(random_seed)

    def install_diversity_mod(self):
        ####################
        # Utility functions
        ####################

        def remove_item_from_all_item_pools(item_name):
            item_id = get_item_id(item_name)
            for pool in itempools_info:
                for item in pool.findall('Item'):
                    if item.attrib['Id'] == item_id:  # Weirdly, it is capitalized this way in the vanilla XML
                        pool.remove(item)

        #############################
        # The main installation code
        #############################

        # Remove everything from the resources directory EXCEPT the "packed" directory and config.ini
        for file_name in os.listdir(isaac_resources_directory):
            if file_name != 'packed' and file_name != 'config.ini':
                delete_file_if_exists(os.path.join(isaac_resources_directory, file_name))

        # If there isn't a config.ini in the resources directory already, copy over the default Jud6s one
        if not os.path.isfile(os.path.join(isaac_resources_directory, 'config.ini')):
            copy_file('other-files/config.ini', os.path.join(isaac_resources_directory, 'config.ini'))

        # Copy over the "jud6s" directory, which represents the base files for the mod
        for file_name in os.listdir('jud6s'):
            copy_file(os.path.join('jud6s', file_name), os.path.join(isaac_resources_directory, file_name))

        # Delete the "versusscreen.anm2" file if the user wants to keep the boss cutscenes
        if remove_boss_cutscenes == False:
            delete_file_if_exists(os.path.join(isaac_resources_directory, 'gfx/ui/boss/versusscreen.anm2'))

        # Trim the whitespace on the seed string if there is any
        self.entry_box_contents.set("".join(self.entry_box_contents.get().split()))

        # If no seed was entered, generate a random one
        if self.entry_box_contents.get() == '':
            self.new_random_seed()

        # This is the seed we will install
        self.installed_diversity_seed = self.entry_box_contents.get()

        # Set the background on the seed entry box to indicate that current seed is installed
        self.seed_entry_box.configure(bg='#d8fbf8')

        # Set the RNG seed based on the diversity seed
        random.seed(binascii.crc32(bytes(self.installed_diversity_seed, 'UTF-8')))

        # "valid_items" is the list of all passive items in the game EXCLUDING the 25 items listed in the README file
        valid_items = [
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
            11, 12, 13, 14, 17, 18, 19, 20, 21, 27,
            28, 32, 46, 48, 50, 51, 52, 53, 54, 55,
            57, 60, 62, 63, 64, 67, 68, 69, 70, 71,
            72, 73, 74, 75, 76, 79, 80, 81, 82, 87,
            88, 89, 90, 91, 94, 95, 96, 98, 99, 100,
            101, 103, 104, 106, 108, 109, 110, 112, 113, 114,
            115, 116, 117, 118, 120, 121, 122, 125, 128, 129,
            131, 132, 134, 138, 139, 140, 141, 142, 143, 144,
            148, 149, 150, 151, 152, 153, 154, 155, 156, 157,
            159, 161, 162, 163, 165, 167, 168, 169, 170, 172,
            173, 174, 178, 179, 180, 182, 183, 184, 185, 187,
            188, 189, 190, 191, 193, 195, 196, 197, 198, 199,
            200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
            210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
            220, 221, 222, 223, 224, 225, 227, 228, 229, 230,
            231, 232, 233, 234, 236, 237, 240, 241, 242, 243,
            244, 245, 246, 247, 248, 249, 250, 251, 252, 254,
            255, 256, 257, 259, 260, 261, 262, 264, 265, 266,
            267, 268, 269, 270, 271, 272, 273, 274, 275, 276,
            277, 278, 279, 280, 281, 299, 300, 301, 302, 303,
            304, 305, 306, 307, 308, 309, 310, 311, 312, 313,
            314, 315, 316, 317, 318, 319, 320, 321, 322, 327,
            328, 329, 330, 331, 332, 333, 335, 336, 337, 340,
            341, 342, 343, 345, 350, 353, 354, 356, 358, 359,
            360, 361, 362, 363, 364, 365, 366, 367, 368, 369,
            370, 371, 372, 373, 374, 375, 376, 377, 378, 379,
            380, 381, 384, 385, 387, 388, 389, 390, 391, 392,
            393, 394, 395, 397, 398, 399, 400, 401, 402, 403,
            404, 405, 407, 408, 409, 410, 411, 412, 413, 414,
            415, 416, 417, 418, 420, 423, 424, 425, 426, 429,
            430, 431, 432, 433, 435, 436, 438, 440
        ]

        # Put the items in a random order
        shuffed_items = list(valid_items)
        random.shuffle(shuffed_items)
        items = []
        items.append(get_item_dict(shuffed_items[0])['name'])
        items.append(get_item_dict(shuffed_items[1])['name'])
        items.append(get_item_dict(shuffed_items[2])['name'])

        # Load the fonts for drawing onto images
        small_font = ImageFont.truetype('fonts/comicbd.ttf', 10)
        large_font = ImageFont.truetype('fonts/comicbd.ttf', 16)

        # Draw the version number on the title menu graphic
        try:
            title_img = Image.open('images/diversity/titlemenu.png')
            title_draw = ImageDraw.Draw(title_img)
            title_draw.text((425, 240), 'v' + mod_version, (54, 47, 45), font=large_font)
            title_img.save(os.path.join(isaac_resources_directory, 'gfx/ui/main menu/titlemenu.png'))
        except Exception as e:
            error('Failed to modify the title menu graphic:', e)

        # Copy over the splashes.png so that the title menu graphic looks correct
        copy_file('images/diversity/splashes.png', os.path.join(isaac_resources_directory, 'gfx/ui/main menu/splashes.png'))

        # Draw the seed on the character selection screen graphic
        try:
            character_img = Image.open('images/diversity/charactermenu.png')
            character_draw = ImageDraw.Draw(character_img)
            character_screen_tile_text = 'Diversity Mod seed:'
            w, h = character_draw.textsize(character_screen_tile_text, font=small_font)
            w2, h2 = character_draw.textsize(self.installed_diversity_seed, font=large_font)
            w3, h3 = character_draw.textsize('(random seed)', font=small_font)
            character_draw.text((240 - w / 2, 25), character_screen_tile_text, (54, 47, 45), font=small_font)
            character_draw.text((240 - w2 / 2, 35), self.installed_diversity_seed, (54, 47, 45), font=large_font)
            if self.seed_was_randomly_selected is True:
                character_draw.text((240 - w3 / 2, 190), '(random seed)', (54, 47, 45), font=small_font)
            character_img.save(os.path.join(isaac_resources_directory, 'gfx/ui/main menu/charactermenu.png'))
        except Exception as e:
            error('Failed to modify the character selection screen graphic:', e)

        # Parse players.xml, items.xml, and itempools.xml
        players_xml = xml.etree.ElementTree.parse('jud6s/players.xml')
        players_info = players_xml.getroot()
        items_xml = xml.etree.ElementTree.parse('xml/items.vanilla.xml')
        items_info = items_xml.getroot()
        itempools_xml = xml.etree.ElementTree.parse('xml/itempools.vanilla.xml')
        itempools_info = itempools_xml.getroot()

        # Remove Karma from the game (done in the Jud6s mod)
        for item in items_info:
            if item.tag == 'trinket' and item.attrib['name'] == 'Karma':
                item.attrib['achievement'] = '0'

        # Remove the "special" status from all items (done in the Diversity mod)
        for item in items_info:
            if 'special' in item.attrib.keys():
                del item.attrib['special']

        # Add the 3 random items to every character
        characters_to_skip = ['Lazarus II', 'Black Judas']
        for character in players_info:
            if character.attrib['name'] in characters_to_skip:
                continue
            if 'items' not in character.attrib.keys():  # Necessary for Eden
                character.attrib['items'] = ''
            if character.attrib['items'] != '':
                character.attrib['items'] += ','
            character.attrib['items'] += get_item_id(items[0]) + ',' + get_item_id(items[1]) + ',' + get_item_id(items[2])

        # Define the item bans
        removed_items = []
        if 'Mom\'s Knife' not in items:
            removed_items.append('Mom\'s Knife')
        if 'Epic Fetus' not in items:
            removed_items.append('Epic Fetus')
        if 'Tech X' not in items:
            removed_items.append('Tech X')
        if 'D4' not in items:
            removed_items.append('D4')
        if 'D100' not in items:
            removed_items.append('D100')

        # Add some extra item bans if necessary
        if 'Soy Milk' in items or 'Libra' in items:
            if 'Soy Milk' not in items and 'Soy Milk' not in removed_items:
                removed_items.append('Soy Milk')
            if 'Libra' not in items and 'Libra' not in removed_items:
                removed_items.append('Libra')

        if 'Isaac\'s Heart' in items:
            if 'Blood Rights' not in items and 'Blood Rights' not in removed_items:
                removed_items.append('Blood Rights')

        if 'Ipecac' in items or 'Dr. Fetus' in items:
            if 'Ipecac' not in items and 'Ipecac' not in removed_items:
                removed_items.append('Ipecac')
            if 'Dr. Fetus' not in items and 'Dr. Fetus' not in removed_items:
                removed_items.append('Dr. Fetus')

        if 'Monstro\'s Lung' in items:
            if 'Ipecac' not in items and 'Ipecac' not in removed_items:
                removed_items.append('Ipecac')

        # Remove the banned items from all pools
        for item in removed_items:
            remove_item_from_all_item_pools(item)

        # Write the changes to the copied over XML files
        players_xml.write(os.path.join(isaac_resources_directory, 'players.xml'))
        itempools_xml.write(os.path.join(isaac_resources_directory, 'itempools.xml'))
        items_xml.write(os.path.join(isaac_resources_directory, 'items.xml'))

        # Draw the start room background and save it in place
        if not os.path.isdir(os.path.join(isaac_resources_directory, 'gfx/backdrop')):
            create_directory(os.path.join(isaac_resources_directory, 'gfx/backdrop'))
        start_room_background = draw_start_room_background(items, removed_items)
        start_room_background.save(os.path.join(isaac_resources_directory, 'gfx/backdrop/controls.png'))

        # We are finished, so launch Isaac
        launch_isaac()

    def about_this_mod(self):
        webbrowser.open('https://github.com/Zamiell/isaac-racing-mods/blob/master/docs/diversity-mod.md')

    def go_back(self):
        get_window_x_y(self)
        self.window.destroy()
        ModSelectionWindow(self.parent)


###########################
# The miscellaneous window
###########################

class MiscellaneousWindow():
    def __init__(self, parent):
        # Initialize a new GUI window
        self.parent = parent
        self.window = tkinter.Toplevel(self.parent)
        self.window.withdraw()  # Hide the GUI
        self.window.title(mod_pretty_name + ' v' + mod_version)  # Set the GUI title
        self.window.iconbitmap('images/icons/the_d6.ico')  # Set the GUI icon
        self.window.resizable(False, False)
        self.window.protocol('WM_DELETE_WINDOW', lambda: uninstall_mod(self))  # Uninstall mod files when the window is closed

        # Start counting rows
        row = 0

        # "Select an option" label
        text = get_text('Select an option') + ':'
        select_message = tkinter.Message(self.window, justify=tkinter.CENTER, text=text, font='font 20', width=400)
        select_message.grid(row=row, pady=5, padx=100)  # Widen the window
        row += 1

        # Spacing
        spacing = tkinter.Message(self.window, text='', font='font 6')
        spacing.grid(row=row)
        row += 1

        # "Remove boss cutscenes" checkbox
        self.remove_boss_cutscenes_mode = tkinter.IntVar()
        if remove_boss_cutscenes == True:
            self.remove_boss_cutscenes_mode.set(1)
        text = get_text('Remove boss cutscenes') + ' (1)'
        self.remove_boss_cutscenes_checkbox = tkinter.Checkbutton(self.window, text=text, font='font 14', variable=self.remove_boss_cutscenes_mode, command=self.remove_boss_cutscenes_checked)
        self.remove_boss_cutscenes_checkbox.grid(row=row)
        self.window.bind('1', lambda event: self.remove_boss_cutscenes_checkbox.invoke())
        row += 1

        # "Automatically attempt to close Isaac" checkbox
        self.close_isaac_mode = tkinter.IntVar()
        if automatically_close_isaac == True:
            self.close_isaac_mode.set(1)
        text = get_text('Automatically attempt to close Isaac') + ' (2)'
        self.close_isaac_checkbox = tkinter.Checkbutton(self.window, text=text, font='font 14', variable=self.close_isaac_mode, command=self.close_isaac_checkbox_checked)
        self.close_isaac_checkbox.grid(row=row)
        self.window.bind('2', lambda event: self.close_isaac_checkbox.invoke())
        row += 1

        # Spacing
        spacing = tkinter.Message(self.window, text='', font='font 6')
        spacing.grid(row=row)
        row += 1

        # "Open Isaac game directory" button
        text = ' ' + get_text('Open Isaac game directory') + ' (3) '
        open1_button = tkinter.Button(self.window, text=text, compound='left')
        open1_button.configure(font=('Helvetica', 13))
        open1_button.configure(command=self.open1)
        open1_button.grid(row=row, pady=5)
        self.window.bind('3', lambda event: self.open1())
        row += 1

        # "Open Isaac documents directory" button
        text = ' ' + get_text('Open Isaac documents directory') + ' (4) '
        open2_button = tkinter.Button(self.window, text=text, compound='left')
        open2_button.configure(font=('Helvetica', 13))
        open2_button.configure(command=self.open2)
        open2_button.grid(row=row, pady=5)
        self.window.bind('4', lambda event: self.open2())
        row += 1

        # "Uninstall all existing mods" button
        text = ' ' + get_text('Uninstall all existing mods') + ' (5) '
        uninstall_button = tkinter.Button(self.window, text=text, compound='left')
        uninstall_button.configure(font=('Helvetica', 13))
        uninstall_button.configure(command=self.uninstall_all)
        uninstall_button.grid(row=row, pady=5)
        self.window.bind('5', lambda event: self.uninstall_all())
        row += 1

        # "Visit the website" button
        text = ' ' + get_text('Visit the website for') + ' ' + mod_pretty_name + ' (6) '
        website_button = tkinter.Button(self.window, text=text, compound='left')
        website_button.configure(font=('Helvetica', 13))
        website_button.configure(command=self.visit_website)
        website_button.grid(row=row, pady=5)
        self.window.bind('6', lambda event: website_button.invoke())
        row += 1

        # "Go Back" button
        text = ' ' + get_text('Go Back') + ' (Esc) '
        go_back_button = tkinter.Button(self.window, text=text, compound='left')
        go_back_button.configure(font=('Helvetica', 13))
        go_back_button.icon = ImageTk.PhotoImage(Image.open('images/icons/go-back.png'))
        go_back_button.configure(image=go_back_button.icon)
        go_back_button.configure(command=self.go_back)
        go_back_button.grid(row=row, pady=25)
        self.window.bind('<Escape>', lambda event: go_back_button.invoke())
        row += 1

        # Spacing
        spacing = tkinter.Message(self.window, text='', font='font 6')
        spacing.grid(row=row)
        row += 1

        # Place the window at the X and Y coordinates from either the INI or the previous window
        self.window.deiconify()  # Show the GUI
        self.window.geometry('+%d+%d' % (window_x, window_y))

    def remove_boss_cutscenes_checked(self):
        # Global variables
        global mod_options
        global remove_boss_cutscenes

        # Check to see if the box was checked or unchecked
        if self.remove_boss_cutscenes_mode.get() == 0:
            remove_boss_cutscenes = False

            # Write the new path to the INI file
            mod_options.set('options', 'remove_boss_cutscenes', 'false')
            try:
                with open(os.path.join('..', 'options.ini'), 'w') as config_file:
                    mod_options.write(config_file)
            except Exception as e:
                error('Failed to write the new "remove_boss_cutscenes" value to the "options.ini" file:', e)

        elif self.remove_boss_cutscenes_mode.get() == 1:
            remove_boss_cutscenes = True

            # Write the new path to the INI file
            mod_options.set('options', 'remove_boss_cutscenes', 'true')
            try:
                with open(os.path.join('..', 'options.ini'), 'w') as config_file:
                    mod_options.write(config_file)
            except Exception as e:
                error('Failed to write the new "remove_boss_cutscenes" value to the "options.ini" file:', e)

        else:
            error('The "Automatically attempt to close Isaac" checkbox has an invalid value of ' + str(self.close_isaac_mode.get()) + '.', None)

    def close_isaac_checkbox_checked(self):
        # Global variables
        global mod_options
        global automatically_close_isaac

        # Check to see if the box was checked or unchecked
        if self.close_isaac_mode.get() == 0:
            automatically_close_isaac = False

            # Write the new path to the INI file
            mod_options.set('options', 'automatically_close_isaac', 'false')
            try:
                with open(os.path.join('..', 'options.ini'), 'w') as config_file:
                    mod_options.write(config_file)
            except Exception as e:
                error('Failed to write the new "automatically_close_isaac" value to the "options.ini" file:', e)

        elif self.close_isaac_mode.get() == 1:
            automatically_close_isaac = True

            # Write the new path to the INI file
            mod_options.set('options', 'automatically_close_isaac', 'true')
            try:
                with open(os.path.join('..', 'options.ini'), 'w') as config_file:
                    mod_options.write(config_file)
            except Exception as e:
                error('Failed to write the new "automatically_close_isaac" value to the "options.ini" file:', e)

        else:
            error('The "Automatically attempt to close Isaac" checkbox has an invalid value of ' + str(self.close_isaac_mode.get()) + '.', None)

    def open1(self):
        game_path = isaac_resources_directory.replace('/', '\\')
        game_path = game_path[:-10]  # Chop off the "\resources" part of the path
        subprocess.call(['explorer', game_path])

    def open2(self):
        documents_path = 'C:\\Users\\' + os.getenv('username') + '\\Documents\\My Games\\Binding of Isaac Afterbirth'
        if os.path.isdir(documents_path):
            subprocess.call(['explorer', documents_path])
        else:
            error('Failed to automatically find your documents directory. To find it, look for the "savedatapath.txt" file in your Isaac game directory.', None)

    def uninstall_all(self):
        purge_resources_directory(self)

    def visit_website(self):
        webbrowser.open('https://zamiell.github.io/isaac-racing-mods/')

    def go_back(self):
        get_window_x_y(self)
        self.window.destroy()
        ModSelectionWindow(self.parent)


###########################
# General window functions
###########################

def get_window_x_y(self):
    # Global variables
    global window_x
    global window_y

    # Get the X and Y location of the current window
    match = re.search(r'\d+x\d+\+(-*\d+)\+(-*\d+)', self.window.geometry())
    if match:
        window_x = int(match.group(1))
        window_y = int(match.group(2))
    else:
        error('Failed to parse the current window\'s X and Y coordinates.', None)


def get_window2_x_y(self):
    # Global variables
    global window_x
    global window_y

    # Get the X and Y location of the current window
    match = re.search(r'\d+x\d+\+(-*\d+)\+(-*\d+)', self.window2.geometry())
    if match:
        window_x = int(match.group(1))
        window_y = int(match.group(2))
    else:
        error('Failed to parse the current window\'s X and Y coordinates.', None)


#####################################
# "get" general functions
#####################################

# get_image - Load the image from the provided path in a format suitable for Tk
def get_image(path):
    raw_image_library = {}
    image = raw_image_library.get(path)
    if image is None:
        canonicalized_path = path.replace('/', os.sep).replace('\\', os.sep)
        image = Image.open(canonicalized_path)

        # Crop the image to 32x32 (A Dollar, A Quarter, and Money = Power have hidden text outside of the 32x32)
        image = image.crop((0, 0, 32, 32))

        # Add the image to the library so that we don't have to load it again in the future
        raw_image_library[path] = image
    return image


# get_item_dict - Return an item dictionary based on the provided item id or name
def get_item_dict(id):
    id = str(id)
    if id.isdigit():
        for child in items_info:
            if child.attrib['id'] == id and child.tag != 'trinket':
                return child.attrib
    else:
        for child in items_info:
            if child.attrib['name'].lower() == id.lower() and child.tag != 'trinket':
                return child.attrib
    return None

# get_item_id - Given an item name or id, return its id
def get_item_id(id):
    id = str(id)
    if id.isdigit():  # Applies to Odd Mushroom (thin) and Odd Mushroom (thick)
        for child in items_info:
            if child.attrib['id'] == id and child.tag != 'trinket':
                return child.attrib['id']
    else:
        for child in items_info:
            if child.attrib['name'].lower() == id.lower() and child.tag != 'trinket':
                return child.attrib['id']
    error('Failed to find a corresponding item ID for "' + id + '".', None)


# get_trinket_id - Given an item name or id, return its id
def get_trinket_id(id):
    id = str(id)
    if id.isdigit():
        for child in items_info:
            if child.attrib['id'] == id and child.tag == 'trinket':
                return child.attrib['id']
    else:
        for child in items_info:
            if child.attrib['name'].lower() == id.lower() and child.tag == 'trinket':
                return child.attrib['id']
    error('Failed to find a corresponding trinket ID for "' + id + '".', None)


# get_item_icon - Given the name or id of an item, return its icon image
def get_item_icon(id):
    dict = get_item_dict(id)
    if dict:
        icon_file = dict['gfx']
        return get_image(os.path.join('images/collectibles', icon_file))
    else:
        return get_image('images/collectibles/question-mark.png')


# get_trinket_icon - Given the name or id of a trinket, return its icon image
def get_trinket_icon(id):
    id = str(id)
    if id.isdigit():
        for child in items_info:
            if child.attrib['id'] == id and child.tag == 'trinket':
                return get_image(os.path.join('images/trinkets', child.attrib['gfx']))
    else:
        for child in items_info:
            if child.attrib['name'].lower() == id.lower() and child.tag == 'trinket':
                return get_image(os.path.join('images/trinkets', child.attrib['gfx']))
    return get_image('images/trinkets/question-mark.png')


####################
# Cleanup functions
####################

def uninstall_mod(self):
    # Global variables
    global mod_options

    # Remove everything from the resources directory EXCEPT the "packed" directory and config.ini
    for file_name in os.listdir(isaac_resources_directory):
        if file_name != 'packed' and file_name != 'config.ini':
            delete_file_if_exists(os.path.join(isaac_resources_directory, file_name))

    # Delete config.ini also if it wasn't there before
    if config_ini_exists == False:
        delete_file_if_exists(os.path.join(isaac_resources_directory, 'config.ini'))

    # If we backed anything up initially when launching the program, copy it back
    if backed_up_resources_directory is True:
        # Check to see if the temporary directory still exists
        if not os.path.isdir(temp_directory):
            error('The temporary directory that ' + mod_pretty_name + ' backed up files from the resources directory to cannot be found.', None)

        # Copy the files back
        for file_name in os.listdir(temp_directory):
            copy_file(os.path.join(temp_directory, file_name), os.path.join(isaac_resources_directory, file_name))

        # Remove the temporary directory we created
        delete_file_if_exists(temp_directory)

    # Write the new path to the INI file
    get_window_x_y(self)
    mod_options.set('options', 'window_x', str(window_x))
    mod_options.set('options', 'window_y', str(window_y))
    try:
        with open(os.path.join('..', 'options.ini'), 'w') as config_file:
            mod_options.write(config_file)
    except Exception as e:
        error('Failed to write the window location to the "options.ini" file:', e)

    # Exit the program
    sys.exit()


def purge_resources_directory(self):
    if backed_up_resources_directory == True:
        # During the start of the program, everything in the resources directory got copied to a temporary directory (except for the "packed" directory)
        # Remove everything from the temporary directory EXCEPT config.ini
        for file_name in os.listdir(temp_directory):
            if file_name != 'config.ini':
                delete_file_if_exists(os.path.join(temp_directory, file_name))

        warning('All mods have been uninstalled. Your "resources" folder is clean.', None)

        # Uninstall the mod and exit
        uninstall_mod(self)
    else:
        warning('It appears that your resources folder is already clean; there were no custom mods installed before you opened ' + mod_pretty_name + '.', None)


###################################
# Language text retrieval function
###################################

def get_text(text):
    # Get the translated text
    translated_text = languages.text[text][language]
    if translated_text == '':
        # There is no translation for this text snippet, so just display the English text
        return text
    else:
        return translated_text


################
# Main function
################

def main():
    # Global variables
    global mod_options
    global mod_version
    global isaac_resources_directory
    global window_x
    global window_y
    global remove_boss_cutscenes
    global automatically_close_isaac
    global language
    global jud6s_version
    global config_ini_exists
    global backed_up_resources_directory
    global temp_directory
    global items_info

    # TkInter callbacks run in different threads, so if we want to handle generic exceptions caused in a TkInter callback, we must define a specific custom exception handler for that
    tkinter.Tk.report_callback_exception = callback_error

    # Initialize the root GUI
    root = tkinter.Tk()
    root.withdraw()  # Hide the GUI

    # Validate that "options.ini" file exists and contains the values we need
    if not os.path.isfile(os.path.join('..', 'options.ini')):
        error('The "options.ini" file was not found in the ' + mod_pretty_name + ' directory. Please redownload this program.', None)
    mod_options = configparser.ConfigParser()
    mod_options.read(os.path.join('..', 'options.ini'))
    if not mod_options.has_section('options'):
        error('The "options.ini" file does not have an "[options]" section. Please redownload this program.', None)
    if 'mod_version' not in mod_options['options']:
        error('The "options.ini" file does not contain the version number of the mod. Try adding "mod_version = 3.0.0" or redownloading the program.', None)
    if 'isaac_resources_directory' not in mod_options['options']:
        error('The "options.ini" does not contain an entry for the location of your Isaac resources directory. Try adding "isaac_resources_directory = C:\Program Files (x86)\Steam\SteamApps\common\The Binding of Isaac Rebirth\resources" or redownloading the program.', None)
    if 'window_x' not in mod_options['options']:
        error('The "options.ini" file does not contain an entry for the X position of your window. Try adding "window_x = 50" or redownloading the program.', None)
    if 'window_y' not in mod_options['options']:
        error('The "options.ini" file does not contain an entry for the Y position of your window. Try adding "window_y = 50" or redownloading the program.', None)
    if 'remove_boss_cutscenes' not in mod_options['options']:
        error('The "options.ini" file does not contain a "remove_boss_cutscenes" entry. Try adding "remove_boss_cutscenes = true" or redownloading the program.', None)
    if 'automatically_close_isaac' not in mod_options['options']:
        error('The "options.ini" file does not contain an "automatically_close_isaac" entry. Try adding "automatically_close_isaac = true" or redownloading the program.', None)
    if 'language' not in mod_options['options']:
        error('The "options.ini" file does not contain an "language" entry. Try adding "language = autodetect" or redownloading the program.', None)

    # Get variables from the "options.ini" file
    mod_version = mod_options['options']['mod_version']
    root.title(mod_pretty_name + ' v' + mod_version)  # Set the GUI title again now that we know the version
    isaac_resources_directory = mod_options['options']['isaac_resources_directory']
    window_x = int(mod_options['options']['window_x'])
    window_y = int(mod_options['options']['window_y'])
    remove_boss_cutscenes = mod_options['options']['remove_boss_cutscenes']
    if remove_boss_cutscenes == 'true':
        remove_boss_cutscenes = True
    elif remove_boss_cutscenes == 'false':
        remove_boss_cutscenes = False
    else:
        error('The "options.ini" value for "remove_boss_cutscenes" is not set to true or false.', None)
    automatically_close_isaac = mod_options['options']['automatically_close_isaac']
    if automatically_close_isaac == 'true':
        automatically_close_isaac = True
    elif automatically_close_isaac == 'false':
        automatically_close_isaac = False
    else:
        error('The "options.ini" value for "automatically_close_isaac" is not set to true or false.', None)
    language = mod_options['options']['language']
    if (language != 'autodetect' and
        language != 'en' and
        language != 'fr' and
        language != 'es' and
        language != 'ru'):

        error('The "options.ini" value for "language" is not set to a valid language.', None)
    if language == 'autodetect':
        # Find the user's locale, from: https://stackoverflow.com/questions/3425294/how-to-detect-the-os-default-language-in-python
        if platform.system() == 'Windows':
            lang_identifier = locale.windows_locale[ctypes.windll.kernel32.GetUserDefaultUILanguage()]
        else:
            lang_identifier = locale.getdefaultlocale()[0]

        # lang_identifier will now be "en-US" or similar
        # http://stackoverflow.com/questions/3191664/list-of-all-locales-and-their-short-codes
        if lang_identifier[:2] == 'fr':
            language = 'fr'
        elif lang_identifier[:2] == 'es':
            language = 'es'
        elif lang_identifier[:2] == 'ru':
            language = 'ru'
        else:
            # Default to English
            language = 'en'

    # Get the version number of the Jud6s mod (which is different than the version number of the racing mod pack)
    try:
        with open('jud6s/jud6s_version.txt', 'r') as file:
            jud6s_version = file.read()
    except Exception as e:
        error('Failed to get the version number of the Jud6s mod from the "jud6s_version.txt" file:', e)

    # Check to see if the resources directory exists
    if not os.path.isdir(isaac_resources_directory):
        text = ''
        if language == 'fr':
            text += 'L\''
        text += mod_pretty_name + ' ' + get_text('was unable to find your Isaac resources directory.') + '\n'
        text += get_text('Navigate to the program "isaac-ng.exe" in your Steam directory.')
        unable_to_find_message = tkinter.Message(root, justify=tkinter.CENTER, font='font 10', text=text, width=600)
        unable_to_find_message.grid(row=0, pady=10)

        text = get_text('Navigate to "isaac-ng.exe"')
        navigate_button = tkinter.Button(root, font='font 12', text=text, command=set_custom_path)
        navigate_button.grid(row=2, pady=10)

        text = get_text('Example location') + ':\n'
        text += 'C:\Program Files (x86)\Steam\steamapps\common\The Binding of Isaac Rebirth\isaac-ng.exe'
        example_message = tkinter.Message(root, justify=tkinter.LEFT, font='font 10', text=text, width=800)
        example_message.grid(row=3, padx=15, pady=10)

        # Show the GUI
        root.deiconify()
        tkinter.mainloop()

    # Exit if the user exited out of the dialog without selecting a directory
    if not os.path.isdir(isaac_resources_directory):
        sys.exit(1)

    # Check to see if we are inside a subdirectory of the resources folder
    if os.path.normpath(isaac_resources_directory).lower() in os.path.normpath(os.getcwd()).lower():
        error(mod_pretty_name + ' is located in a subdirectory of the resources directory. Move it elsewhere before running it.', None)

    # Check to see if the user already has a config.ini in their resources folder
    config_ini_exists = False
    if os.path.isfile(os.path.join(isaac_resources_directory, 'config.ini')):
        config_ini_exists = True

    # Check to see if the resources directory has anything in it that we need to back up
    backed_up_resources_directory = False
    for file_name in os.listdir(isaac_resources_directory):
        if file_name != 'packed' and file_name != 'config.ini':
            backed_up_resources_directory = True
            break

    # If necessary, move everything in the resources directory to a temporarily directory until the mod is closed
    if backed_up_resources_directory:
        # Create a temporarily directory
        epoch_time = str(int(time.time()))  # Get the current epoch timestamp
        temp_directory = os.path.join(isaac_resources_directory, '..', 'resources_backup' + epoch_time)
        delete_file_if_exists(temp_directory)
        os.makedirs(temp_directory)

        # Copy all files EXCEPT for the "packed" directory and config.ini
        for file_name in os.listdir(isaac_resources_directory):
            if file_name != 'packed' and file_name != 'config.ini':
                copy_file(os.path.join(isaac_resources_directory, file_name), os.path.join(temp_directory, file_name))

    # Parse items.xml now so that we can display some images; it will be parsed again before installation
    try:
        items_xml = xml.etree.ElementTree.parse('xml/items.vanilla.xml')
        items_info = items_xml.getroot()
    except Exception as e:
        error('Failed to parse the "xml/items.vanilla.xml" file:', e)

    # Show the mod selection GUI
    ModSelectionWindow(root)
    root.mainloop()


######################
# Program entry point
######################

if __name__ == '__main__':
    # Initialize logging
    log_file = os.path.join('..', mod_name + '-error-log.txt')
    logging.basicConfig(
        filename=log_file,
        format='%(asctime)s - %(message)s',
        datefmt='%m/%d/%Y %H:%M:%S'
    )

    # Run the program
    try:
        main()
    except Exception as e:
        # Show the error to the user and exit
        error('Generic error:', e)
