| Preseason transfer engine. Initial pure pricing and record CRC exports.
| Price/CRC return D0.L; all other registers and original IRQ/X are preserved.
| N/Z follow D0, V/C clear. Invalid UID/even address returnsFFFFFFFF.
    .equ PRICES,0x239500
    .equ POSITIONS,0x239B80
    .equ MAPPER,0xA130F1
    .text
    .globl _start
_start:
    bra.w price
    bra.w crc_contiguous
    bra.w crc_even
    bra.w validate_contiguous
    bra.w validate_even
    bra.w select_even
    bra.w load_even
    bra.w save_even
    bra.w transact
price:
    move.w %sr,-(%sp)
    movem.l %d1/%a0,-(%sp)
    andi.l #0xFFFF,%d0
    cmpi.w #831,%d0
    bhi.s price_bad
    lea POSITIONS,%a0
    cmpi.b #0xFF,(%a0,%d0.w)
    beq.s price_bad
    add.w %d0,%d0
    lea PRICES,%a0
    moveq #0,%d1
    move.w (%a0,%d0.w),%d1
    move.l %d1,%d0
    bra.s price_done
price_bad:
    moveq #-1,%d0
price_done:
    movem.l (%sp)+,%d1/%a0
    move.w (%sp)+,%sr
    tst.l %d0
    rts
crc_contiguous:
    move.w %sr,-(%sp)
    movem.l %d1-%d7/%a0-%a2,-(%sp)
    moveq #1,%d7
    moveq #0,%d6
    bra.s crc_start
crc_even:
    move.w %sr,-(%sp)
    movem.l %d1-%d7/%a0-%a2,-(%sp)
    moveq #2,%d7
    moveq #0,%d6
    cmpa.l #0x204000,%a0
    blo.w crc_bad
    cmpa.l #0x209000,%a0
    bhi.w crc_bad
    move.l %a0,%d0
    andi.l #0xFFF,%d0
    bne.w crc_bad
    move.w #0x2700,%sr
    bsr.w wait_dma
    move.b #1,MAPPER
    moveq #1,%d6
crc_start:
    adda.w %d7,%a0                 | commit byte0 is excluded
    move.w #0xFFFF,%d2
    moveq #1,%d3
crc_byte:
    moveq #0,%d1
    move.b (%a0),%d1
    adda.w %d7,%a0
    cmpi.w #16,%d3
    blo.s crc_use
    cmpi.w #20,%d3
    blo.s crc_next                 | stored checksum and inverse are excluded
crc_use:
    lsl.w #8,%d1
    eor.w %d1,%d2
    moveq #7,%d4
crc_bit:
    add.w %d2,%d2
    bcc.s crc_no_poly
    eori.w #0x1021,%d2
crc_no_poly:
    dbra %d4,crc_bit
crc_next:
    addq.w #1,%d3
    cmpi.w #2048,%d3
    blo.s crc_byte
    moveq #0,%d0
    move.w %d2,%d0
    bra.s crc_done
crc_bad:
    moveq #-1,%d0
crc_done:
    tst.w %d6
    beq.s crc_unmapped
    move.b #0,MAPPER
crc_unmapped:
    movem.l (%sp)+,%d1-%d7/%a0-%a2
    move.w (%sp)+,%sr
    tst.l %d0
    rts

| Validate a complete2048-byte record. D0.W expectedleague1..3,A0 source.
| Result D0.L1valid/0invalid; original GPRs(exceptD0),IRQ/X/SP restored.
    .equ RANKS,0x23AF08
    .equ CURRENCY,0x23AF00
    .equ CATALOGUE_CRC,0x23AF04
validate_contiguous:
    move.w %sr,-(%sp)
    movem.l %d1-%d7/%a0-%a6,-(%sp)
    moveq #1,%d7
    bra.s record_setup
validate_even:
    move.w %sr,-(%sp)
    movem.l %d1-%d7/%a0-%a6,-(%sp)
    moveq #2,%d7
record_setup:
    movea.l %a0,%a4
    move.w %d0,%d6
    lea -160(%sp),%sp
    movea.l %sp,%a5
    movea.l %sp,%a0
    moveq #39,%d1
record_clear:
    clr.l (%a0)+
    dbra %d1,record_clear
    cmpi.w #1,%d6
    blo.w record_bad
    cmpi.w #3,%d6
    bhi.w record_bad
    movea.l %a4,%a0
    cmpi.w #2,%d7
    beq.s record_even_crc
    bsr.w crc_contiguous
    bra.s record_crc_done
record_even_crc:
    move.w #0x2700,%sr
    bsr.w wait_dma
    bsr.w crc_even
record_crc_done:
    tst.l %d0
    bmi.w record_bad
    move.w %d0,%d5
    cmpi.w #2,%d7
    bne.s record_header
    move.l %a4,%d0
    subi.l #0x204000,%d0
    lsr.l #8,%d0
    lsr.l #5,%d0
    addq.w #1,%d0
    cmp.w %d6,%d0
    bne.w record_bad
    move.b #1,MAPPER
    move.w #1,142(%a5)
