Example #1
0
 def __init__(self, clean_iso_path):
   self.arcs_by_path = {}
   self.rels_by_path = {}
   self.symbol_maps_by_path = {}
   self.used_actor_ids = list(range(0x1F6))
   
   self.read_text_file_lists()
   
   if not os.path.isfile(clean_iso_path):
     raise InvalidCleanISOError("Clean ISO does not exist: %s" % clean_iso_path)
   
   self.gcm = GCM(clean_iso_path)
   self.gcm.read_entire_disc()
   
   dol_data = self.gcm.read_file_data("sys/main.dol")
   self.dol = DOL()
   self.dol.read(dol_data)
Example #2
0
    def __init__(self,
                 seed,
                 clean_iso_path,
                 randomized_output_folder,
                 options,
                 permalink=None,
                 cmd_line_args=OrderedDict()):
        self.randomized_output_folder = randomized_output_folder
        self.options = options
        self.seed = seed
        self.permalink = permalink

        self.dry_run = ("-dry" in cmd_line_args)
        self.disassemble = ("-disassemble" in cmd_line_args)
        self.export_disc_to_folder = ("-exportfolder" in cmd_line_args)
        self.no_logs = ("-nologs" in cmd_line_args)
        self.bulk_test = ("-bulk" in cmd_line_args)
        if self.bulk_test:
            self.dry_run = True
            self.no_logs = True
        self.print_used_flags = ("-printflags" in cmd_line_args)

        self.test_room_args = None
        if "-test" in cmd_line_args:
            args = cmd_line_args["-test"]
            if args is not None:
                stage, room, spawn = args.split(",")
                self.test_room_args = {
                    "stage": stage,
                    "room": int(room),
                    "spawn": int(spawn)
                }

        seed_string = self.seed
        if not self.options.get("generate_spoiler_log"):
            seed_string += SEED_KEY

        self.integer_seed = self.convert_string_to_integer_md5(seed_string)
        self.rng = self.get_new_rng()

        self.arcs_by_path = {}
        self.jpcs_by_path = {}
        self.raw_files_by_path = {}

        self.read_text_file_lists()

        if not self.dry_run:
            if not os.path.isfile(clean_iso_path):
                raise InvalidCleanISOError("Clean WW ISO does not exist: %s" %
                                           clean_iso_path)

            self.verify_supported_version(clean_iso_path)

            self.gcm = GCM(clean_iso_path)
            self.gcm.read_entire_disc()

            try:
                self.chart_list = self.get_arc(
                    "files/res/Msg/fmapres.arc").get_file("cmapdat.bin")
            except InvalidOffsetError:
                # An invalid offset error when reading fmapres.arc seems to happen when the user has a corrupted clean ISO.
                # The reason for this is unknown, but when this happens check the ISO's MD5 and if it's wrong say so in an error message.
                self.verify_correct_clean_iso_md5(clean_iso_path)

                # But if the ISO's MD5 is correct just raise the normal offset error.
                raise

            self.bmg = self.get_arc("files/res/Msg/bmgres.arc").get_file(
                "zel_00.bmg")

            if self.disassemble:
                self.disassemble_all_code()
            if self.print_used_flags:
                stage_searcher.print_all_used_item_pickup_flags(self)
                stage_searcher.print_all_used_chest_open_flags(self)
                stage_searcher.print_all_event_flags_used_by_stb_cutscenes(
                    self)

        # Starting items. This list is read by the Logic when initializing your currently owned items list.
        self.starting_items = [
            "Wind Waker",
            "Wind's Requiem",
            "Ballad of Gales",
            "Song of Passing",
            "Hero's Shield",
            "Boat's Sail",
        ]
        self.starting_items += self.options.get("starting_gear", [])

        if self.options.get("sword_mode") == "Start with Sword":
            self.starting_items.append("Progressive Sword")
        # Add starting Triforce Shards.
        num_starting_triforce_shards = int(
            self.options.get("num_starting_triforce_shards", 0))
        for i in range(num_starting_triforce_shards):
            self.starting_items.append("Triforce Shard %d" % (i + 1))

        # Default entrances connections to be used if the entrance randomizer is not on.
        self.entrance_connections = OrderedDict([
            ("Dungeon Entrance On Dragon Roost Island", "Dragon Roost Cavern"),
            ("Dungeon Entrance In Forest Haven Sector", "Forbidden Woods"),
            ("Dungeon Entrance In Tower of the Gods Sector",
             "Tower of the Gods"),
            ("Dungeon Entrance On Headstone Island", "Earth Temple"),
            ("Dungeon Entrance On Gale Isle", "Wind Temple"),
            ("Secret Cave Entrance on Outset Island", "Savage Labyrinth"),
            ("Secret Cave Entrance on Dragon Roost Island",
             "Dragon Roost Island Secret Cave"),
            ("Secret Cave Entrance on Fire Mountain",
             "Fire Mountain Secret Cave"),
            ("Secret Cave Entrance on Ice Ring Isle",
             "Ice Ring Isle Secret Cave"),
            ("Secret Cave Entrance on Private Oasis", "Cabana Labyrinth"),
            ("Secret Cave Entrance on Needle Rock Isle",
             "Needle Rock Isle Secret Cave"),
            ("Secret Cave Entrance on Angular Isles",
             "Angular Isles Secret Cave"),
            ("Secret Cave Entrance on Boating Course",
             "Boating Course Secret Cave"),
            ("Secret Cave Entrance on Stone Watcher Island",
             "Stone Watcher Island Secret Cave"),
            ("Secret Cave Entrance on Overlook Island",
             "Overlook Island Secret Cave"),
            ("Secret Cave Entrance on Bird's Peak Rock",
             "Bird's Peak Rock Secret Cave"),
            ("Secret Cave Entrance on Pawprint Isle",
             "Pawprint Isle Chuchu Cave"),
            ("Secret Cave Entrance on Pawprint Isle Side Isle",
             "Pawprint Isle Wizzrobe Cave"),
            ("Secret Cave Entrance on Diamond Steppe Island",
             "Diamond Steppe Island Warp Maze Cave"),
            ("Secret Cave Entrance on Bomb Island", "Bomb Island Secret Cave"),
            ("Secret Cave Entrance on Rock Spire Isle",
             "Rock Spire Isle Secret Cave"),
            ("Secret Cave Entrance on Shark Island",
             "Shark Island Secret Cave"),
            ("Secret Cave Entrance on Cliff Plateau Isles",
             "Cliff Plateau Isles Secret Cave"),
            ("Secret Cave Entrance on Horseshoe Island",
             "Horseshoe Island Secret Cave"),
            ("Secret Cave Entrance on Star Island", "Star Island Secret Cave"),
        ])
        self.dungeon_and_cave_island_locations = OrderedDict([
            ("Dragon Roost Cavern", "Dragon Roost Island"),
            ("Forbidden Woods", "Forest Haven"),
            ("Tower of the Gods", "Tower of the Gods"),
            ("Earth Temple", "Headstone Island"),
            ("Wind Temple", "Gale Isle"),
            ("Secret Cave Entrance on Outset Island", "Outset Island"),
            ("Secret Cave Entrance on Dragon Roost Island",
             "Dragon Roost Island"),
            ("Secret Cave Entrance on Fire Mountain", "Fire Mountain"),
            ("Secret Cave Entrance on Ice Ring Isle", "Ice Ring Isle"),
            ("Secret Cave Entrance on Private Oasis", "Private Oasis"),
            ("Secret Cave Entrance on Needle Rock Isle", "Needle Rock Isle"),
            ("Secret Cave Entrance on Angular Isles", "Angular Isles"),
            ("Secret Cave Entrance on Boating Course", "Boating Course"),
            ("Secret Cave Entrance on Stone Watcher Island",
             "Stone Watcher Island"),
            ("Secret Cave Entrance on Overlook Island", "Overlook Island"),
            ("Secret Cave Entrance on Bird's Peak Rock", "Bird's Peak Rock"),
            ("Secret Cave Entrance on Pawprint Isle", "Pawprint Isle"),
            ("Secret Cave Entrance on Pawprint Isle Side Isle",
             "Pawprint Isle"),
            ("Secret Cave Entrance on Diamond Steppe Island",
             "Diamond Steppe Island"),
            ("Secret Cave Entrance on Bomb Island", "Bomb Island"),
            ("Secret Cave Entrance on Rock Spire Isle", "Rock Spire Isle"),
            ("Secret Cave Entrance on Shark Island", "Shark Island"),
            ("Secret Cave Entrance on Cliff Plateau Isles",
             "Cliff Plateau Isles"),
            ("Secret Cave Entrance on Horseshoe Island", "Horseshoe Island"),
            ("Secret Cave Entrance on Star Island", "Star Island"),
        ])

        # Default starting island (Outset) if the starting island randomizer is not on.
        self.starting_island_index = 44

        # Default charts for each island.
        self.island_number_to_chart_name = OrderedDict([
            (1, "Treasure Chart 25"),
            (2, "Treasure Chart 7"),
            (3, "Treasure Chart 24"),
            (4, "Triforce Chart 2"),
            (5, "Treasure Chart 11"),
            (6, "Triforce Chart 7"),
            (7, "Treasure Chart 13"),
            (8, "Treasure Chart 41"),
            (9, "Treasure Chart 29"),
            (10, "Treasure Chart 22"),
            (11, "Treasure Chart 18"),
            (12, "Treasure Chart 30"),
            (13, "Treasure Chart 39"),
            (14, "Treasure Chart 19"),
            (15, "Treasure Chart 8"),
            (16, "Treasure Chart 2"),
            (17, "Treasure Chart 10"),
            (18, "Treasure Chart 26"),
            (19, "Treasure Chart 3"),
            (20, "Treasure Chart 37"),
            (21, "Treasure Chart 27"),
            (22, "Treasure Chart 38"),
            (23, "Triforce Chart 1"),
            (24, "Treasure Chart 21"),
            (25, "Treasure Chart 6"),
            (26, "Treasure Chart 14"),
            (27, "Treasure Chart 34"),
            (28, "Treasure Chart 5"),
            (29, "Treasure Chart 28"),
            (30, "Treasure Chart 35"),
            (31, "Triforce Chart 3"),
            (32, "Triforce Chart 6"),
            (33, "Treasure Chart 1"),
            (34, "Treasure Chart 20"),
            (35, "Treasure Chart 36"),
            (36, "Treasure Chart 23"),
            (37, "Treasure Chart 12"),
            (38, "Treasure Chart 16"),
            (39, "Treasure Chart 4"),
            (40, "Treasure Chart 17"),
            (41, "Treasure Chart 31"),
            (42, "Triforce Chart 5"),
            (43, "Treasure Chart 9"),
            (44, "Triforce Chart 4"),
            (45, "Treasure Chart 40"),
            (46, "Triforce Chart 8"),
            (47, "Treasure Chart 15"),
            (48, "Treasure Chart 32"),
            (49, "Treasure Chart 33"),
        ])

        # This list will hold the randomly selected dungeon boss locations that are required in race mode.
        # If race mode is not on, this list will remain empty.
        self.race_mode_required_locations = []
        # This list will hold the dungeon names of the race mode required locations.
        # If race mode is not on, this list will remain empty.
        self.race_mode_required_dungeons = []
        # This list will hold all item location names that should not have any items in them in race mode.
        # If race mode is not on, this list will remain empty.
        self.race_mode_banned_locations = []

        self.custom_model_name = "Link"

        self.logic = Logic(self)

        num_progress_locations = self.logic.get_num_progression_locations()
        num_progress_items = self.logic.get_num_progression_items()
        if num_progress_locations < num_progress_items:
            error_message = "Not enough progress locations to place all progress items.\n\n"
            error_message += "Total progress items: %d\n" % num_progress_items
            error_message += "Progress locations with current options: %d\n\n" % num_progress_locations
            error_message += "You need to check more of the progress location options in order to give the randomizer enough space to place all the items."
            raise TooFewProgressionLocationsError(error_message)

        # We need to determine if the user's selected options result in a dungeons-only-start.
        # Dungeons-only-start meaning that the only locations accessible at the start of the run are dungeon locations.
        # e.g. If the user selects Dungeons, Expensive Purchases, and Sunken Treasures, the dungeon locations are the only ones the player can check first.
        # We need to distinguish this situation because it can cause issues for the randomizer's item placement logic (specifically when placing keys in DRC).
        self.logic.temporarily_make_dungeon_entrance_macros_impossible()
        accessible_undone_locations = self.logic.get_accessible_remaining_locations(
            for_progression=True)
        if len(accessible_undone_locations) == 0:
            self.dungeons_only_start = True
        else:
            self.dungeons_only_start = False
        self.logic.update_entrance_connection_macros(
        )  # Reset the dungeon entrance macros.

        # Also determine if these options result in a dungeons-and-caves-only-start.
        # Dungeons-and-caves-only-start means the only locations accessible at the start of the run are dungeon or secret cave locations.
        # This situation can also cause issues for the item placement logic (specifically when placing the first item of the run).
        self.logic.temporarily_make_entrance_macros_impossible()
        accessible_undone_locations = self.logic.get_accessible_remaining_locations(
            for_progression=True)
        if len(accessible_undone_locations) == 0:
            self.dungeons_and_caves_only_start = True
        else:
            self.dungeons_and_caves_only_start = False
        self.logic.update_entrance_connection_macros(
        )  # Reset the entrance macros.
