mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 20:21:32 -06:00
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:
@@ -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")
|
||||
|
||||
Binary file not shown.
Binary file not shown.
277
worlds/pokemon_rb/client.py
Normal file
277
worlds/pokemon_rb/client.py
Normal 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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user