LADX: generate without rom (#4278)

This commit is contained in:
threeandthreee
2025-07-26 16:16:00 -04:00
committed by GitHub
parent fa49fef695
commit 7a8048a8fd
11 changed files with 317 additions and 290 deletions

View File

@@ -2,13 +2,15 @@ import binascii
import importlib.util
import importlib.machinery
import os
import pkgutil
import random
import pickle
import Utils
import settings
from collections import defaultdict
from typing import TYPE_CHECKING
from typing import Dict
from .romTables import ROMWithTables
from . import assembler
from . import mapgen
from . import patches
from .patches import overworld as _
from .patches import dungeon as _
@@ -57,27 +59,20 @@ from .patches import tradeSequence as _
from . import hints
from .patches import bank34
from .utils import formatText
from .roomEditor import RoomEditor, Object
from .patches.aesthetics import rgb_to_bin, bin_to_rgb
from .locations.keyLocation import KeyLocation
from BaseClasses import ItemClassification
from ..Locations import LinksAwakeningLocation
from ..Options import TrendyGame, Palette, MusicChangeCondition, Warps
if TYPE_CHECKING:
from .. import LinksAwakeningWorld
from .. import Options
# Function to generate a final rom, this patches the rom with all required patches
def generateRom(args, world: "LinksAwakeningWorld"):
def generateRom(base_rom: bytes, args, patch_data: Dict):
random.seed(patch_data["seed"] + patch_data["player"])
multi_key = binascii.unhexlify(patch_data["multi_key"].encode())
item_list = pickle.loads(binascii.unhexlify(patch_data["item_list"].encode()))
options = patch_data["options"]
rom_patches = []
player_names = list(world.multiworld.player_name.values())
rom = ROMWithTables(args.input_filename, rom_patches)
rom.player_names = player_names
rom = ROMWithTables(base_rom, rom_patches)
rom.player_names = patch_data["other_player_names"]
pymods = []
if args.pymod:
for pymod in args.pymod:
@@ -88,10 +83,13 @@ def generateRom(args, world: "LinksAwakeningWorld"):
for pymod in pymods:
pymod.prePatch(rom)
if world.ladxr_settings.gfxmod:
patches.aesthetics.gfxMod(rom, os.path.join("data", "sprites", "ladx", world.ladxr_settings.gfxmod))
item_list = [item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)]
if options["gfxmod"]:
user_settings = settings.get_settings()
try:
gfx_mod_file = user_settings["ladx_options"]["gfx_mod_file"]
patches.aesthetics.gfxMod(rom, gfx_mod_file)
except FileNotFoundError:
pass # if user just doesnt provide gfxmod file, let patching continue
assembler.resetConsts()
assembler.const("INV_SIZE", 16)
@@ -121,7 +119,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
assembler.const("wLinkSpawnDelay", 0xDE13)
#assembler.const("HARDWARE_LINK", 1)
assembler.const("HARD_MODE", 1 if world.ladxr_settings.hardmode != "none" else 0)
assembler.const("HARD_MODE", 1 if options["hard_mode"] else 0)
patches.core.cleanup(rom)
patches.save.singleSaveSlot(rom)
@@ -135,7 +133,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
patches.core.easyColorDungeonAccess(rom)
patches.owl.removeOwlEvents(rom)
patches.enemies.fixArmosKnightAsMiniboss(rom)
patches.bank3e.addBank3E(rom, world.multi_key, world.player, player_names)
patches.bank3e.addBank3E(rom, multi_key, patch_data["player"], patch_data["other_player_names"])
patches.bank3f.addBank3F(rom)
patches.bank34.addBank34(rom, item_list)
patches.core.removeGhost(rom)
@@ -144,19 +142,17 @@ def generateRom(args, world: "LinksAwakeningWorld"):
patches.core.alwaysAllowSecretBook(rom)
patches.core.injectMainLoop(rom)
from ..Options import ShuffleSmallKeys, ShuffleNightmareKeys
if world.options.shuffle_small_keys != ShuffleSmallKeys.option_original_dungeon or\
world.options.shuffle_nightmare_keys != ShuffleNightmareKeys.option_original_dungeon:
if options["shuffle_small_keys"] != Options.ShuffleSmallKeys.option_original_dungeon or\
options["shuffle_nightmare_keys"] != Options.ShuffleNightmareKeys.option_original_dungeon:
patches.inventory.advancedInventorySubscreen(rom)
patches.inventory.moreSlots(rom)
if world.ladxr_settings.witch:
patches.witch.updateWitch(rom)
# if ladxr_settings["witch"]:
patches.witch.updateWitch(rom)
patches.softlock.fixAll(rom)
if not world.ladxr_settings.rooster:
if not options["rooster"]:
patches.maptweaks.tweakMap(rom)
patches.maptweaks.tweakBirdKeyRoom(rom)
if world.ladxr_settings.overworld == "openmabe":
if options["overworld"] == Options.Overworld.option_open_mabe:
patches.maptweaks.openMabe(rom)
patches.chest.fixChests(rom)
patches.shop.fixShop(rom)
@@ -168,10 +164,10 @@ def generateRom(args, world: "LinksAwakeningWorld"):
patches.tarin.updateTarin(rom)
patches.fishingMinigame.updateFinishingMinigame(rom)
patches.health.upgradeHealthContainers(rom)
if world.ladxr_settings.owlstatues in ("dungeon", "both"):
patches.owl.upgradeDungeonOwlStatues(rom)
if world.ladxr_settings.owlstatues in ("overworld", "both"):
patches.owl.upgradeOverworldOwlStatues(rom)
# if ladxr_settings["owlstatues"] in ("dungeon", "both"):
# patches.owl.upgradeDungeonOwlStatues(rom)
# if ladxr_settings["owlstatues"] in ("overworld", "both"):
# patches.owl.upgradeOverworldOwlStatues(rom)
patches.goldenLeaf.fixGoldenLeaf(rom)
patches.heartPiece.fixHeartPiece(rom)
patches.seashell.fixSeashell(rom)
@@ -180,143 +176,95 @@ def generateRom(args, world: "LinksAwakeningWorld"):
patches.songs.upgradeMarin(rom)
patches.songs.upgradeManbo(rom)
patches.songs.upgradeMamu(rom)
patches.tradeSequence.patchTradeSequence(rom, world.ladxr_settings)
patches.bowwow.fixBowwow(rom, everywhere=world.ladxr_settings.bowwow != 'normal')
if world.ladxr_settings.bowwow != 'normal':
patches.bowwow.bowwowMapPatches(rom)
patches.tradeSequence.patchTradeSequence(rom, options)
patches.bowwow.fixBowwow(rom, everywhere=False)
# if ladxr_settings["bowwow"] != 'normal':
# patches.bowwow.bowwowMapPatches(rom)
patches.desert.desertAccess(rom)
if world.ladxr_settings.overworld == 'dungeondive':
patches.overworld.patchOverworldTilesets(rom)
patches.overworld.createDungeonOnlyOverworld(rom)
elif world.ladxr_settings.overworld == 'nodungeons':
patches.dungeon.patchNoDungeons(rom)
elif world.ladxr_settings.overworld == 'random':
patches.overworld.patchOverworldTilesets(rom)
mapgen.store_map(rom, world.ladxr_logic.world.map)
# if ladxr_settings["overworld"] == 'dungeondive':
# patches.overworld.patchOverworldTilesets(rom)
# patches.overworld.createDungeonOnlyOverworld(rom)
# elif ladxr_settings["overworld"] == 'nodungeons':
# patches.dungeon.patchNoDungeons(rom)
#elif world.ladxr_settings["overworld"] == 'random':
# patches.overworld.patchOverworldTilesets(rom)
# mapgen.store_map(rom, world.ladxr_logic.world.map)
#if settings.dungeon_items == 'keysy':
# patches.dungeon.removeKeyDoors(rom)
# patches.reduceRNG.slowdownThreeOfAKind(rom)
patches.reduceRNG.fixHorseHeads(rom)
patches.bomb.onlyDropBombsWhenHaveBombs(rom)
if world.options.music_change_condition == MusicChangeCondition.option_always:
if options["music_change_condition"] == Options.MusicChangeCondition.option_always:
patches.aesthetics.noSwordMusic(rom)
patches.aesthetics.reduceMessageLengths(rom, world.random)
patches.aesthetics.reduceMessageLengths(rom, random)
patches.aesthetics.allowColorDungeonSpritesEverywhere(rom)
if world.ladxr_settings.music == 'random':
patches.music.randomizeMusic(rom, world.random)
elif world.ladxr_settings.music == 'off':
if options["music"] == Options.Music.option_shuffled:
patches.music.randomizeMusic(rom, random)
elif options["music"] == Options.Music.option_off:
patches.music.noMusic(rom)
if world.ladxr_settings.noflash:
if options["no_flash"]:
patches.aesthetics.removeFlashingLights(rom)
if world.ladxr_settings.hardmode == "oracle":
if options["hard_mode"] == Options.HardMode.option_oracle:
patches.hardMode.oracleMode(rom)
elif world.ladxr_settings.hardmode == "hero":
elif options["hard_mode"] == Options.HardMode.option_hero:
patches.hardMode.heroMode(rom)
elif world.ladxr_settings.hardmode == "ohko":
elif options["hard_mode"] == Options.HardMode.option_ohko:
patches.hardMode.oneHitKO(rom)
if world.ladxr_settings.superweapons:
patches.weapons.patchSuperWeapons(rom)
if world.ladxr_settings.textmode == 'fast':
#if ladxr_settings["superweapons"]:
# patches.weapons.patchSuperWeapons(rom)
if options["text_mode"] == Options.TextMode.option_fast:
patches.aesthetics.fastText(rom)
if world.ladxr_settings.textmode == 'none':
patches.aesthetics.fastText(rom)
patches.aesthetics.noText(rom)
if not world.ladxr_settings.nagmessages:
#if ladxr_settings["textmode"] == 'none':
# patches.aesthetics.fastText(rom)
# patches.aesthetics.noText(rom)
if not options["nag_messages"]:
patches.aesthetics.removeNagMessages(rom)
if world.ladxr_settings.lowhpbeep == 'slow':
if options["low_hp_beep"] == Options.LowHpBeep.option_slow:
patches.aesthetics.slowLowHPBeep(rom)
if world.ladxr_settings.lowhpbeep == 'none':
if options["low_hp_beep"] == Options.LowHpBeep.option_none:
patches.aesthetics.removeLowHPBeep(rom)
if 0 <= int(world.ladxr_settings.linkspalette):
patches.aesthetics.forceLinksPalette(rom, int(world.ladxr_settings.linkspalette))
if 0 <= options["link_palette"]:
patches.aesthetics.forceLinksPalette(rom, options["link_palette"])
if args.romdebugmode:
# The default rom has this build in, just need to set a flag and we get this save.
rom.patch(0, 0x0003, "00", "01")
# Patch the sword check on the shopkeeper turning around.
if world.ladxr_settings.steal == 'never':
rom.patch(4, 0x36F9, "FA4EDB", "3E0000")
elif world.ladxr_settings.steal == 'always':
rom.patch(4, 0x36F9, "FA4EDB", "3E0100")
#if ladxr_settings["steal"] == 'never':
# rom.patch(4, 0x36F9, "FA4EDB", "3E0000")
#elif ladxr_settings["steal"] == 'always':
# rom.patch(4, 0x36F9, "FA4EDB", "3E0100")
if world.ladxr_settings.hpmode == 'inverted':
patches.health.setStartHealth(rom, 9)
elif world.ladxr_settings.hpmode == '1':
patches.health.setStartHealth(rom, 1)
#if ladxr_settings["hpmode"] == 'inverted':
# patches.health.setStartHealth(rom, 9)
#elif ladxr_settings["hpmode"] == '1':
# patches.health.setStartHealth(rom, 1)
patches.inventory.songSelectAfterOcarinaSelect(rom)
if world.ladxr_settings.quickswap == 'a':
if options["quickswap"] == 'a':
patches.core.quickswap(rom, 1)
elif world.ladxr_settings.quickswap == 'b':
elif options["quickswap"] == 'b':
patches.core.quickswap(rom, 0)
patches.core.addBootsControls(rom, world.options.boots_controls)
patches.core.addBootsControls(rom, options["boots_controls"])
random.seed(patch_data["seed"] + patch_data["player"])
hints.addHints(rom, random, patch_data["hint_texts"])
world_setup = world.ladxr_logic.world_setup
JUNK_HINT = 0.33
RANDOM_HINT= 0.66
# USEFUL_HINT = 1.0
# TODO: filter events, filter unshuffled keys
all_items = world.multiworld.get_items()
our_items = [item for item in all_items
if item.player == world.player
and item.location
and item.code is not None
and item.location.show_in_spoiler]
our_useful_items = [item for item in our_items if ItemClassification.progression in item.classification]
def gen_hint():
if not world.options.in_game_hints:
return 'Hints are disabled!'
chance = world.random.uniform(0, 1)
if chance < JUNK_HINT:
return None
elif chance < RANDOM_HINT:
location = world.random.choice(our_items).location
else: # USEFUL_HINT
location = world.random.choice(our_useful_items).location
if location.item.player == world.player:
name = "Your"
else:
name = f"{world.multiworld.player_name[location.item.player]}'s"
# filter out { and } since they cause issues with string.format later on
name = name.replace("{", "").replace("}", "")
if isinstance(location, LinksAwakeningLocation):
location_name = location.ladxr_item.metadata.name
else:
location_name = location.name
hint = f"{name} {location.item.name} is at {location_name}"
if location.player != world.player:
# filter out { and } since they cause issues with string.format later on
player_name = world.multiworld.player_name[location.player].replace("{", "").replace("}", "")
hint += f" in {player_name}'s world"
# Cap hint size at 85
# Realistically we could go bigger but let's be safe instead
hint = hint[:85]
return hint
hints.addHints(rom, world.random, gen_hint)
if world_setup.goal == "raft":
if patch_data["world_setup"]["goal"] == "raft":
patches.goal.setRaftGoal(rom)
elif world_setup.goal in ("bingo", "bingo-full"):
patches.bingo.setBingoGoal(rom, world_setup.bingo_goals, world_setup.goal)
elif world_setup.goal == "seashells":
elif patch_data["world_setup"]["goal"] in ("bingo", "bingo-full"):
patches.bingo.setBingoGoal(rom, patch_data["world_setup"]["bingo_goals"], patch_data["world_setup"]["goal"])
elif patch_data["world_setup"]["goal"] == "seashells":
patches.goal.setSeashellGoal(rom, 20)
else:
patches.goal.setRequiredInstrumentCount(rom, world_setup.goal)
patches.goal.setRequiredInstrumentCount(rom, patch_data["world_setup"]["goal"])
# Patch the generated logic into the rom
patches.chest.setMultiChest(rom, world_setup.multichest)
if world.ladxr_settings.overworld not in {"dungeondive", "random"}:
patches.entrances.changeEntrances(rom, world_setup.entrance_mapping)
patches.chest.setMultiChest(rom, patch_data["world_setup"]["multichest"])
#if ladxr_settings["overworld"] not in {"dungeondive", "random"}:
patches.entrances.changeEntrances(rom, patch_data["world_setup"]["entrance_mapping"])
for spot in item_list:
if spot.item and spot.item.startswith("*"):
spot.item = spot.item[1:]
@@ -327,23 +275,22 @@ def generateRom(args, world: "LinksAwakeningWorld"):
# There are only 101 player name slots (99 + "The Server" + "another world"), so don't use more than that
mw = 100
spot.patch(rom, spot.item, multiworld=mw)
patches.enemies.changeBosses(rom, world_setup.boss_mapping)
patches.enemies.changeMiniBosses(rom, world_setup.miniboss_mapping)
patches.enemies.changeBosses(rom, patch_data["world_setup"]["boss_mapping"])
patches.enemies.changeMiniBosses(rom, patch_data["world_setup"]["miniboss_mapping"])
if not args.romdebugmode:
patches.core.addFrameCounter(rom, len(item_list))
patches.core.warpHome(rom) # Needs to be done after setting the start location.
patches.titleScreen.setRomInfo(rom, world.multi_key, world.multiworld.seed_name, world.ladxr_settings,
world.player_name, world.player)
if world.options.ap_title_screen:
patches.titleScreen.setRomInfo(rom, patch_data)
if options["ap_title_screen"]:
patches.titleScreen.setTitleGraphics(rom)
patches.endscreen.updateEndScreen(rom)
patches.aesthetics.updateSpriteData(rom)
if args.doubletrouble:
patches.enemies.doubleTrouble(rom)
if world.options.text_shuffle:
if options["text_shuffle"]:
excluded_ids = [
# Overworld owl statues
0x1B6, 0x1B7, 0x1B8, 0x1B9, 0x1BA, 0x1BB, 0x1BC, 0x1BD, 0x1BE, 0x22D,
@@ -388,6 +335,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
excluded_texts = [ rom.texts[excluded_id] for excluded_id in excluded_ids]
buckets = defaultdict(list)
# For each ROM bank, shuffle text within the bank
random.seed(patch_data["seed"] + patch_data["player"])
for n, data in enumerate(rom.texts._PointerTable__data):
# Don't muck up which text boxes are questions and which are statements
if type(data) != int and data and data != b'\xFF' and data not in excluded_texts:
@@ -395,20 +343,20 @@ def generateRom(args, world: "LinksAwakeningWorld"):
for bucket in buckets.values():
# For each bucket, make a copy and shuffle
shuffled = bucket.copy()
world.random.shuffle(shuffled)
random.shuffle(shuffled)
# Then put new text in
for bucket_idx, (orig_idx, data) in enumerate(bucket):
rom.texts[shuffled[bucket_idx][0]] = data
if world.options.trendy_game != TrendyGame.option_normal:
if options["trendy_game"] != Options.TrendyGame.option_normal:
# TODO: if 0 or 4, 5, remove inaccurate conveyor tiles
room_editor = RoomEditor(rom, 0x2A0)
if world.options.trendy_game == TrendyGame.option_easy:
if options["trendy_game"] == Options.TrendyGame.option_easy:
# Set physics flag on all objects
for i in range(0, 6):
rom.banks[0x4][0x6F1E + i -0x4000] = 0x4
@@ -419,7 +367,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
# Add new conveyor to "push" yoshi (it's only a visual)
room_editor.objects.append(Object(5, 3, 0xD0))
if world.options.trendy_game >= TrendyGame.option_harder:
if options["trendy_game"] >= Options.TrendyGame.option_harder:
"""
Data_004_76A0::
db $FC, $00, $04, $00, $00
@@ -428,17 +376,18 @@ def generateRom(args, world: "LinksAwakeningWorld"):
db $00, $04, $00, $FC, $00
"""
speeds = {
TrendyGame.option_harder: (3, 8),
TrendyGame.option_hardest: (3, 8),
TrendyGame.option_impossible: (3, 16),
Options.TrendyGame.option_harder: (3, 8),
Options.TrendyGame.option_hardest: (3, 8),
Options.TrendyGame.option_impossible: (3, 16),
}
def speed():
return world.random.randint(*speeds[world.options.trendy_game])
random.seed(patch_data["seed"] + patch_data["player"])
return random.randint(*speeds[options["trendy_game"]])
rom.banks[0x4][0x76A0-0x4000] = 0xFF - speed()
rom.banks[0x4][0x76A2-0x4000] = speed()
rom.banks[0x4][0x76A6-0x4000] = speed()
rom.banks[0x4][0x76A8-0x4000] = 0xFF - speed()
if world.options.trendy_game >= TrendyGame.option_hardest:
if options["trendy_game"] >= Options.TrendyGame.option_hardest:
rom.banks[0x4][0x76A1-0x4000] = 0xFF - speed()
rom.banks[0x4][0x76A3-0x4000] = speed()
rom.banks[0x4][0x76A5-0x4000] = speed()
@@ -462,11 +411,11 @@ def generateRom(args, world: "LinksAwakeningWorld"):
for channel in range(3):
color[channel] = color[channel] * 31 // 0xbc
if world.options.warps != Warps.option_vanilla:
patches.core.addWarpImprovements(rom, world.options.warps == Warps.option_improved_additional)
if options["warps"] != Options.Warps.option_vanilla:
patches.core.addWarpImprovements(rom, options["warps"] == Options.Warps.option_improved_additional)
palette = world.options.palette
if palette != Palette.option_normal:
palette = options["palette"]
if palette != Options.Palette.option_normal:
ranges = {
# Object palettes
# Overworld palettes
@@ -496,22 +445,22 @@ def generateRom(args, world: "LinksAwakeningWorld"):
r,g,b = bin_to_rgb(packed)
# 1 bit
if palette == Palette.option_1bit:
if palette == Options.Palette.option_1bit:
r &= 0b10000
g &= 0b10000
b &= 0b10000
# 2 bit
elif palette == Palette.option_1bit:
elif palette == Options.Palette.option_1bit:
r &= 0b11000
g &= 0b11000
b &= 0b11000
# Invert
elif palette == Palette.option_inverted:
elif palette == Options.Palette.option_inverted:
r = 31 - r
g = 31 - g
b = 31 - b
# Pink
elif palette == Palette.option_pink:
elif palette == Options.Palette.option_pink:
r = r // 2
r += 16
r = int(r)
@@ -520,7 +469,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
b += 16
b = int(b)
b = clamp(b, 0, 0x1F)
elif palette == Palette.option_greyscale:
elif palette == Options.Palette.option_greyscale:
# gray=int(0.299*r+0.587*g+0.114*b)
gray = (r + g + b) // 3
r = g = b = gray
@@ -531,10 +480,10 @@ def generateRom(args, world: "LinksAwakeningWorld"):
SEED_LOCATION = 0x0134
# Patch over the title
assert(len(world.multi_key) == 12)
rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(world.multi_key))
assert(len(multi_key) == 12)
rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(multi_key))
for pymod in pymods:
pymod.postPatch(rom)
return rom
return rom.save()

