Add files

This commit is contained in:
ISSOtm
2018-10-19 00:45:50 +02:00
parent f7afa5a932
commit ad3623c9d2
30 changed files with 3399 additions and 0 deletions

111
src/home/header.asm Normal file
View File

@@ -0,0 +1,111 @@
NB_ROM_BANKS = 2
SECTION "Header", ROM0[$100]
EntryPoint::
ld b, $60
jr LogoFade
dbr 0, $150 - $104
; Header ends here
LogoFade:
xor a
ldh [rAUDENA], a
.fadeLogo
ld c, 7 ; Number of frames between each logo fade step
.logoWait
ld a, [rLY]
cp a, SCRN_Y
jr nc, .logoWait
.waitVBlank
ld a, [rLY]
cp a, SCRN_Y
jr c, .waitVBlank
dec c
jr nz, .logoWait
; Shift all colors (fading the logo progressively)
ld a, b
rra
rra
and $FC ; Ensures a proper rotation and sets Z for final check
ldh [rBGP], a
ld b, a
jr nz, .fadeLogo ; End if the palette is fully blank (flag set from `and $FC`)
; xor a
ldh [rDIV], a
Reset::
di
ld sp, wStackBottom
xor a
ldh [rAUDENA], a
.waitVBlank
ld a, [rLY]
cp SCRN_Y
jr c, .waitVBlank
xor a
ldh [rLCDC], a
; Perform some init
; xor a
ldh [rAUDENA], a
; Init HRAM
; Also clears IE, but we're gonna overwrite it just after
ld c, LOW(hClearStart)
.clearHRAM
xor a
ld [$ff00+c], a
inc c
jr nz, .clearHRAM
; Copy OAM DMA routine
ld hl, OAMDMA
lb bc, OAMDMAEnd - OAMDMA, LOW(hOAMDMA)
.copyOAMDMA
ld a, [hli]
ld [$ff00+c], a
inc c
dec b
jr nz, .copyOAMDMA
ld a, LOW(hScanlineFXBuffer1)
ldh [hWhichScanlineBuffer], a
ld a, $FF
ldh [hScanlineFXBuffer1], a
ld a, STATF_LYC
ldh [rSTAT], a
ld a, LCDCF_ON | LCDCF_BGON
ldh [hLCDC], a
ldh [rLCDC], a
ld a, IEF_VBLANK | IEF_LCDC
ldh [rIE], a
xor a
ei ; Delayed until the next instruction: perfectly safe!
ldh [rIF], a
; Init code goes here
SECTION "OAM DMA routine", ROM0
OAMDMA:
ldh [rDMA], a
ld a, $28
.wait
dec a
jr nz, .wait
ret
OAMDMAEnd:

221
src/home/raster_fx.asm Normal file
View File

