mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-07 15:13:52 -08:00
* Add the world * doc update * docs * Fix Blast/Missile not clearing Reflect * Update worlds/earthbound/__init__.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/__init__.py remove unused import Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/__init__.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/modules/dungeon_er.py make bool optional Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/modules/boss_shuffle.py typing update Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/modules/boss_shuffle.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Filter events out of item name to id * we call it a glorp * Update worlds/earthbound/Regions.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/__init__.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/Items.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Update worlds/earthbound/Regions.py Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> * Fix missing optional import * hint stuff * -Fix Apple Kid text being wrong -Fix Slimy Pile text being wrong * -Fix some sprite corruption if PSI was used when an enemy loaded another enemy -Fixed a visible artifact tile during some cutscenes * Update ver * Update docs * Fix some money scripting issues * Add argument to PSI fakeout attack * Updated monkey caves shop description * Remove closing markdown from doc * Add new flavors * Make flavors actually work * Update platforms * Fix common gear getting duplicated * Split region initialization * Condense checks for start inventory + some other junk * Fix some item groups - change receiver phone to warp pad * wow that one was really bad :glorp: * blah * Fix cutoff option text * switch start inventory concatenation to itertools * Fix sky runner scripting bug - added some new comm suggestions * Fix crash when generating with spoiler_only * Fix happy-happy teleport not unlocking after beating carpainter * Hint man hints can now use CreateHint packets to create hints in other games * Adjust some filler rarity * Update world to use CreateHints and deprecate old method * Fix epilogue skip being offset * Rearrange a couple regions * Fix tendapants getting deleted in battle * update doc * i got scared and forgot i had multiple none checks and am worried about this triggering but tested and it works * Fix mostly typing errors from silvris * More type checks * More typing * Typema * Type * Fix enemy levels overwriting music * Fix gihugic blunder * Fix Lumine Hall enabling OSS * del world * Rel 4.2.7 * Remove some debug logs * Fix vanilla bug with weird ambush detection * Fix Starman Junior having an unscaled Freeze * Change shop scaling * Fix shops using the wrong thankful script * Update some bosses in boss shuffle * Loc group adjustment * Update some boss shuffle stuff | Fix Enemizer attacks getting overwritten by Shuffle data | Fix flunkies not updating and still being used with enemizer * Get rid of some debug stuff * Get boss shuffle running, dont merge * Fix json and get boss shuffle no plando back up * Fix Magicant Boost not initializing to Ness if party count = 4 * Fix belch shop using wrong logic * Don't re-send goal status * EBitem * remove : * idk if this is whatvi wanted * All client messagesnow only send when relevant instead of constantly * Patch up the rest of boss plando * Fix Giygas being not excluded from enemizer * Fix epilogue again * adjust the sphere scaling name * add the things * Fix Ness being placed onto monotoli when monotoli was in sea of eden * Fix prefill properly * Fix boss shuffle on vanilla slots. * rename this, apparently * Update archipelago.json --------- Co-authored-by: Duck <31627079+duckboycool@users.noreply.github.com> Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
849 lines
43 KiB
Python
849 lines
43 KiB
Python
import hashlib
|
|
import os
|
|
import Utils
|
|
import typing
|
|
import struct
|
|
import settings
|
|
from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes, APPatchExtension
|
|
from .game_data import local_data
|
|
from .game_data.battle_bg_data import battle_bg_bpp
|
|
from .modules.psi_shuffle import write_psi
|
|
from .game_data.text_data import barf_text, text_encoder
|
|
from .modules.flavor_data import flavor_data, vanilla_flavor_pointers
|
|
from .modules.hint_data import parse_hint_data
|
|
from .modules.enemy_data import scale_enemies
|
|
from .modules.area_scaling import calculate_scaling
|
|
from .modules.boss_shuffle import write_bosses
|
|
from .modules.equipamizer import randomize_armor, randomize_weapons
|
|
from .modules.music_rando import music_randomizer
|
|
from .modules.palette_shuffle import randomize_psi_palettes, map_palette_shuffle
|
|
from .modules.shopsanity import write_shop_checks
|
|
from .modules.enemy_shuffler import apply_enemy_shuffle
|
|
from .modules.dungeon_er import write_dungeon_entrances
|
|
# from .modules.foodamizer import randomize_food
|
|
from .modules.enemizer.randomize_enemy_attributes import randomize_enemy_attributes
|
|
from .modules.enemizer.randomize_enemy_stats import randomize_enemy_stats
|
|
from .modules.enemizer.randomize_enemy_attacks import randomize_enemy_attacks
|
|
from .game_data.static_location_data import location_groups
|
|
from BaseClasses import ItemClassification
|
|
from typing import TYPE_CHECKING, Sequence
|
|
from logging import warning
|
|
# from .local_data import local_locations
|
|
|
|
if TYPE_CHECKING:
|
|
from . import EarthBoundWorld
|
|
|
|
item_id_table = local_data.item_id_table
|
|
location_dialogue = local_data.location_dialogue
|
|
present_locations = local_data.present_locations
|
|
psi_locations = local_data.psi_locations
|
|
npc_locations = local_data.npc_locations
|
|
character_locations = local_data.character_locations
|
|
special_name_table = local_data.special_name_table
|
|
item_space_checks = local_data.item_space_checks
|
|
local_present_types = local_data.local_present_types
|
|
present_text_pointers = local_data.present_text_pointers
|
|
psi_item_table = local_data.psi_item_table
|
|
character_item_table = local_data.character_item_table
|
|
party_id_nums = local_data.party_id_nums
|
|
starting_psi_table = local_data.starting_psi_table
|
|
badge_names = local_data.badge_names
|
|
world_version = local_data.world_version
|
|
protection_checks = local_data.protection_checks
|
|
protection_text = local_data.protection_text
|
|
nonlocal_present_types = local_data.nonlocal_present_types
|
|
ap_text_pntrs = local_data.ap_text_pntrs
|
|
money_item_table = local_data.money_item_table
|
|
|
|
valid_hashes = ["a864b2e5c141d2dec1c4cbed75a42a85", # Cartridge
|
|
"6d71ccc8e2afda15d011348291afdf4f"] # VC
|
|
|
|
|
|
class LocalRom(object):
|
|
|
|
def __init__(self, file: bytes, name: str | None = None) -> None:
|
|
self.file = bytearray(file)
|
|
self.name = name
|
|
|
|
def read_byte(self, offset: int) -> int:
|
|
return self.file[offset]
|
|
|
|
def read_bytes(self, offset: int, length: int) -> bytes:
|
|
return self.file[offset:offset + length]
|
|
|
|
def write_bytes(self, offset: int, values: Sequence[int]) -> None:
|
|
self.file[offset:offset + len(values)] = values
|
|
|
|
def get_bytes(self) -> bytes:
|
|
return bytes(self.file)
|
|
|
|
|
|
def patch_rom(world: "EarthBoundWorld", rom: LocalRom, player: int) -> None:
|
|
rom.copy_bytes(0x1578DD, 0x3E, 0x34A060) # Threed/Saturn teleport move
|
|
rom.copy_bytes(0x15791B, 0xF8, 0x157959)
|
|
|
|
rom.copy_bytes(0x34A000, 0x1F, 0x1578DD)
|
|
rom.copy_bytes(0x34A020, 0x1F, 0x15793A)
|
|
rom.copy_bytes(0x34A040, 0x1F, 0x157A51)
|
|
rom.copy_bytes(0x34A060, 0x3E, 0x1578FC)
|
|
rom.copy_bytes(0x15ED4B, 0x06, 0x15F1FB)
|
|
rom.copy_bytes(0x1A7FA7, 0xBF, 0x389900)
|
|
|
|
starting_area_coordinates = {
|
|
0: [0x50, 0x04, 0xB5, 0x1F], # North Onett
|
|
1: [0x52, 0x06, 0x4C, 0x1F], # Onett
|
|
2: [0xEF, 0x22, 0x41, 0x1F], # Twoson
|
|
3: [0x53, 0x06, 0x85, 0x1D], # Happy Happy
|
|
4: [0x55, 0x24, 0x69, 0x1D], # Threed
|
|
5: [0x60, 0x1D, 0x30, 0x01], # Saturn Valley
|
|
6: [0xAB, 0x10, 0xF3, 0x09], # Fourside
|
|
7: [0xE3, 0x09, 0xA3, 0x1D], # Winters
|
|
8: [0xCB, 0x24, 0x7B, 0x1E], # Summers
|
|
9: [0xD0, 0x1E, 0x31, 0x1D], # Dalaam
|
|
10: [0xC7, 0x1F, 0x37, 0x19], # Scaraba
|
|
11: [0xDD, 0x1B, 0xB7, 0x17], # Deep Darkness
|
|
12: [0xD0, 0x25, 0x47, 0x18], # Tenda Village
|
|
13: [0x9C, 0x00, 0x84, 0x17], # Lost Underworld
|
|
14: [0x4B, 0x11, 0xAD, 0x18] # Magicant
|
|
}
|
|
|
|
starting_levels = {
|
|
"Ness": 0x15F5FB,
|
|
"Paula": 0x15F60F,
|
|
"Jeff": 0x15F623,
|
|
"Poo": 0x15F637
|
|
}
|
|
|
|
atm_card_slots = {
|
|
"Ness": 0x15F5FF,
|
|
"Paula": 0x15F613,
|
|
"Jeff": 0x15F629,
|
|
"Poo": 0x15F63B
|
|
}
|
|
|
|
starting_weapon = {
|
|
"Ness": [0x15F600, 0x12],
|
|
"Paula": [0x15F615, 0x1C],
|
|
"Jeff": [0x15F62A, 0x24]
|
|
}
|
|
|
|
teleport_learnlevel = {
|
|
"Ness": [0x158D53, 0x158D62],
|
|
"Paula": [0x158D54, 0x158D63],
|
|
"Poo": [0x158D55, 0x158D64]
|
|
}
|
|
world.start_items = []
|
|
world.handled_locations = []
|
|
|
|
for item in world.multiworld.precollected_items[world.player]:
|
|
world.start_items.append(item.name)
|
|
|
|
if world.options.random_start_location:
|
|
rom.write_bytes(0x0F96C2, bytearray([0x69, 0x00]))
|
|
rom.write_bytes(0x0F9618, bytearray([0x69, 0x00]))
|
|
rom.write_bytes(0x0F9629, bytearray([0x69, 0x00])) # Block Northern Onett
|
|
else:
|
|
rom.write_bytes(0x00B66A, bytearray([0x06])) # Fix starting direction
|
|
|
|
rom.write_bytes(0x01FE9B, bytearray(starting_area_coordinates[world.start_location][0:2]))
|
|
rom.write_bytes(0x01FE9E, bytearray(starting_area_coordinates[world.start_location][2:4])) # Start position
|
|
|
|
rom.write_bytes(0x01FE91, bytearray(starting_area_coordinates[world.start_location][0:2]))
|
|
rom.write_bytes(0x01FE8B, bytearray(starting_area_coordinates[world.start_location][2:4])) # Respawn position
|
|
|
|
if world.options.skip_epilogue:
|
|
rom.write_bytes(0x09C4D4, struct.pack("I", 0xEEA437))
|
|
|
|
if world.starting_character == "Poo":
|
|
rom.write_bytes(starting_levels[world.starting_character], bytearray([0x06]))
|
|
else:
|
|
rom.write_bytes(starting_levels[world.starting_character], bytearray([0x03]))
|
|
rom.write_bytes(atm_card_slots[world.starting_character], bytearray([0xB1]))
|
|
if world.starting_character != "Ness":
|
|
rom.write_bytes(atm_card_slots["Ness"], bytearray([0x58]))
|
|
if world.starting_character != "Poo":
|
|
rom.write_bytes(starting_weapon[world.starting_character][0], bytearray([starting_weapon[world.starting_character][1]]))
|
|
|
|
if world.starting_character != "Jeff":
|
|
for i in range(2):
|
|
rom.write_bytes(teleport_learnlevel[world.starting_character][i - 1], bytearray([0x01]))
|
|
else:
|
|
rom.write_bytes(0x15F62B, bytearray([0xB5]))
|
|
|
|
if world.options.alternate_sanctuary_goal:
|
|
rom.write_bytes(0x04FD72, bytearray([world.options.sanctuaries_required.value + 2]))
|
|
else:
|
|
rom.write_bytes(0x04FD72, bytearray([0xFF]))
|
|
|
|
if not world.options.giygas_required:
|
|
rom.write_bytes(0x2E9C29, bytearray([0x10, 0xA5]))
|
|
|
|
if world.options.magicant_mode == 2:
|
|
rom.write_bytes(0x04FD71, bytearray([world.options.sanctuaries_required.value + 1]))
|
|
rom.write_bytes(0x2EA26A, bytearray([0x0A, 0x10, 0xA5, 0xEE])) # Alt goal magicant sets the credits
|
|
elif world.options.magicant_mode == 1:
|
|
rom.write_bytes(0x2E9C29, bytearray([0x00, 0xA5]))
|
|
if world.options.giygas_required:
|
|
rom.write_bytes(0x2EA26A, bytearray([0x08, 0xD9, 0x9B, 0xEE])) # Give stat boost if magicant + giygas required
|
|
else:
|
|
rom.write_bytes(0x2EA26A, bytearray([0x0A, 0x10, 0xA5, 0xEE])) # If no giygas, set credits
|
|
elif world.options.magicant_mode == 3:
|
|
rom.write_bytes(0x2EA26A, bytearray([0x08, 0x0F, 0x9C, 0xEE])) # Give only stat boost if set to boost
|
|
|
|
rom.write_bytes(0x04FD74, bytearray([world.options.death_link.value]))
|
|
rom.write_bytes(0x04FD75, bytearray([world.options.death_link_mode.value]))
|
|
rom.write_bytes(0x04FD76, bytearray([world.options.remote_items.value]))
|
|
rom.write_bytes(0x04FD78, bytearray([world.options.energy_link.value]))
|
|
|
|
if world.options.death_link_mode != 1:
|
|
rom.write_bytes(0x2FFDFE, bytearray([0x80])) # Mercy healing
|
|
rom.write_bytes(0x2FFE30, bytearray([0x80])) # Mercy text
|
|
rom.write_bytes(0x2FFE59, bytearray([0x80])) # Mercy revive
|
|
# IF YOU ADD ASM, CHANGE THESE OR THE GAME WILL CRASH
|
|
|
|
if world.options.monkey_caves_mode == 2:
|
|
rom.write_bytes(0x062B87, bytearray([0x0A, 0x28, 0xCA, 0xEE]))
|
|
elif world.options.monkey_caves_mode == 3:
|
|
rom.write_bytes(0x0F1388, bytearray([0x03, 0xCA, 0xEE]))
|
|
|
|
if world.options.no_free_sanctuaries:
|
|
rom.write_bytes(0x0F09F2, bytearray([0x15, 0x84])) # Lock Lilliput steps with flag $0415
|
|
rom.write_bytes(0x0F09EE, struct.pack("I", 0xEEF790)) # Lilliput door script
|
|
|
|
rom.write_bytes(0x0F23D2, bytearray([0x16, 0x84])) # Lock Fire Spring with flag $0146
|
|
rom.write_bytes(0x0F23CE, struct.pack("I", 0xEEF946)) # Fire Spring door script
|
|
|
|
rom.write_bytes(0x04FD70, bytearray([world.options.sanctuaries_required.value]))
|
|
shop_checks = []
|
|
|
|
for location in world.multiworld.get_locations(player):
|
|
if location.address:
|
|
receiver_name = world.multiworld.get_player_name(location.item.player)
|
|
name = location.name
|
|
if world.options.remote_items:
|
|
item = "Remote Item"
|
|
else:
|
|
item = location.item.name
|
|
item_name_loc = (((location.address - 0xEB0000) * 128) + 0x3F0000)
|
|
# todo; replace with the encoder function
|
|
item_text = text_encoder(location.item.name, 128)
|
|
item_text.extend([0x00])
|
|
player_name_loc = (((location.address - 0xEB0000) * 48) + 0x3F8000)
|
|
player_text = text_encoder(receiver_name, 48)
|
|
# Locations over this address are Shopsanity locations and handled in the shopsanity module
|
|
if location.address < 0xEB1000:
|
|
rom.write_bytes(item_name_loc, bytearray(item_text))
|
|
rom.write_bytes(player_name_loc, bytearray(player_text))
|
|
|
|
if item not in item_id_table or location.item.player != location.player:
|
|
item_id = 0xAD
|
|
else:
|
|
item_id = item_id_table[item]
|
|
|
|
if name in location_dialogue:
|
|
for i in range(len(location_dialogue[name])):
|
|
if location.item.player != location.player or item == "Remote Item":
|
|
rom.write_bytes(location_dialogue[name][i] - 1, bytearray([0x17, location.address - 0xEB0000]))
|
|
elif item in item_id_table:
|
|
rom.write_bytes(location_dialogue[name][i], bytearray([item_id]))
|
|
elif item in psi_item_table or item in character_item_table:
|
|
rom.write_bytes(location_dialogue[name][i] - 1, bytearray([0x16, special_name_table[item][0]]))
|
|
elif item in money_item_table:
|
|
rom.write_bytes(location_dialogue[name][i] - 1, bytearray([0x16, (0x16 + list(money_item_table).index(item))]))
|
|
|
|
if name in present_locations:
|
|
world.handled_locations.append(name)
|
|
if item == "Nothing": # I can change this to "In nothing_table" later todo: make it so nonlocal items do not follow this table
|
|
rom.write_bytes(present_locations[name], bytearray([0x00, 0x00, 0x01]))
|
|
elif location.item.player != location.player or item == "Remote Item":
|
|
rom.write_bytes(present_locations[name], bytearray([item_id, 0x00, 0x00, (location.address - 0xEB0000)]))
|
|
elif item in item_id_table:
|
|
rom.write_bytes(present_locations[name], bytearray([item_id, 0x00]))
|
|
elif item in psi_item_table:
|
|
rom.write_bytes(present_locations[name], bytearray([psi_item_table[item], 0x00, 0x02]))
|
|
elif item in character_item_table:
|
|
rom.write_bytes(present_locations[name], bytearray([character_item_table[item][0], 0x00, 0x03]))
|
|
elif item in money_item_table:
|
|
rom.write_bytes(present_locations[name], struct.pack("H", money_item_table[item]))
|
|
rom.write_bytes(present_locations[name] + 2, bytearray([0x01]))
|
|
|
|
if name in npc_locations:
|
|
world.handled_locations.append(name)
|
|
for i in range(len(npc_locations[name])):
|
|
if item in item_id_table or location.item.player != location.player or item == "Remote Item":
|
|
rom.write_bytes(npc_locations[name][i], bytearray([item_id]))
|
|
elif item in psi_item_table or item in character_item_table:
|
|
rom.write_bytes(npc_locations[name][i] - 3, bytearray([0x0E, 0x00, 0x0E, (special_name_table[item][0] + 1)]))
|
|
rom.write_bytes(npc_locations[name][i] + 2, bytearray([0xA5, 0xAA, 0xEE]))
|
|
elif item in money_item_table:
|
|
rom.write_bytes(npc_locations[name][i] - 3, bytearray([0x1D, 0x25]))
|
|
rom.write_bytes(npc_locations[name][i] - 1, struct.pack("H", money_item_table[item]))
|
|
rom.write_bytes(npc_locations[name][i] + 2, bytearray([0x00, 0xF0, 0xF3]))
|
|
|
|
if name in psi_locations:
|
|
world.handled_locations.append(name)
|
|
if item in special_name_table and location.item.player == location.player and item != "Remote Item":
|
|
rom.write_bytes(psi_locations[name][0], special_name_table[item][1].to_bytes(3, byteorder="little"))
|
|
rom.write_bytes(psi_locations[name][0] + 4, bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00]))
|
|
elif item in money_item_table and location.item.player == location.player:
|
|
rom.write_bytes(psi_locations[name][0] - 1, bytearray([0x1D, 0x25]))
|
|
rom.write_bytes(psi_locations[name][0] + 1, struct.pack("H", money_item_table[item]))
|
|
rom.write_bytes(psi_locations[name][0] + 3, bytearray([0x08, 0x26, 0xF0, 0xF3, 0x00, 0x03, 0x00]))
|
|
else:
|
|
rom.write_bytes(psi_locations[name][0], bytearray(psi_locations[name][1:4]))
|
|
rom.write_bytes(psi_locations[name][4], bytearray([item_id]))
|
|
|
|
if name in character_locations:
|
|
world.handled_locations.append(name)
|
|
if item in character_item_table and location.item.player == location.player and item != "Remote Item":
|
|
rom.write_bytes(character_locations[name][0], special_name_table[item][1].to_bytes(3, byteorder="little"))
|
|
if name == "Snow Wood - Bedroom": # Use lying down sprites for the bedroom check
|
|
rom.write_bytes(character_locations[name][1], struct.pack("H", character_item_table[item][2]))
|
|
rom.write_bytes(0x0FB0D8, bytearray([0x06]))
|
|
else:
|
|
rom.write_bytes(character_locations[name][1], struct.pack("H", character_item_table[item][1]))
|
|
elif item in psi_item_table and location.item.player == location.player:
|
|
rom.write_bytes(character_locations[name][0], (special_name_table[item][1] + 1).to_bytes(3, byteorder="little"))
|
|
rom.write_bytes(character_locations[name][1], bytearray([0x62]))
|
|
rom.write_bytes(character_locations[name][2], bytearray([0x70, 0xF9, 0xD5]))
|
|
elif item in money_item_table and location.item.player == location.player:
|
|
rom.write_bytes(character_locations[name][2] - 1, bytearray([0x1D, 0x25]))
|
|
rom.write_bytes(character_locations[name][2] + 1, struct.pack("H", money_item_table[item]))
|
|
rom.write_bytes(character_locations[name][0] - 2, bytearray([0x01]))
|
|
rom.write_bytes(character_locations[name][0], bytearray([0x4B, 0xF0, 0xF3]))
|
|
rom.write_bytes(character_locations[name][1], bytearray([0x97]))
|
|
else:
|
|
rom.write_bytes(character_locations[name][0], bytearray(character_locations[name][4:7]))
|
|
if location.item.name in ["Ness", "Paula", "Jeff", "Poo"]:
|
|
rom.write_bytes(character_locations[name][1], bytearray([character_item_table[location.item.name][1]]))
|
|
else:
|
|
rom.write_bytes(character_locations[name][1], bytearray([0x97]))
|
|
rom.write_bytes(character_locations[name][2], bytearray([0x18, 0xF9, 0xD5]))
|
|
rom.write_bytes(character_locations[name][3], bytearray([item_id]))
|
|
if name == "Deep Darkness - Barf Character":
|
|
if item in character_item_table:
|
|
rom.write_bytes(0x2EA0E2, bytearray(barf_text[item][0:3]))
|
|
rom.write_bytes(0x2EA0E8, bytearray(barf_text[item][3:6]))
|
|
elif item in psi_item_table and location.item.player == location.player:
|
|
rom.write_bytes(0x2EA0E2, bytearray([0x98, 0xC3, 0xEE]))
|
|
rom.write_bytes(0x2EA0E8, bytearray([0xF7, 0xC4, 0xEE]))
|
|
else:
|
|
rom.write_bytes(0x2EA0E2, bytearray([0x6A, 0xC3, 0xEE]))
|
|
rom.write_bytes(0x2EA0E8, bytearray([0xB4, 0xC4, 0xEE]))
|
|
|
|
if name == "Poo - Starting Item":
|
|
world.handled_locations.append(name)
|
|
if item in item_id_table and location.item.player == location.player and item != "Remote Item":
|
|
rom.write_bytes(0x15F63C, bytearray([item_id]))
|
|
else:
|
|
rom.write_bytes(0x15F63C, bytearray([0x00])) # Don't give anything if the item doesn't have a tangible ID
|
|
|
|
if item in special_name_table and location.item.player == location.player: # Apply a special script if teleport or character
|
|
rom.write_bytes(0x15F765, special_name_table[item][1].to_bytes(3, byteorder="little")) # This might be offset, check if it is
|
|
rom.write_bytes(0x2EC618, bytearray([(special_name_table[item][0] + 1)]))
|
|
rom.write_bytes(0x2EC61A, bytearray([0xA5, 0xAA, 0xEE]))
|
|
rom.write_bytes(0x2EC613, bytearray([0x03, 0x01]))
|
|
|
|
if item in money_item_table and location.item.player == location.player:
|
|
rom.write_bytes(0x15F764, bytearray([0x1D, 0x08]))
|
|
rom.write_bytes(0x15F766, struct.pack("H", money_item_table[item]))
|
|
rom.write_bytes(0x15F768, bytearray([0x01]))
|
|
|
|
if location.address >= 0xEB1000:
|
|
world.handled_locations.append(name)
|
|
shop_checks.append(location)
|
|
|
|
if name not in world.handled_locations:
|
|
warning(f"{name} not placed in {world.multiworld.get_player_name(world.player)}'s EarthBound world. Something went wrong here.")
|
|
|
|
if name in item_space_checks:
|
|
if item not in item_id_table or location.item.player != location.player:
|
|
if len(item_space_checks[name]) == 4:
|
|
rom.write_bytes(item_space_checks[name][0], bytearray(item_space_checks[name][1:4]))
|
|
else:
|
|
rom.write_bytes(item_space_checks[name][0], bytearray(item_space_checks[name][1:4]))
|
|
rom.write_bytes(item_space_checks[name][4], bytearray(item_space_checks[name][5:8]))
|
|
|
|
if name in present_locations and "Lost Underworld" not in name and world.options.presents_match_contents:
|
|
if ItemClassification.trap in location.item.classification:
|
|
world.present_type = "trap"
|
|
elif ItemClassification.progression in location.item.classification:
|
|
world.present_type = "progression"
|
|
elif ItemClassification.useful in location.item.classification:
|
|
world.present_type = "useful"
|
|
else:
|
|
world.present_type = "filler"
|
|
|
|
if location.item.player == world.player or world.options.nonlocal_items_use_local_presents:
|
|
rom.write_bytes(present_locations[name] - 12, bytearray(local_present_types[world.present_type]))
|
|
if name != "Threed - Boogey Tent Trashcan":
|
|
rom.write_bytes(present_locations[name] - 4, bytearray(present_text_pointers[world.present_type]))
|
|
else:
|
|
rom.write_bytes(present_locations[name] - 12, bytearray(nonlocal_present_types[world.present_type]))
|
|
if name != "Threed - Boogey Tent Trashcan":
|
|
if world.present_type == "progression":
|
|
rom.write_bytes(present_locations[name] - 4, struct.pack("I", world.random.choice(ap_text_pntrs)))
|
|
elif world.present_type == "trap":
|
|
rom.write_bytes(present_locations[name] - 4, bytearray([0x8D, 0xce, 0xee]))
|
|
else:
|
|
rom.write_bytes(present_locations[name] - 4, bytearray([0xc1, 0xcd, 0xee]))
|
|
|
|
location = world.multiworld.get_location("Twoson - Bike Shop Rental", world.player)
|
|
if location.item.name in item_id_table:
|
|
item_id = item_id_table[location.item.name]
|
|
else:
|
|
item_id = 0xAD
|
|
|
|
rom.write_bytes(0x0800C4, bytearray([item_id])) # Bike shop
|
|
rom.write_bytes(0x0802EA, bytearray([item_id]))
|
|
|
|
rom.write_bytes(0x2EA05C, bytearray([item_id_table[world.slime_pile_wanted_item]]))
|
|
rom.write_bytes(0x2F61F6, bytearray([item_id_table[world.slime_pile_wanted_item]]))
|
|
|
|
hintable_locations = [
|
|
location for location in world.multiworld.get_locations()
|
|
if location.player == world.player or location.item.player == world.player
|
|
]
|
|
world.hint_pointer = 0x0000
|
|
world.hint_number = 0
|
|
for index, hint in enumerate(world.in_game_hint_types):
|
|
if hint == "item_at_location":
|
|
for location in hintable_locations:
|
|
if location.name == world.hinted_locations[index] and location.player == world.player:
|
|
parse_hint_data(world, location, rom, hint, index)
|
|
|
|
elif hint == "region_progression_check":
|
|
world.progression_count = 0
|
|
for location in hintable_locations:
|
|
if location.name in location_groups[world.hinted_regions[index]] and location.player == world.player:
|
|
if ItemClassification.progression in location.item.classification:
|
|
world.progression_count += 1
|
|
world.hinted_area = world.hinted_regions[index] # im doing a little sneaky
|
|
parse_hint_data(world, location, rom, hint, index)
|
|
|
|
elif hint == "hint_for_good_item" or hint == "prog_item_at_region":
|
|
hintable_locations_2 = []
|
|
for location in hintable_locations:
|
|
if location.item.name == world.hinted_items[index] and location.item.player == world.player:
|
|
hintable_locations_2.append(location)
|
|
if hintable_locations_2 == []:
|
|
# This is just failsafe behavior
|
|
warning(f"Warning: Unable to create local hint for {world.hinted_items[index]} for "
|
|
+ f"{world.multiworld.get_player_name(world.player)}'s EarthBound world."
|
|
+ " Please report this.")
|
|
location = world.random.choice(hintable_locations)
|
|
else:
|
|
location = world.random.choice(hintable_locations_2)
|
|
parse_hint_data(world, location, rom, hint, index)
|
|
|
|
elif hint == "item_in_local_region":
|
|
for location in hintable_locations:
|
|
if location.name == world.hinted_locations[index] and location.player == world.player:
|
|
parse_hint_data(world, location, rom, hint, index)
|
|
else:
|
|
location = "null"
|
|
parse_hint_data(world, location, rom, hint, index)
|
|
|
|
for location in hintable_locations:
|
|
if location.item.name == "Paula":
|
|
world.paula_region = location.parent_region
|
|
|
|
if location.item.name == "Jeff":
|
|
world.jeff_region = location.parent_region
|
|
|
|
if location.item.name == "Poo":
|
|
world.poo_region = location.parent_region
|
|
|
|
if world.options.skip_prayer_sequences:
|
|
rom.write_bytes(0x07BC96, bytearray([0x02]))
|
|
rom.write_bytes(0x07BA2C, bytearray([0x02]))
|
|
rom.write_bytes(0x07BAC7, bytearray([0x02]))
|
|
rom.write_bytes(0x07BB38, bytearray([0x02]))
|
|
rom.write_bytes(0x07BBF3, bytearray([0x02]))
|
|
rom.write_bytes(0x07BC56, bytearray([0x02]))
|
|
rom.write_bytes(0x07B9A1, bytearray([0x1f, 0xeb, 0xff, 0x02, 0x1f, 0x1f, 0xca, 0x01, 0x06, 0x1f, 0x1f, 0x72, 0x01, 0x06, 0x02])) # Clean up overworld stuff
|
|
|
|
if world.options.easy_deaths:
|
|
rom.write_bytes(0x2EBFF9, bytearray([0x0A]))
|
|
rom.write_bytes(0x04C7CE, bytearray([0x5C, 0x8A, 0xFB, 0xEF])) # Jump to code that restores the party
|
|
rom.write_bytes(0x04C7D4, bytearray([0xEA, 0xEA, 0xEA]))
|
|
# rom.write_bytes(0x04C7DA, bytearray([0xEA, 0xEA]))#Stop the game from zeroing stuff
|
|
rom.write_bytes(0x0912F2, bytearray([0x0A, 0xFE, 0xBF, 0xEE]))
|
|
rom.write_bytes(0x2EBFFE, bytearray([0x00, 0x1B, 0x04, 0x15, 0x38, 0x1F, 0x81, 0xFF, 0xFF, 0x1B, 0x04, 0x0A, 0xF7, 0x12, 0xC9])) # Hospitals = 0$
|
|
rom.write_bytes(0x04C822, bytearray([0xEA, 0xEA, 0xEA, 0xEA]))
|
|
rom.write_bytes(0x04C7F7, bytearray([0x00, 0x00])) # Stop "rounding up" the money
|
|
|
|
if world.options.magicant_mode >= 2:
|
|
rom.write_bytes(0x077629, bytearray([item_id_table[world.magicant_junk[0]]]))
|
|
rom.write_bytes(0x077614, bytearray([item_id_table[world.magicant_junk[0]]]))
|
|
rom.write_bytes(0x0FF25C, bytearray([item_id_table[world.magicant_junk[1]]]))
|
|
rom.write_bytes(0x0FF27E, bytearray([item_id_table[world.magicant_junk[2]]]))
|
|
rom.write_bytes(0x0FF28F, bytearray([item_id_table[world.magicant_junk[3]]]))
|
|
rom.write_bytes(0x0FF2A0, bytearray([item_id_table[world.magicant_junk[4]]]))
|
|
rom.write_bytes(0x0FF26D, bytearray([item_id_table[world.magicant_junk[5]]]))
|
|
|
|
rom.write_bytes(0x02EC1AA, bytearray([world.options.sanctuaries_required.value]))
|
|
if world.options.alternate_sanctuary_goal and world.options.giygas_required:
|
|
rom.write_bytes(0x02EC1E2, bytearray([0xFD, 0xC1, 0xEE]))
|
|
|
|
if world.options.magicant_mode == 1 and world.options.giygas_required: # Apple kid text
|
|
rom.write_bytes(0x2EC1D8, bytearray([0x33, 0xC2, 0xEE]))
|
|
elif world.options.magicant_mode == 2:
|
|
rom.write_bytes(0x2EC1D8, bytearray([0x6A, 0xC2, 0xEE]))
|
|
|
|
if not world.options.giygas_required:
|
|
rom.write_bytes(0x2EC164, bytearray([0xE8, 0xF0, 0xEE]))
|
|
rom.write_bytes(0x02EC1E2, bytearray([0x40, 0xC1, 0xEE]))
|
|
rom.write_bytes(0x02EC1E2, bytearray([0x40, 0xC1, 0xEE]))
|
|
|
|
flavor_address = 0x3FAF10
|
|
for i in range(4):
|
|
rom.copy_bytes(world.flavor_pointer[i], 2, 0x34B110 + (2 * i))
|
|
|
|
rom.copy_bytes(0x202008, 0x100, 0x34B000)
|
|
for i in range(4):
|
|
if world.available_flavors[i] not in ["Mint flavor", "Strawberry flavor", "Banana flavor", "Peanut flavor"]:
|
|
rom.write_bytes(flavor_address, bytearray(world.flavor_text[i]))
|
|
flavor_addr = flavor_address - 0x3F0000
|
|
flavor_addr = struct.pack("H", flavor_addr)
|
|
rom.write_bytes(world.flavor_pointer[i], flavor_addr)
|
|
rom.write_bytes(world.flavor_pointer[i] + 5, bytearray([0xFF]))
|
|
flavor_address += len(world.flavor_text[i])
|
|
rom.write_bytes(0x202008 + (0x40 * i), bytearray(flavor_data[world.available_flavors[i]]))
|
|
else:
|
|
rom.copy_bytes(vanilla_flavor_pointers[world.available_flavors[i]][1], 0x40, 0x202008 + (0x40 * i))
|
|
rom.copy_bytes(vanilla_flavor_pointers[world.available_flavors[i]][2], 2, world.flavor_pointer[i])
|
|
|
|
rom.write_bytes(0x048037, bytearray(world.lumine_text))
|
|
starting_item_address = 0
|
|
starting_psi = 0
|
|
starting_char = 0
|
|
starting_psi_types = []
|
|
starting_character_count = []
|
|
starting_inventory_pointers = {
|
|
"Ness": 0x99F3,
|
|
"Paula": 0x9A53,
|
|
"Jeff": 0x9AB4,
|
|
"Poo": 0x9B0F
|
|
}
|
|
|
|
starting_inv_amounts = {
|
|
"Ness": 0x0B,
|
|
"Paula": 0x0A,
|
|
"Jeff": 0x08,
|
|
"Poo": 0x0C
|
|
}
|
|
|
|
location = world.multiworld.get_location("Poo - Starting Item", world.player)
|
|
if world.starting_character == "Poo" and location.item.name in item_id_table and location.item.player == world.player:
|
|
starting_inventory_pointers["Poo"] = 0x9B10
|
|
starting_inv_amounts["Poo"] = 0x0B
|
|
rom.write_bytes(0x16FB66, struct.pack("H", starting_inventory_pointers[world.starting_character]))
|
|
rom.write_bytes(0x16FB68, struct.pack("H", starting_inv_amounts[world.starting_character]))
|
|
|
|
for item in world.multiworld.precollected_items[player]:
|
|
if item.name == world.starting_character: # Write the starting character
|
|
rom.write_bytes(0x00B672, bytearray([world.options.starting_character.value + 1]))
|
|
|
|
if world.options.remote_items:
|
|
continue
|
|
|
|
if item.name == "Photograph":
|
|
rom.write_bytes(0x17FEA8, bytearray([0x01]))
|
|
|
|
if item.name == "Poo" and world.multiworld.get_location("Poo - Starting Item", world.player).item.name in special_name_table and item.player == world.player:
|
|
world.multiworld.push_precollected(world.multiworld.get_location("Poo - Starting Item", world.player).item)
|
|
elif item.name == "Poo" and world.multiworld.get_location("Poo - Starting Item", world.player).item.name in money_item_table:
|
|
world.starting_money += money_item_table[world.multiworld.get_location("Poo - Starting Item", world.player).item.name]
|
|
|
|
# if item.name == "Poo" and world.multiworld.get_location("Poo - Starting Item", world.player).item.name == "Photograph":
|
|
# rom.write_bytes(0x17FEA9, bytearray([0x01]))
|
|
|
|
if item.name in ["Progressive Bat", "Progressive Fry Pan", "Progressive Gun", "Progressive Bracelet",
|
|
"Progressive Other"]:
|
|
old_item_name = item.name
|
|
item.name = world.progressive_item_groups[item.name][world.start_prog_counts[item.name]]
|
|
if world.start_prog_counts[old_item_name] != len(world.progressive_item_groups[old_item_name]) - 1:
|
|
world.start_prog_counts[old_item_name] += 1
|
|
|
|
if item.name in item_id_table:
|
|
rom.write_bytes(0x375000 + starting_item_address, bytearray([item_id_table[item.name]]))
|
|
starting_item_address += 1
|
|
elif item.name in psi_item_table:
|
|
if item.name != "Progressive Poo PSI":
|
|
if item.name not in starting_psi_types:
|
|
rom.write_bytes(0x17FC7C + starting_psi, bytearray([starting_psi_table[item.name]]))
|
|
starting_psi_types.append(item.name)
|
|
starting_psi += 1
|
|
else:
|
|
if starting_psi_types.count(item.name) < 2:
|
|
rom.write_bytes(0x17FC7C + starting_psi, bytearray([starting_psi_table[item.name]]))
|
|
starting_psi_types.append(item.name)
|
|
starting_psi += 1
|
|
elif item.name in character_item_table and item.name != "Photograph":
|
|
if item.name not in starting_character_count:
|
|
rom.write_bytes(0x17FC8D + starting_char, bytearray([party_id_nums[item.name]]))
|
|
starting_character_count.append(item.name)
|
|
starting_char += 1
|
|
elif item.name in money_item_table:
|
|
world.starting_money += money_item_table[item]
|
|
|
|
if world.options.random_battle_backgrounds:
|
|
bpp2_bgs = [bg_id for bg_id, bpp in battle_bg_bpp.items() if bpp == 2]
|
|
bpp4_bgs = [bg_id for bg_id, bpp in battle_bg_bpp.items() if bpp == 4]
|
|
for i in range(483):
|
|
world.flipped_bg = world.random.randint(0, 100)
|
|
if i == 480:
|
|
drawn_background = struct.pack("H", 0x00E3)
|
|
else:
|
|
drawn_background = struct.pack("H", world.random.randint(0x01, 0x0146))
|
|
|
|
if battle_bg_bpp[struct.unpack("H", drawn_background)[0]] == 4:
|
|
drawn_background_2 = struct.pack("H", 0x0000)
|
|
else:
|
|
drawn_background_2 = struct.pack("H", world.random.choice(bpp2_bgs))
|
|
#print(f"ello mate we are doing background {i} at {hex(0xCBD89A + (i * 4))}, the background is {drawn_background[0]}.")
|
|
if world.flipped_bg > 33 or drawn_background not in bpp2_bgs:
|
|
rom.write_bytes(0x0BD89A + (i * 4), drawn_background)
|
|
rom.write_bytes(0x0BD89C + (i * 4), drawn_background_2)
|
|
else:
|
|
rom.write_bytes(0x0BD89A + (i * 4), drawn_background_2)
|
|
rom.write_bytes(0x0BD89C + (i * 4), drawn_background)
|
|
|
|
rom.write_bytes(0x00B5F1, struct.pack("H", world.random.choice(bpp4_bgs)))
|
|
|
|
if world.options.random_swirl_colors:
|
|
if world.random.random() < 0.5:
|
|
rom.write_bytes(0x02E98A, bytearray([0x7F])) # Color math mode
|
|
rom.write_bytes(0x02E996, bytearray([0x3F]))
|
|
|
|
rom.write_bytes(0x300240, bytearray([world.random.randint(0x00, 0x1F)])) # Normal swirls
|
|
rom.write_bytes(0x300245, bytearray([world.random.randint(0x00, 0x1F)]))
|
|
rom.write_bytes(0x30024A, bytearray([world.random.randint(0x00, 0x1F)]))
|
|
|
|
rom.write_bytes(0x300253, bytearray([world.random.randint(0x00, 0x1F)])) # Green swirls
|
|
rom.write_bytes(0x300258, bytearray([world.random.randint(0x00, 0x1F)]))
|
|
rom.write_bytes(0x30025D, bytearray([world.random.randint(0x00, 0x1F)]))
|
|
|
|
rom.write_bytes(0x300269, bytearray([world.random.randint(0x00, 0x1F)])) # Red swirls
|
|
rom.write_bytes(0x30026E, bytearray([world.random.randint(0x00, 0x1F)]))
|
|
rom.write_bytes(0x300273, bytearray([world.random.randint(0x00, 0x1F)]))
|
|
|
|
if not world.options.prefixed_items:
|
|
rom.write_bytes(0x15F9DC, bytearray([0x06]))
|
|
rom.write_bytes(0x15F9DE, bytearray([0x08]))
|
|
rom.write_bytes(0x15F9E0, bytearray([0x05]))
|
|
rom.write_bytes(0x15F9E2, bytearray([0x0B]))
|
|
rom.write_bytes(0x15F9E4, bytearray([0x0F]))
|
|
rom.write_bytes(0x15F9E6, bytearray([0x10]))
|
|
# change if necessary
|
|
|
|
if world.options.psi_shuffle:
|
|
write_psi(world, rom)
|
|
|
|
world.description_pointer = 0x1000
|
|
if world.options.armorizer:
|
|
randomize_armor(world, rom)
|
|
|
|
if world.options.weaponizer:
|
|
randomize_weapons(world, rom)
|
|
|
|
music_randomizer(world, rom)
|
|
if world.options.map_palette_shuffle:
|
|
map_palette_shuffle(world, rom)
|
|
if world.options.randomize_psi_palettes:
|
|
randomize_psi_palettes(world, rom)
|
|
|
|
if world.options.randomize_enemy_attributes:
|
|
randomize_enemy_attributes(world, rom)
|
|
|
|
if world.options.randomize_enemy_stats:
|
|
randomize_enemy_stats(world, rom)
|
|
|
|
if world.options.randomize_enemy_attacks:
|
|
randomize_enemy_attacks(world, rom)
|
|
|
|
apply_enemy_shuffle(world, rom)
|
|
# randomize_food(world,rom)
|
|
write_bosses(world, rom)
|
|
if world.options.dungeon_shuffle:
|
|
write_dungeon_entrances(world, rom)
|
|
|
|
world.get_all_spheres.wait()
|
|
calculate_scaling(world)
|
|
if world.options.shop_randomizer:
|
|
write_shop_checks(world, rom, shop_checks)
|
|
|
|
scale_enemies(world, rom)
|
|
world.badge_name = badge_names[world.franklin_protection]
|
|
world.badge_name = text_encoder(world.badge_name, 23)
|
|
world.badge_name.extend([0x00])
|
|
world.starting_money = min(world.starting_money, 99999)
|
|
world.starting_money = struct.pack('<I', world.starting_money)
|
|
|
|
rom.write_bytes(0x17FCD0, world.starting_money)
|
|
rom.write_bytes(0x17FCE0, world.prayer_player)
|
|
rom.write_bytes(0x17FD00, world.credits_player)
|
|
rom.write_bytes(0x155027, world.badge_name)
|
|
rom.write_bytes(0x17FD50, struct.pack("H", world.multiworld.players))
|
|
rom.write_bytes(0x3FF0A0, world.world_version.encode("ascii"))
|
|
display_version = text_encoder(world_version, 15)
|
|
display_version.extend([0x02])
|
|
rom.write_bytes(0x3CFFBF, display_version)
|
|
|
|
for element in world.franklinbadge_elements:
|
|
for address in protection_checks[element]:
|
|
if element == world.franklin_protection:
|
|
rom.write_bytes(address, [0xF0])
|
|
else:
|
|
rom.write_bytes(address, [0x80])
|
|
# THIS WILL CRASH IF ADDRESS IS WRONG.
|
|
rom.write_bytes(0x2EC909, struct.pack("I", protection_text[world.franklin_protection][0])) # help text
|
|
rom.write_bytes(0x2EC957, struct.pack("I", protection_text[world.franklin_protection][1])) # battle text
|
|
from Utils import __version__
|
|
rom.name = bytearray(f'MOM2AP{__version__.replace(".", "")[0:3]}_{player}_{world.multiworld.seed:11}\0', "utf8")[:21]
|
|
rom.name.extend([0] * (21 - len(rom.name)))
|
|
rom.write_bytes(0x00FFC0, rom.name)
|
|
|
|
rom.write_file("token_patch.bin", rom.get_token_binary())
|
|
|
|
|
|
class EBProcPatch(APProcedurePatch, APTokenMixin):
|
|
hash = valid_hashes
|
|
game = "EarthBound"
|
|
patch_file_ending = ".apeb"
|
|
result_file_ending = ".sfc"
|
|
name: bytearray
|
|
procedure = [
|
|
("apply_bsdiff4", ["earthbound_basepatch.bsdiff4"]),
|
|
("apply_tokens", ["token_patch.bin"]),
|
|
("repoint_vanilla_tables", [])
|
|
]
|
|
|
|
@classmethod
|
|
def get_source_data(cls) -> bytes:
|
|
return get_base_rom_bytes()
|
|
|
|
def write_bytes(self, offset: int, value: typing.Iterable[int]) -> None:
|
|
self.write_token(APTokenTypes.WRITE, offset, bytes(value))
|
|
|
|
def copy_bytes(self, source: int, amount: int, destination: int) -> None:
|
|
self.write_token(APTokenTypes.COPY, destination, (amount, source))
|
|
|
|
|
|
class EBPatchExtensions(APPatchExtension):
|
|
game = "EarthBound"
|
|
|
|
@staticmethod
|
|
def repoint_vanilla_tables(caller: APProcedurePatch, rom: bytes) -> bytes:
|
|
rom = LocalRom(rom)
|
|
version_check = rom.read_bytes(0x3FF0A0, 16)
|
|
version_check = version_check.split(b'\x00', 1)[0]
|
|
version_check_str = version_check.decode("ascii")
|
|
client_version = world_version
|
|
if client_version != version_check_str and version_check_str != "":
|
|
raise Exception(f"Error! Patch generated on EarthBound APWorld version {version_check_str} doesn't match client version {client_version}! " +
|
|
f"Please use EarthBound APWorld version {version_check_str} for patching.")
|
|
elif version_check_str == "":
|
|
raise Exception(f"Error! Patch generated on old EarthBound APWorld version, doesn't match client version {client_version}! " +
|
|
f"Please verify you are using the same APWorld as the generator.")
|
|
|
|
for action_number in range(0x013F):
|
|
current_action = rom.read_bytes(0x157B68 + (12 * action_number), 12)
|
|
rom.write_bytes(0x3FAFB0 + (12 * action_number), current_action)
|
|
|
|
for psi_number in range(0x35):
|
|
current_action = rom.read_bytes(0x158A50 + (15 * psi_number), 15)
|
|
rom.write_bytes(0x350000 + (15 * psi_number), current_action)
|
|
|
|
psi_text_table = rom.read_bytes(0x158D7A, (25 * 17))
|
|
rom.write_bytes(0x3B0500, psi_text_table)
|
|
|
|
psi_anim_config = rom.read_bytes(0x0CF04D, 0x0198)
|
|
rom.write_bytes(0x360000, psi_anim_config)
|
|
|
|
psi_anim_pointers = rom.read_bytes(0x0CF58F, 0x088)
|
|
rom.write_bytes(0x360400, psi_anim_pointers)
|
|
|
|
psi_anim_palettes = rom.read_bytes(0x0CF47F, 0x0110)
|
|
rom.write_bytes(0x360600, psi_anim_palettes)
|
|
|
|
for psi_number in range(0x32):
|
|
psi_anim = rom.read_bytes(0x2F8583 + (0x04 * psi_number), 4)
|
|
rom.write_bytes(0x3B0003 + (4 * psi_number), psi_anim)
|
|
rom.write_bytes(0x3B0003, bytearray([0x4C]))
|
|
# rom.write_bytes(0x3B0002, bytearray([0x45]))
|
|
|
|
main_font_data = rom.read_bytes(0x210C7A, 96)
|
|
main_font_gfx = rom.read_bytes(0x210CDA, 0x0C00)
|
|
saturn_font_data = rom.read_bytes(0x201359, 96)
|
|
saturn_font_gfx = rom.read_bytes(0x2013B9, 0x0C00)
|
|
letter_n = rom.read_bytes(0x21169F, 6)
|
|
letter_a = rom.read_bytes(0x2114FF, 6)
|
|
letter_e = rom.read_bytes(0x21157F, 6)
|
|
saturn_a = rom.read_bytes(0x2017DD, 9)
|
|
saturn_n = rom.read_bytes(0x20197E, 8)
|
|
saturn_e = rom.read_bytes(0x20185C, 24)
|
|
|
|
accent_tilde = rom.read_bytes(0x2118A1, 2)
|
|
saturn_tilde = rom.read_bytes(0x201F7F, 2)
|
|
|
|
rom.write_bytes(0x3A0000, main_font_data)
|
|
rom.write_bytes(0x3C0000, main_font_gfx)
|
|
|
|
rom.write_bytes(0x3A0100, saturn_font_data)
|
|
rom.write_bytes(0x3C1000, saturn_font_gfx)
|
|
rom.write_bytes(0x3C0D25, letter_n) # Setup n
|
|
rom.write_bytes(0x3C0D45, letter_a) # Setup a
|
|
rom.write_bytes(0x3C0D65, letter_e)
|
|
rom.write_bytes(0x3C0D22, accent_tilde)
|
|
|
|
rom.write_bytes(0x3C1D25, saturn_n) # Setup n
|
|
rom.write_bytes(0x3C1D45, saturn_a)
|
|
rom.write_bytes(0x3C1D63, saturn_e)
|
|
rom.write_bytes(0x3C1D22, saturn_tilde)
|
|
|
|
# ---------------------------------------
|
|
ness_level = rom.read_bytes(0x15F5FB, 1)
|
|
paula_level = rom.read_bytes(0x15f60f, 1)
|
|
jeff_level = rom.read_bytes(0x15f623, 1)
|
|
poo_level = rom.read_bytes(0x15f637, 1)
|
|
|
|
ness_start_exp = rom.read_bytes(0x158F49 + (ness_level[0] * 4), 4)
|
|
paula_start_exp = rom.read_bytes(0x1590D9 + (paula_level[0] * 4), 4)
|
|
jeff_start_exp = rom.read_bytes(0x159269 + (jeff_level[0] * 4), 4)
|
|
poo_start_exp = rom.read_bytes(0x1593F9 + (poo_level[0] * 4), 4)
|
|
|
|
rom.write_bytes(0x17FD40, ness_start_exp)
|
|
rom.write_bytes(0x17FD44, paula_start_exp)
|
|
rom.write_bytes(0x17FD48, jeff_start_exp)
|
|
rom.write_bytes(0x17FD4C, poo_start_exp)
|
|
return rom.get_bytes()
|
|
|
|
|
|
def get_base_rom_bytes(file_name: str = "") -> bytes:
|
|
base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None)
|
|
if not base_rom_bytes:
|
|
file_name = get_base_rom_path(file_name)
|
|
base_rom_bytes = bytes(Utils.read_snes_rom(open(file_name, "rb")))
|
|
|
|
basemd5 = hashlib.md5()
|
|
basemd5.update(base_rom_bytes)
|
|
if basemd5.hexdigest() not in valid_hashes:
|
|
raise Exception('Supplied Base Rom does not match known MD5 for US(1.0) release. '
|
|
'Get the correct game and version, then dump it')
|
|
get_base_rom_bytes.base_rom_bytes = base_rom_bytes
|
|
return base_rom_bytes
|
|
|
|
|
|
def get_base_rom_path(file_name: str = "") -> str:
|
|
options: settings.Settings = settings.get_settings()
|
|
if not file_name:
|
|
file_name = options["earthbound_options"]["rom_file"]
|
|
if not os.path.exists(file_name):
|
|
file_name = Utils.user_path(file_name)
|
|
return file_name
|
|
|
|
|
|
# Fix hint text, I have a special idea where I can give it info on a random region
|