Links Awakening: Implement New Game (#1334)

Adds Link's Awakening: DX. Fully imports and forks LADXR, with permission - https://github.com/daid/LADXR
This commit is contained in:
zig-for
2023-03-21 01:26:03 +09:00
committed by GitHub
parent 67bf12369a
commit 81a239325d
180 changed files with 24191 additions and 2 deletions

View File

@@ -0,0 +1,436 @@
from ..assembler import ASM
from ..utils import formatText, setReplacementName
from ..roomEditor import RoomEditor
from .. import entityData
import os
import bsdiff4
def imageTo2bpp(filename):
import PIL.Image
baseimg = PIL.Image.new('P', (1,1))
baseimg.putpalette((
128, 0, 128,
0, 0, 0,
128, 128, 128,
255, 255, 255,
))
img = PIL.Image.open(filename)
img = img.quantize(colors=4, palette=baseimg)
print (f"Palette: {img.getpalette()}")
assert (img.size[0] % 8) == 0
tileheight = 8 if img.size[1] == 8 else 16
assert (img.size[1] % tileheight) == 0
cols = img.size[0] // 8
rows = img.size[1] // tileheight
result = bytearray(rows * cols * tileheight * 2)
index = 0
for ty in range(rows):
for tx in range(cols):
for y in range(tileheight):
a = 0
b = 0
for x in range(8):
c = img.getpixel((tx * 8 + x, ty * 16 + y))
if c & 1:
a |= 0x80 >> x
if c & 2:
b |= 0x80 >> x
result[index] = a
result[index+1] = b
index += 2
return result
def updateGraphics(rom, bank, offset, data):
if offset + len(data) > 0x4000:
updateGraphics(rom, bank, offset, data[:0x4000-offset])
updateGraphics(rom, bank + 1, 0, data[0x4000 - offset:])
else:
rom.banks[bank][offset:offset+len(data)] = data
if bank < 0x34:
rom.banks[bank-0x20][offset:offset + len(data)] = data
def gfxMod(rom, filename):
if os.path.exists(filename + ".names"):
for line in open(filename + ".names", "rt"):
if ":" in line:
k, v = line.strip().split(":", 1)
setReplacementName(k, v)
ext = os.path.splitext(filename)[1].lower()
if ext == ".bin":
updateGraphics(rom, 0x2C, 0, open(filename, "rb").read())
elif ext in (".png", ".bmp"):
updateGraphics(rom, 0x2C, 0, imageTo2bpp(filename))
elif ext == ".bdiff":
updateGraphics(rom, 0x2C, 0, prepatch(rom, 0x2C, 0, filename))
elif ext == ".json":
import json
data = json.load(open(filename, "rt"))
for patch in data:
if "gfx" in patch:
updateGraphics(rom, int(patch["bank"], 16), int(patch["offset"], 16), imageTo2bpp(os.path.join(os.path.dirname(filename), patch["gfx"])))
if "name" in patch:
setReplacementName(patch["item"], patch["name"])
else:
updateGraphics(rom, 0x2C, 0, imageTo2bpp(filename))
def createGfxImage(rom, filename):
import PIL.Image
bank_count = 8
img = PIL.Image.new("P", (32 * 8, 32 * 8 * bank_count))
img.putpalette((
128, 0, 128,
0, 0, 0,
128, 128, 128,
255, 255, 255,
))
for bank_nr in range(bank_count):
bank = rom.banks[0x2C + bank_nr]
for tx in range(32):
for ty in range(16):
for y in range(16):
a = bank[tx * 32 + ty * 32 * 32 + y * 2]
b = bank[tx * 32 + ty * 32 * 32 + y * 2 + 1]
for x in range(8):
c = 0
if a & (0x80 >> x):
c |= 1
if b & (0x80 >> x):
c |= 2
img.putpixel((tx*8+x, bank_nr * 32 * 8 + ty*16+y), c)
img.save(filename)
def prepatch(rom, bank, offset, filename):
bank_count = 8
base_sheet = []
result = []
for bank_nr in range(bank_count):
base_sheet[0x4000 * bank_nr:0x4000 * (bank_nr + 1) - 1] = rom.banks[0x2C + bank_nr]
with open(filename, "rb") as patch:
file = patch.read()
result = bsdiff4.patch(src_bytes=bytes(base_sheet), patch_bytes=file)
return result
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 removeNagMessages(rom):
# Remove "this object is heavy, bla bla", and other nag messages when touching an object
rom.patch(0x02, 0x32BB, ASM("ld a, [$C14A]"), ASM("ld a, $01"), fill_nop=True) # crystal blocks
rom.patch(0x02, 0x32EC, ASM("ld a, [$C5A6]"), ASM("ld a, $01"), fill_nop=True) # cracked blocks
rom.patch(0x02, 0x32D3, ASM("jr nz, $25"), ASM("jr $25"), fill_nop=True) # stones/pots
rom.patch(0x02, 0x2B88, ASM("jr nz, $0F"), ASM("jr $0F"), fill_nop=True) # ice blocks
def removeLowHPBeep(rom):
rom.patch(2, 0x233A, ASM("ld hl, $FFF3\nld [hl], $04"), b"", fill_nop=True) # Remove health beep
def slowLowHPBeep(rom):
rom.patch(2, 0x2338, ASM("ld a, $30"), ASM("ld a, $60")) # slow slow hp beep
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 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)
# Fix the waking up from bed palette
if index == 1:
rom.patch(0x21, 0x33FC, "A222", "FF05")
elif index == 2:
rom.patch(0x21, 0x33FC, "A222", "3F14")
elif index == 3:
rom.patch(0x21, 0x33FC, "A222", "037E")
for n in range(6):
rom.patch(0x05, 0x1261 + n * 2, "00", f"{index:02x}")
def fastText(rom):
rom.patch(0x00, 0x24CA, ASM("jp $2485"), ASM("call $2485"))
def noText(rom):
for idx in range(len(rom.texts)):
if not isinstance(rom.texts[idx], int) and (idx < 0x217 or idx > 0x21A):
rom.texts[idx] = rom.texts[idx][-1:]
def reduceMessageLengths(rom, rnd):
# Into text from Marin. Got to go fast, so less text. (This intro text is very long)
rom.texts[0x01] = formatText(rnd.choice([
"Let's a go!",
"Remember, sword goes on A!",
"Avoid the heart piece of shame!",
"Marin? No, this is Zelda. Welcome to Hyrule",
"Why are you in my bed?",
"This is not a Mario game!",
"MuffinJets was here...",
"Remember, there are no bugs in LADX",
"#####, #####, you got to wake up!\nDinner is ready.",
"Go find the stepladder",
"Pizza power!",
"Eastmost penninsula is the secret",
"There is no cow level",
"You cannot lift rocks with your bear hands",
"Thank you, daid!",
"There, there now. Just relax. You've been asleep for almost nine hours now."
]))
# Reduce length of a bunch of common texts
rom.texts[0xEA] = formatText("You've got a Guardian Acorn!")
rom.texts[0xEB] = rom.texts[0xEA]
rom.texts[0xEC] = rom.texts[0xEA]
rom.texts[0x08] = formatText("You got a Piece of Power!")
rom.texts[0xEF] = formatText("You found a {SEASHELL}!")
rom.texts[0xA7] = formatText("You've got the {COMPASS}!")
rom.texts[0x07] = formatText("You need the {NIGHTMARE_KEY}!")
rom.texts[0x8C] = formatText("You need a {KEY}!") # keyhole block
rom.texts[0x09] = formatText("Ahhh... It has the Sleepy {TOADSTOOL}, it does! We'll mix it up something in a jiffy, we will!")
rom.texts[0x0A] = formatText("The last thing I kin remember was bitin' into a big juicy {TOADSTOOL}... Then, I had the darndest dream... I was a raccoon! Yeah, sounds strange, but it sure was fun!")
rom.texts[0x0F] = formatText("You pick the {TOADSTOOL}... As you hold it over your head, a mellow aroma flows into your nostrils.")
rom.texts[0x13] = formatText("You've learned the ^{SONG1}!^ This song will always remain in your heart!")
rom.texts[0x18] = formatText("Will you give me 28 {RUPEES} for my secret?", ask="Give Don't")
rom.texts[0x19] = formatText("How about it? 42 {RUPEES} for my little secret...", ask="Give Don't")
rom.texts[0x1e] = formatText("...You're so cute! I'll give you a 7 {RUPEE} discount!")
rom.texts[0x2d] = formatText("{ARROWS_10}\n10 {RUPEES}!", ask="Buy Don't")
rom.texts[0x32] = formatText("{SHIELD}\n20 {RUPEES}!", ask="Buy Don't")
rom.texts[0x33] = formatText("Ten {BOMB}\n10 {RUPEES}", ask="Buy Don't")
rom.texts[0x3d] = formatText("It's a {SHIELD}! There is space for your name!")
rom.texts[0x42] = formatText("It's 30 {RUPEES}! You can play the game three more times with this!")
rom.texts[0x45] = formatText("How about some fishing, little buddy? I'll only charge you 10 {RUPEES}...", ask="Fish Not Now")
rom.texts[0x4b] = formatText("Wow! Nice Fish! It's a lunker!! I'll give you a 20 {RUPEE} prize! Try again?", ask="Cast Not Now")
rom.texts[0x4e] = formatText("You're short of {RUPEES}? Don't worry about it. You just come back when you have more money, little buddy.")
rom.texts[0x4f] = formatText("You've got a {HEART_PIECE}! Press SELECT on the Subscreen to see.")
rom.texts[0x8e] = formatText("Well, it's an {OCARINA}, but you don't know how to play it...")
rom.texts[0x90] = formatText("You found the {POWER_BRACELET}! At last, you can pick up pots and stones!")
rom.texts[0x91] = formatText("You got your {SHIELD} back! Press the button and repel enemies with it!")
rom.texts[0x93] = formatText("You've got the {HOOKSHOT}! Its chain stretches long when you use it!")
rom.texts[0x94] = formatText("You've got the {MAGIC_ROD}! Now you can burn things! Burn it! Burn, baby burn!")
rom.texts[0x95] = formatText("You've got the {PEGASUS_BOOTS}! If you hold down the Button, you can dash!")
rom.texts[0x96] = formatText("You've got the {OCARINA}! You should learn to play many songs!")
rom.texts[0x97] = formatText("You've got the {FEATHER}! It feels like your body is a lot lighter!")
rom.texts[0x98] = formatText("You've got a {SHOVEL}! Now you can feel the joy of digging!")
rom.texts[0x99] = formatText("You've got some {MAGIC_POWDER}! Try sprinkling it on a variety of things!")
rom.texts[0x9b] = formatText("You found your {SWORD}! It must be yours because it has your name engraved on it!")
rom.texts[0x9c] = formatText("You've got the {FLIPPERS}! If you press the B Button while you swim, you can dive underwater!")
rom.texts[0x9e] = formatText("You've got a new {SWORD}! You should put your name on it right away!")
rom.texts[0x9f] = formatText("You've got a new {SWORD}! You should put your name on it right away!")
rom.texts[0xa0] = formatText("You found the {MEDICINE}! You should apply this and see what happens!")
rom.texts[0xa1] = formatText("You've got the {TAIL_KEY}! Now you can open the Tail Cave gate!")
rom.texts[0xa2] = formatText("You've got the {SLIME_KEY}! Now you can open the gate in Ukuku Prairie!")
rom.texts[0xa3] = formatText("You've got the {ANGLER_KEY}!")
rom.texts[0xa4] = formatText("You've got the {FACE_KEY}!")
rom.texts[0xa5] = formatText("You've got the {BIRD_KEY}!")
rom.texts[0xa6] = formatText("At last, you got a {MAP}! Press the START Button to look at it!")
rom.texts[0xa8] = formatText("You found a {STONE_BEAK}! Let's find the owl statue that belongs to it.")
rom.texts[0xa9] = formatText("You've got the {NIGHTMARE_KEY}! Now you can open the door to the Nightmare's Lair!")
rom.texts[0xaa] = formatText("You got a {KEY}! You can open a locked door.")
rom.texts[0xab] = formatText("You got 20 {RUPEES}! JOY!", center=True)
rom.texts[0xac] = formatText("You got 50 {RUPEES}! Very Nice!", center=True)
rom.texts[0xad] = formatText("You got 100 {RUPEES}! You're Happy!", center=True)
rom.texts[0xae] = formatText("You got 200 {RUPEES}! You're Ecstatic!", center=True)
rom.texts[0xdc] = formatText("Ribbit! Ribbit! I'm Mamu, on vocals! But I don't need to tell you that, do I? Everybody knows me! Want to hang out and listen to us jam? For 300 Rupees, we'll let you listen to a previously unreleased cut! What do you do?", ask="Pay Leave")
rom.texts[0xe8] = formatText("You've found a {GOLD_LEAF}! Press START to see how many you've collected!")
rom.texts[0xed] = formatText("You've got the Mirror Shield! You can now turnback the beams you couldn't block before!")
rom.texts[0xee] = formatText("You've got a more Powerful {POWER_BRACELET}! Now you can almost lift a whale!")
rom.texts[0xf0] = formatText("Want to go on a raft ride for a hundred {RUPEES}?", ask="Yes No Way")
def allowColorDungeonSpritesEverywhere(rom):
# Set sprite set numbers $01-$40 to map to the color dungeon sprites
rom.patch(0x00, 0x2E6F, "00", "15")
# Patch the spriteset loading code to load the 4 entries from the normal table instead of skipping this for color dungeon specific exception weirdness
rom.patch(0x00, 0x0DA4, ASM("jr nc, $05"), ASM("jr nc, $41"))
rom.patch(0x00, 0x0DE5, ASM("""
ldh a, [$F7]
cp $FF
jr nz, $06
ld a, $01
ldh [$91], a
jr $40
"""), ASM("""
jr $0A ; skip over the rest of the code
cp $FF ; check if color dungeon
jp nz, $0DAB
inc d
jp $0DAA
"""), fill_nop=True)
# Disable color dungeon specific tile load hacks
rom.patch(0x00, 0x06A7, ASM("jr nz, $22"), ASM("jr $22"))
rom.patch(0x00, 0x2E77, ASM("jr nz, $0B"), ASM("jr $0B"))
# Finally fill in the sprite data for the color dungeon
for n in range(22):
data = bytearray()
for m in range(4):
idx = rom.banks[0x20][0x06AA + 44 * m + n * 2]
bank = rom.banks[0x20][0x06AA + 44 * m + n * 2 + 1]
if idx == 0 and bank == 0:
v = 0xFF
elif bank == 0x35:
v = idx - 0x40
elif bank == 0x31:
v = idx
elif bank == 0x2E:
v = idx + 0x40
else:
assert False, "%02x %02x" % (idx, bank)
data += bytes([v])
rom.room_sprite_data_indoor[0x200 + n] = data
# Patch the graphics loading code to use DMA and load all sets that need to be reloaded, not just the first and last
rom.patch(0x00, 0x06FA, 0x07AF, ASM("""
;We enter this code with the right bank selected for tile data copy,
;d = tile row (source addr = (d*$100+$4000))
;e = $00
;$C197 = index of sprite set to update (target addr = ($8400 + $100 * [$C197]))
ld a, d
add a, $40
ldh [$51], a
xor a
ldh [$52], a
ldh [$54], a
ld a, [$C197]
add a, $84
ldh [$53], a
ld a, $0F
ldh [$55], a
; See if we need to do anything next
ld a, [$C10E] ; check the 2nd update flag
and a
jr nz, getNext
ldh [$91], a ; no 2nd update flag, so clear primary update flag
ret
getNext:
ld hl, $C197
inc [hl]
res 2, [hl]
ld a, [$C10D]
cp [hl]
ret nz
xor a ; clear the 2nd update flag when we prepare to update the last spriteset
ld [$C10E], a
ret
"""), fill_nop=True)
rom.patch(0x00, 0x0738, "00" * (0x073E - 0x0738), ASM("""
; we get here by some color dungeon specific code jumping to this position
; We still need that color dungeon specific code as it loads background tiles
xor a
ldh [$91], a
ldh [$93], a
ret
"""))
rom.patch(0x00, 0x073E, "00" * (0x07AF - 0x073E), ASM("""
;If we get here, only the 2nd flag is filled and the primary is not. So swap those around.
ld a, [$C10D] ;copy the index number
ld [$C197], a
xor a
ld [$C10E], a ; clear the 2nd update flag
inc a
ldh [$91], a ; set the primary update flag
ret
"""), fill_nop=True)
def updateSpriteData(rom):
# Change the special sprite change exceptions
rom.patch(0x00, 0x0DAD, 0x0DDB, ASM("""
; Check for indoor
ld a, d
and a
jr nz, noChange
ldh a, [$F6] ; hMapRoom
cp $C9
jr nz, sirenRoomEnd
ld a, [$D8C9] ; wOverworldRoomStatus + ROOM_OW_SIREN
and $20
jr z, noChange
ld hl, $7837
jp $0DFE
sirenRoomEnd:
ldh a, [$F6] ; hMapRoom
cp $D8
jr nz, noChange
ld a, [$D8FD] ; wOverworldRoomStatus + ROOM_OW_WALRUS
and $20
jr z, noChange
ld hl, $783B
jp $0DFE
noChange:
"""), fill_nop=True)
rom.patch(0x20, 0x3837, "A4FF8BFF", "A461FF72")
rom.patch(0x20, 0x383B, "A44DFFFF", "A4C5FF70")
# 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, "Room: %03x cannot load graphics for entity: %02x (Index: %d Failed: %s, Active: %s)" % (room_nr, entity, idx, value, values[idx])
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

View File

@@ -0,0 +1,125 @@
import os
import binascii
from ..assembler import ASM
from ..utils import formatText
ItemNameLookupTable = 0x0100
ItemNameLookupSize = 2
TotalRoomCount = 0x316
AnItemText = "an item"
ItemNameStringBufferStart = ItemNameLookupTable + \
TotalRoomCount * ItemNameLookupSize
def addBank34(rom, item_list):
my_path = os.path.dirname(__file__)
rom.patch(0x34, 0x0000, ItemNameLookupTable, ASM("""
; Get the pointer in the lookup table, doubled as it's two bytes
ld hl, $2080
push de
call OffsetPointerByRoomNumber
pop de
add hl, hl
ldi a, [hl] ; hl = *hl
ld h, [hl]
ld l, a
; If there's no data, bail
ld a, l
or h
jp z, SwitchBackTo3E
ld de, wCustomMessage
; Copy "Got " to de
ld a, 71
ld [de], a
inc de
ld a, 111
ld [de], a
inc de
ld a, 116
ld [de], a
inc de
ld a, 32
ld [de], a
inc de
; Copy in our item name
call MessageCopyString
SwitchBackTo3E:
; Bail
ld a, $3e ; Set bank number
jp $080C ; switch bank
; this should be shared but I got link errors
OffsetPointerByRoomNumber:
ldh a, [$F6] ; map room
ld e, a
ld a, [$DBA5] ; is indoor
ld d, a
ldh a, [$F7] ; mapId
cp $FF
jr nz, .notColorDungeon
ld d, $03
jr .notCavesA
.notColorDungeon:
cp $1A
jr nc, .notCavesA
cp $06
jr c, .notCavesA
inc d
.notCavesA:
add hl, de
ret
""" + open(os.path.join(my_path, "bank3e.asm/message.asm"), "rt").read(), 0x4000), fill_nop=True)
nextItemLookup = ItemNameStringBufferStart
nameLookup = {
}
name = AnItemText
def add_or_get_name(name):
nonlocal nextItemLookup
if name in nameLookup:
return nameLookup[name]
if len(name) + 1 + nextItemLookup >= 0x4000:
return nameLookup[AnItemText]
asm = ASM(f'db "{name}", $ff\n')
rom.patch(0x34, nextItemLookup, None, asm)
patch_len = len(binascii.unhexlify(asm))
nameLookup[name] = nextItemLookup + 0x4000
nextItemLookup += patch_len
return nameLookup[name]
item_text_addr = add_or_get_name(AnItemText)
#error_text_addr = add_or_get_name("Please report this check to #bug-reports in the AP discord")
def swap16(x):
assert x <= 0xFFFF
return (x >> 8) | ((x & 0xFF) << 8)
def to_hex_address(x):
return f"{swap16(x):04x}"
# Set defaults for every room
for i in range(TotalRoomCount):
rom.patch(0x34, ItemNameLookupTable + i *
ItemNameLookupSize, None, to_hex_address(0))
for item in item_list:
if not item.custom_item_name:
continue
assert item.room < TotalRoomCount, item.room
# Item names of exactly 255 characters will cause overwrites to occur in the text box
# assert len(item.custom_item_name) < 0x100
# Custom text is only 95 bytes long, restrict to 50
addr = add_or_get_name(item.custom_item_name[:50])
rom.patch(0x34, ItemNameLookupTable + item.room *
ItemNameLookupSize, None, to_hex_address(addr))
if item.extra:
rom.patch(0x34, ItemNameLookupTable + item.extra *
ItemNameLookupSize, None, to_hex_address(addr))

View File

@@ -0,0 +1,303 @@
CheckIfLoadBowWow:
; Check has bowwow flag
ld a, [$DB56]
cp $01
jr nz, .noLoadBowwow
ldh a, [$F6] ; load map number
cp $22
jr z, .loadBowwow
cp $23
jr z, .loadBowwow
cp $24
jr z, .loadBowwow
cp $32
jr z, .loadBowwow
cp $33
jr z, .loadBowwow
cp $34
jr z, .loadBowwow
.noLoadBowwow:
ld e, $00
ret
.loadBowwow:
ld e, $01
ret
; Special handler for when Bowwow tries to eat an entity.
; Our target entity index is loaded in BC.
BowwowEat:
; Load the entity type into A
ld hl, $C3A0 ; entity type
add hl, bc
ld a, [hl]
; Check if we need special handling for bosses
cp $59 ; Moldorm
jr z, BowwowHurtEnemy
cp $5C ; Genie
jr z, BowwowEatGenie
cp $5B ; SlimeEye
jp z, BowwowEatSlimeEye
cp $65 ; AnglerFish
jr z, BowwowHurtEnemy
cp $5D ; SlimeEel
jp z, BowwowEatSlimeEel
cp $5A ; Facade
jr z, BowwowHurtEnemy
cp $63 ; Eagle
jr z, BowwowHurtEnemy
cp $62 ; Hot head
jp z, BowwowEatHotHead
cp $F9 ; Hardhit beetle
jr z, BowwowHurtEnemy
cp $E6 ; Nightmare (all forms)
jp z, BowwowEatNightmare
; Check for special handling for minibosses
cp $87 ; Lanmola
jr z, BowwowHurtEnemy
; cp $88 ; Armos knight
; No special handling, just eat him, solves the fight real quick.
cp $81 ; rolling bones
jr z, BowwowHurtEnemy
cp $89 ; Hinox
jr z, BowwowHurtEnemy
cp $8E ; Cue ball
jr z, BowwowHurtEnemy
;cp $5E ; Gnoma
;jr z, BowwowHurtEnemy
cp $5F ; Master stalfos
jr z, BowwowHurtEnemy
cp $92 ; Smasher
jp z, BowwowEatSmasher
cp $BC ; Grim Creeper
jp z, BowwowEatGrimCreeper
cp $BE ; Blaino
jr z, BowwowHurtEnemy
cp $F8 ; Giant buzz blob
jr z, BowwowHurtEnemy
cp $F4 ; Avalaunch
jr z, BowwowHurtEnemy
; Some enemies
cp $E9 ; Color dungeon shell
jr z, BowwowHurtEnemy
cp $EA ; Color dungeon shell
jr z, BowwowHurtEnemy
cp $EB ; Color dungeon shell
jr z, BowwowHurtEnemy
; Play SFX
ld a, $03
ldh [$F2], a
; Call normal "destroy entity and drop item" handler
jp $3F50
BowwowHurtEnemy:
; Hurt enemy with damage type zero (sword)
ld a, $00
ld [$C19E], a
rst $18
; Play SFX
ld a, $03
ldh [$F2], a
ret
BowwowEatGenie:
; Get private state to find out if this is a bottle or the genie
ld hl, $C2B0
add hl, bc
ld a, [hl]
; Prepare loading state from hl
ld hl, $C290
add hl, bc
cp $00
jr z, .bottle
cp $01
jr z, .ghost
ret
.ghost:
; Get current state
ld a, [hl]
cp $04 ; Flying around without bottle
jr z, BowwowHurtEnemy
ret
.bottle:
; Get current state
ld a, [hl]
cp $03 ; Hopping around in bottle
jr z, BowwowHurtEnemy
ret
BowwowEatSlimeEye:
; On set privateCountdown2 to $0C to split, when privateState1 is $04 and state is $03
ld hl, $C290 ; state
add hl, bc
ld a, [hl]
cp $03
jr nz, .skipSplit
ld hl, $C2B0 ; private state1
add hl, bc
ld a, [hl]
cp $04
jr nz, .skipSplit
ld hl, $C300 ; private countdown 2
add hl, bc
ld [hl], $0C
.skipSplit:
jp BowwowHurtEnemy
BowwowEatSlimeEel:
; Get private state to find out if this is the tail or the head
ld hl, $C2B0
add hl, bc
ld a, [hl]
cp $01 ; not the head, so, skip.
ret nz
; Check if we are pulled out of the wall
ld hl, $C290
add hl, bc
ld a, [hl]
cp $03 ; pulled out of the wall
jr nz, .knockOutOfWall
ld hl, $D204
ld a, [hl]
cp $07
jr nc, .noExtraDamage
inc [hl]
.noExtraDamage:
jp BowwowHurtEnemy
.knockOutOfWall:
ld [hl], $03 ; set state to $03
ld hl, $C210 ; Y position
add hl, bc
ld a, [hl]
ld [hl], $60
cp $48
jp nc, BowwowHurtEnemy
ld [hl], $30
jp BowwowHurtEnemy
BowwowEatHotHead:
; Load health of hothead
ld hl, $C360
add hl, bc
ld a, [hl]
cp $20
jr c, .lowHp
ld [hl], $20
.lowHp:
jp BowwowHurtEnemy
BowwowEatSmasher:
; Check if this is the ball or the monster
ld hl, $C440
add hl, bc
ld a, [hl]
and a
ret nz
jp BowwowHurtEnemy
BowwowEatGrimCreeper:
; Check if this is the main enemy or the smaller ones. Only kill the small ones
ld hl, $C2B0
add hl, bc
ld a, [hl]
and a
ret z
jp BowwowHurtEnemy
BowwowEatNightmare:
; Check if this is the staircase.
ld hl, $C390
add hl, bc
ld a, [hl]
cp $02
ret z
; Prepare loading state from hl
ld hl, $C290
add hl, bc
ld a, [$D219] ; which form has the nightmare
cp $01
jr z, .slimeForm
cp $02
jr z, .agahnimForm
cp $03 ; moldormForm
jp z, BowwowHurtEnemy
cp $04 ; ganon and lanmola
jp z, BowwowHurtEnemy
cp $05 ; dethl
jp z, BowwowHurtEnemy
; 0 is the intro form
ret
.slimeForm:
ld a, [hl]
cp $02
jr z, .canHurtSlime
cp $03
ret nz
.canHurtSlime:
; We need quite some custom handling, normally the nightmare checks very directly if you use powder.
; No idea why this insta kills the slime form...
; Change state to hurt state
ld [hl], $07
; Set flash count
ld hl, $C420
add hl, bc
ld [hl], $14
; play proper sfx
ld a, $07
ldh [$F3], a
ld a, $37
ldh [$F2], a
; No idea why this is done, but it happens when you use powder on the slime
ld a, $03
ld [$D220], a
ret
.agahnimForm:
ld a, [hl]
; only damage in states 2 to 4
cp $02
ret c
cp $04
ret nc
; Decrease health
ld a, [$D220]
inc a
ld [$D220], a
; If dead, do stuff
cp $04
jr c, .agahnimNotDeadYet
ld [hl], $07
ld hl, $C2E0
add hl, bc
ld [hl], $C0
ld a, $36
ldh [$F2], a
.agahnimNotDeadYet:
ld hl, $C420
add hl, bc
ld [hl], $14
ld a, $07
ldh [$F3], a
ret

View File