Example #3
0
 def __init__(self, seed, clean_iso_path, randomized_output_folder, options, permalink=None, cmd_line_args=[]):
   self.randomized_output_folder = randomized_output_folder
   self.options = options
   self.seed = seed
   self.permalink = permalink
   
   self.dry_run = ("-dry" in cmd_line_args)
   self.disassemble = ("-disassemble" in cmd_line_args)
   self.export_disc_to_folder = ("-exportfolder" in cmd_line_args)
   self.no_logs = ("-nologs" in cmd_line_args)
   self.bulk_test = ("-bulk" in cmd_line_args)
   if self.bulk_test:
     self.dry_run = True
     self.no_logs = True
   
   self.integer_seed = self.convert_string_to_integer_md5(self.seed)
   self.rng = self.get_new_rng()
   
   self.arcs_by_path = {}
   self.jpcs_by_path = {}
   self.raw_files_by_path = {}
   
   if not self.dry_run:
     self.verify_supported_version(clean_iso_path)
     
     self.gcm = GCM(clean_iso_path)
     self.gcm.read_entire_disc()
     
     self.chart_list = self.get_arc("files/res/Msg/fmapres.arc").get_file("cmapdat.bin")
     self.bmg = self.get_arc("files/res/Msg/bmgres.arc").get_file("zel_00.bmg")
     
     if self.disassemble:
       self.disassemble_all_code()
   
   self.read_text_file_lists()
   
   # Starting items. This list is read by the Logic when initializing your currently owned items list.
   self.starting_items = [
     "Wind Waker",
     "Wind's Requiem",
     "Ballad of Gales",
     "Song of Passing",
     "Hero's Shield",
     "Boat's Sail",
   ]
   if self.options.get("sword_mode") == "Start with Sword":
     self.starting_items.append("Progressive Sword")
   # Add starting Triforce Shards.
   num_starting_triforce_shards = int(self.options.get("num_starting_triforce_shards", 0))
   for i in range(num_starting_triforce_shards):
     self.starting_items.append("Triforce Shard %d" % (i+1))
   
   # Default dungeon entrances to be used if dungeon entrance randomizer is not on.
   self.dungeon_entrances = OrderedDict([
     ("Dungeon Entrance On Dragon Roost Island", "Dragon Roost Cavern"),
     ("Dungeon Entrance In Forest Haven Sector", "Forbidden Woods"),
     ("Dungeon Entrance In Tower of the Gods Sector", "Tower of the Gods"),
     ("Dungeon Entrance On Headstone Island", "Earth Temple"),
     ("Dungeon Entrance On Gale Isle", "Wind Temple"),
   ])
   self.dungeon_island_locations = OrderedDict([
     ("Dragon Roost Cavern", "Dragon Roost Island"),
     ("Forbidden Woods", "Forest Haven"),
     ("Tower of the Gods", "Tower of the Gods"),
     ("Earth Temple", "Headstone Island"),
     ("Wind Temple", "Gale Isle"),
   ])
   
   # Default starting island (Outset) if the starting island randomizer is not on.
   self.starting_island_index = 44
   
   # Default charts for each island.
   self.island_number_to_chart_name = OrderedDict([
     (1, "Treasure Chart 25"),
     (2, "Treasure Chart 7"),
     (3, "Treasure Chart 24"),
     (4, "Triforce Chart 2"),
     (5, "Treasure Chart 11"),
     (6, "Triforce Chart 7"),
     (7, "Treasure Chart 13"),
     (8, "Treasure Chart 41"),
     (9, "Treasure Chart 29"),
     (10, "Treasure Chart 22"),
     (11, "Treasure Chart 18"),
     (12, "Treasure Chart 30"),
     (13, "Treasure Chart 39"),
     (14, "Treasure Chart 19"),
     (15, "Treasure Chart 8"),
     (16, "Treasure Chart 2"),
     (17, "Treasure Chart 10"),
     (18, "Treasure Chart 26"),
     (19, "Treasure Chart 3"),
     (20, "Treasure Chart 37"),
     (21, "Treasure Chart 27"),
     (22, "Treasure Chart 38"),
     (23, "Triforce Chart 1"),
     (24, "Treasure Chart 21"),
     (25, "Treasure Chart 6"),
     (26, "Treasure Chart 14"),
     (27, "Treasure Chart 34"),
     (28, "Treasure Chart 5"),
     (29, "Treasure Chart 28"),
     (30, "Treasure Chart 35"),
     (31, "Triforce Chart 3"),
     (32, "Triforce Chart 6"),
     (33, "Treasure Chart 1"),
     (34, "Treasure Chart 20"),
     (35, "Treasure Chart 36"),
     (36, "Treasure Chart 23"),
     (37, "Treasure Chart 12"),
     (38, "Treasure Chart 16"),
     (39, "Treasure Chart 4"),
     (40, "Treasure Chart 17"),
     (41, "Treasure Chart 31"),
     (42, "Triforce Chart 5"),
     (43, "Treasure Chart 9"),
     (44, "Triforce Chart 4"),
     (45, "Treasure Chart 40"),
     (46, "Triforce Chart 8"),
     (47, "Treasure Chart 15"),
     (48, "Treasure Chart 32"),
     (49, "Treasure Chart 33"),
   ])
   
   self.custom_model_name = "Link"
   
   self.logic = Logic(self)
   
   num_progress_locations = self.logic.get_num_progression_locations()
   num_progress_items = self.logic.get_num_progression_items()
   if num_progress_locations < num_progress_items: 
     error_message = "Not enough progress locations to place all progress items.\n\n"
     error_message += "Total progress items: %d\n" % num_progress_items
     error_message += "Progress locations with current options: %d\n\n" % num_progress_locations
     error_message += "You need to check more of the progress location options in order to give the randomizer enough space to place all the items."
     raise TooFewProgressionLocationsError(error_message)
   
   # We need to determine if the user's selected options result in a dungeons-only-start.
   # Dungeons-only-start meaning that the only locations accessible at the start of the run are dungeon locations.
   # e.g. If the user selects Dungeons, Expensive Purchases, and Sunken Treasures, the dungeon locations are the only ones the player can check first.
   # We need to distinguish this situation because it can cause issues for the randomizer's item placement logic.
   self.logic.temporarily_make_dungeon_entrance_macros_impossible()
   accessible_undone_locations = self.logic.get_accessible_remaining_locations(for_progression=True)
   if len(accessible_undone_locations) == 0:
     self.dungeons_only_start = True
   else:
     self.dungeons_only_start = False
   self.logic.update_dungeon_entrance_macros() # Reset the dungeon entrance macros.