View File

@@ -1,5 +1,7 @@
from .locations.items import *
from .utils import formatText
from BaseClasses import ItemClassification
from ..Locations import LinksAwakeningLocation
hint_text_ids = [
@@ -49,14 +51,64 @@ useless_hint = [
]
def addHints(rom, rnd, hint_generator):
def addHints(rom, rnd, hint_texts):
hint_texts_copy = hint_texts.copy()
text_ids = hint_text_ids.copy()
rnd.shuffle(text_ids)
for text_id in text_ids:
hint = hint_generator()
hint = hint_texts_copy.pop()
if not hint:
hint = rnd.choice(hints).format(*rnd.choice(useless_hint))
rom.texts[text_id] = formatText(hint)
for text_id in range(0x200, 0x20C, 2):
rom.texts[text_id] = formatText("Read this book?", ask="YES NO")
def generate_hint_texts(world):
JUNK_HINT = 0.33
RANDOM_HINT= 0.66
# USEFUL_HINT = 1.0
# TODO: filter events, filter unshuffled keys
all_items = world.multiworld.get_items()
our_items = [item for item in all_items
if item.player == world.player
and item.location
and item.code is not None
and item.location.show_in_spoiler]
our_useful_items = [item for item in our_items if ItemClassification.progression in item.classification]
hint_texts = []
def gen_hint():
chance = world.random.uniform(0, 1)
if chance < JUNK_HINT:
return None
elif chance < RANDOM_HINT:
location = world.random.choice(our_items).location
else: # USEFUL_HINT
location = world.random.choice(our_useful_items).location
if location.item.player == world.player:
name = "Your"
else:
name = f"{world.multiworld.player_name[location.item.player]}'s"
# filter out { and } since they cause issues with string.format later on
name = name.replace("{", "").replace("}", "")
if isinstance(location, LinksAwakeningLocation):
location_name = location.ladxr_item.metadata.name
else:
location_name = location.name
hint = f"{name} {location.item} is at {location_name}"
if location.player != world.player:
# filter out { and } since they cause issues with string.format later on
player_name = world.multiworld.player_name[location.player].replace("{", "").replace("}", "")
hint += f" in {player_name}'s world"
# Cap hint size at 85
# Realistically we could go bigger but let's be safe instead
hint = hint[:85]
return hint
for _ in hint_text_ids:
hint_texts.append(gen_hint())
return hint_texts

View File

@@ -541,7 +541,7 @@ OAMData:
rom.banks[0x38][0x1400+n*0x20:0x1410+n*0x20] = utils.createTileData(gfx_high)
rom.banks[0x38][0x1410+n*0x20:0x1420+n*0x20] = utils.createTileData(gfx_low)
def addBootsControls(rom, boots_controls: BootsControls):
def addBootsControls(rom, boots_controls: int):
if boots_controls == BootsControls.option_vanilla:
return
consts = {
@@ -578,7 +578,7 @@ def addBootsControls(rom, boots_controls: BootsControls):
jr z, .yesBoots
ld a, [hl]
"""
}[boots_controls.value]
}[boots_controls]
# The new code fits exactly within Nintendo's poorly space optimzied code while having more features
boots_code = assembler.ASM("""

View File

@@ -42,7 +42,7 @@ MINIBOSS_ENTITIES = {
"ARMOS_KNIGHT": [(4, 3, 0x88)],
}
MINIBOSS_ROOMS = {
0: 0x111, 1: 0x128, 2: 0x145, 3: 0x164, 4: 0x193, 5: 0x1C5, 6: 0x228, 7: 0x23F,
"0": 0x111, "1": 0x128, "2": 0x145, "3": 0x164, "4": 0x193, "5": 0x1C5, "6": 0x228, "7": 0x23F,
"c1": 0x30C, "c2": 0x303,
"moblin_cave": 0x2E1,
"armos_temple": 0x27F,

View File

@@ -1,7 +1,6 @@
from ..backgroundEditor import BackgroundEditor
from .aesthetics import rgb_to_bin, bin_to_rgb, prepatch
import copy
import pkgutil
CHAR_MAP = {'z': 0x3E, '-': 0x3F, '.': 0x39, ':': 0x42, '?': 0x3C, '!': 0x3D}
def _encode(s):
@@ -18,17 +17,18 @@ def _encode(s):
return result
def setRomInfo(rom, seed, seed_name, settings, player_name, player_id):
def setRomInfo(rom, patch_data):
seed_name = patch_data["seed_name"]
try:
seednr = int(seed, 16)
seednr = int(patch_data["seed"], 16)
except:
import hashlib
seednr = int(hashlib.md5(seed).hexdigest(), 16)
seednr = int(hashlib.md5(str(patch_data["seed"]).encode()).hexdigest(), 16)
if settings.race:
if patch_data["is_race"]:
seed_name = "Race"
if isinstance(settings.race, str):
seed_name += " " + settings.race
if isinstance(patch_data["is_race"], str):
seed_name += " " + patch_data["is_race"]
rom.patch(0x00, 0x07, "00", "01")
else:
rom.patch(0x00, 0x07, "00", "52")
@@ -37,7 +37,7 @@ def setRomInfo(rom, seed, seed_name, settings, player_name, player_id):
#line_2_hex = _encode(seed[16:])
BASE_DRAWING_AREA = 0x98a0
LINE_WIDTH = 0x20
player_id_text = f"Player {player_id}:"
player_id_text = f"Player {patch_data['player']}:"
for n in (3, 4):
be = BackgroundEditor(rom, n)
ba = BackgroundEditor(rom, n, attributes=True)
@@ -45,9 +45,9 @@ def setRomInfo(rom, seed, seed_name, settings, player_name, player_id):
for n, v in enumerate(_encode(player_id_text)):
be.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 5 + 2 + n] = v
ba.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 5 + 2 + n] = 0x00
for n, v in enumerate(_encode(player_name)):
be.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 6 + 0x13 - len(player_name) + n] = v
ba.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 6 + 0x13 - len(player_name) + n] = 0x00
for n, v in enumerate(_encode(patch_data['player_name'])):
be.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 6 + 0x13 - len(patch_data['player_name']) + n] = v
ba.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 6 + 0x13 - len(patch_data['player_name']) + n] = 0x00
for n, v in enumerate(line_1_hex):
be.tiles[0x9a20 + n] = v
ba.tiles[0x9a20 + n] = 0x00