@@ -0,0 +1,993 @@
RenderChestItem:
ldh a, [$F1] ; active sprite
and $80
jr nz, .renderLargeItem
ld de, ItemSpriteTable
call $3C77 ; RenderActiveEntitySprite
ret
.renderLargeItem:
ld de, LargeItemSpriteTable
dec d
dec d
call $3BC0 ; RenderActiveEntitySpritePair
; If we are an instrument
ldh a, [$F1]
cp $8E
ret c
cp $96
ret nc
; But check if we are not state >3 before that, else the fade-out at the instrument room breaks.
ldh a, [$F0] ; hActiveEntityState
cp $03
ret nc
; Call the color cycling code
xor a
ld [$DC82], a
ld [$DC83], a
ld a, $3e
call $0AD2
ret
GiveItemFromChestMultiworld:
call IncreaseCheckCounter
; Check our "item is for other player" flag
ld hl, $7300
call OffsetPointerByRoomNumber
ld a, [hl]
ld hl, $0055
cp [hl]
ret nz
GiveItemFromChest:
ldh a, [$F1] ; Load active sprite variant
rst 0 ; JUMP TABLE
dw ChestPowerBracelet; CHEST_POWER_BRACELET
dw ChestShield ; CHEST_SHIELD
dw ChestBow ; CHEST_BOW
dw ChestWithItem ; CHEST_HOOKSHOT
dw ChestWithItem ; CHEST_MAGIC_ROD
dw ChestWithItem ; CHEST_PEGASUS_BOOTS
dw ChestWithItem ; CHEST_OCARINA
dw ChestWithItem ; CHEST_FEATHER
dw ChestWithItem ; CHEST_SHOVEL
dw ChestMagicPowder ; CHEST_MAGIC_POWDER_BAG
dw ChestBomb ; CHEST_BOMB
dw ChestSword ; CHEST_SWORD
dw Flippers ; CHEST_FLIPPERS
dw NoItem ; CHEST_MAGNIFYING_LENS
dw ChestWithItem ; Boomerang (used to be unused)
dw SlimeKey ; ?? right side of your trade quest item
dw Medicine ; CHEST_MEDICINE
dw TailKey ; CHEST_TAIL_KEY
dw AnglerKey ; CHEST_ANGLER_KEY
dw FaceKey ; CHEST_FACE_KEY
dw BirdKey ; CHEST_BIRD_KEY
dw GoldenLeaf ; CHEST_GOLD_LEAF
dw ChestWithCurrentDungeonItem ; CHEST_MAP
dw ChestWithCurrentDungeonItem ; CHEST_COMPASS
dw ChestWithCurrentDungeonItem ; CHEST_STONE_BEAK
dw ChestWithCurrentDungeonItem ; CHEST_NIGHTMARE_KEY
dw ChestWithCurrentDungeonItem ; CHEST_SMALL_KEY
dw AddRupees50 ; CHEST_RUPEES_50
dw AddRupees20 ; CHEST_RUPEES_20
dw AddRupees100 ; CHEST_RUPEES_100
dw AddRupees200 ; CHEST_RUPEES_200
dw AddRupees500 ; CHEST_RUPEES_500
dw AddSeashell ; CHEST_SEASHELL
dw NoItem ; CHEST_MESSAGE
dw NoItem ; CHEST_GEL
dw AddKey ; KEY1
dw AddKey ; KEY2
dw AddKey ; KEY3
dw AddKey ; KEY4
dw AddKey ; KEY5
dw AddKey ; KEY6
dw AddKey ; KEY7
dw AddKey ; KEY8
dw AddKey ; KEY9
dw AddMap ; MAP1
dw AddMap ; MAP2
dw AddMap ; MAP3
dw AddMap ; MAP4
dw AddMap ; MAP5
dw AddMap ; MAP6
dw AddMap ; MAP7
dw AddMap ; MAP8
dw AddMap ; MAP9
dw AddCompass ; COMPASS1
dw AddCompass ; COMPASS2
dw AddCompass ; COMPASS3
dw AddCompass ; COMPASS4
dw AddCompass ; COMPASS5
dw AddCompass ; COMPASS6
dw AddCompass ; COMPASS7
dw AddCompass ; COMPASS8
dw AddCompass ; COMPASS9
dw AddStoneBeak ; STONE_BEAK1
dw AddStoneBeak ; STONE_BEAK2
dw AddStoneBeak ; STONE_BEAK3
dw AddStoneBeak ; STONE_BEAK4
dw AddStoneBeak ; STONE_BEAK5
dw AddStoneBeak ; STONE_BEAK6
dw AddStoneBeak ; STONE_BEAK7
dw AddStoneBeak ; STONE_BEAK8
dw AddStoneBeak ; STONE_BEAK9
dw AddNightmareKey ; NIGHTMARE_KEY1
dw AddNightmareKey ; NIGHTMARE_KEY2
dw AddNightmareKey ; NIGHTMARE_KEY3
dw AddNightmareKey ; NIGHTMARE_KEY4
dw AddNightmareKey ; NIGHTMARE_KEY5
dw AddNightmareKey ; NIGHTMARE_KEY6
dw AddNightmareKey ; NIGHTMARE_KEY7
dw AddNightmareKey ; NIGHTMARE_KEY8
dw AddNightmareKey ; NIGHTMARE_KEY9
dw AddToadstool ; Toadstool
dw NoItem ; $51
dw NoItem ; $52
dw NoItem ; $53
dw NoItem ; $54
dw NoItem ; $55
dw NoItem ; $56
dw NoItem ; $57
dw NoItem ; $58
dw NoItem ; $59
dw NoItem ; $5A
dw NoItem ; $5B
dw NoItem ; $5C
dw NoItem ; $5D
dw NoItem ; $5E
dw NoItem ; $5F
dw NoItem ; $60
dw NoItem ; $61
dw NoItem ; $62
dw NoItem ; $63
dw NoItem ; $64
dw NoItem ; $65
dw NoItem ; $66
dw NoItem ; $67
dw NoItem ; $68
dw NoItem ; $69
dw NoItem ; $6A
dw NoItem ; $6B
dw NoItem ; $6C
dw NoItem ; $6D
dw NoItem ; $6E
dw NoItem ; $6F
dw NoItem ; $70
dw NoItem ; $71
dw NoItem ; $72
dw NoItem ; $73
dw NoItem ; $74
dw NoItem ; $75
dw NoItem ; $76
dw NoItem ; $77
dw NoItem ; $78
dw NoItem ; $79
dw NoItem ; $7A
dw NoItem ; $7B
dw NoItem ; $7C
dw NoItem ; $7D
dw NoItem ; $7E
dw NoItem ; $7F
dw PieceOfHeart ; Heart piece
dw GiveBowwow
dw Give10Arrows
dw Give1Arrow
dw UpgradeMaxPowder
dw UpgradeMaxBombs
dw UpgradeMaxArrows
dw GiveRedTunic
dw GiveBlueTunic
dw GiveExtraHeart
dw TakeHeart
dw GiveSong1
dw GiveSong2
dw GiveSong3
dw GiveInstrument
dw GiveInstrument
dw GiveInstrument
dw GiveInstrument
dw GiveInstrument
dw GiveInstrument
dw GiveInstrument
dw GiveInstrument
dw GiveRooster
dw GiveTradeItem1
dw GiveTradeItem2
dw GiveTradeItem3
dw GiveTradeItem4
dw GiveTradeItem5
dw GiveTradeItem6
dw GiveTradeItem7
dw GiveTradeItem8
dw GiveTradeItem9
dw GiveTradeItem10
dw GiveTradeItem11
dw GiveTradeItem12
dw GiveTradeItem13
dw GiveTradeItem14
NoItem:
ret
ChestPowerBracelet:
ld hl, $DB43 ; power bracelet level
jr ChestIncreaseItemLevel
ChestShield:
ld hl, $DB44 ; shield level
jr ChestIncreaseItemLevel
ChestSword:
ld hl, $DB4E ; sword level
jr ChestIncreaseItemLevel
ChestIncreaseItemLevel:
ld a, [hl]
cp $02
jr z, DoNotIncreaseItemLevel
inc [hl]
DoNotIncreaseItemLevel:
jp ChestWithItem
ChestBomb:
ld a, [$DB4D] ; bomb count
add a, $10
daa
ld hl, $DB77 ; max bombs
cp [hl]
jr c, .bombsNotFull
ld a, [hl]
.bombsNotFull:
ld [$DB4D], a
jp ChestWithItem
ChestBow:
ld a, [$DB45]
cp $20
jp nc, ChestWithItem
ld a, $20
ld [$DB45], a
jp ChestWithItem
ChestMagicPowder:
; Reset the toadstool state
ld a, $0B
ldh [$A5], a
xor a
ld [$DB4B], a ; has toadstool
ld a, [$DB4C] ; powder count
add a, $10
daa
ld hl, $DB76 ; max powder
cp [hl]
jr c, .magicPowderNotFull
ld a, [hl]
.magicPowderNotFull:
ld [$DB4C], a
jp ChestWithItem
Flippers:
ld a, $01
ld [wHasFlippers], a
ret
Medicine:
ld a, $01
ld [wHasMedicine], a
ret
TailKey:
ld a, $01
ld [$DB11], a
ret
AnglerKey:
ld a, $01
ld [$DB12], a
ret
FaceKey:
ld a, $01
ld [$DB13], a
ret
BirdKey:
ld a, $01
ld [$DB14], a
ret
SlimeKey:
ld a, $01
ld [$DB15], a
ret
GoldenLeaf:
ld hl, wGoldenLeaves
inc [hl]
ret
AddSeaShell:
ld a, [wSeashellsCount]
inc a
daa
ld [wSeashellsCount], a
ret
PieceOfHeart:
#IF HARD_MODE
ld a, $FF
ld [$DB93], a
#ENDIF
ld a, [$DB5C]
inc a
cp $04
jr z, .FullHeart
ld [$DB5C], a
ret
.FullHeart:
xor a
ld [$DB5C], a
jp GiveExtraHeart
GiveBowwow:
ld a, $01
ld [$DB56], a
ret
ChestInventoryTable:
db $03 ; CHEST_POWER_BRACELET
db $04 ; CHEST_SHIELD
db $05 ; CHEST_BOW
db $06 ; CHEST_HOOKSHOT
db $07 ; CHEST_MAGIC_ROD
db $08 ; CHEST_PEGASUS_BOOTS
db $09 ; CHEST_OCARINA
db $0A ; CHEST_FEATHER
db $0B ; CHEST_SHOVEL
db $0C ; CHEST_MAGIC_POWDER_BAG
db $02 ; CHEST_BOMB
db $01 ; CHEST_SWORD
db $00 ; - (flippers slot)
db $00 ; - (magnifier lens slot)
db $0D ; Boomerang
ChestWithItem:
ldh a, [$F1] ; Load active sprite variant
ld d, $00
ld e, a
ld hl, ChestInventoryTable
add hl, de
ld d, [hl]
call $3E6B ; Give Inventory
ret
ChestWithCurrentDungeonItem:
sub $16 ; a -= CHEST_MAP
ld e, a
ld d, $00
ld hl, $DBCC ; hasDungeonMap
add hl, de
inc [hl]
call $2802 ; Sync current dungeon items with dungeon specific table
ret
AddToadstool:
ld d, $0E
call $3E6B ; Give Inventory
ret
AddKey:
sub $23 ; Make 'A' target dungeon index
ld de, $0004
jr AddDungeonItem
AddMap:
sub $2C ; Make 'A' target dungeon index
ld de, $0000
jr AddDungeonItem
AddCompass:
sub $35 ; Make 'A' target dungeon index
ld de, $0001
jr AddDungeonItem
AddStoneBeak:
sub $3E ; Make 'A' target dungeon index
ld de, $0002
jr AddDungeonItem
AddNightmareKey:
sub $47 ; Make 'A' target dungeon index
ld de, $0003
jr AddDungeonItem
AddDungeonItem:
cp $08
jr z, .colorDungeon
; hl = dungeonitems + type_type + dungeon * 8
ld hl, $DB16
add hl, de
push de
ld e, a
add hl, de
add hl, de
add hl, de
add hl, de
add hl, de
pop de
inc [hl]
; Check if we are in this specific dungeon, and then increase the copied counters as well.
ld hl, $FFF7 ; is current map == target map
cp [hl]
ret nz
ld a, [$DBA5] ; is indoor
and a
ret z
ld hl, $DBCC
add hl, de
inc [hl]
ret
.colorDungeon:
; Special case for the color dungeon, which is in a different location in memory.
ld hl, $DDDA
add hl, de
inc [hl]
ldh a, [$F7] ; is current map == color dungeon
cp $ff
ret nz
ld hl, $DBCC
add hl, de
inc [hl]
ret
AddRupees20:
xor a
ld h, $14
jr AddRupees
AddRupees50:
xor a
ld h, $32
jr AddRupees
AddRupees100:
xor a
ld h, $64
jr AddRupees
AddRupees200:
xor a
ld h, $C8
jr AddRupees
AddRupees500:
ld a, $01
ld h, $F4
jr AddRupees
AddRupees:
ld [$DB8F], a
ld a, h
ld [$DB90], a
ld a, $18
ld [$C3CE], a
ret
Give1Arrow:
ld a, [$DB45]
inc a
jp FinishGivingArrows
Give10Arrows:
ld a, [$DB45]
add a, $0A
FinishGivingArrows:
daa
ld [$DB45], a
ld hl, $DB78
cp [hl]
ret c
ld a, [hl]
ld [$DB45], a
ret
UpgradeMaxPowder:
ld a, $40
ld [$DB76], a
; If we have no powder, we should not increase the current amount, as that would prevent
; The toadstool from showing up.
ld a, [$DB4C]
and a
ret z
ld a, $40
ld [$DB4C], a
ret
UpgradeMaxBombs:
ld a, $60
ld [$DB77], a
ld [$DB4D], a
ret
UpgradeMaxArrows:
ld a, $60
ld [$DB78], a
ld [$DB45], a
ret
GiveRedTunic:
ld a, $01
ld [$DC0F], a
; We use DB6D to store which tunics we have available.
ld a, [wCollectedTunics]
or $01
ld [wCollectedTunics], a
ret
GiveBlueTunic:
ld a, $02
ld [$DC0F], a
; We use DB6D to store which tunics we have available.
ld a, [wCollectedTunics]
or $02
ld [wCollectedTunics], a
ret
GiveExtraHeart:
; Regen all health
ld hl, $DB93
ld [hl], $FF
; Increase max health if health is lower then 14 hearts
ld hl, $DB5B
ld a, $0E
cp [hl]
ret z
inc [hl]
ret
TakeHeart:
; First, reduce the max HP
ld hl, $DB5B
ld a, [hl]
cp $01
ret z
dec a
ld [$DB5B], a
; Next, check if we need to reduce our actual HP to keep it below the maximum.
rlca
rlca
rlca
sub $01
ld hl, $DB5A
cp [hl]
jr nc, .noNeedToReduceHp
ld [hl], a
.noNeedToReduceHp:
; Finally, give all health back.
ld hl, $DB93
ld [hl], $FF
ret
GiveSong1:
ld hl, $DB49
set 2, [hl]
ld a, $00
ld [$DB4A], a
ret
GiveSong2:
ld hl, $DB49
set 1, [hl]
ld a, $01
ld [$DB4A], a
ret
GiveSong3:
ld hl, $DB49
set 0, [hl]
ld a, $02
ld [$DB4A], a
ret
GiveInstrument:
ldh a, [$F1] ; Load active sprite variant
sub $8E
ld d, $00
ld e, a
ld hl, $db65 ; has instrument table
add hl, de
set 1, [hl]
ret
GiveRooster:
ld d, $0F
call $3E6B ; Give Inventory (rooster item)
;ld a, $01
;ld [$DB7B], a ; has rooster
ldh a, [$F9] ; do not spawn rooster in sidescroller
and a
ret z
ld a, $D5 ; ENTITY_ROOSTER
call $3B86 ; SpawnNewEntity_trampoline
ldh a, [$98] ; LinkX
ld hl, $C200 ; wEntitiesPosXTable
add hl, de
ld [hl], a
ldh a, [$99] ; LinkY
ld hl, $C210 ; wEntitiesPosYTable
add hl, de
ld [hl], a
ret
GiveTradeItem1:
ld hl, wTradeSequenceItem
set 0, [hl]
ret
GiveTradeItem2:
ld hl, wTradeSequenceItem
set 1, [hl]
ret
GiveTradeItem3:
ld hl, wTradeSequenceItem
set 2, [hl]
ret
GiveTradeItem4:
ld hl, wTradeSequenceItem
set 3, [hl]
ret
GiveTradeItem5:
ld hl, wTradeSequenceItem
set 4, [hl]
ret
GiveTradeItem6:
ld hl, wTradeSequenceItem
set 5, [hl]
ret
GiveTradeItem7:
ld hl, wTradeSequenceItem
set 6, [hl]
ret
GiveTradeItem8:
ld hl, wTradeSequenceItem
set 7, [hl]
ret
GiveTradeItem9:
ld hl, wTradeSequenceItem2
set 0, [hl]
ret
GiveTradeItem10:
ld hl, wTradeSequenceItem2
set 1, [hl]
ret
GiveTradeItem11:
ld hl, wTradeSequenceItem2
set 2, [hl]
ret
GiveTradeItem12:
ld hl, wTradeSequenceItem2
set 3, [hl]
ret
GiveTradeItem13:
ld hl, wTradeSequenceItem2
set 4, [hl]
ret
GiveTradeItem14:
ld hl, wTradeSequenceItem2
set 5, [hl]
ret
ItemMessageMultiworld:
; Check our "item is for other player" flag
ld hl, $7300
call OffsetPointerByRoomNumber
ld a, [hl]
ld hl, $0055
cp [hl]
jr nz, ItemMessageForOtherPlayer
ItemMessage:
; Fill the custom message slot with this item message.
call BuildItemMessage
ldh a, [$F1]
ld d, $00
ld e, a
ld hl, ItemMessageTable
add hl, de
ld a, [hl]
cp $90
jr z, .powerBracelet
cp $3D
jr z, .shield
jp $2385 ; Opendialog in $000-$0FF range
.powerBracelet:
; Check the power bracelet level, and give a different message when we get the lv2 bracelet
ld hl, $DB43 ; power bracelet level
bit 1, [hl]
jp z, $2385 ; Opendialog in $000-$0FF range
ld a, $EE
jp $2385 ; Opendialog in $000-$0FF range
.shield:
; Check the shield level, and give a different message when we get the lv2 shield
ld hl, $DB44 ; shield level
bit 1, [hl]
jp z, $2385 ; Opendialog in $000-$0FF range
ld a, $ED
jp $2385 ; Opendialog in $000-$0FF range
ItemMessageForOtherPlayer:
push bc
push hl
push af
call BuildRemoteItemMessage
ld hl, SpaceFor
call MessageCopyString
pop af
call MessageAddPlayerName
pop hl
pop bc
;dec de
ld a, $C9
jp $2385 ; Opendialog in $000-$0FF range
ItemSpriteTable:
db $82, $15 ; CHEST_POWER_BRACELET
db $86, $15 ; CHEST_SHIELD
db $88, $14 ; CHEST_BOW
db $8A, $14 ; CHEST_HOOKSHOT
db $8C, $14 ; CHEST_MAGIC_ROD
db $98, $16 ; CHEST_PEGASUS_BOOTS
db $10, $1F ; CHEST_OCARINA
db $12, $1D ; CHEST_FEATHER
db $96, $17 ; CHEST_SHOVEL
db $0E, $1C ; CHEST_MAGIC_POWDER_BAG
db $80, $15 ; CHEST_BOMB
db $84, $15 ; CHEST_SWORD
db $94, $15 ; CHEST_FLIPPERS
db $9A, $10 ; CHEST_MAGNIFYING_LENS
db $24, $1C ; Boomerang
db $4E, $1C ; Slime key
db $A0, $14 ; CHEST_MEDICINE
db $30, $1C ; CHEST_TAIL_KEY
db $32, $1C ; CHEST_ANGLER_KEY
db $34, $1C ; CHEST_FACE_KEY
db $36, $1C ; CHEST_BIRD_KEY
db $3A, $1C ; CHEST_GOLD_LEAF
db $40, $1C ; CHEST_MAP
db $42, $1D ; CHEST_COMPASS
db $44, $1C ; CHEST_STONE_BEAK
db $46, $1C ; CHEST_NIGHTMARE_KEY
db $4A, $1F ; CHEST_SMALL_KEY
db $A6, $15 ; CHEST_RUPEES_50 (normal blue)
db $38, $19 ; CHEST_RUPEES_20 (red)
db $38, $18 ; CHEST_RUPEES_100 (green)
db $38, $1A ; CHEST_RUPEES_200 (yellow)
db $38, $1A ; CHEST_RUPEES_500 (yellow)
db $9E, $14 ; CHEST_SEASHELL
db $8A, $14 ; CHEST_MESSAGE
db $A0, $14 ; CHEST_GEL
db $4A, $1D ; KEY1
db $4A, $1D ; KEY2
db $4A, $1D ; KEY3
db $4A, $1D ; KEY4
db $4A, $1D ; KEY5
db $4A, $1D ; KEY6
db $4A, $1D ; KEY7
db $4A, $1D ; KEY8
db $4A, $1D ; KEY9
db $40, $1C ; MAP1
db $40, $1C ; MAP2
db $40, $1C ; MAP3
db $40, $1C ; MAP4
db $40, $1C ; MAP5
db $40, $1C ; MAP6
db $40, $1C ; MAP7
db $40, $1C ; MAP8
db $40, $1C ; MAP9
db $42, $1D ; COMPASS1
db $42, $1D ; COMPASS2
db $42, $1D ; COMPASS3
db $42, $1D ; COMPASS4
db $42, $1D ; COMPASS5
db $42, $1D ; COMPASS6
db $42, $1D ; COMPASS7
db $42, $1D ; COMPASS8
db $42, $1D ; COMPASS9
db $44, $1C ; STONE_BEAK1
db $44, $1C ; STONE_BEAK2
db $44, $1C ; STONE_BEAK3
db $44, $1C ; STONE_BEAK4
db $44, $1C ; STONE_BEAK5
db $44, $1C ; STONE_BEAK6
db $44, $1C ; STONE_BEAK7
db $44, $1C ; STONE_BEAK8
db $44, $1C ; STONE_BEAK9
db $46, $1C ; NIGHTMARE_KEY1
db $46, $1C ; NIGHTMARE_KEY2
db $46, $1C ; NIGHTMARE_KEY3
db $46, $1C ; NIGHTMARE_KEY4
db $46, $1C ; NIGHTMARE_KEY5
db $46, $1C ; NIGHTMARE_KEY6
db $46, $1C ; NIGHTMARE_KEY7
db $46, $1C ; NIGHTMARE_KEY8
db $46, $1C ; NIGHTMARE_KEY9
db $4C, $1C ; Toadstool
LargeItemSpriteTable:
db $AC, $02, $AC, $22 ; heart piece
db $54, $0A, $56, $0A ; bowwow
db $2A, $41, $2A, $61 ; 10 arrows
db $2A, $41, $2A, $61 ; single arrow
db $0E, $1C, $22, $0C ; powder upgrade
db $00, $0D, $22, $0C ; bomb upgrade
db $08, $1C, $22, $0C ; arrow upgrade
db $48, $0A, $48, $2A ; red tunic
db $48, $0B, $48, $2B ; blue tunic
db $2A, $0C, $2A, $2C ; heart container
db $2A, $0F, $2A, $2F ; bad heart container
db $70, $09, $70, $29 ; song 1
db $72, $0B, $72, $2B ; song 2
db $74, $08, $74, $28 ; song 3
db $80, $0E, $82, $0E ; Instrument1
db $84, $0E, $86, $0E ; Instrument2
db $88, $0E, $8A, $0E ; Instrument3
db $8C, $0E, $8E, $0E ; Instrument4
db $90, $0E, $92, $0E ; Instrument5
db $94, $0E, $96, $0E ; Instrument6
db $98, $0E, $9A, $0E ; Instrument7
db $9C, $0E, $9E, $0E ; Instrument8
db $A6, $2B, $A4, $2B ; Rooster
db $1A, $0E, $1C, $0E ; TradeItem1
db $B0, $0C, $B2, $0C ; TradeItem2
db $B4, $0C, $B6, $0C ; TradeItem3
db $B8, $0C, $BA, $0C ; TradeItem4
db $BC, $0C, $BE, $0C ; TradeItem5
db $C0, $0C, $C2, $0C ; TradeItem6
db $C4, $0C, $C6, $0C ; TradeItem7
db $C8, $0C, $CA, $0C ; TradeItem8
db $CC, $0C, $CE, $0C ; TradeItem9
db $D0, $0C, $D2, $0C ; TradeItem10
db $D4, $0D, $D6, $0D ; TradeItem11
db $D8, $0D, $DA, $0D ; TradeItem12
db $DC, $0D, $DE, $0D ; TradeItem13
db $E0, $0D, $E2, $0D ; TradeItem14
ItemMessageTable:
db $90, $3D, $89, $93, $94, $95, $96, $97, $98, $99, $9A, $9B, $9C, $9D, $D9, $A2
db $A0, $A1, $A3, $A4, $A5, $E8, $A6, $A7, $A8, $A9, $AA, $AC, $AB, $AD, $AE, $C9
db $EF, $BE, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9
db $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9
; $40
db $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9
db $0F, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
; $80
db $4F, $C8, $CA, $CB, $E2, $E3, $E4, $CC, $CD, $2A, $2B, $C9, $C9, $C9, $C9, $C9
db $C9, $C9, $C9, $C9, $C9, $C9, $B8, $44, $C9, $C9, $C9, $C9, $C9, $C9, $C9, $C9
db $C9, $C9, $C9, $C9, $9D
RenderDroppedKey:
;TODO: See EntityInitKeyDropPoint for a few special cases to unload.
RenderHeartPiece:
; Check if our chest type is already loaded
ld hl, $C2C0
add hl, bc
ld a, [hl]
and a
jr nz, .droppedKeyTypeLoaded
inc [hl]
;Load the chest type from the chest table.
ld hl, $7800
call OffsetPointerByRoomNumber
ld a, [hl]
ldh [$F1], a ; set currentEntitySpriteVariant
call $3B0C ; SetEntitySpriteVariant
and $80
ld hl, $C340
add hl, bc
ld a, [hl]
jr z, .singleSprite
; We potentially need to fix the physics flags table to allocate 2 sprites for us
and $F8
or $02
ld [hl], a
jr .droppedKeyTypeLoaded
.singleSprite:
and $F8
or $01
ld [hl], a
.droppedKeyTypeLoaded:
jp RenderChestItem
OffsetPointerByRoomNumber:
ldh a, [$F6] ; map room
ld e, a
ld a, [$DBA5] ; is indoor
ld d, a
ldh a, [$F7] ; mapId
cp $FF
jr nz, .notColorDungeon
ld d, $03
jr .notCavesA
.notColorDungeon:
cp $1A
jr nc, .notCavesA
cp $06
jr c, .notCavesA
inc d
.notCavesA:
add hl, de
ret
GiveItemAndMessageForRoom:
;Load the chest type from the chest table.
ld hl, $7800
call OffsetPointerByRoomNumber
ld a, [hl]
ldh [$F1], a
call GiveItemFromChest
jp ItemMessage
GiveItemAndMessageForRoomMultiworld:
;Load the chest type from the chest table.
ld hl, $7800
call OffsetPointerByRoomNumber
ld a, [hl]
ldh [$F1], a
call GiveItemFromChestMultiworld
jp ItemMessageMultiworld
RenderItemForRoom:
;Load the chest type from the chest table.
ld hl, $7800
call OffsetPointerByRoomNumber
ld a, [hl]
ldh [$F1], a
jp RenderChestItem
; Increase the amount of checks we completed, unless we are on the multichest room.
IncreaseCheckCounter:
ldh a, [$F6] ; map room
cp $F2
jr nz, .noMultiChest
ld a, [$DBA5] ; is indoor
and a
jr z, .noMultiChest
ldh a, [$F7] ; mapId
cp $0A
ret z
.noMultiChest:
call $27D0 ; Enable SRAM
ld hl, $B010
.loop:
ld a, [hl]
and a ; clear carry flag
inc a
daa
ldi [hl], a
ret nc
jr .loop

View File

@@ -0,0 +1,494 @@
BuildRemoteItemMessage:
ld de, wCustomMessage
call CustomItemMessageThreeFour
ld a, $A0 ; low of wCustomMessage
cp e
ret nz
BuildItemMessage:
ld hl, ItemNamePointers
ldh a, [$F1]
ld d, $00
ld e, a
add hl, de
add hl, de
ldi a, [hl]
ld h, [hl]
ld l, a
ld de, wCustomMessage
jp MessageCopyString
; And then see if the custom item message func wants to override
; add hl, de
CustomItemMessageThreeFour:
; the stack _should_ have the address to return to here, so we can just pop it when we're done
ld a, $34 ; Set bank number
ld hl, $4000 ; Set next address
push hl
jp $080C ; switch bank
FoundItemForOtherPlayerPostfix:
db m" for player X", $ff
GotItemFromOtherPlayerPostfix:
db m" from player X", $ff
SpaceFrom:
db " from ", $ff, $ff
SpaceFor:
db " for ", $ff, $ff
MessagePad:
jr .start ; goto start
.loop:
ld a, $20 ; a = ' '
ld [de], a ; *de = ' '
inc de ; de++
ld a, $ff ; a = 0xFF
ld [de], a ; *de = 0xff
.start:
ld a, e ; a = de & 0xF
and $0F ; a &= 0x0xF
jr nz, .loop ; if a != 0, goto loop
ret
MessageAddTargetPlayer:
call MessagePad
ld hl, FoundItemForOtherPlayerPostfix
call MessageCopyString
ret
MessageAddFromPlayerOld:
call MessagePad
ld hl, GotItemFromOtherPlayerPostfix
call MessageCopyString
ret
; hahaha none of this follows calling conventions
MessageAddPlayerName:
; call MessagePad
ld h, 0 ; bc = a, hl = a
ld l, a
ld b, 0
ld c, a
add hl, hl ; 2
add hl, hl ; 4
add hl, hl ; 8
add hl, hl ; 16
add hl, bc ; 17
ld bc, MultiNamePointers
add hl, bc ; hl = MultiNamePointers + wLinkGiveItemFrom * 17
call MessageCopyString
ret
ItemNamePointers:
dw ItemNamePowerBracelet
dw ItemNameShield
dw ItemNameBow
dw ItemNameHookshot
dw ItemNameMagicRod
dw ItemNamePegasusBoots
dw ItemNameOcarina
dw ItemNameFeather
dw ItemNameShovel
dw ItemNameMagicPowder
dw ItemNameBomb
dw ItemNameSword
dw ItemNameFlippers
dw ItemNameNone
dw ItemNameBoomerang
dw ItemNameSlimeKey
dw ItemNameMedicine
dw ItemNameTailKey
dw ItemNameAnglerKey
dw ItemNameFaceKey
dw ItemNameBirdKey
dw ItemNameGoldLeaf
dw ItemNameMap
dw ItemNameCompass
dw ItemNameStoneBeak
dw ItemNameNightmareKey
dw ItemNameSmallKey
dw ItemNameRupees50
dw ItemNameRupees20
dw ItemNameRupees100
dw ItemNameRupees200
dw ItemNameRupees500
dw ItemNameSeashell
dw ItemNameMessage
dw ItemNameGel
dw ItemNameKey1
dw ItemNameKey2
dw ItemNameKey3
dw ItemNameKey4
dw ItemNameKey5
dw ItemNameKey6
dw ItemNameKey7
dw ItemNameKey8
dw ItemNameKey9
dw ItemNameMap1
dw ItemNameMap2
dw ItemNameMap3
dw ItemNameMap4
dw ItemNameMap5
dw ItemNameMap6
dw ItemNameMap7
dw ItemNameMap8
dw ItemNameMap9
dw ItemNameCompass1
dw ItemNameCompass2
dw ItemNameCompass3
dw ItemNameCompass4
dw ItemNameCompass5
dw ItemNameCompass6
dw ItemNameCompass7
dw ItemNameCompass8
dw ItemNameCompass9
dw ItemNameStoneBeak1
dw ItemNameStoneBeak2
dw ItemNameStoneBeak3
dw ItemNameStoneBeak4
dw ItemNameStoneBeak5
dw ItemNameStoneBeak6
dw ItemNameStoneBeak7
dw ItemNameStoneBeak8
dw ItemNameStoneBeak9
dw ItemNameNightmareKey1
dw ItemNameNightmareKey2
dw ItemNameNightmareKey3
dw ItemNameNightmareKey4
dw ItemNameNightmareKey5
dw ItemNameNightmareKey6
dw ItemNameNightmareKey7
dw ItemNameNightmareKey8
dw ItemNameNightmareKey9
dw ItemNameToadstool
dw ItemNameNone ; 0x51
dw ItemNameNone ; 0x52
dw ItemNameNone ; 0x53
dw ItemNameNone ; 0x54
dw ItemNameNone ; 0x55
dw ItemNameNone ; 0x56
dw ItemNameNone ; 0x57
dw ItemNameNone ; 0x58
dw ItemNameNone ; 0x59
dw ItemNameNone ; 0x5a
dw ItemNameNone ; 0x5b
dw ItemNameNone ; 0x5c
dw ItemNameNone ; 0x5d
dw ItemNameNone ; 0x5e
dw ItemNameNone ; 0x5f
dw ItemNameNone ; 0x60
dw ItemNameNone ; 0x61
dw ItemNameNone ; 0x62
dw ItemNameNone ; 0x63
dw ItemNameNone ; 0x64
dw ItemNameNone ; 0x65
dw ItemNameNone ; 0x66
dw ItemNameNone ; 0x67
dw ItemNameNone ; 0x68
dw ItemNameNone ; 0x69
dw ItemNameNone ; 0x6a
dw ItemNameNone ; 0x6b
dw ItemNameNone ; 0x6c
dw ItemNameNone ; 0x6d
dw ItemNameNone ; 0x6e
dw ItemNameNone ; 0x6f
dw ItemNameNone ; 0x70
dw ItemNameNone ; 0x71
dw ItemNameNone ; 0x72
dw ItemNameNone ; 0x73
dw ItemNameNone ; 0x74
dw ItemNameNone ; 0x75
dw ItemNameNone ; 0x76
dw ItemNameNone ; 0x77
dw ItemNameNone ; 0x78
dw ItemNameNone ; 0x79
dw ItemNameNone ; 0x7a
dw ItemNameNone ; 0x7b
dw ItemNameNone ; 0x7c
dw ItemNameNone ; 0x7d
dw ItemNameNone ; 0x7e
dw ItemNameNone ; 0x7f
dw ItemNameHeartPiece ; 0x80
dw ItemNameBowwow
dw ItemName10Arrows
dw ItemNameSingleArrow
dw ItemNamePowderUpgrade
dw ItemNameBombUpgrade
dw ItemNameArrowUpgrade
dw ItemNameRedTunic
dw ItemNameBlueTunic
dw ItemNameHeartContainer
dw ItemNameBadHeartContainer
dw ItemNameSong1
dw ItemNameSong2
dw ItemNameSong3
dw ItemInstrument1
dw ItemInstrument2
dw ItemInstrument3
dw ItemInstrument4
dw ItemInstrument5
dw ItemInstrument6
dw ItemInstrument7
dw ItemInstrument8
dw ItemRooster
dw ItemTradeQuest1
dw ItemTradeQuest2
dw ItemTradeQuest3
dw ItemTradeQuest4
dw ItemTradeQuest5
dw ItemTradeQuest6
dw ItemTradeQuest7
dw ItemTradeQuest8
dw ItemTradeQuest9
dw ItemTradeQuest10
dw ItemTradeQuest11
dw ItemTradeQuest12
dw ItemTradeQuest13
dw ItemTradeQuest14
ItemNameNone:
db m"NONE", $ff
ItemNamePowerBracelet:
db m"Got the {POWER_BRACELET}", $ff
ItemNameShield:
db m"Got a {SHIELD}", $ff
ItemNameBow:
db m"Got the {BOW}", $ff
ItemNameHookshot:
db m"Got the {HOOKSHOT}", $ff
ItemNameMagicRod:
db m"Got the {MAGIC_ROD}", $ff
ItemNamePegasusBoots:
db m"Got the {PEGASUS_BOOTS}", $ff
ItemNameOcarina:
db m"Got the {OCARINA}", $ff
ItemNameFeather:
db m"Got the {FEATHER}", $ff
ItemNameShovel:
db m"Got the {SHOVEL}", $ff
ItemNameMagicPowder:
db m"Got {MAGIC_POWDER}", $ff
ItemNameBomb:
db m"Got {BOMB}", $ff
ItemNameSword:
db m"Got a {SWORD}", $ff
ItemNameFlippers:
db m"Got the {FLIPPERS}", $ff
ItemNameBoomerang:
db m"Got the {BOOMERANG}", $ff
ItemNameSlimeKey:
db m"Got the {SLIME_KEY}", $ff
ItemNameMedicine:
db m"Got some {MEDICINE}", $ff
ItemNameTailKey:
db m"Got the {TAIL_KEY}", $ff
ItemNameAnglerKey:
db m"Got the {ANGLER_KEY}", $ff
ItemNameFaceKey:
db m"Got the {FACE_KEY}", $ff
ItemNameBirdKey:
db m"Got the {BIRD_KEY}", $ff
ItemNameGoldLeaf:
db m"Got the {GOLD_LEAF}", $ff
ItemNameMap:
db m"Got the {MAP}", $ff
ItemNameCompass:
db m"Got the {COMPASS}", $ff
ItemNameStoneBeak:
db m"Got the {STONE_BEAK}", $ff
ItemNameNightmareKey:
db m"Got the {NIGHTMARE_KEY}", $ff
ItemNameSmallKey:
db m"Got a {KEY}", $ff
ItemNameRupees50:
db m"Got 50 {RUPEES}", $ff
ItemNameRupees20:
db m"Got 20 {RUPEES}", $ff
ItemNameRupees100:
db m"Got 100 {RUPEES}", $ff
ItemNameRupees200:
db m"Got 200 {RUPEES}", $ff
ItemNameRupees500:
db m"Got 500 {RUPEES}", $ff
ItemNameSeashell:
db m"Got a {SEASHELL}", $ff
ItemNameGel:
db m"Got a Zol Attack", $ff
ItemNameMessage:
db m"Got ... nothing?", $ff
ItemNameKey1:
db m"Got a {KEY1}", $ff
ItemNameKey2:
db m"Got a {KEY2}", $ff
ItemNameKey3:
db m"Got a {KEY3}", $ff
ItemNameKey4:
db m"Got a {KEY4}", $ff
ItemNameKey5:
db m"Got a {KEY5}", $ff
ItemNameKey6:
db m"Got a {KEY6}", $ff
ItemNameKey7:
db m"Got a {KEY7}", $ff
ItemNameKey8:
db m"Got a {KEY8}", $ff
ItemNameKey9:
db m"Got a {KEY9}", $ff
ItemNameMap1:
db m"Got the {MAP1}", $ff
ItemNameMap2:
db m"Got the {MAP2}", $ff
ItemNameMap3:
db m"Got the {MAP3}", $ff
ItemNameMap4:
db m"Got the {MAP4}", $ff
ItemNameMap5:
db m"Got the {MAP5}", $ff
ItemNameMap6:
db m"Got the {MAP6}", $ff
ItemNameMap7:
db m"Got the {MAP7}", $ff
ItemNameMap8:
db m"Got the {MAP8}", $ff
ItemNameMap9:
db m"Got the {MAP9}", $ff
ItemNameCompass1:
db m"Got the {COMPASS1}", $ff
ItemNameCompass2:
db m"Got the {COMPASS2}", $ff
ItemNameCompass3:
db m"Got the {COMPASS3}", $ff
ItemNameCompass4:
db m"Got the {COMPASS4}", $ff
ItemNameCompass5:
db m"Got the {COMPASS5}", $ff
ItemNameCompass6:
db m"Got the {COMPASS6}", $ff
ItemNameCompass7:
db m"Got the {COMPASS7}", $ff
ItemNameCompass8:
db m"Got the {COMPASS8}", $ff
ItemNameCompass9:
db m"Got the {COMPASS9}", $ff
ItemNameStoneBeak1:
db m"Got the {STONE_BEAK1}", $ff
ItemNameStoneBeak2:
db m"Got the {STONE_BEAK2}", $ff
ItemNameStoneBeak3:
db m"Got the {STONE_BEAK3}", $ff
ItemNameStoneBeak4:
db m"Got the {STONE_BEAK4}", $ff
ItemNameStoneBeak5:
db m"Got the {STONE_BEAK5}", $ff
ItemNameStoneBeak6:
db m"Got the {STONE_BEAK6}", $ff
ItemNameStoneBeak7:
db m"Got the {STONE_BEAK7}", $ff
ItemNameStoneBeak8:
db m"Got the {STONE_BEAK8}", $ff
ItemNameStoneBeak9:
db m"Got the {STONE_BEAK9}", $ff
ItemNameNightmareKey1:
db m"Got the {NIGHTMARE_KEY1}", $ff
ItemNameNightmareKey2:
db m"Got the {NIGHTMARE_KEY2}", $ff
ItemNameNightmareKey3:
db m"Got the {NIGHTMARE_KEY3}", $ff
ItemNameNightmareKey4:
db m"Got the {NIGHTMARE_KEY4}", $ff
ItemNameNightmareKey5:
db m"Got the {NIGHTMARE_KEY5}", $ff
ItemNameNightmareKey6:
db m"Got the {NIGHTMARE_KEY6}", $ff
ItemNameNightmareKey7:
db m"Got the {NIGHTMARE_KEY7}", $ff
ItemNameNightmareKey8:
db m"Got the {NIGHTMARE_KEY8}", $ff
ItemNameNightmareKey9:
db m"Got the {NIGHTMARE_KEY9}", $ff
ItemNameToadstool:
db m"Got the {TOADSTOOL}", $ff
ItemNameHeartPiece:
db m"Got the {HEART_PIECE}", $ff
ItemNameBowwow:
db m"Got the {BOWWOW}", $ff
ItemName10Arrows:
db m"Got {ARROWS_10}", $ff
ItemNameSingleArrow:
db m"Got the {SINGLE_ARROW}", $ff
ItemNamePowderUpgrade:
db m"Got the {MAX_POWDER_UPGRADE}", $ff
ItemNameBombUpgrade:
db m"Got the {MAX_BOMBS_UPGRADE}", $ff
ItemNameArrowUpgrade:
db m"Got the {MAX_ARROWS_UPGRADE}", $ff
ItemNameRedTunic:
db m"Got the {RED_TUNIC}", $ff
ItemNameBlueTunic:
db m"Got the {BLUE_TUNIC}", $ff
ItemNameHeartContainer:
db m"Got a {HEART_CONTAINER}", $ff
ItemNameBadHeartContainer:
db m"Got the {BAD_HEART_CONTAINER}", $ff
ItemNameSong1:
db m"Got the {SONG1}", $ff
ItemNameSong2:
db m"Got {SONG2}", $ff
ItemNameSong3:
db m"Got {SONG3}", $ff
ItemInstrument1:
db m"You've got the {INSTRUMENT1}", $ff
ItemInstrument2:
db m"You've got the {INSTRUMENT2}", $ff
ItemInstrument3:
db m"You've got the {INSTRUMENT3}", $ff
ItemInstrument4:
db m"You've got the {INSTRUMENT4}", $ff
ItemInstrument5:
db m"You've got the {INSTRUMENT5}", $ff
ItemInstrument6:
db m"You've got the {INSTRUMENT6}", $ff
ItemInstrument7:
db m"You've got the {INSTRUMENT7}", $ff
ItemInstrument8:
db m"You've got the {INSTRUMENT8}", $ff
ItemRooster:
db m"You've got the {ROOSTER}", $ff
ItemTradeQuest1:
db m"You've got the Yoshi Doll", $ff
ItemTradeQuest2:
db m"You've got the Ribbon", $ff
ItemTradeQuest3:
db m"You've got the Dog Food", $ff
ItemTradeQuest4:
db m"You've got the Bananas", $ff
ItemTradeQuest5:
db m"You've got the Stick", $ff
ItemTradeQuest6:
db m"You've got the Honeycomb", $ff
ItemTradeQuest7:
db m"You've got the Pineapple", $ff
ItemTradeQuest8:
db m"You've got the Hibiscus", $ff
ItemTradeQuest9:
db m"You've got the Letter", $ff
ItemTradeQuest10:
db m"You've got the Broom", $ff
ItemTradeQuest11:
db m"You've got the Fishing Hook", $ff
ItemTradeQuest12:
db m"You've got the Necklace", $ff
ItemTradeQuest13:
db m"You've got the Scale", $ff
ItemTradeQuest14:
db m"You've got the Magnifying Lens", $ff
MultiNamePointers:

View File

@@ -0,0 +1,89 @@
; Handle the serial link cable
#IF HARDWARE_LINK
; FF> = Idle
; D6> = Read: D0><[L] D1><[H] [HL]>
; D9> = Write: D8><[L] D9><[H] DA><[^DATA] DB><[DATA]
; DD> = OrW: D8><[L] D9><[H] DA><[^DATA] DB><[DATA] (used to set flags without requiring a slow read,modify,write race condition)
handleSerialLink:
; Check if we got a byte from hardware
ldh a, [$01]
cp $D6
jr z, serialReadMem
cp $D9
jr z, serialWriteMem
cp $DD
jr z, serialOrMem
finishSerialLink:
; Do a new idle transfer.
ld a, $E4
ldh [$01], a
ld a, $81
ldh [$02], a
ret
serialReadMem:
ld a, $D0
call serialTransfer
ld h, a
ld a, $D1
call serialTransfer
ld l, a
ld a, [hl]
call serialTransfer
jr finishSerialLink
serialWriteMem:
ld a, $D8
call serialTransfer
ld h, a
ld a, $D9
call serialTransfer
ld l, a
ld a, $DA
call serialTransfer
cpl
ld c, a
ld a, $DB
call serialTransfer
cp c
jr nz, finishSerialLink
ld [hl], a
jr finishSerialLink
serialOrMem:
ld a, $D8
call serialTransfer
ld h, a
ld a, $D9
call serialTransfer
ld l, a
ld a, $DA
call serialTransfer
cpl
ld c, a
ld a, $DB
call serialTransfer
cp c
jr nz, finishSerialLink
ld c, a
ld a, [hl]
or c
ld [hl], a
jr finishSerialLink
; Transfer A to the serial link and wait for it to be done and return the result in A
serialTransfer:
ldh [$01], a
ld a, $81
ldh [$02], a
.loop:
ldh a, [$02]
and $80
jr nz, .loop
ldh a, [$01]
ret
#ENDIF