@@ -0,0 +1,221 @@
SECTION "Raster fx helper functions", ROM0
; Get a pointer to the currently free scanline buffer
; @return a The pointer
; @return c The pointer
; @destroys a c
GetFreeScanlineBuf::
ldh a, [hWhichScanlineBuffer]
xor LOW(hScanlineFXBuffer2) ^ LOW(hScanlineFXBuffer1)
ld c, a
ret
; Switches to the currently free scanline buffer
; @return c A pointer to the newly freed buffer
; @return b A pointer to the newly used buffer
; @destroys a c
SwitchScanlineBuf::
call GetFreeScanlineBuf
ldh [hWhichScanlineBuffer], a
ld b, a
xor LOW(hScanlineFXBuffer2) ^ LOW(hScanlineFXBuffer1)
ld c, a
ret
; Switches to the currently free scanline buffer, and copies it over to the other buffer
; @destroys a c hl
SwitchAndCopyScanlineBuf::
call SwitchScanlineBuf
ld l, b
ld h, HIGH(hScanlineFXBuffer1)
.loop
ld a, [hli]
ld [$ff00+c], a
inc c
inc a
jr nz, .loop
ret
; Finds the effect applied to a given scanline
; @param b The scanline being looked for
; @return Zero Set if the lookup succeeded
; @return c A pointer to the effect's scanline, otherwise to the terminating $FF byte
; @destroys a c
GetFXByScanline::
call GetFreeScanlineBuf
.lookup
ld a, [$ff00+c]
cp b
ret nc ; Return if scanline was greater than or equal
inc c
inc c
inc c
jr .lookup
; Insert effect into the free buffer at the given scanline
; The effect's scanline will have been set, and the other two values will be zero'd
; @param b The scanline to insert the effect at
; @return c A pointer to the new effect's port address
; @return Zero Set if the scanline is already taken (in which case c still holds the correct value)
; @destroys a bc hl
InsertFX::
call GetFreeScanlineBuf
ld l, c
ld h, HIGH(hScanlineFXBuffer1)
scf ; Don't skip the check
.lookForEnd
ld a, [hli]
inc l
; C is inherited from previous `cp b`
jr nc, .skip ; If we're already greater then the scanline, skip this
cp b
ret z ; End now if the scanline is already taken, because two FX can't be on the same line
jr c, .skip ; Skip if we aren't the first greater than the scanline
ld c, l ; Make c point to the target's value
.skip
inc l
inc a
jr nz, .lookForEnd
; Write new terminator ($FF)
ld [hl], h ; Bet you didn't expect this opcode to ever be used, eh?
dec l
.copy
dec l
dec l
dec l
ld a, [hli]
inc l
inc l
ld [hld], a ; If we just copied the target scanline,
ld a, l ; this points to the value
cp c ; which we know the address of!
jr nz, .copy
; Move the pointer and init the new fx
xor a
ld [hld], a
ld [hld], a
ld [hl], b ; Write the desired scanline
inc a ; Don't have Z set
ret
; Remove effect from the free buffer
; @param b The targeted effect's scanline
; @return Zero Set if the lookup succeeded
; @destroys a c hl
RemoveFX::
call GetFXByScanline
ret nz
ld l, c
ld h, HIGH(hScanlineFXBuffer1)
inc c
inc c
inc c
.copy
; Copy scanline
ld a, [$ff00+c]
ld [hli], a
inc a
ret z ; End if we copied the terminator
inc c
; Copy port address
ld a, [$ff00+c]
ld [hli], a
inc c
; Copy value
ld a, [$ff00+c]
ld [hli], a
inc c
jr .copy
; Add the textbox raster FX
; Caution: overwrites the currently free fx buffer with the active (+textbox)
; @param b The number of pixels of the textbox to display (0 closes it)
; @destroys a bc de hl
SetUpTextbox::
ld h, HIGH(hWhichScanlineBuffer)
; Check if backup operations should be performed
ldh a, [hIsTextboxActive]
and a
jr nz, .dontBackup
ld a, b
and a
ret z ; Do nothing if the textbox is closed while it is closed (lol)
ldh a, [hWhichScanlineBuffer]
ld c, a
ld l, LOW(hBackupScanlineFXBuffer)
.backup
ld a, [$ff00+c]
inc c
ld [hli], a
inc a
jr nz, .backup
inc a ; ld a, 1
ldh [hIsTextboxActive], a
; Fall through, but this won't get executed
.dontBackup
ld a, b
and a
jr z, .restoreAndQuit
; Get pointers to buffers
ldh a, [hWhichScanlineBuffer]
ld c, a
xor LOW(hScanlineFXBuffer2) ^ LOW(hScanlineFXBuffer1)
ld l, a
; Calculate scanline
ld a, SCRN_Y
sub b
ld b, a
scf
.copy
ld a, [$ff00+c] ; Get scanline
cp b
jr nc, .insertTextbox
inc c
ld [hl], a
ld a, [$ff00+c] ; Read port
inc c
inc c ; Skip value, jic
and a ; Are we copying a textbox FX?
jr z, .copy ; Abort
inc l
ld [hli], a
dec c
ld a, [$ff00+c] ; Read value
inc c
ld [hli], a
jr .copy
.restoreAndQuit
call GetFreeScanlineBuf
.restore
ld a, [hli]
ld [$ff00+c], a
inc c
inc a
jr nz, .restore
; a = 0
ldh [hIsTextboxActive], a
ret
.insertTextbox
; Place the textbox FX
ld [hl], b
inc l
ld [hl], 0
inc l
ld a, b
sub SCRN_Y
ld [hli], a
ld [hl], $FF ; Don't forget to terminate!
ldh a, [hWhichScanlineBuffer]
xor LOW(hScanlineFXBuffer2) ^ LOW(hScanlineFXBuffer1)
ldh [hWhichScanlineBuffer], a
ret

