Pokemon Emerald: Convert to procedure patch (#2995)

* Pokemon Emerald: Convert to procedure patch

* Pokemon Emerald: Remove assertion for vanilla rom's existence

* Pokemon Emerald: Add APPP implication to changelog

* Pokemon Emerald: Move procedure patch changelog line to new version

* Pokemon Emerald: Modify changelog versions

* Pokemon Emerald: Fix patch file download not appearing

---------

Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
This commit is contained in:
Bryce Wilson 2024-05-04 23:08:24 -06:00 committed by GitHub
parent 7603b4a88f
commit 7e61211365
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 335 additions and 215 deletions

View File

@ -1,3 +1,13 @@
# 2.1.1
### Features
- You no longer need a copy of Pokemon Emerald to generate a game, patch files generate much faster.
# 2.1.0
_Separately released, branching from 2.0.0. Included procedure patch migration, but none of the 2.0.1 fixes._
# 2.0.1
### Fixes

View File

@ -5,6 +5,7 @@ from collections import Counter
import copy
import logging
import os
import pkgutil
from typing import Any, Set, List, Dict, Optional, Tuple, ClassVar, TextIO, Union
from BaseClasses import ItemClassification, MultiWorld, Tutorial, LocationProgressType
@ -25,7 +26,7 @@ from .options import (Goal, DarkCavesRequireFlash, HmRequirements, ItemPoolType,
from .pokemon import (get_random_move, get_species_id_by_label, randomize_abilities, randomize_learnsets,
randomize_legendary_encounters, randomize_misc_pokemon, randomize_starters,
randomize_tm_hm_compatibility,randomize_types, randomize_wild_encounters)
from .rom import PokemonEmeraldDeltaPatch, create_patch
from .rom import PokemonEmeraldProcedurePatch, write_tokens
class PokemonEmeraldWebWorld(WebWorld):
@ -60,7 +61,7 @@ class PokemonEmeraldSettings(settings.Group):
"""File name of your English Pokemon Emerald ROM"""
description = "Pokemon Emerald ROM File"
copy_to = "Pokemon - Emerald Version (USA, Europe).gba"
md5s = [PokemonEmeraldDeltaPatch.hash]
md5s = [PokemonEmeraldProcedurePatch.hash]
rom_file: PokemonEmeraldRomFile = PokemonEmeraldRomFile(PokemonEmeraldRomFile.copy_to)
@ -126,9 +127,6 @@ class PokemonEmeraldWorld(World):
def stage_assert_generate(cls, multiworld: MultiWorld) -> None:
from .sanity_check import validate_regions
if not os.path.exists(cls.settings.rom_file):
raise FileNotFoundError(cls.settings.rom_file)
assert validate_regions()
def get_filler_item_name(self) -> str:
@ -591,7 +589,9 @@ class PokemonEmeraldWorld(World):
randomize_opponent_parties(self)
randomize_starters(self)
create_patch(self, output_directory)
patch = PokemonEmeraldProcedurePatch(player=self.player, player_name=self.multiworld.player_name[self.player])
patch.write_file("base_patch.bsdiff4", pkgutil.get_data(__name__, "data/base_patch.bsdiff4"))
write_tokens(self, patch)
del self.modified_trainers
del self.modified_tmhm_moves
@ -600,6 +600,10 @@ class PokemonEmeraldWorld(World):
del self.modified_starters
del self.modified_species
# Write Output
out_file_name = self.multiworld.get_out_file_name_base(self.player)
patch.write(os.path.join(output_directory, f"{out_file_name}{patch.patch_file_ending}"))
def write_spoiler(self, spoiler_handle: TextIO):
if self.options.dexsanity:
from collections import defaultdict

View File

@ -289,6 +289,7 @@ class TrainerData:
party: TrainerPartyData
address: int
script_address: int
battle_type: int
class PokemonEmeraldData:
@ -1422,7 +1423,8 @@ def _init() -> None:
trainer_json["party_address"]
),
trainer_json["address"],
trainer_json["script_address"]
trainer_json["script_address"],
trainer_json["battle_type"]
))

File diff suppressed because one or more lines are too long

View File