View File

@@ -0,0 +1,16 @@
MessageCopyString:
.loop:
ldi a, [hl]
ld [de], a
cp $ff
ret z
inc de
jr .loop
MessageAddSpace:
ld a, $20
ld [de], a
inc de
ld a, $ff
ld [de], a
ret

View File

@@ -0,0 +1,355 @@
; Handle the multiworld link
MainLoop:
#IF HARDWARE_LINK
call handleSerialLink
#ENDIF
; Check if the gameplay is world
ld a, [$DB95]
cp $0B
ret nz
; Check if the world subtype is the normal one
ld a, [$DB96]
cp $07
ret nz
; Check if we are moving between rooms
ld a, [$C124]
and a
ret nz
; Check if link is in a normal walking/swimming state
ld a, [$C11C]
cp $02
ret nc
; Check if a dialog is open
ld a, [$C19F]
and a
ret nz
; Check if interaction is blocked
ldh a, [$A1]
and a
ret nz
ld a, [wLinkSpawnDelay]
and a
jr z, .allowSpawn
dec a
ld [wLinkSpawnDelay], a
jr .noSpawn
.allowSpawn:
ld a, [wZolSpawnCount]
and a
call nz, LinkSpawnSlime
ld a, [wCuccoSpawnCount]
and a
call nz, LinkSpawnCucco
ld a, [wDropBombSpawnCount]
and a
call nz, LinkSpawnBomb
.noSpawn:
; Have an item to give?
ld hl, wLinkStatusBits
bit 0, [hl]
ret z
; Give an item to the player
ld a, [wLinkGiveItem]
; if zol:
cp $22 ; zol item
jr z, LinkGiveSlime
; if special item
cp $F0
jr nc, HandleSpecialItem
; tmpChestItem = a
ldh [$F1], a
; Give the item
call GiveItemFromChest
; Paste the item text
call BuildItemMessage
; Paste " from "
ld hl, SpaceFrom
call MessageCopyString
; Paste the player name
ld a, [wLinkGiveItemFrom]
call MessageAddPlayerName
ld a, $C9
; hl = $wLinkStatusBits
ld hl, wLinkStatusBits
; clear the 0 bit of *hl
res 0, [hl]
; OpenDialog()
jp $2385 ; Opendialog in $000-$0FF range
LinkGiveSlime:
ld a, $05
ld [wZolSpawnCount], a
ld hl, wLinkStatusBits
res 0, [hl]
ret
HandleSpecialItem:
ld hl, wLinkStatusBits
res 0, [hl]
and $0F
rst 0
dw SpecialSlimeStorm
dw SpecialCuccoParty
dw SpecialPieceOfPower
dw SpecialHealth
dw SpecialRandomTeleport
dw .ret
dw .ret
dw .ret
dw .ret
dw .ret
dw .ret
dw .ret
dw .ret
dw .ret
dw .ret
dw .ret
.ret:
ret
SpecialSlimeStorm:
ld a, $20
ld [wZolSpawnCount], a
ret
SpecialCuccoParty:
ld a, $20
ld [wCuccoSpawnCount], a
ret
SpecialPieceOfPower:
; Give the piece of power and the music
ld a, $01
ld [$D47C], a
ld a, $27
ld [$D368], a
ld a, $49
ldh [$BD], a
ldh [$BF], a
ret
SpecialHealth:
; Regen all health
ld hl, $DB93
ld [hl], $FF
ret
LinkSpawnSlime:
ld a, $1B
ld e, $08
call $3B98 ; SpawnNewEntity in range
ret c
; Place somewhere random
call placeRandom
ld hl, $C310
add hl, de
ld [hl], $7F
ld hl, wZolSpawnCount
dec [hl]
call $280D
and $03
ld [wLinkSpawnDelay], a
ret
LinkSpawnCucco:
ld a, $6C
ld e, $04
call $3B98 ; SpawnNewEntity in range
ret c
; Place where link is at.
ld hl, $C200
add hl, de
ldh a, [$98]
ld [hl], a
ld hl, $C210
add hl, de
ldh a, [$99]
ld [hl], a
; Set the "hits till cucco killer attack" much lower
ld hl, $C2B0
add hl, de
ld a, $21
ld [hl], a
ld hl, wCuccoSpawnCount
dec [hl]
call $280D
and $07
ld [wLinkSpawnDelay], a
ret
LinkSpawnBomb:
ld a, $02
ld e, $08
call $3B98 ; SpawnNewEntity in range
ret c
call placeRandom
ld hl, $C310 ; z pos
add hl, de
ld [hl], $4F
ld hl, $C430 ; wEntitiesOptions1Table
add hl, de
res 0, [hl]
ld hl, $C2E0 ; wEntitiesTransitionCountdownTable
add hl, de
ld [hl], $80
ld hl, $C440 ; wEntitiesPrivateState4Table
add hl, de
ld [hl], $01
ld hl, wDropBombSpawnCount
dec [hl]
call $280D
and $1F
ld [wLinkSpawnDelay], a
ret
placeRandom:
; Place somewhere random
ld hl, $C200
add hl, de
call $280D ; random number
and $7F
add a, $08
ld [hl], a
ld hl, $C210
add hl, de
call $280D ; random number
and $3F
add a, $20
ld [hl], a
ret
SpecialRandomTeleport:
xor a
; Warp data
ld [$D401], a
ld [$D402], a
call $280D ; random number
ld [$D403], a
ld hl, RandomTeleportPositions
ld d, $00
ld e, a
add hl, de
ld e, [hl]
ld a, e
and $0F
swap a
add a, $08
ld [$D404], a
ld a, e
and $F0
add a, $10
ld [$D405], a
ldh a, [$98]
swap a
and $0F
ld e, a
ldh a, [$99]
sub $08
and $F0
or e
ld [$D416], a ; wWarp0PositionTileIndex
call $0C7D
ld a, $07
ld [$DB96], a ; wGameplaySubtype
ret
Data_004_7AE5: ; @TODO Palette data
db $33, $62, $1A, $01, $FF, $0F, $FF, $7F
Deathlink:
; Spawn the entity
ld a, $CA ; $7AF3: $3E $CA
call $3B86 ; $7AF5: $CD $86 $3B ;SpawnEntityTrampoline
ld a, $26 ; $7AF8: $3E $26 ;
ldh [$F4], a ; $7AFA: $E0 $F4 ; set noise
; Set posX = linkX
ldh a, [$98] ; LinkX
ld hl, $C200 ; wEntitiesPosXTable
add hl, de
ld [hl], a
; set posY = linkY - 54
ldh a, [$99] ; LinkY
sub a, 54
ld hl, $C210 ; wEntitiesPosYTable
add hl, de
ld [hl], a
; wEntitiesPrivateState3Table
ld hl, $C2D0 ; $7B0A: $21 $D0 $C2
add hl, de ; $7B0D: $19
ld [hl], $01 ; $7B0E: $36 $01
; wEntitiesTransitionCountdownTable
ld hl, $C2E0 ; $7B10: $21 $E0 $C2
add hl, de ; $7B13: $19
ld [hl], $C0 ; $7B14: $36 $C0
; GetEntityTransitionCountdown
call $0C05 ; $7B16: $CD $05 $0C
ld [hl], $C0 ; $7B19: $36 $C0
; IncrementEntityState
call $3B12 ; $7B1B: $CD $12 $3B
; Remove medicine
xor a ; $7B1E: $AF
ld [$DB0D], a ; $7B1F: $EA $0D $DB ; ld [wHasMedicine], a
; Reduce health by a lot
ld a, $FF ; $7B22: $3E $FF
ld [$DB94], a ; $7B24: $EA $94 $DB ; ld [wSubtractHealthBuffer], a
ld hl, $DC88 ; $7B2C: $21 $88 $DC
; Set palette
ld de, Data_004_7AE5 ; $7B2F: $11 $E5 $7A
loop_7B32:
ld a, [de] ; $7B32: $1A
; ld [hl+], a ; $7B33: $22
db $22
inc de ; $7B34: $13
ld a, l ; $7B35: $7D
and $07 ; $7B36: $E6 $07
jr nz, loop_7B32 ; $7B38: $20 $F8
ld a, $02 ; $7B3A: $3E $02
ld [$DDD1], a ; $7B3C: $EA $D1 $DD
ret
; probalby wants
; ld a, $02 ; $7B40: $3E $02
;ldh [hLinkInteractiveMotionBlocked], a
RandomTeleportPositions:
db $55, $54, $54, $54, $55, $55, $55, $54, $65, $55, $54, $65, $56, $56, $55, $55
db $55, $45, $65, $54, $55, $55, $55, $55, $55, $55, $55, $58, $43, $57, $55, $55
db $55, $55, $55, $55, $55, $54, $55, $53, $54, $56, $65, $65, $56, $55, $57, $65
db $45, $55, $55, $55, $55, $55, $55, $55, $48, $45, $43, $34, $35, $35, $36, $34
db $65, $55, $55, $54, $54, $54, $55, $54, $56, $65, $55, $55, $55, $55, $54, $54
db $55, $55, $55, $55, $56, $55, $55, $54, $55, $55, $55, $53, $45, $35, $53, $46
db $56, $55, $55, $55, $53, $55, $54, $54, $55, $55, $55, $54, $44, $55, $55, $54
db $55, $55, $45, $55, $55, $54, $45, $45, $63, $55, $65, $55, $45, $45, $44, $54
db $56, $56, $54, $55, $54, $55, $55, $55, $55, $55, $55, $56, $54, $55, $65, $56
db $54, $54, $55, $65, $56, $54, $55, $56, $55, $55, $55, $66, $65, $65, $55, $56
db $65, $55, $55, $75, $55, $55, $55, $54, $55, $55, $65, $57, $55, $54, $53, $45
db $55, $56, $55, $55, $55, $45, $54, $55, $54, $55, $56, $55, $55, $55, $55, $54
db $55, $55, $65, $55, $55, $54, $53, $58, $55, $05, $58, $55, $55, $55, $74, $55
db $55, $55, $55, $55, $46, $55, $55, $56, $55, $55, $55, $54, $55, $45, $55, $55
db $55, $55, $54, $55, $55, $55, $65, $55, $55, $46, $55, $55, $56, $55, $55, $55
db $55, $55, $54, $55, $55, $55, $45, $36, $53, $51, $57, $53, $56, $54, $45, $46

View File

@@ -0,0 +1,63 @@
HandleOwlStatue:
call GetRoomStatusAddressInHL
ld a, [hl]
and $20
ret nz
ld a, [hl]
or $20
ld [hl], a
ld hl, $7B16
call OffsetPointerByRoomNumber
ld a, [hl]
ldh [$F1], a
call ItemMessage
call GiveItemFromChest
ret
GetRoomStatusAddressInHL:
ld a, [$DBA5] ; isIndoor
ld d, a
ld hl, $D800
ldh a, [$F6] ; room nr
ld e, a
ldh a, [$F7] ; map nr
cp $FF
jr nz, .notColorDungeon
ld d, $00
ld hl, $DDE0
jr .notIndoorB
.notColorDungeon:
cp $1A
jr nc, .notIndoorB
cp $06
jr c, .notIndoorB
inc d
.notIndoorB:
add hl, de
ret
RenderOwlStatueItem:
ldh a, [$F6] ; map room
cp $B2
jr nz, .NotYipYip
; Add 2 to room to set room pointer to an empty room for trade items
add a, 2
ldh [$F6], a
call RenderItemForRoom
ldh a, [$F6] ; map room
; ...and undo it
sub a, 2
ldh [$F6], a
ret
.NotYipYip:
call RenderItemForRoom
ret

View File

@@ -0,0 +1,225 @@
import os
import binascii
from ..assembler import ASM
from ..utils import formatText
def hasBank3E(rom):
return rom.banks[0x3E][0] != 0x00
def generate_name(l, i):
if i < len(l):
name = l[i]
else:
name = f"player {i}"
name = name[:16]
assert(len(name) <= 16)
return 'db "' + name + '"' + ', $ff' * (17 - len(name)) + '\n'
# Bank $3E is used for large chunks of custom code.
# Mainly for new chest and dropped items handling.
def addBank3E(rom, seed, player_id, player_name_list):
# No default text for getting the bow, so use an unused slot.
rom.texts[0x89] = formatText("Found the {BOW}!")
rom.texts[0xD9] = formatText("Found the {BOOMERANG}!") # owl text slot reuse
rom.texts[0xBE] = rom.texts[0x111] # owl text slot reuse to get the master skull message in the first dialog group
rom.texts[0xC8] = formatText("Found {BOWWOW}! Which monster put him in a chest? He is a good boi, and waits for you at the Swamp.")
rom.texts[0xC9] = 0xC0A0 # Custom message slot
rom.texts[0xCA] = formatText("Found {ARROWS_10}!")
rom.texts[0xCB] = formatText("Found a {SINGLE_ARROW}... joy?")
# Create a trampoline to bank 0x3E in bank 0x00.
# There is very little room in bank 0, so we set this up as a single trampoline for multiple possible usages.
# the A register is preserved and can directly be used as a jumptable in page 3E.
# Trampoline at rst 8
# the A register is preserved and can directly be used as a jumptable in page 3E.
rom.patch(0, 0x0008, "0000000000000000000000000000", ASM("""
ld h, a
ld a, [$DBAF]
push af
ld a, $3E
call $080C ; switch bank
ld a, h
jp $4000
"""), fill_nop=True)
# Special trampoline to jump to the damage-entity code, we use this from bowwow to damage instead of eat.
rom.patch(0x00, 0x0018, "000000000000000000000000000000", ASM("""
ld a, $03
ld [$2100], a
call $71C0
ld a, [$DBAF]
ld [$2100], a
ret
"""))
my_path = os.path.dirname(__file__)
rom.patch(0x3E, 0x0000, 0x2F00, ASM("""
call MainJumpTable
pop af
jp $080C ; switch bank and return to normal code.
MainJumpTable:
rst 0 ; JUMP TABLE
dw MainLoop ; 0
dw RenderChestItem ; 1
dw GiveItemFromChest ; 2
dw ItemMessage ; 3
dw RenderDroppedKey ; 4
dw RenderHeartPiece ; 5
dw GiveItemFromChestMultiworld ; 6
dw CheckIfLoadBowWow ; 7
dw BowwowEat ; 8
dw HandleOwlStatue ; 9
dw ItemMessageMultiworld ; A
dw GiveItemAndMessageForRoom ; B
dw RenderItemForRoom ; C
dw StartGameMarinMessage ; D
dw GiveItemAndMessageForRoomMultiworld ; E
dw RenderOwlStatueItem ; F
dw UpdateInventoryMenu ; 10
dw LocalOnlyItemAndMessage ; 11
StartGameMarinMessage:
; Injection to reset our frame counter
call $27D0 ; Enable SRAM
ld hl, $B000
xor a
ldi [hl], a ;subsecond counter
ld a, $08 ;(We set the counter to 8 seconds, as it takes 8 seconds before link wakes up and marin talks to him)
ldi [hl], a ;second counter
xor a
ldi [hl], a ;minute counter
ldi [hl], a ;hour counter
ld hl, $B010
ldi [hl], a ;check counter low
ldi [hl], a ;check counter high
; Show the normal message
ld a, $01
jp $2385
TradeSequenceItemData:
; tile attributes
db $0D, $0A, $0D, $0D, $0E, $0E, $0D, $0D, $0D, $0E, $09, $0A, $0A, $0D
; tile index
db $1A, $B0, $B4, $B8, $BC, $C0, $C4, $C8, $CC, $D0, $D4, $D8, $DC, $E0
UpdateInventoryMenu:
ld a, [wTradeSequenceItem]
ld hl, wTradeSequenceItem2
or [hl]
ret z
ld hl, TradeSequenceItemData
ld a, [$C109]
ld e, a
ld d, $00
add hl, de
; Check if we need to increase the counter
ldh a, [$E7] ; frame counter
and $0F
jr nz, .noInc
ld a, e
inc a
cp 14
jr nz, .noWrap
xor a
.noWrap:
ld [$C109], a
.noInc:
; Check if we have the item
ld b, e
inc b
ld a, $01
ld de, wTradeSequenceItem
.shiftLoop:
dec b
jr z, .shiftLoopDone
sla a
jr nz, .shiftLoop
; switching to second byte
ld de, wTradeSequenceItem2
ld a, $01
jr .shiftLoop
.shiftLoopDone:
ld b, a
ld a, [de]
and b
ret z ; skip this item
ld b, [hl]
push hl
; Write the tile attribute data
ld a, $01
ldh [$4F], a
ld hl, $9C6E
call WriteToVRAM
inc hl
call WriteToVRAM
ld de, $001F
add hl, de
call WriteToVRAM
inc hl
call WriteToVRAM
; Write the tile data
xor a
ldh [$4F], a
pop hl
ld de, 14
add hl, de
ld b, [hl]
ld hl, $9C6E
call WriteToVRAM
inc b
inc b
inc hl
call WriteToVRAM
ld de, $001F
add hl, de
dec b
call WriteToVRAM
inc hl
inc b
inc b
call WriteToVRAM
ret
WriteToVRAM:
ldh a, [$41]
and $02
jr nz, WriteToVRAM
ld [hl], b
ret
LocalOnlyItemAndMessage:
call GiveItemFromChest
call ItemMessage
ret
""" + open(os.path.join(my_path, "bank3e.asm/multiworld.asm"), "rt").read()
+ open(os.path.join(my_path, "bank3e.asm/link.asm"), "rt").read()
+ open(os.path.join(my_path, "bank3e.asm/chest.asm"), "rt").read()
+ open(os.path.join(my_path, "bank3e.asm/bowwow.asm"), "rt").read()
+ open(os.path.join(my_path, "bank3e.asm/message.asm"), "rt").read()
+ open(os.path.join(my_path, "bank3e.asm/itemnames.asm"), "rt").read()
+ "".join(generate_name(["The Server"] + player_name_list, i ) for i in range(100)) # allocate
+ 'db "another world", $ff\n'
+ open(os.path.join(my_path, "bank3e.asm/owl.asm"), "rt").read(), 0x4000), fill_nop=True)
# 3E:3300-3616: Multiworld flags per room (for both chests and dropped keys)
# 3E:3800-3B16: DroppedKey item types
# 3E:3B16-3E2C: Owl statue or trade quest items
# Put 20 rupees in all owls by default.
rom.patch(0x3E, 0x3B16, "00" * 0x316, "1C" * 0x316)
# Prevent the photo album from crashing due to serial interrupts
rom.patch(0x28, 0x00D2, ASM("ld a, $09"), ASM("ld a, $01"))

View File

@@ -0,0 +1,386 @@
from ..assembler import ASM
from .. import utils
def addBank3F(rom):
# Bank3F is used to initialize the tile data in VRAM:1 at the start of the rom.
# The normal rom does not use this tile data to maintain GB compatibility.
rom.patch(0, 0x0150, ASM("""
cp $11 ; is running on Game Boy Color?
jr nz, notGBC
ldh a, [$4d]
and $80 ; do we need to switch the CPU speed?
jr nz, speedSwitchDone
; switch to GBC speed
ld a, $30
ldh [$00], a
ld a, $01
ldh [$4d], a
xor a
ldh [$ff], a
stop
db $00
speedSwitchDone:
xor a
ldh [$70], a
ld a, $01 ; isGBC = true
jr Init
notGBC:
xor a ; isGBC = false
Init:
"""), ASM("""
; Check if we are a color gameboy, we require a color version now.
cp $11
jr nz, notGBC
; Switch to bank $3F to run our custom initializer
ld a, $3F
ld [$2100], a
call $4000
; Switch back to bank 0 after loading our own initializer
ld a, $01
ld [$2100], a
; set a to 1 to indicate GBC
ld a, $01
jr Init
notGBC:
xor a
Init:
"""), fill_nop=True)
rom.patch(0x3F, 0x0000, None, ASM("""
; switch speed
ld a, $30
ldh [$00], a
ld a, $01
ldh [$4d], a
xor a
ldh [$ff], a
stop
db $00
; Switch VRAM bank
ld a, $01
ldh [$4F], a
call $28CF ; display off
; Use the GBC DMA to transfer our tile data
ld a, $68
ldh [$51], a
ld a, $00
ldh [$52], a
ld a, $80
ldh [$53], a
ld a, $00
ldh [$54], a
ld a, $7F
ldh [$55], a
waitTillTransferDone:
ldh a, [$55]
and $80
jr z, waitTillTransferDone
ld a, $70
ldh [$51], a
ld a, $00
ldh [$52], a
ld a, $88
ldh [$53], a
ld a, $00
ldh [$54], a
ld a, $7F
ldh [$55], a
waitTillTransferDone2:
ldh a, [$55]
and $80
jr z, waitTillTransferDone2
ld a, $68
ldh [$51], a
ld a, $00
ldh [$52], a
ld a, $90
ldh [$53], a
ld a, $00
ldh [$54], a
ld a, $7F
ldh [$55], a
waitTillTransferDone3:
ldh a, [$55]
and $80
jr z, waitTillTransferDone3
; Switch VRAM bank back
ld a, $00
ldh [$4F], a
; Switch the display back on, else the later code hangs
ld a, $80
ldh [$40], a
speedSwitchDone:
xor a
ldh [$70], a
; Check if we are running on a bad emulator
ldh [$02], a
ldh a, [$02]
and $7c
cp $7c
jr nz, badEmu
; Enable the timer to run 32 times per second
xor a
ldh [$06], a
ld a, $04
ldh [$07], a
; Set SB to $FF to indicate we have no data from hardware
ld a, $FF
ldh [$01], a
ret
badEmu:
xor a
ldh [$40], a ; switch display off
; Load some palette
ld a, $80
ldh [$68], a
xor a
ldh [$69], a
ldh [$69], a
ldh [$69], a
ldh [$69], a
; Load a different gfx tile for the first gfx
cpl
ld hl, $8000
ld c, $10
.loop:
ldi [hl], a
dec c
jr nz, .loop
ld a, $01
ld [$9800], a
ld [$9820], a
ld [$9840], a
ld [$9860], a
ld [$9880], a
ld [$9801], a
ld [$9841], a
ld [$9881], a
ld [$9822], a
ld [$9862], a
ld [$9824], a
ld [$9844], a
ld [$9864], a
ld [$9884], a
ld [$9805], a
ld [$9845], a
ld [$9826], a
ld [$9846], a
ld [$9866], a
ld [$9886], a
ld [$9808], a
ld [$9828], a
ld [$9848], a
ld [$9868], a
ld [$9888], a
ld [$9809], a
ld [$9889], a
ld [$982A], a
ld [$984A], a
ld [$986A], a
ld [$9900], a
ld [$9920], a
ld [$9940], a
ld [$9960], a
ld [$9980], a
ld [$9901], a
ld [$9941], a
ld [$9981], a
ld [$9903], a
ld [$9923], a
ld [$9943], a
ld [$9963], a
ld [$9983], a
ld [$9904], a
ld [$9925], a
ld [$9906], a
ld [$9907], a
ld [$9927], a
ld [$9947], a
ld [$9967], a
ld [$9987], a
ld [$9909], a
ld [$9929], a
ld [$9949], a
ld [$9969], a
ld [$9989], a
ld [$998A], a
ld [$990B], a
ld [$992B], a
ld [$994B], a
ld [$996B], a
ld [$998B], a
; lcd on
ld a, $91
ldh [$40], a
blockBadEmu:
di
jr blockBadEmu
"""))
# Copy all normal item graphics
rom.banks[0x3F][0x2800:0x2B00] = rom.banks[0x2C][0x0800:0x0B00] # main items
rom.banks[0x3F][0x2B00:0x2C00] = rom.banks[0x2C][0x0C00:0x0D00] # overworld key items
rom.banks[0x3F][0x2C00:0x2D00] = rom.banks[0x32][0x3D00:0x3E00] # dungeon key items
# Create ruppee for palettes 0-3
rom.banks[0x3F][0x2B80:0x2BA0] = rom.banks[0x3F][0x2A60:0x2A80]
for n in range(0x2B80, 0x2BA0, 2):
rom.banks[0x3F][n+1] ^= rom.banks[0x3F][n]
# Create capacity upgrade arrows
rom.banks[0x3F][0x2A30:0x2A40] = utils.createTileData("""
33
3113
311113
33311333
3113
3333
""")
rom.banks[0x3F][0x2A20:0x2A30] = rom.banks[0x3F][0x2A30:0x2A40]
for n in range(0x2A20, 0x2A40, 2):
rom.banks[0x3F][n] |= rom.banks[0x3F][n + 1]
# Add the slime key and mushroom which are not in the above sets
rom.banks[0x3F][0x2CC0:0x2D00] = rom.banks[0x2C][0x28C0:0x2900]
# Add tunic sprites as well.
rom.banks[0x3F][0x2C80:0x2CA0] = rom.banks[0x35][0x0F00:0x0F20]
# Add the bowwow sprites
rom.banks[0x3F][0x2D00:0x2E00] = rom.banks[0x2E][0x2400:0x2500]
# Zol sprites, so we can have zol anywhere from a chest
rom.banks[0x3F][0x2E00:0x2E60] = rom.banks[0x2E][0x1120:0x1180]
# Patch gel(zol) entity to load sprites from the 2nd bank
rom.patch(0x06, 0x3C09, "5202522254025422" "5200522054005420", "600A602A620A622A" "6008602862086228")
rom.patch(0x07, 0x329B, "FFFFFFFF" "FFFFFFFF" "54005420" "52005220" "56005600",
"FFFFFFFF" "FFFFFFFF" "62086228" "60086028" "64086408")
rom.patch(0x06, 0x3BFA, "56025622", "640A642A");
# Cucco
rom.banks[0x3F][0x2E80:0x2F00] = rom.banks[0x32][0x2500:0x2580]
# Patch the cucco graphics to load from 2nd vram bank
rom.patch(0x05, 0x0514,
"5001" "5201" "5401" "5601" "5221" "5021" "5621" "5421",
"6809" "6A09" "6C09" "6E09" "6A29" "6829" "6E29" "6C29")
# Song symbols
rom.banks[0x3F][0x2F00:0x2F60] = utils.createTileData("""
...
. .222
.2.2222
.22.222.
.22222.3
.2..22.3
.33...3
.33.3.3
..233.3
.22.2333
.222.233
.222...
...
""" + """
..
.22
.223
..222
.33.22
.3..22
.33.33
..23.
..233.
.22.333
.22..233
.. .23
..
""" + """
...
.222.
.2.332
.23.32
.233.2
.222222
.2222222
.2..22.2
.2.3.222
.22...22
.2333..
.23333
.....""", " .23")
# Ghost
rom.banks[0x3F][0x2F60:0x2FE0] = rom.banks[0x32][0x1800:0x1880]
# Instruments
rom.banks[0x3F][0x3000:0x3200] = rom.banks[0x31][0x1000:0x1200]
# Patch the egg song event to use the 2nd vram sprites
rom.patch(0x19, 0x0BAC,
"5006520654065606"
"58065A065C065E06"
"6006620664066606"
"68066A066C066E06",
"800E820E840E860E"
"880E8A0E8C0E8E0E"
"900E920E940E960E"
"980E9A0E9C0E9E0E"
)
# Rooster
rom.banks[0x3F][0x3200:0x3300] = rom.banks[0x32][0x1D00:0x1E00]
rom.patch(0x19, 0x19BC,
"42234023" "46234423" "40034203" "44034603" "4C034C23" "4E034E23" "48034823" "4A034A23",
"A22BA02B" "A62BA42B" "A00BA20B" "A40BA60B" "AC0BAC2B" "AE0BAE2B" "A80BA82B" "AA0BAA2B")
# Replace some main item graphics with the rooster
rom.banks[0x2C][0x0900:0x0940] = utils.createTileData(utils.tileDataToString(rom.banks[0x32][0x1D00:0x1D40]), " 321")
# Trade sequence items
rom.banks[0x3F][0x3300:0x3640] = rom.banks[0x2C][0x0400:0x0740]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,20 @@
from ..assembler import ASM
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)

View File

@@ -0,0 +1,207 @@
from ..assembler import ASM
from ..roomEditor import RoomEditor
def fixBowwow(rom, everywhere=False):
### BowWow patches
rom.patch(0x03, 0x1E0E, ASM("ld [$DB56], a"), "", fill_nop=True) # Do not mark BowWow as kidnapped after we complete dungeon 1.
rom.patch(0x15, 0x06B6, ASM("ld a, [$DB56]\ncp $80"), ASM("xor a"), fill_nop=True) # always load the moblin boss
rom.patch(0x03, 0x182D, ASM("ld a, [$DB56]\ncp $80"), ASM("ld a, [$DAE2]\nand $10")) # load the cave moblins if the chest is not opened
rom.patch(0x07, 0x3947, ASM("ld a, [$DB56]\ncp $80"), ASM("ld a, [$DAE2]\nand $10")) # load the cave moblin with sword if the chest is not opened
# Modify the moblin cave to contain a chest at the end, which contains bowwow
re = RoomEditor(rom, 0x2E2)
re.removeEntities(0x6D)
re.changeObject(8, 3, 0xA0)
re.store(rom)
# Place bowwow in the chest table
rom.banks[0x14][0x560 + 0x2E2] = 0x81
# Patch bowwow follower sprite to be used from 2nd vram bank
rom.patch(0x05, 0x001C,
b"40034023"
b"42034223"
b"44034603"
b"48034A03"
b"46234423"
b"4A234823"
b"4C034C23",
b"500B502B"
b"520B522B"
b"540B560B"
b"580B5A0B"
b"562B542B"
b"5A2B582B"
b"5C0B5C2B")
# Patch to use the chain sprite from second vram bank (however, the chain bugs out various things)
rom.patch(0x05, 0x0282,
ASM("ld a, $4E\njr nz, $02\nld a, $7E\nld [de], a\ninc de\nld a, $00"),
ASM("ld a, $5E\nld [de], a\ninc de\nld a, $08"), fill_nop=True)
# Never load the bowwow tiles in the first VRAM bank, as we do not need them.
rom.patch(0x00, 0x2EB0, ASM("ld a, [$DB56]\ncp $01\nld a, $A4\njr z, $18"), "", fill_nop=True)
# Patch the location where bowwow stores chain X/Y positions so it does not conflict with a lot of other things
rom.patch(0x05, 0x00BE, ASM("ld hl, $D100"), ASM("ld hl, $D180"))
rom.patch(0x05, 0x0275, ASM("ld hl, $D100"), ASM("ld hl, $D180"))
rom.patch(0x05, 0x03AD, ASM("ld [$D100], a"), ASM("ld [$D180], a"))
rom.patch(0x05, 0x03BD, ASM("ld de, $D100"), ASM("ld de, $D180"))
rom.patch(0x05, 0x049F, ASM("ld hl, $D100"), ASM("ld hl, $D180"))
rom.patch(0x05, 0x04C2, ASM("ld a, [$D100]"), ASM("ld a, [$D180]"))
rom.patch(0x05, 0x03C0, ASM("ld hl, $D101"), ASM("ld hl, $D181"))
rom.patch(0x05, 0x0418, ASM("ld [$D106], a"), ASM("ld [$D186], a"))
rom.patch(0x05, 0x0423, ASM("ld de, $D106"), ASM("ld de, $D186"))
rom.patch(0x05, 0x0426, ASM("ld hl, $D105"), ASM("ld hl, $D185"))
rom.patch(0x19, 0x3A4E, ASM("ld hl, $D100"), ASM("ld hl, $D180"))
rom.patch(0x19, 0x3A5A, ASM("ld hl, $D110"), ASM("ld hl, $D190"))
rom.patch(0x05, 0x00D9, ASM("ld hl, $D110"), ASM("ld hl, $D190"))
rom.patch(0x05, 0x026E, ASM("ld hl, $D110"), ASM("ld hl, $D190"))
rom.patch(0x05, 0x03BA, ASM("ld [$D110], a"), ASM("ld [$D190], a"))
rom.patch(0x05, 0x03DD, ASM("ld de, $D110"), ASM("ld de, $D190"))
rom.patch(0x05, 0x0480, ASM("ld hl, $D110"), ASM("ld hl, $D190"))
rom.patch(0x05, 0x04B5, ASM("ld a, [$D110]"), ASM("ld a, [$D190]"))
rom.patch(0x05, 0x03E0, ASM("ld hl, $D111"), ASM("ld hl, $D191"))
rom.patch(0x05, 0x0420, ASM("ld [$D116], a"), ASM("ld [$D196], a"))
rom.patch(0x05, 0x044d, ASM("ld de, $D116"), ASM("ld de, $D196"))
rom.patch(0x05, 0x0450, ASM("ld hl, $D115"), ASM("ld hl, $D195"))
rom.patch(0x05, 0x0039, ASM("ld [$D154], a"), "", fill_nop=True) # normally this stores the index to bowwow, for the kiki fight
rom.patch(0x05, 0x013C, ASM("ld [$D150], a"), ASM("ld [$D197], a"))
rom.patch(0x05, 0x0144, ASM("ld [$D151], a"), ASM("ld [$D198], a"))
rom.patch(0x05, 0x02F9, ASM("ld [$D152], a"), ASM("ld [$D199], a"))
rom.patch(0x05, 0x0335, ASM("ld a, [$D152]"), ASM("ld a, [$D199]"))
rom.patch(0x05, 0x0485, ASM("ld a, [$D151]"), ASM("ld a, [$D198]"))
rom.patch(0x05, 0x04A4, ASM("ld a, [$D150]"), ASM("ld a, [$D197]"))
# Patch the bowwow create code to call our custom check of we are in swamp function.
if everywhere:
# Load followers in dungeons, caves, etc
rom.patch(0x01, 0x1FC1, ASM("ret z"), "", fill_nop=True)
rom.patch(0x01, 0x1FC4, ASM("ret z"), "", fill_nop=True)
rom.patch(0x01, 0x1FC7, ASM("ret z"), "", fill_nop=True)
rom.patch(0x01, 0x1FCA, ASM("ret c"), "", fill_nop=True) # dungeon
# rom.patch(0x01, 0x1FBC, ASM("ret nz"), "", fill_nop=True) # sidescroller: TOFIX this breaks fishing minigame reward
else:
# Patch the bowwow create code to call our custom check of we are in swamp function.
rom.patch(0x01, 0x211F, ASM("ldh a, [$F6]\ncp $A7\nret z\nld a, [$DB56]\ncp $01\njr nz, $36"), ASM("""
ld a, $07
rst 8
ld a, e
and a
ret z
"""), fill_nop=True)
# Patch bowwow to not stay around when we move from map to map
rom.patch(0x05, 0x0049, 0x0054, ASM("""
cp [hl]
jr z, Continue
ld hl, $C280
add hl, bc
ld [hl], b
ret
Continue:
"""), fill_nop=True)
# Patch madam meow meow to not take bowwow
rom.patch(0x06, 0x1BD7, ASM("ld a, [$DB66]\nand $02"), ASM("ld a, $00\nand $02"), fill_nop=True)
# Patch kiki not to react to bowwow, as bowwow is not with link at this map
rom.patch(0x07, 0x18A8, ASM("ld a, [$DB56]\ncp $01"), ASM("ld a, $00\ncp $01"), fill_nop=True)
# Patch the color dungeon entrance not to check for bowwow
rom.patch(0x02, 0x340D, ASM("ld hl, $DB56\nor [hl]"), "", fill_nop=True)
# Patch richard to ignore bowwow
rom.patch(0x06, 0x006C, ASM("ld a, [$DB56]"), ASM("xor a"), fill_nop=True)
# Patch to modify how bowwow eats enemies, normally it just unloads them, but we call our handler in bank 3E
rom.patch(0x05, 0x03A0, 0x03A8, ASM("""
push bc
ld b, d
ld c, e
ld a, $08
rst 8
pop bc
ret
"""), fill_nop=True)
rom.patch(0x05, 0x0387, ASM("ld a, $03\nldh [$F2], a"), "", fill_nop=True) # remove the default chomp sfx
# Various enemies
rom.banks[0x14][0x1218 + 0xC5] = 0x01 # Urchin
rom.banks[0x14][0x1218 + 0x93] = 0x01 # MadBomber
rom.banks[0x14][0x1218 + 0x51] = 0x01 # Swinging ball&chain golden leaf enemy
rom.banks[0x14][0x1218 + 0xF2] = 0x01 # Color dungeon flying hopper
rom.banks[0x14][0x1218 + 0xF3] = 0x01 # Color dungeon hopper
rom.banks[0x14][0x1218 + 0xE9] = 0x01 # Color dungeon shell
rom.banks[0x14][0x1218 + 0xEA] = 0x01 # Color dungeon shell
rom.banks[0x14][0x1218 + 0xEB] = 0x01 # Color dungeon shell
rom.banks[0x14][0x1218 + 0xEC] = 0x01 # Color dungeon thing
rom.banks[0x14][0x1218 + 0xED] = 0x01 # Color dungeon thing
rom.banks[0x14][0x1218 + 0xEE] = 0x01 # Color dungeon thing
rom.banks[0x14][0x1218 + 0x87] = 0x01 # Lanmola (for D4 key)
rom.banks[0x14][0x1218 + 0x88] = 0x01 # Armos knight (for D6 key)
rom.banks[0x14][0x1218 + 0x16] = 0x01 # Spark
rom.banks[0x14][0x1218 + 0x17] = 0x01 # Spark
rom.banks[0x14][0x1218 + 0x2C] = 0x01 # Spiked beetle
rom.banks[0x14][0x1218 + 0x90] = 0x01 # Three of a kind (screw these guys)
rom.banks[0x14][0x1218 + 0x18] = 0x01 # Pols voice
rom.banks[0x14][0x1218 + 0x50] = 0x01 # Boo buddy
rom.banks[0x14][0x1218 + 0xA2] = 0x01 # Pirana plant
rom.banks[0x14][0x1218 + 0x52] = 0x01 # Tractor device
rom.banks[0x14][0x1218 + 0x53] = 0x01 # Tractor device (D3)
rom.banks[0x14][0x1218 + 0x55] = 0x01 # Bounding bombite
rom.banks[0x14][0x1218 + 0x56] = 0x01 # Timer bombite
rom.banks[0x14][0x1218 + 0x57] = 0x01 # Pairod
rom.banks[0x14][0x1218 + 0x15] = 0x01 # Antifairy
rom.banks[0x14][0x1218 + 0xA0] = 0x01 # Peahat
rom.banks[0x14][0x1218 + 0x9C] = 0x01 # Star
rom.banks[0x14][0x1218 + 0xA1] = 0x01 # Snake
rom.banks[0x14][0x1218 + 0xBD] = 0x01 # Vire
rom.banks[0x14][0x1218 + 0xE4] = 0x01 # Moblin boss
# Bosses
rom.banks[0x14][0x1218 + 0x59] = 0x01 # Moldorm
rom.banks[0x14][0x1218 + 0x5C] = 0x01 # Genie
rom.banks[0x14][0x1218 + 0x5B] = 0x01 # Slime Eye
rom.patch(0x04, 0x0AC4, ASM("ld [hl], $28"), ASM("ld [hl], $FF")) # give more time before slimeeye unsplits
rom.patch(0x04, 0x0B05, ASM("ld [hl], $50"), ASM("ld [hl], $FF")) # give more time before slimeeye unsplits
rom.banks[0x14][0x1218 + 0x65] = 0x01 # Angler fish
rom.banks[0x14][0x1218 + 0x5D] = 0x01 # Slime eel
rom.banks[0x14][0x1218 + 0x5A] = 0x01 # Facade
rom.banks[0x14][0x1218 + 0x63] = 0x01 # Eagle
rom.banks[0x14][0x1218 + 0x62] = 0x01 # Hot head
rom.banks[0x14][0x1218 + 0xF9] = 0x01 # Hardhit beetle
rom.banks[0x14][0x1218 + 0xE6] = 0x01 # Nightmare
# Minibosses
rom.banks[0x14][0x1218 + 0x81] = 0x01 # Rolling bones
rom.banks[0x14][0x1218 + 0x89] = 0x01 # Hinox
rom.banks[0x14][0x1218 + 0x8E] = 0x01 # Cue ball
rom.banks[0x14][0x1218 + 0x5E] = 0x01 # Gnoma
rom.banks[0x14][0x1218 + 0x5F] = 0x01 # Master stalfos
rom.banks[0x14][0x1218 + 0x92] = 0x01 # Smasher
rom.banks[0x14][0x1218 + 0xBC] = 0x01 # Grim creeper
rom.banks[0x14][0x1218 + 0xBE] = 0x01 # Blaino
rom.banks[0x14][0x1218 + 0xF8] = 0x01 # Giant buzz blob
rom.banks[0x14][0x1218 + 0xF4] = 0x01 # Avalaunch
# NPCs
rom.banks[0x14][0x1218 + 0x6F] = 0x01 # Dog
rom.banks[0x14][0x1218 + 0x6E] = 0x01 # Butterfly
rom.banks[0x14][0x1218 + 0x6C] = 0x01 # Cucco
rom.banks[0x14][0x1218 + 0x70] = 0x01 # Kid
rom.banks[0x14][0x1218 + 0x71] = 0x01 # Kid
rom.banks[0x14][0x1218 + 0x72] = 0x01 # Kid
rom.banks[0x14][0x1218 + 0x73] = 0x01 # Kid
rom.banks[0x14][0x1218 + 0xD0] = 0x01 # Animal
rom.banks[0x14][0x1218 + 0xD1] = 0x01 # Animal
rom.banks[0x14][0x1218 + 0xD2] = 0x01 # Animal
rom.banks[0x14][0x1218 + 0xD3] = 0x01 # Animal
def bowwowMapPatches(rom):
# Remove all the cystal things that can only be destroyed with a sword.
for n in range(0x100, 0x2FF):
re = RoomEditor(rom, n)
re.objects = list(filter(lambda obj: obj.type_id != 0xDD, re.objects))
re.store(rom)

