def upgradeHealthContainers(rom): # Reuse 2 unused shop messages for the heart containers. rom.texts[0x2A] = formatText("You found a {HEART_CONTAINER}!") rom.texts[0x2B] = formatText("You lost a heart!") rom.patch(0x03, 0x19DC, ASM(""" ld de, $59D8 call $3BC0 """), ASM(""" ld a, $05 ; renderHeartPiece rst 8 """), fill_nop=True) rom.patch(0x03, 0x19F0, ASM(""" ld hl, $DB5B inc [hl] ld hl, $DB93 ld [hl], $FF """), ASM(""" ld a, $06 ; giveItemMultiworld rst 8 ld a, $0A ; messageForItemMultiworld rst 8 skip: """), fill_nop=True) # add heart->remove heart on heart container
def bugfixBossroomTopPush(rom): rom.patch(0x14, 0x14D9, ASM(""" ldh a, [$99] dec a ldh [$99], a """), ASM(""" jp $7F80 """), fill_nop=True) rom.patch(0x14, 0x3F80, "00" * 0x80, ASM(""" ldh a, [$99] cp $50 jr nc, up down: inc a ldh [$99], a jp $54DE up: dec a ldh [$99], a jp $54DE """), fill_nop=True)
def upgradeOverworldOwlStatues(rom): # Replace the code that handles signs/owl statues on the overworld # This removes a "have marin with you" special case to make some room for our custom owl handling. rom.patch(0x00, 0x201A, ASM(""" cp $6F jr z, $2B cp $D4 jr z, $27 ld a, [$DB73] and a jr z, $08 ld a, $78 call $237C jp $20CF """), ASM(""" cp $D4 jr z, $2B cp $6F jr nz, skip ld a, $09 rst 8 jp $20CF skip: """), fill_nop=True)
def fixSeashell(rom): # Do not unload if we have the lvl2 sword. rom.patch(0x03, 0x1FD3, ASM("ld a, [$DB4E]\ncp $02\njp nc, $3F8D"), "", fill_nop=True) # Do not unload in the ghost house rom.patch(0x03, 0x1FE8, ASM("ldh a, [$F8]\nand $40\njp z, $3F8D"), "", fill_nop=True) # Call our special rendering code rom.patch(0x03, 0x1FF2, ASM("ld de, $5FD1\ncall $3C77"), ASM("ld a, $05\nrst 8"), fill_nop=True) # Call our special handlers for messages and pickup rom.patch(0x03, 0x2368, 0x237C, ASM(""" ld a, $0A ; showMessageMultiworld rst 8 ld a, $06 ; giveItemMultiworld rst 8 call $512A ret """), fill_nop=True)
def flameThrowerShieldRequirement(rom): # if you somehow get a lvl3 shield or higher, it no longer works against the flamethrower, easy fix. rom.patch( 0x03, 0x2EBA, ASM("ld a, [$DB44]\ncp $02\nret nz"), # if not shield level 2 ASM("ld a, [$DB44]\ncp $02\nret c")) # if not shield level 2 or higher
def fixInstruments(rom): rom.patch(0x03, 0x1EA9, 0x1EAE, "", fill_nop=True) rom.patch(0x03, 0x1EB9, 0x1EC8, ASM(""" ; Render sprite ld a, $05 rst 8 """), fill_nop=True) # Patch the message and instrument giving code rom.patch(0x03, 0x1EE3, 0x1EF6, ASM(""" ; Handle item effect ld a, $06 ; giveItemMultiworld rst 8 ;Show message ld a, $0A ; showMessageMultiworld rst 8 """), fill_nop=True) # Color cycle palette 7 instead of 1 rom.patch(0x36, 0x30F0, ASM("ld de, $DC5C"), ASM("ld de, $DC84"))
def upgradeDungeonOwlStatues(rom): # Call our custom handler after the check for the stone beak rom.patch(0x18, 0x1EA2, ASM("ldh a, [$F7]\ncp $FF\njr nz, $05"), ASM("ld a, $09\nrst 8\nret"), fill_nop=True)
def fixWrongWarp(rom): rom.patch(0x00, 0x18CE, ASM("cp $04"), ASM("cp $03")) re = RoomEditor(rom, 0x2b) for x in range(10): re.removeObject(x, 7) re.objects.append(ObjectHorizontal(0, 7, 0x2C, 10)) while len(re.getWarps()) < 4: re.objects.append(ObjectWarp(1, 3, 0x7a, 80, 124)) re.store(rom)
def removeFlashingLights(rom): # Remove the switching between two backgrounds at mamu, always show the spotlights. rom.patch(0x00, 0x01EB, ASM("ldh a, [$E7]\nrrca\nand $80"), ASM("ld a, $80"), fill_nop=True) # Remove flashing colors from shopkeeper killing you after stealing and the mad batter giving items. rom.patch(0x24, 0x3B77, ASM("push bc"), ASM("ret"))
def injectMainLoop(rom): rom.patch(0x00, 0x0346, ASM(""" ldh a, [$FE] and a jr z, $08 """), ASM(""" ; Call the mainloop handler xor a rst 8 """), fill_nop=True)
def patch(self, rom, option, *, multiworld=None): rom.banks[0x14][self.addr] = CHEST_ITEMS[option] if self.room == 0x1B6: # Patch the code that gives the nightmare key when you throw the pot at the chest in dungeon 6 # As this is hardcoded for a specific chest type rom.patch(3, 0x145D, ASM("ld a, $19"), ASM("ld a, $%02x" % (CHEST_ITEMS[option]))) if multiworld is not None: rom.banks[0x3E][0x3300 + self.room] = multiworld
def disablePhotoPrint(rom): rom.patch(0x28, 0x07CC, ASM("ldh [$01], a\nldh [$02], a"), "", fill_nop=True) # do not reset the serial link rom.patch(0x28, 0x0483, ASM("ld a, $13"), ASM("jr $EA", 0x4483)) # Do not print on A press, but jump to cancel rom.patch(0x28, 0x0492, ASM("ld hl, $4439"), ASM("ret"), fill_nop=True) # Do not show the print/cancel overlay
def noMusic(rom): rom.patch(0x1B, 0x001E, ASM("ld hl, $D368\nldi a, [hl]"), ASM("xor a"), fill_nop=True) rom.patch(0x1E, 0x001E, ASM("ld hl, $D368\nldi a, [hl]"), ASM("xor a"), fill_nop=True)
def forceLinksPalette(rom, index): # This forces the link sprite into a specific palette index ignoring the tunic options. rom.patch(0, 0x1D8C, ASM("ld a, [$DC0F]\nand a\njr z, $03\ninc a"), ASM("ld a, $%02X" % (index)), fill_nop=True) rom.patch(0, 0x1DD2, ASM("ld a, [$DC0F]\nand a\njr z, $03\ninc a"), ASM("ld a, $%02X" % (index)), fill_nop=True)
def quickswap(rom, button): rom.patch(0x00, 0x1094, ASM("jr c, $49"), ASM("jr nz, $49")) # prevent agressive key repeat rom.patch(0x00, 0x10BC, # Patch the open minimap code to swap the your items instead ASM("xor a\nld [$C16B], a\nld [$C16C], a\nld [$DB96], a\nld a, $07\nld [$DB95], a"), ASM(""" ld a, [$DB%02X] ld e, a ld a, [$DB%02X] ld [$DB%02X], a ld a, e ld [$DB%02X], a ret """ % (button, button + 2, button, button + 2)))
def patch(self, rom, option, *, multiworld=None): assert multiworld is None if self.give_bowwow: option = BOWWOW rom.texts[0xC8] = formatText(b"Got BowWow!") if option != SHIELD: rom.patch( 5, 0x0CDA, ASM("ld a, $22"), ASM("ld a, $00") ) # do not change links sprite into the one with a shield super().patch(rom, option)
def fixHeartPiece(rom): # Patch all locations where the piece of heart is rendered. rom.patch(0x03, 0x1b52, ASM("ld de, $5A4D\ncall $3BC0"), ASM("ld a, $04\nrst 8"), fill_nop=True) # state 0 # Write custom code in the first state handler, this overwrites all state handlers # Till state 5. rom.patch(0x03, 0x1A74, 0x1A98, ASM(""" ; Render sprite ld a, $05 rst 8 ; Handle item effect ld a, $06 ; giveItemMultiworld rst 8 ;Show message ld a, $0A ; showMessageMultiworld rst 8 ; Switch to state 5 ld hl, $C290; stateTable add hl, bc ld [hl], $05 ret """), fill_nop=True) # Insert a state 5 handler rom.patch(0x03, 0x1A98, 0x1B17, ASM(""" ; Render sprite ld a, $05 rst 8 ld a, [$C19F] ; dialog state and a ret nz call $512A ; mark room as done call $3F8D ; unload entity ret """), fill_nop=True)
def noSwordMusic(rom): # Skip no-sword music override # Instead of loading the sword level, we put the value 1 in the A register, indicating we have a sword. rom.patch(2, 0x0151, ASM("ld a, [$DB4E]"), ASM("ld a, $01"), fill_nop=True) rom.patch(2, 0x3AEF, ASM("ld a, [$DB4E]"), ASM("ld a, $01"), fill_nop=True) rom.patch(3, 0x0996, ASM("ld a, [$DB4E]"), ASM("ld a, $01"), fill_nop=True) rom.patch(3, 0x0B35, ASM("ld a, [$DB44]"), ASM("ld a, $01"), fill_nop=True)
def allowRaftGameWithoutFlippers(rom): # Allow jumping down the waterfall in the raft game without the flippers. rom.patch(0x02, 0x2E8F, ASM("ld a, [$DB0C]"), ASM("ld a, $01"), fill_nop=True) # Change the room that goes back up to the raft game from the bottom, so we no longer need flippers re = RoomEditor(rom, 0x1F7) re.changeObject(3, 2, 0x1B) re.changeObject(2, 3, 0x1B) re.changeObject(3, 4, 0x1B) re.changeObject(4, 5, 0x1B) re.changeObject(6, 6, 0x1B) re.store(rom)
def patchOverworldTilesets(rom): rom.patch(0x00, 0x0D5B, 0x0D79, ASM(""" ; Instead of loading tileset info from a small 8x8 table, load it from a 16x16 table to give ; full control. ; A=MapRoom ld hl, $2100 ld [hl], $3F ld d, $00 ld e, a ld hl, $6F00 add hl, de ldh a, [$94] ; We need to load the currently loaded tileset in E to compare it ld e, a ld a, [hl] ld hl, $2100 ld [hl], $20 """), fill_nop=True) # Remove the camera shop exception rom.patch(0x00, 0x0D80, 0x0D8B, "", fill_nop=True) for x in range(16): for y in range(16): rom.banks[0x3F][0x2F00 + x + y * 16] = rom.banks[0x20][0x2E73 + (x // 2) + (y // 2) * 8] rom.banks[0x3F][0x2F07] = rom.banks[0x3F][ 0x2F08] # Fix the room next to the egg # Fix the rooms around the camera shop rom.banks[0x3F][0x2F26] = 0x0F rom.banks[0x3F][0x2F27] = 0x0F rom.banks[0x3F][0x2F36] = 0x0F
def upgradeTunicFairy(rom): rom.texts[0x268] = formatText(b"Welcome, #####. I admire you for coming this far.") rom.texts[0x0CC] = formatText(b"Got the Red Tunic! You can change Tunics at the phone booths.") rom.texts[0x0CD] = formatText(b"Got the Blue Tunic! You can change Tunics at the phone booths.") rom.patch(0x36, 0x111C, 0x1133, ASM(""" call $3B12 ld a, [$DDE1] and $10 jr z, giveItems ld [hl], $09 ret giveItems: ld a, [$DDE1] or $10 ld [$DDE1], a """), fill_nop=True) rom.patch(0x36, 0x1139, 0x1144, ASM(""" ld a, [$51BF] ldh [$F1], a ld a, $02 rst 8 ld a, $03 rst 8 """), fill_nop=True) rom.patch(0x36, 0x1162, 0x1192, ASM(""" ld a, [$51C0] ldh [$F1], a ld a, $02 rst 8 ld a, $03 rst 8 call $3B12 ret """), fill_nop=True) rom.patch(0x36, 0x119D, 0x11A2, "", fill_nop=True) rom.patch(0x36, 0x11B5, 0x11BE, ASM(""" ; Skip to the end ignoring all the tunic giving animation. call $3B12 ld [hl], $09 """), fill_nop=True) rom.banks[0x36][0x11BF] = 0x87 rom.banks[0x36][0x11C0] = 0x88
def patch(self, rom, option, *, multiworld=None): assert multiworld is None if self.give_bowwow: option = BOWWOW rom.texts[0xC8] = formatText("Got BowWow!") if option != SHIELD: rom.patch( 5, 0x0CDA, ASM("ld a, $22"), ASM("ld a, $00") ) # do not change links sprite into the one with a shield if option in (MAGIC_POWDER, BOMB): re = RoomEditor(rom, 0x0A2) re.entities.append((1, 3, 0x41)) re.store(rom) super().patch(rom, option)
def save(self, filename, *, name=None): self.texts.store(self) self.entities.store(self) self.rooms_overworld_top.store(self) self.rooms_overworld_bottom.store(self) self.rooms_indoor_a.store(self) self.rooms_indoor_b.store(self) self.rooms_color_dungeon.store(self) leftover_storage = self.room_sprite_data_overworld.store(self) self.room_sprite_data_indoor.addStorage(leftover_storage) self.patch( 0x00, 0x0DFA, ASM("ld hl, $763B"), ASM("ld hl, $%04x" % (leftover_storage[0]["start"] | 0x4000))) self.room_sprite_data_indoor.adjustDataStart( leftover_storage[0]["start"]) self.room_sprite_data_indoor.store(self) self.background_tiles.store(self) self.background_attributes.store(self) super().save(filename, name=name)
def patch(self, rom, option, *, multiworld=None): super().patch(rom, option, multiworld=multiworld) re = RoomEditor(rom, self.room) # Make the bird key accessible without the rooster re.removeObject(1, 6) re.removeObject(2, 6) re.removeObject(3, 5) re.removeObject(3, 6) re.moveObject(1, 5, 2, 6) re.moveObject(2, 5, 3, 6) re.addEntity(3, 5, 0x9D) re.store(rom) rom.patch(0x19, 0x0010, "F0007806F008782600007A0600087A26", "F000640FF008642F0000660F0008662F") rom.patch(0x19, 0x004F, ASM("cp $01"), ASM("cp $0A")) # Do not give the rooster rom.patch(0x19, 0x0E9D, ASM("ld [$DB7B], a"), "", fill_nop=True)
def updateSpriteData(rom): # Remove all the special sprite change exceptions. rom.patch(0x00, 0x0DAD, 0x0DDB, ASM("jp $0DDB"), fill_nop=True) # For each room update the sprite load data based on which entities are in there. for room_nr in range(0x316): if room_nr == 0x2FF: continue values = [None, None, None, None] if room_nr == 0x00E: # D7 entrance opening values[2] = 0xD6 values[3] = 0xD7 if 0x211 <= room_nr <= 0x21E: # D7 throwing ball thing. values[0] = 0x66 r = RoomEditor(rom, room_nr) for obj in r.objects: if obj.type_id == 0xC5 and room_nr < 0x100: # Pushable Gravestone values[3] = 0x82 for x, y, entity in r.entities: sprite_data = entityData.SPRITE_DATA[entity] if callable(sprite_data): sprite_data = sprite_data(r) if sprite_data is None: continue for m in range(0, len(sprite_data), 2): idx, value = sprite_data[m:m + 2] if values[idx] is None: values[idx] = value elif isinstance(values[idx], set) and isinstance(value, set): values[idx] = values[idx].intersection(value) assert len(values[idx]) > 0 elif isinstance(values[idx], set) and value in values[idx]: values[idx] = value elif isinstance(value, set) and values[idx] in value: pass elif values[idx] == value: pass else: assert False, "%03x: %02x (%s %s)" % (room_nr, entity, values[idx], value) data = bytearray() for v in values: if isinstance(v, set): v = next(iter(v)) elif v is None: v = 0xff data.append(v) if room_nr < 0x100: rom.room_sprite_data_overworld[room_nr] = data else: rom.room_sprite_data_indoor[room_nr - 0x100] = data
def removeBirdKeyHoleDrop(rom): # Prevent the cave with the bird key from dropping you in the water # (if you do not have flippers this would softlock you) rom.patch( 0x02, 0x1176, ASM(""" ldh a, [$F7] cp $0A jr nz, $30 """), ASM(""" nop nop nop nop jr $30 """)) # Remove the hole that drops you all the way from dungeon7 entrance to the water in the cave re = RoomEditor(rom, 0x01E) re.removeObject(5, 4) re.store(rom)
def onlyDropBombsWhenHaveBombs(rom): rom.patch(0x03, 0x1FC5, ASM("call $608C"), ASM("call $50B2")) # We use some of the unused chest code space here to remove the bomb if you do not have bombs in your inventory. rom.patch(0x03, 0x10B2, 0x112A, ASM(""" ld e, INV_SIZE ld hl, $DB00 ld a, $02 loop: cp [hl] jr z, resume dec e inc hl jr nz, loop jp $3F8D ; unload entity resume: jp $608C """), fill_nop=True)
def updateFinishingMinigame(rom): rom.patch(0x04, 0x26BE, 0x26DF, ASM(""" ld a, $0B ; GiveItemAndMessageForRoom rst 8 ; Mark selection as stopping minigame, as we are not asking a question. ld a, $01 ld [$C177], a """), fill_nop=True)
def patch(self, rom, option, *, multiworld=None): if option != SWORD or multiworld is not None: # Set the heart piece data super().patch(rom, option, multiworld=multiworld) # Patch the room to contain a heart piece instead of the sword on the beach re = RoomEditor(rom, 0x0F2) re.removeEntities(0x31) # remove sword re.addEntity(5, 5, 0x35) # add heart piece re.store(rom) # Prevent shield drops from the like-like from turning into swords. rom.patch(0x03, 0x1B9C, ASM("ld a, [$DB4E]"), ASM("ld a, $01"), fill_nop=True) rom.patch(0x03, 0x244D, ASM("ld a, [$DB4E]"), ASM("ld a, $01"), fill_nop=True)
def updateFinishingMinigame(rom): rom.patch(0x04, 0x26BE, 0x26DF, ASM(""" ld a, $0E ; GiveItemAndMessageForRoomMultiworld rst 8 ; Mark selection as stopping minigame, as we are not asking a question. ld a, $01 ld [$C177], a ; Check if we got rupees from the item skip getting rupees from the fish. ld a, [$DB90] ld hl, $DB8F or [hl] jp nz, $66FE """), fill_nop=True)