summaryrefslogtreecommitdiff
path: root/src/home/serial.asm
diff options
context:
space:
mode:
Diffstat (limited to 'src/home/serial.asm')
-rw-r--r--src/home/serial.asm672
1 files changed, 672 insertions, 0 deletions
diff --git a/src/home/serial.asm b/src/home/serial.asm
new file mode 100644
index 0000000..97a9dc8
--- /dev/null
+++ b/src/home/serial.asm
@@ -0,0 +1,672 @@
+; called at roughly 240Hz by TimerHandler
+SerialTimerHandler: ; 0c91 (0:0c91)
+ ld a, [wSerialOp]
+ cp $29
+ jr z, .begin_transfer
+ cp $12
+ jr z, .check_for_timeout
+ ret
+.begin_transfer
+ ldh a, [rSC] ;
+ add a ; make sure that no serial transfer is active
+ ret c ;
+ ld a, SC_INTERNAL
+ ldh [rSC], a ; use internal clock
+ ld a, SC_START | SC_INTERNAL
+ ldh [rSC], a ; use internal clock, set transfer start flag
+ ret
+.check_for_timeout
+ ; sets bit7 of [wSerialFlags] if the serial interrupt hasn't triggered
+ ; within four timer interrupts (60Hz)
+ ld a, [wSerialCounter]
+ ld hl, wSerialCounter2
+ cp [hl]
+ ld [hl], a
+ ld hl, wSerialTimeoutCounter
+ jr nz, .clear_timeout_counter
+ inc [hl]
+ ld a, [hl]
+ cp 4
+ ret c
+ ld hl, wSerialFlags
+ set 7, [hl]
+ ret
+.clear_timeout_counter
+ ld [hl], $0
+ ret
+
+Func_0cc5: ; 0cc5 (0:0cc5)
+ ld hl, wSerialRecvCounter
+ or a
+ jr nz, .asm_cdc
+ ld a, [hl]
+ or a
+ ret z
+ ld [hl], $00
+ ld a, [wSerialRecvBuf]
+ ld e, $12
+ cp $29
+ jr z, .asm_cfa
+ xor a
+ scf
+ ret
+.asm_cdc
+ ld a, $29
+ ldh [rSB], a
+ ld a, SC_INTERNAL
+ ldh [rSC], a
+ ld a, SC_START | SC_INTERNAL
+ ldh [rSC], a
+.asm_ce8
+ ld a, [hl]
+ or a
+ jr z, .asm_ce8
+ ld [hl], $00
+ ld a, [wSerialRecvBuf]
+ ld e, $29
+ cp $12
+ jr z, .asm_cfa
+ xor a
+ scf
+ ret
+.asm_cfa
+ xor a
+ ld [wSerialSendBufIndex], a
+ ld [wcb80], a
+ ld [wSerialSendBufToggle], a
+ ld [wSerialSendSave], a
+ ld [wcba3], a
+ ld [wSerialRecvIndex], a
+ ld [wSerialRecvCounter], a
+ ld [wSerialLastReadCA], a
+ ld a, e
+ cp $29
+ jr nz, .asm_d21
+ ld bc, $800
+.asm_d1b
+ dec bc
+ ld a, c
+ or b
+ jr nz, .asm_d1b
+ ld a, e
+.asm_d21
+ ld [wSerialOp], a
+ scf
+ ret
+
+SerialHandler: ; 0d26 (0:0d26)
+ push af
+ push hl
+ push de
+ push bc
+ ld a, [wce63] ;
+ or a ;
+ jr z, .asm_d35 ; if [wce63] nonzero:
+ call Func_3189 ; ?
+ jr .done ; return
+.asm_d35
+ ld a, [wSerialOp] ;
+ or a ;
+ jr z, .asm_d55 ; skip ahead if [wSerialOp] zero
+ ; send/receive a byte
+ ldh a, [rSB]
+ call SerialHandleRecv
+ call SerialHandleSend ; returns byte to actually send
+ push af
+.wait_for_completion
+ ldh a, [rSC]
+ add a
+ jr c, .wait_for_completion
+ pop af
+ ; end send/receive
+ ldh [rSB], a ; prepare sending byte (from Func_0dc8?)
+ ld a, [wSerialOp]
+ cp $29
+ jr z, .done ; if [wSerialOp] != $29, use external clock
+ jr .asm_d6a ; and prepare for next byte. either way, return
+.asm_d55
+ ld a, $1
+ ld [wSerialRecvCounter], a
+ ldh a, [rSB]
+ ld [wSerialRecvBuf], a
+ ld a, $ac
+ ldh [rSB], a
+ ld a, [wSerialRecvBuf]
+ cp $12 ; if [wSerialRecvBuf] != $12, use external clock
+ jr z, .done ; and prepare for next byte. either way, return
+.asm_d6a
+ ld a, SC_START | SC_EXTERNAL
+ ldh [rSC], a ; transfer start, use external clock
+.done
+ ld hl, wSerialCounter
+ inc [hl]
+ pop bc
+ pop de
+ pop hl
+ pop af
+ reti
+
+; handles a byte read from serial transfer by decoding it and storing it into
+; the receive buffer
+SerialHandleRecv: ; 0d77 (0:0d77)
+ ld hl, wSerialLastReadCA
+ ld e, [hl]
+ dec e
+ jr z, .last_was_ca
+ cp $ac
+ ret z ; return if read_data == $ac
+ cp $ca
+ jr z, .read_ca
+ or a
+ jr z, .read_00_or_ff
+ cp $ff
+ jr nz, .read_data
+.read_00_or_ff
+ ld hl, wSerialFlags
+ set 6, [hl]
+ ret
+.read_ca
+ inc [hl] ; inc [wSerialLastReadCA]
+ ret
+.last_was_ca
+ ; if last byte read was $ca, flip all bits of data received
+ ld [hl], $0
+ cpl
+ jr .handle_byte
+.read_data
+ ; flip top2 bits of data received
+ xor $c0
+.handle_byte
+ push af
+ ld a, [wSerialRecvIndex]
+ ld e, a
+ ld a, [wcba3]
+ dec a
+ and $1f
+ cp e
+ jr z, .set_flag_and_return
+ ld d, $0
+ ; store into receive buffer
+ ld hl, wSerialRecvBuf
+ add hl, de
+ pop af
+ ld [hl], a
+ ; increment buffer index (mod 32)
+ ld a, e
+ inc a
+ and $1f
+ ld [wSerialRecvIndex], a
+ ; increment received bytes counter & clear flags
+ ld hl, wSerialRecvCounter
+ inc [hl]
+ xor a
+ ld [wSerialFlags], a
+ ret
+.set_flag_and_return
+ pop af
+ ld hl, wSerialFlags
+ set 0, [hl]
+ ret
+
+; prepares a byte to send over serial transfer, either from the send-save byte
+; slot or the send buffer
+SerialHandleSend: ; 0dc8 (0:0dc8)
+ ld hl, wSerialSendSave
+ ld a, [hl]
+ or a
+ jr nz, .send_saved
+ ld hl, wSerialSendBufToggle
+ ld a, [hl]
+ or a
+ jr nz, .send_buf
+ ; no more data--send $ac to indicate this
+ ld a, $ac
+ ret
+.send_saved
+ ld a, [hl]
+ ld [hl], $0
+ ret
+.send_buf
+ ; grab byte to send from send buffer, increment buffer index
+ ; and decrement to-send length
+ dec [hl]
+ ld a, [wSerialSendBufIndex]
+ ld e, a
+ ld d, $0
+ ld hl, wSerialSendBuf
+ add hl, de
+ inc a
+ and $1f
+ ld [wSerialSendBufIndex], a
+ ld a, [hl]
+ ; flip top2 bits of sent data
+ xor $c0
+ cp $ac
+ jr z, .send_escaped
+ cp $ca
+ jr z, .send_escaped
+ cp $ff
+ jr z, .send_escaped
+ or a
+ jr z, .send_escaped
+ ret
+.send_escaped
+ ; escape tricky data by prefixing it with $ca and flipping all bits
+ ; instead of just top2
+ xor $c0
+ cpl
+ ld [wSerialSendSave], a
+ ld a, $ca
+ ret
+
+; store byte at a in wSerialSendBuf for sending
+SerialSendByte: ; 0e0a (0:0e0a)
+ push hl
+ push de
+ push bc
+ push af
+.asm_e0e
+ ld a, [wcb80]
+ ld e, a
+ ld a, [wSerialSendBufIndex]
+ dec a
+ and $1f
+ cp e
+ jr z, .asm_e0e
+ ld d, $0
+ ld a, e
+ inc a
+ and $1f
+ ld [wcb80], a
+ ld hl, wSerialSendBuf
+ add hl, de
+ pop af
+ ld [hl], a
+ ld hl, wSerialSendBufToggle
+ inc [hl]
+ pop bc
+ pop de
+ pop hl
+ ret
+
+; sets carry if [wSerialRecvCounter] nonzero
+Func_0e32: ; 0e32 (0:0e32)
+ ld a, [wSerialRecvCounter]
+ or a
+ ret z
+ scf
+ ret
+
+; receive byte in wSerialRecvBuf
+SerialRecvByte: ; 0e39 (0:0e39)
+ push hl
+ ld hl, wSerialRecvCounter
+ ld a, [hl]
+ or a
+ jr nz, .asm_e49
+ pop hl
+ ld a, [wSerialFlags]
+ or a
+ ret nz
+ scf
+ ret
+.asm_e49
+ push de
+ dec [hl]
+ ld a, [wcba3]
+ ld e, a
+ ld d, $0
+ ld hl, wSerialRecvBuf
+ add hl, de
+ ld a, [hl]
+ push af
+ ld a, e
+ inc a
+ and $1f
+ ld [wcba3], a
+ pop af
+ pop de
+ pop hl
+ or a
+ ret
+
+; exchange c bytes. send bytes at hl and store received bytes in de
+SerialExchangeBytes: ; 0e63 (0:0e63)
+ ld b, c
+.asm_e64
+ ld a, b
+ sub c
+ jr c, .asm_e6c
+ cp $1f
+ jr nc, .asm_e75
+.asm_e6c
+ inc c
+ dec c
+ jr z, .asm_e75
+ ld a, [hli]
+ call SerialSendByte
+ dec c
+.asm_e75
+ inc b
+ dec b
+ jr z, .asm_e81
+ call SerialRecvByte
+ jr c, .asm_e81
+ ld [de], a
+ inc de
+ dec b
+.asm_e81
+ ld a, [wSerialFlags]
+ or a
+ jr nz, .asm_e8c
+ ld a, c
+ or b
+ jr nz, .asm_e64
+ ret
+.asm_e8c
+ scf
+ ret
+
+; go into slave mode (external clock) for serial transfer?
+Func_0e8e: ; 0e8e (0:0e8e)
+ call ClearSerialData
+ ld a, $12
+ ldh [rSB], a ; send $12
+ ld a, SC_START | SC_EXTERNAL
+ ldh [rSC], a ; use external clock, set transfer start flag
+ ldh a, [rIF]
+ and ~(1 << INT_SERIAL)
+ ldh [rIF], a ; clear serial interrupt flag
+ ldh a, [rIE]
+ or 1 << INT_SERIAL ; enable serial interrupt
+ ldh [rIE], a
+ ret
+
+; disable serial interrupt, and clear rSB, rSC, and serial registers in WRAM
+ResetSerial: ; 0ea6 (0:0ea6)
+ ldh a, [rIE]
+ and ~(1 << INT_SERIAL)
+ ldh [rIE], a
+ xor a
+ ldh [rSB], a
+ ldh [rSC], a
+; fallthrough
+
+; zero serial registers in WRAM
+ClearSerialData: ; 0eb1 (0:0eb1)
+ ld hl, wSerialOp
+ ld bc, wSerialEnd - wSerialOp
+.loop
+ xor a
+ ld [hli], a
+ dec bc
+ ld a, c
+ or b
+ jr nz, .loop
+ ret
+
+; store bc bytes from hl in wSerialSendBuf for sending
+SerialSendBytes: ; 0ebf (0:0ebf)
+ push bc
+.send_loop
+ ld a, [hli]
+ call SerialSendByte
+ ld a, [wSerialFlags]
+ or a
+ jr nz, .done
+ dec bc
+ ld a, c
+ or b
+ jr nz, .send_loop
+ pop bc
+ or a
+ ret
+.done
+ pop bc
+ scf
+ ret
+
+; receive bc bytes in wSerialRecvBuf and save them to hl
+SerialRecvBytes: ; 0ed5 (0:0ed5)
+ push bc
+.recv_loop
+ call SerialRecvByte
+ jr nc, .save_byte
+ halt
+ nop
+ jr .recv_loop
+.save_byte
+ ld [hli], a
+ ld a, [wSerialFlags]
+ or a
+ jr nz, .done
+ dec bc
+ ld a, c
+ or b
+ jr nz, .recv_loop
+ pop bc
+ or a
+ ret
+.done
+ pop bc
+ scf
+ ret
+
+Func_0ef1: ; 0ef1 (0:0ef1)
+ ld de, wcb79
+ ld hl, sp+$fe
+ ld a, l
+ ld [de], a
+ inc de
+ ld a, h
+ ld [de], a
+ inc de
+ pop hl
+ push hl
+ ld a, l
+ ld [de], a
+ inc de
+ ld a, h
+ ld [de], a
+ or a
+ ret
+
+Func_0f05: ; 0f05 (0:0f05)
+ push hl
+ ld hl, wcb7b
+ ld a, [hli]
+ or [hl]
+ pop hl
+ ret z
+ ld hl, wcb79
+ ld a, [hli]
+ ld h, a
+ ld l, a
+ ld sp, hl
+ ld hl, wcb7b
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ push hl
+ scf
+ ret
+
+Func_0f1d: ; 0f1d (0:0f1d)
+ ld a, [wSerialFlags]
+ or a
+ jr nz, .asm_f27
+ call Func_0e32
+ ret nc
+.asm_f27
+ ld a, $01
+ call BankswitchROM
+ ld hl, wcbf7
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld sp, hl
+ scf
+ ret
+
+; load the number at wSerialFlags (error code?) to TxRam3, print
+; TransmissionErrorText, exit the duel, and reset serial registers.
+DuelTransmissionError: ; 0f35 (0:0f35)
+ ld a, [wSerialFlags]
+ ld l, a
+ ld h, 0
+ call LoadTxRam3
+ ldtx hl, TransmissionErrorText
+ call DrawWideTextBox_WaitForInput
+ ld a, -1
+ ld [wDuelResult], a
+ ld hl, wDuelReturnAddress
+ ld a, [hli]
+ ld h, [hl]
+ ld l, a
+ ld sp, hl
+ xor a
+ call PlaySong
+ call ResetSerial
+ ret
+
+; exchange RNG during a link duel between both games
+ExchangeRNG: ; 0f58 (0:0f58)
+ ld a, [wDuelType]
+ cp DUELTYPE_LINK
+ jr z, .link_duel
+ ret
+.link_duel
+ ld a, DUELVARS_DUELIST_TYPE
+ call GetTurnDuelistVariable
+ or a ; cp DUELIST_TYPE_PLAYER
+ jr z, .player_turn
+; link opponent's turn
+ ld hl, wOppRNG1
+ ld de, wRNG1
+ jr .exchange
+.player_turn
+ ld hl, wRNG1
+ ld de, wOppRNG1
+.exchange
+ ld c, 3 ; wRNG1, wRNG2, and wRNGCounter
+ call SerialExchangeBytes
+ jp c, DuelTransmissionError
+ ret
+
+; sets hOppActionTableIndex to an AI action specified in register a.
+; send 10 bytes of data to the other game from hOppActionTableIndex, hTempCardIndex_ff9f,
+; hTemp_ffa0, and hTempPlayAreaLocation_ffa1, and hTempRetreatCostCards.
+; finally exchange RNG data.
+; the receiving side will use this data to read the OPPACTION_* value in
+; [hOppActionTableIndex] and match it by calling the corresponding OppAction* function
+SetOppAction_SerialSendDuelData: ; 0f7f (0:0f7f)
+ push hl
+ push bc
+ ldh [hOppActionTableIndex], a
+ ld a, DUELVARS_DUELIST_TYPE
+ call GetNonTurnDuelistVariable
+ cp DUELIST_TYPE_LINK_OPP
+ jr nz, .not_link
+ ld hl, hOppActionTableIndex
+ ld bc, 10
+ call SerialSendBytes
+ call ExchangeRNG
+.not_link
+ pop bc
+ pop hl
+ ret
+
+; receive 10 bytes of data from wSerialRecvBuf and store them into hOppActionTableIndex,
+; hTempCardIndex_ff9f, hTemp_ffa0, and hTempPlayAreaLocation_ffa1,
+; and hTempRetreatCostCards. also exchange RNG data.
+SerialRecvDuelData: ; 0f9b (0:0f9b)
+ push hl
+ push bc
+ ld hl, hOppActionTableIndex
+ ld bc, 10
+ call SerialRecvBytes
+ call ExchangeRNG
+ pop bc
+ pop hl
+ ret
+
+; serial send 8 bytes at f, a, l, h, e, d, c, b
+; only during a duel against a link opponent
+SerialSend8Bytes: ; 0fac (0:0fac)
+ push hl
+ push af
+ ld a, DUELVARS_DUELIST_TYPE
+ call GetNonTurnDuelistVariable
+ cp DUELIST_TYPE_LINK_OPP
+ jr z, .link
+ pop af
+ pop hl
+ ret
+
+.link
+ pop af
+ pop hl
+ push af
+ push hl
+ push de
+ push bc
+ push de
+ push hl
+ push af
+ ld hl, wTempSerialBuf
+ pop de
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ inc hl
+ pop de
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ inc hl
+ pop de
+ ld [hl], e
+ inc hl
+ ld [hl], d
+ inc hl
+ ld [hl], c
+ inc hl
+ ld [hl], b
+ ld hl, wTempSerialBuf
+ ld bc, 8
+ call SerialSendBytes
+ jp c, DuelTransmissionError
+ pop bc
+ pop de
+ pop hl
+ pop af
+ ret
+
+; serial recv 8 bytes to f, a, l, h, e, d, c, b
+SerialRecv8Bytes: ; 0fe9 (0:0fe9)
+ ld hl, wTempSerialBuf
+ ld bc, 8
+ push hl
+ call SerialRecvBytes
+ jp c, DuelTransmissionError
+ pop hl
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ inc hl
+ push de
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ inc hl
+ push de
+ ld e, [hl]
+ inc hl
+ ld d, [hl]
+ inc hl
+ ld c, [hl]
+ inc hl
+ ld b, [hl]
+ pop hl
+ pop af
+ ret