Pokémon R/B: Migrate support into Bizhawk Client (#2466)

- Removes the Pokémon Client, adding support for Red and Blue to the Bizhawk Client.
- Adds `/bank` commands that mirror SDV's, allowing transferring money into and out of the EnergyLink storage.
- Adds a fix to the base patch so that the progressive card key counter will not increment beyond 10, which would lead to receiving glitch items. This value is checked against and verified that it is not > 10 as part of crash detection by the client, to prevent erroneous location checks when the game crashes, so this is relevant to the new client (although shouldn't happen unless you're using !getitem, or putting progressive card keys as item link replacement items)
This commit is contained in:
Alchav
2023-11-25 05:57:02 -05:00
committed by GitHub
parent edb62004ef
commit 8a852abdc4
12 changed files with 439 additions and 751 deletions

View File

@@ -2,9 +2,11 @@ import os
import settings
import typing
import threading
import base64
from copy import deepcopy
from typing import TextIO
from Utils import __version__
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification, LocationProgressType
from Fill import fill_restrictive, FillError, sweep_from_pool
from worlds.AutoWorld import World, WebWorld
@@ -22,6 +24,7 @@ from .rules import set_rules
from .level_scaling import level_scaling
from . import logic
from . import poke_data
from . import client
class PokemonSettings(settings.Group):
@@ -36,16 +39,8 @@ class PokemonSettings(settings.Group):
copy_to = "Pokemon Blue (UE) [S][!].gb"
md5s = [BlueDeltaPatch.hash]
class RomStart(str):
"""
Set this to false to never autostart a rom (such as after patching)
True for operating system default program
Alternatively, a path to a program to open the .gb file with
"""
red_rom_file: RedRomFile = RedRomFile(RedRomFile.copy_to)
blue_rom_file: BlueRomFile = BlueRomFile(BlueRomFile.copy_to)
rom_start: typing.Union[RomStart, bool] = True
class PokemonWebWorld(WebWorld):
@@ -141,9 +136,6 @@ class PokemonRedBlueWorld(World):
else:
self.rival_name = encode_name(self.multiworld.rival_name[self.player].value, "Rival")
if len(self.multiworld.player_name[self.player].encode()) > 16:
raise Exception(f"Player name too long for {self.multiworld.get_player_name(self.player)}. Player name cannot exceed 16 bytes for Pokémon Red and Blue.")
if not self.multiworld.badgesanity[self.player]:
self.multiworld.non_local_items[self.player].value -= self.item_name_groups["Badges"]
@@ -621,6 +613,13 @@ class PokemonRedBlueWorld(World):
def generate_output(self, output_directory: str):
generate_output(self, output_directory)
def modify_multidata(self, multidata: dict):
rom_name = bytearray(f'AP{__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:11}\0',
'utf8')[:21]
rom_name.extend([0] * (21 - len(rom_name)))
new_name = base64.b64encode(bytes(rom_name)).decode()
multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
def write_spoiler_header(self, spoiler_handle: TextIO):
spoiler_handle.write(f"Cerulean Cave Total Key Items: {self.multiworld.cerulean_cave_key_items_condition[self.player].total}\n")
spoiler_handle.write(f"Elite Four Total Key Items: {self.multiworld.elite_four_key_items_condition[self.player].total}\n")

277
worlds/pokemon_rb/client.py Normal file
View File

@@ -0,0 +1,277 @@
import base64
import logging
import time
from NetUtils import ClientStatus
from worlds._bizhawk.client import BizHawkClient
from worlds._bizhawk import read, write, guarded_write
from worlds.pokemon_rb.locations import location_data
logger = logging.getLogger("Client")
BANK_EXCHANGE_RATE = 100000000
DATA_LOCATIONS = {
"ItemIndex": (0x1A6E, 0x02),
"Deathlink": (0x00FD, 0x01),
"APItem": (0x00FF, 0x01),
"EventFlag": (0x1735, 0x140),
"Missable": (0x161A, 0x20),
"Hidden": (0x16DE, 0x0E),
"Rod": (0x1716, 0x01),
"DexSanityFlag": (0x1A71, 19),
"GameStatus": (0x1A84, 0x01),
"Money": (0x141F, 3),
"ResetCheck": (0x0100, 4),
# First and second Vermilion Gym trash can selection. Second is not used, so should always be 0.
# First should never be above 0x0F. This is just before Event Flags.
"CrashCheck1": (0x1731, 2),
# Unused, should always be 0. This is just before Missables flags.
"CrashCheck2": (0x1617, 1),
# Progressive keys, should never be above 10. Just before Dexsanity flags.
"CrashCheck3": (0x1A70, 1),
# Route 18 script value. Should never be above 2. Just before Hidden items flags.
"CrashCheck4": (0x16DD, 1),
}
location_map = {"Rod": {}, "EventFlag": {}, "Missable": {}, "Hidden": {}, "list": {}, "DexSanityFlag": {}}
location_bytes_bits = {}
for location in location_data:
if location.ram_address is not None:
if type(location.ram_address) == list:
location_map[type(location.ram_address).__name__][(location.ram_address[0].flag, location.ram_address[1].flag)] = location.address
location_bytes_bits[location.address] = [{'byte': location.ram_address[0].byte, 'bit': location.ram_address[0].bit},
{'byte': location.ram_address[1].byte, 'bit': location.ram_address[1].bit}]
else:
location_map[type(location.ram_address).__name__][location.ram_address.flag] = location.address
location_bytes_bits[location.address] = {'byte': location.ram_address.byte, 'bit': location.ram_address.bit}
location_name_to_id = {location.name: location.address for location in location_data if location.type == "Item"
and location.address is not None}
class PokemonRBClient(BizHawkClient):
system = ("GB", "SGB")
patch_suffix = (".apred", ".apblue")
game = "Pokemon Red and Blue"
def __init__(self):
super().__init__()
self.auto_hints = set()
self.locations_array = None
self.disconnect_pending = False
self.set_deathlink = False
self.banking_command = None
self.game_state = False
self.last_death_link = 0
async def validate_rom(self, ctx):
game_name = await read(ctx.bizhawk_ctx, [(0x134, 12, "ROM")])
game_name = game_name[0].decode("ascii")
if game_name in ("POKEMON RED\00", "POKEMON BLUE"):
ctx.game = self.game
ctx.items_handling = 0b001
ctx.command_processor.commands["bank"] = cmd_bank
seed_name = await read(ctx.bizhawk_ctx, [(0xFFDB, 21, "ROM")])
ctx.seed_name = seed_name[0].split(b"\0")[0].decode("ascii")
self.set_deathlink = False
self.banking_command = None
self.locations_array = None
self.disconnect_pending = False
return True
return False
async def set_auth(self, ctx):
auth_name = await read(ctx.bizhawk_ctx, [(0xFFC6, 21, "ROM")])
if auth_name[0] == bytes([0] * 21):
# rom was patched before rom names implemented, use player name
auth_name = await read(ctx.bizhawk_ctx, [(0xFFF0, 16, "ROM")])
auth_name = auth_name[0].decode("ascii").split("\x00")[0]
else:
auth_name = base64.b64encode(auth_name[0]).decode()
ctx.auth = auth_name
async def game_watcher(self, ctx):
if not ctx.server or not ctx.server.socket.open or ctx.server.socket.closed:
return
data = await read(ctx.bizhawk_ctx, [(loc_data[0], loc_data[1], "WRAM")
for loc_data in DATA_LOCATIONS.values()])
data = {data_set_name: data_name for data_set_name, data_name in zip(DATA_LOCATIONS.keys(), data)}
if self.set_deathlink:
self.set_deathlink = False
await ctx.update_death_link(True)
if self.disconnect_pending:
self.disconnect_pending = False
await ctx.disconnect()
if data["GameStatus"][0] == 0 or data["ResetCheck"] == b'\xff\xff\xff\x7f':
# Do not handle anything before game save is loaded
self.game_state = False
return
elif (data["GameStatus"][0] not in (0x2A, 0xAC)
or data["CrashCheck1"][0] & 0xF0 or data["CrashCheck1"][1] & 0xFF
or data["CrashCheck2"][0]
or data["CrashCheck3"][0] > 10
or data["CrashCheck4"][0] > 2):
# Should mean game crashed
logger.warning("Pokémon Red/Blue game may have crashed. Disconnecting from server.")
self.game_state = False
await ctx.disconnect()
return
self.game_state = True
# SEND ITEMS TO CLIENT
if data["APItem"][0] == 0:
item_index = int.from_bytes(data["ItemIndex"], "little")
if len(ctx.items_received) > item_index:
item_code = ctx.items_received[item_index].item - 172000000
if item_code > 255:
item_code -= 256
await write(ctx.bizhawk_ctx, [(DATA_LOCATIONS["APItem"][0],
[item_code], "WRAM")])
# LOCATION CHECKS
locations = set()
for flag_type, loc_map in location_map.items():
for flag, loc_id in loc_map.items():
if flag_type == "list":
if (data["EventFlag"][location_bytes_bits[loc_id][0]['byte']] & 1 <<
location_bytes_bits[loc_id][0]['bit']
and data["Missable"][location_bytes_bits[loc_id][1]['byte']] & 1 <<
location_bytes_bits[loc_id][1]['bit']):
locations.add(loc_id)
elif data[flag_type][location_bytes_bits[loc_id]['byte']] & 1 << location_bytes_bits[loc_id]['bit']:
locations.add(loc_id)
if locations != self.locations_array:
if locations:
self.locations_array = locations
await ctx.send_msgs([{"cmd": "LocationChecks", "locations": list(locations)}])
# AUTO HINTS
hints = []
if data["EventFlag"][280] & 16:
hints.append("Cerulean Bicycle Shop")
if data["EventFlag"][280] & 32:
hints.append("Route 2 Gate - Oak's Aide")
if data["EventFlag"][280] & 64:
hints.append("Route 11 Gate 2F - Oak's Aide")
if data["EventFlag"][280] & 128:
hints.append("Route 15 Gate 2F - Oak's Aide")
if data["EventFlag"][281] & 1:
hints += ["Celadon Prize Corner - Item Prize 1", "Celadon Prize Corner - Item Prize 2",
"Celadon Prize Corner - Item Prize 3"]
if (location_name_to_id["Fossil - Choice A"] in ctx.checked_locations and location_name_to_id[
"Fossil - Choice B"]
not in ctx.checked_locations):
hints.append("Fossil - Choice B")
elif (location_name_to_id["Fossil - Choice B"] in ctx.checked_locations and location_name_to_id[
"Fossil - Choice A"]
not in ctx.checked_locations):
hints.append("Fossil - Choice A")
hints = [
location_name_to_id[loc] for loc in hints if location_name_to_id[loc] not in self.auto_hints and
location_name_to_id[loc] in ctx.missing_locations and
location_name_to_id[loc] not in ctx.locations_checked
]
if hints:
await ctx.send_msgs([{"cmd": "LocationScouts", "locations": hints, "create_as_hint": 2}])
self.auto_hints.update(hints)
# DEATHLINK
if "DeathLink" in ctx.tags:
if data["Deathlink"][0] == 3:
await ctx.send_death(ctx.player_names[ctx.slot] + " is out of usable Pokémon! "
+ ctx.player_names[ctx.slot] + " blacked out!")
await write(ctx.bizhawk_ctx, [(DATA_LOCATIONS["Deathlink"][0], [0], "WRAM")])
self.last_death_link = ctx.last_death_link
elif ctx.last_death_link > self.last_death_link:
self.last_death_link = ctx.last_death_link
await write(ctx.bizhawk_ctx, [(DATA_LOCATIONS["Deathlink"][0], [1], "WRAM")])
# BANK
if self.banking_command:
original_money = data["Money"]
# Money is stored as binary-coded decimal.
money = int(original_money.hex())
if self.banking_command > money:
logger.warning(f"You do not have ${self.banking_command} to deposit!")
elif (-self.banking_command * BANK_EXCHANGE_RATE) > ctx.stored_data[f"EnergyLink{ctx.team}"]:
logger.warning("Not enough money in the EnergyLink storage!")
else:
if self.banking_command + money > 999999:
self.banking_command = 999999 - money
money = str(money - self.banking_command).zfill(6)
money = [int(money[:2], 16), int(money[2:4], 16), int(money[4:], 16)]
money_written = await guarded_write(ctx.bizhawk_ctx, [(0x141F, money, "WRAM")],
[(0x141F, original_money, "WRAM")])
if money_written:
if self.banking_command >= 0:
deposit = self.banking_command - int(self.banking_command / 4)
tax = self.banking_command - deposit
logger.info(f"Deposited ${deposit}, and charged a tax of ${tax}.")
self.banking_command = deposit
else:
logger.info(f"Withdrew ${-self.banking_command}.")
await ctx.send_msgs([{
"cmd": "Set", "key": f"EnergyLink{ctx.team}", "operations":
[{"operation": "add", "value": self.banking_command * BANK_EXCHANGE_RATE},
{"operation": "max", "value": 0}],
}])
self.banking_command = None
# VICTORY
if data["EventFlag"][280] & 1 and not ctx.finished_game:
await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}])
ctx.finished_game = True
def on_package(self, ctx, cmd, args):
if cmd == 'Connected':
if 'death_link' in args['slot_data'] and args['slot_data']['death_link']:
self.set_deathlink = True
self.last_death_link = time.time()
ctx.set_notify(f"EnergyLink{ctx.team}")
elif cmd == 'RoomInfo':
if ctx.seed_name and ctx.seed_name != args["seed_name"]:
# CommonClient's on_package displays an error to the user in this case, but connection is not cancelled.
self.game_state = False
self.disconnect_pending = True
super().on_package(ctx, cmd, args)
def cmd_bank(self, cmd: str = "", amount: str = ""):
"""Deposit or withdraw money with the server's EnergyLink storage.
/bank - check server balance.
/bank deposit # - deposit money. One quarter of the amount will be lost to taxation.
/bank withdraw # - withdraw money."""
if self.ctx.game != "Pokemon Red and Blue":
logger.warning("This command can only be used while playing Pokémon Red and Blue")
return
if not cmd:
logger.info(f"Money available: {int(self.ctx.stored_data[f'EnergyLink{self.ctx.team}'] / BANK_EXCHANGE_RATE)}")
return
elif (not self.ctx.server) or self.ctx.server.socket.closed or not self.ctx.client_handler.game_state:
logger.info(f"Must be connected to server and in game.")
elif not amount:
logger.warning("You must specify an amount.")
elif cmd == "withdraw":
self.ctx.client_handler.banking_command = -int(amount)
elif cmd == "deposit":
if int(amount) < 4:
logger.warning("You must deposit at least $4, for tax purposes.")
return
self.ctx.client_handler.banking_command = int(amount)
else:
logger.warning(f"Invalid bank command {cmd}")
return

