Files
dockipelago/worlds/tloz_oos/patching/ProcedurePatch.py
Jonathan Tinney 7971961166
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
add schedule I, sonic 1/frontiers/heroes, spirit island
2026-04-02 23:46:36 -07:00

149 lines
6.7 KiB
Python

import json
import pkgutil
import yaml
from worlds.Files import APProcedurePatch, APTokenMixin, APPatchExtension
from .Functions import *
from .data_manager.text import apply_ages_edits, get_modded_seasons_text_data
from .puzzle_rando import randomize_puzzles
from .room_edits import apply_room_edits
from ..common.patching.rooms.encoding import write_room_data
from ..common.patching.text.encoding import write_text_data
from ..common.patching.z80asm.Assembler import Z80Assembler, Z80Block, GameboyAddress
class OoSPatchExtensions(APPatchExtension):
game = "The Legend of Zelda - Oracle of Seasons"
@staticmethod
def apply_patches(caller: APProcedurePatch, rom: bytes, patch_file: str) -> bytes:
from .. import OracleOfSeasonsWorld
rom_data = RomData(rom)
patch_data = json.loads(caller.get_file(patch_file).decode("utf-8"))
version = patch_data["version"].split(".")
world_version = OracleOfSeasonsWorld.world_version
if int(version[0]) != world_version.major or int(version[1]) > world_version.minor:
raise Exception(f"Invalid version: this patch was generated on v{patch_data['version']}, "
f"you are currently using v{world_version.as_simple_string()}")
if patch_data["options"]["cross_items"]:
file_name = get_settings().tloz_oos_options.ages_rom_file
file_path = Utils.user_path(file_name)
rom_file = open(file_path, "rb")
ages_rom = bytes(rom_file.read())
rom_file.close()
for bank in range(0x40, 0x80):
bank = 0xdd # TODO: this is an invalid instruction that hangs the game, it's easier to debug but looks worse, remove/comment out once stable
rom_data.add_bank(bank)
rom_data.update_rom_size()
else:
ages_rom = bytes()
# Initialize random seed with the one used for generation + the player ID, so that cosmetic stuff set
# to "random" always generate the same for successive patchings for a given slot
random.seed(patch_data["seed"] + caller.player)
assembler = Z80Assembler(CAVE_DATA, DEFINES, rom, ages_rom)
dictionary, texts = get_modded_seasons_text_data(rom_data)
if patch_data["options"]["cross_items"]:
if texts["TX_0053"] == "": # Check if cane text exists
# If not, add the Ages texts
apply_ages_edits(texts, RomData(ages_rom))
# Generate dungeon entrance/exit data
dungeon_entrances = dict(DUNGEON_ENTRANCES)
dungeon_exits = dict(DUNGEON_EXITS)
if patch_data["options"]["linked_heros_cave"] & OracleOfSeasonsLinkedHerosCave.samasa:
dungeon_entrances["d11"] = {
"map_tile": 0xcf,
"room": 0xcf,
"group": 0x00,
"position": 0x55
}
if patch_data["options"]["linked_heros_cave"]:
dungeon_exits["d11"] = GameboyAddress(0x04, 0x7b35).address_in_rom()
room_data = apply_room_edits(rom_data, patch_data)
# Define assembly constants & floating chunks
item_data = define_foreign_item_data(assembler, texts, patch_data)
define_location_constants(assembler, patch_data, item_data)
define_option_constants(assembler, patch_data)
define_season_constants(assembler, patch_data)
make_text_data(assembler, texts, patch_data)
define_compass_rooms_table(assembler, patch_data, item_data)
define_collect_properties_table(assembler, patch_data, item_data)
define_additional_tile_replacements(assembler, patch_data)
define_samasa_combination(assembler, patch_data)
define_dungeon_items_text_constants(texts, patch_data)
define_essence_sparkle_constants(assembler, patch_data, dungeon_entrances)
define_lost_woods_sequences(assembler, texts, patch_data)
define_tree_sprites(assembler, patch_data, item_data)
set_file_select_text(assembler, caller.player_name)
set_player_start_inventory(assembler, patch_data)
randomize_puzzles(rom_data, assembler, room_data, patch_data)
if not hasattr(get_settings().tloz_oos_options, "beat_tutorial"):
set_faq_trap(assembler)
# Parse assembler files, compile them and write the result in the ROM
print("Compiling ASM files...")
write_text_data(rom_data, dictionary, texts, True)
write_room_data(rom_data, room_data, True)
for file_path in get_asm_files(patch_data):
data_loaded = yaml.safe_load(pkgutil.get_data(__name__, file_path))
for metalabel, contents in data_loaded.items():
assembler.add_block(Z80Block(metalabel, contents))
assembler.compile_all()
for block in assembler.blocks:
rom_data.write_bytes(block.addr.address_in_rom(), block.byte_array)
if patch_data["options"]["linked_heros_cave"] & OracleOfSeasonsLinkedHerosCave.samasa:
dungeon_entrances["d11"]["addr"] = assembler.global_labels["warpSourceDesert"].address_in_rom() + 2
# Perform direct edits on the ROM
alter_treasure_types(rom_data, item_data)
write_chest_contents(rom_data, patch_data, item_data)
set_old_men_rupee_values(rom_data, patch_data)
set_dungeon_warps(rom_data, patch_data, dungeon_entrances, dungeon_exits)
set_portal_warps(rom_data, patch_data)
apply_miscellaneous_options(rom_data, patch_data)
set_fixed_subrosia_seaside_location(rom_data, patch_data)
if patch_data["options"]["randomize_ai"]:
randomize_ai_for_april_fools(rom_data, patch_data["seed"] + caller.player)
# Apply cosmetic settings
set_heart_beep_interval_from_settings(rom_data)
set_character_sprite_from_settings(rom_data)
inject_slot_name(rom_data, caller.player_name)
rom_data.update_header_checksum()
rom_data.update_checksum(0x14e)
return rom_data.output()
class OoSProcedurePatch(APProcedurePatch, APTokenMixin):
hash = [ROM_HASH]
patch_file_ending: str = ".apoos"
result_file_ending: str = ".gbc"
game = "The Legend of Zelda - Oracle of Seasons"
procedure = [
("apply_patches", ["patch.json"])
]
@classmethod
def get_source_data(cls) -> bytes:
base_rom_bytes = getattr(cls, "base_rom_bytes", None)
if not base_rom_bytes:
file_name = get_settings().tloz_oos_options.rom_file
file_name = Utils.user_path(file_name)
rom_file = open(file_name, "rb")
base_rom_bytes = bytes(rom_file.read())
rom_file.close()
setattr(cls, "base_rom_bytes", base_rom_bytes)
return base_rom_bytes