View File

@@ -0,0 +1,59 @@
from ..assembler import ASM
from ..utils import formatText
from ..locations.constants import CHEST_ITEMS
def fixChests(rom):
# Patch the chest code, so it can give a lvl1 sword.
# Normally, there is some code related to the owl event when getting the tail key,
# as we patched out the owl. We use it to jump to our custom code in bank $3E to handle getting the item
rom.patch(0x03, 0x109C, ASM("""
cp $11 ; if not tail key, skip
jr nz, end
push af
ld a, [$C501]
ld e, a
ld hl, $C2F0
add hl, de
ld [hl], $38
pop af
end:
ld e, a
cp $21 ; if is message chest or higher number, next instruction is to skip giving things.
"""), ASM("""
ld a, $06 ; GiveItemMultiworld
rst 8
and a ; clear the carry flag to always skip giving stuff.
"""), fill_nop=True)
# Instead of the normal logic to on which sprite data to show, we jump to our custom code in bank 3E.
rom.patch(0x07, 0x3C36, None, ASM("""
ld a, $01
rst 8
jp $7C5E
"""), fill_nop=True)
# Instead of the normal logic of showing the proper dialog, we jump to our custom code in bank 3E.
rom.patch(0x07, 0x3C9C, None, ASM("""
ld a, $0A ; showItemMessageMultiworld
rst 8
jp $7CE9
"""))
# Sound to play is normally loaded from a table, which is no longer big enough. So always use the same sound.
rom.patch(0x07, 0x3C81, ASM("""
add hl, de
ld a, [hl]
"""), ASM("ld a, $01"), fill_nop=True)
# Always spawn seashells even if you have the L2 sword
rom.patch(0x14, 0x192F, ASM("ld a, $1C"), ASM("ld a, $20"))
rom.texts[0x9A] = formatText("You found 10 {BOMB}!")
def setMultiChest(rom, option):
room = 0x2F2
addr = room + 0x560
rom.banks[0x14][addr] = CHEST_ITEMS[option]

View File