View File

@@ -83,6 +83,9 @@ you until these have ended.
## Unique Local Commands
The following command is only available when using the PokemonClient to play with Archipelago.
You can use `/bank` commands to deposit and withdraw money from the server's EnergyLink storage. This can be accessed by
any players playing games that use the EnergyLink feature.
- `/gb` Check Gameboy Connection State
- `/bank` - check the amount of money available on the server.
- `/bank withdraw #` - withdraw money from the server.
- `/bank deposit #` - deposit money into the server. 25% of the amount will be lost to taxation.

View File

@@ -11,7 +11,6 @@ As we are using BizHawk, this guide is only applicable to Windows and Linux syst
- Detailed installation instructions for BizHawk can be found at the above link.
- Windows users must run the prereq installer first, which can also be found at the above link.
- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases)
(select `Pokemon Client` during installation).
- Pokémon Red and/or Blue ROM files. The Archipelago community cannot provide these.
## Optional Software
@@ -71,28 +70,41 @@ And the following special characters (these each count as one character):
## Joining a MultiWorld Game
### Obtain your Pokémon patch file
### Generating and Patching a Game
When you join a multiworld game, you will be asked to provide your YAML file to whoever is hosting. Once that is done,
the host will provide you with either a link to download your data file, or with a zip file containing everyone's data
files. Your data file should have a `.apred` or `.apblue` extension.
1. Create your settings file (YAML).
2. Follow the general Archipelago instructions for [generating a game](../../Archipelago/setup/en#generating-a-game).
This will generate an output file for you. Your patch file will have a `.apred` or `.apblue` file extension.
3. Open `ArchipelagoLauncher.exe`
4. Select "Open Patch" on the left side and select your patch file.
5. If this is your first time patching, you will be prompted to locate your vanilla ROM.
6. A patched `.gb` file will be created in the same place as the patch file.
7. On your first time opening a patch with BizHawk Client, you will also be asked to locate `EmuHawk.exe` in your
BizHawk install.
Double-click on your patch file to start your client and start the ROM patch process. Once the process is finished
(this can take a while), the client and the emulator will be started automatically (if you associated the extension
to the emulator as recommended).
If you're playing a single-player seed and you don't care about autotracking or hints, you can stop here, close the
client, and load the patched ROM in any emulator. However, for multiworlds and other Archipelago features, continue
below using BizHawk as your emulator.
### Connect to the Multiserver
Once both the client and the emulator are started, you must connect them. Navigate to your Archipelago install folder,
then to `data/lua`, and drag+drop the `connector_pkmn_rb.lua` script onto the main EmuHawk window. (You could instead
open the Lua Console manually, click `Script``Open Script`, and navigate to `connector_pkmn_rb.lua` with the file
picker.)
By default, opening a patch file will do steps 1-5 below for you automatically. Even so, keep them in your memory just
in case you have to close and reopen a window mid-game for some reason.
1. Pokémon Red and Blue use Archipelago's BizHawk Client. If the client isn't still open from when you patched your
game, you can re-open it from the launcher.
2. Ensure EmuHawk is running the patched ROM.
3. In EmuHawk, go to `Tools > Lua Console`. This window must stay open while playing.
4. In the Lua Console window, go to `Script > Open Script…`.
5. Navigate to your Archipelago install folder and open `data/lua/connector_bizhawk_generic.lua`.
6. The emulator may freeze every few seconds until it manages to connect to the client. This is expected. The BizHawk
Client window should indicate that it connected and recognized Pokémon Red/Blue.
7. To connect the client to the server, enter your room's address and port (e.g. `archipelago.gg:38281`) into the
top text field of the client and click Connect.
To connect the client to the multiserver simply put `<address>:<port>` on the textfield on top and press enter (if the
server uses password, type in the bottom textfield `/connect <address>:<port> [password]`)
Now you are ready to start your adventure in Kanto.
## Auto-Tracking
Pokémon Red and Blue has a fully functional map tracker that supports auto-tracking.
@@ -102,4 +114,5 @@ Pokémon Red and Blue has a fully functional map tracker that supports auto-trac
3. Click on the "AP" symbol at the top.
4. Enter the AP address, slot name and password.
The rest should take care of itself! Items and checks will be marked automatically, and it even knows your settings - It will hide checks & adjust logic accordingly.
The rest should take care of itself! Items and checks will be marked automatically, and it even knows your settings - It
will hide checks & adjust logic accordingly.

View File

@@ -539,6 +539,10 @@ def generate_output(self, output_directory: str):
write_bytes(data, self.rival_name, rom_addresses['Rival_Name'])
data[0xFF00] = 2 # client compatibility version
rom_name = bytearray(f'AP{Utils.__version__.replace(".", "")[0:3]}_{self.player}_{self.multiworld.seed:11}\0',
'utf8')[:21]
rom_name.extend([0] * (21 - len(rom_name)))
write_bytes(data, rom_name, 0xFFC6)
write_bytes(data, self.multiworld.seed_name.encode(), 0xFFDB)
write_bytes(data, self.multiworld.player_name[self.player].encode(), 0xFFF0)

View File

@@ -12,101 +12,101 @@ rom_addresses = {
"Player_Name": 0x4568,
"Rival_Name": 0x4570,
"Price_Master_Ball": 0x45c8,
"Title_Seed": 0x5f1b,
"Title_Slot_Name": 0x5f3b,
"PC_Item": 0x6309,
"PC_Item_Quantity": 0x630e,
"Fly_Location": 0x631c,
"Skip_Player_Name": 0x6335,
"Skip_Rival_Name": 0x6343,
"Pallet_Fly_Coords": 0x666e,
"Option_Old_Man": 0xcb0e,
"Option_Old_Man_Lying": 0xcb11,
"Option_Route3_Guard_A": 0xcb17,
"Option_Trashed_House_Guard_A": 0xcb20,
"Option_Trashed_House_Guard_B": 0xcb26,
"Option_Boulders": 0xcdb7,
"Option_Rock_Tunnel_Extra_Items": 0xcdc0,
"Wild_Route1": 0xd13b,
"Wild_Route2": 0xd151,
"Wild_Route22": 0xd167,
"Wild_ViridianForest": 0xd17d,
"Wild_Route3": 0xd193,
"Wild_MtMoon1F": 0xd1a9,
"Wild_MtMoonB1F": 0xd1bf,
"Wild_MtMoonB2F": 0xd1d5,
"Wild_Route4": 0xd1eb,
"Wild_Route24": 0xd201,
"Wild_Route25": 0xd217,
"Wild_Route9": 0xd22d,
"Wild_Route5": 0xd243,
"Wild_Route6": 0xd259,
"Wild_Route11": 0xd26f,
"Wild_RockTunnel1F": 0xd285,
"Wild_RockTunnelB1F": 0xd29b,
"Wild_Route10": 0xd2b1,
"Wild_Route12": 0xd2c7,
"Wild_Route8": 0xd2dd,
"Wild_Route7": 0xd2f3,
"Wild_PokemonTower3F": 0xd30d,
"Wild_PokemonTower4F": 0xd323,
"Wild_PokemonTower5F": 0xd339,
"Wild_PokemonTower6F": 0xd34f,
"Wild_PokemonTower7F": 0xd365,
"Wild_Route13": 0xd37b,
"Wild_Route14": 0xd391,
"Wild_Route15": 0xd3a7,
"Wild_Route16": 0xd3bd,
"Wild_Route17": 0xd3d3,
"Wild_Route18": 0xd3e9,
"Wild_SafariZoneCenter": 0xd3ff,
"Wild_SafariZoneEast": 0xd415,
"Wild_SafariZoneNorth": 0xd42b,
"Wild_SafariZoneWest": 0xd441,
"Wild_SeaRoutes": 0xd458,
"Wild_SeafoamIslands1F": 0xd46d,
"Wild_SeafoamIslandsB1F": 0xd483,
"Wild_SeafoamIslandsB2F": 0xd499,
"Wild_SeafoamIslandsB3F": 0xd4af,
"Wild_SeafoamIslandsB4F": 0xd4c5,
"Wild_PokemonMansion1F": 0xd4db,
"Wild_PokemonMansion2F": 0xd4f1,
"Wild_PokemonMansion3F": 0xd507,
"Wild_PokemonMansionB1F": 0xd51d,
"Wild_Route21": 0xd533,
"Wild_Surf_Route21": 0xd548,
"Wild_CeruleanCave1F": 0xd55d,
"Wild_CeruleanCave2F": 0xd573,
"Wild_CeruleanCaveB1F": 0xd589,
"Wild_PowerPlant": 0xd59f,
"Wild_Route23": 0xd5b5,
"Wild_VictoryRoad2F": 0xd5cb,
"Wild_VictoryRoad3F": 0xd5e1,
"Wild_VictoryRoad1F": 0xd5f7,
"Wild_DiglettsCave": 0xd60d,
"Ghost_Battle5": 0xd781,
"HM_Surf_Badge_a": 0xda73,
"HM_Surf_Badge_b": 0xda78,
"Option_Fix_Combat_Bugs_Heal_Stat_Modifiers": 0xdcc2,
"Option_Silph_Scope_Skip": 0xe207,
"Wild_Old_Rod": 0xe382,
"Wild_Good_Rod": 0xe3af,
"Option_Fix_Combat_Bugs_PP_Restore": 0xe541,
"Option_Reusable_TMs": 0xe675,
"Wild_Super_Rod_A": 0xeaa9,
"Wild_Super_Rod_B": 0xeaae,
"Wild_Super_Rod_C": 0xeab3,
"Wild_Super_Rod_D": 0xeaba,
"Wild_Super_Rod_E": 0xeabf,
"Wild_Super_Rod_F": 0xeac4,
"Wild_Super_Rod_G": 0xeacd,
"Wild_Super_Rod_H": 0xead6,
"Wild_Super_Rod_I": 0xeadf,
"Wild_Super_Rod_J": 0xeae8,
"Starting_Money_High": 0xf9aa,
"Starting_Money_Middle": 0xf9ad,
"Starting_Money_Low": 0xf9b0,
"Option_Pokedex_Seen": 0xf9cb,
"Title_Seed": 0x5f22,
"Title_Slot_Name": 0x5f42,
"PC_Item": 0x6310,
"PC_Item_Quantity": 0x6315,
"Fly_Location": 0x6323,
"Skip_Player_Name": 0x633c,
"Skip_Rival_Name": 0x634a,
"Pallet_Fly_Coords": 0x6675,
"Option_Old_Man": 0xcb0b,
"Option_Old_Man_Lying": 0xcb0e,
"Option_Route3_Guard_A": 0xcb14,
"Option_Trashed_House_Guard_A": 0xcb1d,
"Option_Trashed_House_Guard_B": 0xcb23,
"Option_Boulders": 0xcdb4,
"Option_Rock_Tunnel_Extra_Items": 0xcdbd,
"Wild_Route1": 0xd138,
"Wild_Route2": 0xd14e,
"Wild_Route22": 0xd164,
"Wild_ViridianForest": 0xd17a,
"Wild_Route3": 0xd190,
"Wild_MtMoon1F": 0xd1a6,
"Wild_MtMoonB1F": 0xd1bc,
"Wild_MtMoonB2F": 0xd1d2,
"Wild_Route4": 0xd1e8,
"Wild_Route24": 0xd1fe,
"Wild_Route25": 0xd214,
"Wild_Route9": 0xd22a,
"Wild_Route5": 0xd240,
"Wild_Route6": 0xd256,
"Wild_Route11": 0xd26c,
"Wild_RockTunnel1F": 0xd282,
"Wild_RockTunnelB1F": 0xd298,
"Wild_Route10": 0xd2ae,
"Wild_Route12": 0xd2c4,
"Wild_Route8": 0xd2da,
"Wild_Route7": 0xd2f0,
"Wild_PokemonTower3F": 0xd30a,
"Wild_PokemonTower4F": 0xd320,
"Wild_PokemonTower5F": 0xd336,
"Wild_PokemonTower6F": 0xd34c,
"Wild_PokemonTower7F": 0xd362,
"Wild_Route13": 0xd378,
"Wild_Route14": 0xd38e,
"Wild_Route15": 0xd3a4,
"Wild_Route16": 0xd3ba,
"Wild_Route17": 0xd3d0,
"Wild_Route18": 0xd3e6,
"Wild_SafariZoneCenter": 0xd3fc,
"Wild_SafariZoneEast": 0xd412,
"Wild_SafariZoneNorth": 0xd428,
"Wild_SafariZoneWest": 0xd43e,
"Wild_SeaRoutes": 0xd455,
"Wild_SeafoamIslands1F": 0xd46a,
"Wild_SeafoamIslandsB1F": 0xd480,
"Wild_SeafoamIslandsB2F": 0xd496,
"Wild_SeafoamIslandsB3F": 0xd4ac,
"Wild_SeafoamIslandsB4F": 0xd4c2,
"Wild_PokemonMansion1F": 0xd4d8,
"Wild_PokemonMansion2F": 0xd4ee,
"Wild_PokemonMansion3F": 0xd504,
"Wild_PokemonMansionB1F": 0xd51a,
"Wild_Route21": 0xd530,
"Wild_Surf_Route21": 0xd545,
"Wild_CeruleanCave1F": 0xd55a,
"Wild_CeruleanCave2F": 0xd570,
"Wild_CeruleanCaveB1F": 0xd586,
"Wild_PowerPlant": 0xd59c,
"Wild_Route23": 0xd5b2,
"Wild_VictoryRoad2F": 0xd5c8,
"Wild_VictoryRoad3F": 0xd5de,
"Wild_VictoryRoad1F": 0xd5f4,
"Wild_DiglettsCave": 0xd60a,
"Ghost_Battle5": 0xd77e,
"HM_Surf_Badge_a": 0xda70,
"HM_Surf_Badge_b": 0xda75,
"Option_Fix_Combat_Bugs_Heal_Stat_Modifiers": 0xdcbf,
"Option_Silph_Scope_Skip": 0xe204,
"Wild_Old_Rod": 0xe37f,
"Wild_Good_Rod": 0xe3ac,
"Option_Fix_Combat_Bugs_PP_Restore": 0xe53e,
"Option_Reusable_TMs": 0xe672,
"Wild_Super_Rod_A": 0xeaa6,
"Wild_Super_Rod_B": 0xeaab,
"Wild_Super_Rod_C": 0xeab0,
"Wild_Super_Rod_D": 0xeab7,
"Wild_Super_Rod_E": 0xeabc,
"Wild_Super_Rod_F": 0xeac1,
"Wild_Super_Rod_G": 0xeaca,
"Wild_Super_Rod_H": 0xead3,
"Wild_Super_Rod_I": 0xeadc,
"Wild_Super_Rod_J": 0xeae5,
"Starting_Money_High": 0xf9a7,
"Starting_Money_Middle": 0xf9aa,
"Starting_Money_Low": 0xf9ad,
"Option_Pokedex_Seen": 0xf9c8,
"HM_Fly_Badge_a": 0x13182,
"HM_Fly_Badge_b": 0x13187,
"HM_Cut_Badge_a": 0x131b8,
@@ -1164,22 +1164,22 @@ rom_addresses = {
"Prize_Mon_E": 0x52944,
"Prize_Mon_F": 0x52946,
"Start_Inventory": 0x52a7b,
"Map_Fly_Location": 0x52c6f,
"Reset_A": 0x52d1b,
"Reset_B": 0x52d47,
"Reset_C": 0x52d73,
"Reset_D": 0x52d9f,
"Reset_E": 0x52dcb,
"Reset_F": 0x52df7,
"Reset_G": 0x52e23,
"Reset_H": 0x52e4f,
"Reset_I": 0x52e7b,
"Reset_J": 0x52ea7,
"Reset_K": 0x52ed3,
"Reset_L": 0x52eff,
"Reset_M": 0x52f2b,
"Reset_N": 0x52f57,
"Reset_O": 0x52f83,
"Map_Fly_Location": 0x52c75,
"Reset_A": 0x52d21,
"Reset_B": 0x52d4d,
"Reset_C": 0x52d79,
"Reset_D": 0x52da5,
"Reset_E": 0x52dd1,
"Reset_F": 0x52dfd,
"Reset_G": 0x52e29,
"Reset_H": 0x52e55,
"Reset_I": 0x52e81,
"Reset_J": 0x52ead,
"Reset_K": 0x52ed9,
"Reset_L": 0x52f05,
"Reset_M": 0x52f31,
"Reset_N": 0x52f5d,
"Reset_O": 0x52f89,
"Warps_Route2": 0x54026,
"Missable_Route_2_Item_1": 0x5404a,
"Missable_Route_2_Item_2": 0x54051,