421 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			421 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								"""
							 | 
						||
| 
								 | 
							
								Classes and functions related to creating a ROM patch
							 | 
						||
| 
								 | 
							
								"""
							 | 
						||
| 
								 | 
							
								import os
							 | 
						||
| 
								 | 
							
								import pkgutil
							 | 
						||
| 
								 | 
							
								from typing import TYPE_CHECKING, List, Tuple
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import bsdiff4
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from worlds.Files import APDeltaPatch
							 | 
						||
| 
								 | 
							
								from settings import get_settings
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from .data import PokemonEmeraldData, TrainerPokemonDataTypeEnum, data
							 | 
						||
| 
								 | 
							
								from .items import reverse_offset_item_value
							 | 
						||
| 
								 | 
							
								from .options import RandomizeWildPokemon, RandomizeTrainerParties, EliteFourRequirement, NormanRequirement
							 | 
						||
| 
								 | 
							
								from .pokemon import get_random_species
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if TYPE_CHECKING:
							 | 
						||
| 
								 | 
							
								    from . import PokemonEmeraldWorld
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class PokemonEmeraldDeltaPatch(APDeltaPatch):
							 | 
						||
| 
								 | 
							
								    game = "Pokemon Emerald"
							 | 
						||
| 
								 | 
							
								    hash = "605b89b67018abcea91e693a4dd25be3"
							 | 
						||
| 
								 | 
							
								    patch_file_ending = ".apemerald"
							 | 
						||
| 
								 | 
							
								    result_file_ending = ".gba"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    @classmethod
							 | 
						||
| 
								 | 
							
								    def get_source_data(cls) -> bytes:
							 | 
						||