130
src/home/sgb.asm Normal file
View File

@@ -0,0 +1,130 @@
SECTION "SGB routines", ROM0
; Sends SGB packets to the SGB, assuming we're running on one.
; @param hl Pointer to the packet data to be sent (can send any number of packets btw)
; @return hl Points to the end of the packet data
; @return de Zero
; @return bc Zero
; @return a Zero
SendPackets::
ld a, [hl] ; Length
and %111
ret z
ld c, a
.sendPacket
call SendPacketNoDelay
call SGBDelay ; Let the ICD chip rest a bit
dec c
jr nz, .sendPacket
ret
; Sends a SGB packet to the SGB to freeze the screen, assuming we're running on one.
; Does not perform any delay after sending the packet.
; Use only if you're not going to send another SGB packet in the next few frames.
; You're likely to perform some decompression or smth after this
; @param hl Pointer to the packet data to be sent.
; @return hl Points to the end of the packet data
; @return b Zero
; @return d Zero
; @return a $30
; @destroy e
FreezeSGBScreen::
ld hl, FreezeScreenPacket
; Sends a SGB packet to the SGB, assuming it's running on one.
; Does not perform any delay after sending the packet.
; Unsuitable to send multi-packet packets.
; Use only if you're not going to send another SGB packet in the next four frames.
; Assumes the joypad won't be polled by interrupts during this time, but since the VBlank handler doesn't poll except when waited for, this is fine.
; @param hl Pointer to the packet data to be sent.
; @return hl Points to the end of the packet data
; @return b Zero
; @return d Zero
; @return a $30
; @destroy e
SendPacketNoDelay::
; Packet transmission begins by sending $00 then $30
xor a
ldh [rP1], a
ld a, $30
ldh [rP1], a
ld b, SGB_PACKET_SIZE
.sendByte
ld d, 8 ; 8 bits in a byte
ld a, [hli] ; Read byte to send
ld e, a
.sendBit
ld a, $10 ; 1 bits are sent with $10
rr e ; Rotate d and get its lower bit, two birds in one stone!
jr c, .bitSet
add a, a ; 0 bits are sent with $20
.bitSet
ldh [rP1], a
ld a, $30 ; Terminate pulse
ldh [rP1], a
dec d
jr nz, .sendBit
dec b
jr nz, .sendByte
; Packets are terminated by a "STOP" 0 bit
ld a, $20
ldh [rP1], a
ld a, $30
ldh [rP1], a
ret
SGBDelay::
ld de, 7000 ; Magic value, apparently
.loop
nop
nop
nop
dec de
ld a, d
or e
jr nz, .loop
ret
FreezeScreenPacket:
sgb_packet MASK_EN, 1, 1
; Fill the $9C00 tilemap with a pattern suitable for SGB _TRN
; Also sets up the rendering parameters for the transfer
; Finally, assumes the LCD is **off**
; @return hl
FillScreenWithSGBMap::
xor a
ldh [hSCY], a
ldh [hSCX], a
ld b, a ; ld b, 0
ld hl, $9C00
.writeRow
ld c, SCRN_X_B
.writeTile
ld a, b
ld [hli], a
inc b
jr z, .done
dec c
jr nz, .writeTile
ld a, l
add a, SCRN_VX_B - SCRN_X_B
ld l, a
jr nc, .writeRow
inc h
jr .writeRow
.done
ld a, %11100100
ldh [hBGP], a
SetupSGBLCDC::
ld a, LCDCF_ON | LCDCF_WINOFF | LCDCF_BG8000 | LCDCF_BG9C00 | LCDCF_OBJOFF | LCDCF_BGON
ldh [rLCDC], a
ldh [hLCDC], a
ret