record_header:
    movea.l %a4,%a0
    bsr.w record_read_long
    cmpi.l #0x54524E46,%d0
    bne.w record_bad
    bsr.w record_read_byte
    cmpi.w #1,%d0
    bne.w record_bad
    bsr.w record_read_byte
    cmp.w %d6,%d0
    bne.w record_bad
    bsr.w record_read_byte
    cmpi.w #1,%d0
    bhi.w record_bad
    move.w %d0,%d2
    bsr.w record_read_byte
    cmp.w %d2,%d0                 | Ready cannot be set when disabled
    bhi.w record_bad
    bsr.w record_read_long
    move.l %d0,%d1
    bsr.w record_read_long
    not.l %d0
    cmp.l %d1,%d0
    bne.w record_bad
    bsr.w record_read_word
    cmp.w %d5,%d0
    bne.w record_bad
    bsr.w record_read_word
    eori.w #0xFFFF,%d0
    cmp.w %d5,%d0
    bne.w record_bad
    bsr.w record_read_long
    cmp.l CURRENCY,%d0
    bne.w record_bad
    bsr.w record_read_word
    cmpi.w #831,%d0
    bne.w record_bad
    bsr.w record_read_word
    cmpi.w #2,%d0
    bne.w record_bad
    bsr.w record_read_long
    cmp.l CATALOGUE_CRC,%d0
    bne.w record_bad
    movea.l %a0,%a2               | first roster UID
    movea.l %a4,%a3
    move.w #1824,%d0
    mulu.w %d7,%d0
    adda.l %d0,%a3               | stored six-position count bytes per club
    lea POSITIONS,%a6
    moveq #0,%d3                  | owned-price sum, then total currency
    moveq #0,%d6                  | club0..31
record_club:
    lea 104(%a5),%a0
    moveq #5,%d1
record_clear_counts:
    clr.w (%a0)+
    dbra %d1,record_clear_counts
    clr.w 120(%a5)                | actual roster count
    clr.w 122(%a5)                | trailing empty slots reached
    move.w #-1,124(%a5)          | previous global roster rank
    clr.w 126(%a5)                | slot0..25
record_uid:
    movea.l %a2,%a0
    bsr.w record_read_word
    movea.l %a0,%a2
    cmpi.w #0xFFFF,%d0
    beq.w record_hole
    tst.w 122(%a5)
    bne.w record_bad
    cmpi.w #831,%d0
    bhi.w record_bad
    moveq #0,%d4
    move.b (%a6,%d0.w),%d4
    cmpi.w #5,%d4
    bhi.w record_bad              | also rejects the duplicate-identity aliasFF
    move.w %d0,%d1
    andi.w #7,%d1
    move.w %d0,%d5
    lsr.w #3,%d5
    bset %d1,(%a5,%d5.w)
    bne.w record_bad              | global duplicate ownership
    move.w %d0,%d5
    add.w %d5,%d5
    lea RANKS,%a1
    move.w (%a1,%d5.w),%d1
    cmp.w 124(%a5),%d1
    ble.w record_bad              | sorted positions/overall/tie order
    move.w %d1,124(%a5)
    lea PRICES,%a1
    moveq #0,%d1
    move.w (%a1,%d5.w),%d1
    add.l %d1,%d3
    add.w %d4,%d4
    addq.w #1,104(%a5,%d4.w)
    addq.w #1,120(%a5)
    bra.s record_next_uid
record_hole:
    move.w #1,122(%a5)
record_next_uid:
    addq.w #1,126(%a5)
    cmpi.w #26,126(%a5)
    blo.w record_uid
    cmpi.w #20,120(%a5)
    blo.w record_bad
    cmpi.w #2,104(%a5)
    blo.w record_bad
    cmpi.w #3,104(%a5)
    bhi.w record_bad
    cmpi.w #6,114(%a5)
    blo.w record_bad
    move.w 106(%a5),%d0
    add.w 108(%a5),%d0
    add.w 110(%a5),%d0
    add.w 112(%a5),%d0
    cmpi.w #12,%d0
    blo.w record_bad
    moveq #0,%d2
record_counts:
    movea.l %a3,%a0
    bsr.w record_read_byte
    movea.l %a0,%a3
    move.w %d2,%d1
    add.w %d1,%d1
    cmp.w 104(%a5,%d1.w),%d0
    bne.w record_bad
    addq.w #1,%d2
    cmpi.w #6,%d2
    blo.s record_counts
    addq.w #1,%d6
    cmpi.w #32,%d6
    blo.w record_club
    movea.l %a4,%a0
    move.w #1696,%d0
    mulu.w %d7,%d0
    adda.l %d0,%a0
    moveq #31,%d2
record_budget:
    bsr.w record_read_long
    cmp.l CURRENCY,%d0
    bhi.w record_bad
    add.l %d0,%d3
    dbra %d2,record_budget
    cmp.l CURRENCY,%d3
    bne.w record_bad
    movea.l %a4,%a0
    move.w #2016,%d0
    mulu.w %d7,%d0
    adda.l %d0,%a0
    moveq #31,%d2
record_padding:
    bsr.w record_read_byte
    tst.w %d0
    bne.s record_bad
    dbra %d2,record_padding
    moveq #1,%d0
    bra.s record_done
record_bad:
    moveq #0,%d0
record_done:
    tst.w 142(%a5)
    beq.s record_unmapped
    move.b #0,MAPPER
record_unmapped:
    lea 160(%sp),%sp
    movem.l (%sp)+,%d1-%d7/%a0-%a6
    move.w (%sp)+,%sr
    tst.l %d0
    rts
record_read_byte:
    moveq #0,%d0
    move.b (%a0),%d0
    adda.w %d7,%a0
    rts
record_read_word:
    moveq #0,%d0
record_read_low_word:
    move.b (%a0),%d0
    adda.w %d7,%a0
    lsl.w #8,%d0
    move.b (%a0),%d0
    adda.w %d7,%a0
    rts
record_read_long:
    bsr.s record_read_word
    swap %d0
    bsr.s record_read_low_word
    rts

| SRAM aliases native animation ROM. All mapping is IRQ-protected AND waits
| for an already active VDP DMA to complete before exposing battery memory.
| Caller uses these blocking routines only in the transfer/preload modal.
wait_dma:
    btst #1,0xC00005
    bne.s wait_dma
    rts

    .include "transfers_store.s"
    .include "transfers_tx.s"
