; variables for safe transfer async function DEF vSafeCopySource EQU $ff80 DEF vSafeCopyDest EQU vSafeCopySource + 2 DEF vSafeCopyCount EQU vSafeCopyDest + 2 ; check this for safe transfer being complete DEF vSafeCopyOriginalCount EQU vSafeCopyCount + 2 ; stash previous interrupt state before using the interrupts DEF vSafeCopyLYC EQU vSafeCopyOriginalCount + 2 ; stashes $FF45, the LYC register DEF vSafeCopySTAT EQU vSafeCopyLYC + 1 ; stashes $FF41, the STAT register DEF vSafeCopyInterrupt EQU vSafeCopySTAT + 1 ; stashes the previous LCD interrupt DEF vSafeCopyInterruptEnable EQU vSafeCopyInterrupt + 2 ; stashes $FFFF, which interrupts are enabled CopyRangeSafe: ; hl is source ; de is destination ; bc is length to copy ; copy an array of bytes to a destination in memory. ; this is completely different from CopyRangeUnsafe. ; this initiates an asynchronous, interrupt-driven copy of BC bytes of memory ; from HL to DE. ; it may return a memory address to look at for progress of the transfer, ; or else it's just going to do it with a hard-coded address. ; check the transfer status address for zero; when it's zero, the transfer is done! ; this works by using the STAT interrupt in LYC mode to interrupt its own execution at ; two scanlines inside the vblank interval to know when to start and stop. ; stash arguments in memory di ld a, l ldh [vSafeCopySource], a ld a, h ldh [vSafeCopySource+1], a ld a, e ldh [vSafeCopyDest], a ld a, d ldh [vSafeCopyDest+1], a ld a, c ldh [vSafeCopyCount], a ld a, b ldh [vSafeCopyCount+1], a ; stash interrupt state in memory ldh a, [$ff41] ldh [vSafeCopySTAT], a ; stashes $FF41, the STAT register ldh a, [$ff45] ldh [vSafeCopyLYC], a ; stashes $FF45, the LYC register ld a, [INTERRUPT_LCD] ld [vSafeCopyInterrupt], a ld a, [INTERRUPT_LCD+1] ld [vSafeCopyInterrupt+1], a ; stashes the current STAT interrupt handler ld a, [$ffff] and a, %0000_0010 ld [vSafeCopyInterruptEnable], a ; stashes whether LCD interrupt are enabled ld hl, CopyRangeSafe_EnterSafeMode ld a, l ld [INTERRUPT_LCD], a ld a, h ld [INTERRUPT_LCD + 1], a; set interrupt handler to "ENTER SAFE MODE" ld a, 143 ; CHANGE ME TO ADJUST SAFE TRANSFER TIMING ld [$ff45], a ld hl, $ffff set 1, [hl] ld a, %0100_0000 ld [$ff41], a ld hl, vSafeCopyCount ld a, 0 ld [$ff0f], a nop nop nop ei nop nop nop ret ; return address of bytes remaining to copy CopyRangeSafe_EnterSafeMode: push hl push bc push de push af ld hl, CopyRangeSafe_ExitSafeMode ld a, l ld [INTERRUPT_LCD], a ld a, h ld [INTERRUPT_LCD+1], a ld a, 153 ; CHANGE ME TO ADJUST SAFE TRANSFER TIMING ld [$ff45], a ; set lcd interrupt handler to EXIT SAFE MODE on line 153 ldh a, [vSafeCopySource] ld l, a ldh a, [vSafeCopySource+1] ld h, a ; fetch the source ldh a, [vSafeCopyDest] ld e, a ldh a, [vSafeCopyDest+1] ld d, a ; fetch the dest ldh a, [vSafeCopyCount] ld c, a ldh a, [vSafeCopyCount+1] ld b, a ; fetch the count ; before starting transfer, make sure the zero flag is false. ld a, 1 cp a, 0 CopyRangeSafe_TransferLoop: ei nop ; ei only sets the flag one instruction later apparently. safety nop! di ; process interrupts jp z, CopyRangeSafe_CleanUp ; zero flag will only be set if the exitsafemode handler fired ld a, [hl+] ld [de], a ; inc de dec bc ld a, b or a, c jp nz, CopyRangeSafe_TransferLoop jp CopyRangeSafe_Done CopyRangeSafe_ExitSafeMode: ld a, 0 cp a, 0 ; set the zero flag, which we're using as a signal to stop transferring reti ; set a to zero and set the zero flag true. now the transfer loop will end CopyRangeSafe_CleanUp: ld a, l ldh [vSafeCopySource], a ld a, h ldh [vSafeCopySource+1], a ; store new source ld a, e ldh [vSafeCopyDest], a ld a, d ldh [vSafeCopyDest+1], a ; store new dest ld a, c ldh [vSafeCopyCount], a ld a, b ldh [vSafeCopyCount+1], a ; store new count ld hl, CopyRangeSafe_EnterSafeMode ld a, l ld [INTERRUPT_LCD], a ld a, h ld [INTERRUPT_LCD+1], a ld a, 143 ; CHANGE ME TO ADJUST SAFE TRANSFER TIMING ld [$ff45], a ; set lcd interrupt handler to ENTER SAFE MODE on line 148 pop af pop de pop bc pop hl reti ; we're done with this memcpy cycle so we return from interrupt. CopyRangeSafe_Done: ; called when the complete transfer is finished, ; this restores interrupts to how they were. ; stash interrupt state in memory ld a, l ldh [vSafeCopySource], a ld a, h ldh [vSafeCopySource+1], a ; store new source ld a, e ldh [vSafeCopyDest], a ld a, d ldh [vSafeCopyDest+1], a ; store new dest ld a, c ldh [vSafeCopyCount], a ld a, b ldh [vSafeCopyCount+1], a ; store new count ld hl, CopyRangeSafe_EnterSafeMode ld a, l ld [INTERRUPT_LCD], a ld a, h ld [INTERRUPT_LCD+1], a ld a, 143 ; CHANGE ME TO ADJUST SAFE TRANSFER TIMING ld [$ff45], a ; set lcd interrupt handler to ENTER SAFE MODE on line 148 pop af pop de pop bc pop hl ldh a, [vSafeCopySTAT] ldh [$ff41], a ldh a, [vSafeCopyLYC] ldh [$ff45], a ldh a, [vSafeCopyInterrupt] ld [INTERRUPT_LCD], a ldh a, [vSafeCopyInterrupt+1] ld [INTERRUPT_LCD+1], a ld hl, $ffff ld a, [hl] cpl set 1, a cpl ; turn off the lcd interrupt enable ld [hl], a ld a, [vSafeCopyInterruptEnable] cp a, 0 ; if the stashed enable was 0 return. if the stashed enable was 1 then turn it on jp z, CopyRangeSafe_Return set 1, [hl] ; turn on the lcd interrupt CopyRangeSafe_Return: reti CopyRangeUnsafe: ; hl is source ; de is destination ; bc is length to copy ld a, [hl+] ld [de], a inc de dec bc ld a, b or a, c ; check if bc is zero jp nz, CopyRangeUnsafe ret CopyRangeUnsafeBy8s: ; hl is source ; de is destination ; bc is length to copy ld a, [hl+] ld [de], a inc de ld a, [hl+] ld [de], a inc de ld a, [hl+] ld [de], a inc de ld a, [hl+] ld [de], a inc de ld a, [hl+] ld [de], a inc de ld a, [hl+] ld [de], a inc de ld a, [hl+] ld [de], a inc de ld a, [hl+] ld [de], a inc de dec bc ld a, b or a, c ; check if bc is zero jp nz, CopyRangeUnsafeBy8s ret