89
src/home/unpb16.asm Normal file
View File

@@ -0,0 +1,89 @@
;
; PB16 decompression for Game Boy
; Copyright 2018 Damian Yerrick
;
; This software is provided 'as-is', without any express or implied
; warranty. In no event will the authors be held liable for any damages
; arising from the use of this software.
;
; Permission is granted to anyone to use this software for any purpose,
; including commercial applications, and to alter it and redistribute it
; freely, subject to the following restrictions:
;
; 1. The origin of this software must not be misrepresented; you must not
; claim that you wrote the original software. If you use this software
; in a product, an acknowledgment in the product documentation would be
; appreciated but is not required.
; 2. Altered source versions must be plainly marked as such, and must not be
; misrepresented as being the original software.
; 3. This notice may not be removed or altered from any source distribution.
;
; This software has been modified by Eldred Habert (ISSOtm):
; - Removal of .inc file
; - Removal of variable allocation
section "pb16", ROM0
; The PB16 format is a starting point toward efficient RLE image
; codecs on Game Boy and Super NES.
;
; 0: Load a literal byte
; 1: Repeat from 2 bytes ago
pb16_unpack_packet:
; Read first bit of control byte. Treat B as a ring counter with
; a 1 bit as the sentinel. Once the 1 bit reaches carry, B will
; become 0, meaning the 8-byte packet is complete.
ld a,[de]
inc de
scf
rla
ld b,a
.byteloop:
; If the bit from the control byte is clear, plane 0 is is literal
jr nc,.p0_is_literal
ldh a,[pb16_byte0]
jr .have_p0
.p0_is_literal:
ld a,[de]
inc de
ldh [pb16_byte0],a
.have_p0:
ld [hl+],a
; Read next bit. If it's clear, plane 1 is is literal.
ld a,c
sla b
jr c,.have_p1
.p1_is_copy:
ld a,[de]
inc de
ld c,a
.have_p1:
ld [hl+],a
; Read next bit of control byte
sla b
jr nz,.byteloop
ret
;;
; Unpacks 2*B packets from DE to HL, producing 8 bytes per packet.
; About 127 cycles (2 scanlines) per 8-byte packet; filling CHR RAM
; thus takes (6144/8)*127 = about 97536 cycles or 93 ms
pb16_unpack_block::
; Prefill with zeroes
xor a
ldh [pb16_byte0],a
ld c,a
.packetloop:
push bc
call pb16_unpack_packet
call pb16_unpack_packet
ld a,c
pop bc
ld c,a
dec b
jr nz,.packetloop
ret

192
src/home/utils.asm Normal file
View File