| 
								 | 
							
								        return get_base_rom_as_bytes()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								location_visited_event_to_id_map = {
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_LITTLEROOT_TOWN": 0,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_OLDALE_TOWN": 1,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_PETALBURG_CITY": 2,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_RUSTBORO_CITY": 3,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_DEWFORD_TOWN": 4,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_SLATEPORT_CITY": 5,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_MAUVILLE_CITY": 6,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_VERDANTURF_TOWN": 7,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_FALLARBOR_TOWN": 8,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_LAVARIDGE_TOWN": 9,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_FORTREE_CITY": 10,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_LILYCOVE_CITY": 11,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_MOSSDEEP_CITY": 12,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_SOOTOPOLIS_CITY": 13,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_PACIFIDLOG_TOWN": 14,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_EVER_GRANDE_CITY": 15,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_BATTLE_FRONTIER": 16,
							 | 
						||
| 
								 | 
							
								    "EVENT_VISITED_SOUTHERN_ISLAND": 17
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def generate_output(world: "PokemonEmeraldWorld", output_directory: str) -> None:
							 | 
						||
| 
								 | 
							
								    base_rom = get_base_rom_as_bytes()
							 | 
						||
| 
								 | 
							
								    base_patch = pkgutil.get_data(__name__, "data/base_patch.bsdiff4")
							 | 
						||
| 
								 | 
							
								    patched_rom = bytearray(bsdiff4.patch(base_rom, base_patch))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set item values
							 | 
						||
| 
								 | 
							
								    for location in world.multiworld.get_locations(world.player):
							 | 
						||
| 
								 | 
							
								        # Set free fly location
							 | 
						||
| 
								 | 
							
								        if location.address is None:
							 | 
						||
| 
								 | 
							
								            if world.options.free_fly_location and location.name == "EVENT_VISITED_LITTLEROOT_TOWN":
							 | 
						||
| 
								 | 
							
								                _set_bytes_little_endian(
							 | 
						||
| 
								 | 
							
								                    patched_rom,
							 | 
						||
| 
								 | 
							
								                    data.rom_addresses["gArchipelagoOptions"] + 0x16,
							 | 
						||
| 
								 | 
							
								                    1,
							 | 
						||
| 
								 | 
							
								                    world.free_fly_location_id
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								            continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if location.item and location.item.player == world.player:
							 | 
						||
| 
								 | 
							
								            _set_bytes_little_endian(
							 | 
						||
| 
								 | 
							
								                patched_rom,
							 | 
						||
| 
								 | 
							
								                location.rom_address,
							 | 
						||
| 
								 | 
							
								                2,
							 | 
						||
| 
								 | 
							
								                reverse_offset_item_value(location.item.code)
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            _set_bytes_little_endian(
							 | 
						||
| 
								 | 
							
								                patched_rom,
							 | 
						||
| 
								 | 
							
								                location.rom_address,
							 | 
						||
| 
								 | 
							
								                2,
							 | 
						||
| 
								 | 
							
								                data.constants["ITEM_ARCHIPELAGO_PROGRESSION"]
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set start inventory
							 | 
						||
| 
								 | 
							
								    start_inventory = world.options.start_inventory.value.copy()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    starting_badges = 0
							 | 
						||
| 
								 | 
							
								    if start_inventory.pop("Stone Badge", 0) > 0:
							 | 
						||
| 
								 | 
							
								        starting_badges |= (1 << 0)
							 | 
						||
| 
								 | 
							
								    if start_inventory.pop("Knuckle Badge", 0) > 0:
							 | 
						||
| 
								 | 
							
								        starting_badges |= (1 << 1)
							 | 
						||
| 
								 | 
							
								    if start_inventory.pop("Dynamo Badge", 0) > 0:
							 | 
						||
| 
								 | 
							
								        starting_badges |= (1 << 2)
							 | 
						||
| 
								 | 
							
								    if start_inventory.pop("Heat Badge", 0) > 0:
							 | 
						||
| 
								 | 
							
								        starting_badges |= (1 << 3)
							 | 
						||
| 
								 | 
							
								    if start_inventory.pop("Balance Badge", 0) > 0:
							 | 
						||
| 
								 | 
							
								        starting_badges |= (1 << 4)
							 | 
						||
| 
								 | 
							
								    if start_inventory.pop("Feather Badge", 0) > 0:
							 | 
						||
| 
								 | 
							
								        starting_badges |= (1 << 5)
							 | 
						||
| 
								 | 
							
								    if start_inventory.pop("Mind Badge", 0) > 0:
							 | 
						||
| 
								 | 
							
								        starting_badges |= (1 << 6)
							 | 
						||
| 
								 | 
							
								    if start_inventory.pop("Rain Badge", 0) > 0:
							 | 
						||
| 
								 | 
							
								        starting_badges |= (1 << 7)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    pc_slots: List[Tuple[str, int]] = []
							 | 
						||
| 
								 | 
							
								    while any(qty > 0 for qty in start_inventory.values()):
							 | 
						||
| 
								 | 
							
								        if len(pc_slots) >= 19:
							 | 
						||
| 
								 | 
							
								            break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for i, item_name in enumerate(start_inventory.keys()):
							 | 
						||
| 
								 | 
							
								            if len(pc_slots) >= 19:
							 | 
						||
| 
								 | 
							
								                break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            quantity = min(start_inventory[item_name], 999)
							 | 
						||
| 
								 | 
							
								            if quantity == 0:
							 | 
						||
| 
								 | 
							
								                continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            start_inventory[item_name] -= quantity
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            pc_slots.append((item_name, quantity))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    pc_slots.sort(reverse=True)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for i, slot in enumerate(pc_slots):
							 | 
						||
| 
								 | 
							
								        address = data.rom_addresses["sNewGamePCItems"] + (i * 4)
							 | 
						||
| 
								 | 
							
								        item = reverse_offset_item_value(world.item_name_to_id[slot[0]])
							 | 
						||
| 
								 | 
							
								        _set_bytes_little_endian(patched_rom, address + 0, 2, item)
							 | 
						||
| 
								 | 
							
								        _set_bytes_little_endian(patched_rom, address + 2, 2, slot[1])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set species data
							 | 
						||
| 
								 | 
							
								    _set_species_info(world, patched_rom)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set encounter tables
							 | 
						||
| 
								 | 
							
								    if world.options.wild_pokemon != RandomizeWildPokemon.option_vanilla:
							 | 
						||
| 
								 | 
							
								        _set_encounter_tables(world, patched_rom)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set opponent data
							 | 
						||
| 
								 | 
							
								    if world.options.trainer_parties != RandomizeTrainerParties.option_vanilla:
							 | 
						||
| 
								 | 
							
								        _set_opponents(world, patched_rom)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set static pokemon
							 | 
						||
| 
								 | 
							
								    _set_static_encounters(world, patched_rom)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set starters
							 | 
						||
| 
								 | 
							
								    _set_starters(world, patched_rom)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set TM moves
							 | 
						||
| 
								 | 
							
								    _set_tm_moves(world, patched_rom)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set TM/HM compatibility
							 | 
						||
| 
								 | 
							
								    _set_tmhm_compatibility(world, patched_rom)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Randomize opponent double or single
							 | 
						||
| 
								 | 
							
								    _randomize_opponent_battle_type(world, patched_rom)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Options
							 | 
						||
| 
								 | 
							
								    # struct ArchipelagoOptions
							 | 
						||
| 
								 | 
							
								    # {
							 | 
						||
| 
								 | 
							
								    #     /* 0x00 */ bool8 advanceTextWithHoldA;
							 | 
						||
| 
								 | 
							
								    #     /* 0x01 */ bool8 isFerryEnabled;
							 | 
						||
| 
								 | 
							
								    #     /* 0x02 */ bool8 areTrainersBlind;
							 | 
						||
| 
								 | 
							
								    #     /* 0x03 */ bool8 canFlyWithoutBadge;
							 | 
						||
| 
								 | 
							
								    #     /* 0x04 */ u16 expMultiplierNumerator;
							 | 
						||
| 
								 | 
							
								    #     /* 0x06 */ u16 expMultiplierDenominator;
							 | 
						||
| 
								 | 
							
								    #     /* 0x08 */ u16 birchPokemon;
							 | 
						||
| 
								 | 
							
								    #     /* 0x0A */ bool8 guaranteedCatch;
							 | 
						||
| 
								 | 
							
								    #     /* 0x0B */ bool8 betterShopsEnabled;
							 | 
						||
| 
								 | 
							
								    #     /* 0x0C */ bool8 eliteFourRequiresGyms;
							 | 
						||
| 
								 | 
							
								    #     /* 0x0D */ u8 eliteFourRequiredCount;
							 | 
						||
| 
								 | 
							
								    #     /* 0x0E */ bool8 normanRequiresGyms;
							 | 
						||
| 
								 | 
							
								    #     /* 0x0F */ u8 normanRequiredCount;
							 | 
						||
| 
								 | 
							
								    #     /* 0x10 */ u8 startingBadges;
							 | 
						||
| 
								 | 
							
								    #     /* 0x11 */ u8 receivedItemMessageFilter; // 0 = Show All; 1 = Show Progression Only; 2 = Show None
							 | 
						||
| 
								 | 
							
								    #     /* 0x12 */ bool8 reusableTms;
							 | 
						||
| 
								 | 
							
								    #     /* 0x14 */ u16 removedBlockers;
							 | 
						||
| 
								 | 
							
								    #     /* 0x13 */ bool8 addRoute115Boulders;
							 | 
						||
| 
								 | 
							
								    #     /* 0x14 */ u16 removedBlockers;
							 | 
						||
| 
								 | 
							
								    #     /* 0x14 */ u16 removedBlockers;
							 | 
						||
| 
								 | 
							
								    #     /* 0x16 */ u8 freeFlyLocation;
							 | 
						||
| 
								 | 
							
								    # };
							 | 
						||
| 
								 | 
							
								    options_address = data.rom_addresses["gArchipelagoOptions"]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set hold A to advance text
							 | 
						||
| 
								 | 
							
								    turbo_a = 1 if world.options.turbo_a else 0
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x00, 1, turbo_a)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set ferry enabled
							 | 
						||
| 
								 | 
							
								    enable_ferry = 1 if world.options.enable_ferry else 0
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x01, 1, enable_ferry)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set blind trainers
							 | 
						||
| 
								 | 
							
								    blind_trainers = 1 if world.options.blind_trainers else 0
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x02, 1, blind_trainers)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set fly without badge
							 | 
						||
| 
								 | 
							
								    fly_without_badge = 1 if world.options.fly_without_badge else 0
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x03, 1, fly_without_badge)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set exp modifier
							 | 
						||