Example #4
0
    def __init__(self,
                 seed,
                 clean_iso_path,
                 randomized_output_folder,
                 options,
                 permalink=None,
                 dry_run=False):
        self.randomized_output_folder = randomized_output_folder
        self.options = options
        self.seed = seed
        self.permalink = permalink
        self.dry_run = dry_run

        self.integer_seed = int(
            hashlib.md5(self.seed.encode('utf-8')).hexdigest(), 16)
        self.rng = Random()
        self.rng.seed(self.integer_seed)

        self.arcs_by_path = {}
        self.jpcs_by_path = {}
        self.raw_files_by_path = {}

        if not self.dry_run:
            self.verify_supported_version(clean_iso_path)

            self.gcm = GCM(clean_iso_path)
            self.gcm.read_entire_disc()

            self.chart_list = self.get_arc(
                "files/res/Msg/fmapres.arc").get_file("cmapdat.bin")
            self.bmg = self.get_arc("files/res/Msg/bmgres.arc").get_file(
                "zel_00.bmg")

        self.read_text_file_lists()

        # Starting items. This list is read by the Logic when initializing your currently owned items list.
        self.starting_items = [
            "Wind Waker",
            "Wind's Requiem",
            "Ballad of Gales",
            "Progressive Sword",
            "Hero's Shield",
            "Boat's Sail",
        ]
        # Add starting Triforce Shards.
        num_starting_triforce_shards = int(
            self.options.get("num_starting_triforce_shards", 0))
        for i in range(num_starting_triforce_shards):
            self.starting_items.append("Triforce Shard %d" % (i + 1))

        # Default dungeon entrances to be used if dungeon entrance randomizer is not on.
        self.dungeon_entrances = OrderedDict([
            ("Dungeon Entrance On Dragon Roost Island", "Dragon Roost Cavern"),
            ("Dungeon Entrance In Forest Haven Sector", "Forbidden Woods"),
            ("Dungeon Entrance In Tower of the Gods Sector",
             "Tower of the Gods"),
            ("Dungeon Entrance On Headstone Island", "Earth Temple"),
            ("Dungeon Entrance On Gale Isle", "Wind Temple"),
        ])
        self.dungeon_island_locations = OrderedDict([
            ("Dragon Roost Cavern", "Dragon Roost Island"),
            ("Forbidden Woods", "Forest Haven"),
            ("Tower of the Gods", "Tower of the Gods"),
            ("Earth Temple", "Headstone Island"),
            ("Wind Temple", "Gale Isle"),
        ])

        # Default starting island (Outset) if the starting island randomizer is not on.
        self.starting_island_index = 44

        # Default charts for each island.
        self.island_number_to_chart_name = OrderedDict([
            (1, "Treasure Chart 25"),
            (2, "Treasure Chart 7"),
            (3, "Treasure Chart 24"),
            (4, "Triforce Chart 2"),
            (5, "Treasure Chart 11"),
            (6, "Triforce Chart 7"),
            (7, "Treasure Chart 13"),
            (8, "Treasure Chart 41"),
            (9, "Treasure Chart 29"),
            (10, "Treasure Chart 22"),
            (11, "Treasure Chart 18"),
            (12, "Treasure Chart 30"),
            (13, "Treasure Chart 39"),
            (14, "Treasure Chart 19"),
            (15, "Treasure Chart 8"),
            (16, "Treasure Chart 2"),
            (17, "Treasure Chart 10"),
            (18, "Treasure Chart 26"),
            (19, "Treasure Chart 3"),
            (20, "Treasure Chart 37"),
            (21, "Treasure Chart 27"),
            (22, "Treasure Chart 38"),
            (23, "Triforce Chart 1"),
            (24, "Treasure Chart 21"),
            (25, "Treasure Chart 6"),
            (26, "Treasure Chart 14"),
            (27, "Treasure Chart 34"),
            (28, "Treasure Chart 5"),
            (29, "Treasure Chart 28"),
            (30, "Treasure Chart 35"),
            (31, "Triforce Chart 3"),
            (32, "Triforce Chart 6"),
            (33, "Treasure Chart 1"),
            (34, "Treasure Chart 20"),
            (35, "Treasure Chart 36"),
            (36, "Treasure Chart 23"),
            (37, "Treasure Chart 12"),
            (38, "Treasure Chart 16"),
            (39, "Treasure Chart 4"),
            (40, "Treasure Chart 17"),
            (41, "Treasure Chart 31"),
            (42, "Triforce Chart 5"),
            (43, "Treasure Chart 9"),
            (44, "Triforce Chart 4"),
            (45, "Treasure Chart 40"),
            (46, "Triforce Chart 8"),
            (47, "Treasure Chart 15"),
            (48, "Treasure Chart 32"),
            (49, "Treasure Chart 33"),
        ])

        self.logic = Logic(self)

        num_progress_locations = self.logic.get_num_progression_locations()
        num_progress_items = self.logic.get_num_progression_items()
        if num_progress_locations < num_progress_items:
            error_message = "Not enough progress locations to place all progress items.\n\n"
            error_message += "Total progress items: %d\n" % num_progress_items
            error_message += "Progress locations with current options: %d\n\n" % num_progress_locations
            error_message += "You need to check more of the progress location options in order to give the randomizer enough space to place all the items."
            raise TooFewProgressionLocationsError(error_message)