@@ -0,0 +1,192 @@
; Since all these functions are independent, declare each of them in individual sections
; Lets the linker place them more liberally. Hooray!
f: MACRO
PURGE \1
SECTION "Utility function \1", ROM0
\1::
ENDM
SECTION "Dummy section", ROM0 ; To have the first `Memcpy` declare properly
; Copies bc bytes of data from de to hl
Memcpy::
f Memcpy
ld a, [de]
ld [hli], a
inc de
dec bc
ld a, b
or c
jr nz, Memcpy
ret
; Copies a null-terminated string from de to hl, including the terminating NUL
Strcpy::
f Strcpy
ld a, [de]
ld [hli], a
inc de
and a
jr nz, Strcpy
ret
; Copies c bytes of data from de to hl in a LCD-safe manner
LCDMemcpySmall::
f LCDMemcpySmall
ldh a, [rSTAT]
and STATF_BUSY
jr nz, LCDMemcpySmall
ld a, [de]
ld [hli], a
inc de
dec c
jr nz, LCDMemcpySmall
ret
; Copies bc bytes of data from de to hl in a LCD-safe manner
LCDMemcpy::
f LCDMemcpy
ldh a, [rSTAT]
and STATF_BUSY
jr nz, LCDMemcpy
ld a, [de]
ld [hli], a
inc de
dec bc
ld a, b
or c
jr nz, LCDMemcpy
ret
; Sets c bytes of data at hl with the value in a
LCDMemsetSmall::
f LCDMemsetSmall
ld b, a
; Sets c bytes of data at hl with the value in b
LCDMemsetSmallFromB::
; No f (...) because there's the slide-in above
.loop
ldh a, [rSTAT]
and STATF_BUSY
jr nz, .loop
ld a, b
ld [hli], a
dec c
jr nz, .loop
ret
; Sets bc bytes of data at hl with the value in a
LCDMemset::
f LCDMemset
ld d, a
; Sets bc bytes of data at hl with the value in d
LCDMemsetFromD::
; No f (...) because of the slide-in above
.loop
ldh a, [rSTAT]
and STATF_BUSY
jr nz, .loop
ld a, d
ld [hli], a
dec bc
ld a, b
or c
jr nz, .loop
ret
; Opens SRAM at some bank
; @param a The bank's number
; @return a CART_RAM_ENABLE, ie. $0A
GetSRAMBank::
f GetSRAMBank
ld [rRAMB], a
ld a, CART_RAM_ENABLE
ld [rRAMG], a
ret
; Closes SRAM
; @return hl = rRAMB (I know, it sounds stupid)
CloseSRAM::
f CloseSRAM
; Implementation note: MUST preserve the Z flag to avoid breaking the call to `PrintSRAMFailure`
ld hl, rRAMG
ld [hl], l ; ld [hl], 0
ld h, HIGH(rRAMB)
ld [hl], l ; Avoid unintentional unlocks corrupting saved data, switch to bank 0 (which is scratch)
ret
; Gets the Nth struct in an array of 'em
; @param hl Array base
; @param bc Size of a struct
; @param a ID of the desired struct
; @return hl Pointer to the struct's base
; @destroys a
GetNthStruct::
f GetNthStruct
and a
ret z
.next
add hl, bc
dec a
jr nz, .next
ret
; Copies tiles into VRAM, using an unrolled loop to go faster
; @param hl Destination (pointer)
; @param de Source
; @param c Number of tiles
; @return hl, de Pointer to end of blocks
; @return c 0
; @destroys a
Tilecpy::
f Tilecpy
REPT $10 / 2
wait_vram
ld a, [de]
ld [hli], a
inc de
ld a, [de]
ld [hli], a
inc de
ENDR
dec c
jr nz, Tilecpy
ret
; Copies a tilemap to VRAM, assuming the LCD is off
; @param hl Destination
; @param de Source
; @return hl, de Pointer to end of blocks
; @return bc Zero
; @return a Equal to h
Mapcpy::
f Mapcpy
ld b, SCRN_Y_B
.copyRow
ld c, SCRN_X_B
.copyTile
ld a, [de]
ld [hli], a
inc de
dec c
jr nz, .copyTile
ld a, l
add a, SCRN_VX_B - SCRN_X_B
ld l, a
adc a, h
sub l
ld h, a
dec b
jr nz, .copyRow
ret
PURGE f

307
src/home/vectors.asm Normal file
View File