| 
								 | 
							
								    numerator = min(max(world.options.exp_modifier.value, 0), 2**16 - 1)
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x04, 2, numerator)
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x06, 2, 100)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set Birch pokemon
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(
							 | 
						||
| 
								 | 
							
								        patched_rom,
							 | 
						||
| 
								 | 
							
								        options_address + 0x08,
							 | 
						||
| 
								 | 
							
								        2,
							 | 
						||
| 
								 | 
							
								        get_random_species(world.random, data.species).species_id
							 | 
						||
| 
								 | 
							
								    )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set guaranteed catch
							 | 
						||
| 
								 | 
							
								    guaranteed_catch = 1 if world.options.guaranteed_catch else 0
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x0A, 1, guaranteed_catch)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set better shops
							 | 
						||
| 
								 | 
							
								    better_shops = 1 if world.options.better_shops else 0
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x0B, 1, better_shops)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set elite four requirement
							 | 
						||
| 
								 | 
							
								    elite_four_requires_gyms = 1 if world.options.elite_four_requirement == EliteFourRequirement.option_gyms else 0
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x0C, 1, elite_four_requires_gyms)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set elite four count
							 | 
						||
| 
								 | 
							
								    elite_four_count = min(max(world.options.elite_four_count.value, 0), 8)
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x0D, 1, elite_four_count)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set norman requirement
							 | 
						||
