diff --git a/worlds/ladx/LADXR/generator.py b/worlds/ladx/LADXR/generator.py index cb9de281..08552081 100644 --- a/worlds/ladx/LADXR/generator.py +++ b/worlds/ladx/LADXR/generator.py @@ -2,6 +2,7 @@ import binascii import importlib.util import importlib.machinery import os +import pkgutil from .romTables import ROMWithTables from . import assembler @@ -61,7 +62,12 @@ from ..Options import TrendyGame, Palette, MusicChangeCondition # Function to generate a final rom, this patches the rom with all required patches def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, multiworld=None, player_name=None, player_names=[], player_id = 0): - rom = ROMWithTables(args.input_filename) + rom_patches = [] + + if ap_settings["ap_title_screen"]: + rom_patches.append(pkgutil.get_data(__name__, "patches/title_screen.bdiff4")) + + rom = ROMWithTables(args.input_filename, rom_patches) rom.player_names = player_names pymods = [] if args.pymod: @@ -271,6 +277,8 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m patches.core.warpHome(rom) # Needs to be done after setting the start location. patches.titleScreen.setRomInfo(rom, auth, seed_name, settings, player_name, player_id) + if ap_settings["ap_title_screen"]: + patches.titleScreen.setTitleGraphics(rom) patches.endscreen.updateEndScreen(rom) patches.aesthetics.updateSpriteData(rom) if args.doubletrouble: @@ -363,15 +371,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m if x > max: return max return x - def bin_to_rgb(word): - red = word & 0b11111 - word >>= 5 - green = word & 0b11111 - word >>= 5 - blue = word & 0b11111 - return (red, green, blue) - def rgb_to_bin(r, g, b): - return (b << 10) | (g << 5) | r + from patches.aesthetics import rgb_to_bin, bin_to_rgb for address in range(start, end, 2): packed = (rom.banks[bank][address + 1] << 8) | rom.banks[bank][address] diff --git a/worlds/ladx/LADXR/patches/aesthetics.py b/worlds/ladx/LADXR/patches/aesthetics.py index ff8cd5d8..2975c804 100644 --- a/worlds/ladx/LADXR/patches/aesthetics.py +++ b/worlds/ladx/LADXR/patches/aesthetics.py @@ -434,3 +434,15 @@ noChange: rom.room_sprite_data_overworld[room_nr] = data else: rom.room_sprite_data_indoor[room_nr - 0x100] = data + + +def bin_to_rgb(word): + red = word & 0b11111 + word >>= 5 + green = word & 0b11111 + word >>= 5 + blue = word & 0b11111 + return (red, green, blue) + +def rgb_to_bin(r, g, b): + return (b << 10) | (g << 5) | r diff --git a/worlds/ladx/LADXR/patches/endscreen.py b/worlds/ladx/LADXR/patches/endscreen.py index 3a8b4c2d..94a03fc6 100644 --- a/worlds/ladx/LADXR/patches/endscreen.py +++ b/worlds/ladx/LADXR/patches/endscreen.py @@ -136,4 +136,4 @@ loadLoop2: addr = 0x1000 data = pkgutil.get_data(__name__, "nyan.bin") rom.banks[0x3F][addr : addr + len(data)] = data - + diff --git a/worlds/ladx/LADXR/patches/titleScreen.py b/worlds/ladx/LADXR/patches/titleScreen.py index b81f1460..3a4dade2 100644 --- a/worlds/ladx/LADXR/patches/titleScreen.py +++ b/worlds/ladx/LADXR/patches/titleScreen.py @@ -1,11 +1,9 @@ from ..backgroundEditor import BackgroundEditor -import subprocess -import binascii - - +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): result = bytearray() for char in s: @@ -82,3 +80,68 @@ def setRomInfo(rom, seed, seed_name, settings, player_name, player_id): ba.tiles[0x9820 + n] = 0x08 | pal be.store(rom) ba.store(rom) + +def setTitleGraphics(rom): + BASE = 0x9800 + ROW_SIZE = 0x20 + + be = BackgroundEditor(rom, 0x11, attributes=True) + for tile in be.tiles: + if be.tiles[tile] == 7: + be.tiles[tile] = 3 + + be.tiles[BASE + 10 * ROW_SIZE + 8] = 7 + be.tiles[BASE + 10 * ROW_SIZE + 10] = 2 + be.tiles[BASE + 10 * ROW_SIZE + 11] = 5 + be.tiles[BASE + 11 * ROW_SIZE + 10] = 6 + be.tiles[BASE + 11 * ROW_SIZE + 11] = 6 + be.tiles[BASE + 12 * ROW_SIZE + 11] = 6 + be.tiles[BASE + 11 * ROW_SIZE + 9] = 1 + be.tiles[BASE + 12 * ROW_SIZE + 9] = 1 + be.tiles[BASE + 12 * ROW_SIZE + 10] = 1 + be.tiles[BASE + 13 * ROW_SIZE + 9] = 1 + be.tiles[BASE + 13 * ROW_SIZE + 10] = 1 + + be.store(rom) + + SKIP_INTRO = True + if SKIP_INTRO: + # Skip intro as it's causing problems + rom.banks[1][0x2F5B : 0x2F5B + 3] = [0xC3, 0x39, 0x6E] + # Disable initial music + rom.banks[1][0x2F03 : 0x2F03 + 5] = [0] * 5 + # Disable music fade on reset + rom.banks[1][0x3436 : 0x3436 + 3] = [0] * 3 + + + # Set egg palette + BASE = 0x3DEE + palettes = [] + BANK = 0x21 + for i in range(8): + palette = [] + for c in range(4): + address = BASE + i * 8 + c * 2 + packed = (rom.banks[BANK][address + 1] << 8) | rom.banks[BANK][address] + r,g,b = bin_to_rgb(packed) + palette.append([r, g, b]) + palettes.append(palette) + + for i in [1, 2, 5, 6, 7]: + palettes[i] = copy.copy(palettes[3]) + + def to_5_bit(r, g, b): + return [r >> 3, g >> 3, b >> 3] + + palettes[1][3] = to_5_bit(0xFF, 0x80, 145) + palettes[2][2] = to_5_bit(119, 198, 155) + palettes[5][3] = to_5_bit(119, 198, 155) + palettes[6][3] = to_5_bit(192, 139, 215) + palettes[7][3] = to_5_bit(229, 196, 139) + + for i in range(8): + for c in range(4): + address = BASE + i * 8 + c * 2 + packed = rgb_to_bin(*palettes[i][c]) + rom.banks[BANK][address] = packed & 0xFF + rom.banks[BANK][address + 1] = packed >> 8 diff --git a/worlds/ladx/LADXR/patches/title_screen.bdiff4 b/worlds/ladx/LADXR/patches/title_screen.bdiff4 new file mode 100644 index 00000000..89e9d644 Binary files /dev/null and b/worlds/ladx/LADXR/patches/title_screen.bdiff4 differ diff --git a/worlds/ladx/LADXR/rom.py b/worlds/ladx/LADXR/rom.py index baab4430..21969f4a 100644 --- a/worlds/ladx/LADXR/rom.py +++ b/worlds/ladx/LADXR/rom.py @@ -1,3 +1,4 @@ +import bsdiff4 import binascii import Utils @@ -6,9 +7,13 @@ h2b = binascii.unhexlify class ROM: - def __init__(self, filename): + def __init__(self, filename, patches=None): data = open(Utils.user_path(filename), "rb").read() - #assert len(data) == 1024 * 1024 + + if patches: + for patch in patches: + data = bsdiff4.patch(data, patch) + self.banks = [] for n in range(0x40): self.banks.append(bytearray(data[n*0x4000:(n+1)*0x4000])) diff --git a/worlds/ladx/LADXR/romTables.py b/worlds/ladx/LADXR/romTables.py index fbabe759..577fa7da 100644 --- a/worlds/ladx/LADXR/romTables.py +++ b/worlds/ladx/LADXR/romTables.py @@ -180,8 +180,8 @@ class IndoorRoomSpriteData(PointerTable): class ROMWithTables(ROM): - def __init__(self, filename): - super().__init__(filename) + def __init__(self, filename, patches=None): + super().__init__(filename, patches) # Ability to patch any text in the game with different text self.texts = Texts(self) diff --git a/worlds/ladx/Options.py b/worlds/ladx/Options.py index 8d301866..8089bd9a 100644 --- a/worlds/ladx/Options.py +++ b/worlds/ladx/Options.py @@ -79,6 +79,12 @@ class DungeonShuffle(DefaultOffToggle, LADXROption): """ ladxr_name = "dungeonshuffle" +class APTitleScreen(DefaultOnToggle): + """ + Enables AP specific title screen and disables the intro cutscene + """ + + class BossShuffle(Choice): none = 0 shuffle = 1 @@ -396,5 +402,6 @@ links_awakening_options: typing.Dict[str, typing.Type[Option]] = { 'shuffle_compasses': ShuffleCompasses, 'shuffle_stone_beaks': ShuffleStoneBeaks, 'music_change_condition': MusicChangeCondition, - 'nag_messages': NagMessages + 'nag_messages': NagMessages, + 'ap_title_screen': APTitleScreen, } diff --git a/worlds/ladx/__init__.py b/worlds/ladx/__init__.py index 47c601a1..0ec22e38 100644 --- a/worlds/ladx/__init__.py +++ b/worlds/ladx/__init__.py @@ -381,16 +381,14 @@ class LinksAwakeningWorld(World): # Kind of kludge, make it possible for the location to differentiate between local and remote items loc.ladxr_item.location_owner = self.player - rom_path = "Legend of Zelda, The - Link's Awakening DX (USA, Europe) (SGB Enhanced).gbc" + rom_name = "Legend of Zelda, The - Link's Awakening DX (USA, Europe) (SGB Enhanced).gbc" out_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.player_name[self.player]}.gbc" - out_file = os.path.join(output_directory, out_name) - - rompath = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gbc") + out_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gbc") parser = get_parser() - args = parser.parse_args([rom_path, "-o", out_name, "--dump"]) + args = parser.parse_args([rom_name, "-o", out_name, "--dump"]) name_for_rom = self.multiworld.player_name[self.player] @@ -408,14 +406,15 @@ class LinksAwakeningWorld(World): player_names=all_names, player_id = self.player) - handle = open(rompath, "wb") + handle = open(out_path, "wb") rom.save(handle, name="LADXR") + handle.close() - patch = LADXDeltaPatch(os.path.splitext(rompath)[0]+LADXDeltaPatch.patch_file_ending, player=self.player, - player_name=self.multiworld.player_name[self.player], patched_path=rompath) + patch = LADXDeltaPatch(os.path.splitext(out_path)[0]+LADXDeltaPatch.patch_file_ending, player=self.player, + player_name=self.multiworld.player_name[self.player], patched_path=out_path) patch.write() if not DEVELOPER_MODE: - os.unlink(rompath) + os.unlink(out_path) def generate_multi_key(self): return bytearray(self.multiworld.random.getrandbits(8) for _ in range(10)) + self.player.to_bytes(2, 'big') diff --git a/worlds/ladx/docs/en_Links Awakening DX.md b/worlds/ladx/docs/en_Links Awakening DX.md index 4e109284..bceda4bc 100644 --- a/worlds/ladx/docs/en_Links Awakening DX.md +++ b/worlds/ladx/docs/en_Links Awakening DX.md @@ -42,7 +42,7 @@ This randomizer is based on (forked from) the wonderful work daid did on LADXR - The autotracker code for communication with magpie tracker is directly copied from kbranch's repo - https://github.com/kbranch/Magpie/tree/master/autotracking -### Sprites +### Graphics The following sprite sheets have been included with permission of their respective authors: @@ -56,6 +56,8 @@ The following sprite sheets have been included with permission of their respecti * Richard * Tarin +Title screen graphics by toomanyteeth✨ (https://instagram.com/toomanyyyteeth) + ## Some tips from LADXR...