@@ -0,0 +1,307 @@
SECTION "rst00", ROM0[$0000]
; Please do not call
; Traps execution errors (mostly to $FFFF / $0000)
rst00:
; Pad, in case we come from FFFF and read a 2-byte operand
nop
nop
jp NullExecError
SECTION "rst08", ROM0[$0008]
; Please call using `rst memcpy_small`
; Copies c bytes of data from de to hl
MemcpySmall:
ld a, [de]
ld [hli], a
inc de
dec c
jr nz, MemcpySmall
EmptyFunc::
ret
SECTION "rst10", ROM0[$0010]
; Please call using `rst memset_small`
; Sets c bytes at hl with the value in a
MemsetSmall:
ld [hli], a
dec c
jr nz, MemsetSmall
ret
SECTION "rst18", ROM0[$0017]
; Please do not call. Use `rst memset`, or, if absolutely needed, `call rst18`.
; Sets bc bytes at hl with the value in d
Memset:
ld a, d
; Please call using `rst memset`
; Sets bc bytes at hl with the value in a
rst18:
ld d, a
ld [hli], a
dec bc
ld a, b
or c
jr nz, Memset
ret
SECTION "rst20", ROM0[$0020]
; Please call using `rst bankswitch`
; Properly switches to a ROM bank
; @param a The ROM bank to switch to
; NOTE: only switches the lower 8 bytes, the upper bit (for 512 banks) is not considered
ROMbankswitch:
ldh [hCurROMBank], a
ld [rROMB0], a
ret
SECTION "rst28", ROM0[$0028]
; Please call using `rst call_hl`
; Jumps to hl. Use as a placeholder for `call hl`!
; Will error out if the target is in RAM
CallHL:
bit 7, h ; Prevent jumping into RAM (doesn't protec against returning to it, but hey :D)
jr nz, .err
jp hl
.err
jp HLJumpingError
SECTION "rst30", ROM0[$0030]
; Please call using `rst wait_vblank`
; Waits for the VBlank interrupt
; Note: if the interrupt occurs without being waited for, it will skip performing some actions
WaitVBlank:
xor a
ldh [hVBlankFlag], a
.waitVBlank
halt
jr z, .waitVBlank
ret
SECTION "rst38", ROM0[$0038]
; Please do not call
; Traps execution of the $FF byte (which serves as padding of the ROM)
rst38:
jp Rst38Error
SECTION "Interrupt vectors", ROM0[$0040]
transfer_reg: MACRO
ldh a, [h\1]
ldh [r\1], a
ENDM
; VBlank
push af
transfer_reg LCDC
jp VBlankHandler
; LCD
push af
push bc
ldh a, [hScanlineFXIndex]
ld c, a
ld a, [c] ; Get port ID
jr LCDHandler
; Timer
reti
ds 7
; Serial
reti
; Fit in a 7-byte function, too!
; Jumps immediately to de, no questions asked (except RAM targets?).
CallDE::
push de
bit 7, d
ret z
jp DEJumpingError
; Joypad
reti
LCDHandler:
ld b, a ; Save port ID for later
inc c
inc c
ld a, [c] ; Get next effect's scanline
dec a ; Compensate for processing time
ldh [rLYC], a ; Get set up (hopefully this should reset the interrupt trigger line)
ld a, c ; Point to next effect's port ID
inc a
ldh [hScanlineFXIndex], a
dec c
; Wait a bit to write during HBlank, to avoid gfx artifacts
ld a, 4
.waitMode0
dec a
jr nz, .waitMode0
; Check if we're trying to write to P1 ($FF*00*)
ld a, b
and a ; Note: `and $7F` can be used instead to have control on bit 7 (if ever needed)
; Perform common ops
ld a, [c] ; Get value
; rP1 is hardwired to instead perform textbox ops
jr nz, .notTextbox
ldh [rSCY], a ; Store value, which is actually for SCY (dat plot twist, eh?)
xor a
ldh [rSCX], a
ld c, LOW(rLCDC)
ldh a, [hLCDC] ; Retrieve LCDC value
and ~(LCDCF_WINON | LCDCF_BG8000 | LCDCF_OBJON)
or LCDCF_BG9C00
; Note: this is scrapped support for sprites on the textbox
; It was initially planned for JP diacritics.
; If for whatever reason, you need to re-activate this feature...
; ...uncomment this, and remove "LCDCF_OBJON" from above.
;
; ld [c], a ; Apply LCDC modification
; ; Perform OAM DMA to get textbox's sprites
; ; Luckily, sprites are hidden during DMA
; ; Also no sprites should be present on the textbox 1st row, hiding our trickery >:P
; ld a, HIGH(wTextboxOAM)
; call hOAMDMA
; ; Reload OAM on next frame
; ldh a, [hCurrentOAMBuffer]
; ldh [hOAMBuffer], a
; jr .onlyOneEffect
.notTextbox
ld c, b ; Retrieve port
res 7, c
ld [c], a ; Apply FX
bit 7, b
jr z, .onlyOneEffect
ldh a, [hSecondFXAddr]
ld c, a
ldh a, [hSecondFXValue]
ld [$ff00+c], a
.onlyOneEffect
pop bc
pop af
reti
SECTION "VBlank handler", ROM0
VBlankHandler:
push bc
; ============= Here are things that need to be updated, even on lag frames ==============
; Update IO from HRAM shadow
transfer_reg SCY
transfer_reg SCX
transfer_reg WY
transfer_reg WX
ldh a, [hWhichScanlineBuffer]
ld c, a
; Get first effect's scanline
ld a, [$ff00+c]
dec a ; Compensate for the processing time
; NOTE: this assumes no effect is scheduled on line 0
; This should never happen; instead, use the HRAM shadow regs (hSCY, etc.)
ldh [rLYC], a
inc c
ld a, c
ldh [hScanlineFXIndex], a
; Update OAM if needed
; Do this last so it will go through even without time
; This will simply cause sprites to not be displayed on the top few scanlines, but that's not as bad as palettes not loading at all, huh?
ldh a, [hOAMBufferHigh]
and a
jr z, .dontUpdateOAM
ld b, a
; Reset OAM buffer high vect
xor a
ldh [hOAMBufferHigh], a
; Perform DMA as specified
ld a, b
call hOAMDMA
.dontUpdateOAM
; ============== In case of lag, don't update further, to avoid breaking stuff ===============
ldh a, [hVBlankFlag]
and a
jr nz, .lagFrame
; Poll joypad and update regs
ld c, LOW(rP1)
ld a, $20 ; Select D-pad
ld [$ff00+c], a
REPT 6
ld a, [$ff00+c]
ENDR
or $F0 ; Set 4 upper bits (give them consistency)
ld b, a
; Filter impossible D-pad combinations
and $0C ; Filter only Down and Up
ld a, b
jr nz, .notUpAndDown
or $0C ; If both are pressed, "unpress" them
ld b, a
.notUpAndDown
and $03 ; Filter only Left and Right
jr nz, .notLeftAndRight
ld a, b
or $03 ; If both are pressed, "unpress" them
ld b, a
.notLeftAndRight
swap b ; Put D-pad buttons in upper nibble
ld a, $10 ; Select buttons
ld [$ff00+c], a
REPT 6
ld a, [$ff00+c]
ENDR
; On SsAB held, soft-reset
and $0F
jp z, Reset
or $F0 ; Set 4 upper bits
xor b ; Mix with D-pad bits, and invert all bits (such that pressed=1) thanks to "or $F0"
ld b, a
ldh a, [hHeldButtons]
cpl
and b
ldh [hPressedButtons], a
ld a, b
ldh [hHeldButtons], a
; Release joypad
ld a, $30
ld [$ff00+c], a
pop bc
pop af
; The main code was waiting for VBlank, so tell it it's OK by resetting Z
xor a
inc a ; Clear Z
ldh [hVBlankFlag], a ; Mark VBlank as ACK'd
reti
.lagFrame
pop bc
pop af
reti