| 
								 | 
							
								    norman_requires_gyms = 1 if world.options.norman_requirement == NormanRequirement.option_gyms else 0
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x0E, 1, norman_requires_gyms)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set norman count
							 | 
						||
| 
								 | 
							
								    norman_count = min(max(world.options.norman_count.value, 0), 8)
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x0F, 1, norman_count)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set starting badges
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x10, 1, starting_badges)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set receive item messages type
							 | 
						||
| 
								 | 
							
								    receive_item_messages_type = world.options.receive_item_messages.value
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x11, 1, receive_item_messages_type)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set reusable TMs
							 | 
						||
| 
								 | 
							
								    reusable_tms = 1 if world.options.reusable_tms else 0
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x12, 1, reusable_tms)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set route 115 boulders
							 | 
						||
| 
								 | 
							
								    route_115_boulders = 1 if world.options.extra_boulders else 0
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x13, 1, route_115_boulders)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set removed blockers
							 | 
						||
| 
								 | 
							
								    removed_roadblocks = world.options.remove_roadblocks.value
							 | 
						||
| 
								 | 
							
								    removed_roadblocks_bitfield = 0
							 | 
						||
| 
								 | 
							
								    removed_roadblocks_bitfield |= (1 << 0) if "Safari Zone Construction Workers" in removed_roadblocks else 0
							 | 
						||
| 
								 | 
							
								    removed_roadblocks_bitfield |= (1 << 1) if "Lilycove City Wailmer" in removed_roadblocks else 0
							 | 
						||
| 
								 | 
							
								    removed_roadblocks_bitfield |= (1 << 2) if "Route 110 Aqua Grunts" in removed_roadblocks else 0
							 | 
						||
| 
								 | 
							
								    removed_roadblocks_bitfield |= (1 << 3) if "Aqua Hideout Grunts" in removed_roadblocks else 0
							 | 
						||
| 
								 | 
							
								    removed_roadblocks_bitfield |= (1 << 4) if "Route 119 Aqua Grunts" in removed_roadblocks else 0
							 | 
						||
| 
								 | 
							
								    removed_roadblocks_bitfield |= (1 << 5) if "Route 112 Magma Grunts" in removed_roadblocks else 0
							 | 
						||
| 
								 | 
							
								    removed_roadblocks_bitfield |= (1 << 6) if "Seafloor Cavern Aqua Grunt" in removed_roadblocks else 0
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(patched_rom, options_address + 0x14, 2, removed_roadblocks_bitfield)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Set slot name
							 | 
						||
| 
								 | 
							
								    player_name = world.multiworld.get_player_name(world.player)
							 | 
						||
| 
								 | 
							
								    for i, byte in enumerate(player_name.encode("utf-8")):
							 | 
						||
| 
								 | 
							
								        _set_bytes_little_endian(patched_rom, data.rom_addresses["gArchipelagoInfo"] + i, 1, byte)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Write Output
							 | 
						||
| 
								 | 
							
								    out_file_name = world.multiworld.get_out_file_name_base(world.player)
							 | 
						||
| 
								 | 
							
								    output_path = os.path.join(output_directory, f"{out_file_name}.gba")
							 | 
						||
| 
								 | 
							
								    with open(output_path, "wb") as out_file:
							 | 
						||
| 
								 | 
							
								        out_file.write(patched_rom)
							 | 
						||
