229 lines
7.0 KiB
Python
229 lines
7.0 KiB
Python
import os
|
|
import binascii
|
|
from ..assembler import ASM
|
|
from ..utils import formatText
|
|
|
|
import pkgutil
|
|
|
|
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
|
|
"""))
|
|
|
|
def get_asm(name):
|
|
return pkgutil.get_data(__name__, os.path.join("bank3e.asm", name)).decode().replace("\r", "")
|
|
|
|
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
|
|
""" + get_asm("multiworld.asm")
|
|
+ get_asm("link.asm")
|
|
+ get_asm("chest.asm")
|
|
+ get_asm("bowwow.asm")
|
|
+ get_asm("message.asm")
|
|
+ get_asm("itemnames.asm")
|
|
+ "".join(generate_name(["The Server"] + player_name_list, i ) for i in range(100)) # allocate
|
|
+ 'db "another world", $ff\n'
|
|
+ get_asm("owl.asm"), 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"))
|