Example #5
0
  def __init__(self, seed, clean_iso_path, randomized_output_folder, options, permalink=None, cmd_line_args=OrderedDict()):
    self.randomized_output_folder = randomized_output_folder
    self.options = options
    self.seed = seed
    self.permalink = permalink
    
    self.dry_run = ("-dry" in cmd_line_args)
    self.disassemble = ("-disassemble" in cmd_line_args)
    self.export_disc_to_folder = ("-exportfolder" in cmd_line_args)
    self.no_logs = ("-nologs" in cmd_line_args)
    self.bulk_test = ("-bulk" in cmd_line_args)
    if self.bulk_test:
      self.dry_run = True
      self.no_logs = True
    self.print_used_flags = ("-printflags" in cmd_line_args)
															
																	
								  
		 
								 
													 
    
	
							  
								
								   
						  
											
																					  
	
						   
													   
							 
    self.integer_seed = self.convert_string_to_integer_md5(self.seed)
    self.rng = self.get_new_rng()
    
    self.arcs_by_path = {}
    self.jpcs_by_path = {}
    self.rels_by_path = {}
    self.symbol_maps_by_path = {}
    self.raw_files_by_path = {}
    self.used_actor_ids = list(range(0x1F6))
    
    self.read_text_file_lists()
    
    if not self.dry_run:
      if not os.path.isfile(clean_iso_path):
        raise InvalidCleanISOError("Clean WW ISO does not exist: %s" % clean_iso_path)
      
      self.verify_supported_version(clean_iso_path)
      
      self.gcm = GCM(clean_iso_path)
      self.gcm.read_entire_disc()
      
      dol_data = self.gcm.read_file_data("sys/main.dol")
      self.dol = DOL()
      self.dol.read(dol_data)
      
      try:
        self.chart_list = self.get_arc("files/res/Msg/fmapres.arc").get_file("cmapdat.bin")
      except (InvalidOffsetError, AssertionError):
        # An invalid offset error when reading fmapres.arc seems to happen when the user has a corrupted clean ISO.
        # Alternatively, fmapres.arc's magic bytes not being RARC can also happen here, also caused by a corrupted clean ISO.
        # The reason for this is unknown, but when this happens check the ISO's MD5 and if it's wrong say so in an error message.
        self.verify_correct_clean_iso_md5(clean_iso_path)
        
        # But if the ISO's MD5 is correct just raise the normal offset error.
        raise
      
      self.bmg = self.get_arc("files/res/Msg/bmgres.arc").get_file("zel_00.bmg")
      
      if self.disassemble:
        self.disassemble_all_code()
      if self.print_used_flags:
        stage_searcher.print_all_used_item_pickup_flags(self)
        stage_searcher.print_all_used_chest_open_flags(self)
        stage_searcher.print_all_event_flags_used_by_stb_cutscenes(self)
																		
	
																									   
						   
				   
					   
						
						
					  
					
	 
																
	
															
													 
								   
																						   
												 
															 

													 
								  
												  

												   
								 
												   
	
																					
											 
																		 
																	 
																			
															   
													   
	  
																	
																						 
																			 
																			 
																	
																				   
																			 
																			   
																						   
																				 
																				   
																			 
																						 
																								
																		 
																				 
																		   
																						 
																				   
																		 
	  
														  
													 
										  
												 
										   
								   
	  
																 
																			 
																 
																 
																 
																	   
																 
																   
																			   
																	 
																	   
																 
																		   
																				 
															 
																	 
															   
																			 
																	   
															 
	  
	
																				   
								   
	
									 
													
							   
							  
							   
							  
							   
							  
							   
							   
							   
								
								
								
								
								
							   
							   
								
								
							   
								
								
								
							   
								
							   
								
								
							   
								
								
							   
							   
							   
								
								
								
								
								
							   
								
								
							   
							   
							   
								
							   
								
								
								
	  
	
																									  
														  
										  
																				
														  
										 
																									  
														  
										
    self.custom_model_name = "Link"
    self.using_custom_sail_texture = False