| 
								 | 
							
								    patch = PokemonEmeraldDeltaPatch(os.path.splitext(output_path)[0] + ".apemerald", player=world.player,
							 | 
						||
| 
								 | 
							
								                                     player_name=player_name, patched_path=output_path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    patch.write()
							 | 
						||
| 
								 | 
							
								    os.unlink(output_path)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_base_rom_as_bytes() -> bytes:
							 | 
						||
| 
								 | 
							
								    with open(get_settings().pokemon_emerald_settings.rom_file, "rb") as infile:
							 | 
						||
| 
								 | 
							
								        base_rom_bytes = bytes(infile.read())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return base_rom_bytes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _set_bytes_little_endian(byte_array: bytearray, address: int, size: int, value: int) -> None:
							 | 
						||
| 
								 | 
							
								    offset = 0
							 | 
						||
| 
								 | 
							
								    while size > 0:
							 | 
						||
| 
								 | 
							
								        byte_array[address + offset] = value & 0xFF
							 | 
						||
| 
								 | 
							
								        value = value >> 8
							 | 
						||
| 
								 | 
							
								        offset += 1
							 | 
						||
| 
								 | 
							
								        size -= 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _set_encounter_tables(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Encounter tables are lists of
							 | 
						||
| 
								 | 
							
								    struct {
							 | 
						||
| 
								 | 
							
								        min_level:  0x01 bytes,
							 | 
						||
| 
								 | 
							
								        max_level:  0x01 bytes,
							 | 
						||
| 
								 | 
							
								        species_id: 0x02 bytes
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for map_data in world.modified_maps:
							 | 
						||
| 
								 | 
							
								        tables = [map_data.land_encounters, map_data.water_encounters, map_data.fishing_encounters]
							 | 
						||
| 
								 | 
							
								        for table in tables:
							 | 
						||
| 
								 | 
							
								            if table is not None:
							 | 
						||
| 
								 | 
							
								                for i, species_id in enumerate(table.slots):
							 | 
						||
| 
								 | 
							
								                    address = table.rom_address + 2 + (4 * i)
							 | 
						||
| 
								 | 
							
								                    _set_bytes_little_endian(rom, address, 2, species_id)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _set_species_info(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
							 | 
						||
| 
								 | 
							
								    for species in world.modified_species:
							 | 
						||
| 
								 | 
							
								        if species is not None:
							 | 
						||
| 
								 | 
							
								            _set_bytes_little_endian(rom, species.rom_address + 6, 1, species.types[0])
							 | 
						||
| 
								 | 
							
								            _set_bytes_little_endian(rom, species.rom_address + 7, 1, species.types[1])
							 | 
						||
| 
								 | 
							
								            _set_bytes_little_endian(rom, species.rom_address + 8, 1, species.catch_rate)
							 | 
						||
| 
								 | 
							
								            _set_bytes_little_endian(rom, species.rom_address + 22, 1, species.abilities[0])
							 | 
						||
| 
								 | 
							
								            _set_bytes_little_endian(rom, species.rom_address + 23, 1, species.abilities[1])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            for i, learnset_move in enumerate(species.learnset):
							 | 
						||
| 
								 | 
							
								                level_move = learnset_move.level << 9 | learnset_move.move_id
							 | 
						||
| 
								 | 
							
								                _set_bytes_little_endian(rom, species.learnset_rom_address + (i * 2), 2, level_move)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _set_opponents(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
							 | 
						||
| 
								 | 
							
								    for trainer in world.modified_trainers:
							 | 
						||
| 
								 | 
							
								        party_address = trainer.party.rom_address
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        pokemon_data_size: int
							 | 
						||
| 
								 | 
							
								        if trainer.party.pokemon_data_type in {TrainerPokemonDataTypeEnum.NO_ITEM_DEFAULT_MOVES, TrainerPokemonDataTypeEnum.ITEM_DEFAULT_MOVES}:
							 | 
						||
| 
								 | 
							
								            pokemon_data_size = 8
							 | 
						||
| 
								 | 
							
								        else:  # Custom Moves
							 | 
						||
| 
								 | 
							
								            pokemon_data_size = 16
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for i, pokemon in enumerate(trainer.party.pokemon):
							 | 
						||
| 
								 | 
							
								            pokemon_address = party_address + (i * pokemon_data_size)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Replace species
							 | 
						||
| 
								 | 
							
								            _set_bytes_little_endian(rom, pokemon_address + 0x04, 2, pokemon.species_id)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Replace custom moves if applicable
							 | 
						||
| 
								 | 
							
								            if trainer.party.pokemon_data_type == TrainerPokemonDataTypeEnum.NO_ITEM_CUSTOM_MOVES:
							 | 
						||
| 
								 | 
							
								                _set_bytes_little_endian(rom, pokemon_address + 0x06, 2, pokemon.moves[0])
							 | 
						||
| 
								 | 
							
								                _set_bytes_little_endian(rom, pokemon_address + 0x08, 2, pokemon.moves[1])
							 | 
						||
| 
								 | 
							
								                _set_bytes_little_endian(rom, pokemon_address + 0x0A, 2, pokemon.moves[2])
							 | 
						||
| 
								 | 
							
								                _set_bytes_little_endian(rom, pokemon_address + 0x0C, 2, pokemon.moves[3])
							 | 
						||
| 
								 | 
							
								            elif trainer.party.pokemon_data_type == TrainerPokemonDataTypeEnum.ITEM_CUSTOM_MOVES:
							 | 
						||
| 
								 | 
							
								                _set_bytes_little_endian(rom, pokemon_address + 0x08, 2, pokemon.moves[0])
							 | 
						||
| 
								 | 
							
								                _set_bytes_little_endian(rom, pokemon_address + 0x0A, 2, pokemon.moves[1])
							 | 
						||
| 
								 | 
							
								                _set_bytes_little_endian(rom, pokemon_address + 0x0C, 2, pokemon.moves[2])
							 | 
						||
| 
								 | 
							
								                _set_bytes_little_endian(rom, pokemon_address + 0x0E, 2, pokemon.moves[3])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _set_static_encounters(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
							 | 
						||
| 
								 | 
							
								    for encounter in world.modified_static_encounters:
							 | 
						||
| 
								 | 
							
								        _set_bytes_little_endian(rom, encounter.rom_address, 2, encounter.species_id)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _set_starters(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
							 | 
						||
| 
								 | 
							
								    address = data.rom_addresses["sStarterMon"]
							 | 
						||
| 
								 | 
							
								    (starter_1, starter_2, starter_3) = world.modified_starters
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(rom, address + 0, 2, starter_1)
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(rom, address + 2, 2, starter_2)
							 | 
						||
| 
								 | 
							
								    _set_bytes_little_endian(rom, address + 4, 2, starter_3)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _set_tm_moves(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
							 | 
						||
| 
								 | 
							
								    tmhm_list_address = data.rom_addresses["sTMHMMoves"]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for i, move in enumerate(world.modified_tmhm_moves):
							 | 
						||
| 
								 | 
							
								        # Don't modify HMs
							 | 
						||
| 
								 | 
							
								        if i >= 50:
							 | 
						||
| 
								 | 
							
								            break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        _set_bytes_little_endian(rom, tmhm_list_address + (i * 2), 2, move)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _set_tmhm_compatibility(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
							 | 
						||
| 
								 | 
							
								    learnsets_address = data.rom_addresses["gTMHMLearnsets"]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for species in world.modified_species:
							 | 
						||
| 
								 | 
							
								        if species is not None:
							 | 
						||
| 
								 | 
							
								            _set_bytes_little_endian(rom, learnsets_address + (species.species_id * 8), 8, species.tm_hm_compatibility)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
							 | 
						||
| 
								 | 
							
								    probability = world.options.double_battle_chance.value / 100
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    battle_type_map = {
							 | 
						||
| 
								 | 
							
								        0: 4,
							 | 
						||
| 
								 | 
							
								        1: 8,
							 | 
						||
| 
								 | 
							
								        2: 6,
							 | 
						||
| 
								 | 
							
								        3: 13,
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for trainer_data in data.trainers:
							 | 
						||
| 
								 | 
							
								        if trainer_data.battle_script_rom_address != 0 and len(trainer_data.party.pokemon) > 1:
							 | 
						||
| 
								 | 
							
								            if world.random.random() < probability:
							 | 
						||
| 
								 | 
							
								                # Set the trainer to be a double battle
							 | 
						||
| 
								 | 
							
								                _set_bytes_little_endian(rom, trainer_data.rom_address + 0x18, 1, 1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								                # Swap the battle type in the script for the purpose of loading the right text
							 | 
						||
| 
								 | 
							
								                # and setting data to the right places
							 | 
						||
| 
								 | 
							
								                original_battle_type = rom[trainer_data.battle_script_rom_address + 1]
							 | 
						||
| 
								 | 
							
								                if original_battle_type in battle_type_map:
							 | 
						||
| 
								 | 
							
								                    _set_bytes_little_endian(
							 | 
						||
| 
								 | 
							
								                        rom,
							 | 
						||
| 
								 | 
							
								                        trainer_data.battle_script_rom_address + 1,
							 | 
						||
| 
								 | 
							
								                        1,
							 | 
						||
| 
								 | 
							
								                        battle_type_map[original_battle_type]
							 | 
						||
| 
								 | 
							
								                    )
							 |