@@ -0,0 +1,539 @@
from ..assembler import ASM
from ..entranceInfo import ENTRANCE_INFO
from ..roomEditor import RoomEditor, ObjectWarp, ObjectHorizontal
from ..backgroundEditor import BackgroundEditor
from .. import utils
def bugfixWrittingWrongRoomStatus(rom):
# The normal rom contains a pretty nasty bug where door closing triggers in D7/D8 can effect doors in
# dungeons D1-D6. This fix should prevent this.
rom.patch(0x02, 0x1D21, 0x1D3C, ASM("call $5B9F"), fill_nop=True)
def fixEggDeathClearingItems(rom):
rom.patch(0x01, 0x1E79, ASM("cp $0A"), ASM("cp $08"))
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 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 bugfixPowderBagSprite(rom):
rom.patch(0x03, 0x2055, "8E16", "0E1E")
def easyColorDungeonAccess(rom):
re = RoomEditor(rom, 0x312)
re.entities = [(3, 1, 246), (6, 1, 247)]
re.store(rom)
def removeGhost(rom):
## Ghost patch
# Do not have the ghost follow you after dungeon 4
rom.patch(0x03, 0x1E1B, ASM("LD [$DB79], A"), "", fill_nop=True)
def alwaysAllowSecretBook(rom):
rom.patch(0x15, 0x3F23, ASM("ld a, [$DB0E]\ncp $0E"), ASM("xor a\ncp $00"), fill_nop=True)
rom.patch(0x15, 0x3F2A, 0x3F30, "", fill_nop=True)
def cleanup(rom):
# Remove unused rooms to make some space in the rom
re = RoomEditor(rom, 0x2C4)
re.objects = []
re.entities = []
re.store(rom, 0x2C4)
re.store(rom, 0x2D4)
re.store(rom, 0x277)
re.store(rom, 0x278)
re.store(rom, 0x279)
re.store(rom, 0x1ED)
re.store(rom, 0x1FC) # Beta room
rom.texts[0x02B] = b'' # unused text
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 fixMarinFollower(rom):
# Allow opening of D0 with marin
rom.patch(0x02, 0x3402, ASM("ld a, [$DB73]"), ASM("xor a"), fill_nop=True)
# Instead of uselessly checking for sidescroller rooms for follower spawns, check for color dungeon instead
rom.patch(0x01, 0x1FCB, 0x1FD3, ASM("cp $FF\nret z"), fill_nop=True)
# Do not load marin graphics in color dungeon
rom.patch(0x00, 0x2EA6, 0x2EB0, ASM("cp $FF\njp $2ED3"), fill_nop=True)
# Fix marin on taltal bridge causing a lockup if you have marin with you
# This changes the location where the index to the marin entity is stored from it's normal location
# To the memory normal reserved for progress on the egg maze (which is reset to 0 on a warp)
rom.patch(0x18, 0x1EF7, ASM("ld [$C50F], a"), ASM("ld [$C5AA], a"))
rom.patch(0x18, 0x2126, ASM("ld a, [$C50F]"), ASM("ld a, [$C5AA]"))
rom.patch(0x18, 0x2139, ASM("ld a, [$C50F]"), ASM("ld a, [$C5AA]"))
rom.patch(0x18, 0x214F, ASM("ld a, [$C50F]"), ASM("ld a, [$C5AA]"))
rom.patch(0x18, 0x2166, ASM("ld a, [$C50F]"), ASM("ld a, [$C5AA]"))
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 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 warpHome(rom):
# Patch the S&Q menu to allow 3 options
rom.patch(0x01, 0x012A, 0x0150, ASM("""
ld hl, $C13F
call $6BA8 ; make sound on keypress
ldh a, [$CC] ; load joystick status
and $04 ; if up
jr z, noUp
dec [hl]
noUp:
ldh a, [$CC] ; load joystick status
and $08 ; if down
jr z, noDown
inc [hl]
noDown:
ld a, [hl]
cp $ff
jr nz, noWrapUp
ld a, $02
noWrapUp:
cp $03
jr nz, noWrapDown
xor a
noWrapDown:
ld [hl], a
jp $7E02
"""), fill_nop=True)
rom.patch(0x01, 0x3E02, 0x3E20, ASM("""
swap a
add a, $48
ld hl, $C018
ldi [hl], a
ld a, $24
ldi [hl], a
ld a, $BE
ldi [hl], a
ld [hl], $00
ret
"""), fill_nop=True)
rom.patch(0x01, 0x00B7, ASM("""
ld a, [$C13F]
cp $01
jr z, $3B
"""), ASM("""
ld a, [$C13F]
jp $7E20
"""), fill_nop=True)
re = RoomEditor(rom, 0x2a3)
warp = re.getWarps()[0]
type = 0x00
map = 0x00
room = warp.room
x = warp.target_x
y = warp.target_y
one_way = [
'd0',
'd1',
'd3',
'd4',
'd6',
'd8',
'animal_cave',
'right_fairy',
'rooster_grave',
'prairie_left_cave2',
'prairie_left_fairy',
'armos_fairy',
'boomerang_cave',
'madbatter_taltal',
'forest_madbatter',
]
one_way = {ENTRANCE_INFO[x].room for x in one_way}
if warp.room in one_way:
# we're starting at a one way exit room
# warp indoors to avoid soft locks
type = 0x01
map = 0x10
room = 0xa3
x = 0x50
y = 0x7f
rom.patch(0x01, 0x3E20, 0x3E6B, ASM("""
; First, handle save & quit
cp $01
jp z, $40F9
and a
jp z, $40BE ; return to normal "return to game" handling
ld a, [$C509] ; Check if we have an item in the shop
and a
jp nz, $40BE ; return to normal "return to game" handling
ld a, $0B
ld [$DB95], a
call $0C7D
; Replace warp0 tile data, and put link on that tile.
ld a, $%02x ; Type
ld [$D401], a
ld a, $%02x ; Map
ld [$D402], a
ld a, $%02x ; Room
ld [$D403], a
ld a, $%02x ; X
ld [$D404], a
ld a, $%02x ; Y
ld [$D405], a
ldh a, [$98]
swap a
and $0F
ld e, a
ldh a, [$99]
sub $08
and $F0
or e
ld [$D416], a
ld a, $07
ld [$DB96], a
ret
jp $40BE ; return to normal "return to game" handling
""" % (type, map, room, x, y)), fill_nop=True)
# Patch the RAM clear not to delete our custom dialog when we screen transition
rom.patch(0x01, 0x042C, "C629", "6B7E")
rom.patch(0x01, 0x3E6B, 0x3FFF, ASM("""
ld bc, $A0
call $29DC
ld bc, $1200
ld hl, $C100
call $29DF
ret
"""), fill_nop=True)
# Patch the S&Q screen to have 3 options.
be = BackgroundEditor(rom, 0x0D)
for n in range(2, 18):
be.tiles[0x99C0 + n] = be.tiles[0x9980 + n]
be.tiles[0x99A0 + n] = be.tiles[0x9960 + n]
be.tiles[0x9980 + n] = be.tiles[0x9940 + n]
be.tiles[0x9960 + n] = be.tiles[0x98e0 + n]
be.tiles[0x9960 + 10] = 0xCE
be.tiles[0x9960 + 11] = 0xCF
be.tiles[0x9960 + 12] = 0xC4
be.tiles[0x9960 + 13] = 0x7F
be.tiles[0x9960 + 14] = 0x7F
be.store(rom)
sprite_data = [
0b00000000,
0b01000100,
0b01000101,
0b01000101,
0b01111101,
0b01000101,
0b01000101,
0b01000100,
0b00000000,
0b11100100,
0b00010110,
0b00010101,
0b00010100,
0b00010100,
0b00010100,
0b11100100,
]
for n in range(32):
rom.banks[0x0F][0x08E0 + n] = sprite_data[n // 2]
def addFrameCounter(rom, check_count):
# Patch marin giving the start the game to jump to a custom handler
rom.patch(0x05, 0x1299, ASM("ld a, $01\ncall $2385"), ASM("push hl\nld a, $0D\nrst 8\npop hl"), fill_nop=True)
# Add code that needs to be called every frame to tick our ingame time counter.
rom.patch(0x00, 0x0091, "00" * (0x100 - 0x91), ASM("""
ld a, [$DB95] ;Get the gameplay type
dec a ; and if it was 1
ret z ; we are at the credits and the counter should stop.
; Check if the timer expired
ld hl, $FF0F
bit 2, [hl]
ret z
res 2, [hl]
; Increase the "subsecond" counter, and continue if it "overflows"
call $27D0 ; Enable SRAM
ld hl, $B000
ld a, [hl]
inc a
cp $20
ld [hl], a
ret nz
xor a
ldi [hl], a
; Increase the seconds counter/minutes/hours counter
increaseSecMinHours:
ld a, [hl]
inc a
daa
ld [hl], a
cp $60
ret nz
xor a
ldi [hl], a
jr increaseSecMinHours
"""), fill_nop=True)
# Replace a cgb check with the call to our counter code.
rom.patch(0x00, 0x0367, ASM("ld a, $0C\ncall $0B0B"), ASM("call $0091\nld a, $2C"))
# Do not switch to 8x8 sprite mode
rom.patch(0x17, 0x2E9E, ASM("res 2, [hl]"), "", fill_nop=True)
# We need to completely reorder link sitting on the raft to work with 16x8 sprites.
sprites = rom.banks[0x38][0x1600:0x1800]
sprites[0x1F0:0x200] = b'\x00' * 16
for index, position in enumerate(
(0, 0x1F,
1, 0x1F, 2, 0x1F,
7, 8,
3, 9, 4, 10, 5, 11, 6, 12,
3, 13, 4, 14, 5, 15, 6, 16,
3, 17, 4, 18, 5, 19, 6, 20,
)):
rom.banks[0x38][0x1600+index*0x10:0x1610+index*0x10] = sprites[position*0x10:0x10+position*0x10]
rom.patch(0x27, 0x376E, 0x3776, "00046601", fill_nop=True)
rom.patch(0x27, 0x384E, ASM("ld c, $08"), ASM("ld c, $04"))
rom.patch(0x27, 0x3776, 0x3826,
"FA046002"
"0208640402006204"
"0A106E030A086C030A006A030AF86803"
"FA046002"
"0208640402006204"
"0A1076030A0874030A0072030AF87003"
"FA046002"
"0208640402006204"
"0A107E030A087C030A007A030AF87803"
, fill_nop=True)
rom.patch(0x27, 0x382E, ASM("ld a, $6C"), ASM("ld a, $80")) # OAM start position
rom.patch(0x27, 0x384E, ASM("ld c, $08"), ASM("ld c, $04")) # Amount of overlay OAM data
rom.patch(0x27, 0x3826, 0x382E, ASM("dw $7776, $7792, $77AE, $7792")) # pointers to animation
rom.patch(0x27, 0x3846, ASM("ld c, $2C"), ASM("ld c, $1C")) # Amount of OAM data
# TODO: fix flying windfish
# Upper line of credits roll into "TIME"
rom.patch(0x17, 0x069D, 0x0713, ASM("""
ld hl, OAMData
ld de, $C000 ; OAM Buffer
ld bc, $0048
call $2914
ret
OAMData:
db $20, $18, $34, $00 ;T
db $20, $20, $20, $00 ;I
db $20, $28, $28, $00 ;M
db $20, $30, $18, $00 ;E
db $20, $70, $16, $00 ;D
db $20, $78, $18, $00 ;E
db $20, $80, $10, $00 ;A
db $20, $88, $34, $00 ;T
db $20, $90, $1E, $00 ;H
db $50, $18, $14, $00 ;C
db $50, $20, $1E, $00 ;H
db $50, $28, $18, $00 ;E
db $50, $30, $14, $00 ;C
db $50, $38, $24, $00 ;K
db $50, $40, $32, $00 ;S
db $68, $38, $%02x, $00 ;0
db $68, $40, $%02x, $00 ;0
db $68, $48, $%02x, $00 ;0
""" % ((((check_count // 100) % 10) * 2) | 0x40, (((check_count // 10) % 10) * 2) | 0x40, ((check_count % 10) * 2) | 0x40), 0x469D), fill_nop=True)
# Lower line of credits roll into XX XX XX
rom.patch(0x17, 0x0784, 0x082D, ASM("""
ld hl, OAMData
ld de, $C048 ; OAM Buffer
ld bc, $0038
call $2914
call $27D0 ; Enable SRAM
ld hl, $C04A
ld a, [$B003] ; hours
call updateOAM
ld a, [$B002] ; minutes
call updateOAM
ld a, [$B001] ; seconds
call updateOAM
ld a, [$DB58] ; death count high
call updateOAM
ld a, [$DB57] ; death count low
call updateOAM
ld a, [$B011] ; check count high
call updateOAM
ld a, [$B010] ; check count low
call updateOAM
ret
updateOAM:
ld de, $0004
ld b, a
swap a
and $0F
add a, a
or $40
ld [hl], a
add hl, de
ld a, b
and $0F
add a, a
or $40
ld [hl], a
add hl, de
ret
OAMData:
db $38, $18, $40, $00 ;0 (10 hours)
db $38, $20, $40, $00 ;0 (1 hours)
db $38, $30, $40, $00 ;0 (10 minutes)
db $38, $38, $40, $00 ;0 (1 minutes)
db $38, $48, $40, $00 ;0 (10 seconds)
db $38, $50, $40, $00 ;0 (1 seconds)
db $00, $00, $40, $00 ;0 (1000 death)
db $38, $80, $40, $00 ;0 (100 death)
db $38, $88, $40, $00 ;0 (10 death)
db $38, $90, $40, $00 ;0 (1 death)
; checks
db $00, $00, $40, $00 ;0
db $68, $18, $40, $00 ;0
db $68, $20, $40, $00 ;0
db $68, $28, $40, $00 ;0
""", 0x4784), fill_nop=True)
# Grab the "mostly" complete A-Z font
sprites = rom.banks[0x38][0x1100:0x1400]
for index, position in enumerate((
0x10, 0x20, # A
0x11, 0x21, # B
0x12, 0x12 | 0x100, # C
0x13, 0x23, # D
0x14, 0x24, # E
0x14, 0x25, # F
0x12, 0x22, # G
0x20 | 0x100, 0x26, # H
0x17, 0x17 | 0x100, # I
0x28, 0x28, # J
0x19, 0x29, # K
0x06, 0x07, # L
0x1A, 0x2A, # M
0x1B, 0x2B, # N
0x00, 0x00, # O?
0x00, 0x00, # P?
#0x00, 0x00, # Q?
0x11, 0x18, # R
0x1C, 0x2C, # S
0x1D, 0x2D, # T
0x26, 0x10, # U
0x00, 0x00, # V?
0x1E, 0x2E, # W
#0x00, 0x00, # X?
#0x00, 0x00, # Y?
0x27, 0x27, # Z
)):
sprite = sprites[(position&0xFF)*0x10:0x10+(position&0xFF)*0x10]
if position & 0x100:
for n in range(4):
sprite[n * 2], sprite[14 - n * 2] = sprite[14 - n * 2], sprite[n * 2]
sprite[n * 2 + 1], sprite[15 - n * 2] = sprite[15 - n * 2], sprite[n * 2 + 1]
rom.banks[0x38][0x1100+index*0x10:0x1110+index*0x10] = sprite
# Number graphics change for the end
tile_graphics = """
........ ........ ........ ........ ........ ........ ........ ........ ........ ........
.111111. ..1111.. .111111. .111111. ..11111. 11111111 .111111. 11111111 .111111. .111111.
11333311 .11331.. 11333311 11333311 .113331. 13333331 11333311 13333331 11333311 11333311
13311331 113331.. 13311331 13311331 1133331. 13311111 13311331 11111331 13311331 13311331
13311331 133331.. 13311331 11111331 1331331. 1331.... 13311331 ...11331 13311331 13311331
13311331 133331.. 11111331 ....1331 1331331. 1331.... 13311111 ...13311 13311331 13311331
13311331 111331.. ...13311 .1111331 1331331. 1331111. 1331.... ..11331. 13311331 13311331
13311331 ..1331.. ..11331. .1333331 13313311 13333311 1331111. ..13311. 11333311 11333331
13311331 ..1331.. ..13311. .1111331 13333331 13311331 13333311 .11331.. 13311331 .1111331
13311331 ..1331.. .11331.. ....1331 11113311 11111331 13311331 .13311.. 13311331 ....1331
13311331 ..1331.. .13311.. ....1331 ...1331. ....1331 13311331 11331... 13311331 ....1331
13311331 ..1331.. 11331... 11111331 ...1331. 11111331 13311331 13311... 13311331 11111331
13311331 ..1331.. 13311111 13311331 ...1331. 13311331 13311331 1331.... 13311331 13311331
11333311 ..1331.. 13333331 11333311 ...1331. 11333311 11333311 1331.... 11333311 11333311
.111111. ..1111.. 11111111 .111111. ...1111. .111111. .111111. 1111.... .111111. .111111.
........ ........ ........ ........ ........ ........ ........ ........ ........ ........
""".strip()
for n in range(10):
gfx_high = "\n".join([line.split(" ")[n] for line in tile_graphics.split("\n")[:8]])
gfx_low = "\n".join([line.split(" ")[n] for line in tile_graphics.split("\n")[8:]])
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)

View File

@@ -0,0 +1,7 @@
from ..roomEditor import RoomEditor
def desertAccess(rom):
re = RoomEditor(rom, 0x0FD)
re.entities = [(6, 2, 0xC4)]
re.store(rom)

View File

@@ -0,0 +1,134 @@
from ..assembler import ASM
def fixDroppedKey(rom):
# Patch the rendering code to use the dropped key rendering code.
rom.patch(0x03, 0x1C99, None, ASM("""
ld a, $04
rst 8
jp $5CA6
"""))
# Patch the key pickup code to use the chest pickup code.
rom.patch(0x03, 0x248F, None, ASM("""
ldh a, [$F6] ; load room nr
cp $7C ; L4 Side-view room where the key drops
jr nz, notSpecialSideView
ld hl, $D969 ; status of the room above the side-view where the key drops in dungeon 4
set 4, [hl]
notSpecialSideView:
call $512A ; mark room as done
; Handle item effect
ld a, $06 ; giveItemMultiworld
rst 8
ldh a, [$F1] ; Load active sprite variant to see if this is just a normal small key
cp $1A
jr z, isAKey
;Show message (if not a key)
ld a, $0A ; showMessageMultiworld
rst 8
isAKey:
ret
"""))
rom.patch(0x03, 0x24B7, "3E", "3E") # sanity check
# Mark all dropped keys as keys by default.
for n in range(0x316):
rom.banks[0x3E][0x3800 + n] = 0x1A
# Set the proper angler key by default
rom.banks[0x3E][0x3800 + 0x0CE] = 0x12
rom.banks[0x3E][0x3800 + 0x1F8] = 0x12
# Set the proper bird key by default
rom.banks[0x3E][0x3800 + 0x27A] = 0x14
# Set the proper face key by default
rom.banks[0x3E][0x3800 + 0x27F] = 0x13
# Set the proper hookshot key by default
rom.banks[0x3E][0x3800 + 0x180] = 0x03
# Set the proper golden leaves
rom.banks[0x3E][0x3800 + 0x058] = 0x15
rom.banks[0x3E][0x3800 + 0x05a] = 0x15
rom.banks[0x3E][0x3800 + 0x2d2] = 0x15
rom.banks[0x3E][0x3800 + 0x2c5] = 0x15
rom.banks[0x3E][0x3800 + 0x2c6] = 0x15
# Set the slime key drop.
rom.banks[0x3E][0x3800 + 0x0C6] = 0x0F
# Set the heart pieces
rom.banks[0x3E][0x3800 + 0x000] = 0x80
rom.banks[0x3E][0x3800 + 0x2A4] = 0x80
rom.banks[0x3E][0x3800 + 0x2B1] = 0x80 # fishing game, unused
rom.banks[0x3E][0x3800 + 0x044] = 0x80
rom.banks[0x3E][0x3800 + 0x2AB] = 0x80
rom.banks[0x3E][0x3800 + 0x2DF] = 0x80
rom.banks[0x3E][0x3800 + 0x2E5] = 0x80
rom.banks[0x3E][0x3800 + 0x078] = 0x80
rom.banks[0x3E][0x3800 + 0x2E6] = 0x80
rom.banks[0x3E][0x3800 + 0x1E8] = 0x80
rom.banks[0x3E][0x3800 + 0x1F2] = 0x80
rom.banks[0x3E][0x3800 + 0x2BA] = 0x80
# Set the seashells
rom.banks[0x3E][0x3800 + 0x0A3] = 0x20
rom.banks[0x3E][0x3800 + 0x2B2] = 0x20
rom.banks[0x3E][0x3800 + 0x0A5] = 0x20
rom.banks[0x3E][0x3800 + 0x0A6] = 0x20
rom.banks[0x3E][0x3800 + 0x08B] = 0x20
rom.banks[0x3E][0x3800 + 0x074] = 0x20
rom.banks[0x3E][0x3800 + 0x0A4] = 0x20
rom.banks[0x3E][0x3800 + 0x0D2] = 0x20
rom.banks[0x3E][0x3800 + 0x0E9] = 0x20
rom.banks[0x3E][0x3800 + 0x0B9] = 0x20
rom.banks[0x3E][0x3800 + 0x0F8] = 0x20
rom.banks[0x3E][0x3800 + 0x0A8] = 0x20
rom.banks[0x3E][0x3800 + 0x0FF] = 0x20
rom.banks[0x3E][0x3800 + 0x1E3] = 0x20
rom.banks[0x3E][0x3800 + 0x0DA] = 0x20
rom.banks[0x3E][0x3800 + 0x00C] = 0x20
# Set heart containers
rom.banks[0x3E][0x3800 + 0x106] = 0x89
rom.banks[0x3E][0x3800 + 0x12B] = 0x89
rom.banks[0x3E][0x3800 + 0x15A] = 0x89
rom.banks[0x3E][0x3800 + 0x1FF] = 0x89
rom.banks[0x3E][0x3800 + 0x185] = 0x89
rom.banks[0x3E][0x3800 + 0x1BC] = 0x89
rom.banks[0x3E][0x3800 + 0x2E8] = 0x89
rom.banks[0x3E][0x3800 + 0x234] = 0x89
# Toadstool
rom.banks[0x3E][0x3800 + 0x050] = 0x50
# Sword on beach
rom.banks[0x3E][0x3800 + 0x0F2] = 0x0B
# Sword upgrade
rom.banks[0x3E][0x3800 + 0x2E9] = 0x0B
# Songs
rom.banks[0x3E][0x3800 + 0x092] = 0x8B # song 1
rom.banks[0x3E][0x3800 + 0x0DC] = 0x8B # song 1
rom.banks[0x3E][0x3800 + 0x2FD] = 0x8C # song 2
rom.banks[0x3E][0x3800 + 0x2FB] = 0x8D # song 3
# Instruments
rom.banks[0x3E][0x3800 + 0x102] = 0x8E
rom.banks[0x3E][0x3800 + 0x12a] = 0x8F
rom.banks[0x3E][0x3800 + 0x159] = 0x90
rom.banks[0x3E][0x3800 + 0x162] = 0x91
rom.banks[0x3E][0x3800 + 0x182] = 0x92
rom.banks[0x3E][0x3800 + 0x1b5] = 0x93
rom.banks[0x3E][0x3800 + 0x22c] = 0x94
rom.banks[0x3E][0x3800 + 0x230] = 0x95
# Start item
rom.banks[0x3E][0x3800 + 0x2a3] = 0x01
# Master stalfos overkill drops
rom.banks[0x3E][0x3800 + 0x195] = 0x1A
rom.banks[0x3E][0x3800 + 0x192] = 0x1A
rom.banks[0x3E][0x3800 + 0x184] = 0x1A

View File

@@ -0,0 +1,129 @@
from ..roomEditor import RoomEditor, Object, ObjectHorizontal
KEY_DOORS = {
0xEC: 0xF4,
0xED: 0xF5,
0xEE: 0xF6,
0xEF: 0xF7,
0xF8: 0xF4,
}
def removeKeyDoors(rom):
for n in range(0x100, 0x316):
if n == 0x2FF:
continue
update = False
re = RoomEditor(rom, n)
for obj in re.objects:
if obj.type_id in KEY_DOORS:
obj.type_id = KEY_DOORS[obj.type_id]
update = True
if obj.type_id == 0xDE: # Keyblocks
obj.type_id = re.floor_object & 0x0F
update = True
if update:
re.store(rom)
def patchNoDungeons(rom):
def setMinimap(dungeon_nr, x, y, room):
for n in range(64):
if rom.banks[0x14][0x0220 + 64 * dungeon_nr + n] == room:
rom.banks[0x14][0x0220 + 64 * dungeon_nr + n] = 0xFF
rom.banks[0x14][0x0220 + 64 * dungeon_nr + x + y * 8] = room
#D1
setMinimap(0, 3, 6, 0x06)
setMinimap(0, 3, 5, 0x02)
re = RoomEditor(rom, 0x117)
for n in range(1, 7):
re.removeObject(n, 0)
re.removeObject(0, n)
re.removeObject(9, n)
re.objects += [Object(4, 0, 0xf0)]
re.store(rom)
re = RoomEditor(rom, 0x11A)
re.getWarps()[0].room = 0x117
re.store(rom)
re = RoomEditor(rom, 0x11B)
re.getWarps()[0].room = 0x117
re.store(rom)
#D2
setMinimap(1, 2, 6, 0x2B)
setMinimap(1, 1, 6, 0x2A)
re = RoomEditor(rom, 0x136)
for n in range(1, 7):
re.removeObject(n, 0)
re.objects += [Object(4, 0, 0xf0)]
re.store(rom)
#D3
setMinimap(2, 1, 6, 0x5A)
setMinimap(2, 1, 5, 0x59)
re = RoomEditor(rom, 0x152)
for n in range(2, 7):
re.removeObject(9, n)
re.store(rom)
#D4
setMinimap(3, 3, 6, 0x66)
setMinimap(3, 3, 5, 0x62)
re = RoomEditor(rom, 0x17A)
for n in range(3, 7):
re.removeObject(n, 0)
re.objects += [Object(4, 0, 0xf0)]
re.store(rom)
#D5
setMinimap(4, 7, 6, 0x85)
setMinimap(4, 7, 5, 0x82)
re = RoomEditor(rom, 0x1A1)
for n in range(3, 8):
re.removeObject(n, 0)
re.removeObject(0, n)
for n in range(4, 6):
re.removeObject(n, 1)
re.removeObject(n, 2)
re.objects += [Object(4, 0, 0xf0)]
re.store(rom)
#D6
setMinimap(5, 3, 6, 0xBC)
setMinimap(5, 3, 5, 0xB5)
re = RoomEditor(rom, 0x1D4)
for n in range(2, 8):
re.removeObject(0, n)
re.removeObject(9, n)
re.objects += [Object(4, 0, 0xf0)]
re.store(rom)
#D7
setMinimap(6, 1, 6, 0x2E)
setMinimap(6, 1, 5, 0x2C)
re = RoomEditor(rom, 0x20E)
for n in range(1, 8):
re.removeObject(0, n)
re.removeObject(9, n)
re.objects += [Object(3, 0, 0x29), ObjectHorizontal(4, 0, 0x0D, 2), Object(6, 0, 0x2A)]
re.store(rom)
re = RoomEditor(rom, 0x22E)
re.objects = [Object(4, 0, 0xf0), Object(3, 7, 0x2B), ObjectHorizontal(4, 7, 0x0D, 2), Object(6, 7, 0x2C), Object(1, 0, 0xA8)] + re.getWarps()
re.floor_object = 13
re.store(rom)
re = RoomEditor(rom, 0x22C)
re.removeObject(0, 7)
re.removeObject(2, 7)
re.objects.append(ObjectHorizontal(0, 7, 0x03, 3))
re.store(rom)
#D8
setMinimap(7, 3, 6, 0x34)
setMinimap(7, 3, 5, 0x30)
re = RoomEditor(rom, 0x25D)
re.objects += [Object(3, 0, 0x25), Object(4, 0, 0xf0), Object(6, 0, 0x26)]
re.store(rom)
#D0
setMinimap(11, 2, 6, 0x00)
setMinimap(11, 3, 6, 0x01)

View File

@@ -0,0 +1,139 @@
from ..assembler import ASM
import os
def updateEndScreen(rom):
# Call our custom data loader in bank 3F
rom.patch(0x00, 0x391D, ASM("""
ld a, $20
ld [$2100], a
jp $7de6
"""), ASM("""
ld a, $3F
ld [$2100], a
jp $4200
"""))
rom.patch(0x17, 0x2FCE, "B170", "D070") # Ignore the final tile data load
rom.patch(0x3F, 0x0200, None, ASM("""
; Disable LCD
xor a
ldh [$40], a
ld hl, $8000
ld de, $5000
copyLoop:
ld a, [de]
inc de
ldi [hl], a
bit 4, h
jr z, copyLoop
ld a, $01
ldh [$4F], a
ld hl, $8000
ld de, $6000
copyLoop2:
ld a, [de]
inc de
ldi [hl], a
bit 4, h
jr z, copyLoop2
ld hl, $9800
ld de, $0190
clearLoop1:
xor a
ldi [hl], a
dec de
ld a, d
or e
jr nz, clearLoop1
ld de, $0190
clearLoop2:
ld a, $08
ldi [hl], a
dec de
ld a, d
or e
jr nz, clearLoop2
xor a
ldh [$4F], a
ld hl, $9800
ld de, $000C
xor a
loadLoop1:
ldi [hl], a
ld b, a
ld a, l
and $1F
cp $14
jr c, .noLineSkip
add hl, de
.noLineSkip:
ld a, b
inc a
jr nz, loadLoop1
loadLoop2:
ldi [hl], a
ld b, a
ld a, l
and $1F
cp $14
jr c, .noLineSkip
add hl, de
.noLineSkip:
ld a, b
inc a
jr nz, loadLoop2
; Load palette
ld hl, $DC10
ld a, $00
ldi [hl], a
ld a, $00
ldi [hl], a
ld a, $ad
ldi [hl], a
ld a, $35
ldi [hl], a
ld a, $94
ldi [hl], a
ld a, $52
ldi [hl], a
ld a, $FF
ldi [hl], a
ld a, $7F
ldi [hl], a
ld a, $00
ld [$DDD3], a
ld a, $04
ld [$DDD4], a
ld a, $81
ld [$DDD1], a
; Enable LCD
ld a, $91
ldh [$40], a
ld [$d6fd], a
xor a
ldh [$96], a
ldh [$97], a
ret
"""))
addr = 0x1000
for c in open(os.path.join(os.path.dirname(__file__), "nyan.bin"), "rb").read():
rom.banks[0x3F][addr] = c
addr += 1

View File

@@ -0,0 +1,462 @@
from ..roomEditor import RoomEditor, Object, ObjectWarp, ObjectHorizontal
from ..assembler import ASM
from ..locations import constants
from typing import List
# Room containing the boss
BOSS_ROOMS = [
0x106,
0x12b,
0x15a,
0x166,
0x185,
0x1bc,
0x223, # Note: unused room normally
0x234,
0x300,
]
BOSS_ENTITIES = [
(3, 2, 0x59),
(4, 2, 0x5C),
(4, 3, 0x5B),
None,
(4, 3, 0x5D),
(4, 3, 0x5A),
None,
(4, 3, 0x62),
(5, 2, 0xF9),
]
MINIBOSS_ENTITIES = {
"ROLLING_BONES": [(8, 3, 0x81), (6, 3, 0x82)],
"HINOX": [(5, 2, 0x89)],
"DODONGO": [(3, 2, 0x60), (5, 2, 0x60)],
"CUE_BALL": [(1, 1, 0x8e)],
"GHOMA": [(2, 1, 0x5e), (2, 4, 0x5e)],
"SMASHER": [(5, 2, 0x92)],
"GRIM_CREEPER": [(4, 0, 0xbc)],
"BLAINO": [(5, 3, 0xbe)],
"AVALAUNCH": [(5, 1, 0xf4)],
"GIANT_BUZZ_BLOB": [(4, 2, 0xf8)],
"MOBLIN_KING": [(5, 5, 0xe4)],
"ARMOS_KNIGHT": [(4, 3, 0x88)],
}
MINIBOSS_ROOMS = {
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,
}
def fixArmosKnightAsMiniboss(rom):
# Make the armos temple room with armos knight drop a ceiling key on kill.
# This makes the door always open, but that's fine.
rom.patch(0x14, 0x017F, "21", "81")
# Do not change the drop from Armos knight into a ceiling key.
rom.patch(0x06, 0x12E8, ASM("ld [hl], $30"), "", fill_nop=True)
def getBossRoomStatusFlagLocation(dungeon_nr):
if BOSS_ROOMS[dungeon_nr] >= 0x300:
return 0xDDE0 - 0x300 + BOSS_ROOMS[dungeon_nr]
return 0xD800 + BOSS_ROOMS[dungeon_nr]
def fixDungeonItem(item_chest_id, dungeon_nr):
if item_chest_id == constants.CHEST_ITEMS[constants.MAP]:
return constants.CHEST_ITEMS["MAP%d" % (dungeon_nr + 1)]
if item_chest_id == constants.CHEST_ITEMS[constants.COMPASS]:
return constants.CHEST_ITEMS["COMPASS%d" % (dungeon_nr + 1)]
if item_chest_id == constants.CHEST_ITEMS[constants.KEY]:
return constants.CHEST_ITEMS["KEY%d" % (dungeon_nr + 1)]
if item_chest_id == constants.CHEST_ITEMS[constants.NIGHTMARE_KEY]:
return constants.CHEST_ITEMS["NIGHTMARE_KEY%d" % (dungeon_nr + 1)]
if item_chest_id == constants.CHEST_ITEMS[constants.STONE_BEAK]:
return constants.CHEST_ITEMS["STONE_BEAK%d" % (dungeon_nr + 1)]
return item_chest_id
def getCleanBossRoom(rom, dungeon_nr):
re = RoomEditor(rom, BOSS_ROOMS[dungeon_nr])
new_objects = []
for obj in re.objects:
if isinstance(obj, ObjectWarp):
continue
if obj.type_id == 0xBE: # Remove staircases
continue
if obj.type_id == 0x06: # Remove lava
continue
if obj.type_id == 0x1c: # Change D1 pits into normal pits
obj.type_id = 0x01
if obj.type_id == 0x1e: # Change D1 pits into normal pits
obj.type_id = 0xaf
if obj.type_id == 0x1f: # Change D1 pits into normal pits
obj.type_id = 0xb0
if obj.type_id == 0xF5: # Change open doors into closing doors.
obj.type_id = 0xF1
new_objects.append(obj)
# Make D4 room a valid fighting room by removing most content.
if dungeon_nr == 3:
new_objects = new_objects[:2] + [Object(1, 1, 0xAC), Object(8, 1, 0xAC), Object(1, 6, 0xAC), Object(8, 6, 0xAC)]
# D7 has an empty room we use for most bosses, but it needs some adjustments.
if dungeon_nr == 6:
# Move around the unused and instrument room.
rom.banks[0x14][0x03a0 + 6 + 1 * 8] = 0x00
rom.banks[0x14][0x03a0 + 7 + 2 * 8] = 0x2C
rom.banks[0x14][0x03a0 + 7 + 3 * 8] = 0x23
rom.banks[0x14][0x03a0 + 6 + 5 * 8] = 0x00
rom.banks[0x14][0x0520 + 7 + 2 * 8] = 0x2C
rom.banks[0x14][0x0520 + 7 + 3 * 8] = 0x23
rom.banks[0x14][0x0520 + 6 + 5 * 8] = 0x00
re.floor_object &= 0x0F
new_objects += [
Object(4, 0, 0xF0),
Object(1, 6, 0xBE),
ObjectWarp(1, dungeon_nr, 0x22E, 24, 16)
]
# Set the stairs towards the eagle tower top to our new room.
r = RoomEditor(rom, 0x22E)
r.objects[-1] = ObjectWarp(1, dungeon_nr, re.room, 24, 112)
r.store(rom)
# Remove the normal door to the instrument room
r = RoomEditor(rom, 0x22e)
r.removeObject(4, 0)
r.store(rom)
rom.banks[0x14][0x22e - 0x100] = 0x00
r = RoomEditor(rom, 0x22c)
r.changeObject(0, 7, 0x03)
r.changeObject(2, 7, 0x03)
r.store(rom)
re.objects = new_objects
re.entities = []
return re
def changeBosses(rom, mapping: List[int]):
# Fix the color dungeon not properly warping to room 0 with the boss.
for addr in range(0x04E0, 0x04E0 + 64):
if rom.banks[0x14][addr] == 0x00 and addr not in {0x04E0 + 1 + 3 * 8, 0x04E0 + 2 + 6 * 8}:
rom.banks[0x14][addr] = 0xFF
# Fix the genie death not really liking pits/water.
rom.patch(0x04, 0x0521, ASM("ld [hl], $81"), ASM("ld [hl], $91"))
# For the sidescroll bosses, we need to update this check to be the evil eagle dungeon.
# But if evil eagle is not there we still need to remove this check to make angler fish work in D7
dungeon_nr = mapping.index(6) if 6 in mapping else 0xFE
rom.patch(0x02, 0x1FC8, ASM("cp $06"), ASM("cp $%02x" % (dungeon_nr if dungeon_nr < 8 else 0xff)))
for dungeon_nr in range(9):
target = mapping[dungeon_nr]
if target == dungeon_nr:
continue
if target == 3: # D4 fish boss
# If dungeon_nr == 6: use normal eagle door towards fish.
if dungeon_nr == 6:
# Add the staircase to the boss, and fix the warp back.
re = RoomEditor(rom, 0x22E)
for obj in re.objects:
if isinstance(obj, ObjectWarp):
obj.type_id = 2
obj.map_nr = 3
obj.room = 0x1EF
obj.target_x = 24
obj.target_y = 16
re.store(rom)
re = RoomEditor(rom, 0x1EF)
re.objects[-1] = ObjectWarp(1, dungeon_nr if dungeon_nr < 8 else 0xff, 0x22E, 24, 16)
re.store(rom)
else:
# Set the proper room event flags
rom.banks[0x14][BOSS_ROOMS[dungeon_nr] - 0x100] = 0x2A
# Add the staircase to the boss, and fix the warp back.
re = getCleanBossRoom(rom, dungeon_nr)
re.objects += [Object(4, 4, 0xBE), ObjectWarp(2, 3, 0x1EF, 24, 16)]
re.store(rom)
re = RoomEditor(rom, 0x1EF)
re.objects[-1] = ObjectWarp(1, dungeon_nr if dungeon_nr < 8 else 0xff, BOSS_ROOMS[dungeon_nr], 72, 80)
re.store(rom)
# Patch the fish heart container to open up the right room.
if dungeon_nr == 6:
rom.patch(0x03, 0x1A0F, ASM("ld hl, $D966"), ASM("ld hl, $%04x" % (0xD800 + 0x22E)))
else:
rom.patch(0x03, 0x1A0F, ASM("ld hl, $D966"), ASM("ld hl, $%04x" % (getBossRoomStatusFlagLocation(dungeon_nr))))
# Patch the proper item towards the D4 boss
rom.banks[0x3E][0x3800 + 0x01ff] = fixDungeonItem(rom.banks[0x3E][0x3800 + BOSS_ROOMS[dungeon_nr]], dungeon_nr)
rom.banks[0x3E][0x3300 + 0x01ff] = fixDungeonItem(rom.banks[0x3E][0x3300 + BOSS_ROOMS[dungeon_nr]], dungeon_nr)
elif target == 6: # Evil eagle
rom.banks[0x14][BOSS_ROOMS[dungeon_nr] - 0x100] = 0x2A
# Patch the eagle heart container to open up the right room.
rom.patch(0x03, 0x1A04, ASM("ld hl, $DA2E"), ASM("ld hl, $%04x" % (getBossRoomStatusFlagLocation(dungeon_nr))))
# Add the staircase to the boss, and fix the warp back.
re = getCleanBossRoom(rom, dungeon_nr)
re.objects += [Object(4, 4, 0xBE), ObjectWarp(2, 6, 0x2F8, 72, 80)]
re.store(rom)
re = RoomEditor(rom, 0x2F8)
re.objects[-1] = ObjectWarp(1, dungeon_nr if dungeon_nr < 8 else 0xff, BOSS_ROOMS[dungeon_nr], 72, 80)
re.store(rom)
# Patch the proper item towards the D7 boss
rom.banks[0x3E][0x3800 + 0x02E8] = fixDungeonItem(rom.banks[0x3E][0x3800 + BOSS_ROOMS[dungeon_nr]], dungeon_nr)
rom.banks[0x3E][0x3300 + 0x02E8] = fixDungeonItem(rom.banks[0x3E][0x3300 + BOSS_ROOMS[dungeon_nr]], dungeon_nr)
else:
rom.banks[0x14][BOSS_ROOMS[dungeon_nr] - 0x100] = 0x21
re = getCleanBossRoom(rom, dungeon_nr)
re.entities = [BOSS_ENTITIES[target]]
if target == 4:
# For slime eel, we need to setup the right wall tiles.
rom.banks[0x20][0x2EB3 + BOSS_ROOMS[dungeon_nr] - 0x100] = 0x06
if target == 5:
# Patch facade so he doesn't use the spinning tiles, which is a problem for the sprites.
rom.patch(0x04, 0x121D, ASM("cp $14"), ASM("cp $00"))
rom.patch(0x04, 0x1226, ASM("cp $04"), ASM("cp $00"))
rom.patch(0x04, 0x127F, ASM("cp $14"), ASM("cp $00"))
if target == 7:
pass
# For hot head, add some lava (causes graphical glitches)
# re.animation_id = 0x06
# re.objects += [
# ObjectHorizontal(3, 2, 0x06, 4),
# ObjectHorizontal(2, 3, 0x06, 6),
# ObjectHorizontal(2, 4, 0x06, 6),
# ObjectHorizontal(3, 5, 0x06, 4),
# ]
re.store(rom)
def readBossMapping(rom):
mapping = []
for dungeon_nr in range(9):
r = RoomEditor(rom, BOSS_ROOMS[dungeon_nr])
if r.entities:
mapping.append(BOSS_ENTITIES.index(r.entities[0]))
elif isinstance(r.objects[-1], ObjectWarp) and r.objects[-1].room == 0x1ef:
mapping.append(3)
elif isinstance(r.objects[-1], ObjectWarp) and r.objects[-1].room == 0x2f8:
mapping.append(6)
else:
mapping.append(dungeon_nr)
return mapping
def changeMiniBosses(rom, mapping):
# Fix avalaunch not working when entering a room from the left or right
rom.patch(0x03, 0x0BE0, ASM("""
ld [hl], $50
ld hl, $C2D0
add hl, bc
ld [hl], $00
jp $4B56
"""), ASM("""
ld a, [hl]
sub $08
ld [hl], a
ld hl, $C2D0
add hl, bc
ld [hl], b ; b is always zero here
ret
"""), fill_nop=True)
# Fix avalaunch waiting until the room event is done (and not all rooms have a room event on enter)
rom.patch(0x36, 0x1C14, ASM("ret z"), "", fill_nop=True)
# Fix giant buzz blob waiting until the room event is done (and not all rooms have a room event on enter)
rom.patch(0x36, 0x153B, ASM("ret z"), "", fill_nop=True)
# Remove the powder fairy from giant buzz blob
rom.patch(0x36, 0x14F7, ASM("jr nz, $05"), ASM("jr $05"))
# Do not allow the force barrier in D3 dodongo room
rom.patch(0x14, 0x14AC, 0x14B5, ASM("jp $7FE0"), fill_nop=True)
rom.patch(0x14, 0x3FE0, "00" * 0x20, ASM("""
ld a, [$C124] ; room transition
ld hl, $C17B
or [hl]
ret nz
ldh a, [$F6] ; room
cp $45 ; check for D3 dodogo room
ret z
cp $7F ; check for armos temple room
ret z
jp $54B5
"""), fill_nop=True)
# Patch smasher to spawn the ball closer, so it doesn't spawn on the wall in the armos temple
rom.patch(0x06, 0x0533, ASM("add a, $30"), ASM("add a, $20"))
for target, name in mapping.items():
re = RoomEditor(rom, MINIBOSS_ROOMS[target])
re.entities = [e for e in re.entities if e[2] == 0x61] # Only keep warp, if available
re.entities += MINIBOSS_ENTITIES[name]
if re.room == 0x228 and name != "GRIM_CREEPER":
for x in range(3, 7):
for y in range(0, 3):
re.removeObject(x, y)
if name == "CUE_BALL":
re.objects += [
Object(3, 3, 0x2c),
ObjectHorizontal(4, 3, 0x22, 2),
Object(6, 3, 0x2b),
Object(3, 4, 0x2a),
ObjectHorizontal(4, 4, 0x21, 2),
Object(6, 4, 0x29),
]
if name == "BLAINO":
# BLAINO needs a warp object to hit you to the entrance of the dungeon.
if len(re.getWarps()) < 1:
# Default to start house.
target = (0x10, 0x2A3, 0x50, 0x7c)
if 0x100 <= re.room < 0x11D: #D1
target = (0, 0x117, 80, 80)
elif 0x11D <= re.room < 0x140: #D2
target = (1, 0x136, 80, 80)
elif 0x140 <= re.room < 0x15D: #D3
target = (2, 0x152, 80, 80)
elif 0x15D <= re.room < 0x180: #D4
target = (3, 0x174, 80, 80)
elif 0x180 <= re.room < 0x1AC: #D5
target = (4, 0x1A1, 80, 80)
elif 0x1B0 <= re.room < 0x1DE: #D6
target = (5, 0x1D4, 80, 80)
elif 0x200 <= re.room < 0x22D: #D7
target = (6, 0x20E, 80, 80)
elif 0x22D <= re.room < 0x26C: #D8
target = (7, 0x25D, 80, 80)
elif re.room >= 0x300: #D0
target = (0xFF, 0x312, 80, 80)
elif re.room == 0x2E1: #Moblin cave
target = (0x15, 0x2F0, 0x50, 0x7C)
elif re.room == 0x27F: #Armos temple
target = (0x16, 0x28F, 0x50, 0x7C)
re.objects.append(ObjectWarp(1, *target))
if name == "DODONGO":
# Remove breaking floor tiles from the room.
re.objects = [obj for obj in re.objects if obj.type_id != 0xDF]
if name == "ROLLING_BONES" and target == 2:
# Make rolling bones pass trough walls so it does not get stuck here.
rom.patch(0x03, 0x02F1 + 0x81, "84", "95")
re.store(rom)
def readMiniBossMapping(rom):
mapping = {}
for key, room in MINIBOSS_ROOMS.items():
r = RoomEditor(rom, room)
for me_key, me_data in MINIBOSS_ENTITIES.items():
if me_data[-1][2] == r.entities[-1][2]:
mapping[key] = me_key
return mapping
def doubleTrouble(rom):
for n in range(0x316):
if n == 0x2FF:
continue
re = RoomEditor(rom, n)
# Bosses
if re.hasEntity(0x59): # Moldorm (TODO; double heart container drop)
re.removeEntities(0x59)
re.entities += [(3, 2, 0x59), (4, 2, 0x59)]
re.store(rom)
if re.hasEntity(0x5C): # Ghini
re.removeEntities(0x5C)
re.entities += [(3, 2, 0x5C), (4, 2, 0x5C)]
re.store(rom)
if re.hasEntity(0x5B): # slime eye
re.removeEntities(0x5B)
re.entities += [(3, 2, 0x5B), (6, 2, 0x5B)]
re.store(rom)
if re.hasEntity(0x65): # angler fish
re.removeEntities(0x65)
re.entities += [(6, 2, 0x65), (6, 5, 0x65)]
re.store(rom)
# Slime eel bugs out on death if duplicated.
# if re.hasEntity(0x5D): # slime eel
# re.removeEntities(0x5D)
# re.entities += [(6, 2, 0x5D), (6, 5, 0x5D)]
# re.store(rom)
if re.hasEntity(0x5A): # facade (TODO: Drops two hearts, shared health?)
re.removeEntities(0x5A)
re.entities += [(2, 3, 0x5A), (6, 3, 0x5A)]
re.store(rom)
# Evil eagle causes a crash, and messes up the intro sequence and generally is just a mess if I spawn multiple
# if re.hasEntity(0x63): # evil eagle
# re.removeEntities(0x63)
# re.entities += [(3, 4, 0x63), (2, 4, 0x63)]
# re.store(rom)
# # Remove that links movement is blocked
# rom.patch(0x05, 0x2258, ASM("ldh [$A1], a"), "0000")
# rom.patch(0x05, 0x1AE3, ASM("ldh [$A1], a"), "0000")
# rom.patch(0x05, 0x1C5D, ASM("ldh [$A1], a"), "0000")
# rom.patch(0x05, 0x1C8D, ASM("ldh [$A1], a"), "0000")
# rom.patch(0x05, 0x1CAF, ASM("ldh [$A1], a"), "0000")
if re.hasEntity(0x62): # hot head (TODO: Drops thwo hearts)
re.removeEntities(0x62)
re.entities += [(2, 2, 0x62), (4, 4, 0x62)]
re.store(rom)
if re.hasEntity(0xF9): # hardhit beetle
re.removeEntities(0xF9)
re.entities += [(2, 2, 0xF9), (5, 4, 0xF9)]
re.store(rom)
# Minibosses
if re.hasEntity(0x89):
re.removeEntities(0x89)
re.entities += [(2, 3, 0x89), (6, 3, 0x89)]
re.store(rom)
if re.hasEntity(0x81):
re.removeEntities(0x81)
re.entities += [(2, 3, 0x81), (6, 3, 0x81)]
re.store(rom)
if re.hasEntity(0x60):
dodongo = [e for e in re.entities if e[2] == 0x60]
x = (dodongo[0][0] + dodongo[1][0]) // 2
y = (dodongo[0][1] + dodongo[1][1]) // 2
re.entities += [(x, y, 0x60)]
re.store(rom)
if re.hasEntity(0x8e):
re.removeEntities(0x8e)
re.entities += [(1, 1, 0x8e), (7, 1, 0x8e)]
re.store(rom)
if re.hasEntity(0x92):
re.removeEntities(0x92)
re.entities += [(2, 3, 0x92), (4, 3, 0x92)]
re.store(rom)
if re.hasEntity(0xf4):
re.removeEntities(0xf4)
re.entities += [(2, 1, 0xf4), (6, 1, 0xf4)]
re.store(rom)
if re.hasEntity(0xf8):
re.removeEntities(0xf8)
re.entities += [(2, 2, 0xf8), (6, 2, 0xf8)]
re.store(rom)
if re.hasEntity(0xe4):
re.removeEntities(0xe4)
re.entities += [(5, 2, 0xe4), (5, 5, 0xe4)]
re.store(rom)
if re.hasEntity(0x88): # Armos knight (TODO: double item drop)
re.removeEntities(0x88)
re.entities += [(3, 3, 0x88), (6, 3, 0x88)]
re.store(rom)
if re.hasEntity(0x87): # Lanmola (TODO: killing one drops the item, and marks as done)
re.removeEntities(0x87)
re.entities += [(2, 2, 0x87), (1, 1, 0x87)]
re.store(rom)

View File

@@ -0,0 +1,58 @@
from ..roomEditor import RoomEditor, ObjectWarp
from ..worldSetup import ENTRANCE_INFO
def changeEntrances(rom, mapping):
warp_to_indoor = {}
warp_to_outdoor = {}
for key in mapping.keys():
info = ENTRANCE_INFO[key]
re = RoomEditor(rom, info.alt_room if info.alt_room is not None else info.room)
warp = re.getWarps()[info.index if info.index not in (None, "all") else 0]
warp_to_indoor[key] = warp
assert info.target == warp.room, "%s != %03x" % (key, warp.room)
re = RoomEditor(rom, warp.room)
for warp in re.getWarps():
if warp.room == info.room:
warp_to_outdoor[key] = warp
assert key in warp_to_outdoor, "Missing warp to outdoor on %s" % (key)
# First collect all the changes we need to do per room
changes_per_room = {}
def addChange(source_room, target_room, new_warp):
if source_room not in changes_per_room:
changes_per_room[source_room] = {}
changes_per_room[source_room][target_room] = new_warp
for key, target in mapping.items():
if key == target:
continue
info = ENTRANCE_INFO[key]
# Change the entrance to point to the new indoor room
addChange(info.room, warp_to_indoor[key].room, warp_to_indoor[target])
if info.alt_room:
addChange(info.alt_room, warp_to_indoor[key].room, warp_to_indoor[target])
# Change the exit to point to the right outside
addChange(warp_to_indoor[target].room, ENTRANCE_INFO[target].room, warp_to_outdoor[key])
if ENTRANCE_INFO[target].instrument_room is not None:
addChange(ENTRANCE_INFO[target].instrument_room, ENTRANCE_INFO[target].room, warp_to_outdoor[key])
# Finally apply the changes, we need to do this once per room to prevent A->B->C issues.
for room, changes in changes_per_room.items():
re = RoomEditor(rom, room)
for idx, obj in enumerate(re.objects):
if isinstance(obj, ObjectWarp) and obj.room in changes:
re.objects[idx] = changes[obj.room].copy()
re.store(rom)
def readEntrances(rom):
result = {}
for key, info in ENTRANCE_INFO.items():
re = RoomEditor(rom, info.alt_room if info.alt_room is not None else info.room)
warp = re.getWarps()[info.index if info.index not in (None, "all") else 0]
for other_key, other_info in ENTRANCE_INFO.items():
if warp.room == other_info.target:
result[key] = other_key
return result

View File

@@ -0,0 +1,19 @@
from ..assembler import ASM
from ..roomEditor import RoomEditor
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)

View File

@@ -0,0 +1,317 @@
from ..assembler import ASM
from ..roomEditor import RoomEditor, Object, ObjectVertical, ObjectHorizontal, ObjectWarp
from ..utils import formatText
def setRequiredInstrumentCount(rom, count):
rom.texts[0x1A3] = formatText("You need %d instruments" % (count))
if count >= 8:
return
if count < 0:
rom.patch(0x00, 0x31f5, ASM("ld a, [$D806]\nand $10\njr z, $25"), ASM(""), fill_nop=True)
rom.patch(0x20, 0x2dea, ASM("ld a, [$D806]\nand $10\njr z, $29"), ASM(""), fill_nop=True)
count = 0
# TODO: Music bugs out at the end, unless you have all instruments.
rom.patch(0x19, 0x0B79, None, "0000") # always spawn all instruments, we need the last one as that handles opening the egg.
rom.patch(0x19, 0x0BF4, ASM("jp $3BC0"), ASM("jp $7FE0")) # instead of rendering the instrument, jump to the code below.
rom.patch(0x19, 0x0BFE, ASM("""
; Normal check fo all instruments
ld e, $08
ld hl, $DB65
loop:
ldi a, [hl]
and $02
jr z, $12
dec e
jr nz, loop
"""), ASM("""
jp $7F2B ; jump to the end of the bank, where there is some space for code.
"""), fill_nop=True)
# Add some code at the end of the bank, as we do not have enough space to do this "in place"
rom.patch(0x19, 0x3F2B, "0000000000000000000000000000000000000000000000000000", ASM("""
ld d, $00
ld e, $08
ld hl, $DB65 ; start of has instrument memory
loop:
ld a, [hl]
and $02
jr z, noinc
inc d
noinc:
inc hl
dec e
jr nz, loop
ld a, d
cp $%02x ; check if we have a minimal of this amount of instruments.
jp c, $4C1A ; not enough instruments
jp $4C0B ; enough instruments
""" % (count)), fill_nop=True)
rom.patch(0x19, 0x3FE0, "0000000000000000000000000000000000000000000000000000", ASM("""
; Entry point of render code
ld hl, $DB65 ; table of having instruments
push bc
ldh a, [$F1]
ld c, a
add hl, bc
pop bc
ld a, [hl]
and $02 ; check if we have this instrument
ret z
jp $3BC0 ; jump to render code
"""), fill_nop=True)
def setSeashellGoal(rom, count):
rom.texts[0x1A3] = formatText("You need %d {SEASHELL}s" % (count))
# Remove the seashell mansion handler (as it will take your seashells) but put a heartpiece instead
re = RoomEditor(rom, 0x2E9)
re.entities = [(4, 4, 0x35)]
re.store(rom)
rom.patch(0x19, 0x0ACB, 0x0C21, ASM("""
ldh a, [$F8] ; room status
and $10
ret nz
ldh a, [$F0] ; active entity state
rst 0
dw state0, state1, state2, state3, state4
state0:
ld a, [$C124] ; room transition state
and a
ret nz
ldh a, [$99] ; link position Y
cp $70
ret nc
jp $3B12 ; increase entity state
state1:
call $0C05 ; get entity transition countdown
jr nz, renderShells
ld [hl], $10
call renderShells
ld hl, $C2B0 ; private state 1 table
add hl, bc
ld a, [wSeashellsCount]
cp [hl]
jp z, $3B12 ; increase entity state
ld a, [hl] ; increase the amount of compared shells
inc a
daa
ld [hl], a
ld hl, $C2C0 ; private state 2 table
add hl, bc
inc [hl] ; increase amount of displayed shells
ld a, $2B
ldh [$F4], a ; SFX
ret
state2:
ld a, [wSeashellsCount]
cp $%02d
jr c, renderShells
; got enough shells
call $3B12 ; increase entity state
call $0C05 ; get entity transition countdown
ld [hl], $40
jp renderShells
state3:
ld a, $23
ldh [$F2], a ; SFX: Dungeon opened
ld hl, $D806 ; egg room status
set 4, [hl]
ld a, [hl]
ldh [$F8], a ; current room status
call $3B12 ; increase entity state
ld a, $00
jp $4C2E
state4:
ret
renderShells:
ld hl, $C2C0 ; private state 2 table
add hl, bc
ld a, [hl]
cp $14
jr c, .noMax
ld a, $14
.noMax:
and a
ret z
ld c, a
ld hl, spriteRect
call $3CE6 ; RenderActiveEntitySpritesRect
ret
spriteRect:
db $10, $1E, $1E, $0C
db $10, $2A, $1E, $0C
db $10, $36, $1E, $0C
db $10, $42, $1E, $0C
db $10, $4E, $1E, $0C
db $10, $5A, $1E, $0C
db $10, $66, $1E, $0C
db $10, $72, $1E, $0C
db $10, $7E, $1E, $0C
db $10, $8A, $1E, $0C
db $24, $1E, $1E, $0C
db $24, $2A, $1E, $0C
db $24, $36, $1E, $0C
db $24, $42, $1E, $0C
db $24, $4E, $1E, $0C
db $24, $5A, $1E, $0C
db $24, $66, $1E, $0C
db $24, $72, $1E, $0C
db $24, $7E, $1E, $0C
db $24, $8A, $1E, $0C
""" % (count), 0x4ACB), fill_nop=True)
def setRaftGoal(rom):
rom.texts[0x1A3] = formatText("Just sail away.")
# Remove the egg and egg event handler.
re = RoomEditor(rom, 0x006)
for x in range(4, 7):
for y in range(0, 4):
re.removeObject(x, y)
re.objects.append(ObjectHorizontal(4, 1, 0x4d, 3))
re.objects.append(ObjectHorizontal(4, 2, 0x03, 3))
re.objects.append(ObjectHorizontal(4, 3, 0x03, 3))
re.entities = []
re.updateOverlay()
re.store(rom)
re = RoomEditor(rom, 0x08D)
re.objects[6].count = 4
re.objects[7].x += 2
re.objects[7].type_id = 0x2B
re.objects[8].x += 2
re.objects[8].count = 2
re.objects[9].x += 1
re.objects[11] = ObjectVertical(7, 5, 0x37, 2)
re.objects[12].x -= 1
re.objects[13].x -= 1
re.objects[14].x -= 1
re.objects[14].type_id = 0x34
re.objects[17].x += 3
re.objects[17].count -= 3
re.updateOverlay()
re.overlay[7 + 60] = 0x33
re.store(rom)
re = RoomEditor(rom, 0x0E9)
re.objects[30].count = 1
re.objects[30].x += 2
re.overlay[7 + 70] = 0x0E
re.overlay[8 + 70] = 0x0E
re.store(rom)
re = RoomEditor(rom, 0x0F9)
re.objects = [
ObjectHorizontal(4, 0, 0x0E, 6),
ObjectVertical(9, 0, 0xCA, 8),
ObjectVertical(8, 0, 0x0E, 8),
Object(3, 0, 0x38),
Object(3, 1, 0x32),
ObjectHorizontal(4, 1, 0x2C, 3),
Object(7, 1, 0x2D),
ObjectVertical(7, 2, 0x38, 5),
Object(7, 7, 0x34),
ObjectHorizontal(0, 7, 0x2F, 7),
ObjectVertical(2, 3, 0xE8, 4),
ObjectVertical(3, 2, 0xE8, 5),
ObjectVertical(4, 2, 0xE8, 2),
ObjectVertical(4, 4, 0x5C, 3),
ObjectVertical(5, 2, 0x5C, 5),
ObjectVertical(6, 2, 0x5C, 5),
Object(6, 4, 0xC6),
ObjectWarp(1, 0x1F, 0xF6, 136, 112)
]
re.updateOverlay(True)
re.entities.append((0, 0, 0x41))
re.store(rom)
re = RoomEditor(rom, 0x1F6)
re.objects[-1].target_x -= 16
re.store(rom)
# Fix the raft graphics (this overrides some unused graphic tiles)
rom.banks[0x31][0x21C0:0x2200] = rom.banks[0x2E][0x07C0:0x0800]
# Patch the owl entity to run our custom end handling.
rom.patch(0x06, 0x27F5, 0x2A77, ASM("""
ld a, [$DB95]
cp $0B
ret nz
; If map is not fully loaded, return
ld a, [$C124]
and a
ret nz
; Check if we are moving off the bottom of the map
ldh a, [$99]
cp $7D
ret c
; Move link back so it does not move off the map
ld a, $7D
ldh [$99], a
xor a
ld e, a
ld d, a
raftSearchLoop:
ld hl, $C280
add hl, de
ld a, [hl]
and a
jr z, .skipEntity
ld hl, $C3A0
add hl, de
ld a, [hl]
cp $6A
jr nz, .skipEntity
; Raft found, check if near the bottom of the screen.
ld hl, $C210
add hl, de
ld a, [hl]
cp $70
jr nc, raftOffWorld
.skipEntity:
inc e
ld a, e
cp $10
jr nz, raftSearchLoop
ret
raftOffWorld:
; Switch to the end credits
ld a, $01
ld [$DB95], a
ld a, $00
ld [$DB96], a
ret
"""), fill_nop=True)
# We need to run quickly trough part of the credits, or else it bugs out
# Skip the whole windfish part.
rom.patch(0x17, 0x0D39, None, ASM("ld a, $18\nld [$D00E], a\nret"))
# And skip the zoomed out laying on the log
rom.patch(0x17, 0x20ED, None, ASM("ld a, $00"))
# Finally skip some waking up on the log.
rom.patch(0x17, 0x23BC, None, ASM("jp $4CD9"))
rom.patch(0x17, 0x2476, None, ASM("jp $4CD9"))

View File

@@ -0,0 +1,34 @@
from ..assembler import ASM
def fixGoldenLeaf(rom):
# Patch the golden leaf code so it jumps to the dropped key handling in bank 3E
rom.patch(3, 0x2007, ASM("""
ld de, $5FFB
call $3C77 ; RenderActiveEntitySprite
"""), ASM("""
ld a, $04
rst 8
"""), fill_nop=True)
rom.patch(3, 0x2018, None, ASM("""
ld a, $06 ; giveItemMultiworld
rst 8
jp $602F
"""))
rom.patch(3, 0x2037, None, ASM("""
ld a, $0a ; showMessageMultiworld
rst 8
jp $604B
"""))
# Patch all over the place to move the golden leafs to a different memory location.
# We use $DB6D (dungeon 9 status), but we could also use $DB7A (which is only used by the ghost)
rom.patch(0x00, 0x2D17, ASM("ld a, [$DB15]"), ASM("ld a, $06"), fill_nop=True) # Always load the slime tiles
rom.patch(0x02, 0x3005, ASM("cp $06"), ASM("cp $01"), fill_nop=True) # Instead of checking for 6 leaves a the keyhole, just check for the key
rom.patch(0x20, 0x1AD1, ASM("ld a, [$DB15]"), ASM("ld a, [wGoldenLeaves]")) # For the status screen, load the number of leafs from the proper memory
rom.patch(0x03, 0x0980, ASM("ld a, [$DB15]"), ASM("ld a, [wGoldenLeaves]")) # If leaves >= 6 move richard
rom.patch(0x06, 0x0059, ASM("ld a, [$DB15]"), ASM("ld a, [wGoldenLeaves]")) # If leaves >= 6 move richard
rom.patch(0x06, 0x007D, ASM("ld a, [$DB15]"), ASM("ld a, [wGoldenLeaves]")) # Richard message if no leaves
rom.patch(0x06, 0x00B8, ASM("ld [$DB15], a"), ASM("ld [wGoldenLeaves], a")) # Stores FF in the leaf counter if we opened the path
# 6:40EE uses leaves == 6 to check if we have collected the key, but only to change the message.
# rom.patch(0x06, 0x2AEF, ASM("ld a, [$DB15]"), ASM("ld a, [wGoldenLeaves]")) # Telephone message handler

View File

@@ -0,0 +1,64 @@
from ..assembler import ASM
def oracleMode(rom):
# Reduce iframes
rom.patch(0x03, 0x2DB2, ASM("ld a, $50"), ASM("ld a, $20"))
# Make bomb explosions damage you.
rom.patch(0x03, 0x2618, ASM("""
ld hl, $C440
add hl, bc
ld a, [hl]
and a
jr nz, $05
"""), ASM("""
call $6625
"""), fill_nop=True)
# Reduce bomb blast push back on link
rom.patch(0x03, 0x2643, ASM("sla [hl]"), ASM("sra [hl]"), fill_nop=True)
rom.patch(0x03, 0x2648, ASM("sla [hl]"), ASM("sra [hl]"), fill_nop=True)
# Never spawn a piece of power or acorn
rom.patch(0x03, 0x1608, ASM("jr nz, $05"), ASM("jr $05"))
rom.patch(0x03, 0x1642, ASM("jr nz, $04"), ASM("jr $04"))
# Let hearts only recover half a container instead of a full one.
rom.patch(0x03, 0x24B7, ASM("ld a, $08"), ASM("ld a, $04"))
# Don't randomly drop fairies from enemies, drop a rupee instead
rom.patch(0x03, 0x15C7, "2E2D382F2E2D3837", "2E2D382E2E2D3837")
# Make dropping in water without flippers damage you.
rom.patch(0x02, 0x3722, ASM("ldh a, [$AF]"), ASM("ld a, $06"))
def heroMode(rom):
# Don't randomly drop fairies and hearts from enemies, drop a rupee instead
rom.patch(0x03, 0x159D,
"2E2E2D2D372DFFFF2F37382E2F2F",
"2E2EFFFF37FFFFFFFF37382EFFFF")
rom.patch(0x03, 0x15C7,
"2E2D382F2E2D3837",
"2E2E382E2E2E3837")
rom.patch(0x00, 0x168F, ASM("ld a, $2D"), "", fill_nop=True)
rom.patch(0x02, 0x0CDB, ASM("ld a, $2D"), "", fill_nop=True)
# Double damage
rom.patch(0x03, 0x2DAB,
ASM("ld a, [$DB94]\nadd a, e\nld [$DB94], a"),
ASM("ld hl, $DB94\nld a, [hl]\nadd a, e\nadd a, e\nld [hl], a"))
rom.patch(0x02, 0x11B2, ASM("add a, $04"), ASM("add a, $08"))
rom.patch(0x02, 0x127E, ASM("add a, $04"), ASM("add a, $08"))
rom.patch(0x02, 0x291C, ASM("add a, $04"), ASM("add a, $08"))
rom.patch(0x02, 0x362B, ASM("add a, $04"), ASM("add a, $08"))
rom.patch(0x06, 0x041C, ASM("ld a, $02"), ASM("ld a, $04"))
rom.patch(0x15, 0x09B8, ASM("add a, $08"), ASM("add a, $10"))
rom.patch(0x15, 0x32FD, ASM("ld a, $08"), ASM("ld a, $10"))
rom.patch(0x18, 0x370E, ASM("ld a, $08"), ASM("ld a, $10"))
rom.patch(0x07, 0x3103, ASM("ld a, $08"), ASM("ld a, $10"))
rom.patch(0x06, 0x1166, ASM("ld a, $08"), ASM("ld a, $10"))
def oneHitKO(rom):
rom.patch(0x02, 0x238C, ASM("ld [$DB94], a"), "", fill_nop=True)

View File

@@ -0,0 +1,33 @@
from ..assembler import ASM
from ..utils import formatText
def setStartHealth(rom, amount):
rom.patch(0x01, 0x0B1C, ASM("ld [hl], $03"), ASM("ld [hl], $%02X" % (amount))) # max health of new save
rom.patch(0x01, 0x0B14, ASM("ld [hl], $18"), ASM("ld [hl], $%02X" % (amount * 8))) # current health of new save
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

View File

@@ -0,0 +1,42 @@
from ..assembler import ASM
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)

View File

@@ -0,0 +1,24 @@
from ..assembler import ASM
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"))

View File

@@ -0,0 +1,421 @@
from ..assembler import ASM
from ..backgroundEditor import BackgroundEditor
def selectToSwitchSongs(rom):
# Do not ignore left/right keys when ocarina is selected
rom.patch(0x20, 0x1F18, ASM("and a"), ASM("xor a"))
# Change the keys which switch the ocarina song to select and no key.
rom.patch(0x20, 0x21A9, ASM("and $01"), ASM("and $40"))
rom.patch(0x20, 0x21C7, ASM("and $02"), ASM("and $00"))
def songSelectAfterOcarinaSelect(rom):
rom.patch(0x20, 0x2002, ASM("ld [$DB00], a"), ASM("call $5F96"))
rom.patch(0x20, 0x1FE0, ASM("ld [$DB01], a"), ASM("call $5F9B"))
# Remove the code that opens the ocerina on cursor movement, but use it to insert code
# for opening the menu on item select
rom.patch(0x20, 0x1F93, 0x1FB2, ASM("""
jp $5FB2
itemToB:
ld [$DB00], a
jr checkForOcarina
itemToA:
ld [$DB01], a
checkForOcarina:
cp $09
jp nz, $6010
ld a, [$DB49]
and a
ret z
ld a, $08
ldh [$90], a ; load ocarina song select graphics
;ld a, $10
;ld [$C1B8], a ; shows the opening animation
ld a, $01
ld [$C1B5], a
ret
"""), fill_nop=True)
# More code that opens the menu, use this to close the menu
rom.patch(0x20, 0x200D, 0x2027, ASM("""
jp $6027
closeOcarinaMenu:
ld a, [$C1B5]
and a
ret z
xor a
ld [$C1B5], a
ld a, $10
ld [$C1B9], a ; shows the closing animation
ret
"""), fill_nop=True)
rom.patch(0x20, 0x2027, 0x2036, "", fill_nop=True) # Code that closes the ocarina menu on item select
rom.patch(0x20, 0x22A2, ASM("""
ld a, [$C159]
inc a
ld [$C159], a
and $10
jr nz, $30
"""), ASM("""
ld a, [$C1B5]
and a
ret nz
ldh a, [$E7] ; frame counter
and $10
ret nz
"""), fill_nop=True)
def moreSlots(rom):
#Move flippers, medicine, trade item and seashells to DB3E+
rom.patch(0x02, 0x292B, ASM("ld a, [$DB0C]"), ASM("ld a, [$DB3E]"))
#rom.patch(0x02, 0x2E8F, ASM("ld a, [$DB0C]"), ASM("ld a, [$DB3E]"))
rom.patch(0x02, 0x3713, ASM("ld a, [$DB0C]"), ASM("ld a, [$DB3E]"))
rom.patch(0x20, 0x1A23, ASM("ld de, $DB0C"), ASM("ld de, $DB3E"))
rom.patch(0x02, 0x23a3, ASM("ld a, [$DB0D]"), ASM("ld a, [$DB3F]"))
rom.patch(0x02, 0x23d7, ASM("ld a, [$DB0D]"), ASM("ld a, [$DB3F]"))
rom.patch(0x02, 0x23aa, ASM("ld [$DB0D], a"), ASM("ld [$DB3F], a"))
rom.patch(0x04, 0x3b1f, ASM("ld [$DB0D], a"), ASM("ld [$DB3F], a"))
rom.patch(0x06, 0x1f58, ASM("ld a, [$DB0D]"), ASM("ld a, [$DB3F]"))
rom.patch(0x06, 0x1ff5, ASM("ld hl, $DB0D"), ASM("ld hl, $DB3F"))
rom.patch(0x07, 0x3c33, ASM("ld [$DB0D], a"), ASM("ld [$DB3F], a"))
rom.patch(0x00, 0x1e01, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x00, 0x2d21, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x00, 0x3199, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x03, 0x0ae6, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x03, 0x0b6d, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x03, 0x0f68, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x04, 0x2faa, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x04, 0x3502, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x04, 0x3624, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x05, 0x0bff, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x05, 0x0d20, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x05, 0x0db1, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x05, 0x0dd5, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x05, 0x0e8e, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x05, 0x11ce, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x06, 0x1a2c, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x06, 0x1a7c, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x06, 0x1ab1, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x06, 0x2214, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x06, 0x223e, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x02f8, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x04bf, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x057f, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x07, 0x0797, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x0856, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x07, 0x0a21, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x0a33, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x0a58, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x0a81, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x0acf, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x0af9, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x0b31, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x07, 0x0bcc, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x0c23, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x0c3c, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x0c60, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x07, 0x0d73, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x07, 0x1549, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x155d, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x159f, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x18e6, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x07, 0x19ce, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
#rom.patch(0x15, 0x3F23, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x18, 0x0966, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x18, 0x0972, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x18, 0x09f3, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x18, 0x0bf1, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x18, 0x0c2c, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x18, 0x0c6d, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x18, 0x0c8b, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x18, 0x0ce4, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x18, 0x0d3c, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x18, 0x0d4a, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x18, 0x0d95, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x18, 0x0da3, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x18, 0x0de4, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x18, 0x0e7a, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x18, 0x0e91, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x18, 0x0eb6, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x18, 0x219e, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x19, 0x05ec, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x19, 0x2d54, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x19, 0x2df2, ASM("ld [$DB0E], a"), ASM("ld [$DB40], a"))
rom.patch(0x19, 0x2ef1, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x19, 0x2f95, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x20, 0x1b04, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x20, 0x1e42, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x36, 0x0948, ASM("ld a, [$DB0E]"), ASM("ld a, [$DB40]"))
rom.patch(0x19, 0x31Ca, ASM("ld a, [$DB0F]"), ASM("ld a, [$DB41]"))
rom.patch(0x19, 0x3215, ASM("ld a, [$DB0F]"), ASM("ld a, [$DB41]"))
rom.patch(0x19, 0x32a2, ASM("ld a, [$DB0F]"), ASM("ld a, [$DB41]"))
rom.patch(0x19, 0x3700, ASM("ld [$DB0F], a"), ASM("ld [$DB41], a"))
rom.patch(0x19, 0x38b3, ASM("ld a, [$DB0F]"), ASM("ld a, [$DB41]"))
rom.patch(0x19, 0x38c3, ASM("ld [$DB0F], a"), ASM("ld [$DB41], a"))
rom.patch(0x20, 0x1a83, ASM("ld a, [$DB0F]"), ASM("ld a, [$DB41]"))
# Fix the whole inventory rendering, this needs to extend a few tables with more entries so it moves tables
# to the end of the bank as well.
rom.patch(0x20, 0x3E53, "00" * 32,
"9C019C06"
"9C619C65"
"9CA19CA5"
"9CE19CE5"
"9D219D25"
"9D619D65"
"9DA19DA5"
"9DE19DE5") # New table with tile addresses for all slots
rom.patch(0x20, 0x1CC7, ASM("ld hl, $5C84"), ASM("ld hl, $7E53")) # use the new table
rom.patch(0x20, 0x1BCC, ASM("ld hl, $5C84"), ASM("ld hl, $7E53")) # use the new table
rom.patch(0x20, 0x1CF0, ASM("ld hl, $5C84"), ASM("ld hl, $7E53")) # use the new table
# sprite positions for inventory cursor, new table, placed at the end of the bank
rom.patch(0x20, 0x3E90, "00" * 16, "28283838484858586868787888889898")
rom.patch(0x20, 0x22b3, ASM("ld hl, $6298"), ASM("ld hl, $7E90"))
rom.patch(0x20, 0x2298, "28284040", "08280828") # Extend the sprite X positions for the inventory table
# Piece of power overlay positions
rom.patch(0x20, 0x233A,
"1038103010301030103010300E0E2626",
"10381030103010301030103010301030")
rom.patch(0x20, 0x3E73, "00" * 16,
"0E0E2626363646465656666676768686")
rom.patch(0x20, 0x2377, ASM("ld hl, $6346"), ASM("ld hl, $7E73"))
# Allow selecting the 4 extra slots.
rom.patch(0x20, 0x1F33, ASM("ld a, $09"), ASM("ld a, $0D"))
rom.patch(0x20, 0x1F54, ASM("ld a, $09"), ASM("ld a, $0D"))
rom.patch(0x20, 0x1F2A, ASM("cp $0A"), ASM("cp $0E"))
rom.patch(0x20, 0x1F4B, ASM("cp $0A"), ASM("cp $0E"))
rom.patch(0x02, 0x217E, ASM("ld a, $0B"), ASM("ld a, $0F"))
# Patch all the locations that iterate over inventory to check the extra slots
rom.patch(0x02, 0x33FC, ASM("cp $0C"), ASM("cp $10"))
rom.patch(0x03, 0x2475, ASM("ld e, $0C"), ASM("ld e, $10"))
rom.patch(0x03, 0x248a, ASM("cp $0C"), ASM("cp $10"))
rom.patch(0x04, 0x3849, ASM("ld c, $0B"), ASM("ld c, $0F"))
rom.patch(0x04, 0x3862, ASM("ld c, $0B"), ASM("ld c, $0F"))
rom.patch(0x04, 0x39C2, ASM("ld d, $0C"), ASM("ld d, $10"))
rom.patch(0x04, 0x39E0, ASM("ld d, $0C"), ASM("ld d, $10"))
rom.patch(0x04, 0x39FE, ASM("ld d, $0C"), ASM("ld d, $10"))
rom.patch(0x05, 0x0F95, ASM("ld e, $0B"), ASM("ld e, $0F"))
rom.patch(0x05, 0x0FD1, ASM("ld c, $0B"), ASM("ld c, $0F"))
rom.patch(0x05, 0x1324, ASM("ld e, $0C"), ASM("ld e, $10"))
rom.patch(0x05, 0x1339, ASM("cp $0C"), ASM("cp $10"))
rom.patch(0x18, 0x005A, ASM("ld e, $0B"), ASM("ld e, $0F"))
rom.patch(0x18, 0x0571, ASM("ld e, $0B"), ASM("ld e, $0F"))
rom.patch(0x19, 0x0703, ASM("cp $0C"), ASM("cp $10"))
rom.patch(0x20, 0x235C, ASM("ld d, $0C"), ASM("ld d, $10"))
rom.patch(0x36, 0x31B8, ASM("ld e, $0C"), ASM("ld e, $10"))
## Patch the toadstool as a different item
rom.patch(0x20, 0x1C84, "9C019C" "069C61", "4C7F7F" "4D7F7F") # Which tiles are used for the toadstool
rom.patch(0x20, 0x1C8A, "9C659C" "C19CC5", "90927F" "91937F") # Which tiles are used for the rooster
rom.patch(0x20, 0x1C6C, "927F7F" "937F7F", "127F7F" "137F7F") # Which tiles are used for the feather (to make space for rooster)
rom.patch(0x20, 0x1C66, "907F7F" "917F7F", "107F7F" "117F7F") # Which tiles are used for the ocarina (to make space for rooster)
# Move the inventory tile numbers to a higher address, so there is space for the table above it.
rom.banks[0x20][0x1C34:0x1C94] = rom.banks[0x20][0x1C30:0x1C90]
rom.patch(0x20, 0x1CDB, ASM("ld hl, $5C30"), ASM("ld hl, $5C34"))
rom.patch(0x20, 0x1D0D, ASM("ld hl, $5C33"), ASM("ld hl, $5C37"))
rom.patch(0x20, 0x1C30, "7F7F", "0A0B") # Toadstool tile attributes
rom.patch(0x20, 0x1C32, "7F7F", "0101") # Rooster tile attributes
rom.patch(0x20, 0x1C28, "0303", "0B0B") # Feather tile attributes (due to rooster)
rom.patch(0x20, 0x1C26, "0202", "0A0A") # Ocarina tile attributes (due to rooster)
# Allow usage of the toadstool (replace the whole manual jump table with an rst 0 jumptable
rom.patch(0x00, 0x129D, 0x12D8, ASM("""
rst 0 ; jump table
dw $12ED ; no item
dw $1528 ; Sword
dw $135A ; Bomb
dw $1382 ; Bracelet
dw $12EE ; Shield
dw $13BD ; Bow
dw $1319 ; Hookshot
dw $12D8 ; Magic rod
dw $12ED ; Boots (no action)
dw $41FC ; Ocarina
dw $14CB ; Feather
dw $12F8 ; Shovel
dw $148D ; Magic powder
dw $1383 ; Boomerang
dw $1498 ; Toadstool
dw RoosterUse ; Rooster
RoosterUse:
ld a, $01
ld [$DB7B], a ; has rooster
call $3958 ; spawn followers
xor a
ld [$DB7B], a ; has rooster
ret
""", 0x129D), fill_nop=True)
# Fix the graphics of the toadstool hold over your head
rom.patch(0x02, 0x121E, ASM("ld e, $8E"), ASM("ld e, $4C"))
rom.patch(0x02, 0x1241, ASM("ld a, $14"), ASM("ld a, $1C"))
# Do not remove powder when it is used up.
rom.patch(0x20, 0x0C59, ASM("jr nz, $12"), ASM("jr $12"))
# Patch the toadstool entity code to give the proper item, and not set the has-toadstool flag.
rom.patch(0x03, 0x1D6F, ASM("""
ld a, $0A
ldh [$A5], a
ld d, $0C
call $6472
ld a, $01
ld [$DB4B], a
"""), ASM("""
ld d, $0E
call $6472
"""), fill_nop=True)
# Patch the debug save game so it does not give a bunch of swords
rom.patch(0x01, 0x0673, "01010100", "0D0E0F00")
# Patch the witch to use the new toadstool instead of the old flag
rom.patch(0x05, 0x081A, ASM("ld a, [$DB4B]"), ASM("ld a, $01"), fill_nop=True)
rom.patch(0x05, 0x082A, ASM("cp $0C"), ASM("cp $0E"))
rom.patch(0x05, 0x083E, ASM("cp $0C"), ASM("cp $0E"))
def advancedInventorySubscreen(rom):
# Instrument positions
rom.patch(0x01, 0x2BCF,
"0F51B1EFECAA4A0C",
"090C0F12494C4F52")
be = BackgroundEditor(rom, 2)
be.tiles[0x9DA9] = 0x4A
be.tiles[0x9DC9] = 0x4B
for x in range(1, 10):
be.tiles[0x9DE9 + x] = 0xB0 + (x % 9)
be.tiles[0x9DE9] = 0xBA
be.store(rom)
be = BackgroundEditor(rom, 2, attributes=True)
# Remove all attributes out of range.
for y in range(0x9C00, 0x9E40, 0x20):
for x in range(0x14, 0x20):
del be.tiles[x + y]
for n in range(0x9E40, 0xA020):
del be.tiles[n]
# Remove palette of instruments
for y in range(0x9D00, 0x9E20, 0x20):
for x in range(0x00, 0x14):
be.tiles[x + y] = 0x01
# And place it at the proper location
for y in range(0x9D00, 0x9D80, 0x20):
for x in range(0x09, 0x14):
be.tiles[x + y] = 0x07
# Key from 2nd vram bank
be.tiles[0x9DA9] = 0x09
be.tiles[0x9DC9] = 0x09
# Nightmare heads from 2nd vram bank with proper palette
for n in range(1, 10):
be.tiles[0x9DA9 + n] = 0x0E
be.store(rom)
rom.patch(0x20, 0x19D3, ASM("ld bc, $5994\nld e, $33"), ASM("ld bc, $7E08\nld e, $%02x" % (0x33 + 24)))
rom.banks[0x20][0x3E08:0x3E08+0x33] = rom.banks[0x20][0x1994:0x1994+0x33]
rom.patch(0x20, 0x3E08+0x32, "00" * 25, "9DAA08464646464646464646" "9DCA08B0B0B0B0B0B0B0B0B0" "00")
# instead of doing an GBC specific check, jump to our custom handling
rom.patch(0x20, 0x19DE, ASM("ldh a, [$FE]\nand a\njr z, $40"), ASM("call $7F00"), fill_nop=True)
rom.patch(0x20, 0x3F00, "00" * 0x100, ASM("""
ld a, [$DBA5] ; isIndoor
and a
jr z, RenderKeysCounts
ldh a, [$F7] ; mapNr
cp $FF
jr z, RenderDungeonFix
cp $06
jr z, D7RenderDungeonFix
cp $08
jr c, RenderDungeonFix
RenderKeysCounts:
; Check if we have each nightmare key, and else null out the rendered tile
ld hl, $D636
ld de, $DB19
ld c, $08
NKeyLoop:
ld a, [de]
and a
jr nz, .hasNKey
ld a, $7F
ld [hl], a
.hasNKey:
inc hl
inc de
inc de
inc de
inc de
inc de
dec c
jr nz, NKeyLoop
ld a, [$DDDD]
and a
jr nz, .hasCNKey
ld a, $7F
ld [hl], a
.hasCNKey:
; Check the small key count for each dungeon and increase the tile to match the number
ld hl, $D642
ld de, $DB1A
ld c, $08
KeyLoop:
ld a, [de]
add a, $B0
ld [hl], a
inc hl
inc de
inc de
inc de
inc de
inc de
dec c
jr nz, KeyLoop
ld a, [$DDDE]
add a, $B0
ld [hl], a
ret
D7RenderDungeonFix:
ld de, D7DungeonFix
ld c, $11
jr RenderDungeonFixGo
RenderDungeonFix:
ld de, DungeonFix
ld c, $0D
RenderDungeonFixGo:
ld hl, $D633
.copyLoop:
ld a, [de]
inc de
ldi [hl], a
dec c
jr nz, .copyLoop
ret
DungeonFix:
db $9D, $09, $C7, $7F
db $9D, $0A, $C7, $7F
db $9D, $13, $C3, $7F
db $00
D7DungeonFix:
db $9D, $09, $C7, $7F
db $9D, $0A, $C7, $7F
db $9D, $6B, $48, $7F
db $9D, $0F, $C7, $7F
db $00
""", 0x7F00), fill_nop=True)

View File

@@ -0,0 +1,42 @@
from ..assembler import ASM
from ..utils import formatText
def upgradeMadBatter(rom):
# Normally the madbatter won't do anything if you have full capacity. Remove that check.
rom.patch(0x18, 0x0F05, 0x0F1D, "", fill_nop=True)
# Remove the code that finds which upgrade to apply,
rom.patch(0x18, 0x0F9E, 0x0FC4, "", fill_nop=True)
rom.patch(0x18, 0x0FD2, 0x0FD8, "", fill_nop=True)
# Finally, at the last step, give the item and the item message.
rom.patch(0x18, 0x1016, 0x101B, "", fill_nop=True)
rom.patch(0x18, 0x101E, 0x1051, ASM("""
; Mad batter rooms are E0,E1 and E2, load the item type from a table in the rom
; which only has 3 entries, and store it where bank 3E wants it.
ldh a, [$F6] ; current room
and $0F
ld d, $00
ld e, a
ld hl, $4F90
add hl, de
ld a, [hl]
ldh [$F1], a
; Give item
ld a, $06 ; giveItemMultiworld
rst 8
; Message
ld a, $0A ; showMessageMultiworld
rst 8
; Force the dialog at the bottom
ld a, [$C19F]
or $80
ld [$C19F], a
"""), fill_nop=True)
# Setup the default items
rom.patch(0x18, 0x0F90, "406060", "848586")
rom.texts[0xE2] = formatText("You can now carry more Magic Powder!")
rom.texts[0xE3] = formatText("You can now carry more Bombs!")
rom.texts[0xE4] = formatText("You can now carry more Arrows!")

View File

@@ -0,0 +1,27 @@
from ..roomEditor import RoomEditor, ObjectWarp, ObjectVertical
def tweakMap(rom):
# 5 holes at the castle, reduces to 3
re = RoomEditor(rom, 0x078)
re.objects[-1].count = 3
re.overlay[7 + 6 * 10] = re.overlay[9 + 6 * 10]
re.overlay[8 + 6 * 10] = re.overlay[9 + 6 * 10]
re.store(rom)
def addBetaRoom(rom):
re = RoomEditor(rom, 0x1FC)
re.objects[-1].target_y -= 0x10
re.store(rom)
re = RoomEditor(rom, 0x038)
re.changeObject(5, 1, 0xE1)
re.removeObject(0, 0)
re.removeObject(0, 1)
re.removeObject(0, 2)
re.removeObject(6, 1)
re.objects.append(ObjectVertical(0, 0, 0x38, 3))
re.objects.append(ObjectWarp(1, 0x1F, 0x1FC, 0x50, 0x7C))
re.store(rom)
rom.room_sprite_data_indoor[0x0FC] = rom.room_sprite_data_indoor[0x1A1]

View File

@@ -0,0 +1,308 @@
from ..assembler import ASM
from ..roomEditor import RoomEditor, ObjectHorizontal, ObjectVertical, Object
from .. import entityData
def addMultiworldShop(rom, this_player, player_count):
# Make a copy of the shop into GrandpaUlrira house
re = RoomEditor(rom, 0x2A9)
re.objects = [
ObjectHorizontal(1,1, 0x00, 8),
ObjectHorizontal(1,2, 0x00, 8),
ObjectHorizontal(1,3, 0xCD, 8),
Object(2, 0, 0xC7),
Object(7, 0, 0xC7),
Object(7, 7, 0xFD),
] + re.getWarps()
re.entities = [(0, 6, 0xD4)]
for n in range(player_count):
if n != this_player:
re.entities.append((n + 1, 6, 0xD4))
re.animation_id = 0x04
re.floor_object = 0x0D
re.store(rom)
# Fix the tileset
rom.banks[0x20][0x2EB3 + 0x2A9 - 0x100] = rom.banks[0x20][0x2EB3 + 0x2A1 - 0x100]
re = RoomEditor(rom, 0x0B1)
re.getWarps()[0].target_x = 128
re.store(rom)
# Load the shopkeeper sprites
entityData.SPRITE_DATA[0xD4] = entityData.SPRITE_DATA[0x4D]
rom.patch(0x03, 0x01CF, "00", "98") # Fix the hitbox of the ghost to be 16x16
# Patch Ghost to work as a multiworld shop
rom.patch(0x19, 0x1E18, 0x20B0, ASM("""
ld a, $01
ld [$C50A], a ; this stops link from using items
ldh a, [$EE] ; X
cp $08
; Jump to other code which is placed on the old owl code. As we do not have enough space here.
jp z, shopItemsHandler
;Draw shopkeeper
ld de, OwnerSpriteData
call $3BC0 ; render sprite pair
ldh a, [$E7] ; frame counter
swap a
and $01
call $3B0C ; set sprite variant
ldh a, [$F0]
and a
jr nz, checkTalkingResult
call $7CA2 ; prevent link from moving into the sprite
call $7CF0 ; check if talking to NPC
call c, talkHandler ; talk handling
ret
checkTalkingResult:
ld a, [$C19F]
and a
ret nz ; still taking
call $3B12 ; increase entity state
ld [hl], $00
ld a, [$C177] ; dialog selection
and a
ret nz
jp TalkResultHandler
OwnerSpriteData:
;db $60, $03, $62, $03, $62, $23, $60, $23 ; down
db $64, $03, $66, $03, $66, $23, $64, $23 ; up
;db $68, $03, $6A, $03, $6C, $03, $6E, $03 ; left
;db $6A, $23, $68, $23, $6E, $23, $6C, $23 ; right
shopItemsHandler:
; Render the shop items
ld h, $00
loop:
; First load links position to render the item at
ldh a, [$98] ; LinkX
ldh [$EE], a ; X
ldh a, [$99] ; LinkY
sub $0E
ldh [$EC], a ; Y
; Check if this is the item we have picked up
ld a, [$C509] ; picked up item in shop
dec a
cp h
jr z, .renderCarry
ld a, h
swap a
add a, $20
ldh [$EE], a ; X
ld a, $30
ldh [$EC], a ; Y
.renderCarry:
ld a, h
push hl
ldh [$F1], a ; variant
cp $03
jr nc, .singleSprite
ld de, ItemsDualSpriteData
call $3BC0 ; render sprite pair
jr .renderDone
.singleSprite:
ld de, ItemsSingleSpriteData
call $3C77 ; render sprite
.renderDone:
pop hl
.skipItem:
inc h
ld a, $07
cp h
jr nz, loop
; check if we want to pickup or drop an item
ldh a, [$CC]
and $30 ; A or B button
call nz, checkForPickup
; check if we have an item
ld a, [$C509] ; carry item
and a
ret z
; Set that link has picked something up
ld a, $01
ld [$C15C], a
call $0CAF ; reset spin attack...
; Check if we are trying to exit the shop and so drop our item.
ldh a, [$99]
cp $78
ret c
xor a
ld [$C509], a
ret
checkForPickup:
ldh a, [$9E] ; direction
cp $02
ret nz
ldh a, [$99] ; LinkY
cp $48
ret nc
ld a, $13
ldh [$F2], a ; play SFX
ld a, [$C509] ; picked up shop item
and a
jr nz, .drop
ldh a, [$98] ; LinkX
sub $08
swap a
and $07
ld [$C509], a ; picked up shop item
ret
.drop:
xor a
ld [$C509], a
ret
ItemsDualSpriteData:
db $60, $08, $60, $28 ; zol
db $68, $09 ; chicken (left)
ItemsSingleSpriteData: ; (first 3 entries are still dual sprites)
db $6A, $09 ; chicken (right)
db $14, $02, $14, $22 ; piece of power
;Real single sprite data starts here
db $00, $0F ; bomb
db $38, $0A ; rupees
db $20, $0C ; medicine
db $28, $0C ; heart
;------------------------------------trying to buy something starts here
talkHandler:
ld a, [$C509] ; carry item
add a, a
ret z ; check if we have something to buy
sub $02
ld hl, itemNames
ld e, a
ld d, b ; b=0
add hl, de
ld e, [hl]
inc hl
ld d, [hl]
ld hl, wCustomMessage
call appendString
dec hl
call padString
ld de, postMessage
call appendString
dec hl
ld a, $fe
ld [hl], a
ld de, $FFEF
add hl, de
ldh a, [$EE]
swap a
and $0F
add a, $30
ld [hl], a
ld a, $C9
call $2385 ; open dialog
call $3B12 ; increase entity state
ret
appendString:
ld a, [de]
inc de
and a
ret z
ldi [hl], a
jr appendString
padString:
ld a, l
and $0F
ret z
ld a, $20
ldi [hl], a
jr padString
itemNames:
dw itemZol
dw itemChicken
dw itemPieceOfPower
dw itemBombs
dw itemRupees
dw itemMedicine
dw itemHealth
postMessage:
db "For player X? Yes No ", $00
itemZol:
db m"Slime storm|100 {RUPEES}", $00
itemChicken:
db m"Coccu party|50 {RUPEES}", $00
itemPieceOfPower:
db m"Piece of Power|50 {RUPEES}", $00
itemBombs:
db m"10 Bombs|50 {RUPEES}", $00
itemRupees:
db m"100 {RUPEES}|200 {RUPEES}", $00
itemMedicine:
db m"Medicine|100 {RUPEES}", $00
itemHealth:
db m"Health refill|10 {RUPEES}", $00
TalkResultHandler:
ld hl, ItemPriceTableBCD
ld a, [$C509]
dec a
add a, a
ld c, a ; b=0
add hl, bc
ldi a, [hl]
ld d, [hl]
ld e, a
ld a, [$DB5D]
cp d
ret c
jr nz, .highEnough
ld a, [$DB5E]
cp e
ret c
.highEnough:
; Got enough money, take it.
ld hl, ItemPriceTableDEC
ld a, [$C509]
dec a
ld c, a ; b=0
add hl, bc
ld a, [hl]
ld [$DB92], a ; set substract buffer
; Set the item to send
ld hl, $DDFE
ld a, [$C509] ; currently picked up item
ldi [hl], a
ldh a, [$EE] ; X position of NPC
ldi [hl], a
ld hl, $DDF7
set 2, [hl]
; No longer picked up item
xor a
ld [$C509], a
ret
ItemPriceTableBCD:
dw $0100, $0050, $0050, $0050, $0200, $0100, $0010
ItemPriceTableDEC:
db $64, $32, $32, $32, $C8, $64, $0A
""", 0x5E18), fill_nop=True)

View File

@@ -0,0 +1,27 @@
from ..assembler import ASM
_LOOPING_MUSIC = (1, 2, 3, 4, 5, 6, 7, 8, 9, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1C, 0x1D, 0x1F, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x2F, 0x31, 0x32, 0x33, 0x37,
0x39, 0x3A, 0x3C, 0x3E, 0x40, 0x48, 0x49, 0x4A, 0x4B, 0x4E, 0x50, 0x53, 0x54, 0x55, 0x57, 0x58, 0x59,
0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61)
def randomizeMusic(rom, rnd):
# Randomize overworld
for x in range(0, 16, 2):
for y in range(0, 16, 2):
idx = x + y * 16
result = rnd.choice(_LOOPING_MUSIC)
rom.banks[0x02][idx] = result
rom.banks[0x02][idx+1] = result
rom.banks[0x02][idx+16] = result
rom.banks[0x02][idx+17] = result
# Random music in dungeons/caves
for n in range(0x20):
rom.banks[0x02][0x100 + n] = rnd.choice(_LOOPING_MUSIC)
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)

Binary file not shown.

View File

@@ -0,0 +1,224 @@
from ..assembler import ASM
from ..roomEditor import RoomEditor, ObjectWarp, Object, WARP_TYPE_IDS
from .. import entityData
import os
import json
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, $7F00
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][0x3F00+x+y*16] = rom.banks[0x20][0x2E73 + (x // 2) + (y // 2) * 8]
rom.banks[0x3F][0x3F07] = rom.banks[0x3F][0x3F08] # Fix the room next to the egg
rom.banks[0x3F][0x3F17] = rom.banks[0x3F][0x3F08] # Fix the room next to the egg
rom.banks[0x3F][0x3F3A] = 0x0F # room below mambo cave
rom.banks[0x3F][0x3F3B] = 0x0F # room below D4
rom.banks[0x3F][0x3F4B] = 0x0F # room next to castle
rom.banks[0x3F][0x3F5B] = 0x0F # room next to castle
# Fix the rooms around the camera shop
rom.banks[0x3F][0x3F26] = 0x0F
rom.banks[0x3F][0x3F27] = 0x0F
rom.banks[0x3F][0x3F36] = 0x0F
def createDungeonOnlyOverworld(rom):
# Skip the whole egg maze.
rom.patch(0x14, 0x0453, "75", "73")
instrument_rooms = [0x102, 0x12A, 0x159, 0x162, 0x182, 0x1B5, 0x22C, 0x230, 0x301]
path = os.path.dirname(__file__)
# Start with clearing all the maps, because this just generates a bunch of room in the rom.
for n in range(0x100):
re = RoomEditor(rom, n)
re.entities = []
re.objects = []
if os.path.exists("%s/overworld/dive/%02X.json" % (path, n)):
re.loadFromJson("%s/overworld/dive/%02X.json" % (path, n))
entrances = list(filter(lambda obj: obj.type_id in WARP_TYPE_IDS, re.objects))
for obj in re.objects:
if isinstance(obj, ObjectWarp) and entrances:
e = entrances.pop(0)
other = RoomEditor(rom, obj.room)
for o in other.objects:
if isinstance(o, ObjectWarp) and o.warp_type == 0:
o.room = n
o.target_x = e.x * 16 + 8
o.target_y = e.y * 16 + 16
other.store(rom)
if obj.room == 0x1F5:
# Patch the boomang guy exit
other = RoomEditor(rom, "Alt1F5")
other.getWarps()[0].room = n
other.getWarps()[0].target_x = e.x * 16 + 8
other.getWarps()[0].target_y = e.y * 16 + 16
other.store(rom)
if obj.warp_type == 1 and (obj.map_nr < 8 or obj.map_nr == 0xFF) and obj.room not in (0x1B0, 0x23A, 0x23D):
other = RoomEditor(rom, instrument_rooms[min(8, obj.map_nr)])
for o in other.objects:
if isinstance(o, ObjectWarp) and o.warp_type == 0:
o.room = n
o.target_x = e.x * 16 + 8
o.target_y = e.y * 16 + 16
other.store(rom)
re.store(rom)
def exportOverworld(rom):
import PIL.Image
path = os.path.dirname(__file__)
for room_index in list(range(0x100)) + ["Alt06", "Alt0E", "Alt1B", "Alt2B", "Alt79", "Alt8C"]:
room = RoomEditor(rom, room_index)
if isinstance(room_index, int):
room_nr = room_index
else:
room_nr = int(room_index[3:], 16)
tileset_index = rom.banks[0x3F][0x3F00 + room_nr]
attributedata_bank = rom.banks[0x1A][0x2476 + room_nr]
attributedata_addr = rom.banks[0x1A][0x1E76 + room_nr * 2]
attributedata_addr |= rom.banks[0x1A][0x1E76 + room_nr * 2 + 1] << 8
attributedata_addr -= 0x4000
metatile_info = rom.banks[0x1A][0x2B1D:0x2B1D + 0x400]
attrtile_info = rom.banks[attributedata_bank][attributedata_addr:attributedata_addr+0x400]
palette_index = rom.banks[0x21][0x02EF + room_nr]
palette_addr = rom.banks[0x21][0x02B1 + palette_index * 2]
palette_addr |= rom.banks[0x21][0x02B1 + palette_index * 2 + 1] << 8
palette_addr -= 0x4000
hidden_warp_tiles = []
for obj in room.objects:
if obj.type_id in WARP_TYPE_IDS and room.overlay[obj.x + obj.y * 10] != obj.type_id:
if obj.type_id != 0xE1 or room.overlay[obj.x + obj.y * 10] != 0x53: # Ignore the waterfall 'caves'
hidden_warp_tiles.append(obj)
if obj.type_id == 0xC5 and room_nr < 0x100 and room.overlay[obj.x + obj.y * 10] == 0xC4:
# Pushable gravestones have the wrong overlay by default
room.overlay[obj.x + obj.y * 10] = 0xC5
if obj.type_id == 0xDC and room_nr < 0x100:
# Flowers above the rooster windmill need a different tile
hidden_warp_tiles.append(obj)
image_filename = "tiles_%02x_%02x_%02x_%02x_%04x.png" % (tileset_index, room.animation_id, palette_index, attributedata_bank, attributedata_addr)
data = {
"width": 10, "height": 8,
"type": "map", "renderorder": "right-down", "tiledversion": "1.4.3", "version": 1.4,
"tilewidth": 16, "tileheight": 16, "orientation": "orthogonal",
"tilesets": [
{
"columns": 16, "firstgid": 1,
"image": image_filename, "imageheight": 256, "imagewidth": 256,
"margin": 0, "name": "main", "spacing": 0,
"tilecount": 256, "tileheight": 16, "tilewidth": 16
}
],
"layers": [{
"data": [n+1 for n in room.overlay],
"width": 10, "height": 8,
"id": 1, "name": "Tiles", "type": "tilelayer", "visible": True, "opacity": 1, "x": 0, "y": 0,
}, {
"id": 2, "name": "EntityLayer", "type": "objectgroup", "visible": True, "opacity": 1, "x": 0, "y": 0,
"objects": [
{"width": 16, "height": 16, "x": entity[0] * 16, "y": entity[1] * 16, "name": entityData.NAME[entity[2]], "type": "entity"} for entity in room.entities
] + [
{"width": 8, "height": 8, "x": 0, "y": idx * 8, "name": "%x:%02x:%03x:%02x:%02x" % (obj.warp_type, obj.map_nr, obj.room, obj.target_x, obj.target_y), "type": "warp"} for idx, obj in enumerate(room.getWarps()) if isinstance(obj, ObjectWarp)
] + [
{"width": 16, "height": 16, "x": obj.x * 16, "y": obj.y * 16, "name": "%02X" % (obj.type_id), "type": "hidden_tile"} for obj in hidden_warp_tiles
],
}],
"properties": [
{"name": "tileset", "type": "string", "value": "%02X" % (tileset_index)},
{"name": "animationset", "type": "string", "value": "%02X" % (room.animation_id)},
{"name": "attribset", "type": "string", "value": "%02X:%04X" % (attributedata_bank, attributedata_addr)},
{"name": "palette", "type": "string", "value": "%02X" % (palette_index)},
]
}
if isinstance(room_index, str):
json.dump(data, open("%s/overworld/export/%s.json" % (path, room_index), "wt"))
else:
json.dump(data, open("%s/overworld/export/%02X.json" % (path, room_index), "wt"))
if not os.path.exists("%s/overworld/export/%s" % (path, image_filename)):
tilemap = rom.banks[0x2F][tileset_index*0x100:tileset_index*0x100+0x200]
tilemap += rom.banks[0x2C][0x1200:0x1800]
tilemap += rom.banks[0x2C][0x0800:0x1000]
anim_addr = {2: 0x2B00, 3: 0x2C00, 4: 0x2D00, 5: 0x2E00, 6: 0x2F00, 7: 0x2D00, 8: 0x3000, 9: 0x3100, 10: 0x3200, 11: 0x2A00, 12: 0x3300, 13: 0x3500, 14: 0x3600, 15: 0x3400, 16: 0x3700}.get(room.animation_id, 0x0000)
tilemap[0x6C0:0x700] = rom.banks[0x2C][anim_addr:anim_addr + 0x40]
palette = []
for n in range(8*4):
p0 = rom.banks[0x21][palette_addr]
p1 = rom.banks[0x21][palette_addr + 1]
pal = p0 | p1 << 8
palette_addr += 2
r = (pal & 0x1F) << 3
g = ((pal >> 5) & 0x1F) << 3
b = ((pal >> 10) & 0x1F) << 3
palette += [r, g, b]
img = PIL.Image.new("P", (16*16, 16*16))
img.putpalette(palette)
def drawTile(x, y, index, attr):
for py in range(8):
a = tilemap[index * 16 + py * 2]
b = tilemap[index * 16 + py * 2 + 1]
if attr & 0x40:
a = tilemap[index * 16 + 14 - py * 2]
b = tilemap[index * 16 + 15 - py * 2]
for px in range(8):
bit = 0x80 >> px
if attr & 0x20:
bit = 0x01 << px
c = (attr & 7) << 2
if a & bit:
c |= 1
if b & bit:
c |= 2
img.putpixel((x+px, y+py), c)
for x in range(16):
for y in range(16):
idx = x+y*16
metatiles = metatile_info[idx*4:idx*4+4]
attrtiles = attrtile_info[idx*4:idx*4+4]
drawTile(x * 16 + 0, y * 16 + 0, metatiles[0], attrtiles[0])
drawTile(x * 16 + 8, y * 16 + 0, metatiles[1], attrtiles[1])
drawTile(x * 16 + 0, y * 16 + 8, metatiles[2], attrtiles[2])
drawTile(x * 16 + 8, y * 16 + 8, metatiles[3], attrtiles[3])
img.save("%s/overworld/export/%s" % (path, image_filename))
world = {
"maps": [
{"fileName": "%02X.json" % (n), "height": 128, "width": 160, "x": (n & 0x0F) * 160, "y": (n >> 4) * 128}
for n in range(0x100)
],
"onlyShowAdjacentMaps": False,
"type": "world"
}
json.dump(world, open("%s/overworld/export/world.world" % (path), "wt"))
def isNormalOverworld(rom):
return len(RoomEditor(rom, 0x010).getWarps()) > 0

View File

@@ -0,0 +1,124 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 125, 126, 1, 129, 78, 78, 78, 130, 1, 125, 240, 240, 240, 56, 11, 11, 11, 57, 240, 240, 2, 2, 30, 47, 73, 225, 74, 79, 94, 2, 2, 2, 56, 58, 226, 225, 59, 60, 57, 2, 2, 2, 56, 10, 10, 10, 10, 10, 123, 123, 2, 2, 56, 10, 10, 10, 10, 10, 57, 2, 2, 2, 47, 48, 48, 48, 48, 48, 79, 2],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":16,
"id":1,
"name":"HEART_PIECE",
"rotation":0,
"type":"entity",
"visible":true,
"width":16,
"x":64,
"y":32
},
{
"height":16,
"id":2,
"name":"CROW",
"rotation":0,
"type":"entity",
"visible":true,
"width":16,
"x":96,
"y":32
},
{
"height":16,
"id":3,
"name":"MINI_MOLDORM",
"rotation":0,
"type":"entity",
"visible":true,
"width":16,
"x":112,
"y":96
},
{
"height":8,
"id":4,
"name":"1:07:23a:58:10",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":5,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"0B"
},
{
"name":"attribset",
"type":"string",
"value":"25:3400"
},
{
"name":"palette",
"type":"string",
"value":"0F"
},
{
"name":"tileset",
"type":"string",
"value":"1C"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_1c_0b_0f_25_3400.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,102 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[125, 126, 1, 1, 1, 1, 1, 1, 1, 125, 29, 29, 126, 1, 1, 129, 78, 130, 125, 29, 240, 240, 240, 240, 240, 56, 4, 57, 240, 240, 2, 2, 30, 81, 81, 47, 48, 79, 94, 2, 2, 2, 56, 4, 4, 206, 226, 216, 57, 2, 123, 123, 123, 11, 4, 4, 4, 4, 57, 2, 2, 2, 56, 11, 11, 11, 11, 11, 57, 2, 2, 2, 47, 48, 48, 48, 48, 48, 79, 2],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":16,
"id":1,
"name":"CROW",
"rotation":0,
"type":"entity",
"visible":true,
"width":16,
"x":32,
"y":80
},
{
"height":8,
"id":2,
"name":"1:07:23d:58:10",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":3,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"0B"
},
{
"name":"attribset",
"type":"string",
"value":"25:3400"
},
{
"name":"palette",
"type":"string",
"value":"0F"
},
{
"name":"tileset",
"type":"string",
"value":"1C"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_1c_0b_0f_25_3400.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,113 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[1, 1, 1, 1, 6, 7, 8, 1, 1, 1, 125, 126, 1, 129, 100, 101, 102, 130, 125, 126, 240, 240, 240, 56, 114, 29, 128, 57, 240, 240, 230, 230, 30, 56, 170, 171, 192, 57, 94, 230, 230, 230, 56, 47, 73, 225, 74, 79, 57, 230, 230, 230, 56, 63, 59, 225, 59, 64, 57, 230, 230, 30, 47, 48, 73, 225, 74, 48, 79, 94, 230, 56, 63, 59, 59, 225, 59, 59, 64, 57],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":16,
"id":1,
"name":"EGG_SONG_EVENT",
"rotation":0,
"type":"entity",
"visible":true,
"width":16,
"x":0,
"y":0
},
{
"height":8,
"id":2,
"name":"1:08:270:50:7c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
},
{
"height":16,
"id":3,
"name":"E1",
"rotation":0,
"type":"hidden_tile",
"visible":true,
"width":16,
"x":80,
"y":48
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":4,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"0B"
},
{
"name":"attribset",
"type":"string",
"value":"27:1620"
},
{
"name":"palette",
"type":"string",
"value":"13"
},
{
"name":"tileset",
"type":"string",
"value":"3C"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_3c_0b_13_27_1620.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,91 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[30, 47, 48, 48, 73, 225, 48, 48, 48, 79, 56, 63, 59, 59, 59, 225, 59, 59, 59, 64, 56, 58, 59, 59, 59, 225, 59, 59, 59, 60, 47, 48, 48, 48, 73, 225, 74, 48, 48, 48, 58, 59, 226, 59, 59, 225, 59, 59, 59, 59, 201, 213, 4, 4, 4, 4, 4, 4, 4, 201, 201, 4, 4, 4, 4, 4, 4, 4, 4, 201, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":8,
"id":1,
"name":"0:00:082:48:30",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"0B"
},
{
"name":"attribset",
"type":"string",
"value":"27:1620"
},
{
"name":"palette",
"type":"string",
"value":"13"
},
{
"name":"tileset",
"type":"string",
"value":"3C"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_3c_0b_13_27_1620.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,91 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[2, 2, 2, 115, 117, 117, 117, 116, 2, 2, 2, 2, 2, 115, 118, 215, 119, 116, 2, 2, 2, 2, 30, 115, 117, 226, 117, 116, 94, 2, 2, 2, 56, 183, 117, 120, 117, 184, 57, 2, 2, 2, 56, 4, 4, 4, 4, 4, 57, 2, 2, 2, 56, 4, 4, 4, 4, 4, 57, 2, 2, 2, 47, 48, 73, 225, 74, 48, 79, 2, 2, 2, 63, 59, 59, 225, 59, 59, 64, 2],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":8,
"id":1,
"name":"1:06:20e:50:7c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"00"
},
{
"name":"attribset",
"type":"string",
"value":"27:1E40"
},
{
"name":"palette",
"type":"string",
"value":"16"
},
{
"name":"tileset",
"type":"string",
"value":"30"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_30_00_16_27_1e40.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,91 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 44, 45, 45, 46, 15, 15, 15, 15, 15, 15, 56, 161, 199, 57, 15, 15, 15, 15, 15, 15, 56, 5, 5, 57, 15, 15, 15, 15, 15, 15, 52, 48, 48, 53, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":8,
"id":1,
"name":"1:05:1b0:78:10",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"03"
},
{
"name":"attribset",
"type":"string",
"value":"22:0000"
},
{
"name":"palette",
"type":"string",
"value":"16"
},
{
"name":"tileset",
"type":"string",
"value":"0F"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_0f_03_16_22_0000.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,91 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[3, 56, 63, 59, 59, 59, 59, 59, 64, 48, 3, 56, 58, 183, 59, 226, 59, 183, 60, 10, 3, 56, 33, 184, 10, 10, 10, 184, 10, 10, 3, 56, 33, 10, 10, 10, 10, 10, 10, 4, 3, 56, 201, 10, 10, 10, 10, 10, 4, 4, 3, 56, 201, 201, 10, 10, 10, 10, 10, 4, 3, 47, 48, 48, 48, 48, 48, 48, 48, 48, 3, 63, 59, 59, 59, 59, 59, 59, 59, 59],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":8,
"id":1,
"name":"1:07:25d:50:7c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"00"
},
{
"name":"attribset",
"type":"string",
"value":"25:3400"
},
{
"name":"palette",
"type":"string",
"value":"19"
},
{
"name":"tileset",
"type":"string",
"value":"1C"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_1c_00_19_25_3400.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,80 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[48, 54, 58, 59, 59, 225, 59, 59, 60, 46, 4, 4, 4, 4, 4, 4, 4, 4, 4, 57, 4, 4, 4, 4, 4, 93, 93, 93, 4, 77, 4, 4, 12, 12, 4, 93, 93, 93, 93, 4, 4, 4, 12, 4, 4, 4, 93, 93, 4, 4, 4, 4, 12, 4, 4, 4, 4, 4, 4, 4, 48, 73, 225, 74, 48, 73, 75, 74, 48, 48, 59, 59, 225, 59, 59, 59, 59, 59, 59, 59],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":1,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"00"
},
{
"name":"attribset",
"type":"string",
"value":"22:0000"
},
{
"name":"palette",
"type":"string",
"value":"16"
},
{
"name":"tileset",
"type":"string",
"value":"0F"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_0f_00_16_22_0000.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,113 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[2, 2, 2, 180, 180, 180, 180, 180, 2, 2, 2, 2, 44, 180, 180, 180, 180, 180, 46, 2, 81, 81, 76, 174, 178, 232, 174, 178, 57, 2, 4, 4, 4, 175, 179, 228, 175, 179, 57, 2, 4, 4, 4, 4, 4, 4, 4, 4, 57, 2, 4, 4, 4, 4, 4, 4, 4, 4, 57, 2, 48, 48, 48, 48, 48, 48, 48, 48, 53, 2, 59, 59, 59, 59, 59, 59, 59, 59, 64, 2],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":16,
"id":1,
"name":"ARMOS_STATUE",
"rotation":0,
"type":"entity",
"visible":true,
"width":16,
"x":64,
"y":64
},
{
"height":16,
"id":2,
"name":"ARMOS_STATUE",
"rotation":0,
"type":"entity",
"visible":true,
"width":16,
"x":96,
"y":64
},
{
"height":8,
"id":3,
"name":"1:05:1d4:50:7c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":4,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"00"
},
{
"name":"attribset",
"type":"string",
"value":"25:3C00"
},
{
"name":"palette",
"type":"string",
"value":"1C"
},
{
"name":"tileset",
"type":"string",
"value":"2A"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_2a_00_1c_25_3c00.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,91 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[11, 57, 63, 59, 214, 215, 216, 59, 64, 63, 11, 57, 58, 59, 206, 226, 207, 59, 60, 63, 11, 57, 15, 15, 15, 12, 15, 15, 15, 58, 11, 57, 15, 15, 15, 12, 15, 15, 15, 5, 11, 57, 15, 15, 15, 12, 15, 15, 5, 5, 55, 53, 15, 15, 15, 12, 12, 12, 5, 5, 38, 39, 10, 15, 15, 15, 15, 15, 15, 38, 40, 42, 39, 38, 39, 38, 39, 38, 39, 40],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":8,
"id":1,
"name":"1:03:17a:50:7c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"03"
},
{
"name":"attribset",
"type":"string",
"value":"27:2640"
},
{
"name":"palette",
"type":"string",
"value":"01"
},
{
"name":"tileset",
"type":"string",
"value":"34"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_34_03_01_27_2640.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,91 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[59, 59, 225, 59, 59, 59, 59, 59, 59, 59, 59, 59, 225, 59, 59, 59, 59, 59, 59, 59, 59, 59, 225, 59, 187, 59, 59, 59, 59, 59, 5, 5, 12, 10, 93, 10, 5, 5, 5, 5, 5, 5, 12, 5, 10, 5, 11, 11, 11, 5, 5, 5, 12, 5, 5, 11, 93, 93, 93, 11, 39, 5, 5, 5, 5, 11, 93, 93, 93, 11, 41, 5, 5, 5, 5, 5, 11, 11, 11, 62],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":8,
"id":1,
"name":"0:00:016:28:50",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"00"
},
{
"name":"attribset",
"type":"string",
"value":"25:0C00"
},
{
"name":"palette",
"type":"string",
"value":"01"
},
{
"name":"tileset",
"type":"string",
"value":"0F"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_0f_00_01_25_0c00.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,91 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[59, 64, 38, 39, 72, 59, 59, 59, 59, 60, 59, 64, 40, 41, 57, 183, 184, 103, 82, 82, 59, 60, 212, 212, 57, 104, 228, 105, 82, 203, 5, 5, 5, 5, 57, 15, 15, 15, 15, 203, 5, 5, 62, 225, 53, 15, 15, 15, 15, 203, 5, 5, 57, 15, 15, 15, 15, 15, 82, 203, 5, 33, 57, 15, 15, 15, 15, 82, 82, 203, 48, 61, 51, 45, 45, 45, 45, 45, 45, 45],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":8,
"id":1,
"name":"1:04:1a1:50:7c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"03"
},
{
"name":"attribset",
"type":"string",
"value":"22:2400"
},
{
"name":"palette",
"type":"string",
"value":"09"
},
{
"name":"tileset",
"type":"string",
"value":"3A"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_3a_03_09_22_2400.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,91 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[43, 42, 63, 59, 59, 59, 59, 59, 64, 42, 42, 43, 63, 59, 183, 216, 183, 59, 64, 40, 43, 41, 58, 183, 184, 226, 184, 183, 60, 11, 41, 82, 82, 28, 28, 28, 28, 82, 19, 5, 82, 28, 28, 28, 28, 28, 28, 27, 23, 5, 82, 82, 28, 28, 82, 82, 28, 19, 33, 5, 82, 82, 82, 28, 28, 28, 28, 19, 33, 5, 38, 39, 38, 39, 38, 39, 38, 39, 38, 39],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":8,
"id":1,
"name":"1:01:136:50:7c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"03"
},
{
"name":"attribset",
"type":"string",
"value":"22:3400"
},
{
"name":"palette",
"type":"string",
"value":"0E"
},
{
"name":"tileset",
"type":"string",
"value":"36"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_36_03_0e_22_3400.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,102 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[39, 5, 5, 5, 5, 5, 5, 5, 5, 51, 41, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 11, 11, 11, 5, 38, 70, 39, 5, 5, 5, 83, 83, 83, 11, 40, 226, 41, 5, 5, 5, 92, 227, 92, 11, 11, 93, 11, 5, 5, 5, 5, 5, 5, 11, 11, 11, 5, 10, 5, 5, 5, 5, 5, 5, 5, 5, 10, 10, 38, 39, 5, 5, 5, 5, 5, 5, 10, 111],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":8,
"id":1,
"name":"1:10:2cb:50:7c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
},
{
"height":8,
"id":2,
"name":"1:0e:2a1:50:7c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":8
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":3,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"03"
},
{
"name":"attribset",
"type":"string",
"value":"22:0000"
},
{
"name":"palette",
"type":"string",
"value":"0E"
},
{
"name":"tileset",
"type":"string",
"value":"0F"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_0f_03_0e_22_0000.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,91 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[45, 50, 63, 59, 59, 59, 59, 59, 64, 63, 5, 5, 58, 107, 109, 109, 109, 107, 60, 63, 5, 5, 183, 108, 99, 228, 99, 108, 183, 63, 5, 5, 184, 18, 28, 28, 28, 19, 184, 63, 5, 5, 5, 22, 17, 17, 17, 23, 5, 63, 10, 5, 10, 183, 5, 5, 5, 183, 5, 58, 10, 10, 10, 184, 5, 5, 5, 184, 5, 38, 111, 38, 39, 38, 39, 38, 39, 38, 39, 40],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":8,
"id":1,
"name":"1:02:152:50:7c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"03"
},
{
"name":"attribset",
"type":"string",
"value":"22:1800"
},
{
"name":"palette",
"type":"string",
"value":"01"
},
{
"name":"tileset",
"type":"string",
"value":"2E"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_2e_03_01_22_1800.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,91 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[43, 42, 44, 45, 45, 45, 46, 42, 43, 42, 42, 43, 47, 48, 48, 48, 79, 40, 41, 40, 43, 41, 58, 99, 228, 99, 60, 11, 11, 5, 41, 11, 11, 10, 10, 10, 10, 10, 10, 10, 111, 11, 183, 10, 10, 10, 183, 10, 10, 10, 111, 5, 184, 10, 10, 10, 184, 10, 5, 5, 111, 5, 5, 10, 10, 10, 10, 5, 5, 5, 48, 48, 73, 75, 74, 48, 48, 48, 48, 48],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":8,
"id":1,
"name":"1:00:117:50:7c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"00"
},
{
"name":"attribset",
"type":"string",
"value":"22:0C00"
},
{
"name":"palette",
"type":"string",
"value":"01"
},
{
"name":"tileset",
"type":"string",
"value":"24"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_24_00_01_22_0c00.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,113 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[43, 41, 5, 5, 5, 5, 5, 10, 10, 111, 41, 5, 5, 5, 5, 5, 5, 5, 5, 10, 5, 5, 38, 39, 93, 93, 93, 38, 39, 11, 10, 5, 40, 41, 83, 83, 83, 40, 41, 11, 10, 5, 11, 11, 92, 227, 92, 11, 11, 11, 5, 5, 11, 11, 11, 12, 11, 11, 11, 11, 5, 5, 5, 11, 12, 12, 5, 5, 5, 11, 61, 4, 4, 4, 12, 4, 4, 4, 4, 62],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":16,
"id":1,
"name":"BUTTERFLY",
"rotation":0,
"type":"entity",
"visible":true,
"width":16,
"x":32,
"y":48
},
{
"height":16,
"id":2,
"name":"BUTTERFLY",
"rotation":0,
"type":"entity",
"visible":true,
"width":16,
"x":112,
"y":80
},
{
"height":8,
"id":3,
"name":"1:10:2a3:50:7c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":4,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"00"
},
{
"name":"attribset",
"type":"string",
"value":"22:0000"
},
{
"name":"palette",
"type":"string",
"value":"01"
},
{
"name":"tileset",
"type":"string",
"value":"0F"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_0f_00_01_22_0000.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,91 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[111, 82, 82, 82, 82, 82, 82, 82, 82, 82, 11, 11, 11, 5, 5, 5, 5, 11, 11, 82, 11, 183, 184, 10, 183, 201, 184, 5, 11, 82, 11, 206, 207, 10, 206, 226, 207, 5, 11, 82, 11, 5, 5, 11, 11, 11, 11, 11, 11, 82, 11, 11, 11, 5, 197, 10, 197, 11, 11, 82, 11, 5, 11, 11, 11, 11, 5, 5, 11, 82, 48, 48, 48, 48, 48, 48, 48, 48, 48, 61],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":8,
"id":1,
"name":"1:ff:312:50:5c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"00"
},
{
"name":"attribset",
"type":"string",
"value":"25:2800"
},
{
"name":"palette",
"type":"string",
"value":"01"
},
{
"name":"tileset",
"type":"string",
"value":"38"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_38_00_01_25_2800.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,80 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[56, 4, 4, 4, 4, 4, 4, 4, 4, 57, 56, 183, 184, 4, 4, 4, 4, 62, 48, 53, 56, 206, 207, 4, 4, 4, 4, 57, 183, 184, 56, 161, 93, 4, 4, 4, 4, 57, 206, 207, 56, 93, 93, 62, 73, 75, 74, 79, 183, 184, 56, 93, 4, 57, 59, 59, 59, 60, 206, 207, 47, 48, 48, 79, 31, 31, 31, 31, 31, 31, 58, 59, 59, 60, 32, 32, 32, 32, 32, 32],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":1,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"02"
},
{
"name":"attribset",
"type":"string",
"value":"22:1000"
},
{
"name":"palette",
"type":"string",
"value":"01"
},
{
"name":"tileset",
"type":"string",
"value":"22"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_22_02_01_22_1000.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,102 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[44, 46, 44, 45, 45, 45, 45, 46, 44, 46, 52, 53, 52, 48, 227, 48, 48, 53, 52, 53, 183, 184, 9, 9, 9, 9, 201, 183, 184, 9, 206, 207, 9, 9, 9, 9, 9, 206, 207, 9, 183, 184, 201, 9, 9, 9, 9, 183, 184, 9, 206, 207, 9, 9, 9, 9, 9, 206, 207, 9, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":16,
"id":1,
"name":"MARIN",
"rotation":0,
"type":"entity",
"visible":true,
"width":16,
"x":48,
"y":48
},
{
"height":8,
"id":2,
"name":"1:1f:1e1:88:50",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":3,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"02"
},
{
"name":"attribset",
"type":"string",
"value":"22:1000"
},
{
"name":"palette",
"type":"string",
"value":"01"
},
{
"name":"tileset",
"type":"string",
"value":"22"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_22_02_01_22_1000.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,91 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[56, 4, 4, 4, 12, 4, 4, 4, 4, 57, 52, 54, 4, 4, 4, 9, 9, 9, 55, 53, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 183, 184, 9, 9, 9, 9, 9, 37, 9, 9, 206, 207, 36, 9, 9, 9, 9, 9, 9, 9, 9, 36, 9, 9, 9, 9, 9, 9, 9, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":16,
"id":1,
"name":"HEART_PIECE",
"rotation":0,
"type":"entity",
"visible":true,
"width":16,
"x":80,
"y":80
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"02"
},
{
"name":"attribset",
"type":"string",
"value":"22:1000"
},
{
"name":"palette",
"type":"string",
"value":"01"
},
{
"name":"tileset",
"type":"string",
"value":"22"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_22_02_01_22_1000.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,91 @@
{ "compressionlevel":-1,
"editorsettings":
{
"export":
{
"target":"."
}
},
"height":8,
"infinite":false,
"layers":[
{
"data":[63, 59, 59, 63, 59, 59, 59, 64, 59, 56, 58, 59, 59, 63, 59, 59, 59, 64, 59, 56, 9, 9, 9, 58, 59, 187, 59, 60, 9, 56, 9, 9, 36, 9, 9, 36, 9, 9, 9, 56, 9, 9, 9, 9, 9, 9, 9, 9, 9, 56, 9, 9, 9, 9, 36, 9, 9, 9, 37, 56, 31, 31, 31, 31, 31, 31, 31, 31, 31, 52, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32],
"height":8,
"id":1,
"name":"Tiles",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":2,
"name":"EntityLayer",
"objects":[
{
"height":8,
"id":1,
"name":"1:1f:1f5:48:7c",
"rotation":0,
"type":"warp",
"visible":true,
"width":8,
"x":0,
"y":0
}],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
}],
"nextlayerid":1,
"nextobjectid":2,
"orientation":"orthogonal",
"properties":[
{
"name":"animationset",
"type":"string",
"value":"02"
},
{
"name":"attribset",
"type":"string",
"value":"22:1000"
},
{
"name":"palette",
"type":"string",
"value":"01"
},
{
"name":"tileset",
"type":"string",
"value":"22"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":16,
"tilesets":[
{
"columns":16,
"firstgid":1,
"image":"tiles_22_02_01_22_1000.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"main",
"spacing":0,
"tilecount":256,
"tileheight":16,
"tilewidth":16
}],
"tilewidth":16,
"type":"map",
"version":1.4,
"width":10
}

View File

@@ -0,0 +1,144 @@
from ..roomEditor import RoomEditor
from ..assembler import ASM
from ..utils import formatText
def removeOwlEvents(rom):
# Remove all the owl events from the entity tables.
for room in range(0x100):
re = RoomEditor(rom, room)
if re.hasEntity(0x41):
re.removeEntities(0x41)
re.store(rom)
# Clear texts used by the owl. Potentially reused somewhere o else.
rom.texts[0x0D9] = b'\xff' # used by boomerang
# 1 Used by empty chest (master stalfos message)
# 8 unused (0x0C0-0x0C7)
# 1 used by bowwow in chest
# 1 used by item for other player message
# 2 used by arrow chest messages
# 2 used by tunics
for idx in range(0x0BE, 0x0CE):
rom.texts[idx] = b'\xff'
# Patch the owl entity into a ghost to allow refill of powder/bombs/arrows
rom.texts[0xC0] = formatText("Everybody hates me, so I give away free things in the hope people will love me. Want something?", ask="Okay No")
rom.texts[0xC1] = formatText("Good for you.")
rom.patch(0x06, 0x27F5, 0x2A77, ASM("""
; Check if we have powder or bombs.
ld e, INV_SIZE
ld hl, $DB00
loop:
ldi a, [hl]
cp $02 ; bombs
jr z, hasProperItem
cp $0C ; powder
jr z, hasProperItem
cp $05 ; bow
jr z, hasProperItem
dec e
jr nz, loop
ret
hasProperItem:
; Render ghost
ld de, sprite
call $3BC0
call $64C6 ; check if game is busy (pops this stack frame if busy)
ldh a, [$E7] ; frame counter
swap a
and $01
call $3B0C ; set entity sprite variant
call $641A ; check collision
ldh a, [$F0] ;entity state
rst 0
dw waitForTalk
dw talking
waitForTalk:
call $645D ; check if talked to
ret nc
ld a, $C0
call $2385 ; open dialog
call $3B12 ; increase entity state
ret
talking:
; Check if we are still talking
ld a, [$C19F]
and a
ret nz
call $3B12 ; increase entity state
ld [hl], $00 ; set to state 0
ld a, [$C177] ; get which option we selected
and a
ret nz
; Give powder
ld a, [$DB4C]
cp $10
jr nc, doNotGivePowder
ld a, $10
ld [$DB4C], a
doNotGivePowder:
ld a, [$DB4D]
cp $10
jr nc, doNotGiveBombs
ld a, $10
ld [$DB4D], a
doNotGiveBombs:
ld a, [$DB45]
cp $10
jr nc, doNotGiveArrows
ld a, $10
ld [$DB45], a
doNotGiveArrows:
ld a, $C1
call $2385 ; open dialog
ret
sprite:
db $76, $09, $78, $09, $7A, $09, $7C, $09
""", 0x67F5), fill_nop=True)
rom.patch(0x20, 0x0322 + 0x41 * 2, "734A", "564B") # Remove the owl init handler
re = RoomEditor(rom, 0x2A3)
re.entities.append((7, 6, 0x41))
re.store(rom)
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 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)

View File

@@ -0,0 +1,60 @@
from ..assembler import ASM
def patchPhone(rom):
rom.texts[0x141] = b""
rom.texts[0x142] = b""
rom.texts[0x143] = b""
rom.texts[0x144] = b""
rom.texts[0x145] = b""
rom.texts[0x146] = b""
rom.texts[0x147] = b""
rom.texts[0x148] = b""
rom.texts[0x149] = b""
rom.texts[0x14A] = b""
rom.texts[0x14B] = b""
rom.texts[0x14C] = b""
rom.texts[0x14D] = b""
rom.texts[0x14E] = b""
rom.texts[0x14F] = b""
rom.texts[0x16E] = b""
rom.texts[0x1FD] = b""
rom.texts[0x228] = b""
rom.texts[0x229] = b""
rom.texts[0x22A] = b""
rom.texts[0x240] = b""
rom.texts[0x241] = b""
rom.texts[0x242] = b""
rom.texts[0x243] = b""
rom.texts[0x244] = b""
rom.texts[0x245] = b""
rom.texts[0x247] = b""
rom.texts[0x248] = b""
rom.patch(0x06, 0x2A8F, 0x2BBC, ASM("""
; We use $DB6D to store which tunics we have. This is normally the Dungeon9 instrument, which does not exist.
ld a, [$DC0F]
ld hl, wCollectedTunics
inc a
cp $01
jr nz, notTunic1
bit 0, [HL]
jr nz, notTunic1
inc a
notTunic1:
cp $02
jr nz, notTunic2
bit 1, [HL]
jr nz, notTunic2
inc a
notTunic2:
cp $03
jr nz, noWrap
xor a
noWrap:
ld [$DC0F], a
ret
"""), fill_nop=True)

View File

@@ -0,0 +1,19 @@
from ..assembler import ASM
def fixPhotographer(rom):
# Allow richard photo without slime key
rom.patch(0x36, 0x3234, ASM("jr nz, $52"), "", fill_nop=True)
rom.patch(0x36, 0x3240, ASM("jr z, $46"), "", fill_nop=True)
# Allow richard photo when castle is opened
rom.patch(0x36, 0x31FF, ASM("jp nz, $7288"), "", fill_nop=True)
# Allow photographer with bowwow saved
rom.patch(0x36, 0x0398, ASM("or [hl]"), "", fill_nop=True)
rom.patch(0x36, 0x3183, ASM("ret nz"), "", fill_nop=True)
rom.patch(0x36, 0x31CB, ASM("jp nz, $7288"), "", fill_nop=True)
rom.patch(0x36, 0x03DC, ASM("and $7F"), ASM("and $00"))
# Allow bowwow photo with follower
rom.patch(0x36, 0x31DA, ASM("jp nz, $7288"), "", fill_nop=True)
# Allow bridge photo with follower
rom.patch(0x36, 0x004D, ASM("call nz, $3F8D"), "", fill_nop=True)
rom.patch(0x36, 0x006D, ASM("ret nz"), "", fill_nop=True) # Checks if any entity is alive

View File

@@ -0,0 +1,9 @@
from ..assembler import ASM
def slowdownThreeOfAKind(rom):
rom.patch(0x06, 0x096B, ASM("ldh a, [$E7]\nand $0F"), ASM("ldh a, [$E7]\nand $3F"))
def fixHorseHeads(rom):
rom.patch(0x07, 0x3653, "00010400", "00010000")

View File

@@ -0,0 +1,40 @@
from ..assembler import ASM
from ..utils import formatText
def patchRooster(rom):
# Do not give the rooster
rom.patch(0x19, 0x0E9D, ASM("ld [$DB7B], a"), "", fill_nop=True)
# Do not load the rooster sprites
rom.patch(0x00, 0x2EC7, ASM("jr nz, $08"), "", fill_nop=True)
# Draw the found item
rom.patch(0x19, 0x0E4A, ASM("ld hl, $4E37\nld c, $03\ncall $3CE6"), ASM("ld a, $0C\nrst $08"), fill_nop=True)
rom.patch(0x19, 0x0E7B, ASM("ld hl, $4E37\nld c, $03\ncall $3CE6"), ASM("ld a, $0C\nrst $08"), fill_nop=True)
# Give the item and message
rom.patch(0x19, 0x0E69, ASM("ld a, $6D\ncall $2373"), ASM("ld a, $0E\nrst $08"), fill_nop=True)
# Reuse unused evil eagle text slot for rooster message
rom.texts[0x0B8] = formatText("Got the {ROOSTER}!")
# Allow rooster pickup with special rooster item
rom.patch(0x19, 0x1ABC, ASM("cp $03"), ASM("cp $0F"))
rom.patch(0x19, 0x1AAE, ASM("cp $03"), ASM("cp $0F"))
# Ignore the has-rooster flag in the rooster entity (do not despawn)
rom.patch(0x19, 0x19E0, ASM("jp z, $7E61"), "", fill_nop=True)
# If we are spawning the rooster, and the rooster is already existing, do not do anything, instead of despawning the rooster.
rom.patch(0x01, 0x1FEF, ASM("ld [hl], d"), ASM("ret"))
# Allow rooster to unload when changing rooms
rom.patch(0x19, 0x19E9, ASM("ld [hl], a"), "", fill_nop=True)
# Do not take away the rooster after D7
rom.patch(0x03, 0x1E25, ASM("ld [$DB7B], a"), "", fill_nop=True)
# Patch the color dungeon entrance not to check for rooster
rom.patch(0x02, 0x3409, ASM("ld hl, $DB7B\nor [hl]"), "", fill_nop=True)
# Spawn marin at taltal even with rooster
rom.patch(0x18, 0x1EE3, ASM("jp nz, $7F08"), "", fill_nop=True)

View File

@@ -0,0 +1,54 @@
from ..assembler import ASM
from ..backgroundEditor import BackgroundEditor
def singleSaveSlot(rom):
# Do not validate/erase slots 2 and 3 at rom start
rom.patch(0x01, 0x06B3, ASM("call $4794"), "", fill_nop=True)
rom.patch(0x01, 0x06B9, ASM("call $4794"), "", fill_nop=True)
# Patch the code that checks if files have proper filenames to skip file 2/3
rom.patch(0x01, 0x1DD9, ASM("ld b, $02"), ASM("ret"), fill_nop=True)
# Remove the part that writes death counters for save2/3 on the file select screen
rom.patch(0x01, 0x0821, 0x084B, "", fill_nop=True)
# Remove the call that updates the hearts for save2
rom.patch(0x01, 0x0800, ASM("call $4DBE"), "", fill_nop=True)
# Remove the call that updates the hearts for save3
rom.patch(0x01, 0x0806, ASM("call $4DD6"), "", fill_nop=True)
# Remove the call that updates the names for save2 and save3
rom.patch(0x01, 0x0D70, ASM("call $4D94\ncall $4D9D"), "", fill_nop=True)
# Remove the 2/3 slots from the screen and remove the copy text
be = BackgroundEditor(rom, 0x03)
del be.tiles[0x9924]
del be.tiles[0x9984]
be.store(rom)
be = BackgroundEditor(rom, 0x04)
del be.tiles[0x9924]
del be.tiles[0x9984]
for n in range(0x99ED, 0x99F1):
del be.tiles[n]
be.store(rom)
# Do not do left/right for erase/copy selection.
rom.patch(0x01, 0x092B, ASM("jr z, $0B"), ASM("jr $0B"))
# Only switch between players
rom.patch(0x01, 0x08FA, 0x091D, ASM("""
ld a, [$DBA7]
and a
ld a, [$DBA6]
jr z, skip
xor $03
skip:
"""), fill_nop=True)
# On the erase screen, only switch between save 1 and return
rom.patch(0x01, 0x0E12, ASM("inc a\nand $03"), ASM("xor $03"), fill_nop=True)
rom.patch(0x01, 0x0E21, ASM("dec a\ncp $ff\njr nz, $02\nld a,$03"), ASM("xor $03"), fill_nop=True)
be = BackgroundEditor(rom, 0x06)
del be.tiles[0x9924]
del be.tiles[0x9984]
be.store(rom)

View File

@@ -0,0 +1,64 @@
from ..assembler import ASM
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 upgradeMansion(rom):
rom.patch(0x19, 0x38EC, ASM("""
ld hl, $78DC
jr $03
"""), "", fill_nop=True)
rom.patch(0x19, 0x38F1, ASM("""
ld hl, $78CC
ld c, $04
call $3CE6
"""), ASM("""
ld a, $0C
rst 8
"""), fill_nop=True)
rom.patch(0x19, 0x3718, ASM("sub $13"), ASM("sub $0D"))
rom.patch(0x19, 0x3697, ASM("""
cp $70
jr c, $15
ld [hl], $70
"""), ASM("""
cp $73
jr c, $15
ld [hl], $73
"""))
rom.patch(0x19, 0x36F5, ASM("""
ld a, $02
ld [$DB4E], a
"""), ASM("""
ld a, $0E ; give item and message for current room multiworld
rst 8
"""), fill_nop=True)
rom.patch(0x19, 0x36E6, ASM("""
ld a, $9F
call $2385
"""), "", fill_nop=True)
rom.patch(0x19, 0x31E8, ASM("""
ld a, [$DB4E]
and $02
"""), ASM("""
ld a, [$DAE9]
and $10
"""))

View File

@@ -0,0 +1,152 @@
from ..assembler import ASM
def fixShop(rom):
# Move shield visuals to the 2nd slot, and arrow to 3th slot
rom.patch(0x04, 0x3732 + 22, "986A027FB2B098AC01BAB1", "9867027FB2B098A801BAB1")
rom.patch(0x04, 0x3732 + 55, "986302B1B07F98A4010A09", "986B02B1B07F98AC010A09")
# Just use a fixed location in memory to store which inventory we give.
rom.patch(0x04, 0x37C5, "0708", "0802")
# Patch the code that decides which shop to show.
rom.patch(0x04, 0x3839, 0x388E, ASM("""
push bc
jr skipSubRoutine
checkInventory:
ld hl, $DB00 ; inventory
ld c, INV_SIZE
loop:
cp [hl]
ret z
inc hl
dec c
jr nz, loop
and a
ret
skipSubRoutine:
; Set the shop table to all nothing.
ld hl, $C505
xor a
ldi [hl], a
ldi [hl], a
ldi [hl], a
ldi [hl], a
ld de, $C505
; Check if we want to load a key item into the shop.
ldh a, [$F8]
bit 4, a
jr nz, checkForSecondKeyItem
ld a, $01
ld [de], a
jr checkForShield
checkForSecondKeyItem:
bit 5, a
jr nz, checkForShield
ld a, $05
ld [de], a
checkForShield:
inc de
; Check if we have the shield or the bow to see if we need to remove certain entries from the shop
ld a, [$DB44]
and a
jr z, hasNoShieldLevel
ld a, $03
ld [de], a ; Add shield buy option
hasNoShieldLevel:
inc de
ld a, $05
call checkInventory
jr nz, hasNoBow
ld a, $06
ld [de], a ; Add arrow buy option
hasNoBow:
inc de
ld a, $02
call checkInventory
jr nz, hasNoBombs
ld a, $04
ld [de], a ; Add bomb buy option
hasNoBombs:
pop bc
call $3B12 ; increase entity state
""", 0x7839), fill_nop=True)
# We do not have enough room at the shovel/bow buy entry to handle this
# So jump to a bit where we have some more space to work, as there is some dead code in the shop.
rom.patch(0x04, 0x3AA9, 0x3AAE, ASM("jp $7AC3"), fill_nop=True)
# Patch over the "you stole it" dialog
rom.patch(0x00, 0x1A1C, 0x1A21, ASM("""ld a, $C9
call $2385"""), fill_nop=True)
rom.patch(0x04, 0x3AC3, 0x3AD8, ASM("""
; No room override needed, we're in the proper room
; Call our chest item giving code.
ld a, $0E
rst 8
; Update the room status to mark first item as bought
ld hl, $DAA1
ld a, [hl]
or $10
ld [hl], a
ret
"""), fill_nop=True)
rom.patch(0x04, 0x3A73, 0x3A7E, ASM("jp $7A91"), fill_nop=True)
rom.patch(0x04, 0x3A91, 0x3AA9, ASM("""
; Override the room - luckily nothing will go wrong here if we leave it as is
ld a, $A7
ldh [$F6], a
; Call our chest item giving code.
ld a, $0E
rst 8
; Update the room status to mark second item as bought
ld hl, $DAA1
ld a, [hl]
or $20
ld [hl], a
ret
"""), fill_nop=True)
# Patch shop item graphics rendering to use some new code at the end of the bank.
rom.patch(0x04, 0x3B91, 0x3BAC, ASM("""
call $7FD0
"""), fill_nop=True)
rom.patch(0x04, 0x3BD3, 0x3BE3, ASM("""
jp $7FD0
"""), fill_nop=True)
rom.patch(0x04, 0x3FD0, "00" * 42, ASM("""
; Check if first key item
and a
jr nz, notShovel
ld a, [$77C5]
ldh [$F1], a
ld a, $01
rst 8
ret
notShovel:
cp $04
jr nz, notBow
ld a, [$77C6]
ldh [$F1], a
ld a, $01
rst 8
ret
notBow:
cp $05
jr nz, notArrows
; Load arrow graphics and render then as a dual sprite
ld de, $7B58
call $3BC0
ret
notArrows:
; Load the normal graphics
ld de, $7B5A
jp $3C77
"""), fill_nop=True)

View File

@@ -0,0 +1,93 @@
from ..roomEditor import RoomEditor, Object
from ..assembler import ASM
def fixAll(rom):
# Prevent soft locking in the first mountain cave if we do not have a feather
re = RoomEditor(rom, 0x2B7)
re.removeObject(3, 3)
re.store(rom)
# Prevent getting stuck in the sidescroll room in the beginning of dungeon 5
re = RoomEditor(rom, 0x1A9)
re.objects[6].count = 7
re.store(rom)
# Cave that allows you to escape from D4 without flippers, make it no longer require a feather
re = RoomEditor(rom, 0x1EA)
re.objects[9].count = 8
re.removeObject(5, 4)
re.moveObject(4, 4, 7, 5)
re.store(rom)
# D3 west side room requires feather to get the key. But feather is not required to unlock the door, potentially softlocking you.
re = RoomEditor(rom, 0x155)
re.changeObject(4, 1, 0xcf)
re.changeObject(4, 6, 0xd0)
re.store(rom)
# D3 boots room requires boots to escape
re = RoomEditor(rom, 0x146)
re.removeObject(5, 6)
re.store(rom)
allowRaftGameWithoutFlippers(rom)
# We cannot access thes holes in logic:
# removeBirdKeyHoleDrop(rom)
fixDoghouse(rom)
flameThrowerShieldRequirement(rom)
fixLessThen3MaxHealth(rom)
def fixDoghouse(rom):
# Fix entering the dog house from the back, and ending up out of bounds.
re = RoomEditor(rom, 0x0A1)
re.objects.append(Object(6, 2, 0x0E2))
re.objects.append(re.objects[20]) # Move the flower patch after the warp entry definition so it overrules the tile
re.objects.append(re.objects[3])
re.objects.pop(22)
re.objects.pop(21)
re.objects.pop(20) # Remove the flower patch at the normal entry index
re.objects.pop(11) # Duplicate object, we can just remove it, gives room for our custom entry door
re.store(rom)
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 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 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 fixLessThen3MaxHealth(rom):
# The table that starts your start HP when you die is not working for less then 3 HP, and locks the game.
rom.patch(0x01, 0x1295, "18181818", "08081018")

View File

@@ -0,0 +1,159 @@
from ..assembler import ASM
def upgradeMarin(rom):
# Show marin outside, even without a sword.
rom.patch(0x05, 0x0E78, ASM("ld a, [$DB4E]"), ASM("ld a, $01"), fill_nop=True)
# Make marin ignore the fact that you did not save the tarin yet, and allowing getting her song
rom.patch(0x05, 0x0E87, ASM("ld a, [$D808]"), ASM("ld a, $10"), fill_nop=True)
rom.patch(0x05, 0x0F73, ASM("ld a, [$D808]"), ASM("ld a, $10"), fill_nop=True)
rom.patch(0x05, 0x0FB0, ASM("ld a, [$DB48]"), ASM("ld a, $01"), fill_nop=True)
# Show marin in the animal village
rom.patch(0x03, 0x0A86, ASM("ld a, [$DB74]"), ASM("ld a, $01"), fill_nop=True)
rom.patch(0x05, 0x3F2E, ASM("ld a, [$DB74]"), ASM("ld a, $01"), fill_nop=True) # animal d0
rom.patch(0x15, 0x3F96, ASM("ld a, [$DB74]"), ASM("ld a, $01"), fill_nop=True) # animal d1
rom.patch(0x18, 0x11B0, ASM("ld a, [$DB74]"), ASM("ld a, $01"), fill_nop=True) # animal d2
# Instead of checking if we have the ballad, check if we have a specific room flag set
rom.patch(0x05, 0x0F89, ASM("""
ld a, [$DB49]
and $04
"""), ASM("""
ld a, [$D892]
and $10
"""), fill_nop=True)
rom.patch(0x05, 0x0FDF, ASM("""
ld a, [$DB49]
and $04
"""), ASM("""
ld a, [$D892]
and $10
"""), fill_nop=True)
rom.patch(0x05, 0x1042, ASM("""
ld a, [$DB49]
and $04
"""), ASM("""
ld a, [$D892]
and $10
"""), fill_nop=True)
# Patch that we call our specific handler instead of giving the song
rom.patch(0x05, 0x1170, ASM("""
ld hl, $DB49
set 2, [hl]
xor a
ld [$DB4A], a
"""), ASM("""
; Mark Marin as done.
ld a, [$D892]
or $10
ld [$D892], a
"""), fill_nop=True)
# Show the right item instead of the ocerina
rom.patch(0x05, 0x11B3, ASM("""
ld de, $515F
xor a
ldh [$F1], a
jp $3C77
"""), ASM("""
ld a, $0C
rst 8
ret
"""), fill_nop=True)
# Patch the message that tells we got the song, to give the item and show the right message
rom.patch(0x05, 0x119C, ASM("""
ld a, $13
call $2385
"""), ASM("""
ld a, $0E
rst 8
"""), fill_nop=True)
def upgradeManbo(rom):
# Instead of checking if we have the song, check if we have a specific room flag set
rom.patch(0x18, 0x0536, ASM("""
ld a, [$DB49]
and $02
"""), ASM("""
ld a, [$DAFD]
and $20
"""), fill_nop=True)
# Show the right item instead of the ocerina
rom.patch(0x18, 0x0786, ASM("""
ld de, $474D
xor a
ldh [$F1], a
jp $3C77
"""), ASM("""
ld a, $0C
rst 8
ret
"""), fill_nop=True)
# Patch to replace song giving to give the right item
rom.patch(0x18, 0x0757, ASM("""
ld a, $01
ld [$DB4A], a
ld hl, $DB49
set 1, [hl]
"""), ASM("""
; Mark Manbo as done.
ld hl, $DAFD
set 5, [hl]
; Show item message and give item
ld a, $0E
rst 8
"""), fill_nop=True)
# Remove the normal "got song message")
rom.patch(0x18, 0x076F, 0x0774, "", fill_nop=True)
def upgradeMamu(rom):
# Always allow the sign maze instead of only allowing the sign maze if you do not have song3
rom.patch(0x00, 0x2057, ASM("ld a, [$DB49]"), ASM("ld a, $00"), fill_nop=True)
# Patch the condition at which Mamu gives you the option to listen to him
rom.patch(0x18, 0x0031, ASM("""
ld a, [$DB49]
and $01
"""), ASM("""
ld a, [$DAFB] ; load room flag of the Mamu room
and $10
"""), fill_nop=True)
# Show the right item instead of the ocerina
rom.patch(0x18, 0x0299, ASM("""
ld de, $474D
xor a
ldh [$F1], a
call $3C77
"""), ASM("""
ld a, $0C
rst 8
"""), fill_nop=True)
# Patch given an item
rom.patch(0x18, 0x0270, ASM("""
ld a, $02
ld [$DB4A], a
ld hl, $DB49
set 0, [hl]
"""), ASM("""
; Set the room complete flag.
ld hl, $DAFB
set 4, [hl]
"""), fill_nop=True)
# Patch to show the right message for the item
rom.patch(0x18, 0x0282, ASM("""
ld a, $DF
call $4087
"""), ASM("""
; Give item and message for room.
ld a, $0E
rst 8
"""), fill_nop=True)

View File

@@ -0,0 +1,51 @@
from ..assembler import ASM
from ..utils import formatText
def updateTarin(rom):
# Do not give the shield.
rom.patch(0x05, 0x0CD0, ASM("""
ld d, $04
call $5321
ld a, $01
ld [$DB44], a
"""), "", fill_nop=True)
# Instead of showing the usual "your shield back" message, give the proper message and give the item.
rom.patch(0x05, 0x0CDE, ASM("""
ld a, $91
call $2385
"""), ASM("""
ld a, $0B ; GiveItemAndMessageForRoom
rst 8
"""), fill_nop=True)
rom.patch(0x05, 0x0CF0, ASM("""
xor a
ldh [$F1], a
ld de, $4CC6
call $3C77
"""), ASM("""
ld a, $0C ; RenderItemForRoom
rst 8
xor a
ldh [$F1], a
"""), fill_nop=True)
# Set the room status to finished. (replaces a GBC check)
rom.patch(0x05, 0x0CAB, 0x0CB0, ASM("""
ld a, $20
call $36C4
"""), fill_nop=True)
# Instead of checking for the shield level to put you in the bed, check the room flag.
rom.patch(0x05, 0x1202, ASM("ld a, [$DB44]\nand a"), ASM("ldh a, [$F8]\nand $20"))
rom.patch(0x05, 0x0C6D, ASM("ld a, [$DB44]\nand a"), ASM("ldh a, [$F8]\nand $20"))
# If the starting item is picked up, load the right palette when entering the room
rom.patch(0x21, 0x0176, ASM("ld a, [$DB48]\ncp $01"), ASM("ld a, [$DAA3]\ncp $A1"), fill_nop=True)
rom.patch(0x05, 0x0C94, "FF473152C5280000", "FD2ED911CE100000")
rom.patch(0x05, 0x0CB0, ASM("ld hl, $DC88"), ASM("ld hl, $DC80"))
# Patch the text that Tarin uses to give your shield back.
rom.texts[0x54] = formatText("#####, it is dangerous to go alone!\nTake this!")

View File

@@ -0,0 +1,89 @@
from ..backgroundEditor import BackgroundEditor
import subprocess
import binascii
CHAR_MAP = {'z': 0x3E, '-': 0x3F, '.': 0x39, ':': 0x42, '?': 0x3C, '!': 0x3D}
def _encode(s):
result = bytearray()
for char in s:
if ord("A") <= ord(char) <= ord("Z"):
result.append(ord(char) - ord("A"))
elif ord("a") <= ord(char) <= ord("y"):
result.append(ord(char) - ord("a") + 26)
elif ord("0") <= ord(char) <= ord("9"):
result.append(ord(char) - ord("0") + 0x70)
else:
result.append(CHAR_MAP.get(char, 0x7E))
return result
def setRomInfo(rom, seed, settings, player_name, player_id):
#try:
# version = subprocess.run(['git', 'describe', '--tags', '--dirty=-D'], stdout=subprocess.PIPE).stdout.strip().decode("ascii", "replace")
#except:
# version = ""
try:
seednr = int(seed, 16)
except:
import hashlib
seednr = int(hashlib.md5(seed.encode('ascii', 'replace')).hexdigest(), 16)
if settings.race:
seed = "Race"
if isinstance(settings.race, str):
seed += " " + settings.race
rom.patch(0x00, 0x07, "00", "01")
else:
rom.patch(0x00, 0x07, "00", "52")
line_1_hex = _encode(seed)
#line_2_hex = _encode(seed[16:])
BASE_DRAWING_AREA = 0x98a0
LINE_WIDTH = 0x20
player_id_text = f"Player {player_id}:"
for n in (3, 4):
be = BackgroundEditor(rom, n)
ba = BackgroundEditor(rom, n, attributes=True)
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(line_1_hex):
be.tiles[0x9a20 + n] = v
ba.tiles[0x9a20 + n] = 0x00
for n in range(0x09, 0x14):
be.tiles[0x9820 + n] = 0x7F
be.tiles[0x9840 + n] = 0xA0 + (n % 2)
be.tiles[0x9860 + n] = 0xA2
sn = seednr
for n in range(0x0A, 0x14):
tilenr = sn % 30
sn //= 30
if tilenr > 12:
tilenr += 2
if tilenr > 16:
tilenr += 1
if tilenr > 19:
tilenr += 3
if tilenr > 27:
tilenr += 1
if tilenr > 29:
tilenr += 2
if tilenr > 35:
tilenr += 1
be.tiles[0x9800 + n] = tilenr * 2
be.tiles[0x9820 + n] = tilenr * 2 + 1
pal = sn % 8
sn //= 8
ba.tiles[0x9800 + n] = 0x08 | pal
ba.tiles[0x9820 + n] = 0x08 | pal
be.store(rom)
ba.store(rom)

View File

@@ -0,0 +1,355 @@
from ..assembler import ASM
def patchTradeSequence(rom, boomerang_option):
patchTrendy(rom)
patchPapahlsWife(rom)
patchYipYip(rom)
patchBananasSchule(rom)
patchKiki(rom)
patchTarin(rom)
patchBear(rom)
patchPapahl(rom)
patchGoatMrWrite(rom)
patchGrandmaUlrira(rom)
patchFisherman(rom)
patchMermaid(rom)
patchMermaidStatue(rom)
patchSharedCode(rom)
patchVarious(rom, boomerang_option)
patchInventoryMenu(rom)
def patchTrendy(rom):
# Trendy game yoshi
rom.patch(0x04, 0x3502, 0x350F, ASM("""
ldh a, [$F8] ; room status
and a, $20
jp nz, $6D7A ; clear entity
; Render sprite
ld a, $0F
rst 8
; Reset the sprite variant, else the code gets confused
xor a
ldh [$F1], a ; sprite variant
"""), fill_nop=True)
rom.patch(0x04, 0x2E80, ASM("ldh a, [$F8]"), ASM("ld a, $10")) # Prevent marin cutscene from triggering, as that locks the game now.
rom.patch(0x04, 0x3622, 0x3627, "", fill_nop=True) # Dont set the trade item
def patchPapahlsWife(rom):
# Rewrite how the first dialog is generated.
rom.patch(0x18, 0x0E7A, 0x0EA8, ASM("""
ldh a, [$F8] ; room status
and a, $20
jr nz, tradeDone
ld a, [wTradeSequenceItem]
and $01
jr nz, requestTrade
ld a, $2A ; Dialog about wanting a yoshi doll
jp $2373 ; OpenDialogInTable1
tradeDone:
ld a, $2C ; Dialog about kids, after trade is done
jp $2373 ; OpenDialogInTable1
requestTrade:
ld a, $2B ; Dialog about kids, after trade is done
call $3B12; IncrementEntityState
jp $2373 ; OpenDialogInTable1
"""), fill_nop=True)
rom.patch(0x18, 0x0EB4, 0x0EBD, ASM("ld hl, wTradeSequenceItem\nres 0, [hl]"), fill_nop=True) # Take the trade item
def patchYipYip(rom):
# Change how the decision is made to draw yipyip with a ribbon
rom.patch(0x06, 0x1A2C, 0x1A36, ASM("""
ldh a, [$F8] ; room status
and $20
jr z, tradeNotDone
ld de, $59C8 ; yipyip with ribbon
tradeNotDone:
"""), fill_nop=True)
# Check if we have the ribbon
rom.patch(0x06, 0x1A7C, 0x1A83, ASM("""
ld a, [wTradeSequenceItem]
and $02
jr z, $07
"""), fill_nop=True)
rom.patch(0x06, 0x1AAF, 0x1AB8, ASM("ld hl, wTradeSequenceItem\nres 1, [hl]"), fill_nop=True) # Take the trade item
def patchBananasSchule(rom):
# Change how to check if we have the right trade item
rom.patch(0x19, 0x2D54, 0x2D5B, ASM("""
ld a, [wTradeSequenceItem]
and $04
jr z, $08
"""), fill_nop=True)
rom.patch(0x19, 0x2DF0, 0x2DF9, ASM("ld hl, wTradeSequenceItem\nres 2, [hl]"), fill_nop=True) # Take the trade item
# Change how the decision is made to render less bananas
rom.patch(0x19, 0x2EF1, 0x2EFA, ASM("""
ldh a, [$F8]
and $20
jr z, skip
dec c
dec c
skip: """), fill_nop=True)
# Part of the same entity code, but this is the painter, which changes the dialog depending on mermaid scale or magnifier
rom.patch(0x19, 0x2F95, 0x2F9C, ASM("""
ld a, [wTradeSequenceItem2]
and $10 ; Check for mermaid scale
jr z, $04
"""))
rom.patch(0x19, 0x2FA0, 0x2FA4, ASM("""
and $20 ; Check for magnifier
jr z, $07
"""))
rom.patch(0x19, 0x2CE3, "9A159C15", "B41DB61D") # Properly draw the dog food
def patchKiki(rom):
rom.patch(0x07, 0x18E6, 0x18ED, ASM("""
ld a, [wTradeSequenceItem]
and $08 ; check for banana
jr z, $08
"""))
rom.patch(0x07, 0x19AF, 0x19B4, "", fill_nop=True) # Do not change trading item memory
rom.patch(0x07, 0x19CC, 0x19D5, ASM("ld hl, wTradeSequenceItem\nres 3, [hl]"), fill_nop=True) # Take the trade item
rom.patch(0x07, 0x194D, "9A179C17", "B81FBA1F") # Properly draw the banana above kiki
def patchTarin(rom):
rom.patch(0x07, 0x0EC5, 0x0ECA, ASM("""
ld a, [wTradeSequenceItem]
and $10 ; check for stick
"""))
rom.patch(0x07, 0x0F30, 0x0F33, "", fill_nop=True) # Take the trade item
# Honeycomb, change how we detect that it should fall on entering the room
rom.patch(0x07, 0x0CCC, 0x0CD3, ASM("""
ld a, [$D887]
and $40
jr z, $14
"""))
# Something about tarin changing messages or not showing up depending on the trade sequence
rom.patch(0x05, 0x0BFF, 0x0C07, "", fill_nop=True) # Just ignore the trade sequence
rom.patch(0x05, 0x0D20, 0x0D27, "", fill_nop=True) # Just ignore the trade sequence
rom.patch(0x05, 0x0DAF, 0x0DB8, "", fill_nop=True) # Tarin giving bananas?
rom.patch(0x07, 0x0D6D, 0x0D7A, ASM("ld hl, wTradeSequenceItem\nres 4, [hl]"), fill_nop=True) # Take the trade item
def patchBear(rom):
# Change the trade item check
rom.patch(0x07, 0x0BCC, 0x0BD3, ASM("""
ld a, [wTradeSequenceItem]
and $20 ; check for honeycomb
jr z, $0E
"""))
rom.patch(0x07, 0x0C21, ASM("jr nz, $22"), "", fill_nop=True)
rom.patch(0x07, 0x0C23, 0x0C2A, ASM("""
ld a, [wTradeSequenceItem]
and $20 ; check for honeycomb
jr z, $08
"""))
rom.patch(0x07, 0x0C3C, 0x0C43, ASM("""
nop
nop
nop
nop
nop
jr $02
"""))
rom.patch(0x07, 0x0C5E, 0x0C67, ASM("ld hl, wTradeSequenceItem\nres 5, [hl]"), fill_nop=True) # Take the trade item
def patchPapahl(rom):
rom.patch(0x07, 0x0A21, 0x0A30, ASM("call $7EA4"), fill_nop=True) # Never show indoor papahl
# Render the bag condition
rom.patch(0x07, 0x0A81, 0x0A88, ASM("""
ldh a, [$F8] ; current room status
and $20
nop
jr nz, $18
"""))
# Check for the right item
rom.patch(0x07, 0x0ACF, 0x0AD4, ASM("""
ld a, [wTradeSequenceItem]
and $40 ; pineapple
"""))
rom.patch(0x07, 0x0AD6, ASM("jr z, $02"), ASM("jr nz, $02"))
rom.patch(0x07, 0x0AF9, 0x0B00, ASM("""
ld a, [wTradeSequenceItem]
and $40 ; pineapple
jr z, $0E
"""))
rom.patch(0x07, 0x0B2F, 0x0B38, ASM("ld hl, wTradeSequenceItem\nres 6, [hl]"), fill_nop=True) # Take the trade item
def patchGoatMrWrite(rom): # The goat and mrwrite are the same entity
rom.patch(0x18, 0x0BF1, 0x0BF8, ASM("""
ldh a, [$F8]
and $20
nop
jr nz, $03
""")) # Check if we made the trade with the goat
rom.patch(0x18, 0x0C2C, 0x0C33, ASM("""
ld a, [wTradeSequenceItem]
and $80 ; hibiscus
jr z, $08
""")) # Check if we have the hibiscus
rom.patch(0x18, 0x0C3D, 0x0C41, "", fill_nop=True)
rom.patch(0x18, 0x0C6B, 0x0C74, ASM("ld hl, wTradeSequenceItem\nres 7, [hl]"), fill_nop=True) # Take the trade item for the goat
rom.patch(0x18, 0x0C8B, 0x0C92, ASM("""
ld a, [wTradeSequenceItem2]
and $01 ; letter
jr z, $08
""")) # Check if we have the letter
rom.patch(0x18, 0x0C9C, 0x0CA0, "", fill_nop=True)
rom.patch(0x18, 0x0CE2, 0x0CEB, ASM("ld hl, wTradeSequenceItem2\nres 0, [hl]"), fill_nop=True) # Take the trade item for mrwrite
def patchGrandmaUlrira(rom):
rom.patch(0x18, 0x0D2C, ASM("jr z, $02"), "", fill_nop=True) # Always show up in animal village
rom.patch(0x18, 0x0D3C, 0x0D51, ASM("""
ldh a, [$F8]
and $20
jp nz, $4D58
"""), fill_nop=True)
rom.patch(0x18, 0x0D95, 0x0D9A, "", fill_nop=True)
rom.patch(0x18, 0x0D9C, 0x0DA0, "", fill_nop=True)
rom.patch(0x18, 0x0DA3, 0x0DAA, ASM("""
ld a, [wTradeSequenceItem2]
and $02 ; broom
jr z, $0B
"""))
rom.patch(0x18, 0x0DC4, 0x0DC7, "", fill_nop=True)
rom.patch(0x18, 0x0DE2, 0x0DEB, ASM("ld hl, wTradeSequenceItem2\nres 1, [hl]"), fill_nop=True) # Take the trade item
rom.patch(0x18, 0x0E1D, 0x0E20, "", fill_nop=True)
rom.patch(0x18, 0x0D13, "9A149C14", "D01CD21C")
def patchFisherman(rom):
# Not sure what this first check is for
rom.patch(0x07, 0x02F8, 0x0300, ASM("""
"""), fill_nop=True)
# Check for the hook
rom.patch(0x07, 0x04BF, 0x04C6, ASM("""
ld a, [wTradeSequenceItem2]
and $04 ; hook
jr z, $08
"""))
rom.patch(0x07, 0x04F3, 0x04F6, "", fill_nop=True)
rom.patch(0x07, 0x057D, 0x0586, ASM("ld hl, wTradeSequenceItem2\nres 2, [hl]"), fill_nop=True) # Take the trade item
rom.patch(0x04, 0x1F88, 0x1F8B, "", fill_nop=True)
def patchMermaid(rom):
# Check for the right trade item
rom.patch(0x07, 0x0797, 0x079E, ASM("""
ld a, [wTradeSequenceItem2]
and $08 ; necklace
jr z, $0B
"""))
rom.patch(0x07, 0x0854, 0x085B, ASM("ld hl, wTradeSequenceItem2\nres 3, [hl]"), fill_nop=True) # Take the trade item
def patchMermaidStatue(rom):
rom.patch(0x18, 0x095D, 0x0962, "", fill_nop=True)
rom.patch(0x18, 0x0966, 0x097A, ASM("""
ld a, [wTradeSequenceItem2]
and $10 ; scale
ret z
ldh a, [$F8]
and $20
ret nz
"""), fill_nop=True)
def patchSharedCode(rom):
# Trade item render code override.
rom.patch(0x07, 0x1535, 0x1575, ASM("""
ldh a, [$F9]
and a
jr z, notSideScroll
ldh a, [$EC]; hActiveEntityVisualPosY
add a, $02
ldh [$EC], a
notSideScroll:
; Render sprite
ld a, $0F
rst 8
"""), fill_nop=True)
# Trade item message code
# rom.patch(0x07, 0x159F, 0x15B9, ASM("""
# ld a, $09 ; give message and item (from alt item table)
# rst 8
# """), fill_nop=True)
rom.patch(0x07, 0x159F, 0x15B9, ASM("""
ldh a, [$F6] ; map room
cp $B2
jr nz, NotYipYip
add a, 2 ; Add 2 to room to set room pointer to an empty room for trade items
ldh [$F6], a
ld a, $0e ; giveItemMultiworld
rst 8
ldh a, [$F6] ; map room
sub a, 2 ; ...and undo it
ldh [$F6], a
jr Done
NotYipYip:
ld a, $0e ; giveItemMultiworld
rst 8
Done:
"""), fill_nop=True)
# Prevent changing the 2nd trade item memory
rom.patch(0x07, 0x15BD, 0x15C1, ASM("""
call $7F7F
xor a ; we need to exit with A=00
"""), fill_nop=True)
rom.patch(0x07, 0x3F7F, "00" * 7, ASM("ldh a, [$F8]\nor $20\nldh [$F8], a\nret"))
def patchVarious(rom, boomerang_option):
# Make the zora photo work with the magnifier
rom.patch(0x18, 0x09F3, 0x0A02, ASM("""
ld a, [wTradeSequenceItem2]
and $20 ; MAGNIFYING_GLASS
jp z, $7F08 ; ClearEntityStatusBank18
"""), fill_nop=True)
rom.patch(0x03, 0x0B6D, 0x0B75, ASM("""
ld a, [wTradeSequenceItem2]
and $20 ; MAGNIFYING_GLASS
jp z, $3F8D ; UnloadEntity
"""), fill_nop=True)
# Mimic invisibility
rom.patch(0x18, 0x2AC8, 0x2ACE, "", fill_nop=True)
# Ignore trade quest state for marin at beach
rom.patch(0x18, 0x219E, 0x21A6, "", fill_nop=True)
# Shift the magnifier 8 pixels
rom.patch(0x03, 0x0F68, 0x0F6F, ASM("""
ldh a, [$F6] ; map room
cp $97 ; check if we are in the maginfier room
jp z, $4F83
"""), fill_nop=True)
# Something with the photographer
rom.patch(0x36, 0x0948, 0x0950, "", fill_nop=True)
if boomerang_option not in {'trade', 'gift'}: # Boomerang cave is not patched, so adjust it
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
rom.patch(0x19, 0x05F4, 0x05FB, "", fill_nop=True)
def patchInventoryMenu(rom):
# Never draw the trade item the normal way
rom.patch(0x20, 0x1A2E, ASM("ld a, [wTradeSequenceItem2]\nand a\njr nz, $23"), ASM("jp $5A57"), fill_nop=True)
rom.patch(0x20, 0x1EB5, ASM("ldh a, [$FE]\nand a\njr z, $34"), ASM("ld a, $10\nrst 8"), fill_nop=True)

View File

@@ -0,0 +1,3 @@
def fixTrendy(rom):
rom.patch(0x04, 0x2F29, "04", "02") # Patch the trendy game shield to be a ruppee

View File

@@ -0,0 +1,45 @@
from ..utils import formatText
from ..assembler import ASM
def upgradeTunicFairy(rom):
rom.texts[0x268] = formatText("Welcome, #####. I admire you for coming this far.")
rom.texts[0x0CC] = formatText("Got the {RED_TUNIC}! You can change Tunics at the phone booths.")
rom.texts[0x0CD] = formatText("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, $04
ldh [$F6], a
ld a, $0E
rst 8
"""), fill_nop=True)
rom.patch(0x36, 0x1162, 0x1192, ASM("""
ld a, $01
ldh [$F6], a
ld a, $0E
rst 8
"""), 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

View File

@@ -0,0 +1,64 @@
from ..assembler import ASM
from ..roomEditor import RoomEditor
def patchSuperWeapons(rom):
# Feather jump height
rom.patch(0x00, 0x1508, ASM("ld a, $20"), ASM("ld a, $2C"))
# Boots charge speed
rom.patch(0x00, 0x1731, ASM("cp $20"), ASM("cp $01"))
# Power bracelet pickup speed
rom.patch(0x00, 0x2121, ASM("ld e, $08"), ASM("ld e, $01"))
# Throwing speed (of pickups and bombs)
rom.patch(0x14, 0x1313, "30D0000018E80000", "60A0000040C00000")
rom.patch(0x14, 0x1323, "0000D0300000E818", "0000A0600000C040")
# Allow as many bombs to be placed as you want!
rom.patch(0x00, 0x135F, ASM("ret nc"), "", fill_nop=True)
# Maximum amount of arrows in the air
rom.patch(0x00, 0x13C5, ASM("cp $02"), ASM("cp $05"))
# Delay between arrow shots
rom.patch(0x00, 0x13C9, ASM("ld a, $10"), ASM("ld a, $01"))
# Maximum amount of firerod fires
rom.patch(0x00, 0x12E4, ASM("cp $02"), ASM("cp $05"))
# Projectile speed (arrows, firerod)
rom.patch(0x00, 0x13AD,
"30D0000040C00000" "0000D0300000C040",
"60A0000060A00000" "0000A0600000A060")
# Hookshot shoot speed
rom.patch(0x02, 0x024C,
"30D00000" "0000D030",
"60A00000" "0000A060")
# Hookshot retract speed
rom.patch(0x18, 0x3C41, ASM("ld a, $30"), ASM("ld a, $60"))
# Hookshot pull speed
rom.patch(0x18, 0x3C21, ASM("ld a, $30"), ASM("ld a, $60"))
# Super shovel, always price!
rom.patch(0x02, 0x0CC6, ASM("jr nz, $57"), "", fill_nop=True)
# Unlimited boomerangs!
rom.patch(0x00, 0x1387, ASM("ret nz"), "", fill_nop=True)
# Increase shield push power
rom.patch(0x03, 0x2FC5, ASM("ld a, $08"), ASM("ld a, $10"))
rom.patch(0x03, 0x2FCA, ASM("ld a, $20"), ASM("ld a, $40"))
# Decrease link pushback of shield
rom.patch(0x03, 0x2FB9, ASM("ld a, $12"), ASM("ld a, $04"))
rom.patch(0x03, 0x2F9A, ASM("ld a, $0C"), ASM("ld a, $03"))
# Super charge the ocarina
rom.patch(0x02, 0x0AD8, ASM("cp $38"), ASM("cp $08"))
rom.patch(0x02, 0x0B05, ASM("cp $14"), ASM("cp $04"))
re = RoomEditor(rom, 0x23D)
tiles = re.getTileArray()
tiles[11] = 0x0D
tiles[12] = 0xA7
tiles[22] = 0x98
re.buildObjectList(tiles)
re.store(rom)

View File

@@ -0,0 +1,58 @@
from ..assembler import ASM
from ..roomEditor import RoomEditor
def updateWitch(rom):
# Add a heartpiece at the toadstool, the item patches turn this into a 1 time toadstool item
# Or depending on flags, in something else.
re = RoomEditor(rom, 0x050)
re.addEntity(2, 3, 0x35)
re.store(rom)
# Change what happens when you trade the toadstool with the witch
# Note that the 2nd byte of this code gets patched with the item to give from the witch.
rom.patch(0x05, 0x08D4, 0x08F0, ASM("""
; Get the room flags and mark the witch as done.
ld hl, $DAA2
ld a, [hl]
and $30
set 4, [hl]
set 5, [hl]
jr z, item
powder:
ld e, $09 ; give powder every time after the first time.
ld a, e
ldh [$F1], a
ld a, $11
rst 8
jp $48F0
item:
ld a, $0E
rst 8
"""), fill_nop=True)
# Patch the toadstool to unload when you haven't delivered something to the witch yet.
rom.patch(0x03, 0x1D4B, ASM("""
ld hl, $DB4B
ld a, [$DB4C]
or [hl]
jp nz, $3F8D
"""), ASM("""
ld a, [$DAA2]
and $20
jp z, $3F8D
"""), fill_nop=True)
# Patch what happens when we pickup the toadstool, call our chest code to give a toadstool.
rom.patch(0x03, 0x1D6F, 0x1D7D, ASM("""
ld a, $50
ldh [$F1], a
ld a, $02 ; give item
rst 8
ld hl, $DAA2
res 5, [hl]
"""), fill_nop=True)
def witchIsPatched(rom):
return sum(rom.banks[0x05][0x08D4:0x08F0]) != 0x0DC2