@ -3,12 +3,10 @@ Classes and functions related to creating a ROM patch
"""
import copy
import os
import pkgutil
import struct
from typing import TYPE_CHECKING, Dict, List, Tuple
import bsdiff4
from worlds.Files import APDeltaPatch
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes
from settings import get_settings
from .data import TrainerPokemonDataTypeEnum, BASE_OFFSET, data
@ -96,38 +94,32 @@ CAVE_EVENT_NAME_TO_ID = {
}
def _set_bytes_le(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
class PokemonEmeraldDeltaPatch(APDeltaPatch):
class PokemonEmeraldProcedurePatch(APProcedurePatch, APTokenMixin):
game = "Pokemon Emerald"
hash = "605b89b67018abcea91e693a4dd25be3"
patch_file_ending = ".apemerald"
result_file_ending = ".gba"
procedure = [
("apply_bsdiff4", ["base_patch.bsdiff4"]),
("apply_tokens", ["token_data.bin"])
]
@classmethod
def get_source_data(cls) -> bytes:
return get_base_rom_as_bytes()
with open(get_settings().pokemon_emerald_settings.rom_file, "rb") as infile:
base_rom_bytes = bytes(infile.read())
return base_rom_bytes
def create_patch(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))
def write_tokens(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
# Set free fly location
if world.options.free_fly_location:
_set_bytes_le(
patched_rom,
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gArchipelagoOptions"] + 0x20,
1,
world.free_fly_location_id
struct.pack("<B", world.free_fly_location_id)
)
location_info: List[Tuple[int, int, str]] = []
@ -141,26 +133,32 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
# Set local item values
if not world.options.remote_items and location.item.player == world.player:
if type(location.item_address) is int:
_set_bytes_le(
patched_rom,
patch.write_token(
APTokenTypes.WRITE,
location.item_address,
2,
reverse_offset_item_value(location.item.code)
struct.pack("<H", location.item.code - BASE_OFFSET)
)
elif type(location.item_address) is list:
for address in location.item_address:
_set_bytes_le(patched_rom, address, 2, reverse_offset_item_value(location.item.code))
patch.write_token(
APTokenTypes.WRITE,
address,
struct.pack("<H", location.item.code - BASE_OFFSET)
)
else:
if type(location.item_address) is int:
_set_bytes_le(
patched_rom,
patch.write_token(
APTokenTypes.WRITE,
location.item_address,
2,
data.constants["ITEM_ARCHIPELAGO_PROGRESSION"]
struct.pack("<H", data.constants["ITEM_ARCHIPELAGO_PROGRESSION"])
)
elif type(location.item_address) is list:
for address in location.item_address:
_set_bytes_le(patched_rom, address, 2, data.constants["ITEM_ARCHIPELAGO_PROGRESSION"])
patch.write_token(
APTokenTypes.WRITE,
address,
struct.pack("<H", data.constants["ITEM_ARCHIPELAGO_PROGRESSION"])
)
# Creates a list of item information to store in tables later. Those tables are used to display the item and
# player name in a text box. In the case of not enough space, the game will default to "found an ARCHIPELAGO
@ -194,9 +192,21 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
# message (the message for receiving an item will pop up when the client eventually gives it to them).
# In race mode, no item location data is included, and only recieved (or own) items will show any text box.
if item_player == world.player or world.multiworld.is_race:
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 0, 2, flag)
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 2, 2, 0)
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 4, 1, 0)
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 0,
struct.pack("<H", flag)
)
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 2,
struct.pack("<H", 0)
)
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 4,
struct.pack("<B", 0)
)
else:
player_name = world.multiworld.player_name[item_player]
@ -207,11 +217,10 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
player_name_ids[player_name] = len(player_name_ids)
for j, b in enumerate(encode_string(player_name, 17)):
_set_bytes_le(
patched_rom,
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gArchipelagoPlayerNames"] + (player_name_ids[player_name] * 17) + j,
1,
b
struct.pack("<B", b)
)
if item_name not in item_name_offsets:
@ -224,18 +233,28 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
item_name_offsets[item_name] = next_item_name_offset
next_item_name_offset += len(item_name) + 1
for j, b in enumerate(encode_string(item_name) + b"\xFF"):
_set_bytes_le(
patched_rom,
data.rom_addresses["gArchipelagoItemNames"] + (item_name_offsets[item_name]) + j,
1,
b
)
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gArchipelagoItemNames"] + (item_name_offsets[item_name]),
encode_string(item_name) + b"\xFF"
)
# There should always be enough space for one entry per location
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 0, 2, flag)
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 2, 2, item_name_offsets[item_name])
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 4, 1, player_name_ids[player_name])
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 0,
struct.pack("<H", flag)
)
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 2,
struct.pack("<H", item_name_offsets[item_name])
)
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gArchipelagoNameTable"] + (i * 5) + 4,
struct.pack("<B", player_name_ids[player_name])
)
easter_egg = get_easter_egg(world.options.easter_egg.value)
@ -282,40 +301,40 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
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_le(patched_rom, address + 0, 2, item)
_set_bytes_le(patched_rom, address + 2, 2, slot[1])
patch.write_token(APTokenTypes.WRITE, address + 0, struct.pack("<H", item))
patch.write_token(APTokenTypes.WRITE, address + 2, struct.pack("<H", slot[1]))
# Set species data
_set_species_info(world, patched_rom, easter_egg)
_set_species_info(world, patch, easter_egg)
# Set encounter tables
if world.options.wild_pokemon != RandomizeWildPokemon.option_vanilla:
_set_encounter_tables(world, patched_rom)
_set_encounter_tables(world, patch)
# Set opponent data
if world.options.trainer_parties != RandomizeTrainerParties.option_vanilla or easter_egg[0] == 2:
_set_opponents(world, patched_rom, easter_egg)
_set_opponents(world, patch, easter_egg)
# Set legendary pokemon
_set_legendary_encounters(world, patched_rom)
_set_legendary_encounters(world, patch)
# Set misc pokemon
_set_misc_pokemon(world, patched_rom)
_set_misc_pokemon(world, patch)
# Set starters
_set_starters(world, patched_rom)
_set_starters(world, patch)
# Set TM moves
_set_tm_moves(world, patched_rom, easter_egg)
_set_tm_moves(world, patch, easter_egg)
# Randomize move tutor moves
_randomize_move_tutor_moves(world, patched_rom, easter_egg)
_randomize_move_tutor_moves(world, patch, easter_egg)
# Set TM/HM compatibility
_set_tmhm_compatibility(world, patched_rom)
_set_tmhm_compatibility(world, patch)
# Randomize opponent double or single
_randomize_opponent_battle_type(world, patched_rom)
_randomize_opponent_battle_type(world, patch)
# Options
# struct ArchipelagoOptions
@ -360,73 +379,118 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
options_address = data.rom_addresses["gArchipelagoOptions"]
# Set Birch pokemon
_set_bytes_le(
patched_rom,
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x00,
2,
world.random.choice(list(data.species.keys()))
struct.pack("<H", world.random.choice(list(data.species.keys())))
)
# Set hold A to advance text
_set_bytes_le(patched_rom, options_address + 0x02, 1, 1 if world.options.turbo_a else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x02,
struct.pack("<B", 1 if world.options.turbo_a else 0)
)
# Set receive item messages type
_set_bytes_le(patched_rom, options_address + 0x03, 1, world.options.receive_item_messages.value)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x03,
struct.pack("<B", world.options.receive_item_messages.value)
)
# Set better shops
_set_bytes_le(patched_rom, options_address + 0x04, 1, 1 if world.options.better_shops else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x04,
struct.pack("<B", 1 if world.options.better_shops else 0)
)
# Set reusable TMs
_set_bytes_le(patched_rom, options_address + 0x05, 1, 1 if world.options.reusable_tms_tutors else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x05,
struct.pack("<B", 1 if world.options.reusable_tms_tutors else 0)
)
# Set guaranteed catch
_set_bytes_le(patched_rom, options_address + 0x06, 1, 1 if world.options.guaranteed_catch else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x06,
struct.pack("<B", 1 if world.options.guaranteed_catch else 0)
)
# Set purge spinners
_set_bytes_le(patched_rom, options_address + 0x07, 1, 1 if world.options.purge_spinners else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x07,
struct.pack("<B", 1 if world.options.purge_spinners else 0)
)
# Set blind trainers
_set_bytes_le(patched_rom, options_address + 0x08, 1, 1 if world.options.blind_trainers else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x08,
struct.pack("<B", 1 if world.options.blind_trainers else 0)
)
# Set exp modifier
_set_bytes_le(patched_rom, options_address + 0x09, 2, min(max(world.options.exp_modifier.value, 0), 2**16 - 1))
_set_bytes_le(patched_rom, options_address + 0x0B, 2, 100)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x09,
struct.pack("<H", min(max(world.options.exp_modifier.value, 0), 2**16 - 1))
)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x0B,
struct.pack("<H", 100)
)
# Set match trainer levels
_set_bytes_le(patched_rom, options_address + 0x0D, 1, 1 if world.options.match_trainer_levels else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x0D,
struct.pack("<B", 1 if world.options.match_trainer_levels else 0)
)
# Set match trainer levels bonus
if world.options.match_trainer_levels == MatchTrainerLevels.option_additive:
match_trainer_levels_bonus = max(min(world.options.match_trainer_levels_bonus.value, 100), -100)
_set_bytes_le(patched_rom, options_address + 0x0E, 1, match_trainer_levels_bonus) # Works with negatives
patch.write_token(APTokenTypes.WRITE, options_address + 0x0E, struct.pack("<b", match_trainer_levels_bonus))
elif world.options.match_trainer_levels == MatchTrainerLevels.option_multiplicative:
_set_bytes_le(patched_rom, options_address + 0x2E, 2, world.options.match_trainer_levels_bonus.value + 100)
_set_bytes_le(patched_rom, options_address + 0x30, 2, 100)
patch.write_token(APTokenTypes.WRITE, options_address + 0x2E, struct.pack("<H", world.options.match_trainer_levels_bonus.value + 100))
patch.write_token(APTokenTypes.WRITE, options_address + 0x30, struct.pack("<H", 100))
# Set elite four requirement
_set_bytes_le(
patched_rom,
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x0F,
1,
1 if world.options.elite_four_requirement == EliteFourRequirement.option_gyms else 0
struct.pack("<B", 1 if world.options.elite_four_requirement == EliteFourRequirement.option_gyms else 0)
)
# Set elite four count
_set_bytes_le(patched_rom, options_address + 0x10, 1, min(max(world.options.elite_four_count.value, 0), 8))
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x10,
struct.pack("<B", min(max(world.options.elite_four_count.value, 0), 8))
)
# Set norman requirement
_set_bytes_le(
patched_rom,
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x11,
1,
1 if world.options.norman_requirement == NormanRequirement.option_gyms else 0
struct.pack("<B", 1 if world.options.norman_requirement == NormanRequirement.option_gyms else 0)
)
# Set norman count
_set_bytes_le(patched_rom, options_address + 0x12, 1, min(max(world.options.norman_count.value, 0), 8))
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x12,
struct.pack("<B", min(max(world.options.norman_count.value, 0), 8))
)
# Set starting badges
_set_bytes_le(patched_rom, options_address + 0x13, 1, starting_badges)
patch.write_token(APTokenTypes.WRITE, options_address + 0x13, struct.pack("<B", starting_badges))
# Set HM badge requirements
field_move_order = [
@ -455,7 +519,7 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
hm_badge_counts = 0
for i, hm in enumerate(field_move_order):
hm_badge_counts |= (world.hm_requirements[hm] if isinstance(world.hm_requirements[hm], int) else 0xF) << (i * 4)
_set_bytes_le(patched_rom, options_address + 0x14, 4, hm_badge_counts)
patch.write_token(APTokenTypes.WRITE, options_address + 0x14, struct.pack("<I", hm_badge_counts))
# Specific badges
for i, hm in enumerate(field_move_order):
@ -463,21 +527,37 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
bitfield = 0
for badge in world.hm_requirements:
bitfield |= badge_to_bit[badge]
_set_bytes_le(patched_rom, options_address + 0x18 + i, 1, bitfield)
patch.write_token(APTokenTypes.WRITE, options_address + 0x18, struct.pack("<B", bitfield))
# Set terra/marine cave locations
terra_cave_id = CAVE_EVENT_NAME_TO_ID[world.multiworld.get_location("TERRA_CAVE_LOCATION", world.player).item.name]
marine_cave_id = CAVE_EVENT_NAME_TO_ID[world.multiworld.get_location("MARINE_CAVE_LOCATION", world.player).item.name]
_set_bytes_le(patched_rom, options_address + 0x21, 1, terra_cave_id | (marine_cave_id << 4))
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x21,
struct.pack("<B", terra_cave_id | (marine_cave_id << 4))
)
# Set route 115 boulders
_set_bytes_le(patched_rom, options_address + 0x22, 1, 1 if world.options.extra_boulders else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x22,
struct.pack("<B", 1 if world.options.extra_boulders else 0)
)
# Swap route 115 layout if bumpy slope enabled
_set_bytes_le(patched_rom, options_address + 0x23, 1, 1 if world.options.extra_bumpy_slope else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x23,
struct.pack("<B", 1 if world.options.extra_bumpy_slope else 0)
)
# Swap route 115 layout if bumpy slope enabled
_set_bytes_le(patched_rom, options_address + 0x24, 1, 1 if world.options.modify_118 else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x24,
struct.pack("<B", 1 if world.options.modify_118 else 0)
)
# Set removed blockers
removed_roadblocks = world.options.remove_roadblocks.value
@ -489,44 +569,72 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
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_le(patched_rom, options_address + 0x25, 2, removed_roadblocks_bitfield)
patch.write_token(APTokenTypes.WRITE, options_address + 0x25, struct.pack("<H", removed_roadblocks_bitfield))
# Mark berry trees as randomized
_set_bytes_le(patched_rom, options_address + 0x27, 1, 1 if world.options.berry_trees else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x27,
struct.pack("<B", 1 if world.options.berry_trees else 0)
)
# Mark dexsanity as enabled
_set_bytes_le(patched_rom, options_address + 0x28, 1, 1 if world.options.dexsanity else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x28,
struct.pack("<B", 1 if world.options.dexsanity else 0)
)
# Mark trainersanity as enabled
_set_bytes_le(patched_rom, options_address + 0x29, 1, 1 if world.options.trainersanity else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x29,
struct.pack("<B", 1 if world.options.trainersanity else 0)
)
# Set easter egg data
_set_bytes_le(patched_rom, options_address + 0x2B, 1, easter_egg[0])
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x2B,
struct.pack("<B", easter_egg[0])
)
# Set normalize encounter rates
_set_bytes_le(patched_rom, options_address + 0x2C, 1, 1 if world.options.normalize_encounter_rates else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x2C,
struct.pack("<B", 1 if world.options.normalize_encounter_rates else 0)
)
# Set allow wonder trading
_set_bytes_le(patched_rom, options_address + 0x2D, 1, 1 if world.options.enable_wonder_trading else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x2D,
struct.pack("<B", 1 if world.options.enable_wonder_trading else 0)
)
# Set allowed to skip fanfares
_set_bytes_le(patched_rom, options_address + 0x32, 1, 1 if world.options.fanfares else 0)
patch.write_token(
APTokenTypes.WRITE,
options_address + 0x32,
struct.pack("<B", 1 if world.options.fanfares else 0)
)
if easter_egg[0] == 2:
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (easter_egg[1] * 12) + 4, 1, 50)
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_CUT"] * 12) + 4, 1, 1)
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_FLY"] * 12) + 4, 1, 1)
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_SURF"] * 12) + 4, 1, 1)
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_STRENGTH"] * 12) + 4, 1, 1)
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_FLASH"] * 12) + 4, 1, 1)
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_ROCK_SMASH"] * 12) + 4, 1, 1)
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_WATERFALL"] * 12) + 4, 1, 1)
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_DIVE"] * 12) + 4, 1, 1)
_set_bytes_le(patched_rom, data.rom_addresses["gBattleMoves"] + (data.constants["MOVE_DIG"] * 12) + 4, 1, 1)
offset = data.rom_addresses["gBattleMoves"] + 4
patch.write_token(APTokenTypes.WRITE, offset + (easter_egg[1] * 12), struct.pack("<B", 50))
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_CUT"] * 12), struct.pack("<B", 1))
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_FLY"] * 12), struct.pack("<B", 1))
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_SURF"] * 12), struct.pack("<B", 1))
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_STRENGTH"] * 12), struct.pack("<B", 1))
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_FLASH"] * 12), struct.pack("<B", 1))
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_ROCK_SMASH"] * 12), struct.pack("<B", 1))
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_WATERFALL"] * 12), struct.pack("<B", 1))
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_DIVE"] * 12), struct.pack("<B", 1))
patch.write_token(APTokenTypes.WRITE, offset + (data.constants["MOVE_DIG"] * 12), struct.pack("<B", 1))
# Set slot auth
for i, byte in enumerate(world.auth):
_set_bytes_le(patched_rom, data.rom_addresses["gArchipelagoInfo"] + i, 1, byte)
patch.write_token(APTokenTypes.WRITE, data.rom_addresses["gArchipelagoInfo"], world.auth)
# Randomize music
if world.options.music:
@ -534,11 +642,10 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
randomized_looping_music = copy.copy(_LOOPING_MUSIC)
world.random.shuffle(randomized_looping_music)
for original_music, randomized_music in zip(_LOOPING_MUSIC, randomized_looping_music):
_set_bytes_le(
patched_rom,
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gRandomizedSoundTable"] + (data.constants[original_music] * 2),
2,
data.constants[randomized_music]
struct.pack("<H", data.constants[randomized_music])
)
# Randomize fanfares
@ -547,40 +654,21 @@ def create_patch(world: "PokemonEmeraldWorld", output_directory: str) -> None:
randomized_fanfares = [fanfare_name for fanfare_name in _FANFARES]
world.random.shuffle(randomized_fanfares)
for i, fanfare_pair in enumerate(zip(_FANFARES.keys(), randomized_fanfares)):
_set_bytes_le(
patched_rom,
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gRandomizedSoundTable"] + (data.constants[fanfare_pair[0]] * 2),
2,
data.constants[fanfare_pair[1]]
struct.pack("<H", data.constants[fanfare_pair[1]])
)
_set_bytes_le(
patched_rom,
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["sFanfares"] + (i * 4) + 2,
2,
_FANFARES[fanfare_pair[1]]
struct.pack("<H", _FANFARES[fanfare_pair[1]])
)
# 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=world.multiworld.get_player_name(world.player),
patched_path=output_path)
patch.write()
os.unlink(output_path)
patch.write_file("token_data.bin", patch.get_token_binary())
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_encounter_tables(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
def _set_encounter_tables(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
"""
Encounter tables are lists of
struct {
@ -595,30 +683,31 @@ def _set_encounter_tables(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
if table is not None:
for i, species_id in enumerate(table.slots):
address = table.address + 2 + (4 * i)
_set_bytes_le(rom, address, 2, species_id)
patch.write_token(APTokenTypes.WRITE, address, struct.pack("<H", species_id))
def _set_species_info(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
def _set_species_info(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None:
for species in world.modified_species.values():
_set_bytes_le(rom, species.address + 6, 1, species.types[0])
_set_bytes_le(rom, species.address + 7, 1, species.types[1])
_set_bytes_le(rom, species.address + 8, 1, species.catch_rate)
_set_bytes_le(rom, species.address + 22, 1, species.abilities[0])
_set_bytes_le(rom, species.address + 23, 1, species.abilities[1])
patch.write_token(APTokenTypes.WRITE, species.address + 6, struct.pack("<B", species.types[0]))
patch.write_token(APTokenTypes.WRITE, species.address + 7, struct.pack("<B", species.types[1]))
patch.write_token(APTokenTypes.WRITE, species.address + 8, struct.pack("<B", species.catch_rate))
if easter_egg[0] == 3:
_set_bytes_le(rom, species.address + 22, 1, easter_egg[1])
_set_bytes_le(rom, species.address + 23, 1, easter_egg[1])
patch.write_token(APTokenTypes.WRITE, species.address + 22, struct.pack("<B", easter_egg[1]))
patch.write_token(APTokenTypes.WRITE, species.address + 23, struct.pack("<B", easter_egg[1]))
else:
patch.write_token(APTokenTypes.WRITE, species.address + 22, struct.pack("<B", species.abilities[0]))
patch.write_token(APTokenTypes.WRITE, species.address + 23, struct.pack("<B", species.abilities[1]))
for i, learnset_move in enumerate(species.learnset):
level_move = learnset_move.level << 9 | learnset_move.move_id
if easter_egg[0] == 2:
level_move = learnset_move.level << 9 | easter_egg[1]
_set_bytes_le(rom, species.learnset_address + (i * 2), 2, level_move)
patch.write_token(APTokenTypes.WRITE, species.learnset_address + (i * 2), struct.pack("<H", level_move))
def _set_opponents(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
def _set_opponents(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None:
for trainer in world.modified_trainers:
party_address = trainer.party.address
@ -632,53 +721,50 @@ def _set_opponents(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tup
pokemon_address = party_address + (i * pokemon_data_size)
# Replace species
_set_bytes_le(rom, pokemon_address + 0x04, 2, pokemon.species_id)
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x04, struct.pack("<H", pokemon.species_id))
# Replace custom moves if applicable
if trainer.party.pokemon_data_type == TrainerPokemonDataTypeEnum.NO_ITEM_CUSTOM_MOVES:
if easter_egg[0] == 2:
_set_bytes_le(rom, pokemon_address + 0x06, 2, easter_egg[1])
_set_bytes_le(rom, pokemon_address + 0x08, 2, easter_egg[1])
_set_bytes_le(rom, pokemon_address + 0x0A, 2, easter_egg[1])
_set_bytes_le(rom, pokemon_address + 0x0C, 2, easter_egg[1])
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x06, struct.pack("<H", easter_egg[1]))
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x08, struct.pack("<H", easter_egg[1]))
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0A, struct.pack("<H", easter_egg[1]))
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0C, struct.pack("<H", easter_egg[1]))
else:
_set_bytes_le(rom, pokemon_address + 0x06, 2, pokemon.moves[0])
_set_bytes_le(rom, pokemon_address + 0x08, 2, pokemon.moves[1])
_set_bytes_le(rom, pokemon_address + 0x0A, 2, pokemon.moves[2])
_set_bytes_le(rom, pokemon_address + 0x0C, 2, pokemon.moves[3])
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x06, struct.pack("<H", pokemon.moves[0]))
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x08, struct.pack("<H", pokemon.moves[1]))
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0A, struct.pack("<H", pokemon.moves[2]))
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0C, struct.pack("<H", pokemon.moves[3]))
elif trainer.party.pokemon_data_type == TrainerPokemonDataTypeEnum.ITEM_CUSTOM_MOVES:
if easter_egg[0] == 2:
_set_bytes_le(rom, pokemon_address + 0x08, 2, easter_egg[1])
_set_bytes_le(rom, pokemon_address + 0x0A, 2, easter_egg[1])
_set_bytes_le(rom, pokemon_address + 0x0C, 2, easter_egg[1])
_set_bytes_le(rom, pokemon_address + 0x0E, 2, easter_egg[1])
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x08, struct.pack("<H", easter_egg[1]))
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0A, struct.pack("<H", easter_egg[1]))
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0C, struct.pack("<H", easter_egg[1]))
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0E, struct.pack("<H", easter_egg[1]))
else:
_set_bytes_le(rom, pokemon_address + 0x08, 2, pokemon.moves[0])
_set_bytes_le(rom, pokemon_address + 0x0A, 2, pokemon.moves[1])
_set_bytes_le(rom, pokemon_address + 0x0C, 2, pokemon.moves[2])
_set_bytes_le(rom, pokemon_address + 0x0E, 2, pokemon.moves[3])
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x08, struct.pack("<H", pokemon.moves[0]))
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0A, struct.pack("<H", pokemon.moves[1]))
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0C, struct.pack("<H", pokemon.moves[2]))
patch.write_token(APTokenTypes.WRITE, pokemon_address + 0x0E, struct.pack("<H", pokemon.moves[3]))
def _set_legendary_encounters(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
def _set_legendary_encounters(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
for encounter in world.modified_legendary_encounters:
_set_bytes_le(rom, encounter.address, 2, encounter.species_id)
patch.write_token(APTokenTypes.WRITE, encounter.address, struct.pack("<H", encounter.species_id))
def _set_misc_pokemon(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
def _set_misc_pokemon(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
for encounter in world.modified_misc_pokemon:
_set_bytes_le(rom, encounter.address, 2, encounter.species_id)
patch.write_token(APTokenTypes.WRITE, encounter.address, struct.pack("<H", 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_le(rom, address + 0, 2, starter_1)
_set_bytes_le(rom, address + 2, 2, starter_2)
_set_bytes_le(rom, address + 4, 2, starter_3)
def _set_starters(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
patch.write_token(APTokenTypes.WRITE, data.rom_addresses["sStarterMon"] + 0, struct.pack("<H", world.modified_starters[0]))
patch.write_token(APTokenTypes.WRITE, data.rom_addresses["sStarterMon"] + 2, struct.pack("<H", world.modified_starters[1]))
patch.write_token(APTokenTypes.WRITE, data.rom_addresses["sStarterMon"] + 4, struct.pack("<H", world.modified_starters[2]))
def _set_tm_moves(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
def _set_tm_moves(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None:
tmhm_list_address = data.rom_addresses["sTMHMMoves"]
for i, move in enumerate(world.modified_tmhm_moves):
@ -686,19 +772,24 @@ def _set_tm_moves(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tupl
if i >= 50:
break
_set_bytes_le(rom, tmhm_list_address + (i * 2), 2, move)
if easter_egg[0] == 2:
_set_bytes_le(rom, tmhm_list_address + (i * 2), 2, easter_egg[1])
patch.write_token(APTokenTypes.WRITE, tmhm_list_address + (i * 2), struct.pack("<H", easter_egg[1]))
else:
patch.write_token(APTokenTypes.WRITE, tmhm_list_address + (i * 2), struct.pack("<H", move))
def _set_tmhm_compatibility(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
def _set_tmhm_compatibility(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
learnsets_address = data.rom_addresses["gTMHMLearnsets"]
for species in world.modified_species.values():
_set_bytes_le(rom, learnsets_address + (species.species_id * 8), 8, species.tm_hm_compatibility)
patch.write_token(
APTokenTypes.WRITE,
learnsets_address + (species.species_id * 8),
struct.pack("<Q", species.tm_hm_compatibility)
)
def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", rom: bytearray) -> None:
def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch) -> None:
probability = world.options.double_battle_chance.value / 100
battle_type_map = {
@ -710,26 +801,29 @@ def _randomize_opponent_battle_type(world: "PokemonEmeraldWorld", rom: bytearray
for trainer_data in data.trainers:
if trainer_data.script_address != 0 and len(trainer_data.party.pokemon) > 1:
original_battle_type = rom[trainer_data.script_address + 1]
original_battle_type = trainer_data.battle_type
if original_battle_type in battle_type_map: # Don't touch anything other than regular single battles
if world.random.random() < probability:
# Set the trainer to be a double battle
_set_bytes_le(rom, trainer_data.address + 0x18, 1, 1)
patch.write_token(APTokenTypes.WRITE, trainer_data.address + 0x18, struct.pack("<B", 1))
# Swap the battle type in the script for the purpose of loading the right text
# and setting data to the right places
_set_bytes_le(
rom,
patch.write_token(
APTokenTypes.WRITE,
trainer_data.script_address + 1,
1,
battle_type_map[original_battle_type]
struct.pack("<B", battle_type_map[original_battle_type])
)
def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", rom: bytearray, easter_egg: Tuple[int, int]) -> None:
def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", patch: PokemonEmeraldProcedurePatch, easter_egg: Tuple[int, int]) -> None:
if easter_egg[0] == 2:
for i in range(30):
_set_bytes_le(rom, data.rom_addresses["gTutorMoves"] + (i * 2), 2, easter_egg[1])
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gTutorMoves"] + (i * 2),
struct.pack("<H", easter_egg[1])
)
else:
if world.options.tm_tutor_moves:
new_tutor_moves = []
@ -737,17 +831,27 @@ def _randomize_move_tutor_moves(world: "PokemonEmeraldWorld", rom: bytearray, ea
new_move = get_random_move(world.random, set(new_tutor_moves) | world.blacklisted_moves | HM_MOVES)
new_tutor_moves.append(new_move)
_set_bytes_le(rom, data.rom_addresses["gTutorMoves"] + (i * 2), 2, new_move)
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gTutorMoves"] + (i * 2),
struct.pack("<H", new_move)
)
# Always set Fortree move tutor to Dig
_set_bytes_le(rom, data.rom_addresses["gTutorMoves"] + (24 * 2), 2, data.constants["MOVE_DIG"])
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["gTutorMoves"] + (24 * 2),
struct.pack("<H", data.constants["MOVE_DIG"])
)
# Modify compatibility
if world.options.tm_tutor_compatibility.value != -1:
for species in data.species.values():
_set_bytes_le(
rom,
patch.write_token(
APTokenTypes.WRITE,
data.rom_addresses["sTutorLearnsets"] + (species.species_id * 4),
4,
bool_array_to_int([world.random.randrange(0, 100) < world.options.tm_tutor_compatibility.value for _ in range(32)])
struct.pack("<I", bool_array_to_int([
world.random.randrange(0, 100) < world.options.tm_tutor_compatibility.value
for _ in range(32)
]))
)