View File

@@ -387,7 +387,7 @@ def patchVarious(rom, settings):
# Boomerang trade guy
# if settings.boomerang not in {'trade', 'gift'} or settings.overworld in {'normal', 'nodungeons'}:
if settings.tradequest:
if settings["tradequest"]:
# Update magnifier checks
rom.patch(0x19, 0x05EC, ASM("ld a, [wTradeSequenceItem]\ncp $0E\njp nz, $7E61"), ASM("ld a, [wTradeSequenceItem2]\nand $20\njp z, $7E61")) # show the guy
rom.patch(0x00, 0x3199, ASM("ld a, [wTradeSequenceItem]\ncp $0E\njr nz, $06"), ASM("ld a, [wTradeSequenceItem2]\nand $20\njr z, $06")) # load the proper room layout

View File

@@ -7,9 +7,7 @@ h2b = binascii.unhexlify
class ROM:
def __init__(self, filename, patches=None):
data = open(Utils.user_path(filename), "rb").read()
def __init__(self, data, patches=None):
if patches:
for patch in patches:
data = bsdiff4.patch(data, patch)
@@ -64,18 +62,10 @@ class ROM:
self.banks[0][0x14E] = checksum >> 8
self.banks[0][0x14F] = checksum & 0xFF
def save(self, file, *, name=None):
def save(self):
# don't pass the name to fixHeader
self.fixHeader()
if isinstance(file, str):
f = open(file, "wb")
for bank in self.banks:
f.write(bank)
f.close()
print("Saved:", file)
else:
for bank in self.banks:
file.write(bank)
return b"".join(self.banks)
def readHexSeed(self):
return self.banks[0x3E][0x2F00:0x2F10].hex().upper()

View File

@@ -181,8 +181,8 @@ class IndoorRoomSpriteData(PointerTable):
class ROMWithTables(ROM):
def __init__(self, filename, patches=None):
super().__init__(filename, patches)
def __init__(self, data, patches=None):
super().__init__(data, patches)
# Ability to patch any text in the game with different text
self.texts = Texts(self)
@@ -203,7 +203,7 @@ class ROMWithTables(ROM):
self.itemNames = {}
def save(self, filename, *, name=None):
def save(self):
# Assert special handling of bank 9 expansion is fine
for i in range(0x3d42, 0x4000):
assert self.banks[9][i] == 0, self.banks[9][i]
@@ -221,4 +221,4 @@ class ROMWithTables(ROM):
self.room_sprite_data_indoor.store(self)
self.background_tiles.store(self)
self.background_attributes.store(self)
super().save(filename, name=name)
return super().save()